欢迎光临
我们一直在努力

闪电智能 Voice Agent 如何为 RAG 设置“拒答”边界?从引用校验到人工兜底

客服 RAG 最容易被误解的地方,是把“检索到了几段相似文本”当成“现在可以回答”。这两个判断之间差得很远。

比如用户问:“退款什么时候能到账?我这笔还是重复扣款。”检索可能找到了退款时效,也找到了支付异常入口。可它们未必能证明这一次重复扣款的处理状态,更不能授权系统报出一个确定到账日期。此时继续组织一段流畅的回复,风险不在语言不够自然,而在系统把资料里的常规规则补成了个案结论。

我不建议把拒答做成模型输出的一句“抱歉,我不知道”。拒答应该是一条可检查的工程决策:本轮有什么证据、证据是否覆盖必须回答的事实、引用是否真的来自本轮检索结果,以及为什么最终选择转人工。

本文以闪电智能 Voice Agent 的 RAG 服务设计为例,给出一个保守的最小策略。代码中的阈值只用于说明决策顺序,不代表生产环境的命中率、准确率或业务效果。

目录

  • “有结果”不等于“可以答”
  • 拒答边界应该落在四个检查点上
  • 一个可运行的拒答决策夹具
  • 转人工不是拒答的失败分支
  • 把策略接进 Voice Agent 链路
  • FAQ
  • 参考资料

“有结果”不等于“可以答”

先把 RAG 回答拆成三个独立问题:

能不能找到相关资料?
资料能不能覆盖这一次问题里的关键事实?
即使资料足够,这个业务风险是否允许自动回答?

Day 19 的命中率评测主要验证第一层,也会检查引用覆盖。Day 20 要处理的是后两层。Recall@3 再高,也不说明“退款、账户、隐私、投诉”这样的具体个案已经可以安全自动处理。

一个常见的错误实现,是把相似度分数当作唯一开关:超过阈值就回答,低于阈值就拒答。这个实现看起来很省事,但排障时解释不了两件事:为什么答案引用了上一轮的资料?为什么检索分数很高,却漏掉了用户提到的“重复扣款”条件?

分数只能说明检索器认为资料相似,不说明资料足以支撑结论。拒答需要保存更多证据。

拒答边界应该落在四个检查点上

1. 低置信度:先停止补全,再考虑改写查询

低分不是模型“谦虚”,而是当前检索结果无法达到设定的证据门槛。策略可以先做一次受控的 query 改写或扩大召回,但不要把这一步悄悄变成无限重试。重试次数、改写后的 query、每次 Top K 和分数都应进入追踪日志。

如果仍没有足够证据,回复应明确说清可以做什么,例如请用户补充订单号,或切换到人工渠道。不要为了避免空白而给出看似合理的猜测。

2. 引用校验:引用必须属于这一次检索

有引用不等于有证据。第一层至少检查答案引用的资料编号是否属于本轮可用证据集;这能避免把上一轮会话、缓存或模型臆造的编号带进当前回复。

第二层要检查引用内容是否支持关键事实。规则可以先覆盖金额、时效、资格条件和办理入口;高风险主题则保留人工抽检。这里的人工抽检不是给模型“补课”,而是用来发现知识库版本、切分和策略是否正在偏离业务规则。

3. 事实覆盖:别用半份资料回答完整问题

用户句子里经常有多个约束。上面的重复扣款例子同时涉及退款时效、支付异常和订单状态。若证据只覆盖其中一项,系统最多能解释已确认的规则,不能替剩余部分下结论。

工程上不必追求一个抽象的“完整度”。可以把业务问题拆成必须覆盖的字段,例如 订单状态、款项状态、处理条件 和 下一步动作。任何必填字段缺证据,都进入拒答或人工分流。

4. 高风险:即使资料齐全,也要允许人工接管

隐私删除、身份核验、资金争议、投诉升级等问题,不该仅凭一次检索自动闭环。即使知识库给出标准流程,系统也应记录风险类型,展示已获取的上下文,并把会话交给具备权限的人处理。

用户明确说“转人工”同样是最高优先级的输入。不要因为检索分数高,就继续让机器人解释一遍。

