最近在搭一个简单的RAG问答系统,用的是LangChain + OpenAI的embedding,检索效果还行,但LLM回答时总喜欢“自由发挥”——比如用户问“张三在2023年说过什么政策”,明明库里只有一段原文,它非要自己总结一遍,甚至加些原文没有的细节。我试过在system prompt里写“请严格引用原文回答”,但效果不稳定,有时候还是乱编。想请教下大家,有没有什么prompt技巧或者模板能让模型更“死板”一点?另外,如果原文很长,是不是应该把检索到的片段分段塞进prompt,还是直接整段放进去比较好?感谢!
RAG系统里prompt怎么写才能让LLM更准确引用原文?
全部回复
共 141 条我之前也踩过这个坑,后来发现光靠system prompt压不住,关键是让模型“无路可走”。我会在user prompt里直接塞原文,然后明确告诉它“只能从下面这段里找答案,找不到就说不知道”,再配合few-shot给一个“引用+简短解释”的例子,效果比单纯说“严格引用”稳很多。你试试把检索到的片段按相关性排序分段塞进去,每段前加个编号,让模型在回答里写“根据片段3”,这样它至少有个锚点,不太敢乱编。另外,如果原文特别长,建议先做个简单的截断,只保留包含关键词的上下文窗口,整段全塞容易让模型注意力散掉,反而更爱自由发挥。还有个土办法,把temperature调到0,再把max tokens限制得刚好够回答,能减少很多幻觉。不过说到底,调prompt只是兜底,如果检索回来的片段本身质量不行,模型再“死板”也救不回来,你可以试试在召回后加一步重排,把最相关的两三条顶到最前面。对了,你用的是哪个Embedding模型?有些模型对长文本的语义切分不敏感,换一个说不定检索精度就上来了。
我之前也遇到过这问题,后来发现光靠system prompt压不住,得在user prompt里把原文用特殊标记包起来,比如【引用开始】...【引用结束】,然后明确要求“回答只能包含标记内的内容”。分段塞我觉得比整段好,长文本容易让模型抓不住重点,你可以把检索到的片段按相关性排序,每段前面加个小标题。另外试试temperature调低到0.1或以下,效果立竿见影。
试试在prompt里加“如果原文没有明确信息就直接说不知道”,再配上分段引用格式,效果会稳很多。
我试过在prompt里加“如果没有完全对应的原文,就直接说不知道”,比单纯说“引用原文”管用得多。另外分段塞确实比整段好,但每段前面最好标个来源编号,让模型知道引用哪段。你试试把system prompt改成“回答必须包含原文引用的双引号,且不能添加引号外的信息”,会强制它贴原文。
试试给每段原文加个编号,然后要求模型“只能引用编号对应的原文”,比纯靠prompt强调靠谱点。
我最近也在调这个,试了下把原文按段落编号塞进prompt,然后明确要求模型回答时必须带编号引用,比如“根据[3]”,效果比单纯说“严格引用”靠谱多了。另外,如果原文太长,建议先按语义切块,只把和问题最相关的几段放进去,整段塞容易让模型抓不住重点,反而更爱自由发挥。
还有就是system prompt里别只写“引用原文”,最好给个具体例子,比如“如果原文是xxx,你应该回答yyy”,模型看到范例会老实很多。你用的是LangChain的话,还可以试试在retriever阶段调高相似度阈值,减少不相关片段混进来,LLM乱编的概率也会小一点。
试试把原文拆成带编号的小段,让模型先引用编号再回答,会收敛很多。
我最近也在搞类似的,试过在prompt里加“如果原文没有相关信息就明确说不知道”这类约束,比单纯说“严格引用”好用一些。另外把检索到的片段按相关度排序,再在每段前面标个序号,让模型在回答时直接引用序号对应的内容,这样它就不太容易自己发挥了。原文太长的话建议分段塞,不然容易把模型注意力搞分散,反而漏掉关键细节。
我之前也踩过这个坑,后来发现光靠system prompt不行,得在user prompt里把原文用引号或者特殊标记框起来,然后明确告诉它“只能从标记范围内提取信息”,效果会稳很多。另外原文太长的话分段塞确实更可控,但每段前面最好加个编号,不然模型容易把顺序搞混,尤其是跨段引用的时候。你可以试试在结尾加一句“如果原文里没有,就回答不知道”,这个对减少幻觉挺管用的。
我之前也遇到过这情况,后来在prompt里加了“如果检索内容中没有答案就直接说不知道”这个约束,幻觉明显少了。至于长文本,我试过分段塞进去效果更好,但每段前面得标注清楚来源和上下文,不然模型还是容易串。另外可以试试在user prompt里把原文用引号包起来再强调“只基于引号内内容回答”,比单靠system prompt稳一些。
试试在user prompt里把原文用引号包起来,然后明确要求“只输出引号内的内容”,效果比system指令稳很多。
我之前也踩过这个坑,后来发现光靠system prompt压不住,得在user prompt里把“引用原文”变成硬性格式要求,比如让模型输出带引号的片段,再附一句“如果原文没有对应内容,请直接回答未找到”。片段太长的话,我习惯按段落拆开分别塞,每段前面标好来源编号,这样模型引用时更容易指对位置,整段塞进去反而容易让它抓不住重点。另外可以试试few-shot,给一两个“正确引用”和“错误编造”的对比例子,比单纯说“不要乱编”管用得多。
我试过在prompt里直接把原文用引号包起来,然后明确告诉模型“回答只能包含引号内的内容,禁止改写”,效果比单纯说“严格引用”好很多。另外原文长的话分段塞更好,但每段前面加个编号,让模型在回答时标注引用来源,比如“根据[片段2]”,这样就算它想发挥也会被锚住。还有个土办法,把系统提示改成“如果找不到直接对应的句子,就回答‘未找到相关信息’”,能逼它少编点。
这个问题我最近也踩过坑,光在system prompt里强调“引用原文”真没用,模型还是会忍不住总结。后来我试了个笨办法,把检索到的片段直接改成对话历史里的“用户输入”格式,比如“已知信息:……”,然后明确要求“只能基于已知信息回答,不允许添加任何未出现的表述”,效果好了不少。至于长文本,我建议分段塞,但每段前面加个编号,然后让模型在回答里标注对应编号,这样它反而更不敢乱编,因为一旦编了就对不上号。另外你可以试试把温度调低到0.1以下,配合这个prompt思路,基本能压住自由发挥的冲动。不过还有个坑,如果片段之间语义有重叠,模型可能会合并信息,这时候最好在prompt里加一句“禁止合并不同来源的细节,逐句引用”。我自己还试过在结尾强制加“请用原文原句回答,不要改写”,但有时候模型会直接复读一整段,也挺头疼,所以可能还得根据你文档的类型微调一下。
我之前也踩过这个坑,后来发现单纯强调“引用”不如直接把原文片段切成小块,每块前面加个编号,然后让模型回答时带上编号和引文。另外system prompt里加一句“如果原文没有直接答案,就明确说没有”能减少瞎编。长原文还是分段塞吧,整段塞容易让模型抓不住重点。
我之前也踩过这个坑,后来发现光靠system prompt不够,得把原文拆成带编号的片段塞进user message里,然后明确要求“只引用片段编号对应的内容”。你可以试试在prompt里加一句“如果原文没提到,直接说不知道”,比“严格引用”管用。另外原文太长的话建议分段,但每段前加个简短的标题或摘要,模型不容易迷失重点。
试试在prompt里限定只输出“原文摘录+编号”,并明确禁止改写,实测比单纯说“引用”管用。长文本分段塞确实更稳,但别超过模型上下文窗口。
试试把原文切块后每块前面加编号,让模型回答时直接引编号,比让它复述靠谱多了。
我之前也踩过这个坑,后来发现光靠system prompt压不住,得在user prompt里把原文用引号框起来,然后明确要求“只能从引号内容中提取信息,禁止推理”。另外分段塞确实比整段好,但别切太碎,我一般按语义块切,每块前面加个编号,让模型回答时直接引用编号,准确率会高不少。
我之前也踩过这个坑,光靠system prompt压不住,后来在user prompt里把检索片段用引号包起来,并且明确写“只基于引号内的内容作答,不要扩展”会稳很多。原文太长的话建议分段塞,每段前加个小标题,模型反而更容易定位,整段塞进去经常捡了芝麻丢西瓜。还有个土办法,让模型先复述一遍原文再回答,虽然笨但效果奇好,你可以试试。