RAG 该在什么时候「拒绝回答」?一个带三处检查点的 Demo,附 20 例评测数字

4 阅读4分钟

先说结论

我把一个质检场景的 RAG Demo 开源了(TriageOps,仓库在文末)。它最值得看的不是准确率,而是它在哪里停下来:

指标数值怎么读
用例数20固定种子,可复跑
决策与规则引擎一致率100%自洽性检查,不是准确率
升级人工比例25%5 例主动放弃作答
检查点失败次数6全部是"缺过程能力记录"
平均工具调用次数5.55上限 6,超了升级
测试数36规则、工具参数、检查点、引用校验
单例平均耗时~9 msmock provider,无网络
20 例 token 成本$0.0187按 gpt-4o-mini 价格估算

注意第二条:我没有把它写成"准确率 100%"。因为期望答案本来就是同一个规则引擎算出来的,拿它去比模型输出,只能证明"流水线没有把策略答案带偏",证明不了对未见问题的准确率。这个区别在给客户交付时非常要命——写错一次,后面所有数字都会被质疑。

为什么写这个

市面上的 Agent / RAG Demo 大多在演示"模型很聪明":问一句,答一段,截图很好看。但真正交付时会吵架的地方,是证据缺失和模型答错这两种情况:

  • 检索回来的文档里没有这条规则,模型照样能编出一段很像的话
  • 引用编号张冠李戴,用户去翻原文发现对不上
  • 工具调用失败,流水线继续往下走,最后给出一个自信的错误结论

这三件事不会出现在 Demo 视频里,但会出现在验收会上。

三处硬检查点

整个流程是"intake → evidence → decision"三段,每段之间有一个可以失败的检查点:

1. intake:分类必须是已知枚举,且能识别出工单号。识别不出直接升级,不进入检索。

2. evidence:至少有 1 个文档来源 + 1 条生产记录;如果是与测量相关的缺陷代码,还必须有过程能力(Cpk)记录。工具循环上限 6 次,给一次重试机会,仍然不足则升级。

3. decision:决策由规则代码算出来,模型只负责写理由;理由里每个 [evidence-id] 都要和证据集合比对,不存在的 ID 会被拒。

第 3 条里的引用校验值得单独说。实现大致是:解析模型输出里的引用 ID → 与本次证据集合求差 → 有未解析的 ID 就重新生成一次 → 仍然失败,则用真实证据 ID 重写这句话,而不是把假引用放出去。代码在 app/agent.py 的 _enforce_citations。

两个设计取舍

决策交给代码,模型只写解释。 质保是否覆盖、是否需要返工,这些是算术加政策条款,本质上是确定性的。让模型"决策",你没法回归测试,而且一段错的决策在日志里和一段对的决策长得一模一样。

Cpk 用 Python 算,不让模型算。 模型生成的 Cpk 看起来更像模像样,但这恰恰更危险,因为它看起来权威。

分阶段,而不是自由 ReAct。 自由工具选择在同样的 20 个用例上能跑出"正确但不可重复"的路径——同一个问题两次跑,证据集合不一样。分阶段让停止条件显式化、可审计。

技术栈

刻意做得极简,方便在客户内网跑:

  • 检索:BM25 + TF-IDF 混合打分,纯标准库,无 embedding 服务、无向量库、无网络
  • 存储:sqlite
  • 语料:8 份合成的程序文件(质保政策、供应商政策、SPC 标准、CAPA 流程等)
  • 工具:6 个,全部严格校验参数
  • UI:Streamlit,三个页签(跑单例 / 评测 / 护栏)

怎么跑

git clone https://github.com/ShaohengXui/triageops-demo
cd triageops-demo
pip install -r requirements.txt
python scripts/seed_data.py      # 生成合成数据,固定种子 42
python -m app.cli --eval          # 复跑上面那张表
streamlit run app.py              # 打开界面

Windows 上跑脚本前记得设 PYTHONIOENCODING=utf-8,否则打印路径会乱码。

它不做什么

  • 数据全是合成的,脚本固定种子生成,不是客户数据,也不代表任何真实工厂
  • 不承诺准确率。任何在跑评测之前给出的准确率数字,都是编的
  • 只覆盖一个语料、一个部署目标,不是托管服务

链接

如果你也在做企业 RAG,欢迎在评论区说说你遇到的"证据不足"场景是怎么处理的。我目前比较想聊的是:什么时候该升级给人工,这个阈值到底怎么定——我现在用的是置信度 0.7 加检查点双保险,但阈值本身没有理论依据,只是经验值。