怎样把企业售后 Agent 做成可验收的工单闭环

企业售后系统里的模型可以归类工单、检索政策、起草回复,也能把一句未执行的“已退款”写得像真的。后者不是语言问题,而是系统没有把事实、权限和业务动作分开。

一套能用于售后流程的 Agent,需要明确谁可以看数据、哪些知识仍然有效、什么情况必须停下来等人、发生问题后怎样还原过程。模型只负责其中一段:根据受限上下文给出建议。它不能凭生成的文字获得退款、外发消息或改订单的权限。

本文以 Wmengti/enterprise-ai-after-sales-service-platform 为样本,结合 pangmadee 对该系列的阶段复盘,梳理一套售后工单 AI 工作台的设计方法。仓库包含源码、合成夹具、任务书和阶段证据。下文说的“仓库实现”,都指当前 main 分支可读到的代码和文档,不表示我已在真实客户数据或公网环境中复验过它。仓库也明确把成果定为本地 L0 Demo,尚不是生产系统。

一、先界定第一版要处理什么

“做一个企业客服 Agent”不算完整需求。它可能是聊天机器人、工单分流、知识问答、退款审批、外呼、质检,也可能想成为 CRM 的总入口。第一版把这些全塞进去,往往只会得到一个展示效果不错、责任边界却说不清的页面。

仓库的第一版把问题压缩为“企业售后工单 AI 工作台”。它选择了一个很具体的闭环:

1
2
3
4
5
6
7
登录
→ 查看有权限的工单
→ 检索当前有效的企业知识
→ 生成带来源的结构化建议
→ 人工编辑、确认、拒绝或转人工
→ 保存终稿和处理结果
→ 留下审计记录,并能用固定案例回归评测

它刻意不做真实退款、改订单、支付、对客户外发消息、自由注册、复杂多租户和“模型直接访问数据库”。这一点写在 D00 总任务书 里,也和源码中的合成数据边界一致。

第一版的重点是把责任链补齐。比起“接了多少模型”,更该先回答下面这些问题:

  • 谁能看到这张工单?
  • 模型看到了哪些数据,哪些数据没有给它?
  • 它的每一个引用是否来自这一轮允许的知识?
  • 它的草稿和客服最终发出的版本能否区分?
  • 它说“已退款”“已补发”时,系统如何拒绝?
  • 模型、Prompt 或知识库改动后,怎样发现一个原本正确的案例变差了?

这些问题没有答案时,生成得越快,未经证实的判断就越容易被带到客户面前。

二、先按责任拆系统,再决定是否需要多个 Agent

有些架构图把系统分成“协调 Agent、检索 Agent、回复 Agent、审核 Agent、执行 Agent”。名称很多不代表控制更严。权限判断、状态流转和写入条件原本可以由代码确定,交给模型猜反而增加了不确定性。

先按责任划分,模型角色放在最后考虑。

图中只有“模型适配层”需要调用模型。其余环节主要由普通程序和数据库约束:

责任 应由谁决定 失败时的正确行为
身份、角色和资源范围 服务端策略 拒绝,不透露资源是否存在
当前可用知识 知识版本状态和检索逻辑 无可靠来源时不给模型伪装答案
分类、草稿、下一步建议 模型在受限上下文中提出 结构不合法时进入降级或人工处理
退款、换货、外发等业务执行 明确授权的业务系统和人 模型不得声称已完成
风险升级、最终确认 服务端规则加人工责任 暂停、拒绝或转人工
审计与评测 不可随意改写的记录和固定案例 保留失败,不用“总分不错”掩盖退步

Human-in-the-loop 也不等于在结果旁放一个“确认”按钮。人工确认应是一条状态迁移:记录确认人、时间、AI 原稿、人工终稿、拒绝或转人工的理由,并关联工单版本与 AI 运行记录。少了这些信息,出现争议时很难还原最后的决定由谁作出。

三、先画工单状态,再写 Prompt

不少原型先写提示词,把客户问题和知识库交给模型,再要求它“专业地回答”。但模型输出只是一瞬间,售后处理有完整的前后状态。

