1. 为什么“证据到手”之后,答案依然会翻车
先聊个现象:很多团队把检索增强生成(RAG)跑起来之后,发现检索精度明明做得不错,Top 5 里已经躺着正确答案了,但最终大模型吐出来的回答要么是丢三落四,要么是把几条互相矛盾的信息糊在一起,甚至有几次直接无视了证据、凭“记忆”编了一段。我最早做知识库问答时也踩过这个坑,检索引擎那边已经做到 85% 的命中率,结果端到端评估一问,答案正确率只有 60% 出头。问题显然出在“检索之后”的环节,也就是响应生成。
响应生成(Response Generation)是 RAG 架构里最后一段路,也是决定用户看到的最终文本是否丝滑、准确、完整的关键关卡。输入是检索回来的证据块(passages)、用户原始问题,以及偶尔附带的历史对话记录,输出是最终答案。这一步看起来只是“把证据塞进 Prompt 里让模型写”,但实际上,证据怎么摆、摆多少、怎么处理冲突、怎么约束模型不要放飞自我,每一处细节都在影响答案质量。
这篇文章的目标读者是已经在做 RAG 应用、或者准备把知识库问答落地到生产环境的开发者。我会用一个从“检索到的证据”到“最终答案”的完整流程为主线,拆解证据筛选、重排、提示词组装、生成策略、质量兜底这几个环节,并穿插我在实际项目中遇到过的问题和调试过程。你会发现,响应生成不是“写一段 Prompt 就完事”,而是一套需要反复打磨的装配流水线。
2. 证据组装前先解决三个前提问题
在把证据塞进 Prompt 之前,首先要确认手里的“原料”是能直接用的。很多问题在证据阶段就埋下了,到了生成阶段才会爆炸。所以响应生成的第一个模块不是生成,而是证据的预处理。这里我通常按三个问题来排查。
2.1 证据是否真的与问题对齐
检索返回的 Top K 个片段,看起来都跟问题关键词有重合,但语义上可能根本不是一回事。比如用户问“今年的退款政策是什么”,检索到的片段可能是去年的一条公告,关键词高度相似,答案却早就失效了。证据与问题的对齐,不能只看词面,还要看语义和时效。实践中我习惯在进入提示词模板之前,先做一轮轻量级的语义校验,比如计算问题向量与每个证据向量的余弦相似度,低于一定分数就直接拦下。这么做不一定能完全解决错配问题,但能明显降低把不相关证据塞进上下文的概率。
还有一类情况是证据本身是正确的,但它对应的子问题与用户意图粒度不一致。用户问的是“退货需要哪些材料”,检索回来一个很长的服务条款片段,里面确实包含材料清单,但夹杂在大量不相关的段落里。这种情况下,直接整段塞给大模型,回答很容易把无关条款也带进结果。所以预处理时最好把长片段按段落、按语义边界切分,让每个证据单元尽量只覆盖一个主题。
2.2 证据之间是否存在冗余
检索系统为了召回率,往往会返回多个来源相同、内容重叠的段落。比如三个片段都在讲同一份政策的同一款条文,只是措辞略有不同。如果全塞进 Prompt,大模型可能被冗余信息带偏,回答冗长,甚至在某些情况下把所有变体都“合并”成一句包含幻觉的总结。我在项目里处理过的最夸张的例子,是 5 个证据里有 4 个在重复同一件事,模型最终把其中一句话里不存在的细节当成了额外信息,写进了答案。
因此,证据在进入 Prompt 前要做去重。最简单的手段是计算片段之间的文本相似度(比如 MinHash 或嵌入向量的余弦距离),把相似度超过阈值的片段合并保留一个。稍微精细一点的做法是“按需保留”:优先保留与问题语义最相关且信息量最大的片段,其余直接丢弃,而不是简单保留第一个。
2.3 证据之间是否存在矛盾
这是响应生成里最容易出问题、也最容易被忽略的一环。不同来源的证据可能说法不一致,甚至同一来源的不同版本之间也存在冲突。如果模型没有被告知“该信谁、以谁为准”,它通常会把冲突信息揉在一起,生成一个看起来平滑但实则不准确的答案。
矛盾检测不能指望在找 Prompt 里硬写“如果矛盾,请指出”,模型往往会选择迁就最新出现的证据。我建议做一个“证据分组”的步骤:把回答同一问题的证据归为一组,组内按来源权威度、时间戳排序,在提示词里显式标注权威度和时间。比如“证据 A 来自最新版官方手册,证据 B 来自三年前的博客”,让模型有依据地做取舍。关于这个点,后面讲提示词组装时我会给出一个具体模板。
3. 提示词组装:证据的“摆盘”方式决定答案走向
拿到干净、不冗余、不冲突的证据之后,接下来就是怎么把它们组织成提示词。这块很多人直接用一个硬编码模板:“以下是一些参考信息:... 请回答问题。” 这种做法能跑通,但离“高质量”还有距离。我把它拆成几个可调节的设计维度。
3.1 证据排序:先抛结论还是先抛背景?
Prompt 里证据的排列顺序,会显著影响模型的注意力分布。一般来说,我建议把与问题最匹配、信息最完整的证据放在靠前位置,作为“主证据”;其他补充证据放后面。这个策略来自一个很朴素的观察:大模型对上下文前部的信息更敏感,尤其是长上下文场景下,中间部分容易被“稀释”。
但这里有一个例外:如果你希望模型先做全局理解再做判断,而不是直接复用某一段话,那么把不同角度的证据交错排列反而效果更好。我在一次测试中发现,对于“某个设备三种故障模式如何区分”这类问题,如果三个证据按故障类型并列摆放,模型回答时更容易做对比分析;一旦把某个证据放在最前面,它就会不自觉地偏向那个故障类型。所以证据排序不是固定的,要看问题是“选一个答案”还是“比较多个答案”。
3.2 证据标记:让模型知道每段文字的身份
原始检索片段是没有“身份”的。你要主动告诉模型:这一段是官方文档,那一段是论坛用户评论,这一段来自 2019 年,那一段来自今年。我常用的一种做法是给每条证据加元信息前缀,类似文献引用标记:
[1] 来源:产品操作手册 v2024-03,第22页;字段:退款条件。 [2] 来源:官方常见问题页(更新于2024-06);字段:退货流程。这样一来,模型在生成时能更好地把“证据归属”和“答案陈述”区分开,减少对不同来源信息的无差別混用。更重要的是,当你要求模型引用来源时,它能准确引用到具体编号,而不是凭空编一个链接。
3.3 指令分层:把“要求”分成硬约束和软建议
提示词里的指令不要一锅炖。我习惯分层:
- 硬约束:必须只基于给定证据回答;不得编造证据中不存在的内容;如果证据不足,明确说不知道。
- 软建议:尽量用通顺自然的语言组织答案;可以适度提炼证据核心意思,但不改变事实。
硬约束要放在系统提示词里,软建议可以放在用户消息末尾。这样做的原因是,系统提示词在所有轮次中会持续生效,模型对它的一致性执行能力更强。而软建议放在末尾,相当于一个“润色风格提示”,不会干扰事实性控制。
3.4 控制长度与结构:问题怎么问决定答案怎么长
想让答案简洁,就得在提示词里给出结构约束。光写“请简要回答”,模型还是会啰嗦。我常用的做法是用一个“输出格式”字段把它焊死:
回答格式要求: - 第一句直接给出结论,不超过50字; - 随后最多列出3个要点,每个要点不超过80字; - 如果证据不足以回答,直接输出“当前资料中未找到相关信息”,并补一句可能相关的线索。你会发现,模型对这个结构的遵循程度,明显高于“请回答得简洁一点”这种模糊指令。这也是响应生成环节里性价比最高的优化之一。
4. 核心实现:从提示词模板到生成参数的全链路配置
理论和设计讲了一堆,下面给出一个可以直接落地的参考实现。这里的模板和参数,是我在多个项目里调试过的基线配置,不一定完美,但至少能帮你跑出一个及格分以上的结果。
4.1 基础提示词模板(可直接复制调整)
[系统指令] 你是一名严谨的问答助手。回答时只能依赖用户提供的“证据片段”,不得使用你自己的常识或记忆补充证据中不存在的事实。 硬性要求: 1. 所有结论必须来自下列证据,并在回答中标注对应证据编号,例如 [1]。 2. 如果证据不足以回答用户问题,请明确说“现有资料不足以回答该问题”,不允许强行编造。 3. 不要对不同证据中的信息做无依据的拼接。 4. 回答语言与用户问题语言一致。 [证据片段] [1] 来源:官方产品手册(2024-03)| 内容:退款申请需在购买后7天内发起,且商品未拆封。 [2] 来源:官方帮助中心FAQ(2024-06)| 内容:跨境订单的退货周期可延长至14天,需提供海关申报凭证。 [3] 来源:社区用户反馈(2023-11)| 内容:有用户表示退款到账时间为5-7个工作日。 [用户问题] 跨境订单的退款周期是多久?需要哪些材料? [输出格式] 第一句:直接回答核心问题,不超过60字。 第二段起:分点列出关键条件,每点不超过80字。 最后:用“【证据依据】”列出本次回答引用的证据编号。你可能会注意到,这个模板里把来源信息直接嵌入证据编号,让模型在回答时分得清出处。实际测试里,这种带“来源:时间/属性”的标记,比光秃秃的文本片段更能压低编造率。
4.2 关键生成参数:温度与 top-p 怎么调
响应生成环节的大模型推理参数,不能照搬通用聊天场景。我整理过一组常用的起点:
| 参数 | 建议值 | 说明 |
|---|---|---|
| temperature | 0.1 ~ 0.3 | 控制在低随机性区间,减少创造性扩写 |
| top_p | 0.5 ~ 0.8 | 配合 temperature 使用,进一步限制候选词范围 |
| max_tokens | 根据答案类型设置 | 短问答 200 左右;解析长文 500 ~ 800 |
| frequency_penalty | 0.3 ~ 0.5 | 适度抑制重复和冗余表达 |
| presence_penalty | 0.2 ~ 0.4 | 避免模型反复提及同一证据 |
我一般先固定 temperature=0.2,再通过 top_p 微调。有个容易踩的坑:不要同时把 temperature 调得很低又把 top_p 调得很高,这样模型既不敢冒险,又在候选词里选不到精准词,输出会变“平”且机械。相反, температуре 过高配合 top_p 过低,则容易胡诌。两组参数要联动着调。
4.3 证据量控制:不是越多越好
上下文里的证据条数,我建议控制在 3~5 条之间。少于 3 条,信息覆盖率不足,模型容易因“缺料”而补写;多于 5 条,模型的选择压力大增,可能把不那么相关的证据也写进答案,导致噪音上升。
这里我还会做一个“证据裁剪”的动作:在进入 Prompt 之前,按语义相关性排序,只保留 Top 3~5 条。如果某条证据本身是长段落,还会按 200~400 字切块,优先取最相关的一块。这个切块的粒度要和检索系统使用的切块粒度保持一致,否则前一步召回的高分段和这里的裁剪逻辑会互相打架。
4.4 融合检索与反馈:调试时的增量策略
响应生成不是一次性配置完就固定的。我在项目里习惯用一个“增量测试法”:先把搜索 Top 1 的证据单独喂给模型,看它能不能给出准确答案;如果不能,再逐渐加证据。这样做的好处是,你能把问题定位到“证据不够”还是“生成不行”两个层面。如果 Top 1 证据足够但答案错了,那大概率是提示词约束不到位;如果 Top 3 一起上才答对,说明检索相关性还有优化空间。
5. 实操中的常见问题与排障实录
这一节分享几个我在真实项目里反复遇到的情况,以及对应的排查思路。每一条都来自实际调试记录,比理论更有参考价值。
5.1 模型无视证据、直接凭内部知识回答
现象:即使用户问题对应的证据已经出现在上下文里,模型依然给出一个“听起来很合理”但和证据不符的回答。原因通常是系统提示词里的“仅依赖证据”约束不够强,或者被放在不易感知的位置。
排障方法:
- 把“只可依赖给出的证据,禁止使用先验知识”提升到系统消息的第一句。
- 在每个证据块前增加质量标记,例如“【可信度:官方来源】”。
- 打开模型的“证据引用”开关(部分推理服务支持强制引用模式)。
- 如果还不行,则换用更强的提示策略:要求模型逐段引用证据原文再改写,而不是直接作答。
5.2 回答冗长、车轱辘话反复说
现象:答案明明一句话能说完,模型却铺陈了三段,且中间夹杂重复语义。这通常不是因为模型“话多”,而是提示词里给了太多宽松的改写空间。
排障方法:
- 在输出格式中强制“第一句直接给结论”,并限制字数。
- 降低 frequency_penalty 的负值方向,比如把它从 0.3 提到 0.8,能显著减少重复词。
- 检查证据块本身是否重复,去重没做干净的模型就会逐条“汇报”,自然啰嗦。
5.3 多个证据冲突,模型各打五十大板
现象:两个证据一个说“可以退款”,一个说“不能退款”,模型的回答是“退款政策视情况而定,一般可以退款,也存在不能退款的情况”,等于没说。这种情况的根因是证据没有明确的优先级信息。
排障方法:
- 在证据元信息中增加“is_current”字段,并在提示词指令中写明“优先采用最新版本/权威来源”。
- 用一种更直接的策略:如果证据存在明显冲突,先让模型输出“检测到冲突”再作答,而不是无缝拼接。
- 在数据层解决:提前做一次冲突消解,只保留高置信证据,从源头避免把冲突带给模型。
5.4 引用编号张冠李戴
现象:模型在回答末尾写“【证据依据:[2]】”,但实际引用内容是 [1] 的。这是提示词里没有强调“引用必须与内容一致”导致的。
排障方法:
- 在输出格式中要求“每个要点末尾标注实际支撑该要点的证据编号”,而不是在回答最后统一列。
- 检查是否因为证据编号变化导致模型记忆混乱,比如动态调整了证据顺序。建议会话内保持证据编号稳定。
5.5 证据不足时模型强行作答
这是 RAG 里比较难缠的幻觉来源之一。模型被训练得“必须回答问题”,即使证据不足,它也会启动补全机制。单纯靠提示词“如果没有资料就说不知道”有时不够。
我试过最有效的方式是给模型一个“逃生出口”:在提示词末尾加一句“如果证据不完全支持回答,请输出‘现有资料不足以回答,我补充了以下推测’,并在正文前做区分”。把“不知道”变成一种可接受的输出,而不是强迫它硬答。另外会在后处理逻辑中拦截“推测”标记,再决定是否转人工或多轮追问。
6. 工程落地:把响应生成做成可观测的流水线
上面这些策略,如果只停留在单次调测层面,很难长期稳定。我在项目里会把响应生成拆成一组独立可观测的服务模块,每步都打日志、存指标。这里分享一套比较实用的流水线设计。
6.1 模块划分与职责边界
- 证据预处理模块:负责相关性筛选、冗余去重、元信息标记。
- 证据冲突检测模块:对同一问题下的多证据做一致性比对,输出冲突信号。
- 提示词组装模块:按预设模板拼接系统提示词、证据块、问题、输出格式。
- 生成模块:调用大模型 API,记录请求与响应日志。
- 质量校验模块:做规则化校验(是否引用缺失、是否包含幻觉高发词、字数是否超限),必要时触发重答逻辑。
模块之间通过 JSON 传数据,每层只关注自己的职责。这样一旦某次回答质量差,你能快速定位是证据错、模板错还是模型错,而不是对着整段 Prompt 瞎猜。
6.2 可观测指标怎么设计
响应生成的观测指标,我比较关注这五个:
| 指标 | 计算方式 | 说明 |
|---|---|---|
| 证据引用率 | 回答中包含证据编号的句子占比 | 偏低说明模型在“裸答” |
| 引用准确率 | 抽样检查编号与内容是否一致 | 反映引用可靠性 |
| 答案完整性 | 人工或LLM打分层级:覆盖要点数/应覆盖要点数 | 越接近1越好 |
| 幻觉率 | LLM交叉检查回答中的事实是否都能在证据中找到 | 越高越危险 |
| 平均响应延迟 | 从证据入Prompt到首字返回的时间 | 反映工程效率 |
前三项可以在离线阶段用测试集做;后两项需要线上流量采样。我在实践中会把幻觉率的检测做成一整天跑一次批任务,把前一天的所有问答对拉出来复查,一旦发现异常趋势就主动调模板。
6.3 后处理兜底:当生成结果不达标时怎么办
生成结果不达标的兜底机制有几种,按触发优先级排:
- 校验不通过则重答一次:把 temperature 降到 0.1,减少随机性。
- 重答仍失败的,转为“资料不足”模板:不强行给答案,而是引导用户补充关键词。
- 对于高风险场景(如医疗建议、法律条款),始终附加“人工复核”标记,不对最终结果做自动承诺。
这套兜底逻辑,可以在不做复杂模型微调的情况下,把线上由于随机性导致的偶发错误压到最低。它解决的不是“模型笨”,而是“模型飘”。
7. 再讲两个容易被忽略的生成细节
响应生成虽然已经在前面做了很多控制,但还有两个细节在实践中影响很大,我单独拿出来说。
7.1 一定要“先过滤后生成”,不要在生成后指望清洗
很多人喜欢在生成后用规则把不想要的句子删掉,比如去掉“根据我的理解”这种默认套话。这种思路可以修饰措辞,但救不了事实错误。事实层面的问题,必须在生成前通过证据控制解决,因为大模型一旦把错误信息编进句子,后置过滤很难精准识别哪些是幻觉。我给团队立的规矩是:后处理只做格式校验,不做事实纠错。
7.2 知识体系里的“术语一致性”要靠术语表约束
如果你做的领域是医疗、法律、金融,每个细分的概念都有严格的定义。模型可能会把“确诊”和“疑似”混用,或者把“授权数”和“成交数”划等号。与其靠提示词反复叮嘱“请注意术语”,不如在提示词里嵌入一个“关键术语表”,把高频易混的词语和标准定义列出来,并明确要求“凡涉及这些术语,必须以术语表定义为准”。这个策略特别适合专业领域的问答系统,我用下来的效果比追加几百字自然语言约束要好得多。
8. 从单轮到多轮:证据记忆如何延续
前面讲的内容基本是单轮问答。一旦用户连续追问,比如“如果我在7天后申请呢?”“那海关凭证需要原件吗?”,响应生成就不能只看着当前证据,还得把前几轮的关键信息作为上下文带上。
这里有一个常见的坑:把上一轮完整答案直接塞进这一轮的上下文。这样会导致模型把上一轮的推测、引用、语气一起带进来,污染当前答案。我建议用一种“结构化摘要”的方式,把历史关键事实抽出来,转成“当前已知信息”列表,而历史回答的具体措辞就丢掉。
多轮场景的证据组装,本质上是在做“跨轮证据的累积与更新”。若新证据和旧证据冲突,需要明确出现新证据生效、旧信息作废的指令。这个如果在第一轮就设计好,后续轮次的响应生成会省很多事。
9. 评估响应生成质量的两个可复用方案
很多读者会问:响应生成的质量怎么量化评估?总不能每次都靠人工读。我给出两个我实际在用的方案,难度不同,按需选用。
9.1 规则+抽样人工复核(轻量级)
先制定一组可量化的规则,比如引用编号是否出现、字数是否超出上限、是否包含“可能”“大概”等模糊词。跑完自动规则后,抽 10%~20% 样本人工复核事实准确性和表达自然度。这个方法搭建快,适合冷启动阶段。
9.2 LLM-as-a-Judge 自动评估(重量级)
用一个更强大的模型(或者同一模型但加载“评估专用提示词”)对回答进行多维打分,维度包括忠实度、完整性、相关性、简洁性。为了避免评估模型自身对“流畅答案”偏爱而放掉事实错误,我通常会在评估提示词里内置“忠实度优先”的权重说明,并且在测试集里故意加入一些含幻觉的破解样本,看评估模型能否识别出来。
这两种方案结合使用,基本能覆盖从开发期到上线后的质量追踪需求。
10. 结尾:一点个人体会
响应生成这块,我踩过的最深一个坑是“把大模型当万能生成器”,以为只要检索得好,答案就一定好。做了几轮数据优化之后才明白,证据和答案之间的距离,靠的不是模型的神力,而是工程上的约束和诱导。每加一条提示词规则、每调一个温度参数、每次做一次冲突消解,都是在缩小那个“从证据到答案”的沟。
如果你正准备做RAG应用,我的建议是:先花时间把证据预处理做扎实,再进入提示词调优。别急着换更大参数的模型,很多问题在证据层面就解决了。响应生成这门手艺,真正难的不是写生成代码,而是知道什么信息该进上下文、什么信息不该进,以及如何让模型沿着一条可控的路把证据变成人话。