在 RAG 系统中,用户查询往往存在 “表述模糊”(如 “怎么煮米饭”)、“结构复杂”(如 “多步骤推理问题”)或 “上下文依赖”(如 “它的核心观点是什么”)等问题,直接导致检索漏检、错检。查询重写通过多视角扩展、复杂拆解、抽象回溯、上下文关联四大策略,将 “不友好” 的查询转化为 “精准适配检索” 的输入,是提升 RAG 效果的关键环节。
多查询重写(Multi-Query Rewriting)
多查询重写的核心是 “基于用户原始查询,生成多个语义相关但表述不同的查询变体”,通过扩大检索范围提升召回率,解决 “单一表述漏检相关信息” 的痛点。
原理
利用 LLM 对原始查询进行 “同义改写、角度扩展、细节补充”,生成 3-5 个查询变体,每个变体覆盖一个检索角度。示例:原始查询 “怎么煮米饭”→ 生成变体:
- “电饭煲煮米饭的详细步骤(水量比例)”
- “煮米饭避免夹生的技巧”
- “不同米种(大米 / 糙米)的煮制时间差异”
优势
- 降低对用户表述的依赖(即使用户说 “煮米”,变体也能覆盖 “米饭煮制步骤”);
- 增加检索结果多样性,避免单一查询漏检关键信息;
- 无需修改向量数据库,仅通过查询层优化,落地成本低。
实现步骤(LangChain 示例)
1. 定义查询生成模板(引导 LLM 生成高质量变体)
QUERY_PROMPT = """ 你需要基于用户原始查询,生成{num_queries}个不同角度的查询变体,要求: 1. 每个变体语义与原始查询相关,但表述不同; 2. 覆盖原始查询的不同细节(如步骤、技巧、场景); 3. 变体需简洁,适合向量检索。 原始查询:{question} 生成的{num_queries}个查询变体: """2. 初始化 MultiQueryRetriever
from langchain.retrievers.multi_query import MultiQueryRetriever from langchain.llms import OpenAI from langchain.vectorstores import Chroma # 1. 加载基础组件(LLM+向量数据库) llm = OpenAI(model="gpt-3.5-turbo-instruct") vectorstore = Chroma(persist_directory="./chroma_db", embedding_function=OpenAIEmbeddings()) base_retriever = vectorstore.as_retriever(search_kwargs={"k": 5}) # 2. 初始化多查询检索器 multi_query_retriever = MultiQueryRetriever.from_llm( llm=llm, retriever=base_retriever, prompt_template=QUERY_PROMPT, num_queries=3, # 生成3个变体(推荐3-5个,过多会增加成本) verbose=True # 打印生成的查询变体 ) # 3. 执行检索 results = multi_query_retriever.get_relevant_documents("怎么煮米饭")适用场景
- 用户查询表述模糊(如 “推荐好的笔记本”);
- 需覆盖多细节的查询(如 “旅游攻略”→ 变体覆盖 “交通”“住宿”“景点”);
- 单一查询召回率低的场景(如专业术语查询 “LoRA 微调”)。
优化效果
检索召回率提升 20%-30%(实测:原始查询漏检 30% 相关文档,多查询后漏检率降至 5% 以内)。
问题分解(Multi-Query Rewriting)
针对 “多步骤、多维度” 的复杂查询(如 “华东区门店导购客单价最高的前 3 名及他们的培训师”),问题分解将其拆分为多个简单子问题,逐个检索后合并结果,解决 “复杂问题一次性检索难以覆盖所有维度” 的痛点。
原理
利用 LLM 将复杂查询拆解为 “逻辑独立、可单独检索” 的子问题,每个子问题对应一个检索目标,最后通过 LLM 整合子问题的检索结果生成最终答案。示例:复杂查询 “华东区门店导购客单价最高的前 3 名及他们的培训师”→ 拆解为:
- 子问题 1:华东区有哪些门店?
- 子问题 2:各门店导购的客单价数据是多少?
- 子问题 3:客单价最高的前 3 名导购是谁?
- 子问题 4:这 3 名导购的培训师分别是谁?
核心优势
- 降低单检索的复杂度,每个子问题仅需检索特定维度信息;
- 便于定位错误(如子问题 2 检索不到客单价数据,可单独排查数据源);
- 支持多源检索(如子问题 1 从门店表检索,子问题 2 从销售表检索)。
实现步骤(LangChain 示例)
1. 定义问题分解模板(引导 LLM 按逻辑拆解)
DECOMPOSE_PROMPT = """ 你需要将用户的复杂查询拆解为多个简单子问题,要求: 1. 每个子问题仅包含一个检索目标,可独立通过数据库/知识库获取答案; 2. 子问题按逻辑顺序排列(如先获取列表,再筛选,最后关联信息); 3. 子问题数量不超过5个(避免过度拆解)。 用户查询:{question} 拆解后的子问题(按顺序): """2. 初始化 DecomposingRetriever 并执行
from langchain.retrievers import DecomposingRetriever from langchain.chains import LLMChain # 1. 构建问题分解链 decompose_chain = LLMChain( llm=llm, prompt=DECOMPOSE_PROMPT, output_key="sub_questions" # 输出子问题列表 ) # 2. 初始化分解检索器(base_retriever为基础向量检索器) decompose_retriever = DecomposingRetriever( llm_chain=decompose_chain, retriever=base_retriever, verbose=True # 打印拆解的子问题 ) # 3. 执行检索(每个子问题单独检索,返回所有子问题的结果) results = decompose_retriever.get_relevant_documents( "华东区门店导购客单价最高的前3名及他们的培训师" ) # 4. 整合结果(用LLM将子问题结果合并为最终答案) merge_prompt = """ 基于以下子问题的检索结果,回答用户原始查询: 原始查询:{question} 子问题结果:{sub_results} 要求:逻辑连贯,信息完整,不遗漏关键数据。 """ merge_chain = LLMChain(llm=llm, prompt=merge_prompt) final_answer = merge_chain.run(question=original_question, sub_results=results)适用场景
- 多步骤推理查询(如 “分析 2024 年 Q1 新能源汽车销量 TOP3 品牌的市场份额变化”);
- 多维度关联查询(如 “某产品的用户评价中,正面评价的主要原因及改进建议”);
- 跨数据源查询(如 “结合销售数据和库存数据,推荐需补货的商品”)。
优化效果
复杂查询的检索准确率提升 40%-50%(实测:未拆解时复杂查询错误率 60%,拆解后错误率降至 25% 以内)。
Step-Back(回答回退)
Step-Back 策略的核心是 “退一步思考”—— 当用户查询具体细节时,先生成一个 “更抽象、更宏观” 的问题,检索该问题的结果(如全书框架、核心概念),再基于框架定位具体细节,解决 “因查询太具体而漏检相关上下文” 的痛点。
原理
用户查询具体细节时,LLM 先生成 “抽象问题”(Step-Back Query),检索抽象问题的结果(获取全局上下文),再用全局上下文辅助定位具体细节。示例:用户查询 “《跨越鸿沟》中技术采用生命周期的 5 个阶段名称”→
- Step-Back 抽象问题:“《跨越鸿沟》的核心理论框架是什么?包含哪些关键模型?”
- 检索抽象问题:获取 “技术采用生命周期是该书核心模型,分 5 个阶段” 的上下文;
- 定位具体细节:在全局框架中找到 5 个阶段的具体名称(创新者、早期采用者、早期大众、晚期大众、落后者)。
优势
- 避免 “只见树木不见森林”—— 先获取全局框架,再定位细节,减少漏检;
- 提升专业问题的准确性(如专业书籍、论文的细节查询,需先理解框架);
- 降低对 “具体表述” 的依赖(即使细节表述有偏差,框架检索仍能覆盖)。
实现步骤(LangChain 示例)
1. 定义 Step-Back 模板(引导 LLM 生成抽象问题)
STEP_BACK_PROMPT = """ 你需要基于用户的具体查询,生成一个“退一步”的抽象问题,要求: 1. 抽象问题比原始查询更宏观,覆盖原始查询所在的“全局框架/核心概念”; 2. 检索抽象问题的结果,能为回答原始查询提供上下文支撑; 3. 抽象问题不包含原始查询的具体细节,但与原始查询强相关。 原始查询:{question} Step-Back抽象问题: """2. 初始化 StepBackRetriever 并执行
from langchain.retrievers import StepBackRetriever # 1. 初始化Step-Back检索器 step_back_retriever = StepBackRetriever( llm=llm, retriever=base_retriever, step_back_template=STEP_BACK_PROMPT, verbose=True # 打印抽象问题和检索结果 ) # 2. 执行检索(先检索抽象问题,再用抽象问题的结果辅助检索原始查询) results = step_back_retriever.get_relevant_documents( "《跨越鸿沟》中技术采用生命周期的5个阶段名称" ) # 3. 生成最终答案(结合抽象问题的全局上下文和原始查询的细节) answer_prompt = """ 基于以下检索结果(包含全局框架和细节信息),回答用户原始查询: 原始查询:{question} 检索结果:{results} 要求:先简要说明全局框架,再准确列出具体细节,逻辑清晰。 """ answer_chain = LLMChain(llm=llm, prompt=answer_prompt) final_answer = answer_chain.run(question=original_question, results=results)适用场景
- 专业书籍 / 论文的细节查询(如 “《深度学习》中反向传播的数学推导步骤”);
- 依赖全局框架的具体问题(如 “Scrum 框架中每日站会的 3 个核心问题”);
- 表述较具体但易漏检上下文的查询(如 “iPhone 15 的摄像头像素”→ 抽象问题 “iPhone 15 的核心硬件配置”)。
优化效果
专业细节查询的漏检率降低 35%-45%(实测:原始查询漏检 “全局框架相关文档” 的概率 40%,Step-Back 后降至 10% 以内)。
指代消解(Coreference Resolution)
在多轮对话中,用户常使用代词(如 “它”“他”“这个”)或省略表述(如先问 “推荐 AI 书”,再问 “核心观点是什么”),指代消解通过 “关联上下文实体”,将模糊查询转化为 “明确实体查询”,解决 “对话中检索上下文断裂” 的痛点。
原理
分析多轮对话历史,识别查询中代词 / 省略表述的 “真实指代实体”,将模糊查询改写为 “实体 + 需求” 的明确查询。示例:
- 对话历史:用户 1:“推荐一本讲解 RAG 的书”→ 助手:“《Retrieval-Augmented Generation》”;
- 用户 2:“它的核心章节有哪些?”→ 指代消解后:“《Retrieval-Augmented Generation》的核心章节有哪些?”
优势
- 确保对话检索的连贯性,避免 “断章取义”;
- 降低用户重复输入成本(无需每次都完整提及实体);
- 减少因指代模糊导致的检索错误(如 “它” 误关联到其他实体)。
实现步骤(LangChain 对话场景示例)
1. 构建对话记忆(存储历史上下文)
from langchain.memory import ConversationBufferMemory from langchain.chains import ConversationChain # 1. 初始化对话记忆(保留最近5轮对话,避免上下文过长) memory = ConversationBufferMemory( memory_key="chat_history", k=5, # 保留5轮对话 return_messages=True # 返回Message对象,便于提取实体 ) # 2. 定义指代消解链(从历史中提取实体,改写查询) resolve_prompt = """ 基于以下对话历史和当前用户查询,完成指代消解: 1. 从对话历史中提取用户当前查询中代词(如“它”“他”“这个”)的真实指代实体; 2. 将当前查询改写为“实体+需求”的明确查询,不改变用户原意; 3. 若未找到明确指代,直接返回原查询。 对话历史:{chat_history} 当前用户查询:{current_query} 改写后的明确查询: """ resolve_chain = LLMChain(llm=llm, prompt=resolve_prompt)2. 执行指代消解与检索
# 1. 模拟多轮对话 chat_history = [ ("user", "推荐一本讲解RAG的书"), ("assistant", "推荐《Retrieval-Augmented Generation: Principles and Practices》") ] current_query = "它的核心章节有哪些?" # 2. 执行指代消解 resolved_query = resolve_chain.run( chat_history=chat_history, current_query=current_query ) # 输出:《Retrieval-Augmented Generation: Principles and Practices》的核心章节有哪些? # 3. 用改写后的查询执行检索 results = base_retriever.get_relevant_documents(resolved_query) # 4. 生成回答并更新对话记忆 memory.save_context({"input": current_query}, {"output": final_answer})适用场景
- 多轮对话 RAG(如客服机器人、智能助手);
- 上下文依赖的查询(如 “上一个推荐的产品参数是什么”);
- 省略表述的查询(如 “这个功能怎么用”,“这个” 关联之前提到的功能)。
优化效果
对话场景中检索错误率降低 50%-60%(实测:未消解时代词查询错误率 70%,消解后降至 25% 以内)。
多策略协同集成
单一策略难以覆盖所有查询场景,需将多查询重写、问题分解、Step-Back、指代消解协同配合,形成 “全链路优化”,具体流程如下:
协同流程
示例:用户对话历史 “推荐一本 AI 产品经理的书”→ 助手 “《AI Product Management: A Practical Guide》”→ 当前查询 “它的核心内容里,怎么解决 AI 产品的落地难点?”
- 第一步:指代消解→ 改写为 “《AI Product Management: A Practical Guide》中,解决 AI 产品落地难点的方法是什么?”;
- 第二步:Step-Back→ 生成抽象问题 “《AI Product Management: A Practical Guide》中关于 AI 产品落地的核心章节是什么?”,检索全局框架;
- 第三步:问题分解→ 将改写后的查询拆为 “AI 产品落地的常见难点有哪些?”“书中针对每个难点的解决方法是什么?”;
- 第四步:多查询重写→ 每个子问题生成 3 个查询变体(如子问题 1 生成 “AI 产品落地的典型挑战”“AI 产品上线难点”);
- 第五步:结果整合→ 合并所有子问题的检索结果,生成逻辑连贯的最终答案
集成关键原则
- 优先级排序:指代消解(先明确实体)→ Step-Back(再获取框架)→ 问题分解(拆分子问题)→ 多查询重写(扩展视角);
- 成本控制:子问题数量≤5 个,每个子问题的查询变体≤3 个,避免过度增加检索成本;
- 动态开关:简单查询(如 “RAG 是什么”)仅启用多查询重写;复杂查询启用全流程;对话查询必启指代消解。
落地建议与工具选型
工具选型
- 基础框架:LangChain(内置所有查询重写检索器,支持快速集成);
- LLM 选择:简单消解 / 重写用 GPT-3.5-turbo,复杂分解 / Step-Back 用 GPT-4(确保逻辑准确性);
- 向量数据库:Chroma(轻量)、Milvus(大规模),支持多查询结果去重。
参数调优
- 多查询重写:
num_queries=3(平衡召回率与成本); - 问题分解:子问题数量≤5(避免过度拆解);
- Step-Back:抽象问题需与原始查询强相关(避免检索无关框架);
- 指代消解:对话记忆 k=5(保留足够上下文,不增加计算量)。
效果评估
- 核心指标:检索召回率(相关文档占比)、查询改写准确率(改写后是否符合原意);
- 人工评估:抽样 100 个查询,检查改写后检索结果的相关性与完整性。
总结
查询重写是 RAG 系统的 “前端优化引擎”—— 通过多视角扩展(多查询)、复杂拆解(问题分解)、框架回溯(Step-Back)、上下文关联(指代消解),将 “不精准” 的用户查询转化为 “高适配” 的检索输入,直接决定后续检索与生成的质量。落地时需根据查询类型(模糊 / 复杂 / 对话 / 专业)灵活组合策略,平衡效果与成本,避免 “一刀切”