说明:下文整理 RAG 查询转换 / 多路检索 的六种常见策略与工业级分层融合思路,附 LangChain 风格 Demo。文末 §四 MemoryOS 对照 说明在 MemoryOS 仓库里哪些适合现在做、哪些应放进 EP04-03。
检索是 RAG 系统的核心瓶颈。用户原始问句普遍存在碎片化、语义不对称、多维度混杂、关键词缺失等问题,直接单路检索极易出现召回不全、精准度不足、答案片面等缺陷。行业内形成了一套标准化 查询转换优化体系,包含多查询重写、RRF 多查询融合、Step-Back 回退检索、HyDE 对称检索、Map-Reduce 问题分解、Ensemble 多路混合检索六大主流方案。
本文逐一拆解每种策略的实现原理、使用方法 Demo、优缺点、适用场景,最后给出可直接落地的多策略分层融合生产级完整链路。
一、六大检索优化策略分项解析(含可运行使用 Demo)
1. 多查询重写(基础查询改写)
1.1 实现方式
- 输入原始用户Query,调用轻量LLM生成多条同义、多角度改写子查询(Q1/Q2/Q3);
- 每条改写后的子查询独立向量化,分别检索向量数据库,得到多组文档列表;
- 简单合并:直接拼接所有检索结果,仅做文档去重,无统一打分重排逻辑;
- LangChain对应组件:
MultiQueryRetriever。
1.2 使用方法Demo
① 子查询生成Prompt模板
1
2
3
4
5
| 请针对用户的原始问题,从3个不同的语义角度生成3条独立的检索子查询,要求子查询覆盖问题的不同侧面,便于更全面地召回知识库相关文档。
仅输出3条查询,每行一条,不要序号和额外解释。
原始问题:{user_question}
子查询列表:
|
② 完整代码实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
| from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI
from langchain_community.vectorstores import FAISS
from langchain_openai import OpenAIEmbeddings
# 初始化LLM与向量检索器
llm = ChatOpenAI(model="gpt-3.5-turbo", temperature=0)
embeddings = OpenAIEmbeddings(model="text-embedding-3-small")
vector_db = FAISS.load_local("./knowledge_base", embeddings, allow_dangerous_deserialization=True)
base_retriever = vector_db.as_retriever(search_kwargs={"k": 4})
# 多查询生成提示词
multi_query_prompt = ChatPromptTemplate.from_template("""
请针对用户的原始问题,从3个不同的语义角度生成3条独立的检索子查询,要求子查询覆盖问题的不同侧面,便于更全面地召回知识库相关文档。
仅输出3条查询,每行一条,不要序号和额外解释。
原始问题:{user_question}
子查询列表:
""")
def generate_multi_queries(query: str) -> list[str]:
"""生成多路子查询"""
chain = multi_query_prompt | llm
result = chain.invoke({"user_question": query}).content
return [q.strip() for q in result.split("\n") if q.strip()]
def multi_query_retrieve(query: str) -> list:
"""多查询检索 + 简单去重合并"""
queries = generate_multi_queries(query)
all_docs = []
for q in queries:
docs = base_retriever.get_relevant_documents(q)
all_docs.extend(docs)
# 按文档内容去重
seen = set()
unique_docs = []
for doc in all_docs:
if doc.page_content not in seen:
seen.add(doc.page_content)
unique_docs.append(doc)
return unique_docs
|
1.3 优点
- 实现成本极低,仅需一次轻量LLM调用做改写,不涉及完整问答生成,token消耗小;
- 子查询可异步并行检索,向量库IO耗时可重叠,接口响应性能优秀;
- 多角度问句覆盖原始问题不同语义,拓宽基础召回范围。
1.4 缺点
- 合并逻辑简陋,仅简单去重,无文档权重计算;
- 高频、高相关文档不会加权靠前,排名靠后的高价值文档极易被截断剔除;
- 无区分检索来源、匹配强度的能力,语义匹配与关键词匹配文档混杂无序。
1.5 适用场景
轻量化Demo、极简FAQ问答系统、低并发内部知识库,对检索排序精度无强要求,仅需提升基础召回量。
2. 多查询RRF融合(多查询重写进阶版)
2.1 实现方式
在多查询重写基础上引入RRF倒数排名融合算法,核心公式:$score += \frac{1}{rank+k}$(k默认取60):
- LLM生成多条改写子查询,多路独立检索;
- 对所有返回文档统一计算融合得分,重复出现在多路结果的文档会累加分数;
- 按总分降序重排文档,过滤低分重复内容后送入LLM;
- LangChain原生封装:
EnsembleRetriever内置RRF融合,支持多路检索自定义权重。
2.2 使用方法Demo
① 手动实现RRF融合函数(灵活可控)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
| def rrf_fuse(doc_lists: list[list], k: int = 60, weights: list[float] = None) -> list:
"""
RRF倒数排名融合
:param doc_lists: 多路检索结果列表,每个元素是一路文档
:param k: RRF常数,默认60
:param weights: 每路检索的权重,默认全1
:return: 融合排序后的文档列表
"""
if weights is None:
weights = [1.0] * len(doc_lists)
fused_scores = {}
doc_map = {}
# enumerate遍历每一路检索,匹配对应权重
for i, docs in enumerate(doc_lists):
weight = weights[i]
for rank, doc in enumerate(docs):
doc_key = doc.page_content
doc_map[doc_key] = doc
# 计算单路RRF得分并加权
score = (1 / (rank + k)) * weight
fused_scores[doc_key] = fused_scores.get(doc_key, 0) + score
# 按总分降序排序
sorted_items = sorted(fused_scores.items(), key=lambda x: x[1], reverse=True)
return [doc_map[key] for key, _ in sorted_items]
|
② LangChain EnsembleRetriever开箱即用(BM25+向量混合检索)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
| from langchain_community.retrievers import BM25Retriever
from langchain_community.vectorstores import FAISS
from langchain_openai import OpenAIEmbeddings
from langchain.retrievers import EnsembleRetriever
# 准备知识库文档列表
docs = [...] # 你的Document对象列表
# 1. 初始化BM25关键词检索器
bm25_retriever = BM25Retriever.from_documents(docs)
bm25_retriever.k = 4
# 2. 初始化FAISS向量检索器
embeddings = OpenAIEmbeddings(model="text-embedding-3-small")
faiss_db = FAISS.from_documents(docs, embeddings)
faiss_retriever = faiss_db.as_retriever(search_kwargs={"k": 4})
# 3. 集成检索器(底层内置RRF融合,支持自定义权重)
ensemble_retriever = EnsembleRetriever(
retrievers=[bm25_retriever, faiss_retriever],
weights=[0.5, 0.5]
)
# 直接调用检索
result_docs = ensemble_retriever.get_relevant_documents("耳机进水保修多久?")
|
2.3 优点
- 解决多查询重写无加权排序的缺陷,高相关、多路命中文档自动置顶;
- 完全规避向量相似度分值标准不统一问题,仅依靠检索排名公平打分;
- 支持多路检索器加权(关键词BM25+向量FAISS),兼容混合检索链路;
- 容错性强,单路子查询检索质量差不会完全污染整体结果。
2.4 缺点
- 相比简单多查询重写增加内存计算开销,需拉取全部候选文档做打分;
- LangChain内置RRF的k值固定,无法自由调参,精细化排序需求需手动实现;
- 多路并行检索会增加向量数据库请求次数,高并发场景需做缓存限流。
2.5 适用场景
绝大多数企业标准RAG系统、客服知识库、产品文档问答,是检索优化的基础标配,可单独使用,也可作为所有其他策略的后置融合底座。
3. Step-Back 回答回退检索策略
3.1 实现方式
核心解决「用户细碎局部问句与宏观知识库语义错位」问题:
- FewShot提示词引导LLM,将细粒度原始问句抽象、回退为宏观通用上层问题;
- 双路检索:原始问句检索(保精准关键词匹配)+ Step-Back抽象问句检索(补全全局背景知识);
- 两路结果送入RRF融合重排,合并细节与宏观维度文档。
3.2 使用方法Demo
① FewShot提示词模板(严格对齐示例规范)
1
2
3
4
5
6
7
8
9
10
| 示例1:
用户问题:耳机进水保修多久?
回退后问题:电子产品售后保修政策
示例2:
用户问题:月度报表里研发费用抵扣标准?
回退后问题:企业研发费用财务抵扣管理规定
现在请按上面例子,改写问题:{user_question}
仅输出改写后的回退问题,不要额外解释、不要序号。
|
② 完整双路检索+RRF融合代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
| from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(model="gpt-3.5-turbo", temperature=0)
# Step-Back 提示词模板
step_back_prompt = ChatPromptTemplate.from_template("""
示例1:
用户问题:耳机进水保修多久?
回退后问题:电子产品售后保修政策
示例2:
用户问题:月度报表里研发费用抵扣标准?
回退后问题:企业研发费用财务抵扣管理规定
现在请按上面例子,改写问题:{user_question}
仅输出改写后的回退问题,不要额外解释、不要序号。
""")
def generate_step_back_query(query: str) -> str:
"""生成回退宏观问题"""
chain = step_back_prompt | llm
return chain.invoke({"user_question": query}).content
def step_back_retrieve(query: str, retriever) -> list:
"""Step-Back双路检索 + RRF融合"""
# 1. 生成两路查询
original_query = query
step_back_query = generate_step_back_query(query)
# 2. 双路独立检索
original_docs = retriever.get_relevant_documents(original_query)
step_back_docs = retriever.get_relevant_documents(step_back_query)
# 3. RRF融合排序
fused_docs = rrf_fuse([original_docs, step_back_docs], k=60, weights=[0.6, 0.4])
return fused_docs
# 使用示例
final_docs = step_back_retrieve("耳机进水保修多久?", ensemble_retriever)
|
3.3 优点
- 完美弥补短句、细节提问召回不足的痛点,补充全局制度、流程类背景文档;
- FewShot模板稳定可控,可精准约束LLM抽象逻辑,幻觉风险低;
- 可无缝嵌套进多查询、问题分解流程,每个子查询均可独立执行Step-Back改写。
3.4 缺点
- 每条Query额外增加一次LLM调用,token成本与响应延迟上升;
- 若用户原始问题本身已是宏观问题,Step-Back会生成高度重复问句,产生冗余检索;
- 单纯Step-Back仅两路检索,多维度复杂问题覆盖能力弱于问题分解。
3.5 适用场景
产品售后、法律条款、设备运维等专业知识库;用户提问多为细分型号、局部流程、单点细节的业务场景。
4. HyDE假设文档对称检索策略
4.1 实现方式
解决传统Query-Doc非对称检索语义鸿沟,将短句问句转化为长文文档再检索:
- LLM基于原始问句生成一段完整陈述式「假设回答伪文档」;
- 放弃原始短句向量,使用伪文档做向量检索(Doc-Doc对称匹配,与知识库Chunk文本形态对齐);
- 工业混合策略:原始问句检索 + HyDE伪文档检索两路并行,RRF融合输出。
4.2 使用方法Demo
① HyDE生成Prompt模板
1
2
3
4
5
| 请基于用户的问题,生成一段完整、专业的假设性回答文档,要求内容符合企业知识库的陈述风格,包含对应领域专业术语,逻辑通顺,长度控制在150-200字。
仅输出假设文档内容,不要额外解释、不要标题。
用户问题:{user_question}
假设回答文档:
|
② 完整混合检索代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| hyde_prompt = ChatPromptTemplate.from_template("""
请基于用户的问题,生成一段完整、专业的假设性回答文档,要求内容符合企业知识库的陈述风格,包含对应领域专业术语,逻辑通顺,长度控制在150-200字。
仅输出假设文档内容,不要额外解释、不要标题。
用户问题:{user_question}
假设回答文档:
""")
def generate_hyde_doc(query: str) -> str:
"""生成假设伪文档"""
chain = hyde_prompt | llm
return chain.invoke({"user_question": query}).content
def hyde_retrieve(query: str, retriever) -> list:
"""HyDE混合检索 + RRF融合"""
# 原始问句检索
original_docs = retriever.get_relevant_documents(query)
# HyDE伪文档检索
hyde_doc = generate_hyde_doc(query)
hyde_docs = retriever.get_relevant_documents(hyde_doc)
# 两路RRF融合,伪文档权重更高
fused_docs = rrf_fuse([original_docs, hyde_docs], k=60, weights=[0.4, 0.6])
return fused_docs
|
4.3 优点
- 自动补全用户缺失的专业术语、领域词汇,弥合口语化提问与专业文档的词汇断层;
- 自动过滤问句内闲聊、情绪等无关噪声,检索向量纯净度更高;
- 向量语义分布与知识库完全对齐,长尾模糊问题召回率大幅提升。
4.4 缺点
- 必须新增一轮LLM生成,高并发实时问答场景会显著拉高延迟与API成本;
- LLM生成存在轻微幻觉,极端场景下伪文档语义跑偏,会引入无关检索结果;
- 极简关键词FAQ场景收益极低,属于性能冗余。
4.5 适用场景
医疗、财务、工业技术手册等强专业领域;用户提问口语化、碎片化、缺少专业术语的复杂问答业务。
5. Map-Reduce问题分解策略(并行/迭代双模式)
5.1 实现方式
针对多维度、超长复杂综合问题,拆分后分治处理:
- Map阶段:LLM将原始大问题拆解为多个互相独立的子问题;
- 执行阶段:
- 并行模式:所有子问题同步执行检索、生成子答案,适合无依赖的多维度问题;
- 迭代模式:串行分步解答,前序子答案作为上下文带入下一轮推理,适合强逻辑依赖问题;
- Reduce阶段:将全部子答案、融合文档汇总,LLM整合输出完整最终回答。
5.2 使用方法Demo
① Map拆分子问题Prompt模板
1
2
3
4
5
| 请将用户的复杂综合问题拆解为3-4个互相独立的子问题,每个子问题对应一个独立的知识维度,确保所有子问题覆盖原始问题的全部核心需求。
仅输出子问题列表,每行一个,不要序号和额外解释。
原始问题:{user_question}
子问题列表:
|
② Reduce汇总Prompt模板
1
2
3
4
5
6
7
8
| 请结合以下所有子问题的检索文档与子答案,针对用户的原始问题生成一份完整、逻辑连贯的最终回答。
要求:1. 覆盖所有子维度信息,结构清晰;2. 所有结论均有文档依据;3. 语言专业严谨。
原始问题:{original_question}
子问题与对应参考资料:
{sub_results}
最终回答:
|
③ 完整Map-Reduce链路代码(嵌套Step-Back)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
| map_prompt = ChatPromptTemplate.from_template("""
请将用户的复杂综合问题拆解为3-4个互相独立的子问题,每个子问题对应一个独立的知识维度,确保所有子问题覆盖原始问题的全部核心需求。
仅输出子问题列表,每行一个,不要序号和额外解释。
原始问题:{user_question}
子问题列表:
""")
reduce_prompt = ChatPromptTemplate.from_template("""
请结合以下所有子问题的检索文档与子答案,针对用户的原始问题生成一份完整、逻辑连贯的最终回答。
要求:1. 覆盖所有子维度信息,结构清晰;2. 所有结论均有文档依据;3. 语言专业严谨。
原始问题:{original_question}
子问题与对应参考资料:
{sub_results}
最终回答:
""")
def map_reduce_rag(query: str, retriever) -> str:
# 1. Map阶段:拆分子问题
map_chain = map_prompt | llm
sub_queries = [q.strip() for q in map_chain.invoke({"user_question": query}).content.split("\n") if q.strip()]
# 2. 每个子问题独立执行Step-Back检索 + 生成子答案(生产环境可异步并发)
sub_results = []
all_fused_docs = []
for sq in sub_queries:
# 子问题嵌套Step-Back双路检索
docs = step_back_retrieve(sq, retriever)
all_fused_docs.extend(docs)
# 生成子答案
context = "\n".join([f"参考文档{i+1}:{d.page_content}" for i, d in enumerate(docs[:3])])
sub_answer = llm.invoke(f"参考资料:\n{context}\n请回答问题:{sq}").content
sub_results.append(f"【子问题】{sq}\n【子答案】{sub_answer}")
# 3. 全局文档去重重排
final_docs = rrf_fuse([all_fused_docs], k=60)
# 4. Reduce阶段:汇总生成最终答案
reduce_chain = reduce_prompt | llm
final_answer = reduce_chain.invoke({
"original_question": query,
"sub_results": "\n---\n".join(sub_results)
}).content
return final_answer
|
5.3 优点
- 拆分超大复杂问题,规避单轮上下文token超限报错;
- 并行模式可异步并发执行,多维度信息分离检索,互不干扰;
- 适配多层逻辑推理、多维度对比、跨模块综合咨询类需求。
5.4 缺点
- 链路包含多次LLM调用,整体延迟、token开销是所有策略中最高;
- 迭代串行模式存在误差传递,前置子答案错误会污染全部后续推理;
- 简单单点FAQ使用会造成严重性能浪费。
5.5 适用场景
招投标方案对比、财务综合咨询、多层逻辑推理、长文档综合解读等多维度复合问题场景;仅复杂咨询流量开启,简单问答走轻量化链路。
6. Ensemble多路混合检索(BM25关键词+向量检索融合)
6.1 实现方式
同时兼顾关键词精准匹配与深层语义相似,解决纯向量检索漏关键词、纯关键词检索无语义理解的短板:
- 同时初始化BM25关键词检索器与向量检索器;
- 两路独立检索,通过RRF算法加权融合排序;
- 是所有上层查询转换策略的底层检索基座。
6.2 使用方法Demo
同本文2.2节EnsembleRetriever代码,是所有检索策略的底层基础,可与任意查询改写策略嵌套组合。
6.3 优点
- 同时覆盖精确关键词命中与深层语义相似,单一检索模式的短板互补;
- LangChain开箱即用,一行代码实现混合检索,无需手动编写RRF逻辑;
- 权重可灵活调整,专业名词多的场景调高BM25权重,语义模糊场景调高向量权重。
6.4 缺点
- 本地完成RRF打分,分布式海量向量库场景需拉取全部候选文档到内存,内存开销大;
- 内置RRF参数k固定,无法自定义调优;
- 多检索器并行会增加向量库、关键词索引双重IO压力。
6.5 适用场景
全行业通用标配,所有RAG系统底层检索层,搭配上层查询转换策略使用。
二、工业级多策略融合完整生产链路落地
2.1 全链路分层架构流程图
生产环境不会单一使用某一种优化,而是通过前置路由动态分流,兼顾召回精度、响应速度与成本控制,完整链路如下:
1
2
3
4
5
6
7
8
9
10
11
| 用户输入Query
↓
【前置智能路由层】规则+LLM分类,判定问题复杂度等级
├─ 等级1:极简单点FAQ → 直接Ensemble混合检索 → LLM生成答案 → 返回
├─ 等级2:细节专业问题 → Step-Back+HyDE双改写 → 多路Ensemble检索 → RRF融合 → LLM生成答案 → 返回
└─ 等级3:复杂综合问题 → 开启Map-Reduce全链路
↓ Map阶段:LLM拆分为多个独立子问题Q1/Q2/Q3
├─ 每个子问题单独执行 Step-Back + HyDE 改写:原问句 + 回退宏观问句 + 伪文档三检索
├─ 每路子问题产出3路文档列表,每路均为BM25+向量混合检索结果
↓ 全部多路文档汇总 → RRF统一融合重排 → 去重 → MMR多样性过滤
↓ Reduce阶段:把融合后的全部上下文交给LLM合并输出最终答案 → 返回
|
2.2 核心模块设计说明
① 前置路由层(性能节流核心)
- 规则初筛:通过问句长度、关键词数量、是否有并列连词(和/同时/分别/对比)快速判定复杂度;
- LLM精分:模糊边界问题调用LLM做三分类,确保分流准确;
- 降级兜底:LLM调用异常时默认走等级2链路,保障可用性。
② 缓存层(成本优化核心)
- Embedding缓存:使用
CacheBackedEmbeddings+Redis,重复文本不复刻向量化,节省Embedding API成本; - 高频Query检索缓存:Top N高频问题直接缓存检索结果与最终答案,毫秒级响应;
- TTL自动过期:冷数据自动淘汰,控制内存/磁盘占用。
③ 后处理过滤层
- 去重:基于文档内容哈希去重,避免重复Chunk占用上下文;
- MMR多样性重排:平衡相关性与多样性,避免检索结果高度同质化;
- Token截断:按LLM上下文窗口限制截断文档,控制输入token总量。
2.3 生产落地最佳实践
- 分层开启,避免全量全开:80%简单流量走基础链路,仅20%复杂流量执行全链路优化,成本与收益平衡最优;
- 异步并发执行:子问题改写、多路检索全部异步并发,将多轮LLM调用的延迟从线性叠加降低为最长单轮耗时;
- 可观测性埋点:每一步记录召回量、命中率、token消耗、耗时,便于持续调优权重与策略;
- 灰度迭代:先上线基础混合检索,再逐步叠加Step-Back、Map-Reduce,每一步验证召回率与答案质量提升。
2.4 不同业务场景选型推荐
三、总结
- 单一优化策略只能解决检索某一类短板,存在明确边界缺陷,生产环境必须分层融合使用;
- RRF 融合、Ensemble 关键词+向量混合检索是所有链路的底层基础,属于必选组件;
- Step-Back、HyDE 负责拓宽语义召回,Map-Reduce 负责拆解复杂多维度问题,三者按需路由触发,平衡精度与成本;
- 前置路由判断是工程落地的核心设计,通过区分 Query 复杂度动态切换链路,避免无差别全量执行优化造成资源浪费。