让客服 Agent 有据可查:做一个带引用的政策问答页面

用户问客服:“这件商品能退吗,运费谁承担?”Agent 很快给出一段回答,还在末尾放上了几个引用编号。看起来有出处,但点击以后,可能只是打开了整份文档,甚至指向与结论无关的条款。

带引用的问答,需要能回答两个具体问题:这句话根据哪一段原文,以及那一段是否真的在本轮查询中被取回。一个漂亮的来源列表,不能自动证明回答有依据。

这一篇用 Vue 3 和 TypeScript 做一个最小客服政策助手:调用只读检索工具,取得政策片段;固定模拟模型选择片段;校验通过后展示原文式回答与引用入口。 没有查到依据时,就明确停在“缺少依据”。

所有政策均为专门编写的教学示例,不对应真实商家或法定规则。模型响应用固定模拟,没有调用真实大模型、向量数据库或客服系统。演示可完全离线运行,重点验证引用链路与页面交互,不宣称已经完成真实模型的政策问答评测。

机器人从政策书中找到编号1和2的原文,用连线把回答中的引用带回对应片段;没有依据的答复被留在一旁。

先限定这个最小 Agent 的工作范围

本例只处理三类演示政策:普通商品退货、运费说明和定制商品。用户提交问题后,流程固定为一次检索、一次模拟选择、一次引用校验,不会连续调用未知工具,也不会发起退款或创建售后申请。

检索使用受控关键词匹配,最多返回三个片段,便于重现结果。它没有理解所有自然语言表达,也没有实现向量相似度、重排或多轮追问。因此没有命中时,应说“本次未找到可用依据”,不能把检索失败写成“商城没有这项规定”。

RAG 的基本思路是先检索相关内容,再把内容交给模型作为回答依据。官方介绍也将检索、补充上下文和生成分成不同步骤,并指出来源元数据有助于组织引用。Microsoft Learn:RAG 与索引

本例只实现这条链路的离线、抽取式演示。固定模拟模型负责选择返回片段,不代表真实模型已经学会分析政策或判断用户资格。

片段除了文本,还需要稳定身份

不能只给模型一段没有出处的长文本。检索结果需要携带片段 ID、文档 ID、标题、版本、章节与原文。页面的引用编号则根据本次答案重新排列,不把编号当成永久的文档身份。

typescript
type CitationSelection = {
  chunkId: string;
  quote: string;
};

模拟模型只返回这样的选择,不生成来源标题、版本号和链接。文档信息由应用从检索结果取回,避免模型凭空编造一个看起来可信的文件名或 URL。

三份原始政策文件随源码一起提供。引用展开时,读者既能看到对应片段,也能打开完整演示文件,检查上下文。版本只是本例的固定元数据;真实政策更新后,需要重新建立索引与答案失效规则,不能只换页面上的日期。

片段也应保留适用范围和限制条件。如果把“仅限普通商品”与后面的条件切散,再只引用其中一句,原文真实也可能让回答产生歧义。

引用只允许来自本轮检索

最容易写错的校验,是拿引用 ID 去全库里找:只要这个 ID 存在,就允许显示。这样模型即使没有在当前查询中拿到那段政策,也能引用它。

本例把本轮检索结果变成允许集合,逐条核对模拟输出。除了结构与长度,还需要检查:选择数量有界、ID 不重复、ID 属于本轮返回,以及引文与该片段原文完全一致。

Text
引用 ID 是否在本轮检索结果中?
引文是否逐字等于该片段完整原文?
全部选择是否通过校验后,才构建答案?

如果某一条不通过,就拒绝整份模拟答案,不先显示前半段,再把后半段标成失败。页面清理旧答案,展示具体错误与可重试入口,避免用户把上一次结果误认成本次答案。

这比仅验证 JSON 结构多了一步来源核对,但仍然不是通用的事实判断器。合法引用可以被用在不适用的问题上;引用检查通过,只证明来源和摘录对应,不能独立证明检索相关、政策完整或最终推理成立。

先做抽取式回答,再考虑自由改写

本例不接受模型随意生成“保证退款”“一定包邮”等承诺。校验通过后,应用从受控片段取出原文,用固定前缀组织成“演示政策写明”,并附上引用编号。

这样可以确定回答中展示的政策句子就是被引用的文本,不需要先解决任意自然语言改写的语义验证问题。代价是回答更像政策摘录,而不是流畅的人工客服对话。

