AI纠错RAG:这次怎么落地的

最近做产品时遇到个实际问题:RAG 系统上线后,总会蹦出一些似是而非的回答。

我们的场景下用的是向量检索 + BM25 混合,召回 5-10 个文档片段。

背景和问题

先说下场景。我们做的是技术文档问答系统,基于官方文档库给用户提供答案。用户提问后,系统会:

  1. 检索相关文档片段
  2. 把片段和问题塞给大模型
  3. 模型生成回答

这套逻辑看起来没问题,但实际跑起来就暴露出问题了。有次用户问"这个 SDK 支持哪些数据格式",模型回答了一大堆:JSON、CSV、XML、Parquet,甚至还提了"Protobuf"。

查了下文档,实际上就支持 JSON 和 CSV,其他的要么是高级特性要么压根不支持。模型为什么会这样?因为它可能在训练时见过"Protobuf"这个词,又在这个上下文里"觉得"可以往里塞。

这不是单一现象。随便测了一晚上,发现大概 15% 的回答多多少少有问题:有的把 A 产品的特性说成 B 产品的,有的把旧文档内容当成现行的,有的干脆把完全不相关的技术点凑在一起。

用户当然不会买账。技术问答最重要的就是准确,错了还不如不答。

需求梳理

既然问题出现了,就得想个办法。一开始我也想过要不要上重排序(Rerank),或者提升检索质量,但这些更多是解决"找不找得到"的问题,而不是"说得对不对"的问题。

想了想,核心需求其实是:

  1. 能检测出回答中可能存在的不准确内容
  2. 能在检测到问题时,触发重新检索或重新生成
  3. 能控制整体的时延,不能为了纠正错误让用户等太久

换句话说,需要的是一个"自我审查"机制:模型生成回答后,再检查一遍,发现问题就修正。

设计思路

先梳理下流程。如果把 RAG 看成"检索 + 生成",那么 Corrective RAG 就是"检索 + 生成 + 审查 + 修正"。

用一个简单的流程图来表示:

flowchart LR A[用户问题] --> B[检索文档] B --> C[生成初步回答] C --> D[自我审查] D --> E{审查通过?} E -- 是 --> F[返回回答] E -- 否 --> G[重新检索/生成] G --> D

关键在于 D 到 E 这段。怎么判断"是否通过"?不是让用户来判断,也不是人工校验,而是让模型自己来判断。

最朴素的想法:让模型看一遍自己生成的回答和检索到的文档,然后回答"这个回答是否基于检索到的文档内容?如果有不符合的地方,请指出"。

这个思路听上去简单,但落地时会遇到不少问题。先说大概的实现,再说踩过的坑。

实现步骤

1. 检索层先不动

先不折腾检索,用已有的检索能力。我们的场景下用的是向量检索 + BM25 混合,召回 5-10 个文档片段。

不先动检索是有原因的:如果连"找得到"都做不好,后面再怎么纠正也是白搭。但这个前提我们要默认成立。

2. 生成层多给一些约束

生成时可以加一些提示词约束,比如:

  • “严格基于检索到的文档内容回答”
  • “如果文档中没有相关信息,直接说不知道”
  • “不要编造文档中没有提及的内容”

这些提示词有点用,但不能彻底解决问题。模型有时还是会在"语境不完整"的情况下补全内容,因为它觉得这样更"自然"。

3. 增加审查层

这是核心。生成回答后,用同一个(或更强的)模型来审查。

审查的提示词可以这样设计:

任务:审查以下回答是否准确基于检索到的文档。

原始问题:{question}
检索到的文档:
{retrieved_docs}

生成的回答:
{generated_answer}

请回答:
1. 回答中是否有与检索文档内容相矛盾的信息?
2. 回答中是否有检索文档完全未提及的内容?
3. 如果有问题,请指出具体是哪些部分,并说明为什么有问题。

如果回答完全基于文档内容,请回答"审查通过"。

这里有个关键点:审查必须"具体到部分",不能只给一个"通过/不通过"的判断。因为我们需要知道"哪部分有问题",而不是仅仅知道"有问题"。

4. 根据审查结果修正

如果审查结果是"通过",就直接返回。如果有问题,则进入修正流程。

修正可以有两种策略:

  • 策略 1:重新检索。可能是检索词不够准确,或者检索库本身就没覆盖到,需要换一个检索策略。
  • 策略 2:重新生成。检索到的文档是合适的,但生成时"说多了",需要基于同样的文档再生成一次,这次更加约束。

