检索增强生成 (RAG) 四大检索策略详解

前言

检索增强生成(Retrieval-Augmented Generation,RAG)是解决大语言模型(LLM)知识幻觉、时效性不足、私有领域知识缺失的核心技术方案。其核心逻辑为:通过外部知识库检索获取真实、精准的参考文本,再将检索结果与用户查询拼接后输入大模型,最终生成可靠答案。

检索环节作为 RAG 链路的入口核心,检索策略的选择直接决定知识召回的准确率、覆盖率与最终问答效果。在实际落地场景中,用户查询存在简单直白、抽象开放、多维度对比、复杂带前置条件等不同形态,单一检索模式无法适配全场景。

本文系统性梳理工业界主流的四大核心检索策略:直接检索、假设文档检索(HyDE)、子查询检索、回溯问题检索。从定义、适用场景、技术原理、优缺点、工程实践、代码示例多维度进行完整解析,同时区分各策略的选型标准,为 RAG 系统开发、调优与落地提供参考。


目录

  1. 核心概念与整体架构
  2. 策略一:直接检索(Direct Retrieval)
  3. 策略二:假设文档检索(HyDE)
  4. 策略三:子查询检索(子问题拆解检索)
  5. 策略四:回溯问题检索(问题改写检索)
  6. 四大策略对比与选型指南
  7. 全策略整合工程代码示例
  8. 总结与落地建议

1. 核心概念与整体架构

1.1 RAG 基础链路

标准 RAG 完整执行流程: 用户查询检索策略分发知识库检索(向量库/全文库)检索结果拼接PromptLLM生成答案

1.2 四大检索策略整体流程

复制代码
用户原始Query
    |
    ├─意图分类模块 → 判定检索策略
    |
    ├─【直接检索】→ 原始Query向量化 → 知识库检索
    ├─【HyDE检索】→ LLM生成假设文档 → 假设文档向量化 → 知识库检索
    ├─【子查询检索】→ Query拆解为多个子问题 → 多子问题并行检索 → 结果合并
    └─【回溯问题检索】→ LLM简化/改写原始Query → 新Query向量化 → 知识库检索
                                    |
                        统一汇总检索上下文
                                    |
                        拼接上下文+原始Query送入LLM
                                    |
                            输出最终问答答案

1.3 前置依赖说明

本文代码基于 Python 实现,依赖组件:

  • 大模型:通义千问 / 文心一言 / 本地开源 LLM(Qwen/Llama)
  • 向量数据库:FAISS(轻量本地向量库,演示用)
  • 依赖包:langchainfaiss-cpunumpylangchain-openai

2. 策略一:直接检索(Direct Retrieval)

2.1 定义

不对用户原始查询做任何改写、扩充、拆解,直接使用原始 Query 完成向量化,再基于向量库 / 全文知识库执行检索,是最简单、最高效的检索模式。

2.2 核心适用场景

  1. 用户查询意图极度明确,无歧义;
  2. 查询句式简单、短句为主,结构单一;
  3. 目标信息在知识库中存在精准匹配的文本片段
  4. 高频固定问答、知识库词条类查询。

2.3 典型示例

  • 示例 1:AI 学科学费是多少?
  • 示例 2:JAVA 课程大纲是什么?
  • 示例 3:公司上下班时间?

2.4 技术特征

优点
  1. 无 LLM 二次调用,检索延迟最低、资源消耗最小;
  2. 逻辑简单,工程实现成本低;
  3. 召回结果精准,不会引入额外噪声。
缺点
  1. 抽象、模糊、开放式查询适配性极差;
  2. 若原始 Query 语义简短、语义稀疏,易出现向量匹配失败、召回为空;
  3. 无法解决 "字面不匹配、语义相似" 的检索场景。

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 核心适用场景

  1. 抽象类、开放式、探索性查询;
  2. 原始 Query 语义简短、字面匹配困难;
  3. 观点类、应用类、总结类问题;
  4. 知识库文本偏长、语义宽泛的场景。

3.3 典型示例

  • 查询:人工智能在教育领域的应用有哪些?
  • 查询:如何改善睡眠质量?
  • 查询:大数据的发展趋势是什么?

3.4 技术特征

优点
  1. 假设文档语义更饱满,大幅提升语义召回能力
  2. 解决短 Query、模糊 Query 检索失效问题;
  3. 对开放式问答场景提升效果显著。
缺点
  1. 额外增加一轮 LLM 调用,检索耗时、成本上升
  2. 若 LLM 生成的假设文档存在错误,会误导后续检索;
  3. 不适合精准词条类查询,易召回冗余内容。

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 核心适用场景

  1. 包含对比、比较、优缺点分析的查询;
  2. 一个问题包含多个独立实体 / 多个查询维度;
  3. 复合型问题,单一检索无法覆盖全部信息。