如果要增加自由生成的总结,应把“引用有效”与“结论有依据”分开评估。例如模型可能引用一段运费条款,却把责任方说反;也可能逐条引用正确,却遗漏适用条件。这些问题不能靠 ID 存在或原文匹配全部排除。

无命中时,本例不会继续让模拟模型补一个答案,而是显示缺少依据。接真实模型时也应让模型能表达不知道、需要补充条件或转人工,而不是把每次提问都强制变成确定结论。

让用户能从答案回到原文

答案旁的引用按钮打开对应来源区域,并把焦点带到该片段。展开区域显示标题、版本、章节与完整原文,而不是只展示一个外部链接。

这里使用原生 details 与 summary 组织折叠内容。它们提供展开交互和键盘入口,适合这个小示例;引用编号与来源区域 ID 由页面生成,避免让模型决定 DOM 目标。MDN:details 展开元素

原文、问题和状态文案均按普通文本渲染。Vue 会转义普通文本内容,但不能把模型输出拼成模板或随意交给 v-html;来源链接同样来自受控文档配置。Vue:安全实践

页面还展示本轮问题快照、检索到的片段和工具执行记录。用户编辑输入框不会暗中改变已经展示的答案;再次提交时创建新一轮任务,旧响应只有仍属于当前轮次才能更新页面。

用错误引用验证失败路径

只测试正常问题能显示两条来源,覆盖不到引用组件最关键的边界。本例提供故障夹具,让固定模拟模型返回错误选择,直接检查校验层是否拒绝。

场景 应看到的结果
正常演示政策问题 展示原文式答案,引用可展开核对
本轮没有检索命中 提示缺少依据,不生成政策承诺
引用一个不存在的 ID 拒绝答案,不补造来源
引用全库存在但本轮未返回的 ID 仍然拒绝
ID 正确但引文被改写 拒绝,不把篡改文本当原文
模型返回错误结构 拒绝,不渲染部分答案
新问题已开始,旧任务才返回 旧结果不能覆盖当前轮次

核心测试除了检查引用 ID,还要检查全文引文、重复选择、数量限制和任务状态。浏览器测试则实际点击引用、操作折叠区,并在手机视口检查长引文与文档信息是否能读完整。

这些测试验证固定夹具和交互,不等于真实模型的回答正确率。接真实模型后,还要分别测检索命中、问题覆盖、引用对应和答案是否被依据支持,避免把一个指标当成全部质量。

运行示例与查看源码

打开带引用的政策问答演示 · 下载 Vue / TypeScript 完整源码

解压后直接打开 index.html 即可运行。Vue、政策数据和固定模拟逻辑都已打包,不需要模型密钥、服务端或 CDN。若要通过 HTTP 预览静态文件,在解压目录运行:

Bash
python3 -m http.server 8092 --bind 127.0.0.1

访问 http://127.0.0.1:8092/。这里的 HTTP 仅用于静态预览,提问仍在浏览器里检索和选择演示片段,没有新增一个真实模型接口。

修改源码时执行:

Bash
npm ci
npm run typecheck
npm test
npm run build

先运行普通退货、运费和定制问题,再试无依据与错误引用场景。点击答案中的引用编号,对照展开原文与源文件;不要只看答案末尾是否有几个蓝色数字。

本次类型检查、构建和 19 组核心测试通过,测试结果随源码附带。浏览器已核对三个政策主题、无依据拒答、四种错误引用、来源展开与键盘操作;桌面和手机均无整页横向溢出。验证范围是离线示例,没有调用真实模型或线上客服业务。

接真实模型时保留相同边界

真实模型可以替换固定选择适配器,但本轮检索集合、结构校验与来源信息仍应由程序控制。不要为了让答案更流畅,就放开任意来源 URL 或把没有检索到的文档当作本轮依据。

正式知识库还需要文档生效与失效状态、访问权限、适用人群和版本追踪;若用户条件不完整,应先追问。向量检索或重排可以改进召回,却不能自动解决政策冲突和承诺范围。

这个最小案例先把“带引用”变成一条可检查的链:当前问题找到片段,答案使用这些片段,用户能够回到原文。链路缺了一环,就展示缺少依据或校验失败,而不是生成一个有出处外观的确定答案。

  • Copyright: Copyright is owned by the author. For commercial reprints, please contact the author for authorization. For non-commercial reprints, please indicate the source.
  • Copyrights © 2022-2026 alan_mf
  • 全站访客— 累计浏览— 统计加载中…

请我喝杯咖啡吧~

支付宝收款码 支付宝
微信收款码 微信