仓库的 Prisma Schema 把两类状态分开:工单状态包括 OPENIN_PROGRESSWAIT_CONFIRMESCALATEDCOMPLETED;AI 运行状态则包括 RUNNINGWAIT_CONFIRMFAILEDFALLBACKHUMAN_HANDOFFCOMPLETED 等。数据模型 中还有 version 字段,用来处理写入冲突。

简化后,状态关系如下:

状态名可以调整,边的守卫条件不能含糊。AI_RUNNING → WAIT_CONFIRM 不应只因为“模型返回了文字”就发生,至少还要满足:

  1. 输出能被解析为既定的结构;
  2. 所有引用都在本轮检索结果白名单中;
  3. 没有把未执行的操作说成已经完成;
  4. 风险等级经过服务端规则复核;
  5. 当前用户确实有权访问这张工单和相关知识。

权限不能只靠前端隐藏菜单。仓库的 授权测试 检查了未登录返回 401、不同角色得到 200/403/404、伪造 role=admin 或请求头不能提权,以及受保护资源对无权限用户不暴露。测试不能覆盖现实世界中的全部安全问题,但它把权限检查放在了正确的位置:服务端。

3.1 乐观并发不是“技术细节”

设想两个客服同时打开一张工单。A 依据旧信息确认了 AI 草稿;B 刚刚补充了客户的新地址和新描述。如果 A 的保存无条件覆盖 B,系统就制造了一个客服自己也未必发现的数据错误。

最小接口契约应带上期望版本:

1
2
3
4
5
6
7
8
type ConfirmDraftRequest = {
ticketId: string;
expectedVersion: number;
aiRunId: string;
finalReply: string;
action: "CONFIRMED" | "REJECTED" | "HUMAN_HANDOFF";
reason?: string;
};

服务端在事务里做条件更新:只有数据库中的 version === expectedVersion 才写入,并将版本加一。若更新条数为 0,就返回冲突,让客服刷新后再处理。模型不该决定怎样合并冲突,这需要了解业务语义的人来判断。

四、RAG 的起点是“哪一版知识可以被引用”

售后系统常把 RAG 理解为“上传几个 PDF,再做向量检索”。问题是知识不是静态文件。

退换货规则会更新,特殊时期政策可能停用,型号故障说明也会更正。模型引用旧版本时,回复写得再流畅也会得出错误结论。知识对象至少要有这些字段:

1
2
3
4
5
6
document_id      文档身份,例如退换货政策
version 版本号
status processing / available / disabled / failed
effective_scope 适用产品、地区、团队或租户
chunk_id 可精确定位的片段
source_metadata 标题、章节、更新时间、权限信息

仓库的夹具包含同一政策的停用版本和有效版本,任务书要求“停用知识不进入新的检索”。它的 selected-retrieval.ts 不只按向量分数排序,还计算词法覆盖、实体匹配、敏感凭据问题的规则加分、阈值过滤,并最多保留三条结果。实现代码 传达的原则很直接:检索策略可以变,但每个入选片段都要能追溯来源。

4.1 给引用设白名单

把检索结果放进 Prompt,再写一句“请引用资料”,不能保证模型只引用这些资料。它可能编造一个很像内部文档编号的来源,也可能按训练记忆补出并不存在的规则。

更可靠的做法是让代码先生成白名单:

1
2
3
4
5
6
7
8
9
10
11
const retrieved = await retrieveAuthorizedKnowledge({
query: ticket.description,
userId: actor.id,
limit: 3,
});

const allowedSourceIds = retrieved.map((item) => item.chunkId);
const modelInput = {
ticket: minimizeTicket(ticket),
sources: retrieved.map(toPromptSource),
};

模型返回后,服务端直接做集合校验:

1
2
3
4
5
6
7
const invalid = suggestion.sources.filter(
(sourceId) => !allowedSourceIds.includes(sourceId),
);

if (invalid.length > 0) {
return reject("UNVERIFIED_SOURCE", invalid);
}

仓库的 validateAiSuggestion 采用了这类校验:输出必须是固定 JSON,引用必须属于本轮允许来源;没有可靠来源会被拒绝;“已经退款到账”“已补发并寄出”一类完成性业务描述也会被拒绝。产品质量类工单或命中风险词时,服务端会把风险升为高风险并要求人工确认。

