RAG 提示词工程
请使用电脑访问
https://myblog-ad4.pages.devRAG 提示词工程
一、RAG Prompt 的独特挑战:它和普通 Prompt 不一样
核心问题: RAG Prompt 不能当普通 Prompt 写。
两者本质区别
- 普通 Prompt:模型用自己训练时积累的参数知识回答,知识边界就是答案边界。
- RAG Prompt:传入问题 + 检索文档,要求模型 只用文档 回答,不能用参数知识。
幻觉的根源
模型预训练积累了大量知识,默认会把检索文档和参数知识 混合使用,而不是只看你给它的文档。
真实案例
用户问某保险产品重疾险等待期,文档明确写 180 天,但模型因为训练数据里有其他产品是 90 天,混合后回答了 90 天。用户据此去理赔被拒——这是真实业务风险。
RAG Prompt 的核心任务: 切断模型对参数知识的依赖,强制只从检索文档里找答案。
二、System Prompt 设计:角色 + 规则 + 负面约束
System Prompt 是整个 RAG Prompt 架构的基础层,它做三件事:定义角色、设置规则、写明负面约束。
先看我们在保险 RAG 项目中实际使用的 System Prompt:
你是金融保险公司的智能客服助手。你只能基于提供的参考文档回答用户问题。 |
这段 System Prompt 的每一条都有其设计逻辑,逐条解释如下。
1. 角色定义:明确身份边界
“你是金融保险公司的智能客服助手”——这句话不只是客套,它在给模型划定知识边界。一个保险客服助手,不应该去聊股票行情,不应该去聊其他公司的产品。角色定义是第一层过滤。
紧接着”你只能基于提供的参考文档回答用户问题”,这是整个 System Prompt 最重要的一句话。注意用词是”只能“,不是”尽量”,不是”主要”。这个词的强度直接影响模型的行为。
2. 规则第 1 条:正向约束
“只使用参考文档中明确提到的信息”——这是正向约束,告诉模型能做什么。
关键词是”明确提到“,不是”相关的”,不是”可以推断的”。这把推理空间大幅压缩,强迫模型找原文,而不是推断。
3. 规则第 2 条:拒绝兜底
当检索文档里真的没有答案时,模型该怎么办?这条规则给了明确指令:承认不知道,不猜测。
很多初级写法里没有这条,导致模型在文档信息不足时,会悄悄调用参数知识来”补全”答案,这正是幻觉的高发场景。
4. 规则第 3 条:引用要求(反幻觉的核武器)
强制引用来源,格式是 [来源:文档名称第X页]。它的原理是:
模型要写出引用,必须真的在文档里找到对应内容。
如果文档里没有,它无法凭空捏造一个引用(极少数情况也会捏造,但概率大幅下降)。引用要求本质上是给模型增加了一个”验证步骤”,它必须先找到原文,再写回答。
5. 规则第 4 条:数字核实
金融保险场景里,数字错误是最危险的。等待期、保额、免赔额、理赔时限——任何一个数字写错都可能产生纠纷。这条规则把数字单独列出来,加强约束。
6. 规则第 5 条:置信度标记
“根据文档显示” vs 直接陈述,这是一种微妙但有用的设计。它让回答本身携带置信度信息,用户可以据此判断信息的可靠程度。
System Prompt 把规则定好了,User Prompt 要解决的是如何把每次请求的内容结构化地传进去。
三、User Prompt 设计:问题 + 上下文的结构化组织
System Prompt 设好了基础规则,User Prompt 负责把每次请求的具体内容传进去:检索到的文档片段 + 用户问题。
关键点在于格式化。 你不能把五段检索文档直接拼一起扔给模型,模型会搞不清楚哪段是哪段。
看我们项目的实际实现:
def build_rag_prompt(query: str, retrieved_docs: list) -> str: |
这段代码的设计有几个细节值得说明:
每段文档标了编号和来源。
参考文档1(来自:XX产品条款第3页)这样的格式,让模型在引用时有依据,不用自己猜来源名称。文档之间用双换行分隔。这比单换行更清晰,减少文档边界模糊带来的混淆。
问题放在文档后面,不放在前面。这个顺序是有讲究的。大量实验表明,先文档后问题的顺序,比先问题后文档的顺序,幻觉率更低。原因可能是:先文档让模型先把上下文”读进去”,再看问题时有了基础;而先问题可能激活模型的参数知识,导致后续处理文档时仍受参数知识干扰。
结尾的指令再次重申了”基于文档”和”没有就说没有”。System Prompt 的规则有时候在长上下文中会被稀释,User Prompt 末尾再提一次,起到强化作用。
格式定好了,下一步是弄清楚约束措辞本身的强弱差异有多大。
四、约束写法:一字之差,幻觉率巨大
核心结论:约束措辞越强硬,幻觉率越低。
| 写法 | 示例 | 幻觉率 |
|---|---|---|
| 弱约束 | 参考以上文档回答用户问题 |
18% |
| 中等约束 | 请主要基于参考文档回答,尽量不要添加文档之外的内容 |
12% |
| 强约束 | 只能使用文档中明确提到的信息,未提及的不得回答 |
9% |
| 强约束 + 引用要求 | (见下方) | 7% |
最佳写法
只能使用参考文档中明确提到的信息,文档中未提及的内容不得回答。 |
为什么引用要求有效
强制模型先找原文再作答,有引用的回答占比从 32% → 91%。
关键原则: 用”只能””不得”等强制性语气替代”参考””尽量”等软性词汇,杜绝模型用参数知识填空。
五、长上下文压缩策略:超长时怎么处理
我们项目里,用户有时会问跨产品的比较类问题,或者问涉及多个条款的复杂问题。检索系统可能召回 10–15 个片段,总 token 数轻松超过 32K。
超长上下文的两个问题
- 超过模型的上下文窗口,直接报错。
- “迷失在中间”现象(Lost in the Middle)——即使没超,模型对上下文中间部分的注意力显著弱于开头和结尾,导致关键信息被忽略。
三种策略,根据场景选择:
策略一:截断法
保留相关性最高的 Top-3 片段,其余丢弃。
- ✅ 实现最简单,延迟几乎不增加
- ❌ 信息损失大
- 📌 适合对速度敏感、问题比较简单的场景
策略二:压缩法
用 LLM 把每个片段压缩为关键信息摘要,保留核心内容,剔除冗余。
- ✅ 信息损失小
- ❌ 增加了一次 LLM 调用的延迟
def compress_context(docs: list, query: str, max_tokens: int = 2000) -> str: |
注意压缩提示词里有 '{query}'——这是 query-aware 压缩,不是通用摘要。模型会优先保留与用户问题相关的信息,而不是文档本身认为重要的信息。
这个细节影响很大:用通用摘要的话,压缩出来的内容往往是文档的前几句(因为那里通常是主题句),而不是用户真正需要的那部分。
策略三:分批处理
把 N 个文档片段分成几批,每批单独问模型,得到多个中间答案,最后再用一次 LLM 把中间答案合并成最终答案。
- ✅ 适合超长文档
- ❌ 延迟最高,也最复杂
- 📌 我们在用户问”帮我总结所有产品的等待期规定”这类问题时才启用
综合效果
三种策略加起来,超过 32K token 的请求处理成功率从 45% 提升到 92%。
另外 8% 基本是极端情况(单个文档本身就超长,无法有效压缩),我们对这类请求做了降级处理,返回”该文档过长,建议直接查阅原文”。
长上下文处理好之后,整个 Prompt 体系还需要一套系统的测试方法来验证效果,不能靠感觉。
六、Prompt 迭代测试方法:怎么知道你的 Prompt 好不好
写完 Prompt 不能靠感觉判断好坏,必须建立系统的评测机制。
建立 Prompt 版本库
我们的做法是建立 Prompt 版本库,用 Git 管理每一版 Prompt,提交记录里写清楚:
- 改了什么
- 为什么改
- 改后的测试指标变化
这样几个月后回头看,能清楚地看到每次优化的效果,也能方便地回滚。
评测指标:忠实度(Faithfulness)
衡量 RAG 系统幻觉的核心指标是 忠实度——模型的回答有多大比例可以在检索文档里找到原文依据。
我们在 200 条测试样本上计算忠实度,样本覆盖了三类:
- 文档里有明确答案的问题(应该回答正确)
- 文档里只有部分答案的问题(应该部分回答,说明缺失)
- 文档里完全没有答案的问题(应该拒绝回答)
三类样本都要测,光测第一类是不够的。很多团队在”文档有答案”的情况下表现不错,但在”文档没答案”时模型会编造,这才是最危险的场景。
典型 badcase 复盘
测试过程中发现一个高频 badcase:用户问”等待期多久”,模型回答时混入了其他产品的数据。
复盘下来,原因有三层:
第一层:检索召回了多个产品的条款片段,模型混淆了。这是检索层的问题,但 Prompt 也要应对这种情况。
第二层:Prompt 里没有要求模型区分不同产品分开回答。加了”如果文档涉及多个产品,请分产品分别回答”之后,混淆大幅减少。
第三层:引用要求不够细。之前要求”标注文档名称”,但文档名称里有时候没有产品名称,加了”引用时必须包含产品名称”之后,模型被迫把产品信息搞清楚才能写引用。
这种层层深挖的 badcase 复盘,是 Prompt 迭代最有价值的工作。每个 badcase 背后都有原因,找到原因,加一条规则,验证效果——这就是 Prompt 工程的核心循环。
掌握了这套设计和测试方法,再遇到面试官追问 RAG Prompt 的问题,就可以有条理地把这几个层次都讲清楚。
七、面试怎么答 RAG Prompt 设计?
面试官问这个问题,考察的是三个层次的理解:知道问题在哪、知道怎么设计、知道怎么验证。
第一层:说清楚 RAG Prompt 的独特性
不要一上来就讲技术细节。先定性:
RAG Prompt 和普通 Prompt 的根本差异在于,普通 Prompt 让模型用参数知识回答,RAG Prompt 要切断参数知识依赖,强制模型只用检索文档。
这个前提讲清楚,说明你理解问题的本质。
第二层:讲 System / User 分工原则
- System Prompt 管”规则”,跨所有请求不变:角色定义、回答约束、引用要求、拒绝策略。
- User Prompt 管”内容”,每次请求不同:这次检索到的文档、这次用户的问题。
分工清晰有两个好处:一是逻辑清晰,调试容易;二是 System Prompt 可以复用,修改规则时不用动 User Prompt 的构建逻辑。
第三层:讲约束强度和引用要求
面试官通常会追问:你具体怎么写约束?
这里要把”弱约束 vs 强约束”的对比讲清楚,然后重点讲引用要求。引用要求是 RAG Prompt 里反幻觉效果最显著的单一手段,要说清楚原理:强制引用迫使模型先找原文,无法凭空编造。
第四层:讲长上下文处理
很多候选人讲到第三层就停了,能讲到长上下文处理的人不多。这里说三个策略(截断、压缩、分批),以及 query-aware 压缩的细节。
第五层:讲评测方法
如果面试还有时间,补一句 Prompt 迭代怎么测:建版本库、200 条测试集、跑忠实度指标、复盘 badcase。这说明你不是靠感觉写 Prompt,而是有系统方法。