外观
第 14 章 RAG 系统中的 Prompt 设计
很多工程师都遇到过同一个问题。现在市面上的大语言模型知识面很广,但涉及具体事实时并不靠谱。
问它唐朝的安史之乱,它能从范阳起兵讲到兵败邺城,细节丰富;问它量子力学里的薛定谔的猫,它也能把波函数坍缩讲得头头是道。这些公开领域的通识问题,它确实答得上来。
但换成具体、私有的问题,它就靠不住了。问它:“咱们公司上个季度的财报关键指标怎么样?”最好的情况,它会老老实实地回答:“对不起,我是一个语言模型,无法获取你公司的内部数据。”这还算诚实。
更糟糕、也更常见的情况是,模型一本正经地胡说八道,编造一份看起来特别像那么回事的假财报,里面有模有样的图表、煞有介事的分析,甚至还有几句听起来很专业的“高管点评”。这份“幻觉”财报没准真能唬住不明就里的新人,让他拿着去跟老板汇报,后果不堪设想。
这就是大模型的“知识边界”,也是它最致命的弱点,即“幻觉”(Hallucination)。它的知识只来自训练时使用的大量公开互联网数据。这个知识库很大,但也仅限于此。对于那些在训练数据截止日期之后发生的最新信息、公司内部的文档、某个高度专业领域的知识库(比如最新的医疗研究论文、公司祖传的几十万行代码库),它一无所知。
为了解决让无数工程师头疼的“幻觉”问题,大家想了很多办法。其中,RAG(检索增强生成)就是目前最主流、也最有效的方案。
别让那些动辄画出十几二十个方框的复杂架构图吓到你。说白了,RAG 的思想很朴素。模型不需要把所有知识都记在参数里,回答之前,先从外部资料里把与问题相关的内容查出来,再基于这些内容作答。
具体做法是,给大模型外接一个可以随时查阅的知识库(通常是企业私有的文档、数据库、API 等等)。当用户提问时,系统不直接让模型去“猜”,而是先拿着用户的问题,从知识库里把最相关的几篇文章检索出来,再把这些检索到的信息连同用户最初的问题一起交给大模型,对它说:“嘿,别瞎想了,答案就在这些材料里,你给我好好当个阅读理解高手,总结一下再回答。”
通过这种方式,模型从凭训练记忆回答开放性问题,变成基于给定材料作答。这不仅极大地降低了模型胡说八道的概率,也让它能回答那些训练数据里根本没有的问题。
这样看,RAG 既压住了“幻觉”,又让通用模型能回答专业领域的问题。
但现实是,很多团队兴冲冲地花了几个月时间,吭哧吭哧地搭了一套 RAG 系统,结果发现效果一塌糊涂。要么用户问个问题,检索出来的东西和问题对不上;要么模型明明拿着正确的材料,总结出来的答案还是乱七八糟,把不同来源的信息混在一起。用户一用就放弃,留下一句评价:“这玩意儿还不如搜索引擎,简直是个人工智障。”
问题主要出在对 RAG 的理解上。
很多人把 RAG 当成了一个纯粹的“检索”问题。他们花大量精力去优化向量数据库的索引、调整文本分块(Chunking)的大小、研究各种 embedding(嵌入)模型,却忽略了整个 RAG 链条里真正串起所有环节的关键角色,也就是 Prompt。
在 RAG 系统里,Prompt 不再是简简单单的一句“请回答这个问题”,而是一个分布在各个关键节点、各司其职、高度专业化的“指令集”。查询处理、检索后处理、最终生成,每个环节都对应一份 Prompt。Prompt 写得含糊,后面每个环节的输出质量都会跟着下降。
所以这一章暂时不聊复杂的算法和模型,专门讲 RAG 各环节的 Prompt 设计方法。这可能是花最少力气、收益却最大的一章。
14.1 RAG 架构与 Prompt 的触点
在动手写 Prompt 之前,得先搞清楚一套典型的 RAG 系统里,到底有哪些地方需要设计 Prompt。
一套典型的 RAG 系统可以按功能拆成几个模块。用户的提问从入口进来,经过逐级处理,最终得到一个有依据的答案。
其中有几个关键环节,每个环节都需要一份写得明明白白的 Prompt。
链路从Query 处理模块开始。用户的提问有时候写得很模糊,比如就写了“来个好吃的”,或者“跟上次那个差不多的”。系统不能把这种模糊的问题直接交给检索模块,得先把它转换成规范、明确的查询,比如把“来个好吃的”写成“一份清蒸鲈鱼,配姜葱豉油,不要香菜”。在 RAG 里,这就是查询重写和意图理解的环节。
检索模块排在下一环。它拿着规范化的查询,去知识库(通常是向量数据库)里,把与问题相关的文档片段都找出来。这个环节本身不太需要 Prompt,它更依赖于算法和索引。
检索之后是检索后处理模块。检索模块拿回来的文档片段不可能都有用。有的完全不相关,有的只有局部相关,有的相互重复,甚至有的相互冲突。这个模块负责把相关的挑出来,把无关的过滤掉,把重复的合并,把冲突的标记出来。在 RAG 里,这就是知识的去噪、排序、融合与压缩。
最终生成模块是链路的最后一环。它拿着后处理过的文档片段和用户的原始问题,生成最终答案。在 RAG 里,这就是最终答案的生成,它要求严格忠实于检索材料,同时把答案组织得清楚、易读。
还有一个环节是输出与溯源模块。答案不能只有内容,还要附上出处,让用户能核对,比如标注某个结论出自知识库的哪份文档、哪一节。在 RAG 里,这就是引用与溯源。
在这整个流程里,Prompt 贯穿始终,它规定每个环节的输入怎么处理、输出什么。
所以 RAG 里的 Prompt 设计绝非单点问题,只在最后一步写一句“请总结”远远不够,它是一个分布式的、端到端的工程。工程师至少需要在三个核心“触点”上精心设计 Prompt。
最靠前的触点在查询处理阶段。在把用户的原始问题交给检索系统之前,用 Prompt 驱动一个 LLM,对问题进行“预处理”、“净化”和“重写”。
往下一站是检索后处理阶段。在拿到检索系统返回的一堆质量参差的文档片段后,用 Prompt 驱动一个 LLM,对这些片段进行“筛选”、“去噪”、“排序”和“压缩”。
再往后是最终生成阶段。在把整理好的“上下文”和原始问题交给主 LLM 进行最终回答时,用一个极其严谨的 Prompt,来指导它如何“忠实地”、“有条理地”生成答案,并且“附上参考文献”。
现在,整套流程的环节位置清楚了。接下来逐个讲每个环节的 Prompt 该怎么写。
14.2 把模糊提问重写成规范查询
查询处理是整条链路的第一站,也是影响最终效果的关键一步。
做过面向真实用户的 RAG 系统,多半有过这种体验,用户能把人逼疯。辛辛苦苦熬了好几个大夜把系统搭建起来,满怀期待地开放测试,结果用户上来就问:“AI”,或者“你们公司怎么样?”,再或者是一大段前言不搭后语、包含七八个不同问题的混乱输入。
那一刻,工程师的内心是崩溃的,恨不得顺着网线爬过去,抓住用户的肩膀使劲摇晃:“大哥!你到底想问什么啊?!”
但情绪归情绪,用户的每一个问题,终究都得认真对待、妥善回应。可要是把这种“垃圾”一样的问题直接扔给向量检索系统,等着你的就是那条颠扑不破的真理,“垃圾进,垃圾出”(Garbage in, garbage out)。
向量检索这东西听起来很高大上,但说白了只做一件事。它会把问题通过一个叫 embedding 的模型,转换成一个由几百上千个数字组成的数学向量,然后拿着这个向量去库里找那些在数学上跟它“长得最像”(也就是向量空间距离最近)的文档向量。
给它一个模糊、歧义、信息量为零的问题,比如“AI”,转换出来的向量会落在一个语义不明确的位置。检索系统照样执行,找回来一堆同样不知所云、可能包含“人工智能”、“喜爱”、“未来”这些词的无关文档。
所以,在把问题交给检索系统之前,必须先让一个 LLM 把它改写清楚,变成规范的查询。这个过程就叫查询重写(Query Rewriting)。
用户提问的四类典型问题与改写策略
用户的提问通常有四类典型问题,各有对应的改写办法。
第一种,也是最常见的,叫指代不明。这在多轮对话里简直是家常便饭。用户第一句问:“给我介绍一下星途 X7 这款车。” RAG 系统表现很好,给出了详细的介绍,用户紧接着第二句就问:“那它和上一款相比呢?”
最直接的做法是把“那它和上一款相比呢?”这句毫无信息量的话扔给检索系统,结果可想而知,什么都找不到。这里的“它”和“上一款”到底指代什么?只有结合对话历史才能知道。所以需要一个 Prompt,让 LLM 根据聊天记录把这些代词还原成具体的名词。这个过程在 NLP 领域有个专门的术语,叫回指消解(Anaphora Resolution)。
第二种,叫问题过于宽泛。比如在公司内部知识库里,用户就问了四个字:“市场策略”。这个知识库里可能有关于社交媒体营销、SEO 优化、线下推广活动的几十份不同文档,到底该给哪一份?直接检索,可能会返回一篇最“平均”、但用户并不想要的文档。这时候需要一个 Prompt,让 LLM 把宽泛的问题变具体。它可以是反问用户:“您是指哪一方面的市场策略呢?比如社交媒体,还是内容营销?” 也可以根据用户的历史行为猜测他可能想问的方向,然后生成几个更具体的查询,比如“最新的社交媒体市场策略”、“Q3 内容营销复盘报告”。
第三种,叫复杂问题,或者叫复合问题。用户特别喜欢一口气问一个包罗万象的问题,比如:“对比一下 A 方案和 B 方案在成本、实施周期和技术风险这三个方面的优劣,并给出你的最终建议。”
千万别直接拿这个大长句去做向量检索,因为向量检索的本质是找“语义相似”,又长又复杂的问题的“平均语义”可能指向一个非常奇怪的地方,导致检索结果非常糟糕。正确的做法是让 LLM 先把这个复杂问题分解(Decomposition)成多个独立的、原子化的子问题。
比如,一个好的查询重写 Prompt,应该能把上面那个问题,拆解成这样的一组查询:
- “A 方案的预计成本是多少?”
- “B 方案的预计成本是多少?”
- “A 方案的实施周期是多久?”
- “B 方案的实施周期是多久?”
- “A 方案存在哪些技术风险?”
- “B 方案存在哪些技术风险?”
拿着这些清晰的子问题分别去检索,把结果汇总起来,最后交给生成模型去“作答”。这就叫“分而治之”,是处理复杂查询的通用做法。
第四种,叫语言风格不匹配。用户用的是口语化的大白话,甚至网络梗,而知识库里都是专业、严谨、书面的术语,两者之间存在着巨大的“语义鸿沟”。比如用户问“我们公司那个‘爆款’产品最近卖得咋样?”,而财报里对应的术语是“旗舰型号‘启明星’的季度销售额”。直接用“爆款”去检索,大概率找不到。这时需要一个 Prompt,让 LLM 识别出这种口语化表达,并翻译成知识库里对应的专业术语。
查询重写 Prompt 的写法:从基础到进阶
面对这些情况,需要设计一个专门的“查询重写” Prompt,让 LLM 先把这些模糊的提问改写成规范的查询语句。
先看基础写法,它解决的是指代问题。这个 Prompt 的核心任务,是让 LLM 结合对话线索做重写。
“你是一个对话历史分析专家。你的任务是,分析下面提供的多轮对话历史和用户的最新提问。如果最新提问中包含了代词(如‘它’、‘那个’、‘前者’)或不明确的指代,你必须结合对话历史,将其重写为一个独立的、上下文无关的、完整的查询语句。如果最新提问本身已经足够清晰,则直接输出原话。
对话历史:
用户:给我介绍一下星途 X7 这款车。 助手:好的,星途 X7 是一款中大型 SUV,搭载了最新的‘飞鱼’底盘技术... 用户:那它的油耗怎么样? 助手:星途 X7 的综合工况油耗大约是百公里 8.5 升。 用户:那和 X9 相比呢?当前用户提问: “那和 X9 相比呢?”
你的重写结果(只输出最终的查询语句):”
模型看到这个 Prompt 后,会往前追溯对话历史。它会找到,用户上一句问的是“油耗”,再上一句的主体是“星途 X7”。所以,当前这个问题的完整意图,是比较 X7 和 X9 的油耗。于是,它会输出一个高质量的查询:
“星途 X7 和星途 X9 的油耗对比”
拿到这样清晰的查询语句,检索系统的结果会可靠得多。
再上一级是进阶写法,HyDE(Hypothetical Document Embeddings)。它的中文叫法是“假设性文档嵌入”,原理说穿了很有趣。
传统的思路是想办法把“问题”变清晰,HyDE 的思路恰恰相反,不改问题,而是让 LLM 先“猜”一下这个问题的答案可能会出现在一篇什么样的文档里,先“伪造”一篇这样的文档。最后不用原始问题去做检索,而用这篇伪造的、信息量更丰富的“假设性文档”去做检索。
Gao 等人在 ACL 2023 上正式提出了 HyDE(arXiv:2212.10496)。它的原理在于,伪造的文档虽然事实可能不准确,但它和“真正能回答这个问题的文档”在向量空间里距离很近(近邻),因此用伪造文档的向量去做检索,反而比用信息量有限的原始问题更精准。
也就是说,这里比的不是事实对错,而是语义方向。假设性文档把问题的语义展开成一段完整文本,方向上更接近目标文档,这就是它有效的原因。
这个 Prompt 可以这样设计:
“你是一个知识渊博的专家。用户会向你提出一个问题。你的任务不是直接回答问题,而是生成一段你认为最有可能包含该问题答案的、假设性的、详细的文档段落。
用户问题: ‘RAG 系统中,什么是‘迷失中间’问题?’
你生成的假设性文档:”
模型可能会生成这样一段话:
“在检索增强生成(RAG)系统中,‘迷失中间’(Lost in the Middle)是一个常见的问题。该问题指的是,当提供给大语言模型的上下文窗口非常长,包含了多个检索到的文档时,模型倾向于更好地利用开头和结尾部分的信息,而可能会忽略或‘忘记’放在中间部分的关键信息。这种现象最早在谷歌的一篇名为《大海捞针》的论文中被系统性地揭示。为了缓解这个问题,常见的工程实践包括对检索到的文档进行重排序,将最相关的文档放在上下文的开头或结尾。”
这段模型“伪造”出来的文档,比原始问题“什么是‘迷失中间’问题?”丰富得多,包含了更多的关键词和上下文信息。把这段“伪造文档”转换成向量去知识库里检索,就更有可能找到那篇真正讲解“迷失中间”的原始论文或技术博客。
细看这段伪造文档,还能发现一个小错误。它把“迷失中间”的揭示归到了谷歌名下,说这出自一篇名为《大海捞针》的论文,而真正的提出者,是 14.5 节会讲到的普林斯顿、UC Berkeley、Stanford 与 Samaya AI 联合团队。这个错误恰好说明,HyDE 借用的是伪造文档的语义,不是它的事实细节。哪怕出处写错了,只要它和真正的目标文档在向量空间里的距离仍然足够近,检索照样能命中。
专家级写法是 Step-Back Prompting(退一步提示)。Zheng 等人(Google Research)在 ICLR 2024 上正式提出了这一方法(arXiv:2310.06117)。它代表了另一种解决问题的思路,特别适合处理那些过于具体、细节化,导致检索系统找不到宏观背景知识的问题。
它的核心思想是,当用户提出一个非常具体的问题时,先让 LLM“退一步”,想一想回答这个问题需要什么样的更宏观、更高层次的背景知识,然后先去检索这些背景知识,再结合它们回答那个具体的问题。
举个例子,一个历史系的学生可能会问:“伽利略在比萨斜塔实验中,用来测量两个铁球下落时间的工具是什么?”
这是一个非常具体的问题。直接拿去做检索,可能会找不到直接描述这个工具的文档。
如果用 Step-Back Prompting,第一步是让 LLM 生成一个更高层次的“退一步”问题:
“17 世纪,科学家们是如何精确测量极短时间的?”
拿着这个更宏观的问题去检索,可能会找到关于钟摆、水钟等当时计时技术发展的通用性文章。把这些文章作为背景知识,连同用户的原始问题一起交给生成模型,它就能更有依据地推理和回答:“在伽利略的时代,还没有精确的计时工具,他很可能是通过水钟或者自己的脉搏来估算的,比萨斜塔实验本身可能只是一个思想实验。”
通过查询重写,检索从被动接收原始问题,变成先理解问题再检索。这一步投入的成本,会在后续环节中以更高的准确率回报。
14.3 检索结果的去噪与融合
经过查询重写,现在手上有了高质量的查询。检索系统拿着这些查询,找回来 5 到 10 个在向量空间里最相关的文档片段(Chunks)。
新的问题来了,而且比想象中棘手得多。
要是以为检索系统找回来的东西都干净、直接可用,项目很快就会出问题。
现实情况是,检索结果的质量参差不齐,通常混着四类问题。
第一类是完全不相关的文档。尽管查询经过了优化,检索系统总会犯错,它可能因为某些关键词的巧合,或者向量距离上的偶然接近,拿回来一些毫不相关的文档,比如问“苹果公司的财报”,它可能返回一篇讲“苹果种植技巧”的文章。
第二类是只部分相关的文档。有些文档确实相关,但只有其中一两句话和问题有关,其他绝大部分内容都是噪音、废话,比如问“项目 A 的预算”,它返回了整整一万字的项目立项报告,而关于预算的,只有其中不起眼的一句话。
第三类是信息高度重复的文档。好几个不同的文档片段,可能都在用不同的话翻来覆去地讲同一个意思,比如一份会议纪要、一份周报、一封邮件,都在说“项目 A 的预算是 100 万”。
第四类是信息相互冲突的文档。可能找到两份看起来都很有道理的文档,一份是年初的规划报告,说“项目 A 的预算是 100 万”;另一份是年中评审会的纪要,说“考虑到需求变更,项目 A 的预算已经追加到 150 万”。
这些材料不加处理就全部塞给主 LLM,会出问题。噪音可能把它带偏,不相关的内容会分散它的注意力,让它最后生成一个完全跑题的答案。
它也可能忽略关键信息,在长篇文档里漏掉那句最关键的话,导致答案不完整。或者过分强调重复信息,把重复出现的细节当成重点,导致答案的重点跑偏。面对冲突信息还可能各打五十大板,最后给出一个模棱两可、毫无用处的答案,比如“项目 A 的预算,根据不同文档,可能是 100 万或 150 万。”
所以,在交给生成模型之前,必须先做一道检索后处理(Post-retrieval Processing),核心任务是对检索回来的原始材料进行知识的融合与去噪。而驱动这个环节的,同样是 Prompt。
检索后处理的两种 Prompt 思路:筛选重排与融合总结
这个阶段的 Prompt,通常有两种设计思路,一种是筛选和重排,一种是融合和总结。
先看第一种,筛选和重排序(Filtering and Re-ranking)。这种方法比较简单直接。让一个 LLM 把原始的用户问题和检索回来的每一个文档片段成对对照,挨个给相关性打分。扮演质检员的通常是一个比主 LLM 更小、更便宜、更快的模型,比如各家 API 的 mini 档位,或者通义千问 Qwen、DeepSeek 开源的小参数版本,选哪一款不重要,关键是快且便宜。
这个打分 Prompt 可以这样写,而且语气要非常明确:
“你是一个极其严格的信息相关性质检员。你的唯一任务是,判断下面提供的‘文档片段’,是否与‘用户原始问题’高度相关,并且能够为回答该问题提供直接的、有价值的核心信息。请给出一个从 1 到 10 的整数相关性分数,并用一句话简要说明你的打分理由。
打分标准:
- 8-10分:极度相关。文档片段直接、明确地回答了用户的核心问题。
- 5-7分:中度相关。文档片段提供了一些有用的背景信息或上下文,但并非核心答案。
- 1-4分:低度或不相关。文档片段与问题无关,或者是纯粹的噪音。
用户原始问题: ‘星途 X7 的百公里加速时间是多少?’
文档片段 [ID: doc-034]: ‘星途 X7 搭载了 2.0T 涡轮增压发动机,最大功率为 192kW,峰值扭矩为 400N·m,这套动力总成在业内广受好评。在传动方面,匹配的是 8 速手自一体变速箱,换挡平顺。’
你的评分(请严格按照 JSON 格式输出):”
模型可能会输出:{"score": 6, "reason": "文档详细描述了影响加速性能的动力总成参数,属于强相关的背景信息,但没有直接给出百公里加速的具体时间数值。"}
对所有检索回来的 5 到 10 个文档片段都跑一遍这个流程,然后在代码里设定一个阈值,比如把所有分数低于 5 分的直接过滤掉,再把剩下的按分数从高到低重新排列。
这样,最终交给生成模型的,就是一份经过筛选、最重要的信息排在最前面的上下文。这个过程在 RAG 领域通常叫重排序(Re-ranking)。很多时候,一个简单的重排序层就能让 RAG 效果明显提升。
顺带说一句,用 LLM 加 Prompt 来打分,并不是实现重排序的唯一路子。业界还有一类专门的重排序模型(Re-ranker),比如 BAAI 开源的 bge-reranker 系列(较新的 bge-reranker-v2-m3 基于多语言模型 bge-m3),以及 Cohere 提供的重排序 API。这类模型不需要写 Prompt,输入“问题 + 文档”对,直接输出一个相关性分数,通常比调用一次 LLM 更快、更便宜,相关性判断也更专业,是生产系统里这个环节的主力选择。它们背后的交叉编码器原理,第 15 章会详细展开。
另一种是融合与总结(Fusion and Summarization)。这种思路更进一步,不只是筛选,而是让 LLM 直接把这批文档整理成一份去重、解决冲突后的摘要。
这个融合 Prompt 需要设计得非常具体,因为它本身就是一个小型的“阅读理解并总结”任务。
“你是一个顶级的、极其细致的信息融合专家。下面是用户的一个问题,以及一组从知识库中检索到的、可能包含重复、冗余甚至矛盾信息的原始文档片段。
你的任务是,像一个侦探一样,完成以下工作:
- 仔细阅读并理解每一个文档片段的每一个字。
- 将所有与回答用户问题直接相关的核心事实信息,进行无损的提炼和整合。
- 识别并报告冲突:如果发现不同的文档片段对同一个事实给出了不同的描述(例如,不同的日期、不同的数字),你必须明确地指出这些冲突点,并如果可能的话,根据文档的元信息(如日期)判断哪个信息更可能是最新的。
- 消除冗余:如果多个文档片段在说同一件事,你只需要保留这个事实一次。
- 最终,生成一段绝对忠实于原文的、简洁、连贯、不包含任何冗余信息的‘核心知识摘要’。如果原始材料无法回答问题,就直接说无法回答。
用户原始问题: ‘项目 A 的预算和负责人是谁?’
检索到的文档片段:
[来源: doc-001, 日期: 2023-01-15]‘根据年初的规划,项目 A 的初始预算被设定为 100 万,项目负责人为张三。’[来源: doc-002, 日期: 2023-02-10]‘在项目 A 的启动会上,张三介绍了项目的整体规划。’[来源: doc-003, 日期: 2023-06-20]‘在 6 月的项目评审会上,考虑到范围扩大,会议决定将项目 A 的预算追加至 150 万。’[来源: doc-004, 日期: 2023-08-01]‘通知:因组织架构调整,自 8 月 1 日起,项目 A 的负责人变更为李四。’你的核心知识摘要:”
模型在处理完这样复杂的指令后,会输出一段质量极高的、可以直接用来回答问题的摘要:
“根据提供的资料,项目 A 的初始预算为 100 万 [来源: doc-001],但在 2023 年 6 月 20 日,预算已被追加至 150 万 [来源: doc-003]。该项目的初始负责人为张三 [来源: doc-001],但在 2023 年 8 月 1 日,负责人已变更为李四 [来源: doc-004]。”
把这样一段干净、清晰、已经解决信息冲突和时间线问题的摘要交给最终的生成模型,答案质量就有了保障。
这个后处理环节是很多初级 RAG 系统忽略的,因为它会增加额外的 API 调用成本和延迟,但它恰恰是决定系统稳定性和可靠性的关键。多花这一步的计算成本,换来的是最终答案质量和用户信任度的提升,这笔投入对严肃的企业级应用来说是划算的。
14.4 给答案标注出处
经过了查询重写和知识去噪,可用的上下文已经准备好。现在轮到主 LLM 生成最终答案。
下一步,是把处理好的上下文(精选的文档片段或融合后的摘要)连同用户的原始问题,一起打包成一个最终的 Prompt 交给它,生成最终的答案。
这个最终生成阶段的 Prompt 看似简单,其实直接决定 RAG 系统是否可信赖。如果只是简单地对模型说:“请根据以下信息,回答用户的问题。” 得到的答案可能看起来还不错,但这种做法同时也丢掉了一个让 RAG 系统变得“值得信赖”的关键特性,也就是可溯源性(Traceability)。
溯源性指的是,答案里的每个关键结论,都能指明它出自哪份材料。
没有出处的答案,用户没法核对,也没法采用。答案说“预算是 150 万”,用户想知道这是哪份文件、哪个版本说的,系统给不出出处,这个答案在决策里就不敢用。
RAG 系统也一样。在医疗咨询、法律问答、金融分析、工程故障排查这类高风险场景里,一个没有出处的答案,既没法采用,也没法追责。
所以必须让 AI 在给出答案的同时,明确告诉用户:“我这个结论,是根据你公司内部知识库里的《XX 项目技术方案评审 V2.3.pdf》这份文件的第三章第二节的那句话得出来的。” 学术写作里每个论点都要有参考文献(Citation)支撑,RAG 答案同理。
这个过程叫引用与溯源,实现它的关键在于最终生成阶段的 Prompt 设计。工程师要在 Prompt 里用不容置疑的语气,强制模型这么做。
强制引用 Prompt 的写法
要让模型严谨地引用,得在工程上和 Prompt 上双管齐下。
第一,在工程上,把上下文信息交给模型之前,得先给每一份材料附上独一无二的、机器可读的来源标识。比如在处理检索结果时,把它们格式化成一种清晰的、包含元数据的结构:
[来源 ID: doc-001, 文件名: 2023年预算规划.pdf, 页码: 5]根据 2023 年初的规划,项目 A 的初始预算被设定为 100 万。
[来源 ID: doc-003, 文件名: 2023年6月项目评审会纪要.docx, 日期: 2023-06-20]在 2023 年 6 月的项目评审会上,考虑到范围扩大,会议决定将项目 A 的预算追加至 150 万。
第二,也是最关键的,在主 Prompt 里,要把“强制引用”作为一条不可协商的铁律,用最强硬、不留任何模糊空间的语言,命令模型必须遵守。
这个生成 Prompt 可以这样写,甚至可以用不容置疑的措辞:
“你是一个极其严谨、细致、诚实的问答机器人。你的任务是,根据下面提供的、由
[上下文开始]和[上下文结束]包裹的背景材料,来回答用户的提问。你必须像对法律条文一样,严格遵守以下规则,这是你的最高指令,任何情况下都不得违反:
- 绝对忠实:你的回答必须严格基于所提供的背景材料。严禁使用任何你自己的、外部的知识,严禁进行任何形式的猜测或推理。
- 强制引用:对于你答案中的每一句话、每一个关键信息点,你都必须在句末,使用
[来源 ID: ...]的格式,明确标注出该信息来自于哪一份背景材料。如果没有引用,你的回答就是无效的。- 知之为知之:如果背景材料不足以回答用户的问题,或者材料中的信息存在无法解决的冲突,你必须直接、诚实地回答‘根据现有资料,无法回答该问题’,并说明原因。绝对不要尝试编造答案。
[上下文开始][来源 ID: doc-001, 文件名: 2023年预算规划.pdf, 页码: 5]根据 2023 年初的规划,项目 A 的初始预算被设定为 100 万。
[来源 ID: doc-003, 文件名: 2023年6月项目评审会纪要.docx, 日期: 2023-06-20]在 2023 年 6 月的项目评审会上,考虑到范围扩大,会议决定将项目 A 的预算追加至 150 万。[上下文结束]用户提问: ‘项目 A 的预算是多少?’
你的回答(请记住,每一句话都必须有引用):”
当模型看到这样一段措辞严厉、规则清晰、几乎没有空子可钻的 Prompt 后,它就会给出这样严密的答案:
“根据提供的资料,项目 A 的初始预算为 100 万 [来源 ID: doc-001]。然而,在后续的一份文件中提到,该预算在 2023 年 6 月已被追加至 150 万 [来源 ID: doc-003]。”
这样的答案能让用户直接核对每条信息的来源。
用户不仅得到了准确的信息,还清楚地知道信息的来源和演变过程。如果对这个答案有疑问,可以点击“来源 ID”(前端可以把它做成可点击的链接),直接跳转到原始文档的对应页面交叉验证。
这种“说话有根据”的能力,是区分“玩具级”RAG 和“企业级”RAG 的核心标志。它构建了用户对系统的信任,而这种信任在很多时候比答案本身的正确性更加重要。
14.5 上下文压缩
这一节讲每个 RAG 工程师都必须面对的问题,它同时关乎成本和性能。这个环节同样发生在最终生成之前。
大模型的上下文窗口有硬性上限。不管是早期的 4k、8k,还是现在动辄 128k、200k,总有一个物理上限。而且必须清醒地认识到,上下文窗口越大,API 调用费用越高,响应时间也越长。
在 RAG 系统里,从检索系统拿回来的文档片段动不动就好几百甚至上千个 token。不假思索地把五六个这样的长片段全部塞进最终生成模型的上下文,很快就会遇到两个问题。
一个是太贵了,API 账单会随调用量快速上涨,用户量上来之后,这笔开销会压垮项目预算。
另一个是太慢了。用户问一个问题,后台吭哧吭哧地处理几千上万个 token,等个七八秒甚至十几秒才返回结果。Google 早年的移动端调研给过一个常见引用的数字,页面加载超过 3 秒,53% 的访问会直接流失(该数字常被引为 DoubleClick 2016 调研)。在问答场景里,用户的耐心也差不了多少。
更糟糕的是,根据普林斯顿、UC Berkeley、Stanford 与 Samaya AI 联合团队 Liu 等人在 TACL 2023 上发表的研究(arXiv:2307.03172,“Lost in the Middle: How Language Models Use Long Contexts”),当上下文变得非常长时,大模型对中间位置信息的利用率会下降。它对放在开头和结尾的内容利用得更好,而对放在中间的关键信息给予更低的权重,也就是社区里常说的“迷失中间”现象。后来业界流行的“大海捞针(Needle in a Haystack)”测试,考察的正是同一类位置效应,并把它直观地可视化了。
把所有检索结果不加压缩地塞进上下文,等于把关键信息也推到了模型容易忽略的中间位置,同时还要为大量噪音内容支付 token 费用。
这就是上下文压缩要解决的问题。
在把上下文交给生成模型之前,先把与问题直接相关的内容提取出来,扔掉不相关的部分。这个过程叫上下文压缩(Context Compression)。
上下文压缩 Prompt 的写法:只保留相关句子
上下文压缩本质上也是一个 Prompt 驱动的“预处理”步骤。让一个 LLM 来做定向提取,同样为了降本增效,完全可以用一个更小、更快的模型来承担这个环节,比如通义千问 Qwen 的小参数开源版本。
这个 Prompt 的关键在于,它不是要做一个通用的、泛泛的摘要,而是目标性极强,只提取那些与回答用户原始问题最相关的句子或事实。
这个提取 Prompt 可以这样设计,语气要直接,任务要明确:
“你是一个极其高效的信息提取机器人。你的任务是,仔细阅读下面提供的‘原始文档片段’,并严格根据‘用户问题’,从中提取出所有能够直接回答该问题的、最关键的句子。
你的输出必须严格遵守以下规则:
- 只提取,不创作:只从原文中复制粘贴句子,绝对不要进行任何形式的改写、总结或解释。
- 高度相关:只提取那些包含了核心答案的句子,忽略所有背景信息、铺垫和无关细节。
- 保持为空:如果文档片段中没有任何与问题直接相关的句子,请直接输出一个空字符串。
- 拼接输出:将所有你提取出的句子,用换行符拼接成一个连贯的文本块。
用户问题: ‘星途 X7 的整车终身质保政策,对车主有什么限制条件?’
原始文档片段: ‘为了回馈广大消费者的厚爱,星途汽车品牌一直致力于为客户提供行业顶尖的、超越期待的卓越售后服务。我们郑重承诺,旗下所有车型,包括广受欢迎的旗舰 SUV 星途 X7,都享有行业领先的整车终身质保服务。这项前所未有的政策覆盖了发动机、变速箱等几乎所有核心部件,让您用车无忧。当然,为了保证服务的公平性和可持续性,需要注意的是,该质保政策仅对首任车主有效。同时,为了确保您的爱车始终处于最佳状态,我们要求享受该政策的车辆必须全程在官方授权的 4S 店进行定期保养。此外,我们的标准保养套餐价格为每年 1200 元,包含了 20 多项检测,性价比极高。’
你提取的精华句子:”
模型在处理后,会输出一段只保留关键信息的精炼文本:
“该质保政策仅对首任车主有效。 要求享受该政策的车辆必须全程在官方授权的 4S 店进行定期保养。”
原始文档里那些关于“回馈消费者”、“卓越服务”、“用车无忧”、“保养套餐价格”的营销话术和噪音信息,全都过滤掉了,只留下了最核心的、严格对应用户问题的两句话。
接下来,把从多个文档片段中提取出的、经过压缩的内容拼接在一起,交给最终的生成模型。这样一来,不仅可能把一个 5000 token 的超长上下文压缩到 500 token,大大降低 API 费用和用户等待时间,还因为排除了大量噪音干扰,让主模型更专注于核心信息,从而提高最终答案的质量和“忠实度”。
这就是工程上的取舍。一味追求“大而全”行不通,盲目相信堆资源同样行不通,真正的功夫是在成本、性能和质量之间,通过设计找到效益最大化的平衡点。
14.6 Prompt 与传统 NLP 的混合架构
再讲一个更宏观的架构话题。
这套“重写-检索-去噪-压缩-生成”的 RAG 流程虽然很强大,但也相当复杂、相当重。是不是所有问题都非得走一遍这么长的流水线?
这个问题涉及架构层面的取舍。
答案是明确的,当然不是。
把所有用户的问题,无论简单的“你好”,还是复杂的“请帮我写一份市场分析报告”,都毫无差别地扔给这套昂贵的、全功能的 RAG 流水线去处理,是一种巨大的浪费。简单问题用简单路径就能解决,让它们也走全流程,用户等待时间长,成本也高。
一个成熟的、企业级的、真正好用的问答系统,它的前端,应该是一个“智能调度中心(Smart Router)”,而不是一个简单的、只会把所有请求都转发给后端的输入框。
这个调度中心的核心任务,就是对用户的问题进行快速的意图分类,然后把它分发到最合适的、成本效益最高的处理路径。
而驱动这个调度中心的,同样可以是一个经过精心设计的、轻量级的 Prompt。
意图分类路由 Prompt 的写法
这个路由 Prompt 的任务,不是回答问题,而是“分析问题”,给问题分类。
“你是一个顶级的、极其高效的智能客服请求调度员。你的任务是,快速分析用户的提问,并将其准确地分类到以下几种处理路径中的一种。你的输出必须是且仅是下面定义的类别名称之一。
处理路径选项及其定义:
greeting_or_chitchat:适用于那些简单的问候、闲聊、感谢或不包含明确信息需求的对话。例如:‘你好’、‘谢谢你’、‘你真聪明’。faq_search:适用于那些非常明确的、可以在常见问题(FAQ)知识库中找到唯一、标准答案的问题。这些问题通常不涉及个人信息。例如:‘你们的退货政策是什么?’、‘工作时间是几点到几点?’database_query:适用于那些需要查询与用户个人账户相关的、结构化数据库中的精确信息的问题。这些问题通常包含订单号、用户名等标识。例如:‘我的订单号为 12345 的物流状态是什么?’、‘查一下我上个月的话费。’rag_pipeline:适用于那些开放性的、复杂的、需要结合多份非结构化文档进行深入理解和总结才能回答的问题。这是最昂贵的路径,只在必要时使用。例如:‘分析一下我们公司和主要竞争对手相比,在产品设计上的核心优势和劣势。’human_escalation:适用于那些包含强烈负面情绪(如愤怒、失望)、明确投诉、或明显超出机器人处理能力的复杂问题。现在,请对以下用户提问进行分类。
用户提问: ‘你们这个破 App 怎么又双叒叕闪退了啊?我刚写了一半的报告都没了!气死我了!我要投诉!’
你的分类结果(只输出类别名称):”
模型会毫不犹豫地输出:human_escalation
后端系统拿到这个分类结果就知道,这个问题绝对不该再往下走。它应该立刻中断自动流程,自动创建一个高优先级的工单,并给用户返回一条安抚性的话术:“非常抱歉给您带来了不好的体验,您的问题我们已经收到,正在为您转接人工专家客服,请稍候。”
通过这样一个前置的“路由”层,可以构建一个灵活、高效、成本可控的混合架构(Hybrid Architecture)。在这个架构里,那套全功能的 RAG 管道专门用来处理最复杂、需要深度理解的问题;而海量的、简单的日常问题,则用更便宜、更快速的方案(比如简单的关键词匹配、FAQ 向量搜索、Text-to-SQL 查询)解决。
不要迷信任何单一的技术,也不要试图用一种方案处理所有问题,而是按问题的类型和复杂度,分别选择成本最低、效果够用的处理方式。
14.7 RAG 范式演进(响应式检索走向主动协同)
前面几节讲的内容,把 RAG 当成一条单趟流水线。用户提问,经过查询重写、检索、去噪、压缩、生成五个环节,输出最终答案。这种朴素范式至今仍是生产环境里部署最广的形态,但 2024 年开始,社区陆续涌现出几条更激进的演进路线,把 RAG 从“响应式检索 + 生成”改造成“主动评估 + 自适应”,对 Prompt 设计也提出了新的要求。
这一节把四条主流演进路线并排放在一起,逐个说明 Prompt 的写法需要跟着做哪些调整。
自评检索质量的 Self-RAG
Self-RAG 由 Asai 等人在 2023 年底提出(arXiv:2310.11511)。传统 RAG 永远假设检索回来的材料就该用,Self-RAG 不做这个假设,而是在模型内部训练出一组特殊的“反思 Token”,让模型对自己的检索质量、生成质量做自评。
落到 Prompt 上,关键差别是要给模型一组明确的反思指令。下面是一段典型的 Self-RAG 风格指令。
“下面提供一段检索材料。请先判断它是否包含回答用户问题的关键信息。如果不包含,请直接输出
[No Retrieval]并说明原因;如果包含,请用[Retrieval]标记,并在最终答案后面用[Relevant]或[Partially Relevant]标注利用程度。所有反思标记都必须出现在答案的最前面,不能省略。”
这套指令把检索质量的判断权交给了生成模型,Prompt 的重心从“如何基于材料回答”前移到“是否需要材料、材料的可信度如何”。Self-RAG 特别适合对单条答案的事实性要求极高、但允许牺牲一些吞吐量的场景,比如医疗问答、合规审计。
检索后加修正的 CRAG
CRAG(Corrective RAG)由 Yan 等人在 2024 年初提出(arXiv:2401.15884)。Self-RAG 让模型自评,CRAG 把评估步骤外置,做成一个独立的“修正器”模块。
修正器通常是一个轻量分类器或一个更小的 LLM,对每一条检索结果打上三个标签:正确(Correct)、错误(Incorrect)、模糊(Ambiguous)。拿到标签后,系统对“错误”的丢弃,对“模糊”的触发一次补充检索,对“正确”的直接进入生成。
Prompt 的设计重点因此变成两段。第一段是给修正器的分类指令。
“你是一个检索质量评估员。请判断下面这条文档片段是否包含回答用户问题的关键事实。如果完全相关,输出
Correct;如果完全无关,输出Incorrect;如果部分相关或难以判断,输出Ambiguous。”
第二段是给生成模型的“去伪存真”指令。CRAG 论文建议在最终 Prompt 里明确写上“仅使用被标记为 Correct 的文档作为依据,忽略 Incorrect,谨慎处理 Ambiguous”。这一行话让生成环节的忠实度有显著提升。
CRAG 适合已经在线上跑的朴素 RAG,需要在不重训模型的前提下快速补强。
自主决策的 Agentic RAG
Agentic RAG 不再把检索看成必经环节,而是把检索当成 Agent 工具箱里的一种工具,由 Agent 自己决定要不要用、用几次、用哪个。
这一套范式没有单一的提出文献,更接近 LangChain、LlamaIndex 在 2024 年陆续推出的 ReAct 风格 RAG 框架。核心机制是 Agent 在每一步思考后输出两类动作,要么发起检索请求,要么直接给出答案。系统根据 Agent 的动作路由到不同分支。
Prompt 的写法因此变成一个“决策 + 执行”的模板。下面这段指令是典型的 Agentic RAG 入口 Prompt。
“你是一个具备检索能力的问答助手。回答用户问题时,请按以下规则行动。如果问题可以直接用通用知识回答,就直接给出答案;如果需要最新或私有的信息,请输出
SEARCH: <改写后的查询>,等待系统返回检索结果后再继续作答。每轮最多发起 3 次检索,超过 3 次仍未找到答案,请如实告知用户。”
这套 Prompt 鼓励模型在“回答”和“检索”之间灵活切换,不再强行走完整条流水线。Agentic RAG 适合多跳推理、跨源整合的场景,比如让模型先搜公司政策、再查产品手册、最后汇总成一份内部申请流程说明。
引入知识图谱的 GraphRAG
GraphRAG 由微软研究院 Edge 等人在 2024 年 4 月公布(arXiv:2404.16130,并在同年 7 月开源)。传统 RAG 把文档当作扁平的文本块,GraphRAG 在索引阶段额外构建一张实体关系图,把文档中的实体、实体间的关系、实体所属的社区全部抽取出来。
查询进来后,系统同时从两个通道取证据,一条走传统的向量检索,一条走图谱遍历。图谱通道特别擅长回答“跨文档的全局性问题”,例如“这家公司过去三年所有产品线共同依赖的核心技术是什么”。这类问题在朴素 RAG 里几乎无法回答,因为答案的线索散落在几十份文档里,向量检索只能召回局部相关片段。
Prompt 的写法相应分成两层。索引阶段需要一段实体抽取指令,要求 LLM 从每篇文档里抽出实体三元组。
“阅读下面文档,输出所有
(主体, 关系, 客体)三元组,主体和客体用名词短语表示,关系用动词短语表示。每行一个三元组。”
查询阶段则需要在最终生成 Prompt 里多塞一段图谱上下文。
“下面除了检索片段,还提供一段从知识图谱中提取的实体关系摘要。请综合两类信息作答。如果两类信息冲突,请优先采纳图谱摘要中的关系型结论,并标注其来源节点。”
GraphRAG 的代价是索引成本高、构建周期长,适合离线一次性建设、长期运行的内部知识库。
这四条路线对 Prompt 设计意味着什么
把四条路线并排,Prompt 设计的重心发生了清晰迁移。朴素 RAG 把 Prompt 集中在最终生成环节,Self-RAG 把重心前移到对检索质量的反思,CRAG 在两者之间插入一个外部评估模块,Agentic RAG 让模型在多轮交互中自主决策检索时机,GraphRAG 则把索引阶段的实体抽取 Prompt 提到与查询阶段同等重要的位置。
无论是哪一条路线,Prompt 都不再是“一句请总结”这种一句话工具,而是一组分布在检索前后、围绕事实性与可控性精心编排的指令集。这也是这一章反复强调的核心命题。
本章总结 Prompt 贯穿 RAG 全链路
这一章的信息量不小,到这里收个尾。
从查询处理阶段的“查询重写”,到检索后处理的“知识去噪”,再到生成阶段的“引用生成”,最后到系统入口的“智能调度”。
在 RAG 这条由向量数据库、Embedding 模型、LLM 等模块拼接起来的流水线上,真正把各模块串成完整链路的,恰恰是那些分布在各个环节的 Prompt。
它们规定每个环节做什么、输出什么,决定整个系统的智能水平和可靠性。一个 RAG 系统的好坏,很大程度上并不取决于用了多牛的向量数据库,而取决于 Prompt 写得多好。
所以,从今天起,不要再把 RAG 系统里的 Prompt 设计看作收尾阶段的、无关紧要的“调参”工作,而要从项目的第一天开始,就把它提升到架构设计的高度。设计查询处理模块时,就要想好“重写”、“分解”、“假设”等多种 Prompt 策略,以及它们之间的路由逻辑。设计检索模块时,就要考虑到后续“去噪”、“排序”、“融合”的 Prompt 该如何与之高效地配合。到了设计生成模块时,则要把“强制引用”、“忠实性”、“知之为知之”这些代表着系统责任感的规则,用最严谨的、不容置疑的语言,刻进 Prompt 里。
掌握了这些分布在 RAG 全链路上的 Prompt 设计方法,才算真正能驾驭 RAG 系统。这也是只会调用 API 的“调包侠”,和能够构建稳健、可靠、值得信赖的 AI 系统的工程师之间的分水岭。
现在,可以回到自己的项目里,把这一章的每份 Prompt 逐个写出来、跑一遍了。
参考资料
- Gao, L., Ma, X., Lin, J., & Callan, J. (2023). Precise Zero-Shot Dense Retrieval without Relevance Labels. Proceedings of the 61st Annual Meeting of the Association for Computational Linguistics (ACL 2023, Long Papers), pp. 1762–1777. arXiv:2212.10496. https://arxiv.org/abs/2212.10496 (HyDE 的提出文献)
- Zheng, H. S., Mishra, S., Chen, X., Cheng, H.-T., Chi, E. H., Le, Q. V., & Zhou, D. (2023). Take a Step Back: Evoking Reasoning via Abstraction in Large Language Models. ICLR 2024. arXiv:2310.06117. https://arxiv.org/abs/2310.06117 (Step-Back Prompting 的提出文献,作者来自 Google Research)
- Liu, N. F., Lin, K., Hewitt, J., Paranjape, A., Bevilacqua, M., Petroni, F., & Liang, P. (2023). Lost in the Middle: How Language Models Use Long Contexts. Transactions of the Association for Computational Linguistics (TACL), 2023. arXiv:2307.03172. https://arxiv.org/abs/2307.03172 (“迷失中间”现象的提出文献,作者来自 Princeton University、UC Berkeley、Stanford 与 Samaya AI)
- BAAI. bge-reranker-v2-m3 模型卡(Hugging Face). https://huggingface.co/BAAI/bge-reranker-v2-m3
- Google. The Need for Mobile Speed(移动端加载时长与访问流失研究,2016). https://blog.google/products/admanager/the-need-for-mobile-speed/ (53% 移动端用户在加载超过 3 秒时流失的数字,常被引为 DoubleClick 2016 调研)
- Asai, A., Wu, Z., Wang, Y., Sil, A., & Hajishirzi, H. (2023). Self-RAG: Learning to Retrieve, Generate, and Critique through Self-Reflection. arXiv:2310.11511. https://arxiv.org/abs/2310.11511 (Self-RAG 的提出文献,作者来自 University of Washington、AI2 与 IBM Research)
- Yan, S., Gu, J., Zhu, Y., & Ling, Z. (2024). Corrective Retrieval Augmented Generation. arXiv:2401.15884. https://arxiv.org/abs/2401.15884 (CRAG 的提出文献)
- Edge, D., Trinh, H., Cheng, N., Bradley, J., Chao, A., Mody, A., Truitt, S., & Larson, J. (2024). From Local to Global: A Graph RAG Approach to Query-Focused Summarization. arXiv:2404.16130. https://arxiv.org/abs/2404.16130 (微软研究院提出的 GraphRAG 方法)
- Microsoft Research. GraphRAG 项目主页与开源仓库. https://microsoft.github.io/graphrag/