在我们的实践中,策略 2 更常见。因为检索到的问题片段时,主要还是生成环节"放飞了"。所以默认走重新生成,只有在多次尝试都失败时,才考虑换检索策略。

5. 控制循环次数

不能让系统无限循环下去。一般来说,设置 2-3 次尝试就够了。如果三次都审查不过,就直接告诉用户"回答可能不准确,建议查阅官方文档"。

这不是"认输",而是尊重事实:如果系统的能力边界就是这里,硬要生成一个"好像对"的回答,不如坦诚说"我不确定"。

踩坑记录

坑 1:审查模型本身会犯错

刚开始时,我犯了个错误:认为审查是"客观判断",模型只要看了文档和回答,就能给出正确判断。

实际上不是这样。审查模型也可能"睁眼说瞎话"。有时它会把明显错误的回答判为"通过",有时又会把正确的回答挑刺。

原因在于:审查模型本身也是概率模型,它的判断取决于训练数据、提示词、上下文等多种因素。它不是"真理裁判",只是另一个"生成器"。

解决方法:在审查提示词中加入更多约束,比如要求"逐条说明问题",“给出文档中的具体原文作为对比”。这样即使判断不准,至少能给出依据,人也好介入。

坑 2:审查太严格,系统不敢回答

有一段时间,我把审查做得太严格了,几乎每个回答都会被挑出"可能有问题"。

问题在于,有些"问题"其实是合理的推断。比如文档说"支持 JSON 格式",模型说"支持 JSON 等结构化格式",严格来说"等"字没有文档依据,但从语义上看不算错。

如果把这类"合理推断"都判为错误,系统就变得过于保守,很难给出有用的回答。

解决方法:在审查提示词中明确"语义等价不算问题",“合理的概括和总结不算问题”。重点是"实质性矛盾"和"无关内容",而不是字面一致。

坑 3:修正过程时延太大

加上审查和修正后,整体时延明显增加。尤其是需要重新生成时,用户要等好几秒甚至十几秒。

这对体验有影响。用户问个问题,如果等太久,哪怕回答再准确,也可能不耐烦。

解决方法:只对关键问题做完整审查。比如可以把问题分为"简单"和"复杂",简单的走快速通道,复杂的才走完整审查。或者在第一次生成时并行启动审查,而不是串行。

坑 4:有些"错误"其实是检索问题

不是所有问题都来自生成环节。有时检索到的文档本身就不够准确,或者根本没覆盖到用户问的点。

这种情况下,即使审查发现了问题,重新生成也解决不了,因为输入的文档有问题。

解决方法:在审查时增加一个"文档是否足够"的判断。如果检索到的文档明显不足,就标记为"需要补充检索",然后换个检索词再试。

效果和边界

跑了一段时间后,效果是有改善的。最明显的是"硬编造"的变少了,系统变得更"谨慎"。

一些具体的变化:

  • 回答中"不支持的特性"这种说法减少了约 40%
  • 用户反馈"回答不准确"的比例从 15% 降到 8% 左右
  • 平均时延增加了 1.5-2 倍,但可接受

但也要说下边界。

首先,这个方法不是万能的。如果检索库本身就缺内容,或者文档本身有错,RAG 再怎么纠错也没用。

其次,审查和修正本身也可能引入新的错误。模型可能在修正时"矫枉过正",把本来对的内容改错了。

最后,时延和成本是必须要考虑的。多一次审查就多一次推理,多一次修正就再多一次。如果追求极致准确,成本会线性增加。

所以,真实落地时需要权衡:愿意为准确度付出多少代价?在哪些场景下值得做?在哪些场景下可以"差不多就行"?

结语

做 RAG 时,容易把"生成"当成终点,把模型当成"黑盒"。但实际上,模型生成的只是"概率上的可能",不是"事实本身"。

正确的做法应该是:生成之后,再加一层"事实校验"。这个校验可以是人,也可以是另一个模型,甚至可以是一个规则系统。

但无论用什么方式,核心思路是一样的:承认模型会犯错,并在系统层面增加纠错能力。

这次折腾下来,最大的感受是:别指望一次生成就完美,要给系统留出"修正空间"。这跟写代码一样,第一版能跑就行,后面再改。

技术如此,做产品也是如此。

参考

  • Corrective RAG (CRAG) 相关论文和实践
  • LangGraph 中关于自我修正的文档
  • 开源 RAG 框架中审查机制的实现

版权声明: 本文首发于 指尖魔法屋-AI纠错RAG:这次怎么落地的https://blog.thinkmoon.cn/post/380-ai-corrective-rag-error-correction-practice/) 转载或引用必须申明原指尖魔法屋来源及源地址!