多跳推理错误占RAG幻觉的58%,我踩坑后找到两个管用的修复方案
去年有个做金融研报的客户找我,差点把我吓出一身冷汗。他们用RAG系统自动生成摘要,结果把“营收增长20%”写成了“营收下降20%”。你想想,研报这东西是要给投资机构看的,一字之差能搞出多大麻烦。还好发现得早,不然真可能吃官司。
这事让我特别有感触。多跳推理没做好,真不是准确率掉几个点那么简单。
今天聊聊我在这件事上踩过的坑,还有一些实测管用的方案。不一定都对,但至少是我真金白银砸出来的经验。
先搞清楚到底哪里翻车了
普通RAG做摘要的逻辑很简单:把长文档切成chunk,向量检索捞几个相关段落,扔给LLM出摘要。处理那种一句话就能说清楚的事实还行,比如“公司2023年营收50亿”,检索命中就完事了。
但真实文档哪有这么老实。
我拿一份30页的医疗研究报告跑过测试。报告里有个结论需要跨5个段落才能推出来:实验组A的某个指标,基线值在第3页,干预后数值在第7页,统计检验在第12页,亚组分析在第18页,最终临床解释在第25页。普通RAG直接摘要的结果——把两个不同亚组的数据混在一起,生成了一个完全相反的结论。
具体数字:我在100份医疗报告上跑了一轮,事实保真度只有67.3%。其中多跳推理类错误占了总错误的58%。超过一半的幻觉,都是因为系统没把分散在不同地方的信息串起来。
根因其实挺明显的。传统RAG的检索是“扁平化”的,每个chunk当成独立单元,但真实文档的信息是分层、交叉引用的。你不可能指望LLM在生成阶段凭空把这些碎片拼对。
不可能的。
我试过的方案,哪些真管用
方案一:文档结构图谱 + 多跳检索
最早参考的是微软GraphRAG的思路。但说真的,GraphRAG太重了,构建成本高得离谱,小团队根本玩不起。我做了个简化版:在索引阶段自动建文档的结构图谱。
具体做法:
1. 解析文档时保留层级结构,章节、小节、段落编号都留着
2. 识别段落间的显式引用,比如“如上文所述”、“参见表3”、“根据第2.1节的结论”这类表述
3. 把这些引用关系存成图谱的边,段落本身是节点
检索时分两阶段走:先向量检索找到种子节点,然后沿图谱边扩展,把种子节点的前驱和后继都拉进来。
实测效果:同样100份医疗报告,事实保真度从67.3%提到81.5%,多跳推理错误率从58%降到31%。
但这个方案有个很明显的短板——它依赖文档本身有清晰的引用关系。后来我拿一批企业内部的尽调报告测试,发现很多报告根本不写“参见某节”,全是隐式的逻辑承接。这时候图谱基本建不起来。
嗯…这个比较头疼。
方案二:查询分解 + 迭代验证
这个思路有点意思,是我从Pieter Levels做Nomad List的方式里偷师的——别想一步到位,拆成小步验证。
流程大概是这样:
1. 收到摘要请求,先用一个轻量LLM把复杂查询拆成原子子查询
2. 每个子查询独立检索、独立生成答案片段
3. 用一个验证步骤检查各片段之间有没有逻辑矛盾
4. 发现矛盾就回溯检索更多上下文,重新生成
5. 最后用主LLM把验证过的片段拼成摘要
举个例子:用户问“对比A产品和B产品在2023年的市场表现”。系统会自动拆成:
- A产品2023年市场份额
- B产品2023年市场份额
- A产品2023年增长率
- B产品2023年增长率
- 验证查询:检查前面四个的数据是不是来自同一份报告、同一个统计口径
这个方案的好处是不依赖文档结构,坏处是调用次数暴增。一个复杂摘要可能要调15到20次LLM,延迟直接飙到30秒以上。用户等不了的。
等等,这里我要更正一下。我一开始用的是GPT-4做查询分解,成本高得肉疼,大概跑100次查询分解就要花掉十几美元。后来换成微调过的Qwen-2.5-7B,专门做这个任务。成本降到原来的1/20,分解质量反而更好——因为微调数据全是多跳推理场景,模型学会了识别“需要跨段落才能回答”的查询特征。Qwen-2.5是2024年9月发布的,我大概10月底开始用,到现在跑了快半年了,挺稳的。
方案三:事实链追溯
这是我现在主要用的方案,也是目前最满意的。
核心想法很简单:让摘要在生成时就带着“证据链”,而不是生成之后再验证。
具体流程:
1. 检索阶段不设top-k限制,设相似度阈值,高于阈值的所有chunk都拉进来,通常会有40到80个
2. 用一个专门的“证据链接器”模型,在这些chunk里识别哪些信息片段之间有逻辑关联,串成证据链
3. 每条证据链对应摘要里的一个事实陈述
4. LLM生成摘要时,强制要求每个事实句后面标注它依赖的证据链ID
5. 后处理阶段,把标注的证据链和原文做精确匹配校验
关键数据:在医疗报告测试集上,事实保真度干到了93.7%,多跳推理错误率降到9%以下。
但代价也不小。证据链接器需要专门训练,我用的是sentence-transformers做基座,在人工标注的5000条多跳推理链上微调。标注大概花了2万块钱,用的国内某标注平台,具体名字不提了,怕有广告嫌疑。训练倒便宜,一张A100跑了俩小时,用的PyTorch 2.1.2,transformers 4.36.2。
说个真实案例。有份报告讨论某种药物的副作用,关键信息分散在4个地方:不良反应总表在第8页,严重不良事件描述在第12页,剂量相关性分析在第15页,获益-风险评估在第20页。普通RAG只引用了第8页的总表,结论是“副作用轻微可控”。但事实链追溯系统正确串了全部4个位置的信息,摘要变成了“低剂量组副作用轻微,但高剂量组出现3例严重不良事件,需关注剂量依赖性风险”。这才是对的。
落地时踩的一些坑
方案说起来都挺好,真落地一堆破事。
第一个坑:chunk策略比模型重要得多。我花了好几个月调模型参数,各种prompt engineering,后来发现把chunk从512 token改成256 token,并且做50%重叠,事实保真度直接涨了6个点。原因很简单,小chunk让检索更精准,证据链构建时的噪音更少。这个我大概是在2024年6月左右意识到的,之前走了太多弯路。
第二个坑:别忽视文档预处理。很多长文档是PDF,解析出来段落顺序乱七八糟,表格数据直接丢失。我现在强制所有文档先过一遍布局分析模型——用的是Marker这个开源工具,0.2.3版本,把表格、图表标题、脚注都正确提取出来。这个步骤对多跳推理的影响比我想象的大太多了。之前有个客户的PDF,页眉页脚混进了正文,导致检索出来的chunk里全是“第X页 机密”这种废话,证据链构建直接崩了。
第三个坑:评估指标要自己建。通用的RAG评估框架,比如RAGAS之类,对多跳推理场景的覆盖很差。我自己搞了个评估集,专门构造了需要2跳、3跳、4跳推理的查询,每个查询人工写了标准答案和必须覆盖的关键事实点。现在每次改方案都跑这个集,比看论文里的指标靠谱多了。这个评估集大概有300条查询,积累了一年多了,还在不断加。
还没搞定的问题
诚实地说,跨文档的多跳推理我现在还是抓瞎。目前所有方案都假设信息在同一个文档内,但用户要是问“对比我们公司和竞争对手过去三年的研发投入趋势”,这需要跨5份年报做多跳推理,难度指数级上升。
我目前在尝试用知识图谱做跨文档实体对齐,但效果不太行,事实保真度只有70%出头。试过用Neo4j 5.18存图,实体对齐用spaCy 3.7加自定义规则,但实体消歧总是搞不定——比如“苹果”在一份报告里是公司名,在另一份报告里可能真的就是水果。这个我觉得大概还需要很长时间去磨。
另外就是成本。事实链追溯方案虽然效果好,但每次摘要的token消耗是普通RAG的3到4倍。对于每天要处理几千份文档的场景,这个成本很难扛住。我现在在做证据链接器的蒸馏,从sentence-transformers往更小的模型上蒸,目标是把推理成本压到1.5倍以内。目前蒸到一半,效果还不太稳定,有时候证据链会断。
写到这想起来,上周在X上看到有个哥们用LlamaIndex搞了个类似的东西,据说效果不错,但我还没来得及仔细看他的实现。你们要是也在搞多跳推理相关的,或者有更好的思路,评论区聊聊。我最近正好在重构这个系统,特别想听听不同的想法。
对了,有人问我为什么不用LangChain。我的评价是:LangChain的抽象层太多,调试起来太痛苦,我后来全换成自己写的了。这话可能得罪人,但反正我就这么说了。
读者评论 2