4.3 典型示例

  • 查询:比较 Milvus 和 Zilliz Cloud 的优缺点;
  • 查询:分析 MySQL 和 PostgreSQL 在性能、生态、成本上的差异;
  • 查询:分别介绍 Python 和 Java 的就业方向与学习难度。

4.4 技术特征

优点
  1. 拆分复杂问题,降低单条检索难度,提升各维度召回精度;
  2. 支持并行检索,可通过多线程 / 异步提升整体速度;
  3. 信息覆盖全面,避免复杂查询漏召回。
缺点
  1. 依赖 LLM 拆解子问题,增加调用开销;
  2. 子问题数量过多会导致检索次数暴增,耗时变长;
  3. 多结果合并需要额外做去重、排序逻辑。

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 核心适用场景

  1. 带有前置背景、场景描述的长问句;
  2. 口语化严重、表述啰嗦的用户查询;
  3. "是否可以、能不能、我该怎么做" 类意图隐含的问题;
  4. 嵌套逻辑、需要前置推理的技术类问题。

5.3 典型示例

  • 原始查询:我有一个包含 100 亿条记录的数据集,想把它存储到 Milvus 中进行查询。可以吗?
  • 改写后核心问题:Milvus 支持多大规模的数据集存储与检索?

5.4 技术特征

优点
  1. 过滤无效口语、场景描述,精准命中核心检索意图;
  2. 把非标口语问题转为标准检索问句,适配知识库;
  3. 大幅降低长问句带来的检索噪声。
缺点
  1. 依赖 LLM 改写,增加调用成本;
  2. 若改写偏差,会偏离用户真实意图;
  3. 不用于简单短句查询,画蛇添足。

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 快速选型规则

  1. 短句、查固定信息 → 直接检索
  2. 抽象提问、问应用 / 趋势 / 方法 → HyDE
  3. 出现 "对比、比较、优缺点" → 子查询检索
  4. 长句、带个人场景、口语化提问 → 回溯问题检索

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 核心总结

  1. 四大检索策略本质是对原始查询做不同形式的增强 / 改造,适配不同用户问句形态;
  2. 直接检索追求效率与精准 ,HyDE 追求语义泛化 ,子查询解决复杂对比 ,回溯检索解决口语化长问句
  3. 单一策略无法覆盖全业务,工业级 RAG 系统必须搭配意图分类 + 策略路由架构。

8.2 生产环境落地建议

  1. 性能优先场景(客服 FAQ、高频查询):主力使用直接检索,关闭冗余 LLM 调用;
  2. 问答 / 知识库场景(文档问答、科普问答):HyDE 作为默认增强策略;
  3. 对比分析类场景(选型、测评):固定使用子查询检索;
  4. 用户自由输入场景(C 端聊天问答):启用自动策略路由,全策略兼容;
  5. 风险规避:对 LLM 生成的假设文档、改写问题、子查询增加简单校验,过滤明显错误内容。

8.3 拓展方向

  1. 多策略混合检索:同时执行 2 种策略,融合检索结果;
  2. 轻量化分类器:用小模型替代大模型做策略分类,进一步降本提速;
  3. 动态 Top-K:根据查询复杂度动态调整检索返回条数。
相关推荐
学Linux的语莫1 小时前
redis的数据类型和使用
数据库·redis·缓存
IvorySQL2 小时前
PGv19预发布对现有生产系统的隐患思考,MySQL别看!
数据库·postgresql·开源
点灯小铭2 小时前
基于单片机的鱼缸监测与远程管理系统设计
数据库·单片机·嵌入式硬件·毕业设计·课程设计·期末大作业
Amnesia0_02 小时前
MYSQL表的约束
数据库·mysql
C137的本贾尼2 小时前
锁的分类:表锁、行锁、页锁与意向锁
数据库
Full Stack Developme2 小时前
SQL 执行顺序 及 全部关键字
数据库·sql
专注API从业者2 小时前
电商选品效率翻倍!基于 Open Claw + 淘宝商品 API 实现自动化监控选品(附完整可运行代码)
大数据·运维·数据结构·数据库·自动化
C137的本贾尼2 小时前
InnoDB 内存架构:Buffer Pool、Change Buffer 与 Log Buffer
数据库·oracle·架构
DigitalOcean3 小时前
深度评测:RAG 向量数据库选型指南 —— OpenSearch、Weaviate、pgvector 怎么选?
数据库·ai编程