前言
检索增强生成(Retrieval-Augmented Generation,RAG)是解决大语言模型(LLM)知识幻觉、时效性不足、私有领域知识缺失的核心技术方案。其核心逻辑为:通过外部知识库检索获取真实、精准的参考文本,再将检索结果与用户查询拼接后输入大模型,最终生成可靠答案。
检索环节作为 RAG 链路的入口核心,检索策略的选择直接决定知识召回的准确率、覆盖率与最终问答效果。在实际落地场景中,用户查询存在简单直白、抽象开放、多维度对比、复杂带前置条件等不同形态,单一检索模式无法适配全场景。
本文系统性梳理工业界主流的四大核心检索策略:直接检索、假设文档检索(HyDE)、子查询检索、回溯问题检索。从定义、适用场景、技术原理、优缺点、工程实践、代码示例多维度进行完整解析,同时区分各策略的选型标准,为 RAG 系统开发、调优与落地提供参考。
目录
- 核心概念与整体架构
- 策略一:直接检索(Direct Retrieval)
- 策略二:假设文档检索(HyDE)
- 策略三:子查询检索(子问题拆解检索)
- 策略四:回溯问题检索(问题改写检索)
- 四大策略对比与选型指南
- 全策略整合工程代码示例
- 总结与落地建议
1. 核心概念与整体架构
1.1 RAG 基础链路
标准 RAG 完整执行流程: 用户查询 → 检索策略分发 → 知识库检索(向量库/全文库) → 检索结果拼接Prompt → LLM生成答案
1.2 四大检索策略整体流程
用户原始Query
|
├─意图分类模块 → 判定检索策略
|
├─【直接检索】→ 原始Query向量化 → 知识库检索
├─【HyDE检索】→ LLM生成假设文档 → 假设文档向量化 → 知识库检索
├─【子查询检索】→ Query拆解为多个子问题 → 多子问题并行检索 → 结果合并
└─【回溯问题检索】→ LLM简化/改写原始Query → 新Query向量化 → 知识库检索
|
统一汇总检索上下文
|
拼接上下文+原始Query送入LLM
|
输出最终问答答案
1.3 前置依赖说明
本文代码基于 Python 实现,依赖组件:
- 大模型:通义千问 / 文心一言 / 本地开源 LLM(Qwen/Llama)
- 向量数据库:FAISS(轻量本地向量库,演示用)
- 依赖包:
langchain、faiss-cpu、numpy、langchain-openai
2. 策略一:直接检索(Direct Retrieval)
2.1 定义
不对用户原始查询做任何改写、扩充、拆解,直接使用原始 Query 完成向量化,再基于向量库 / 全文知识库执行检索,是最简单、最高效的检索模式。
2.2 核心适用场景
- 用户查询意图极度明确,无歧义;
- 查询句式简单、短句为主,结构单一;
- 目标信息在知识库中存在精准匹配的文本片段;
- 高频固定问答、知识库词条类查询。
2.3 典型示例
- 示例 1:AI 学科学费是多少?
- 示例 2:JAVA 课程大纲是什么?
- 示例 3:公司上下班时间?
2.4 技术特征
优点
- 无 LLM 二次调用,检索延迟最低、资源消耗最小;
- 逻辑简单,工程实现成本低;
- 召回结果精准,不会引入额外噪声。
缺点
- 对抽象、模糊、开放式查询适配性极差;
- 若原始 Query 语义简短、语义稀疏,易出现向量匹配失败、召回为空;
- 无法解决 "字面不匹配、语义相似" 的检索场景。
2.5 工程代码示例(基于 LangChain + FAISS)
python
from langchain.vectorstores import FAISS
from langchain.embeddings import OpenAIEmbeddings
from langchain.text_splitter import CharacterTextSplitter
# 1. 模拟知识库文本
knowledge_docs = [
"AI学科每年学费为8000元",
"JAVA课程分为基础语法、面向对象、框架三大模块",
"Python课程主要讲解数据类型、函数、爬虫、数据分析"
]
# 2. 初始化嵌入模型 & 文本切分
embeddings = OpenAIEmbeddings()
text_splitter = CharacterTextSplitter(chunk_size=100, chunk_overlap=10)
split_docs = text_splitter.create_documents(knowledge_docs)
# 3. 构建FAISS向量库
db = FAISS.from_documents(split_docs, embeddings)
# 4. 直接检索核心逻辑
def direct_retrieval(query: str, top_k: int = 2):
"""直接检索:使用原始查询检索知识库"""
retriever = db.as_retriever(search_kwargs={"k": top_k})
result_docs = retriever.get_relevant_documents(query)
# 提取检索内容
return [doc.page_content for doc in result_docs]
# 测试调用
if __name__ == "__main__":
user_query = "AI学科学费是多少?"
res = direct_retrieval(user_query)
print("直接检索结果:", res)
2.6 选型建议
优先使用直接检索:内部 FAQ、固定词条、信息查询类短问句。
3. 策略二:假设文档检索(HyDE, Hypothetical Document Embeddings)
3.1 定义
HyDE 是语义增强型检索策略 ,核心思路:不使用原始 Query 做向量检索,而是先调用 LLM基于问题生成一段假设性答案 / 假设文档,再将这份假设文档进行向量化并检索知识库。
本质:用「语义丰富的假设文本」替代「语义稀疏的原始问句」,提升向量匹配的语义覆盖率。
3.2 核心适用场景
- 抽象类、开放式、探索性查询;
- 原始 Query 语义简短、字面匹配困难;
- 观点类、应用类、总结类问题;
- 知识库文本偏长、语义宽泛的场景。
3.3 典型示例
- 查询:人工智能在教育领域的应用有哪些?
- 查询:如何改善睡眠质量?
- 查询:大数据的发展趋势是什么?
3.4 技术特征
优点
- 假设文档语义更饱满,大幅提升语义召回能力;
- 解决短 Query、模糊 Query 检索失效问题;
- 对开放式问答场景提升效果显著。
缺点
- 额外增加一轮 LLM 调用,检索耗时、成本上升;
- 若 LLM 生成的假设文档存在错误,会误导后续检索;
- 不适合精准词条类查询,易召回冗余内容。
3.5 标准提示词模板
假设你是一名专业解答者,请针对以下问题生成一段简短、通顺的假设答案:
问题: {query}
假设答案:
3.6 工程代码示例
python
from langchain.chat_models import ChatOpenAI
from langchain.prompts import PromptTemplate
# 复用上方已构建的FAISS向量库、知识库
# 1. 初始化大模型
llm = ChatOpenAI(temperature=0.1) # 低温度保证假设答案规整
# 2. HyDE提示词
hyde_prompt = PromptTemplate(
input_variables=["query"],
template="假设你是一名专业解答者,请针对以下问题生成一段简短、通顺的假设答案:\n问题: {query}\n假设答案:"
)
# 3. HyDE检索核心逻辑
def hyde_retrieval(query: str, top_k: int = 2):
# 步骤1:LLM生成假设文档
prompt = hyde_prompt.format(query=query)
hypothetical_doc = llm.predict(prompt)
print("LLM生成的假设文档:", hypothetical_doc)
# 步骤2:使用假设文档进行向量检索
retriever = db.as_retriever(search_kwargs={"k": top_k})
result_docs = retriever.get_relevant_documents(hypothetical_doc)
return [doc.page_content for doc in result_docs]
# 测试调用
if __name__ == "__main__":
user_query = "人工智能在教育领域的应用有哪些?"
res = hyde_retrieval(user_query)
print("HyDE检索结果:", res)
3.7 原理补充
用户问句偏向 "提问句式",向量语义特征弱;而 LLM 生成的假设答案偏向 "陈述句式",和知识库文本句式、语义风格高度一致,向量空间中相似度更高,因此召回效果更优。
4. 策略三:子查询检索(子问题拆解检索)
4.1 定义
针对多实体、多维度、复合型复杂查询,将原始大查询拆解为若干个独立、简单的子查询,对每个子查询分别执行检索,最后合并、去重所有子查询的检索结果,再送入 LLM 生成答案。
4.2 核心适用场景
- 包含对比、比较、优缺点分析的查询;
- 一个问题包含多个独立实体 / 多个查询维度;
- 复合型问题,单一检索无法覆盖全部信息。
4.3 典型示例
- 查询:比较 Milvus 和 Zilliz Cloud 的优缺点;
- 查询:分析 MySQL 和 PostgreSQL 在性能、生态、成本上的差异;
- 查询:分别介绍 Python 和 Java 的就业方向与学习难度。
4.4 技术特征
优点
- 拆分复杂问题,降低单条检索难度,提升各维度召回精度;
- 支持并行检索,可通过多线程 / 异步提升整体速度;
- 信息覆盖全面,避免复杂查询漏召回。
缺点
- 依赖 LLM 拆解子问题,增加调用开销;
- 子问题数量过多会导致检索次数暴增,耗时变长;
- 多结果合并需要额外做去重、排序逻辑。
4.5 标准提示词模板
将以下复杂查询拆解为多个简单子查询,每行仅输出一个子查询,不要额外解释:
查询: {query}
子查询:
4.6 工程代码示例
python
# 复用上方llm、向量库
sub_query_prompt = PromptTemplate(
input_variables=["query"],
template="将以下复杂查询拆解为多个简单子查询,每行仅输出一个子查询,不要额外解释:\n查询: {query}\n子查询:"
)
def split_sub_query(query: str) -> list:
"""拆解子查询,返回子问题列表"""
prompt = sub_query_prompt.format(query=query)
sub_res = llm.predict(prompt)
# 按换行分割子查询,并过滤空行
sub_queries = [q.strip() for q in sub_res.split("\n") if q.strip()]
return sub_queries
def sub_query_retrieval(query: str, top_k: int = 2):
"""子查询检索:拆解+分别检索+合并结果"""
sub_queries = split_sub_query(query)
all_results = []
# 逐个子查询检索
for sq in sub_queries:
retriever = db.as_retriever(search_kwargs={"k": top_k})
docs = retriever.get_relevant_documents(sq)
all_results.extend([doc.page_content for doc in docs])
# 简单去重
unique_results = list(set(all_results))
return sub_queries, unique_results
# 测试调用
if __name__ == "__main__":
user_query = "比较Python和Java的优缺点"
subs, res = sub_query_retrieval(user_query)
print("拆解后的子查询:", subs)
print("子查询合并检索结果:", res)
5. 策略四:回溯问题检索(问题改写 / 重构检索)
5.1 定义
也叫问题重写检索 。针对包含前置条件、口语化、嵌套逻辑、推理链路的复杂查询,由 LLM 将原始口语化 / 复杂问题简化、重构为标准、基础的核心问题,再使用改写后的新问题执行检索。
核心:剥离冗余语境、前置条件,提炼用户真实核心意图。
5.2 核心适用场景
- 带有前置背景、场景描述的长问句;
- 口语化严重、表述啰嗦的用户查询;
- "是否可以、能不能、我该怎么做" 类意图隐含的问题;
- 嵌套逻辑、需要前置推理的技术类问题。
5.3 典型示例
- 原始查询:我有一个包含 100 亿条记录的数据集,想把它存储到 Milvus 中进行查询。可以吗?
- 改写后核心问题:Milvus 支持多大规模的数据集存储与检索?
5.4 技术特征
优点
- 过滤无效口语、场景描述,精准命中核心检索意图;
- 把非标口语问题转为标准检索问句,适配知识库;
- 大幅降低长问句带来的检索噪声。
缺点
- 依赖 LLM 改写,增加调用成本;
- 若改写偏差,会偏离用户真实意图;
- 不用于简单短句查询,画蛇添足。
5.5 标准提示词模板
将以下复杂、口语化的查询简化为一个简洁标准的核心问题,只输出简化后的问题:
查询: {query}
简化问题:
5.6 工程代码示例
python
# 复用llm、向量库
rewrite_prompt = PromptTemplate(
input_variables=["query"],
template="将以下复杂、口语化的查询简化为一个简洁标准的核心问题,只输出简化后的问题:\n查询: {query}\n简化问题:"
)
def rewrite_query(query: str) -> str:
"""改写/简化原始查询"""
prompt = rewrite_prompt.format(query=query)
new_query = llm.predict(prompt).strip()
return new_query
def rewrite_retrieval(query: str, top_k: int = 2):
"""回溯问题检索:改写问题 + 检索"""
new_q = rewrite_query(query)
print("改写后的核心问题:", new_q)
retriever = db.as_retriever(search_kwargs={"k": top_k})
docs = retriever.get_relevant_documents(new_q)
return [doc.page_content for doc in docs]
# 测试调用
if __name__ == "__main__":
user_query = "我有海量数据,想存进向量库查询,Milvus能支持吗?"
res = rewrite_retrieval(user_query)
print("回溯检索结果:", res)
6. 四大策略对比与选型指南
6.1 核心参数对比表
表格
| 检索策略 | LLM 调用次数 | 检索速度 | 语义覆盖能力 | 最佳适用场景 |
|---|---|---|---|---|
| 直接检索 | 0 次 | 最快 | 弱(精准匹配) | 短句、意图明确、词条查询 |
| HyDE 假设文档检索 | 1 次 | 中等 | 强(语义增强) | 抽象、开放式、探索性问题 |
| 子查询检索 | 1 次(拆解)+ N 次检索 | 较慢 | 全维度覆盖 | 对比、多实体、复合型问题 |
| 回溯问题检索 | 1 次(改写) | 中等 | 中等(提纯意图) | 口语化、带场景的复杂长问句 |
6.2 快速选型规则
- 短句、查固定信息 → 直接检索
- 抽象提问、问应用 / 趋势 / 方法 → HyDE
- 出现 "对比、比较、优缺点" → 子查询检索
- 长句、带个人场景、口语化提问 → 回溯问题检索
6.3 组合进阶方案(工业落地)
高复杂度 RAG 系统可叠加使用:意图分类 → 策略分发 → 混合检索,即先通过分类模型判断查询类型,再路由至对应检索策略。
7. 全策略整合代码(策略自动分发系统)
整合意图分类 + 四大检索策略,实现一条入口自动匹配最优策略,可直接用于 RAG 服务封装:
python
from langchain.chat_models import ChatOpenAI
from langchain.prompts import PromptTemplate
from langchain.vectorstores import FAISS
from langchain.embeddings import OpenAIEmbeddings
from langchain.text_splitter import CharacterTextSplitter
# ===================== 1. 全局初始化 =====================
# 知识库
knowledge_docs = [
"AI学科每年学费为8000元",
"JAVA课程分为基础语法、面向对象、框架三大模块",
"人工智能可应用在智慧课堂、作业批改、学情分析等教育场景",
"Milvus 向量数据库支持千亿级数据存储与高速检索",
"Python入门简单,生态丰富;Java性能强,企业级应用广泛"
]
embeddings = OpenAIEmbeddings()
text_splitter = CharacterTextSplitter(chunk_size=100, chunk_overlap=10)
split_docs = text_splitter.create_documents(knowledge_docs)
db = FAISS.from_documents(split_docs, embeddings)
llm = ChatOpenAI(temperature=0.1)
# 策略分类提示词
classify_prompt = PromptTemplate(
input_variables=["query"],
template="""分析用户查询,从【直接检索、假设文档检索、子查询检索、回溯问题检索】中选择唯一最合适的策略,仅返回策略名称:
查询:{query}
策略:"""
)
# ===================== 2. 四大检索函数(复用上方逻辑) =====================
def direct_retrieval(query: str, top_k=2):
retriever = db.as_retriever(search_kwargs={"k": top_k})
return [d.page_content for d in retriever.get_relevant_documents(query)]
def hyde_retrieval(query: str, top_k=2):
hyde_tpl = "生成简短假设答案:\n问题:{query}\n假设答案:"
hyp_doc = llm.predict(hyde_tpl.format(query=query))
retriever = db.as_retriever(search_kwargs={"k": top_k})
return [d.page_content for d in retriever.get_relevant_documents(hyp_doc)]
def sub_query_retrieval(query: str, top_k=2):
split_tpl = "拆解为子查询,每行一个:\n查询:{query}\n子查询:"
subs = [q.strip() for q in llm.predict(split_tpl.format(query=query)).split("\n") if q.strip()]
res = []
for sq in subs:
docs = db.as_retriever(k=top_k).get_relevant_documents(sq)
res.extend([d.page_content for d in docs])
return list(set(res))
def rewrite_retrieval(query: str, top_k=2):
rewrite_tpl = "简化为标准问题:\n查询:{query}\n简化问题:"
new_q = llm.predict(rewrite_tpl.format(query=query)).strip()
retriever = db.as_retriever(search_kwargs={"k": top_k})
return [d.page_content for d in retriever.get_relevant_documents(new_q)]
# ===================== 3. 自动策略分发入口 =====================
def rag_router(query: str):
# 1. 分类策略
strategy = llm.predict(classify_prompt.format(query=query)).strip()
print(f"自动判定检索策略:{strategy}")
# 2. 路由执行
if strategy == "直接检索":
return direct_retrieval(query)
elif strategy == "假设文档检索":
return hyde_retrieval(query)
elif strategy == "子查询检索":
return sub_query_retrieval(query)
elif strategy == "回溯问题检索":
return rewrite_retrieval(query)
else:
return direct_retrieval(query)
# ===================== 4. 整体测试 =====================
if __name__ == "__main__":
test_queries = [
"AI学科学费是多少?",
"人工智能在教育领域有什么应用?",
"比较Python和Java的优缺点",
"我有海量数据想存入Milvus,是否可行?"
]
for q in test_queries:
print(f"\n【用户查询】{q}")
result = rag_router(q)
print(f"【检索结果】{result}")
8. 总结与落地建议
8.1 核心总结
- 四大检索策略本质是对原始查询做不同形式的增强 / 改造,适配不同用户问句形态;
- 直接检索追求效率与精准 ,HyDE 追求语义泛化 ,子查询解决复杂对比 ,回溯检索解决口语化长问句;
- 单一策略无法覆盖全业务,工业级 RAG 系统必须搭配意图分类 + 策略路由架构。
8.2 生产环境落地建议
- 性能优先场景(客服 FAQ、高频查询):主力使用直接检索,关闭冗余 LLM 调用;
- 问答 / 知识库场景(文档问答、科普问答):HyDE 作为默认增强策略;
- 对比分析类场景(选型、测评):固定使用子查询检索;
- 用户自由输入场景(C 端聊天问答):启用自动策略路由,全策略兼容;
- 风险规避:对 LLM 生成的假设文档、改写问题、子查询增加简单校验,过滤明显错误内容。
8.3 拓展方向
- 多策略混合检索:同时执行 2 种策略,融合检索结果;
- 轻量化分类器:用小模型替代大模型做策略分类,进一步降本提速;
- 动态 Top-K:根据查询复杂度动态调整检索返回条数。