Commit Graph
2 Commits
Author SHA1 Message Date
lzf_0626 570493e71c feat(knowledge): 知识检索增加产品名的字面兜底召回
问题:客户问「季季盈90天的起投金额是多少」会被引导到人工客服,而知识库里明明有答案。
实测根因不是阈值拍错了,而是专有名词在 embedding 空间里不占优势——该问句的向量 top1
只有 0.6291,够不到 0.75 硬门槛,只能靠与次优的差值勉强通过;而同一次查询用
title like "%季季盈%" 是唯一命中 PROD-007。既然客户已经说出了产品名,就不该再赌相似度。

做法(三条边界都是实测逼出来的,不是设想):
1. 只对产品集合做字面匹配。客户问「季季盈90天的起投金额是多少」与通用 FAQ 标题
   「基金起投金额是多少?」有 7 个字连续重合;把 FAQ 纳入字面匹配会让它和真正的产品块
   一起拿到满分、差距归零,反而又退化成"转人工"。
2. 字面命中只在向量结果不够确定时采用。客户问「基金赎回几天到账」时向量已给出正确答案
   (FAQ-0016 得 0.8060),但手册章节标题「5.2 基金赎回流程」与问句也有 4 个字连续重合,
   无条件采纳会把"操作步骤"顶掉客户真正问的"到账时间"。
3. 重叠门槛取 6 字而不是 4 字:"基金赎回"这类业务动作词正好 4 字,会骗过 4 字门槛;
   产品名("南方季季盈90天")更长,6 字能同时保住产品名、挡住动作词。

未改动任何转人工判定阈值;VECTOR_CONFIDENT_SCORE 与 Agent 的 HIGH_SCORE 由单测锁定一致,
避免两处各自漂移出"谁都答不出来"的死角。

验证:季季盈类问法由"转人工"变为正确答出,基金赎回问法仍答 FAQ-0016;
ruff / mypy(113 文件) / 453 unit+contract / 29 integration 全绿。
2026-09-10 22:15:50 +08:00
lzf_0626 13bab7c3d0 feat: 客服 Agent 端到端跑通(知识直返 + 答不了引导人工客服)
按业务方确定的取向实现:金融场景确定性优先,能溯源到公司资料的才答,答不了就
引导客户拨打客服热线,绝不用模型猜答案。端到端验收 8/8 通过。

新增:
- app/service/knowledge_search_service.py:知识检索。未复用记忆的 VectorMemoryAdapter
  是因为它只返回 (memory_uuid, score),会丢掉知识块的标题与正文,而客服回答必须能把
  原文与出处一起交付。检索失败一律返回 degraded 而不抛异常,由 Agent 走兜底。
- app/service/knowledge_tool.py + app/core/knowledge_contracts.py:只读工具 search_knowledge。
  走 ToolExecutor 而不是让 Agent 直接持有检索服务,是为了让白名单、权限、审计、超时
  都归基座统一管理;工具只读也符合 ToolRegistry 的硬约束。复用既有权限码
  knowledge:reference:read(customer 角色已具备),不新增权限点。
- app/service/agent/implementations/customer_service.py:Agent 本体,刻意保持薄——
  意图分发 + 四条出口(faq/产品/政策直返、闲聊走模型、其余与异常引导人工)。
  直接返回知识原文而不经模型改写,答案的字面内容全部来自公司已发布资料。
- tools/publish_customer_service_config.py:发布意图工具白名单。
- tools/customer_service_check.py:端到端验收(8 个用例,含越界请求与知识库外问题)。

装配:
- bootstrap 新增 get_knowledge_search_service 工厂,注册 search_knowledge 工具与
  customer_service Agent。
- runtime_config_service 新增 load_active_prompt:提示词绑定 release_id,按当前生效
  版本读取,未发布时回落代码默认值。闲聊话术因此可审核、可回滚,不必改代码发版。

过程中发现并处理的三个问题:
1. 自造 source_references 被基座合规闸门拒绝。governance.review_output 只接受
   「本次召回的记忆」与「本次成功调用的工具」两类引用(用于防止伪造来源),
   knowledge 类型会被判非法并使整个 run 失败。处理方式是**不放开那道校验**,
   而把知识出处(文件标题与内部编号)写进正文,source_references 交给基座自动附加。
2. 发布配置是整版本替换语义:新版本会清空旧版本的全部配置项。若只发客服白名单,
   示例 Agent 的 fund_query_demo:fund_quote 会被静默清空。故发布脚本先读取当前生效
   版本的全部配置项并原样继承,再追加新增项。
3. 验收脚本自身两处自伤:打印 emoji 触发 GBK UnicodeEncodeError、以及读错结果字段
   (RunQueryService 返回的答案键是 content 不是 text)。

已知缺口(未修,已记录):
- CoreResult.transfer_required 未持久化:conversation_message 不存该标记,
  API 读不到"本次是否引导了人工"。当前靠正文里的固定话术判断。
- 知识块引用(source_type=knowledge)尚未启用,需先让 ToolExecutor 把工具返回的
  doc_id 登记为本次可引用来源。

验证:ruff 通过、mypy 107 文件无错、unit+contract 447 passed;
tools/customer_service_check.py 8/8 通过(含越界请求、投诉、知识库外问题三类
必须引导人工的场景,以及 7 个零容忍负面词零命中)。
2026-09-10 20:22:42 +08:00