## 新入库(`docs/演示用/`)
- `代码库全面审查报告-2026-09-14.md`
- `代码修改方案-2026-09-14.md`
- `记忆系统排查报告-2026-09-14.md`
- `记忆系统修复文档-2026-09-14.md`
- `文档一致性审计报告-2026-09-14.md`
- `多Worker接入方案-2026-09-14.md`
## 全量校对(32 个既有文档 + `AGENTS.md`)
跨 39 个文件、**1125 insertions / 148 deletions**。
⚠️ **这批改动同样不是本次会话写的**。我抽样核对过性质:是**实质内容补充**而不是
格式/换行转换。例如 `docs/44-演示流程.md` 新增两条"2026-09-14 补注":
- `启动金融Agent平台.bat` 只在**桌面**上,仓库里只有 `启动平台.bat` 这一份
(两份由同一个 `tools/make_launcher_bat.py` 产出,改完 `start.ps1` 重跑它一起更新);
- `advisor_t`(9020) 与 `offsite_t`(9006) **不在 `tools/seed_test_rbac.py` 的演示用户里**
(那里只有 `cust_t`/`risk_t`/`admin_t`/`review_t` 四个),由 `grant_*.py` 系列创建,
**重跑种子不会重建它们** —— 换机器时这两个账号登录失败,要先查 `sys_user` 有没有这两行,
而不是查密码。
这两条都是对的地方,与我这一路踩到的现象一致(我确实用到了 `advisor_t`/`offsite_t`)。
**我没有逐字审阅全部 39 个文件**,只抽样确认了改动性质与规模。若其中有需要复核的段落,
请指明文件,我逐处核对。
14 KiB
设计文档:客服 Agent 本体(子项目 B)
🗂 过程产物 · 结论已归档(2026-09-14 批注) 本设计已实现(
CustomerServiceAgent已注册)。⚠️ 注意:本文写的依赖工具名是query_knowledge, 实现时正式名改为search_knowledge(query_knowledge保留为别名)。 判断当前进度请看docs/验收与审计/phase1-acceptance-report.md。
状态:待用户审阅 日期:2026-09-10 前置依赖:子项目 A(
2026-09-10-knowledge-retrieval-infra-design.md)必须先完成,本文档依赖其query_knowledge工具。 权威来源优先级:行为边界以《客服Agent一期运行边界基线_v1.md》为最高优先级(该文档自身声明"高于历史问答、示例话术和模型自由生成内容"); 底座接入方式以docs/14-Agent组员统一接入说明书.md和TODO.mdT8.1 为准; 二者冲突时以边界基线为准(业务行为),接入方式冲突时以底座文档为准(工程约束)。
1. 背景
TODO.md T8.1 要求客服 Agent 覆盖 FAQ、产品咨询、政策解释、闲聊、转人工五类意图。桌面胜宇提供的三份"一期"材料把这个需求收窄为一套更具体、更严格的产品规则:一个虚构品牌"奶龙基金责任有限公司"及其客服"奶龙基金智能助手",服务范围只是解释已审核公开信息和指引办理路径,不触碰任何个人账户数据、不代办交易、不提供投资建议。
2. Agent 定义
class CustomerServiceAgent(BaseAgent):
definition = AgentDefinition(
agent_type="customer_service",
version="1.0.0",
allowed_roles=("customer",),
allowed_portals=("api",),
allowed_tools=("query_knowledge",),
supported_intents=("faq", "product_inquiry", "policy_explain",
"chitchat", "transfer_human"),
)
supported_intents 沿用 docs/02 的五类,供底座自动意图分类器使用、供配置中心按意图配置工具白名单使用。但这五类分类结果不是 handle() 内部安全路由的唯一依据,理由见第 3 节。
范围确认:本 Agent 只服务已登录客户端模块,游客版客服是完全独立的功能
《一期运行边界基线》§2 把服务对象分成"访客"和"已登录用户"两档,一度让人以为本 Agent 需要同时处理未登录访问——
但结合整体前端规划确认:产品分为面向游客的公开站点(公司介绍/产品介绍/登录页/游客版智能客服)和登录后按角色
(客户/风控/投顾等)区分的客户端,本文档设计的 customer_service Agent 只是客户端里的一个模块,只服务已登录客户;
游客版智能客服是公开站点上完全独立的另一套功能,不经过本 Agent,也不在本文档范围内。
这与底座鉴权架构(app/api/dependencies/auth.py 要求任何 Agent 运行都必须携带有效 JWT,没有匿名访问路径)天然吻合,
不需要额外改动底座。第 3 节 P1 分支"已登录场景可附加引导至我的账户页面"这句话因此永远成立——能走到本 Agent 的
请求都必然来自已登录客户。《一期运行边界基线》里关于"访客"那一档的描述,适用对象是游客版智能客服(独立功能,
未来若需要设计,是另一个独立任务),不适用于本文档。
3. 核心设计决策:安全路由与知识检索分离
《一期运行边界基线》第 9 节明确要求:安全、权限、个人数据、写操作、投诉赔偿类问题"必须在 RAG 检索前完成识别与拦截",且"不应仅依赖向量检索命中"。这意味着不能把"要不要查知识库、要不要转人工"这件事完全交给:
- 底座自动跑的 LLM 意图分类器(
self._classified_intent,本身是私有属性,且分类结果依赖模型输出,不是确定性的); - 向量检索是否命中某条"应该拒答"的知识条目(万一语义相似度没打准,就会漏判)。
因此 handle() 内部自己实现一个确定性的安全路由(关键词/正则规则,不调用模型),独立于底座自动意图分类,逐条对照《一期运行边界基线》§5 和《向量化前流程文档》§3 的优先级表:
P0 安全优先(诈骗/盗号/验证码泄露/资金风险关键词命中)
→ 固定安全话术 + transfer_required=True,不查知识库
P1 本人/他人账户数据(持仓/收益/订单/银行卡/投诉进度/风险测评结果类关键词)
→ 固定降级话术("当前客服 Agent 无法读取该项本人数据")
已登录场景可附加引导至"我的账户"页面;不查知识库
P2 转人工诉求(代办交易/资料修改/销户/投诉赔偿/法律争议/明确要求人工)
→ 固定转人工话术 + transfer_required=True,不查知识库
P3 公开静态问题、闲聊、人设问答
→ 调用 query_knowledge 工具检索(闲聊类问答本身也在 105 条知识库内,
走同一条检索路径,不单独分支)
注意:子项目 A 第一版只有 fin_faq_collection 有数据,product_inquiry/
policy_explain 两个意图对应的集合暂时是空的,命中率等同于未命中,
会自然落入下面的 P4 兜底话术,这是已知的第一版限制,不是 bug。
P4 低置信度/未命中
→ 固定"无法确认,建议转人工"话术,不得猜测或编造
这个安全路由表本身不依赖底座的 IntentClassifier,因此不需要解决"handle() 里读不到 self._classified_intent"这个此前发现的底座缺口——本 Agent 完全绕开这个问题,用自己的确定性规则做主要业务判断。底座的自动分类结果仍然会被记录进 CoreResult.intent(供审计和统计用),但不驱动业务分支。
安全路由的关键词/正则规则来源:《一期运行边界基线》§4 的禁止事项分类描述和《一期向量化前流程文档》§3 的场景描述,实现时需要把这些自然语言描述转成具体的匹配规则列表,作为 Agent 内部常量维护(不需要走数据库配置化,因为这是硬编码的合规红线,不是可调业务参数)。
4. 知识命中后的回复生成
按《向量化前流程文档》的要求,标准答案不应被模型自由改写。结合确认过的决策:
- 命中 1 条:直接返回该条
content_text原文,不调用模型。 - 命中多条:允许调用
self.generate_with_model()把这几条标准答案"组织一下语言",但 Prompt 必须明确约束"只能使用给定的几段文字内容,不得添加任何未出现在原文中的新事实、数字、承诺或渠道信息",属于"复述/合并"而非"生成新内容"。 - 未命中或全部相似度低于阈值:走 P4 固定话术,不调用模型编造。
source_references 由 query_knowledge 工具调用自动生成(ToolExecutor 已有机制),Agent 不需要自己拼引用。
CoreResult.intent 必须由 handle() 显式设置,不能留空等底座自动分类结果回填:底座会在 handle()
执行前自动跑一次独立的 LLM 意图分类(结果存在 self._classified_intent,handle() 内部读不到,也不应该依赖它,
见第 3 节)。BaseAgent._execute_governed() 的规则是 result.intent or self._classified_intent——如果
handle() 返回的 CoreResult.intent 是 None,最终写入 conversation_message.intent 的会是那个独立分类器的结果,
它完全可能和 Agent 本节安全路由实际走的分支对不上(例如安全路由判定为 P1 账户数据问题直接拒答,但自动分类器
独立跑出来的结果是 intent=faq, confidence=0.8,两者没有关联)。
因此 handle() 必须根据自己在第 3 节安全路由里实际走的分支,显式构造并返回准确的 IntentResult:
P0/P1/P2(安全/账户数据/转人工诉求) → intent="transfer_human", confidence=1.0, needs_clarification=False
P3 命中知识库 → intent 取 "faq"/"product_inquiry"/"policy_explain" 中实际命中的一个,
confidence 用检索相似度或固定较高值
P3 闲聊/人设问答 → intent="chitchat", confidence=1.0
P4 未命中/低置信度 → intent="transfer_human", confidence=<实际值>, needs_clarification=True
这样落库的 intent 字段才能真实反映业务分支,供审计、统计和(如果第 6 节采用方案二)闲聊连续计数使用。
5. 转人工
Agent 只做两件事:① 在回复文本里包含《一期向量化前流程文档》§3 规定的固定联系方式(人工客服 15936583816,工作日 09:00-18:00 等);② 在 CoreResult 上设置 transfer_required=True 和 transfer_reason。
联系方式文案的来源需要澄清:由于第 3 节 P0/P1/P2 分支明确"不查知识库",这几个分支的固定话术(含电话、邮箱、地址等联系方式)
只能是硬编码在 Agent 代码里的常量,不可能像 P3 分支那样从 query_knowledge 动态取——安全关键路径不应该依赖额外的
检索调用才能返回正确结果。这意味着联系方式如果未来变更,需要同时改两个地方:QA 知识库源文件(供 P3 检索到的知识内容使用)
和 Agent 代码里的常量(供 P0/P1/P2 安全话术使用)。这是刻意的取舍,不是疏漏。
是否真正调用 POST /conversations/{session_id}/handover-requests 创建 svc_handover_ticket,由前端根据 transfer_required 标记决定是否调用,不属于本 Agent 职责——这与 AGENTS.md"Agent 只能提出转人工请求,不能分配、接单、解决或关闭工单"的约束一致,也是本轮讨论中厘清的、胜宇材料与团队大文档表面冲突实际不冲突的点(见子项目 A 文档记录之外,本文档单独说明:大文档设想的"生成完整工单"是前端调用已有接口后的结果,不是 Agent 的新增职责)。
6. 闲聊计数与软引导
《一期运行边界基线》§3 和《向量化前流程文档》§3 都提到"连续闲聊第 4 条仅做一次自然业务引导"。
实现方式需要先说明一个架构约束:业务 Agent 不允许直接创建 SQLAlchemy Session、直接查
ConversationRepository 或任何 Model(AGENTS.md/docs/14 明文禁止),第一版设计里"直接查
conversation_message 最近 3 条"的写法违反了这条规则,已修正如下:
这是一条 UX 软性提示规则,不是合规红线(不像 P0-P2 那样错了会有实质风险),因此第一版不追求跨请求的精确计数, 按以下简化方式实现,两种都不需要新增底座能力:
- 方案一(推荐,第一版采用):不做跨轮次持久计数,只在单次
handle()内根据本次和上一轮用户消息的表面特征做启发式判断, 或者干脆先不实现"连续第 4 条"的精确触发,只保证"闲聊类回复本身语气自然、不生硬",把精确计数列为已知的第一版简化项。 - 方案二(如果产品认为这条规则必须精确执行):需要底座新增一个只读工具(例如
get_recent_intent_history,走ToolExecutor注册,权限和其他工具一致地声明),Agent 通过self.call_tool(...)调用,而不是绕过工具体系直接查库。这属于对底座的小额扩展,需要和底座负责人确认是否本轮一起做。
本设计文档默认采用方案一,方案二作为备选留给用户决定是否升级范围。
7. 合规治理的兼容性
handle() 产出的 CoreResult 仍然会经过底座既有的 governance.review_output()(负面词过滤、引用来源校验、手机号/证件号脱敏),不需要额外适配——因为 Agent 返回的是知识库原文或"复述"文本,本身已经是审核过的合规内容,治理层是兜底而非主要防线,两层防线不冲突。
8. 测试计划
采纳《客服Agent一期向量化前流程文档_v1.md》§5 第五步给出的验收测试清单,逐项覆盖:
公开产品资料、15 点规则、费率、适当性问题 → 正确命中知识库并作答
本人持仓、订单状态、投诉进度问题 → 一律固定降级话术,不返回任何数据
明确要求人工 → 固定转人工话术 + transfer_required=True
验证码/密码泄露、诈骗场景 → 立即安全话术,不继续收集信息
投资推荐类问题 → 明确拒绝给出具体建议
连续闲聊达到第 4 条 → 触发一次软性业务引导,之后不重复
低置信度/无法判断的问题 → 固定"无法确认"话术,不编造
另外补充底座接入层面的常规测试(参照 docs/14):未授权角色/入口调用拒绝、工具白名单外调用拒绝、模型格式错误时安全路由仍正常工作(因为安全路由不依赖模型)。
9. 范围外事项
- 真实创建
svc_handover_ticket工单——前端职责。 fin_product_collection/fin_policy_collection的实际内容——见子项目 A 文档第 2 节。- 知识库内容的在线编辑/审核——见子项目 A 文档第 2、8 节。
- "我的账户"页面本身——独立前端页面和后端专属接口,本 Agent 只做跳转提示,不实现该页面。
- 游客版智能客服(公开站点上面向未登录访客的客服功能)——与本 Agent 是完全独立的两套功能,不共用后端实现,见第 2 节范围确认。
已确认后续也要接入本底座,但这涉及匿名身份认证(
AGENTS.md列为高风险区域的 Authentication), 需要单独作为子项目 C 走一次完整的 brainstorm 和设计评审,不在本文档展开,也不应该为了预留接口而现在改动build_request_context/JWT 相关代码。子项目 C 启动时的关键设计问题:匿名身份怎么签发和校验、新增角色role=guest的权限边界(大概率只能调用query_knowledge,不能召回记忆、不能有任何客户归属数据)、 游客会话如何过渡到登录后的正式会话(如果产品要求两者衔接)。