https://python.langchain.com.cn/docs/expression_language/cookbook/retrieval

要搞懂“跟进问题+对话历史改写为独立问题”,核心是解决“指代词(他/她/它、这个/那个)没上下文,检索器看不懂”的问题。我用文档里的具体案例拆解,不绕抽象概念,每一步都对应实际场景和代码逻辑。

一、先明确3个关键概念(用文档案例定义)

先把“对话历史、跟进问题、独立问题”这三个词和具体例子绑定,你就能直观理解:

概念 定义(文档案例) 例子(文档中的对话)
对话历史 之前的交互记录,包含用户和AI的对话内容,是理解“指代词”的关键上下文。 「用户:Who wrote this notebook?(谁写的这个笔记本?);AI:Harrison(哈里森)」
跟进问题 用户在对话历史后提出的新问题,通常包含指代词(他/她/它、这个/那个),依赖历史才能理解。 「where did he work?(他在哪工作?)」——“he”(他)依赖历史才知道是指Harrison
独立问题 把“跟进问题+对话历史”合并改写后,去掉指代词、补充完整上下文的问题,不依赖历史也能看懂。 「where did Harrison work?(哈里森在哪工作?)」——无指代词,单独看也明确

二、为什么基础RAG处理不了“跟进问题”?(核心矛盾)

基础RAG的检索逻辑是:用用户当前的问题直接去向量库匹配相似文本。但“跟进问题”里的指代词(如“he”)没有上下文,向量库看不懂“他”指谁,会导致检索失效——

比如基础RAG遇到“where did he work?”:

  1. 检索器拿到问题“where did he work?”,生成对应的向量;
  2. 向量库中只有“harrison worked at kensho”这条文本,其向量与“he”(无明确主体)的向量语义不匹配
  3. 检索器找不到相关上下文,LLM就无法生成准确回答。

这就是必须改写的原因:跟进问题有“歧义”,必须用对话历史补充信息,把歧义消除,变成无歧义的独立问题,检索器才能正确匹配上下文

三、改写的具体过程(对应文档代码,分3步)

文档里是通过“提示模板+LLM”实现改写的,核心是让LLM读“对话历史+跟进问题”,自动把指代词替换成明确的主体。具体步骤对应文档中的代码逻辑:

步骤1:准备“改写提示模板”(CONDENSE_QUESTION_PROMPT)

先定义一个提示,明确告诉LLM“你的任务是把‘对话历史+跟进问题’改成独立问题”,模板内容来自文档:

_template = """Given the following conversation and a follow up question, rephrase the follow up question to be a standalone question, in its original language.
Chat History:
{chat_history}  # 这里填对话历史
Follow Up Input: {question}  # 这里填跟进问题
Standalone question:"""
CONDENSE_QUESTION_PROMPT = PromptTemplate.from_template(_template)

这个模板的作用是给LLM“发指令”,让它按规则改写。

步骤2:把“对话历史+跟进问题”传入模板

将实际的对话历史和跟进问题填充到模板中,形成LLM能理解的完整指令。比如文档中的案例:

  • 对话历史(chat_history):"Human: Who wrote this notebook?\nAI: Harrison"(用get_buffer_string函数把Message列表转成这种字符串格式)
  • 跟进问题(question):"where did he work?"

填充后,传给LLM的指令是:

Given the following conversation and a follow up question, rephrase the follow up question to be a standalone question, in its original language.
Chat History:
Human: Who wrote this notebook?
AI: Harrison
Follow Up Input: where did he work?
Standalone question:

步骤3:LLM生成独立问题

用ChatOpenAI(文档中用的是这个模型)处理上述指令,LLM会自动识别“he”指的是对话历史中的“Harrison”,生成独立问题:

where did Harrison work?

到这里,“跟进问题+对话历史”就成功改写成了“独立问题”——没有指代词,单独看也明确,检索器能直接用它去向量库匹配。

四、改写后的作用(回到RAG流程)

改写完成后,整个对话式RAG的流程就通了,对比基础RAG的区别:

流程环节 基础RAG(无改写) 对话式RAG(有改写)
1. 接收用户输入 直接拿“where did he work?”去检索 先把“对话历史+where did he work?”改写成“where did Harrison work?”
2. 检索上下文 因“he”无明确主体,检索不到“harrison worked at kensho” 用“where did Harrison work?”检索,成功匹配到相关文本
3. LLM生成回答 无上下文,无法准确回答 基于检索到的“harrison worked at kensho”,回答“Harrison worked at Kensho.”

五、总结:改写的核心目的

一句话说清:给跟进问题中的“指代词”(他/她/它、这个/那个)补充对话历史中的“明确主体”,消除歧义,让检索器能看懂问题,从而找到正确的上下文

比如:

  • “他” → 补充历史后明确是“Harrison”;
  • “这个工具” → 补充历史后明确是“LangChain”;
  • “那个问题的答案” → 补充历史后明确是“‘2+2等于几’的答案”。

没有这一步,对话式RAG就无法处理日常对话中常见的“指代性提问”,只能像基础RAG一样,要求用户每次都问“无歧义的完整问题”,体验很差。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