这道题其实问的是:在[[RAG]]的生成阶段,怎么把一堆检索回来的文档整合进Prompt,让模型既准确又有依据,还能连贯回答。说白了,核心就是平衡两件事,一是文档和问题的相关性,二是模型输出的可控性。我不会一上来就堆策略,而是先想清楚这个Prompt到底该怎么组织。
我一般会用一个四层架构来设计。先讲第一层,上下文预处理。这块不能把检索结果直接往里塞,得先做筛选、排序和去重。比如相关性分数低于阈值的直接扔掉,保留Top-K,通常是3到5篇。排序上我会综合[[BM25]]分数、向量相似度还有时效性来加权。去重这块有个坑,就是语义重复,比如两篇文档用不同措辞说同一件事,得用[[Embedding]]聚类做语义去重,否则模型容易被重复信息带偏。
第二层是上下文注入的结构化模板。我习惯把Prompt分成几段:先是系统指令,告诉模型你是基于检索文档回答的助手,必须引用来源;然后是检索上下文,每篇文档按「内容【来源:URL】」的格式列出来;接着是用户查询;最后是输出约束,比如先给答案,再标注引用编号,不确定就说信息不足。这样模型就知道该按什么顺序干活。
第三层是长度优化。检索回来的文档可能很长,但上下文窗口有限。我一般会按句子边界截断,保留前N个token,或者用轻量模型对长文档做摘要压缩后再注入。这里有个动态选择策略,如果问题问得很细,比如「具体条款是什么」,那就用片段级,只取相关句子;如果问题比较泛,比如「这个政策的整体思路是什么」,那就用文档级,保留整篇。
第四层是可追溯性保障。我会强制模型输出引用标记,比如[1]、[2],生成后再做一次后置校验,看引用的编号和实际检索内容是否匹配,过滤掉那些[[幻觉]]引用。
举个例子,有个电商客服场景,用户问「我申请退款为什么被拒?」。检索回来的文档可能包括退款政策、订单状态、客服话术。如果我不做预处理,直接把一堆文档塞进去,模型可能从客服话术里摘一句「我们会在48小时内处理」,但实际政策说「退款需在收货后7天内申请」。模型如果没看到政策那篇,就会给出错误答案。所以预处理里我会按相关性排序,把政策文档放在最前面,并且对冲突信息打标签,提示模型「存在不同观点,请综合判断」。这样模型就能给出准确回答,比如「根据政策,您需要在收货后7天内申请,但您已超过期限,所以被拒。建议您联系客服特殊处理。」
落地时有个很现实的风险,引用幻觉。模型可能自己编一个[3]出来,但检索结果里根本没有第3篇。我上线时会特别关注这个,用后置校验脚本跑一遍,如果引用编号不存在,就触发重生成或者直接去掉那个引用。还有一个前提,就是检索质量本身要过关。如果检索回来的文档全是噪声,那Prompt设计得再好也没用。所以我会把检索和生成看成整体,检索端得先保证Recall够高。
说到这,其实还有一个延伸问题:当检索回来的文档之间有明显矛盾时,比如A文档说「退货免运费」,B文档说「运费自理」,模型该怎么处理?我目前的方案是让模型输出时主动标注矛盾,并给出不同来源的结论,但如果你希望模型能做判断,那就需要引入[[Self-RAG]]机制,让模型自己学会反思和选择。这块其实挺有意思,涉及到模型自主决策和可信度的平衡。
所以整体上,我更倾向于用结构化模板加引用强制来保障基本盘,再根据业务场景做动态调整。这样既不会让模型太发散,又能保证答案有据可查。