一个可运行的拒答决策夹具

项目中的 demo/refusal_policy.py 把判断压缩成三个动作:answer、refuse、transfer_to_human。它不连接向量库,也不调用大模型;目的只是把策略顺序固定下来,让单元测试能保护边界不被以后改坏。

先看最关键的顺序:人工要求和高风险事项先分流;之后才检查检索分数、引用和事实覆盖。高风险不能因为分数高而绕过人工。

def decide(request: Request, *, minimum_score: float = 0.72) > Action:
if request.user_requested_human or request.risk_type in HIGH_RISK_TYPES:
return Action.TRANSFER_TO_HUMAN
if request.retrieval_score < minimum_score:
return Action.REFUSE
if not request.citation_ids:
return Action.REFUSE
if not set(request.citation_ids).issubset(request.available_citation_ids):
return Action.REFUSE
if not request.required_facts_covered:
return Action.REFUSE
return Action.ANSWER

这里最容易被删掉的是“引用是否属于本轮检索”的检查。因为答案已经带了一个编号,很多实现会默认它有效。可一旦把会话记忆、缓存和重排结果接进来,这个假设很快就不成立。缺失就是缺失,不要用其他轮次的一条资料补齐。

运行方式:

cd demo
python3 -m unittest -v

预期验证五件事:完整证据才允许回答;分数不足拒答;无引用拒答;引用不属于当前结果拒答;高风险或用户指定人工时转人工。测试并不衡量模型能力,也不能据此设定生产阈值。

转人工不是拒答的失败分支

“拒答”和“转人工”常被合并为同一种兜底,但它们面向的情况不同。

动作典型触发对用户的下一步日志至少记录什么
拒答 资料不足、引用无效、关键事实缺失 请补充信息或说明当前知识库无法确认 query、Top K、缺失字段、拒答原因
转人工 资金争议、隐私、身份、投诉、用户明确要求 交接给具备权限的人工坐席 风险类型、已确认事实、证据编号、交接时间

如果系统把两者都说成“暂时无法回答”,人工只能从头再问一遍;如果系统在转人工时携带已确认的信息和证据编号,坐席才知道哪些结论可以复用,哪些仍要核验。

把策略接进 Voice Agent 链路

在闪电智能 Voice Agent 中,这条策略应放在检索与生成之间,也要留在最终回复发送前做一次检查:

