Files
group_fqcd_jr/docs/superpowers/specs/2026-09-10-customer-service-agent-design.md
T

169 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 设计文档:客服 Agent 本体(子项目 B)
> 状态:待用户审阅
> 日期:2026-09-10
> 前置依赖:子项目 A(`2026-09-10-knowledge-retrieval-infra-design.md`)必须先完成,本文档依赖其 `query_knowledge` 工具。
> 权威来源优先级:行为边界以《客服Agent一期运行边界基线_v1.md》为最高优先级(该文档自身声明"高于历史问答、示例话术和模型自由生成内容");
> 底座接入方式以 `docs/14-Agent组员统一接入说明书.md` 和 `TODO.md` T8.1 为准;
> 二者冲突时以边界基线为准(业务行为),接入方式冲突时以底座文档为准(工程约束)。
## 1. 背景
`TODO.md` T8.1 要求客服 Agent 覆盖 FAQ、产品咨询、政策解释、闲聊、转人工五类意图。桌面胜宇提供的三份"一期"材料把这个需求收窄为一套更具体、更严格的产品规则:一个虚构品牌"奶龙基金责任有限公司"及其客服"奶龙基金智能助手",服务范围只是解释已审核公开信息和指引办理路径,不触碰任何个人账户数据、不代办交易、不提供投资建议。
## 2. Agent 定义
```python
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 的优先级表:
```text
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`:
```text
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 第五步给出的验收测试清单,逐项覆盖:
```text
公开产品资料、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`,不能召回记忆、不能有任何客户归属数据)、
游客会话如何过渡到登录后的正式会话(如果产品要求两者衔接)。