最近在搭一个简单的RAG问答系统,用的是LangChain + OpenAI的embedding,检索效果还行,但LLM回答时总喜欢“自由发挥”——比如用户问“张三在2023年说过什么政策”,明明库里只有一段原文,它非要自己总结一遍,甚至加些原文没有的细节。我试过在system prompt里写“请严格引用原文回答”,但效果不稳定,有时候还是乱编。想请教下大家,有没有什么prompt技巧或者模板能让模型更“死板”一点?另外,如果原文很长,是不是应该把检索到的片段分段塞进prompt,还是直接整段放进去比较好?感谢!
RAG系统里prompt怎么写才能让LLM更准确引用原文?
全部回复
共 141 条我最近也踩过这个坑,试下来最有用的一招是在prompt里明确要求“只输出原文中出现的连续句子,禁止改写或拼接”,同时把检索片段按原文段落顺序标好序号塞进去,比整段丢进去效果好很多。另外,如果原文长,分段时保留上下文边界很重要,不然模型容易断章取义。你试试在user prompt里加一句“如果问题在原文中找不到直接对应内容,直接回答无法回答”,能减少不少幻觉。
试试在prompt里加一句“如果原文没有就直接说不知道”,再让模型先抄原文再回答,会稳很多。
我之前也踩过这个坑,后来发现光靠system prompt里喊“严格引用”真没用,模型该发挥还是发挥。我觉得关键是把“引用”变成一种任务约束,而不是道德要求——比如让模型先逐字摘录原文,再基于摘录内容做解释,这样它的“自由发挥”空间就被压缩了。另外,有个小技巧是把检索到的片段按段落标记成[1][2]这种编号,然后明确告诉模型“回答时必须在对应编号后跟原文,禁止改写”,效果比纯文字描述好很多。关于长文本,我个人建议别整段塞,因为token一多模型注意力会分散,反而更容易漏细节,最好按语义切块,每块前面加一句简短摘要,让模型知道这段在讲什么。还有个疑问想请教下,你有没有试过在user prompt里把问题拆成“先找证据,再给结论”两步?我试过这种两步走,比单次生成稳不少。最后说一句,其实可以配合few-shot示例,给它看一个“正确引用”的问答对,模型模仿起来会更死板。
这问题我也踩过坑,后来发现光靠system prompt压不住,得在user prompt里把原文用引号包起来,再明确要求“只基于以上引号内容回答,禁止扩展”。片段太长的话建议按语义切分,每段前面标个编号,让模型引用时带编号,这样能显著减少编造。另外可以试试few-shot,给一个“问题-正确引用回答”的例子,比单纯说“严格引用”管用得多。
说实话我也踩过这个坑,后来发现光靠system prompt压不住,得在user prompt里把原文用引号框起来,再明确加一句“只能从引号里提取信息,禁止联想”。另外分段塞确实比整段丢进去稳,长文本一多模型就容易抓不住重点,你可以按语义切成小块,每块前面标个编号,让模型回答时带上引用编号,这样就算它想编也会被约束住。
试试在prompt里加一句“如果原文没有就直接说不知道”,再让模型把引用的原文片段用引号标出来,分段塞比整段有效。
我之前也踩过这个坑,后来发现光在system prompt里喊“严格引用”没用,得在user prompt里把原文用XML标签包起来,然后明确指示“只能使用
我最近也在搞这个,发现让模型“引用原文”不如直接给它一个明确的输出模板,比如“请从提供的【资料】中摘录相关句子,不要改写,如果找不到就回答未提及”。另外建议把检索片段按相关度排序分段塞进去,每段前面加个编号,这样模型更容易定位,比整段塞进去效果好很多。
试试few-shot,给一两个“原文+引用”的示例,比光说“严格引用”管用。长原文拆成带编号的段落,让模型引用时标段号,能少跑偏。
我之前也踩过这个坑,光靠system prompt压效果真的不稳定。后来我把引用要求直接写进user prompt里,并且把原文用特殊标记包起来,比如加上“【原文开始】...【原文结束】”,然后明确告诉模型“你的回答必须包含至少一处与【原文】完全一致的连续句子,并且用引号标出”。这样模型会更倾向于直接摘录,而不是自己重写。
另外关于长文本,我试过整段塞进去,但token一多,模型反而会“偷懒”去抓中间部分,容易忽略你真正想让ta引用的那句话。我现在是先把检索到的片段按句子或段落切分,然后只挑和问题最相关的1-2个片段,手动拼进prompt里,效果比全丢进去好很多。你可以试试在切分时保留原文的换行符和标点,这样模型能更清楚哪里是边界。
还有个土办法,就是让模型先“复述”再“回答”,比如先让它用原文原话回答,然后再允许它补充解释。这样至少能保证第一段是严格引用的。另外你可以在prompt里加一个负面例子,比如“不要这样回答:...”,把之前乱编的case直接写进去,模型会更容易避开。
我之前也踩过这个坑,后来发现光靠system prompt压不住,得在user prompt里把“引用”变成硬性任务。比如直接把原文拆成带编号的段落,然后要求模型“回答时必须在每个观点后面标注对应段落号,如果原文没有就直接说不知道”,这样它就没法自由发挥了。另外你试试把“请严格引用原文”改成“请逐字复制原文中与问题相关的句子,不要改写”,效果会好很多——因为LLM对“总结”和“引用”的理解边界很模糊,但对“复制”这个词更敏感。至于长文本分段的问题,我建议别整段塞,尤其是超过模型上下文窗口30%的时候,分段反而能减少注意力分散,但每段开头要加个类似“[片段1]”的标记,方便模型指路。还有个土办法,就是检索回来之后,用规则先高亮出包含关键词的句子,再拼进prompt,等于帮模型划了重点。不过说实话,prompt只是缓解,真要根治得在生成后加一层校验,比对输出和原文的token重合率,低了就强制重试。
试试让模型先引用再回答,把原文用固定符号框起来,效果比单纯说“严格引用”靠谱。长文本分段塞进去确实更稳,整段容易让模型抓不住重点。
试试让模型先逐字摘录再作答,把引用步骤拆出来,效果比光喊口号强。长原文分段喂更好,整段塞容易让模型抓不住重点。
这问题我太有同感了,之前搭RAG也卡在这。你光在system prompt里喊“严格引用”没用,模型压根不知道“严格”的边界在哪。我后来是把检索到的原文按段落拆开,每段前面标个【1】【2】这种编号,然后在prompt里明确写“回答时只允许使用【】里的内容,每个观点后面必须跟上来源编号”。效果立竿见影,它就算想编也没法把编号凭空安上去。另外你可以试试在user query后面加一句“如果原文没有直接答案,就回答‘原文未提及’”,这比单纯强调引用更管用。还有个小坑——原文太长的时候别整段扔进去,一是浪费token,二是模型注意力会被稀释,它反而抓不住关键句。我一般会先用embedding做个粗筛,再按字符数截个800字左右的关键片段,分段塞进去,每段之间用分隔符隔开,这样模型更容易“盯住”某几行字。你现在的检索是直接top-k返回吗?可以考虑加个rerank步骤,把最相关的那一段顶到最前面,模型引用准确率能再上一个台阶。
试试在prompt里加一句“如果原文没有就直接说不知道”,同时把检索片段按段落拆开,每个段落前标个编号让模型引用。
试试在每段检索文本前加“原文如下:”,然后让模型先逐字引用再解释,效果会稳很多。
试试把原文分段标记成“【1】”这种编号,要求回答必须带编号引用,效果比单纯说“严格引用”强多了。
刚入门,这个对我帮助很大。
我之前也踩过这个坑,光靠“严格引用”这种指令确实压不住模型的自由发挥。后来我发现一个比较管用的办法是,在prompt里明确告诉它“如果检索片段里没有直接答案,就回答‘未找到相关信息’”,而不是让它自己推导。另外,你可以在每个片段前加上来源编号,比如[1][2],然后要求回答时必须在句子末尾标注对应编号,这样模型为了不标错号,会更倾向于抄原文而不是改写。关于长文本的问题,我的经验是不要整段硬塞,因为模型注意力会分散,尤其是中间部分容易被忽略,分段塞进去反而效果好,但每段之间要加个分隔符,比如“---”,并且告诉它“每个片段都是独立的证据,不要跨片段拼接信息”。还有个技巧是,把用户问题拆成几个子问题,分别检索再组合答案,这样模型每一步都有据可依,编造空间就小很多。不过说实话,prompt只是辅助,底层还是得靠检索质量兜底,如果召回的内容本身就模糊,模型再怎么限制也会打擦边球。你试过在few-shot例子里放一个“原文引用”的正例和一个“错误改写”的反例吗?有时候一个对比示例比十句指令都管用。
我之前也踩过这个坑,后来发现光靠system prompt施压真不如在user prompt里下功夫。你可以试试把检索到的原文片段用特殊的标记符包起来,比如【引用开始】...【引用结束】,然后明确告诉模型“回答时只能使用标记内的句子,禁止改写或外推”。另外我试过在每条引用后面加上来源ID,让模型在回答里用【1】【2】这种编号标注,它反而会老实很多,因为心理上觉得是在“引用”而不是“复述”。至于原文太长的问题,我自己的经验是分段塞比整段塞强——但每段前面最好加个“第X段:”的标题,不然模型容易混淆上下文。还有个笨但有效的办法,就是限制输出格式,比如要求它先写“根据原文,张三在2023年提到...”,然后再跟一段原文引用,这样至少能逼它先找证据再说话。不过说实话,如果检索回来一堆相似片段,模型还是会选最顺眼的那个,这时候可以考虑调低temperature到0.1以下,效果比改prompt更直接。