这不是防止模型胡说的万能办法。规则词表仍会漏判或误判,真实业务操作也要由后端系统确认。不过它把原本只能靠提示词约束的问题,变成了可以写进单元测试的规则。

4.2 不命中是正常结果,不要拿旧片段凑答案

检索系统不该无论问题是什么都返回 Top 3。排序总会给出前三条,但不代表这些内容足以回答。政策已停用、客户问的是新型号,或问题缺少订单信息时,正确结果可以是“没有可靠来源”。

这时系统可以做三件事:

  1. 告诉客服缺的是订单号、产品型号还是地区信息;
  2. 允许客服补充信息后再次检索;
  3. 转给有权限的人处理,并把“知识缺口”记录下来。

不要让模型用相邻知识拼出一个看似合理的确定答案。售后回复要能被政策、订单和操作记录支持。

五、模型返回的是建议对象

API 直接返回一段 Markdown 时,前端很好展示,系统却很难判断分类、风险、依据和是否需要人工确认。再从自然语言里抽取这些字段,可靠性只会更差。

更适合业务系统的方式是要求模型生成受约束的对象,例如:

1
2
3
4
5
6
7
8
9
10
{
"classification": "product_quality",
"priority": "high",
"sources": ["KB-PROD-003#safety"],
"draft_reply": "为保障安全,请先停止使用设备……",
"risk_level": "high",
"risks": ["设备发烫,需要人工核对售后政策"],
"requires_human_confirmation": true,
"next_actions": ["核对订单和保修状态", "转主管确认处置方案"]
}

结构化输出让前端可以分别展示草稿、引用、风险和下一步;服务端可以校验枚举值、长度、来源与风险规则;评测也能落到字段级,例如引用是否正确、是否漏判高风险,而不是凭印象评价整段文字。

仓库的 AI 校验器限制了来源、风险、下一步数组和草稿长度,解析失败时返回 STRUCTURE_INVALID。没有可靠来源时,它返回明确的人工核验建议,而不会编出一个“安全答案”。

模型供应商应被隔在适配层后。业务层只依赖 generateSuggestion(input),适配层才处理 DeepSeek 或其他供应商的请求格式、流式事件、超时、重试和错误码。更换模型、做 A/B 对比或冻结版本时,业务状态机不必重写。

六、人工确认要保留原稿与终稿

客服会补充客户刚提供的信息,删去过度承诺,调整语气,也可能发现模型分错了类。系统如果只保存最终发出的文本,后续就不知道 AI 当时建议了什么,也不知道人工为什么改动。

一个足够小但完整的确认记录可以是:

1
2
3
4
5
6
7
8
9
confirmation_id
ticket_id + ticket_version
ai_run_id + prompt_version + model_version
retrieval_snapshot_id
ai_draft
final_reply
action confirmed / rejected / human_handoff
reason 可选但对拒绝和转人工建议必填
actor_id + confirmed_at

这些字段用于回答维护中绕不开的问题:

  • 某条不当回复来自模型原稿,还是人工编辑?
  • 哪些知识片段最常被人工改写,是否过期或表达不清?
  • 新 Prompt 让“确认率”上升,是质量真的提升,还是客服放松了审查?
  • 一次投诉发生时,能否重建当时的模型、知识和人工决策上下文?

仓库的 D06/D08 文档和数据模型围绕原稿、终稿、确认动作与追溯关系组织。完整追溯记录 展示了合成工单上的运行记录、来源元数据、高风险确认、终稿、审计与评测如何关联。它没有接通真实外发或退款渠道,文章也不把这部分写成已实现能力。

6.1 哪些动作必须停下来等人?

不要指望一条很长的“高风险 Prompt”覆盖所有场景。业务和安全团队应维护清晰、可更新的风险清单。第一版可以从下表起步:

