编程与 AI12 分钟阅读更新于 2026-07-31

RAG 答案生成与引用溯源:让回答能回到原文

围绕RetrievalQA、自定义 Prompt、return_source_documents、stuff、map_reduce、refine 与 ConversationalRetrievalChain 的内容,讲清 RAG 最后一步如何生成答案、保留来源并做人工核对。

相关工具

RAG 的最后一步,不只是把资料交给模型

前面几步把文档加载、切分、向量化、检索和重排都做好以后,RAG 还剩一个容易被低估的环节:答案生成。很多系统到这里会直接把检索片段拼进 prompt,让模型写一段话。这样能跑,但很难上线,因为你不知道答案用了哪些材料,也不知道它有没有编造。

相关概念 在 RetrievalQA 章节里给了一个很清楚的做法:通过检索器拿到相关文档,再用问答链把这些上下文交给 LLM。更重要的是,可以设置 `return_source_documents=True`,让系统在返回答案的同时,把命中的源文档也带回来。

这一步决定了 RAG 是一个“看起来会答”的聊天框,还是一个可以检查、可以追责、可以改进的知识库问答系统。答案要写得通顺,但它不能只靠通顺赢。它要能回到原文,能解释为什么这么答,不能确定时还要敢说不知道。

RetrievalQA 答案生成链路图
RetrievalQA 答案生成链路

用户问题经过 Retriever 命中文档,文档和问题一起进入 Prompt 模板,最后由 LLM 生成带来源的答案。

Prompt 要先把回答边界说清楚

示例里的 Prompt 模板有几个很实用的约束:使用给定上下文回答最后的问题;如果不知道答案,就说不知道,不要试图编造;答案最多使用三个句子;尽量简明扼要。它没有写得很花,但每一句都在限制模型的行为。

RAG 的 Prompt 不应该只是“请根据以下资料回答”。那样模型会尽量帮你补全缺口,把资料里没有的内容写得像有依据。更稳的模板会明确三件事:只能使用上下文,缺证据时说明不知道,回答要短而具体。业务场景还可以加上“不要引用过期文档”“冲突时优先采用最新制度”“输出必须带来源编号”等规则。

这里不要迷信复杂模板。Prompt 的任务不是装饰答案,而是把检索结果、用户问题和回答规则放在同一个框里。规则越贴近业务失败案例,越有用;规则越像通用口号,越容易被模型忽略。

source_documents 是答案的证据链

在 相关的 RetrievalQA 示例中,设置 `return_source_documents=True` 后,结果里除了 `result`,还会有 `source_documents`。打印源文档时,可以看到 page_content 和 metadata,metadata 里有 source、page 这样的字段。这个结构非常关键。

用户看到的是答案,系统维护者要看的却是证据。答案说“这门课会涉及 Python”,你要能看到它来自哪份讲义、哪一页、哪个 chunk。否则一旦用户质疑,使用者只能重新猜:是检索错了,还是模型读错了,还是资料本身旧了。

source_documents 最好不要只留在后端日志里。面向用户的知识库可以显示引用编号;面向运营和客服的后台可以显示 source、page、chunk_id、命中分数、召回时间。答案一旦能回到原文,RAG 才有改进抓手。

答案引用与溯源结构图
答案引用与溯源结构

答案里的引用编号连接到源文档 metadata,方便回到原文核对。

stuff、map_reduce、refine 不是越高级越好

RetrievalQA 可以选择不同 chain_type。文中先用了默认的 stuff,也就是把检索到的文档一起塞进上下文,让模型一次生成答案。这种方式简单、快,只调用一次 LLM,但上下文窗口放不下太多材料。

MapReduce 的思路是每个文档先单独生成中间答案,再把中间答案合并。它能处理更多文档,但调用次数更多,速度更慢。示例里还出现了一个很现实的问题:MapReduce 有时结果反而更差,因为每个文档被孤立处理,分散在多份文档里的信息不一定能合起来。

Refine 会让模型按文档顺序逐步修正答案。它比 MapReduce 更能保留累积上下文,但也有成本和稳定性问题。工程上不需要一开始就追求复杂链。资料少、答案依赖集中时,stuff 往往更好调;资料多、证据分散时,再考虑 MapReduce 或 Refine。

答案不好,不一定是生成模型的问题

