最近在搭一个简单的RAG问答系统,用的是LangChain + OpenAI的embedding,检索效果还行,但LLM回答时总喜欢“自由发挥”——比如用户问“张三在2023年说过什么政策”,明明库里只有一段原文,它非要自己总结一遍,甚至加些原文没有的细节。我试过在system prompt里写“请严格引用原文回答”,但效果不稳定,有时候还是乱编。想请教下大家,有没有什么prompt技巧或者模板能让模型更“死板”一点?另外,如果原文很长,是不是应该把检索到的片段分段塞进prompt,还是直接整段放进去比较好?感谢!
RAG系统里prompt怎么写才能让LLM更准确引用原文?
全部回复
共 141 条我之前也踩过这个坑,后来发现光靠system prompt压不住,得在user prompt里把检索原文用引号包起来,然后明确加一句“只基于以上引号内容作答,禁止推理”。另外长文本分段确实比整段塞进去效果好,不然模型注意力容易被稀释,我一般按段落切分然后给每段编个号,让模型回答时标注引用编号,这样它就算想编也会先掂量一下。
试试在prompt里加一句“如果原文没有直接答案,就明确说没找到”,比硬逼它引用管用。
试试在user prompt里把原文标成引用块,再明说“只能复述引号内内容”,效果比system prompt稳。长文本建议按段落拆开,每段前加编号。
分段塞确实更靠谱,整段太长模型容易抓不住重点,而且引用时容易串行。再配合few-shot给个正例,基本能治住乱编。
试试在user prompt里把原文用引号包起来,再明确要求“只能引用引号内内容”,效果比system prompt稳。长文本的话建议分段塞,每段前加编号,让模型引用时标注段号。
这问题我太有同感了,当时搞RAG也卡在这。可以试试在prompt里明确要求“用引号标出原文片段,回答只由引用拼接而成”,比单纯说“严格引用”管用。另外分段确实比整段塞效果好,尤其是长文本,我一般按句子切,再加个“如果原文没有相关信息就直接说不知道”的兜底,能明显减少瞎编。
我最近也在搞类似的东西,发现光靠system prompt压不住模型自由发挥,后来我加了个硬性要求:让它在回答里必须带上“根据原文第X段”这种标记,然后配合few-shot给两个正反例子,效果立刻稳了不少。你试过把检索到的片段切成更小的chunk吗?我之前发现如果原文很长,整段塞进去模型容易抓不住重点,反而更容易脑补,分段后每段前面加个简短标题或者关键词提示,它引用起来会更准。还有个土办法,就是让模型先输出“原文摘录”再输出“基于摘录的回答”,两步分开走,这样就算它想编也会被第一步卡住。不过说实话,prompt只是兜底,真正靠谱的还是得把检索质量提上去,比如用混合检索或者重排序,不然prompt写得再死板也架不住上下文里没有正确答案。你试过在user prompt里把原文用特殊符号包起来然后明确说“只准用符号内的内容”吗?我这么干之后幻觉少多了,但偶尔还是会漏细节,所以现在还在折腾怎么把多段检索结果按照逻辑顺序编排进prompt,感觉比单纯堆砌要重要。
可以试试在prompt里加“若原文无此信息,直接说不知道”,比单纯要求引用管用得多。分段塞进去效果会好点,但别超过模型上下文窗口的70%。
我之前也踩过这个坑,后来发现光靠system prompt真不够,得在user prompt里把原文片段用引号包起来,再加一句“只能根据引号内内容回答,不得补充其他信息”,效果会稳很多。分段塞肯定比整段好,尤其原文长的时候,不然模型注意力会分散,容易自己抓重点。另外可以试试让模型先输出“引用原文”再给结论,或者用few-shot给个正例反例,比单纯命令管用。
我之前也踩过这坑,后来在prompt里加了“只输出原文包含的内容,禁止推理”才好点,你可以试试。
分段塞进去效果更好,整段太长模型容易抓不住重点,我一般按段落切开放。
我之前也踩过这个坑,后来发现光靠system prompt压不住,得在user prompt里把原文用引号框起来,然后明确要求“只能从引号内内容提取答案,禁止联想”。分段塞比整段放效果好很多,尤其是长文本,模型注意力容易涣散,你可以按语义切几段,每段前面标个编号,让LLM回答时标注引用来源,这样就算它想编也编不圆。
试试在提示词里加一句“若原文未提及则明确说未找到”,再让模型逐段标注引用来源,比单靠“严格引用”管用。
我最近也在搞类似的,试过在prompt里加“如果原文没有明确提到,就回答不知道”,效果比单纯说“引用原文”好很多,你可以试试把约束具体化。另外原文太长的话建议分段塞,但每段前面标个序号,然后让模型在回答里标注是引用了哪一段,这样它会更有“依据感”,很少自己编了。
我之前也踩过这个坑,试下来最管用的还是让模型先“抄”再“答”:在prompt里明确要求它第一步把相关原文完整摘出来,第二步再基于摘录做解释,这样能压住自由发挥的冲动。另外原文太长的话建议分段塞,但每段前面加个来源标记(比如[1][2]),回答时强制它带上标记引用,不然模型容易抓错重点。还有个土办法,system里直接写“如果原文没有明确信息,就说不知道”,比单纯说“严格引用”管用得多。
我之前也踩过这个坑,后来发现光靠system prompt压不住,得在user prompt里把原文用引号框起来,再明确加一句“如果找不到直接依据就回答不知道”。另外你可以试试把检索片段按相关性标号,让模型引用时带上编号,这样就算它自由发挥,你也能追溯到底引没引对。至于长原文,我建议分段塞,但每段前加个简短的标题提示,不然模型容易把上下文搞混,反而更爱瞎编。
我之前也踩过这个坑,后来发现光靠system prompt压效果真不行,得在user prompt里把检索片段和问题绑一块儿,比如明确写“根据下面这段原文回答,不要提原文外信息”,再给个引用格式示例,比如“原文提到……”,模型会乖很多。另外你试过few-shot吗?给一个“问题+原文+严格引用回答”的样例,比纯指令管用,尤其对GPT-4这类模型,模仿比服从更稳定。片段太长的话,我建议别整段塞,按语义切块再标上编号,让模型引用时带编号,比如“根据片段2”,这样既省token又能逼它指向具体位置。还有个土办法,把检索分数低于阈值的片段直接删掉,宁可让它说“找不到”,也别给它自由发挥的机会,不然它老觉得自己懂了。你用的OpenAI embedding是text-embedding-ada-002吗?我感觉换bge或者带指令微调的embedding,检索质量会影响后面引用的准确性,源头不干净,prompt再强也白搭。对了,你有没有试过在解析层强制输出JSON,比如要求返回{quote: “原文句子”, answer: “简短结论”},这样至少能逼它先抄一遍原文,逻辑上就减少编造空间了。
我之前也踩过这个坑,后来发现光靠system prompt压不住,关键是把检索片段按“原文引用”格式直接塞进user message里,比如每条前面加“【原文段落N】”,再要求模型只能摘录不能改写。另外你试过temperature调到0.1以下没?这招比写prompt管用。至于长文分段,我建议按语义切块而不是硬截断,每块带元数据(日期、发言人),这样模型引用时更容易锁定位置。
试试在prompt里加一句“若原文无对应信息就直接说不知道”,再把检索片段按段落标号塞进去,让模型引用时带编号。
我之前也踩过这个坑,光靠system prompt压不住它自由发挥。后来我改成在user prompt里把原文用特殊标记包起来,比如明确说“以下内容来自文档,请只基于这些内容作答”,效果会稳很多。另外分段塞确实比整段扔进去好,尤其原文长的时候,模型注意力容易散,你试试把相关片段拆成带编号的小块,让它在回答里引用编号,这样既能溯源又不容易编。
我之前也踩过这个坑,后来发现光靠system prompt不够,得在user prompt里把原文用明确标记包起来,比如“以下是参考资料,请只基于这些内容作答”,再配合few-shot给个正反例效果会稳很多。分段塞确实比整段好,尤其原文长的时候,模型注意力容易分散,你可以按语义切成几个小块,每块前面标个编号,让它在回答里引用编号。另外如果还乱编,可以试试把temperature调低到0.1左右,代价是回答会显得有点呆,但准确率上来了。
试试在prompt里加一句“如果原文没有答案就直接说不知道”,然后再把每段检索内容前面标上来源编号,让它按编号引用。