场景 模型能做什么 必须由谁确认
常见使用说明 给出有来源的草稿 客服确认后发送,或按制度自动化
退换货、补偿、发票 提示适用规则与所缺信息 有相应授权的客服或主管
产品冒烟、发烫、破损 给出安全提示与转交建议 主管 / 专项安全流程
要求验证码、密码、银行卡、身份证 拒绝索取并提示正规渠道 不应由模型继续推进
改地址、补发、退款、对外发送 只能提出待执行建议 已授权业务系统与人工

这不是通用合规清单。不同地区、行业和公司有不同政策,应由实际业务负责人维护。模型可以总结证据并提出建议,但不能凭一段生成文本获得更高权限。

七、审计记录要能还原一次处理

客服问“这条建议为什么出现”时,单独存一段请求日志不够。需要能串起谁读取了什么、系统检索到什么、模型用了什么版本、校验是否放行、人工做了什么,以及结果落在哪张工单上。

建议为每次请求生成 request_id,每次模型运行生成 run_id,并让它们贯穿:

1
2
3
4
5
6
7
8
request_id
├─ actor_id / role / authorization outcome
├─ ticket_id / ticket_version
├─ retrieval_snapshot_id / source ids / knowledge versions
├─ ai_run_id / provider / model / prompt version / status
├─ validation outcome / forced risk upgrade
├─ human confirmation or handoff
└─ final ticket transition / audit event ids

审计不等于把所有数据写进日志。客户正文、联系方式、Cookie、口令和完整模型上下文都可能含有敏感信息。审计只应保留复核决策所需的最小元数据,对敏感字段脱敏、分级访问,并规定保留期限。仓库的授权测试检查了会话只保存哈希、审计同时覆盖允许与拒绝结果、审计摘要不含密码或 token 等敏感字段。真实企业仍要按自己的数据分类和法律义务评审。

八、固定评测用来发现回归

“这次模型回答感觉更好”不能作为发布依据。模型、Prompt、嵌入模型、分块方式、检索阈值或知识库版本任意改一项,都可能让某些历史场景变差。

售后 Agent 的最小评测集不必从几千题开始。先做 30 到 100 个覆盖关键路径的合成案例,更容易维护。每个案例最好包含:

1
2
3
4
5
6
case_id
输入工单与授权上下文
允许或禁止的知识来源
预期分类 / 风险等级 / 是否转人工
不可出现的越权或完成性表述
人工可读的失败说明

不要只看一个总分。至少分成四层:

问题 例子
检索 找到的依据对吗 新政策是否压过已停用旧政策
结构 输出能消费吗 JSON、枚举、来源格式是否合法
安全 是否越权或漏判风险 是否把退款承诺为已到账
业务 建议是否有用 分类、优先级、下一步是否合理

仓库的 D07 运行比较 记录了一个很实际的结果:候选版本的总通过数更高,却有一个原本通过的案例退步。界面因此要显示 improvedregressed 和案例级失败原因,安全案例还要单独设门槛。只看百分比会掩盖这类退步。

发布门可以很简单:任一关键安全案例失败,就不自动通过;非关键业务案例可以保留待审查项,但要写清责任人和修复计划。模型有随机性不等于可以忽略失败。它要求记录模型参数、Prompt、知识快照和运行次数。

九、开发过程也要有验收关口

仓库没有让 AI 从一页需求直接跳到“生成完整网站”。它把开发拆为 D01 到 D10:工程与数据访问、登录与权限、工单、知识检索、AI 流式输出、人工复核、固定评测、审计管理、全链路验收、部署方案与模拟上线。每个阶段都要求任务书、自动测试、页面或后端证据、已知限制和人工冻结。

这个过程也适用于其他行业:

“AI 执行,人做选择”不表示人只负责点确认。人负责范围、权限、风险阈值、接受标准,以及是否允许进入下一阶段;AI 可以查资料、生成候选实现、跑测试、整理证据。没有这些判断,AI 只会更快地产生不受约束的代码。