文中的实验很有代表性。第一次问“这门课会学习 Python 吗”,系统能答出会涉及 Python;接着追问“为什么需要这一前提”,普通 RetrievalQA 链并不能记住上一轮的指代,回答就跑偏了。这不是单纯的文笔问题,而是链路没有状态。

如果是单轮问答,RetrievalQA 足够简单;如果要连续追问,就要用 ConversationalRetrievalChain,把聊天历史和新问题合并成完整查询。文中对话检索链的流程是:先把历史对话与新问题合并,生成完整查询;再到向量数据库检索;然后回答,并把答案存入记忆区。

这提醒我们排查 RAG 时不要只看最后一句话。答案错了,可能是问题改写错了,可能是检索没有命中文档,可能是 prompt 没限制住模型,也可能是上一轮历史没有传进去。把链路拆开,才能知道该修哪一段。

对话型 RAG 要记录生成后的数据库查询

聊天机器人示例很细:它不仅保存 chat_history 和 answer,还保存 `db_query` 与 `db_response`。`db_query` 是最后发送给数据库的问题,`db_response` 是数据库返回的源文档。这两个字段对调试特别有用。

用户问“为什么需要这个前提”,人知道“这个前提”指的是上一轮的 Python,但向量数据库不知道。对话链要先把问题改写成可检索的问题,再去查资料。如果改写后的 db_query 不对,后面检索再好也没用。

所以对话型 RAG 的后台最好能看到四样东西:原始用户问题、改写后的数据库查询、命中的 source_documents、最终答案。少了其中任何一项,排查都会变慢。尤其是客服、教学、内部制度问答这类场景,追问很多,指代很多,db_query 记录几乎是必需品。

给答案做检查台,比事后猜错因更省时间

RAG 上线后,人工评估不应该只看“回答好不好”。最好让评估人员同时看到用户问题、生成答案、引用来源、人工判断和处理动作。答案正确但缺来源,要补引用;答案引用了旧文档,要修 metadata 或检索过滤;答案说不知道但源文档里有答案,要查召回和 Prompt。

这个检查台不一定复杂。早期可以是一张表:问题、答案、source、page、判断结果、备注。后面再加命中分数、chunk_id、重答按钮、重新检索按钮。关键是让每个错误都能归因,而不是留下一句“模型不稳定”。

文中的 `source_documents` 和聊天机器人 UI 示例,其实已经把这个方向讲明白了:用户看到聊天,使用者要看到数据库查询和源文件。答案质量是生成效果,也是检索、提示词、来源管理和评估流程一起作用的结果。

RAG 答案检查台 UI 草图
RAG 答案检查台

检查台把问题、答案、引用来源、人工判断和处理动作放在一起,方便定位错误来自哪一层。

让回答更可信,靠的是少编造和能核对

RAG 很容易被包装成“让大模型拥有私有知识”。这句话没错,但实际交付时,用户在意的是另一件事:答案能不能相信。可信不是模型语气坚定,也不是回答很长,而是它知道自己依据什么回答,也知道什么时候证据不够。

一条比较稳的答案生成链路应该是这样:检索器返回文档,文档带 metadata;Prompt 限制模型只根据上下文回答;返回结果时带上 source_documents;前台或后台展示引用;评估时能回到原文核对。这个链路不花哨,但它能把 RAG 从演示推进到可维护。

如果只能先做一个改动,我会先打开 `return_source_documents`,并把引用来源展示出来。因为一旦答案有了来源,后面的优化才有方向。没有来源的 RAG,只是在更认真地聊天;有来源的 RAG,才开始像知识库系统。

常见问题

为什么 RAG 答案一定要返回 source_documents?

因为 source_documents 能说明答案依据了哪些原文片段。没有来源,答案错了很难判断是检索问题、文档问题还是生成问题。

stuff、map_reduce、refine 应该怎么选?

资料少、上下文放得下时优先用 stuff;文档很多时可以试 map_reduce;证据分散且需要逐步修正时再考虑 refine。

对话型 RAG 为什么要记录 db_query?

追问里常有“这个”“它”“为什么”等指代。db_query 能显示系统最后拿什么问题去查数据库,方便排查问题改写是否正确。

RAG、知识库与长期记忆

继续阅读

返回专题