本文将系统拆解 RAG 召回环节的核心进阶策略 ——优化查询扩展(含双向改写)、索引扩展、Small-to-Big 索引策略,结合原理、场景、落地逻辑,讲清每种方法的核心价值和适配场景,同时提供可落地的代码示例(chatpdf-faiss-MultiQueryRetriever)。
一、优化查询扩展:从 “单一查询” 到 “多维度意图覆盖”
查询扩展的核心是解决 “用户查询短、表述单一、语义不充分” 导致的召回漏检问题,核心分为「多查询召回」和「双向改写」两类,本质都是 “丰富查询的语义表达”。
1. 基础版:MultiQueryRetriever(多相似查询召回)
核心逻辑
用大模型将用户原始查询改写为多个语义等价、表述不同的查询,分别召回文档后合并去重,避免因用户表述习惯导致的漏召回。
- 适配场景:短查询、模糊查询(如 “客户经理考核标准”“如何优化深度学习训练”)。
- 工具:LangChain 的
MultiQueryRetriever(开箱即用)。
落地代码:chatpdf-faiss-MultiQueryRetriever.py
import os
from langchain_community.vectorstores import FAISS
from langchain_community.document_loaders import PyPDFLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_community.embeddings import HuggingFaceEmbeddings
from langchain.retrievers.multi_query import MultiQueryRetriever
from langchain.chat_models import ChatOpenAI
from langchain.prompts import PromptTemplate
from langchain.chains import RetrievalQA
# -------------------------- 1. 环境配置 --------------------------
# 配置OpenAI API Key(或替换为国产大模型,如智谱、通义千问)
os.environ["OPENAI_API_KEY"] = "your-api-key"
# 配置本地模型路径(BGE嵌入模型,适配中文)
EMBEDDING_MODEL_PATH = "BAAI/bge-base-zh-v1.5"
# -------------------------- 2. 加载PDF并构建向量库 --------------------------
def load_pdf_and_build_faiss(pdf_path, faiss_save_path):
"""加载PDF文档,拆分后构建FAISS向量库"""
# 1. 加载PDF
loader = PyPDFLoader(pdf_path)
docs = loader.load()
# 2. 文本拆分(适配长文档,避免向量截断)
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=500, # 每个chunk 500字
chunk_overlap=50, # 重叠50字,保证上下文连贯
separators=["\n\n", "\n", "。", "!", "?", ",", ""]
)
split_docs = text_splitter.split_documents(docs)
# 3. 初始化嵌入模型
embeddings = HuggingFaceEmbeddings(
model_name=EMBEDDING_MODEL_PATH,
model_kwargs={"device": "cpu"}, # 若有GPU,改为"cuda"
encode_kwargs={"normalize_embeddings": True} # 归一化向量,提升相似度计算精度
)
# 4. 构建FAISS向量库并保存
vector_db = FAISS.from_documents(split_docs, embeddings)
vector_db.save_local(faiss_save_path, allow_dangerous_deserialization=True)
return vector_db
# -------------------------- 3. 初始化MultiQueryRetriever --------------------------
def init_multi_query_retriever(vector_db):
"""初始化多查询召回器"""
# 1. 初始化大模型(用于生成多查询)
llm = ChatOpenAI(
model_name="gpt-3.5-turbo",
temperature=0, # 0温度保证改写的语义一致性
max_tokens=500
)
# 2. 自定义多查询生成提示词(适配中文场景)
MULTI_QUERY_PROMPT = PromptTemplate(
input_variables=["question"],
template="""你是一个专业的检索助手,需要将用户的查询改写成3个语义相同但表述不同的中文查询,用于文档检索。
要求:
1. 改写后的查询需覆盖用户核心意图,不偏离主题;
2. 表述方式多样化(比如同义词替换、句式调整);
3. 仅输出改写后的查询,每行一个,无其他多余内容。
用户原始查询:{question}"""
)
# 3. 创建MultiQueryRetriever
retriever = MultiQueryRetriever.from_llm(
retriever=vector_db.as_retriever(search_kwargs={"k": 5}), # 每个查询召回5篇
llm=llm,
prompt=MULTI_QUERY_PROMPT,
number_of_queries=3 # 生成3个改写查询
)
return retriever
# -------------------------- 4. 构建RAG问答链 --------------------------
def build_qa_chain(retriever):
"""构建检索增强问答链"""
llm = ChatOpenAI(model_name="gpt-3.5-turbo", temperature=0.1)
qa_chain = RetrievalQA.from_chain_type(
llm=llm,
chain_type="stuff", # 简单场景用stuff,复杂场景用map_reduce
retriever=retriever,
return_source_documents=True # 返回召回的源文档,方便验证
)
return qa_chain
# -------------------------- 5. 主函数:执行问答 --------------------------
if __name__ == "__main__":
# 配置路径
PDF_PATH = "your_pdf_file.pdf" # 替换为你的PDF文件路径
FAISS_SAVE_PATH = "./faiss_pdf_db" # FAISS向量库保存路径
# 1. 构建/加载向量库
if os.path.exists(FAISS_SAVE_PATH):
# 加载已存在的向量库
embeddings = HuggingFaceEmbeddings(
model_name=EMBEDDING_MODEL_PATH,
model_kwargs={"device": "cpu"},
encode_kwargs={"normalize_embeddings": True}
)
vector_db = FAISS.load_local(
FAISS_SAVE_PATH,
embeddings,
allow_dangerous_deserialization=True
)
print("✅ 加载已存在的FAISS向量库")
else:
# 新建向量库
vector_db = load_pdf_and_build_faiss(PDF_PATH, FAISS_SAVE_PATH)
print("✅ 新建FAISS向量库完成")
# 2. 初始化多查询召回器
multi_query_retriever = init_multi_query_retriever(vector_db)
# 3. 构建问答链
qa_chain = build_qa_chain(multi_query_retriever)
# 4. 执行问答
user_query = "如何提高深度学习模型的训练效率?"
result = qa_chain({"query": user_query})
# 5. 输出结果
print("\n===== 问答结果 =====")
print(result["result"])
print("\n===== 召回的源文档 =====")
for i, doc in enumerate(result["source_documents"]):
print(f"\n【文档{i+1}】")
print(f"内容:{doc.page_content[:200]}...")
print(f"来源:{doc.metadata['source']} 页码:{doc.metadata['page']}")
代码说明
- 支持 PDF 加载、文本拆分、FAISS 向量库构建 / 加载;
- 自定义中文多查询提示词,适配中文语义改写;
- 返回源文档信息,方便验证召回效果;
- 可替换大模型(如替换为智谱 GLM-4、通义千问),只需调整
llm初始化逻辑。
2. 进阶版:双向改写(Query2Doc + Doc2Query)
核心解决 “短文本向量化效果差” 的问题 —— 通过 “查询→文档” 或 “文档→查询” 的双向改写,丰富语义信息,提升召回精度。
(1)Query2Doc:将短查询改写为长文档
- 核心逻辑:用户查询通常是短文本(如 “如何提高训练效率?”),向量化时语义信息不足,将查询改写为一段完整的、包含核心意图的长文本,再做向量检索,提升匹配精度。
- 改写示例:原始查询:“如何提高深度学习模型的训练效率?”改写后文档:
提高深度学习模型的训练效率可以从以下几个方面入手: 1. 使用更高效的优化算法,如AdamW或LAMB; 2. 采用混合精度训练,减少显存占用并加速计算; 3. 使用分布式训练技术,如数据并行或模型并行; 4. 调整学习率调度策略,避免训练震荡; 5. 对数据进行预处理,减少冗余计算。 - 落地方式:
- 用大模型生成改写文档(提示词:“将以下查询扩展为一段完整的、包含核心解决方案的文档,用于向量检索:{query}”);
- 将改写后的文档向量化,与知识库文档向量匹配,召回相关内容。
(2)Doc2Query:为文档生成关联查询
- 核心逻辑:对知识库中的短文档 / 文档片段,生成多个用户可能的查询语句,将这些查询作为文档的 “补充索引”,当用户查询匹配这些生成的查询时,可快速召回文档。
- 改写示例:文档内容:“本文介绍了深度学习训练的优化技巧,包括 AdamW 优化器、混合精度训练、分布式训练。”生成查询:
- 如何选择深度学习模型的优化器?
- 混合精度训练有哪些优势?
- 分布式训练如何加速深度学习?
- 如何减少深度学习训练的显存占用?
- 落地方式:
- 构建向量库时,对每个文档片段生成 5-10 个关联查询;
- 将 “文档原文 + 生成的查询” 合并后向量化,或单独为生成的查询建立索引;
- 用户查询时,同时匹配文档向量和生成查询向量,提升召回率。
双向改写的核心价值
- 解决 “短文本语义稀疏” 问题:短查询 / 短文档向量化时,因信息少导致相似度计算偏差,改写后补充语义信息;
- 提升召回精准度:改写后的文本更贴近知识库文档的表述方式,匹配度更高。
二、索引扩展:从 “单一向量索引” 到 “多维度索引互补”
索引扩展的核心是 “不依赖单一向量索引”,通过构建离散索引(关键词 / 实体)、多模型连续索引、混合索引,覆盖不同召回场景,提升准确性和覆盖率。
1. 离散索引扩展:关键词抽取 + 实体识别
离散索引是 “非向量” 的索引方式,通过提取文档的 “关键词 / 实体” 作为标签,补充向量检索的不足(向量检索可能忽略字面精准匹配)。
(1)关键词抽取
- 核心逻辑:从文档中提取核心关键词(如 TF-IDF、TextRank 算法),作为离散索引,当用户查询包含这些关键词时,快速匹配文档。
- 示例:文档内容:“本文介绍了深度学习训练的优化技巧,包括 AdamW 优化器、混合精度训练、分布式训练。”提取关键词:["深度学习", "模型训练", "优化技巧", "AdamW", "混合精度训练", "分布式训练"]
- 落地方式:
- 构建向量库时,对每个文档片段抽取关键词,存储到文档元数据(metadata)中;
- 用户查询时,先抽取查询关键词,过滤出元数据中包含匹配关键词的文档,再做向量检索(缩小召回范围,提升精度)。
(2)实体识别
- 核心逻辑:提取文档中的命名实体(时间、地点、人物、组织、专有名词等),作为离散索引,增强 “实体类查询” 的召回精度。
- 示例:文档内容:“2023 年诺贝尔物理学奖授予了三位科学家,表彰他们在量子纠缠领域的研究。”提取实体:["2023 年", "诺贝尔物理学奖", "量子纠缠"]
- 落地工具:SpaCy(多语言 NER)、HanLP(中文 NER)、BERT-NER(自定义领域 NER)。
- 落地价值:解决 “实体类查询漏召回” 问题,比如用户问 “2023 年诺贝尔物理学奖获奖者”,实体索引可快速定位相关文档,而向量检索可能因表述差异漏检。
2. 连续索引扩展:多路向量模型召回
- 核心逻辑:不同向量模型(如 OpenAI Ada、智源 BGE、百度 ERNIE)对文本的语义编码侧重不同,用多个模型分别构建向量索引,并行召回后合并结果,取长补短。
- 示例:
- OpenAI Ada:擅长通用语义匹配;
- BGE-base-zh:擅长中文语义匹配;
- 领域微调模型(如医疗 BGE):擅长垂直领域语义匹配。
- 落地方式:
- 用 3 个不同模型分别构建 FAISS 向量库;
- 用户查询时,用 3 个模型分别向量化并召回 Top 5 文档;
- 合并结果去重,进入重排序环节。
3. 混合索引召回:离散索引 + 连续索引
- 核心逻辑:将 “离散索引(关键词 / 实体)” 与 “连续索引(向量)” 结合,通过
Ensemble Retriever(集成召回器)实现混合召回,兼顾 “字面精准匹配” 和 “语义相似匹配”。 - 示例流程(用户查询:“人工智能在医疗领域的应用有哪些?”):
- 离散索引召回:匹配关键词 ["人工智能", "医疗领域", "深度学习", "医学影像分析"] 的文档;
- 向量索引召回:匹配语义相似的文档;
- 合并结果:去重后得到 “精准 + 语义” 的召回列表,再通过重排序模型筛选 Top N。
- 落地工具:LangChain 的
EnsembleRetriever(支持组合多个召回器)。
混合索引召回代码示例
from langchain.retrievers import EnsembleRetriever
from langchain_community.retrievers import BM25Retriever # 离散索引(关键词)
from langchain_community.vectorstores import FAISS
from langchain_community.embeddings import HuggingFaceEmbeddings
# 1. 初始化BM25召回器(离散索引,关键词匹配)
bm25_retriever = BM25Retriever.from_documents(split_docs) # split_docs为拆分后的文档
bm25_retriever.k = 5 # 召回5篇
# 2. 初始化FAISS向量召回器(连续索引,语义匹配)
embeddings = HuggingFaceEmbeddings(model_name="BAAI/bge-base-zh-v1.5")
vector_retriever = FAISS.from_documents(split_docs, embeddings).as_retriever(search_kwargs={"k": 5})
# 3. 构建混合召回器
ensemble_retriever = EnsembleRetriever(
retrievers=[bm25_retriever, vector_retriever],
weights=[0.4, 0.6] # BM25权重40%,向量权重60%,可根据场景调整
)
# 4. 执行召回
docs = ensemble_retriever.get_relevant_documents("人工智能在医疗领域的应用有哪些?")
三、Small-to-Big 索引策略:长文档 / 多文档的高效召回
Small-to-Big 是针对 “长文档(如 PDF 论文、万字报告)” 的召回优化策略,核心是 “先检索小规模摘要 / 关键句,再链接到完整文档”,兼顾召回效率和上下文完整性。
1. 核心原理
将文档分为 “小规模索引内容” 和 “大规模主体内容”,通过 “先小后大” 的检索逻辑,解决长文档向量化效率低、召回精度差的问题:
- 小规模内容(索引层):文档摘要、关键句、核心段落(短文本,易索引、检索快);
- 大规模内容(主体层):完整文档、全文 PDF(长文本,包含详细信息)。
2. 核心流程
用户查询 → 检索小规模内容(摘要/关键句)→ 匹配到相关条目 → 链接到对应的大规模内容 → 提取完整上下文 → 生成答案
3. 关键环节拆解
(1)小规模内容构建
- 摘要生成:用大模型 / 文本摘要算法(如 TextRank)提取文档摘要(每篇文档 1-2 段);
- 关键句提取:提取文档中与核心主题相关的句子(如 “Transformer 通过自注意力实现并行计算”);
- 段落拆分:将长文档拆分为核心段落(如每个段落对应一个子主题)。
(2)索引构建
- 为小规模内容构建 “混合索引”(关键词 + 向量),保证检索速度和精度;
- 建立小规模内容与大规模内容的映射关系(如文档 ID、页码、URL)。
(3)检索与链接
- 先在小规模内容中检索,快速定位相关文档的 “入口”;
- 通过映射关系找到对应的完整文档,提取所需上下文;
- 仅将 “小规模内容 + 匹配的完整段落” 传入生成环节,避免冗余信息。
4. 核心优势
- 效率高:小规模内容检索速度比全文检索快 10-100 倍,适配百万级文档库;
- 精度高:摘要 / 关键句是文档核心,检索时能精准匹配用户意图;
- 上下文完整:链接到完整文档后,可提取详细信息,保证答案的丰富性。
5. 落地示例(论文库检索)
- 小规模内容:每篇论文的摘要、核心结论句、实验关键指标;
- 大规模内容:论文全文 PDF、实验数据、公式推导;
- 用户查询:“Transformer 在机器翻译中的改进方法”;
- 检索流程:
- 检索小规模内容,匹配到 “Transformer 改进注意力机制的机器翻译论文摘要”;
- 链接到该论文的完整 PDF;
- 提取 “改进注意力机制” 的相关章节,作为上下文生成答案。
四、各策略适配场景与选型建议
| 策略类型 | 核心优势 | 适配场景 | 注意事项 |
|---|---|---|---|
| MultiQueryRetriever | 简单易落地,覆盖多表述 | 短查询、模糊查询、通用场景 | 避免生成过多查询(3-5 个为宜),防止冗余 |
| 双向改写 | 解决短文本语义稀疏 | 短查询 / 短文档、垂直领域(如医疗 / 金融) | 改写需保证语义一致性,避免偏离主题 |
| 离散索引扩展 | 字面精准匹配,检索快 | 实体查询、术语查询、精准匹配场景 | 需维护关键词 / 实体词典,适配领域术语 |
| 混合索引召回 | 兼顾精准与语义 | 通用场景、多类型查询混合的场景 | 权重需调优(如 BM25: 向量 = 4:6) |
| Small-to-Big | 高效处理长文档 / 多文档 | 论文库、PDF 报告库、万字长文档检索 | 需做好小规模内容与大规模内容的映射 |
五、总结
RAG 高效召回的核心是 “兼顾全面性与精准性”:
- 查询扩展解决 “意图覆盖不足” 问题,通过多查询、双向改写丰富语义;
- 索引扩展解决 “单一索引局限” 问题,通过离散 + 连续索引互补;
- Small-to-Big 解决 “长文档检索效率低” 问题,通过 “先小后大” 平衡速度与精度。
落地时建议按 “基础→进阶” 的顺序:先实现 MultiQueryRetriever + 混合索引,再根据场景补充双向改写 / Small-to-Big,最后通过重排序模型(如 BGE-Rerank)优化最终结果,可大幅提升 RAG 的召回质量和生成效果。

3186

被折叠的 条评论
为什么被折叠?