#mermaid-svg-I4iwHt4EfvOuHIKe{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-I4iwHt4EfvOuHIKe .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-I4iwHt4EfvOuHIKe .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-I4iwHt4EfvOuHIKe .error-icon{fill:#552222;}#mermaid-svg-I4iwHt4EfvOuHIKe .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-I4iwHt4EfvOuHIKe .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-I4iwHt4EfvOuHIKe .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-I4iwHt4EfvOuHIKe .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-I4iwHt4EfvOuHIKe .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-I4iwHt4EfvOuHIKe .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-I4iwHt4EfvOuHIKe .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-I4iwHt4EfvOuHIKe .marker{fill:#333333;stroke:#333333;}#mermaid-svg-I4iwHt4EfvOuHIKe .marker.cross{stroke:#333333;}#mermaid-svg-I4iwHt4EfvOuHIKe svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-I4iwHt4EfvOuHIKe p{margin:0;}#mermaid-svg-I4iwHt4EfvOuHIKe .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-I4iwHt4EfvOuHIKe .cluster-label text{fill:#333;}#mermaid-svg-I4iwHt4EfvOuHIKe .cluster-label span{color:#333;}#mermaid-svg-I4iwHt4EfvOuHIKe .cluster-label span p{background-color:transparent;}#mermaid-svg-I4iwHt4EfvOuHIKe .label text,#mermaid-svg-I4iwHt4EfvOuHIKe span{fill:#333;color:#333;}#mermaid-svg-I4iwHt4EfvOuHIKe .node rect,#mermaid-svg-I4iwHt4EfvOuHIKe .node circle,#mermaid-svg-I4iwHt4EfvOuHIKe .node ellipse,#mermaid-svg-I4iwHt4EfvOuHIKe .node polygon,#mermaid-svg-I4iwHt4EfvOuHIKe .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-I4iwHt4EfvOuHIKe .rough-node .label text,#mermaid-svg-I4iwHt4EfvOuHIKe .node .label text,#mermaid-svg-I4iwHt4EfvOuHIKe .image-shape .label,#mermaid-svg-I4iwHt4EfvOuHIKe .icon-shape .label{text-anchor:middle;}#mermaid-svg-I4iwHt4EfvOuHIKe .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-I4iwHt4EfvOuHIKe .rough-node .label,#mermaid-svg-I4iwHt4EfvOuHIKe .node .label,#mermaid-svg-I4iwHt4EfvOuHIKe .image-shape .label,#mermaid-svg-I4iwHt4EfvOuHIKe .icon-shape .label{text-align:center;}#mermaid-svg-I4iwHt4EfvOuHIKe .node.clickable{cursor:pointer;}#mermaid-svg-I4iwHt4EfvOuHIKe .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-I4iwHt4EfvOuHIKe .arrowheadPath{fill:#333333;}#mermaid-svg-I4iwHt4EfvOuHIKe .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-I4iwHt4EfvOuHIKe .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-I4iwHt4EfvOuHIKe .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-I4iwHt4EfvOuHIKe .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-I4iwHt4EfvOuHIKe .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-I4iwHt4EfvOuHIKe .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-I4iwHt4EfvOuHIKe .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-I4iwHt4EfvOuHIKe .cluster text{fill:#333;}#mermaid-svg-I4iwHt4EfvOuHIKe .cluster span{color:#333;}#mermaid-svg-I4iwHt4EfvOuHIKe div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-I4iwHt4EfvOuHIKe .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-I4iwHt4EfvOuHIKe rect.text{fill:none;stroke-width:0;}#mermaid-svg-I4iwHt4EfvOuHIKe .icon-shape,#mermaid-svg-I4iwHt4EfvOuHIKe .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-I4iwHt4EfvOuHIKe .icon-shape p,#mermaid-svg-I4iwHt4EfvOuHIKe .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-I4iwHt4EfvOuHIKe .icon-shape .label rect,#mermaid-svg-I4iwHt4EfvOuHIKe .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-I4iwHt4EfvOuHIKe .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-I4iwHt4EfvOuHIKe .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-I4iwHt4EfvOuHIKe :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

允许回答

资料不足

高风险或指定人工

通过

失败

用户语音与上下文

ASR 与问题整理

检索和重排

证据、引用和风险校验

带引用生成

拒答或补充信息

人工接管

发送前引用复核

TTS 播放

这不是多加一个拦截器就结束了。策略日志至少要有:知识库版本、检索配置、原始 query 与改写 query、Top K、引用编号、缺失事实字段、风险类型、最终动作和用户是否纠正。日志记录的是策略依据,不是给用户贴“高风险人格”或其他标签。

阈值也不应写死在 Prompt 里。每次更换切分器、嵌入模型、重排器或知识库版本,都可能改变分数分布。正确做法是先用脱敏的版本化样本回放,按业务类型观察误答、误拒答和转人工情况,再决定是否调整门槛。

FAQ

检索分数达到阈值,为什么还要检查引用?

因为分数来自检索器,引用来自生成结果。两者不是同一个对象。分数高只能说明资料可能相关,不能保证模型引用了正确资料,更不能保证关键条件没有被漏掉。

拒答会不会让客服体验变差?

会,前提是拒答只留下一句模板话。若系统能说明缺少什么信息、给出下一步,或在高风险场景把上下文交给人工,用户至少不会拿到一个错误但很笃定的答案。对资金和隐私问题,我宁愿多一次交接,也不建议自动补全。

是否可以把所有问题都转人工?

可以,但那不是 RAG 客服的目标。低风险、证据完整的常见问题应稳定自动回答;拒答策略的作用是把不能安全回答的部分分出来,而不是放弃自动化。

参考资料

  • Python dataclasses 模块文档
  • Python enum 模块文档
  • Python unittest 模块文档
赞(0)
未经允许不得转载:171主机测评 » 闪电智能 Voice Agent 如何为 RAG 设置“拒答”边界?从引用校验到人工兜底
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址