在构建企业级 RAG 系统时,很多人把精力全押在“大模型选型”或“Prompt 工程”上,却忽略了最关键的一步:召回(Retrieval)。试想:
- 用户问"如何优化 LLM 推理延迟?",系统却召回一堆"训练加速"文档;
- 客服场景中,明明有解决方案,AI 却回答"暂无相关信息";
- 甚至因召回偏差,导致生成内容出现事实性幻觉……
⚠️ 问题不在模型,而在"没找到对的文档"。
本文作为《RAG高效召回方法》系列的第一篇,将深入剖析6 大高阶召回策略:
从基础的 Top-K 与混合检索,到重排序(Rerank)、查询改写(Query Rewriting),再到动态联网增强——每一种方法都经过工业级验证,可显著提升 Hit Rate 与 MRR。
更重要的是,后续要掌握的双向改写(Query2Doc/Doc2Query)、索引扩展(Small-to-Big、连续/离散索引)、知识库健康治理等能力深度融合,共同构建一个高精度、高鲁棒、可演进的 RAG 引擎。
现在,让我们先打好地基——从这 6 个关键召回策略开始。
📊 1、合理设置Top-K
如果要召回更多的片段,如何设置?
docs = knowledgeBase.similarity_search(query, k=10)
🔍 2、改进检索算法
**🗺️ 知识图谱:**利用知识图谱中的语义信息和实体关系,增强对查询和文档的理解,提升召回的相关性。(后面章节介绍)
🔄 3、引入重排序(Reranking)
🔄 重排序模型:
对召回结果进行重排,提升问题和文档的相关性。常见的重排序模型有BGE-Rerank和Cohere Rerank。
🎬 场景:
用户查询"如何提高深度学习模型的训练效率?"
📄 召回结果:
初步召回10篇文档,其中包含与"深度学习"、"训练效率"相关的文章。
🔄 重排序:
BGE-Rerank对召回的10篇文档进行重新排序,将与"训练效率"最相关的文档(如"优化深度学习训练的技巧")排在最前面,而将相关性较低的文档(如"深度学习基础理论")排在后面。
🔀 混合检索:
结合向量检索和关键词检索的优势,通过重排序模型对结果进行归一化处理,提升召回质量。
💡 什么是重排序模型?
重排序Rerank主要用于优化初步检索结果的排序,提高最终输出的相关性或准确性。
BGE-Rerank和Cohere Rerank是两种广泛使用的重排序模型,它们在检索增强生成(RAG)系统、搜索引擎优化和问答系统中表现优异。
🔬 1、BGE-Rerank 由北京智源人工智能研究院(BAAI)开源发布,属于FlagEmbedding项目的一部分。
基于Transformer的Cross-Encoder结构,直接计算查询(Query)与文档(Document)的交互相关性得分。
📚 训练数据:
支持多语言(中、英等),训练数据包括T2Ranking、MSMARCO、NLI等数据集。
提供bge-reranker-base和bge-reranker-large两个版本,后者在精度上更优。
🚀 部署方式:
可本地部署
✨ 优势:
开源免费,适合本地化部署,保护数据隐私。
在中文任务中表现优秀,适用于垂直领域优化
💻 示例代码
#模型下载
from modelscope import snapshot_download
model_dir = snapshot_download('BAAI/bge-reranker-large', cache_dir='/root/autodl-tmp/models')
import torch
from transformers import AutoModelForSequenceClassification, AutoTokenizer
tokenizer = AutoTokenizer.from_pretrained('/root/models/BAAI/bge-reranker-large')
model = AutoModelForSequenceClassification.from_pretrained('/root/models/BAAI/bge-reranker-large')
model.eval()
pairs = [['what is panda?', 'The giant panda is a bear species endemic to China.']]
inputs = tokenizer(pairs, padding=True, truncation=True, return_tensors='pt')
scores = model(**inputs).logits.view(-1).float()
print(scores) # 输出相关性分数
# 输出相关性分数
pairs = [
['what is panda?', 'The giant panda is a bear species endemic to China.'], # 高相关
['what is panda?', 'Pandas are cute.'], # 中等相关
['what is panda?', 'The Eiffel Tower is in Paris.'] # 不相关
]
inputs = tokenizer(pairs, padding=True, truncation=True, return_tensors='pt')
scores = model(**inputs).logits.view(-1).float()
print(scores)
在BGE-Rerank模型中,相关性分数scores是一个未归一化的对数几率(logits)值,范围没有固定的上限或下限(不像某些模型限制在0-1)。不过BGE-Rerank的分数通常落在以下范围:
-
✅ 高相关性: 3.0~10.0
-
⚠️ 中等相关性: 0.0~3.0
-
❌ 低相关性/不相关: 负数(如-5.0以下)
☁️ 2、Cohere Reank 由Cohere公司提供的商业API服务。基于专有的深度学习模型,支持多语言(如rerank-multilingual-v3.0)。
📚 训练数据:
优化了语义匹配,特别适用于混合检索(如结合BM25和向量检索)后的结果优化。
🔧 使用方式:
通过API调用,集成到LangChain、LlamaIndex等框架中。
✨ 优势:
• 简单易用,适合快速集成到现有系统。
• 在英文和多语言任务中表现优异,如提升Hit Rate(命中率)和MRR(平均倒数排名)。
import cohere
co = cohere.Client(api_key="YOUR_API_KEY")
query = "What is the capital of France?"
docs = ["Paris is the capital of France.", "Berlin is the capital of Germany."]
results = co.rerank(query=query, documents=docs, top_n=2, model='rerank-multilingual-v3.0')
print(results) #Cohere Rerank的API返回的是归一化后的相关性分数(如0-1),更易解释
📊 对比
| 特性 | BGE-Rerank | Cohere Rerank |
|---|---|---|
| 开源/商业 | 开源 | 商业API |
| 部署方式 | 本地部署 | 云端调用 |
| 多语言支持 | 中英优化 | 多语言 |
| 适用场景 | 数据敏感,垂直领域 | 快速集成,多语言优化 |
🔄 4、优化查询扩展
💬 相似语义改写:
使用大模型将用户查询改写成多个语义相近的查询,提升召回多样性。
- 例如,LangChain的
MultiQueryRetriever支持多查询召回,再进行回答问题。
# 加载向量数据库,添加allow_dangerous_deserialization=True参数以允许反序列化
vectorstore = FAISS.load_local("./faiss-1", embeddings, allow_dangerous_deserialization=True)
# 创建MultiQueryRetriever
retriever = MultiQueryRetriever.from_llm(
retriever=vectorstore.as_retriever(),
llm=llm
)
query = "客户经理的考核标准是什么?"
# 执行查询
results = retriever.get_relevant_documents(query)
✏️ 5、查询改写
❓ 为什么需要查询改写?
RAG 的核心在于"检索-生成"。如果第一步"检索"就走偏了,那么后续的"生成"质量也会降低。
用户提出的问题往往是口语化的、承接上下文的、模糊的,甚至是包含了情绪的。
而知识库里的文本(切片/Chunks)通常是陈述性的、客观的。=> 需要一个翻译官的角色,将用户的"口语化查询"转换成"书面化、精确的检索语句"
💡 如何针对不同类型的Query进行改写?
通过精心设计的 Prompt 来引导 LLM完成这项任务。
🔗 1、上下文依赖型
📋 特征:
当前问题无法独立理解,必须结合前一轮或多轮对话历史才能明确意图。
🎬 典型场景:
"它支持哪些格式?"
"这个方法有效吗?"
❌ 问题:
"它""这个"等代词指代不明,直接检索会失败。
✅ 改写策略: 结合对话上下文,将代词替换为具体实体或动作。
📝 改写示例:
"""上下文依赖型Query改写"""
instruction = """
你是一个智能的查询优化助手。请分析用户的当前问题以及前序对话历史,判断当前问题是否依赖于上下文。
如果依赖,请将当前问题改写成一个独立的、包含所有必要上下文信息的完整问题。
如果不依赖,直接返回原问题。
"""
prompt = f"""
### 指令 ###
{instruction}
### 对话历史 ###
{conversation_history}
### 当前问题 ###
{current_query}
### 改写后的问题 ###
💬 对话历史:
👤 用户**:**"我想了解一下上海迪士尼乐园的最新项目。"
🤖 AI:"上海迪士尼乐园最新推出了'疯狂动物城'主题园区,这里有朱迪警 官和尼克狐的互动体验。"
👤 用户:"这个园区有什么游乐设施?"
🤖 AI:"'疯狂动物城'园区目前有疯狂动物城警察局、朱迪警官训练营和尼 克狐的冰淇淋店等设施。"
**❓ 当前查询:**还有其他设施吗?
**✅ 改写结果:**除了疯狂动物城警察局、朱迪警官训练营和尼克狐的冰淇淋 店之外,'疯狂动物城'园区还有其他设施吗?
⚖️ 2、对比型
📋 特征:
用户希望比较两个或多个对象、方法、模型的异同或优劣。
🎬 典型场景: "RAG 和微调哪个更好?"
"BGE-Rerank 和 Cohere Rerank 有什么区别?"
❌ 问题: 知识库中文档通常单独描述某个技术,很少直接包含"A vs B"的对比内容。若按原句检索,可能只召回其中一方的信息。
✅ 改写策略: 拆解为多个独立查询,或生成涵盖双方的中性检索语句。
📝 改写示例:
"""对比型Query改写"""
instruction = """
你是一个查询分析专家。请分析用户的输入和相关的对话上下文,识别出问题中需要进行比较的多个对象。
然后,将原始问题改写成一个更明确、更适合在知识库中检索的对比性查询。
"""
prompt = f"""
### 指令 ###
{instruction}
### 对话历史/上下文信息 ###
{context_info}
### 原始问题 ###
{query}
### 改写后的查询 ###
💬 对话历史:
👤 用户: "我想了解一下上海迪士尼乐园的最新项目。"
🤖 AI: "上海迪士尼乐园最新推出了疯狂动物城主题园区,还有蜘蛛侠主 题园区"
❓ 当前查询: 哪个游玩的时间比较长,比较有趣
✅ 改写结果: 哪个游玩时间更长、更有趣:上海迪士尼乐园的疯狂动物城 主题园区和蜘蛛侠主题园区?
🔤 3、模糊指代型
📋 特征:
用户用"你""你们""你的模型"等指代当前 AI 系统本身。
🎬 典型场景:
"你支持多少种语言?"
"你的训练数据截止到什么时候?"
❌ 问题:
直接根据这些代词去知识库中查询,是查找不到结果的。
✅ 改写策略:
将这些指代词替换为明确的对象名称。
📝 改写示例:
"""模糊指代型Query改写"""
instruction = """
你是一个消除语言歧义的专家。请分析用户的当前问题和对话历史,找出问题中 "都"、"它"、"这个" 等模糊指代词具体指向的对象。
然后,将这些指代词替换为明确的对象名称,生成一个清晰、无歧义的新问题。
"""
prompt = f"""
### 指令 ###
{instruction}
### 对话历史 ###
{conversation_history}
### 当前问题 ###
{current_query}
### 改写后的问题 ###
💬 对话历史:
👤 用户: "我想了解一下上海迪士尼乐园和香港迪士尼乐园的烟花表演。"
🤖 AI: "好的,上海迪士尼乐园和香港迪士尼乐园都有精彩的烟花表演。"
**❓ 当前查询:**都什么时候开始?
**✅ 改写结果:**上海迪士尼乐园和香港迪士尼乐园的烟花表演都什么时候开始?
🎯 4、多意图型
📋 特征:
一个问题中隐含多个独立子问题或目标。
🎬 场景:
"怎么安装 CUDA 并配置 PyTorch 环境?"
"RAG 的召回率低怎么办?有哪些优化方法?"
❌ 问题:
单一检索可能只覆盖部分意图,导致答案不完整。
✅ 改写策略:
使用 LLM 将复合问题拆分为多个原子查询,分别检索后再融合。
📝 改写示例:
"""多意图型Query改写 - 分解查询"""
instruction = """
你是一个任务分解机器人。请将用户的复杂问题分解成多个独立的、可以单独回答的简单问题。以JSON数组格式输出。
"""
prompt = f"""
### 指令 ###
{instruction}
### 原始问题 ###
{query}
### 分解后的问题列表 ###
请以JSON数组格式输出,例如:["问题1", "问题2", "问题3"]
"""
response = get_completion(prompt, self.model)
try:
return json.loads(response)
except:
return [response]
❓ 原始查询: 门票多少钱?需要提前预约吗?停车费怎么收?
✅ 分解结果: ['门票多少钱?', '需要提前预约吗?', '停车费怎么收?']
❓ 5、反问型
📋 特征:
以否定、质疑或反问形式表达真实需求。
🎬 场景:
"难道就没有办法解决 RAG 召回不准的问题吗?"
"是不是所有大模型都不能联网?"
❌ 问题:
表面是否定句,实际是寻求肯定解决方案。若按字面检索"没有""不能",会召回错误或无关内容。
✅ 改写策略:
去除情绪化/修辞成分,提取核心正向意图。
📝 改写示例:
"""反问型Query改写"""
instruction = """
你是一个沟通理解大师。请分析用户的反问或带有情绪的陈述,识别其背后真实的意图和问题。
然后,将这个反问改写成一个中立、客观、可以直接用于知识库检索的问题。
"""
prompt = f"""
### 指令 ###
{instruction}
### 对话历史 ###
{conversation_history}
### 当前问题 ###
{current_query}
### 改写后的问题 ###
"""
return get_completion(prompt, self.model)
💬 对话历史:
👤 用户: "你好,我想预订下周六上海迪士尼乐园的门票。"
🤖 AI: "正在为您查询... 查询到下周六的门票已经售罄。"
👤 用户: "售罄是什么意思?我朋友上周去还能买到当天的票。"
❓ 当前查询: 这不会也要提前一个月预订吧?
✅ 改写结果: 迪士尼乐园门票是否需要提前一个月预订?
💻 基于Prompt生成文本
def get_completion(prompt, model="qwen-turbo-latest"):
messages = [{"role": "user", "content": prompt}]
response = dashscope.Generation.call(
model=model,
messages=messages,
result_format='message',
temperature=0,
)
return response.output.choices[0].message.content
🤖 6、自动识别Query类型进行改写
自动识别Query类型并进行改写
"""自动识别Query类型并进行改写"""
instruction = """
你是一个智能的查询分析专家。请分析用户的查询,识别其属于以下哪种类型:
1. 上下文依赖型 - 包含"还有"、"其他"等需要上下文理解的词汇
2. 对比型 - 包含"哪个"、"比较"、"更"、"哪个更好"、"哪个更"等比较词汇
3. 模糊指代型 - 包含"它"、"他们"、"都"、"这个"等指代词
4. 多意图型 - 包含多个独立问题,用"、"或"?"分隔
5. 反问型 - 包含"不会"、"难道"等反问语气
说明:如果同时存在多意图型、模糊指代型,优先级为多意图型>模糊指代型
请返回JSON格式的结果:
{
"query_type": "查询类型",
"rewritten_query": "改写后的查询",
"confidence": "置信度(0-1)"
}
"""
prompt = f"""
### 指令 ###
{instruction}
### 对话历史 ###
{conversation_history}
### 上下文信息 ###
{context_info}
### 原始查询 ###
{query}
### 分析结果 ###
"""
response = get_completion(prompt, self.model)
try:
return json.loads(response)
except:
return {
"query_type": "未知类型",
"rewritten_query": query,
"confidence": 0.5
}
🌐 6、查询+联网搜索
🤔 你有没有遇到过这样的情况?
❓ 问AI:"今天北京天气怎么样?"
它却回答:"北京四季分明,春暖花开……"(但今天明明在下暴雨!)
❓ 问AI:"iPhone 17什么时候发布?"
它一本正经地告诉你:"苹果最新款是iPhone 15。"(可实际上,发布会就在下周!)
❓ 问题出在哪儿?
因为绝大多数大模型(比如GPT、通义千问、DeepSeek等)本质上是一个"静态知识库"——它们的知识截止于训练数据的时间点。比如,如果一个模型训练数据只更新到2024年7月,那么它对2025年发生的事就一无所知,只能靠"猜"或者"编",这就是所谓的 "幻觉"。
而联网搜索,就是给大模型装上一双"实时看世界的眼睛"。
💡 那么,哪些问题必须联网才能答对?
1. 🔍 识别查询是否需要联网搜索
identify_web_search_needs
📥 输入:
query:用户查询conversation_history:对话历史上下文
📤 输出:
need_web_search: 是否需要联网搜索search_reason: 搜索原因confidence: 置信度(0-1)
instruction = """
你是一个智能的查询分析专家。请分析用户的查询,判断是否需要联网搜索来获取最新、最准确的信息。
需要联网搜索的情况包括:
1. 时效性信息 - 包含"最新"、"今天"、"现在"、"实时"、"当前"等时间相关词汇
2. 价格信息 - 包含"多少钱"、"价格"、"费用"、"票价"等价格相关词汇
3. 营业信息 - 包含"营业时间"、"开放时间"、"闭园时间"、"是否开放"等营业状态
4. 活动信息 - 包含"活动"、"表演"、"演出"、"节日"、"庆典"等动态信息
5. 天气信息 - 包含"天气"、"下雨"、"温度"等天气相关
6. 交通信息 - 包含"怎么去"、"交通"、"地铁"、"公交"等交通方式
7. 预订信息 - 包含"预订"、"预约"、"购票"、"订票"等预订相关
8. 实时状态 - 包含"排队"、"拥挤"、"人流量"等实时状态
请返回JSON格式:
{
"need_web_search": true/false,
"search_reason": "需要搜索的原因",
"confidence": "置信度(0-1)"
}
"""
prompt = f"""
### 指令 ###
{instruction}
### 对话历史 ###
{conversation_history}
### 用户查询 ###
{query}
### 分析结果 ###
"""
2. ✏️ 为联网搜索改写查询
rewrite_for_web_search
📥 输入:
query:原始查询search_type:搜索类型
📤 输出:
rewritten_query: 改写后的查询search_keywords: 搜索关键字列表search_intent: 搜索意图suggested_sources: 建议搜索来源
instruction = """
你是一个专业的搜索查询优化专家。请将用户的查询改写为更适合搜索引擎检索的形式。
改写技巧:
1. 添加具体地点 - 如"上海迪士尼乐园"、"香港迪士尼乐园"
2. 添加时间范围 - 如"2024年"、"今天"、"本周"
3. 使用关键词组合 - 将长句拆分为关键词
4. 添加搜索意图 - 明确搜索目的
5. 去除口语化表达 - 转换为标准搜索词
6. 添加相关词汇 - 增加同义词或相关词
请返回JSON格式:
{
"rewritten_query": "改写后的搜索查询",
"search_keywords": ["关键词1", "关键词2", "关键词3"],
"search_intent": "搜索意图",
"suggested_sources": ["建议搜索的网站类型"]
}
"""
prompt = f"""
### 指令 ###
{instruction}
### 原始查询 ###
{query}
### 搜索类型 ###
{search_type}
### 改写结果 ###
"""
3. 🎯 生成搜索策略
generate_search_strategy
📥 输入:
query:原始查询search_type:搜索类型
📤 输出:
primary_keywords: 主要关键字extended_keywords: 扩展关键字search_platforms: 搜索平台search_tips: 搜索技巧
instruction = f"""
你是一个搜索策略专家。请为用户的查询制定详细的搜索策略。
当前日期:{current_date}
搜索策略包括:
1. 主要搜索词 - 核心关键词
2. 扩展搜索词 - 相关词汇和同义词
3. 搜索网站 - 推荐的搜索平台
4. 时间范围 - 具体的搜索时间范围
请返回JSON格式:
{{
"primary_keywords": ["主要关键词"],
"extended_keywords": ["扩展关键词"],
"search_platforms": ["搜索平台"],
"time_range": "具体的时间范围"
}}
"""
prompt = f"""
### 指令 ###
{instruction}
### 用户查询 ###
{query}
### 搜索类型 ###
{search_type}
### 搜索策略 ###
"""
📝 示例
💬 对话历史:
👤 用户:"我想去上海迪士尼乐园玩"
🤖 AI:"上海迪士尼乐园是一个很棒的选择!"
❓ 当前查询: 下周六的门票多少钱?需要提前多久预订?
✅ 需要联网搜索
🔍 搜索原因: 查询下周六的门票价格和预订时间,涉及价格信息和预订信息,需要联网获取最新、最准确的数据。
📊 置信度: 0.98
✏️ 改写查询: 下周六上海迪士尼乐园门票价格及预订时间要求
🔑 搜索关键词: ['下周六', '上海迪士尼乐园', '门票价格', '预订时间', '提前多久预订']
🎯 搜索意图: 获取特定日期的门票价格和预订政策信息
📚 建议来源: ['官方网站', '旅游预订平台(如携程、飞猪)', '景点官方社交媒体账号']
🎯 搜索策略:
-
主要关键词: ['下周六的门票多少钱?需要提前多久预订?']
-
扩展关键词: []
-
搜索平台: ['百度', '谷歌']
-
时间范围: 最近一周
📝 4.总结
大模型再聪明,也活在"过去"。
要让它回答"现在"和"未来"的事,必须给它插上联网的翅膀。
🎉 END
如果你觉得本文有帮助,欢迎点赞👍、在看👀、转发📤,也欢迎留言💬分享你的经验!