对于第一次做这类项目的团队,我建议按下面顺序开工:

  1. 写一页范围声明:目标用户、唯一主路径、明确不做的动作、成功与失败怎样判定。
  2. 画出工单和 AI 运行的状态机,并列出每条边的守卫条件。
  3. 定义角色到资源的权限矩阵,先写越权测试再写页面。
  4. 用合成、脱敏的数据搭建知识生命周期,保留停用和更新案例。
  5. 固定模型输出 schema,在服务端校验来源、风险和业务断言。
  6. 先实现人工确认、拒绝和转人工,再考虑自动发送。
  7. 建一组可重复运行的安全与业务评测案例,任何改动都跑比较。
  8. 最后再谈外部渠道、真实订单和部署,不要反过来。

十、先把仓库作为教学工程跑起来

仓库的 README 给出了本地流程:启动 PostgreSQL,复制环境文件,安装依赖,执行迁移,导入合成数据与知识,建立索引,然后分别启动 Web、API 和 Worker。README 中的默认账户也全部使用 .test 域名和统一演示口令环境变量。

可按 README 在本地运行:

1
2
3
4
5
6
7
8
9
git clone https://github.com/Wmengti/enterprise-ai-after-sales-service-platform.git
cd enterprise-ai-after-sales-service-platform
cp .env.example .env
npm install
docker compose up -d postgres
npm run db:migrate
npm run db:seed
npm run knowledge:seed
npm run knowledge:index

之后另开终端启动 npm run dev:apinpm run dev:web,需要后台任务时再运行 npm run dev:worker。没有配置模型密钥时,先检查权限、知识版本、失败和人工路径。真实模型验证需要在本地配置密钥,密钥不应提交进仓库。

“能在本地 production build 跑通”不等于“可以部署为企业生产服务”。仓库 D10 的交付审计明确写的是“已验证的 L0 本地 Demo,不是公网生产上线”。它没有完成云环境兼容性验证、线上备份恢复、告警和值班、SLA、企业 SSO、私有网络和真实客户数据治理。代码即使部署到公网,这些责任也不会自动补齐。

真正要走向生产,还需要单独立项解决:

  • 身份接入、租户隔离、最小权限与权限审计;
  • 真实订单、退款和消息渠道的幂等性、审批与回滚;
  • 数据最小化、脱敏、保留与删除策略;
  • 模型供应商、区域、数据使用条款与故障降级;
  • 密钥管理、网络隔离、备份恢复演练、监控告警和事件响应;
  • 负载、成本、限流、队列积压、模型超时及人工接管预案。

这些项目需要单独计划和验证。它们决定了错误回复、越权访问或数据损失发生后,由谁负责处理。

十一、用问题检查它是否能进入业务流程

准备下一次演示前,可以用下面十个问题反向验收:

  1. 无权限用户请求一张工单时,系统会不会泄露该工单是否存在?
  2. 知识文档停用后,新的 AI 运行还能不能引用它?
  3. 模型编出一个不存在的文档编号,服务端会怎么处理?
  4. 模型说“已经退款”,而业务系统没有执行退款,结果会是什么?
  5. 两人并发编辑同一工单,旧版本能否悄悄覆盖新版本?
  6. 高风险草稿被拒绝或转人工时,是否保存了原因和状态,而不是只消失?
  7. 能否定位一条最终回复对应的模型、Prompt、知识版本和人工确认人?
  8. 修改检索阈值后,能否知道哪个固定案例退步了?
  9. 模型超时、返回坏 JSON、检索没有结果时,客服还能否安全继续工作?
  10. 如果今天下线模型服务,哪些业务仍可运行,哪些必须明确转人工?

如果大部分问题只能回答“应该可以”,系统还不适合被称为企业 Agent。先把答案落实为接口契约、自动测试、审计字段和演练记录,再扩展能力。

结语

可靠的售后 Agent 应限制模型能看到的数据,也限制模型建议能进入哪些业务环节。授权、当前有效的知识和结构化输入约束前者;校验、人工确认、审计和评测约束后者。

这个仓库是一个教学样本。它从工单主路径出发,把权限、RAG、受约束生成、人工复核、回归评测和证据化开发连接起来。它没有证明自己已经能处理真实客户数据,更不是生产平台。它提供的启发很朴素:先设计停止条件、失败路径和责任人,再提高 Agent 的自主程度。

有了这些基础,再接入更多渠道、工具调用和模型,才是在扩展一条可以检查的业务闭环。

参考资料