feat: 建客服知识库(Milvus 三集合)并修复模型端点筛选缺陷
一、知识库建设 - 新增 tools/build_knowledge_chunks.py:把 knowledge/ 下文档切成可检索知识块。 采用「叶子标题」策略(其后没有更深标题的标题即切分点),同时覆盖三种真实结构: 带子条款的按子条款切、无子条款的条款单独成块、无小节的章整章成块。 第一版按固定标题级别切是失败的——适当性指南的条款是 ### 而没有 ####,产品手册的 ### 1.1 又不匹配「第X条」,两条规则互相打架,导致 4 个文件一块都没切出来。 - 新增 tools/load_knowledge_milvus.py:向量化并写入 Milvus,用 upsert 保证幂等。 schema 按方案 §4.2 统一字段,另加 chapter/section/source_file/doc_no/visibility 五个 检索与合规必需字段;索引 IVF_FLAT + COSINE + nlist=128;向量输入取「标题+正文」, 标题含条款号与章节名,是比正文更干净的检索信号。 - 知识内容按业务范围裁剪:反洗钱合规操作手册不入客服知识库(业务只做公募基金、 不涉及资金划付,且该手册标注内部机密、禁止向客户透露可疑交易信息),留给后续风控; 高净值客户服务规范只保留「客户分层标准」与「各层级专属权益」两章, 家族信托、资产配置流程、客户经理考核、隐私应急预案等内部管理章节不入库。 - 入库现状:fin_faq_collection 61 块、fin_product_collection 26 块、 fin_policy_collection 73 块,合计 160 块。检索自检 5/6——未命中的一条分数 0.660 落在中置信区间,按三档兜底策略本应提示信息可能不完整,属于预期行为。 二、embedding 端点 - 新增 tools/configure_embedding_endpoint.py:走管理 API(draft→approved→active) 配置并激活 qwen-embedding 端点,而不是直接写库。理由是状态机与审计都要留痕, 且 DatabaseModelGateway 只认 status='active',手工写错状态会报成与病因无关的 「模型端点未注册或未激活」。脚本先查 endpoint_code 是否已存在,幂等可重跑。 三、修复模型端点筛选缺陷(app/service/model_gateway.py) - 原 DatabaseModelEndpointResolver 忽略 agent_type 与 task_type、直接返回全部 active 端点,而 ModelDispatchService 只按顺序尝试前 max_attempts(默认 2)个。两者叠加使 「能否选到支持该任务的端点」取决于端点表顺序:实测每次 embedding 都先拿文本生成 端点失败一次再落到向量端点(0.61s,修复后 0.42s)。 - 新增 TASK_CAPABILITY 显式映射后按能力筛选。用映射而不是同名筛选是必需的: memory_extraction 并不是任何端点的能力名(deepseek 声明的是 text_generation 等), 按同名筛会得到空集、把记忆抽取打成失败关闭——这是本次修复最容易引入的回归。 - 保守兜底:未映射的 task_type、以及没有任何端点声明该能力时,都退回全部端点, 让配置缺口表现为调用失败,而不是让上层收到「解析为空」这种与病因无关的报错。 - 验证结果:embedding→[qwen-embedding]、intent_classification→[deepseek-flash]、 memory_extraction→[deepseek-flash]、未映射 task_type→全部;ruff 通过、 mypy 103 文件无错、unit+contract 447 passed。 四、需求文档提取物 - 新增 _flows/:三份流程文档(智能客服 Agent 专项设计方案、投资顾问流程、基金运营流程) 的纯文本提取,供开发期对照。原始 .docx/.html 保留在业务方目录侧。 说明:本次仅本地提交,未推送远程仓库。knowledge/ 内含公司内部制度与产品资料, 是否入远程库待确认。
This commit is contained in:
@@ -161,15 +161,46 @@ class DatabaseModelGateway:
|
||||
)
|
||||
|
||||
|
||||
# task_type → 端点必须声明的能力名。
|
||||
#
|
||||
# 为什么需要一张映射表而不是把 task_type 直接当能力名用:`memory_extraction` 并不是
|
||||
# 任何端点的能力名(deepseek 声明的是 text_generation/json_output/intent_classification),
|
||||
# 它需要的是「能生成结构化文本」的端点。若按同名筛选会得到空集,把记忆抽取打成
|
||||
# 失败关闭——这是修复端点筛选时最容易引入的回归。
|
||||
TASK_CAPABILITY: dict[str, str] = {
|
||||
"embedding": "embedding",
|
||||
"intent_classification": "intent_classification",
|
||||
"memory_extraction": "text_generation",
|
||||
"text_generation": "text_generation",
|
||||
}
|
||||
|
||||
|
||||
class DatabaseModelEndpointResolver:
|
||||
"""为统一意图分类提供当前已激活模型端点快照。"""
|
||||
"""按任务类型筛选当前已激活的模型端点。
|
||||
|
||||
修复的缺陷:原实现 `del agent_type, task_type` 后返回**全部** active 端点,而
|
||||
`ModelDispatchService` 只按顺序尝试前 `max_attempts`(默认 2)个端点。两者叠加的
|
||||
后果是「能不能选到支持该任务的端点」取决于端点在表里的顺序——实测表现为每次
|
||||
embedding 都先拿文本生成端点失败一次再落到真正的向量端点。
|
||||
|
||||
筛选策略保持保守:只在确实筛到端点时收窄,未映射的 task_type 与「一个都没声明
|
||||
该能力」的配置缺口都退回全部端点,让故障表现为调用失败而不是解析为空。
|
||||
"""
|
||||
|
||||
async def resolve(self, *, agent_type: str, task_type: str) -> list[ModelEndpointConfig]:
|
||||
del agent_type, task_type # 路由筛选由发布配置和 ModelRouterService 扩展。
|
||||
del agent_type # 按 agent_type 分配端点的路由仍由发布配置与 ModelRouterService 扩展。
|
||||
async with SessionFactory() as session:
|
||||
return list(await session.scalars(select(ModelEndpointConfig).where(
|
||||
endpoints = list(await session.scalars(select(ModelEndpointConfig).where(
|
||||
ModelEndpointConfig.status == "active"
|
||||
)))
|
||||
capability = TASK_CAPABILITY.get(task_type)
|
||||
if capability is None:
|
||||
return endpoints
|
||||
matched = [
|
||||
endpoint for endpoint in endpoints
|
||||
if isinstance(endpoint.capabilities, list) and capability in endpoint.capabilities
|
||||
]
|
||||
return matched or endpoints
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
|
||||
Reference in New Issue
Block a user