精准雷达:高效召回------RAG系统召回策略深度解析

| 🚀 核心目标 | 🛠️ 关键技术 | 📈 性能提升 |
|---|---|---|
| 精准定位 | Rerank, HyDE | 显著降低噪音,提升相关性 |
| 语义增强 | Query2Doc, Doc2Query | 解决词不达意,丰富查询语义 |
| 高效检索 | Small-to-Big, Multi-Vector | 兼顾速度与深度,长文处理更优 |
| 知识联动 | Graph-based Retrieval | 挖掘潜在关联,支持复杂推理 |
目录

- 引言:RAG,你真的"召"对了吗?
- 一、Rerank模型:让"粗心"的召回变得"精明"
- 二、双向改写:让"词不达意"的查询"心领神会"
- 三、Small-to-Big索引:让"大象"也能"跳舞"
- 四、拓展方案:让RAG召回能力"更上一层楼"
- [4.1 拓展方案一:HyDE (Hypothetical Document Embedding)------"脑补"式召回](#4.1 拓展方案一:HyDE (Hypothetical Document Embedding)——“脑补”式召回)
- [4.2 拓展方案二:Multi-Vector Retrieval------"多角度"看文档](#4.2 拓展方案二:Multi-Vector Retrieval——“多角度”看文档)
- [4.3 拓展方案三:Graph-based Retrieval------"关系网"式召回](#4.3 拓展方案三:Graph-based Retrieval——“关系网”式召回)
- 五、总结与展望:RAG召回的"未来已来"
- 六、互动环节:你的"召回秘籍"是什么?
- 转载声明
引言:RAG,你真的"召"对了吗?
在信息爆炸的时代,我们常常被海量数据淹没。如何从浩瀚的知识海洋中精准捞出我们想要的那一瓢水?这正是检索增强生成(Retrieval-Augmented Generation, RAG)技术的核心魅力。RAG就像一个超级图书馆管理员,它不仅能帮你找到书,还能根据书的内容为你总结答案。但如果这个管理员"召"回的书不对,那答案自然也就"生成"得不靠谱了。所以,今天我们就来聊聊RAG的"精准雷达"------高效召回策略,看看如何让你的RAG系统变成一个真正的"信息捕手"!
一、Rerank模型:让"粗心"的召回变得"精明"

1.1 专业解释:精排重排序的奥秘
Rerank模型,顾名思义,是对初步召回结果进行二次排序的模型。在RAG系统中,我们通常会先通过向量检索(如Faiss、Milvus)或关键词检索(如BM25)进行"粗排召回",快速筛选出一批与查询相关的文档。然而,粗排召回的结果可能包含一些相关性不高的"噪音",或者未能将最相关的文档排在前面。Rerank模型的作用就是对这些初步召回的文档进行更细致的评估,根据查询与文档内容的深层语义相关性,重新进行排序,从而提升最终结果的精准度。
1.2 大白话解读:从"大海捞针"到"精准定位"
想象一下,你在图书馆找一本关于"人工智能伦理"的书。粗排召回就像你先跑到"人工智能"区域,随手拿了10本书。这里面可能有讲AI历史的,有讲机器学习算法的,当然也有几本是关于伦理的。但你不知道哪本最符合你的需求。Rerank模型就像一个专业的图书顾问,他会仔细翻阅你拿的这10本书,然后告诉你:"这本《AI的道德困境》最符合你的要求,其次是这本《机器人与社会》。"一下子,你就能精准找到最想要的那本书了!
1.3 生活案例:电商购物的"心头好"推荐
在电商平台上购物时,你搜索"无线耳机"。系统会先给你展示几百款耳机(粗排召回)。但你可能更关心音质、续航或者品牌。Rerank模型会根据你的历史购买记录、浏览偏好、甚至你对"音质"这个词的潜在理解,对这几百款耳机进行重新排序,把最可能让你"剁手"的那几款排在前面。这就是Rerank在生活中的应用,让你更快找到"心头好"。
1.4 示例Python代码:Rerank模型简易实现
python
from typing import List
class Document:
def __init__(self, page_content: str, metadata: dict = None):
self.page_content = page_content
self.metadata = metadata if metadata is not None else {}
class SimpleReranker:
def __init__(self, model_name: str = "bge-reranker-base"): # 假设这里加载了一个预训练的rerank模型
print(f"Loading Reranker model: {model_name}...")
# 实际项目中会加载真正的模型,例如使用Hugging Face Transformers库
# self.tokenizer = AutoTokenizer.from_pretrained(model_name)
# self.model = AutoModelForSequenceClassification.from_pretrained(model_name)
# self.device = "cuda" if torch.cuda.is_available() else "cpu"
# self.model.to(self.device)
print("Reranker model loaded (simulated).")
def rerank(self, query: str, documents: List[Document], top_k: int = None) -> List[Document]:
if not documents:
return []
print(f"Reranking {len(documents)} documents for query: '{query}'")
# 模拟Rerank分数,实际应通过模型计算
# 这里简单地假设文档内容与查询的重合度越高,分数越高
scores = []
for doc in documents:
# 简单的关键词匹配作为模拟分数
score = sum(1 for word in query.split() if word.lower() in doc.page_content.lower())
scores.append(score)
# 按分数排序
scored_docs = sorted(zip(documents, scores), key=lambda x: x[1], reverse=True)
if top_k:
return [doc for doc, _ in scored_docs[:top_k]]
return [doc for doc, _ in scored_docs]
# 示例使用
if __name__ == "__main__":
reranker = SimpleReranker()
query = "如何提高深度学习模型的训练效率?"
candidate_docs = [
Document("本文介绍了深度学习模型训练中的优化技巧,包括:1. 使用 AdamW 优化器替代传统的 SGD。2. 采用混合精度训练,减少显存占用。3. 使用分布式训练技术加速大规模模型的训练......"),
Document("深度学习基础理论:神经网络的构建与反向传播。"),
Document("Python编程入门:变量、循环与函数。"),
Document("优化深度学习训练的技巧:学习率调度与正则化。")
]
print("\n--- 原始召回文档 ---")
for i, doc in enumerate(candidate_docs):
print(f"Doc {i+1}: {doc.page_content[:50]}...")
reranked_docs = reranker.rerank(query, candidate_docs, top_k=2)
print("\n--- Rerank 后文档 ---")
for i, doc in enumerate(reranked_docs):
print(f"Top {i+1}: {doc.page_content[:50]}...")
1.5 Rerank模型核心知识点总结
面试核心考点回顾:
- Rerank的位置:处于粗排(Vector/BM25)之后,生成(LLM)之前,属于"精排"阶段。
- 工作原理:将 Query 与每个候选 Doc 拼接,通过交叉编码器(Cross-Encoder)计算深度语义相关性分数。
- 主流模型:BGE-Reranker(国产之光)、Cohere Rerank(商业标杆)。
- 性能平衡:由于 Cross-Encoder 计算开销大,候选文档数通常控制在 10-20 个。
二、双向改写:让"词不达意"的查询"心领神会"

2.1 专业解释:Query2Doc与Doc2Query
双向改写(Bidirectional Rewriting)是解决短文本查询与长文档匹配难题的有效策略。它包含两种主要方法:
- Query2Doc (Query-to-Document):将用户输入的短查询改写或扩展成一个更长的、更具描述性的"假想文档"。这样做的目的是让查询的语义信息更丰富,更容易与文档的嵌入向量匹配,从而缓解短查询信息量不足导致召回不准确的问题。
- Doc2Query (Document-to-Query):为文档生成一组潜在的用户查询。在索引阶段,我们不仅存储文档本身的嵌入,还为每个文档生成多个可能的用户查询,并将这些查询也进行嵌入。当用户发起查询时,系统会匹配用户查询与这些"假想查询",从而找到相关的文档。
2.2 大白话解读:你问我答,我猜你心
想象你问一个朋友:"那个电影怎么样?"(短查询)朋友可能一头雾水,不知道你说的是哪部电影。Query2Doc就像你把问题补充完整:"你觉得上周五上映的,那个关于人工智能觉醒的科幻电影怎么样?"(扩展成假想文档),这样朋友就明白你在问什么了。
Doc2Query则像朋友很聪明,他知道自己最近看了《流浪地球2》、《满江红》和《阿凡达2》。他会提前想好:"如果有人问我科幻片,我可以说《流浪地球2》;如果有人问我历史片,我可以说《满江红》。"(为文档生成假想查询)。这样,无论你问"科幻片怎么样",还是"最近有什么好看的电影",他都能快速给出相关推荐。
2.3 生活案例:客服机器人"善解人意"的背后
你问客服机器人:"话费。"(短查询)机器人如果直接匹配"话费"这个词,可能会给你一堆无关信息。但如果它使用了Query2Doc,将你的"话费"扩展为"我想查询我的手机话费余额",或者"我想充值话费",就能更准确地理解你的意图。反过来,Doc2Query则让机器人能从"话费充值流程"这篇文档中,预设出"如何充话费"、"话费怎么交"等多种用户提问方式,从而实现"善解人意"。
2.4 示例Python代码:Query2Doc与Doc2Query模拟
python
from typing import List
class Document:
def __init__(self, page_content: str, metadata: dict = None):
self.page_content = page_content
self.metadata = metadata if metadata is not None else {}
class QueryRewriter:
def __init__(self, llm_model_name: str = "gpt-3.5-turbo"): # 假设这里使用一个LLM进行改写
print(f"Initializing QueryRewriter with LLM: {llm_model_name}...")
# 实际项目中会调用LLM API
self.llm_model_name = llm_model_name
print("QueryRewriter initialized (simulated LLM calls).")
def query_to_doc(self, query: str) -> str:
print(f"Rewriting query '{query}' to document...")
# 模拟LLM生成扩展文档
extended_doc = f"用户查询:'{query}'。这是一个关于{query}的详细描述,可能包含以下方面:... (此处应由LLM生成更丰富的上下文)"
return extended_doc
def doc_to_queries(self, document_content: str, num_queries: int = 3) -> List[str]:
print(f"Generating {num_queries} queries for document (first 50 chars): '{document_content[:50]}'...")
# 模拟LLM为文档生成多个查询
generated_queries = [
f"关于'{document_content[:20]}...'的第一个问题",
f"关于'{document_content[:20]}...'的第二个问题",
f"关于'{document_content[:20]}...'的第三个问题",
]
return generated_queries[:num_queries]
# 示例使用
if __name__ == "__main__":
rewriter = QueryRewriter()
# Query2Doc 示例
user_query = "深度学习效率"
extended_query_doc = rewriter.query_to_doc(user_query)
print(f"\nQuery2Doc 结果:{extended_query_doc}")
# Doc2Query 示例
sample_document = Document("本文详细介绍了Transformer模型在自然语言处理中的应用,包括其自注意力机制、编码器-解码器结构以及在机器翻译、文本生成等任务中的卓越表现。")
generated_queries = rewriter.doc_to_queries(sample_document.page_content, num_queries=2)
print(f"\nDoc2Query 结果:{generated_queries}")
2.5 双向改写核心知识点总结
面试核心考点回顾:
- Query2Doc:利用 LLM 将短 Query 扩展为"伪文档",利用伪文档的丰富语义去检索,解决 Query 向量过短、信息量不足的问题。
- Doc2Query:在离线索引阶段,为每个 Doc 生成 N 个潜在提问并建立索引。检索时匹配 Query 与"伪提问",提升检索的命中率。
- 核心优势:通过 LLM 的常识和推理能力,对齐用户提问与文档内容之间的"语义鸿沟"。
三、Small-to-Big索引:让"大象"也能"跳舞"

3.1 专业解释:分层索引与上下文扩展
Small-to-Big索引策略是一种高效处理长文档的召回方法。其核心思想是建立"小规模内容"的索引,并将其与对应的"大规模内容"关联起来。在检索时,首先通过用户查询匹配小规模内容(如文档摘要、关键句或段落),快速定位相关信息。一旦找到相关的小规模内容,系统便通过预设的链接(如文档ID)获取其对应的大规模内容(如完整的文档或相关章节),并将其作为RAG的上下文输入,从而提供更丰富、更连贯的背景知识。
3.2 大白话解读:先看"目录",再翻"正文"
你正在查阅一本厚厚的百科全书。Small-to-Big策略就像你先看目录(小规模内容),找到感兴趣的章节标题。一旦找到,你再直接翻到那一页(大规模内容)去阅读详细内容。这样比你一页一页地翻找(直接对长文档做全量检索)要高效得多,而且能确保你读到的内容是完整且相关的。
3.3 生活案例:论文查重与知识管理
在学术研究中,如果你想快速了解一篇长篇论文的核心观点,你通常会先看摘要和引言(小规模内容)。如果摘要吸引了你,你才会去阅读全文(大规模内容)。Small-to-Big索引正是模拟了这种人类的阅读习惯,在RAG系统中,它可以帮助我们更高效地从海量论文中找到最相关的知识点。在企业知识管理中,也可以用这种方式,先索引文档的标题、标签或关键段落,当员工查询时,快速定位到相关文档的完整内容。
3.4 示例Python代码:Small-to-Big索引模拟
python
from typing import List, Dict
class Document:
def __init__(self, doc_id: str, title: str, summary: str, full_content: str):
self.doc_id = doc_id
self.title = title
self.summary = summary
self.full_content = full_content
class SmallToBigIndexer:
def __init__(self):
self.small_content_index: Dict[str, Document] = {}
self.big_content_store: Dict[str, str] = {}
def add_document(self, doc: Document):
# 索引小规模内容(摘要、标题)
self.small_content_index[doc.doc_id] = doc
# 存储大规模内容
self.big_content_store[doc.doc_id] = doc.full_content
def retrieve(self, query: str, top_k_small: int = 3) -> List[str]:
print(f"Retrieving documents for query: '{query}' using Small-to-Big strategy...")
# 模拟在小规模内容中检索
# 实际会使用向量检索或关键词检索在small_content_index中查找
matched_small_docs = []
for doc_id, doc in self.small_content_index.items():
if query.lower() in doc.title.lower() or query.lower() in doc.summary.lower():
matched_small_docs.append(doc)
# 简单排序,实际会根据相关性分数
matched_small_docs = sorted(matched_small_docs, key=lambda x: x.title.count(query) + x.summary.count(query), reverse=True)
retrieved_big_contents = []
for doc in matched_small_docs[:top_k_small]:
print(f" Found small content (ID: {doc.doc_id}, Title: {doc.title}). Fetching big content...")
retrieved_big_contents.append(self.big_content_store[doc.doc_id])
return retrieved_big_contents
# 示例使用
if __name__ == "__main__":
indexer = SmallToBigIndexer()
doc1 = Document(
doc_id="paper_transformer",
title="Transformer模型在机器翻译中的应用",
summary="本文介绍了Transformer模型在机器翻译任务中的应用,并提出了改进的注意力机制。",
full_content="... (这里是Transformer论文的完整内容,包含详细的实验和结果) ..."
)
doc2 = Document(
doc_id="paper_bert",
title="BERT:基于Transformer的预训练模型",
summary="BERT是基于Transformer的预训练模型,在多项NLP任务中取得了显著效果。",
full_content="... (这里是BERT论文的完整内容,包含模型架构和性能分析) ..."
)
doc3 = Document(
doc_id="article_rag_overview",
title="RAG技术概述与发展",
summary="本文概述了检索增强生成(RAG)技术的基本原理、发展历程及其在问答系统中的应用。",
full_content="... (这里是RAG概述文章的完整内容) ..."
)
indexer.add_document(doc1)
indexer.add_document(doc2)
indexer.add_document(doc3)
query = "Transformer模型"
retrieved_contents = indexer.retrieve(query, top_k_small=2)
print("\n--- 检索到的大规模内容 ---")
for i, content in enumerate(retrieved_contents):
print(f"Content {i+1}: {content[:100]}...")
3.5 Small-to-Big索引核心知识点总结
面试核心考点回顾:
- 核心逻辑:索引小块(Sentence/Chunk),召回大块(Parent Doc/Window)。
- 解决痛点:解决了向量检索中"块太小信息碎、块太大向量表达模糊"的矛盾。
- 常见实现:Parent Document Retrieval(父文档检索)和 Windowed Sentence Retrieval(窗口句子检索)。
四、拓展方案:让RAG召回能力"更上一层楼"
除了上述经典方法,RAG的召回技术还在不断演进。这里我们再介绍几个"黑科技",让你的RAG系统真正拥有"超能力"!
4.1 拓展方案一:HyDE (Hypothetical Document Embedding)------"脑补"式召回

4.1.1 专业解释
HyDE(Hypothetical Document Embedding)是一种创新的召回策略,它不直接使用用户查询进行向量检索,而是首先利用大型语言模型(LLM)根据用户查询生成一个"假设性文档"(Hypothetical Document),这个假设性文档是LLM对用户查询的潜在答案或相关内容的"脑补"。然后,系统对这个假设性文档进行嵌入,并使用其向量来检索与假设性文档相似的真实文档。这种方法能够有效弥补短查询语义信息不足的问题,因为LLM生成的假设性文档通常包含更丰富、更准确的语义上下文。
4.1.2 大白话解读
你问一个朋友:"最近有什么好吃的?"(短查询)朋友可能不知道你想吃什么菜系、什么口味。但如果这个朋友很聪明,他会先"脑补"一下:"嗯,他可能想吃火锅、烧烤或者日料。"然后他会根据这些"脑补"出来的答案,去回忆他吃过的哪些店符合这些特点,再推荐给你。HyDE就是RAG系统里的这个"脑补"朋友,它先自己生成一个"标准答案",再用这个"标准答案"去数据库里找最匹配的资料。
4.1.3 示例Python代码:HyDE召回模拟
python
from typing import List
class Document:
def __init__(self, content: str, doc_id: str):
self.content = content
self.doc_id = doc_id
class VectorDatabase:
def __init__(self):
self.documents: List[Document] = []
self.embeddings: Dict[str, List[float]] = {}
def add_document(self, doc: Document, embedding: List[float]):
self.documents.append(doc)
self.embeddings[doc.doc_id] = embedding
def search(self, query_embedding: List[float], top_k: int = 3) -> List[Document]:
# 模拟向量相似度搜索
scores = []
for doc_id, emb in self.embeddings.items():
# 简单模拟余弦相似度
score = sum(q * d for q, d in zip(query_embedding, emb)) # 假设嵌入是单位向量
scores.append((score, doc_id))
scores.sort(key=lambda x: x[0], reverse=True)
results = []
for _, doc_id in scores[:top_k]:
for doc in self.documents:
if doc.doc_id == doc_id:
results.append(doc)
break
return results
class HyDERetriever:
def __init__(self, llm_model_name: str = "gpt-3.5-turbo", embedding_model_name: str = "text-embedding-ada-002"):
print(f"Initializing HyDERetriever with LLM: {llm_model_name} and Embedding Model: {embedding_model_name}...")
self.llm_model_name = llm_model_name
self.embedding_model_name = embedding_model_name
# 实际项目中会调用LLM和Embedding API
print("HyDERetriever initialized (simulated LLM and Embedding calls).")
def generate_hypothetical_document(self, query: str) -> str:
print(f"Generating hypothetical document for query: '{query}'...")
# 模拟LLM生成假设性文档
hypothetical_doc = f"根据查询'{query}',一个可能的答案或相关内容是:... (此处应由LLM生成详细内容) ..."
return hypothetical_doc
def get_embedding(self, text: str) -> List[float]:
print(f"Generating embedding for text (first 30 chars): '{text[:30]}'...")
# 模拟嵌入模型生成向量
# 实际会调用嵌入模型API,这里返回一个随机向量作为模拟
import random
return [random.random() for _ in range(128)] # 假设嵌入维度是128
def retrieve(self, query: str, vector_db: VectorDatabase, top_k: int = 3) -> List[Document]:
hypothetical_doc = self.generate_hypothetical_document(query)
hypothetical_embedding = self.get_embedding(hypothetical_doc)
return vector_db.search(hypothetical_embedding, top_k)
# 示例使用
if __name__ == "__main__":
vector_db = VectorDatabase()
vector_db.add_document(Document("关于人工智能在医疗领域的应用前景和挑战", "doc_ai_medical"), [0.1, 0.2, 0.3, 0.4]) # 模拟嵌入
vector_db.add_document(Document("深度学习模型在图像识别中的最新进展", "doc_dl_image"), [0.5, 0.6, 0.7, 0.8])
vector_db.add_document(Document("自然语言处理中的Transformer架构详解", "doc_nlp_transformer"), [0.9, 0.8, 0.7, 0.6])
vector_db.add_document(Document("AI伦理与社会影响的探讨", "doc_ai_ethics"), [0.2, 0.3, 0.4, 0.5])
hyde_retriever = HyDERetriever()
user_query = "AI在医疗方面有什么应用?"
retrieved_docs = hyde_retriever.retrieve(user_query, vector_db, top_k=2)
print("\n--- HyDE 召回结果 ---")
for i, doc in enumerate(retrieved_docs):
print(f"Top {i+1} (ID: {doc.doc_id}): {doc.content[:50]}...")
4.2 拓展方案二:Multi-Vector Retrieval------"多角度"看文档

4.2.1 专业解释
传统的向量检索通常将整个文档或其摘要表示为一个单一的向量。然而,长文档往往包含多个主题或不同的信息片段,一个单一的向量很难全面捕捉其所有语义。Multi-Vector Retrieval(多向量检索)通过为文档的不同部分(如标题、摘要、每个段落、关键实体等)生成独立的向量表示,从而实现从多个角度对文档进行检索。当用户查询时,系统会尝试匹配查询向量与文档的多个子向量,只要文档的任何一个部分与查询相关,该文档就有机会被召回,极大地提高了召回的灵活性和覆盖面。
4.2.2 大白话解读
一本教科书,如果只用一个词"物理"来代表它,那太笼统了。Multi-Vector Retrieval就像给这本书的每一章、每一节都贴上不同的标签:"牛顿定律"、"量子力学"、"相对论"等等。当你搜索"牛顿定律"时,即使这本书的整体向量可能不那么突出,但因为"牛顿定律"这个标签被匹配到了,这本书依然会被召回。这就像你找书时,不仅看书名,还会看目录、看章节标题,甚至看每段的关键词,确保不会错过任何一本好书。
4.2.3 示例Python代码:Multi-Vector Retrieval模拟
python
from typing import List, Dict
class DocumentChunk:
def __init__(self, content: str, chunk_id: str, parent_doc_id: str):
self.content = content
self.chunk_id = chunk_id
self.parent_doc_id = parent_doc_id
class MultiVectorDatabase:
def __init__(self):
self.chunks: List[DocumentChunk] = []
self.chunk_embeddings: Dict[str, List[float]] = {}
self.parent_documents: Dict[str, str] = {}
def add_document_with_chunks(self, doc_id: str, full_content: str, chunks: List[str]):
self.parent_documents[doc_id] = full_content
for i, chunk_content in enumerate(chunks):
chunk_id = f"{doc_id}_chunk_{i}"
chunk = DocumentChunk(chunk_content, chunk_id, doc_id)
self.chunks.append(chunk)
# 模拟生成嵌入
import random
self.chunk_embeddings[chunk_id] = [random.random() for _ in range(128)]
def get_embedding(self, text: str) -> List[float]:
# 模拟嵌入模型生成向量
import random
return [random.random() for _ in range(128)]
def search(self, query: str, top_k: int = 3) -> List[str]: # 返回父文档ID
query_embedding = self.get_embedding(query)
candidate_doc_ids = set()
chunk_scores = []
for chunk in self.chunks:
chunk_embedding = self.chunk_embeddings[chunk.chunk_id]
# 模拟余弦相似度
score = sum(q * d for q, d in zip(query_embedding, chunk_embedding))
chunk_scores.append((score, chunk.parent_doc_id))
# 对所有chunk的分数进行排序,并聚合父文档ID
chunk_scores.sort(key=lambda x: x[0], reverse=True)
retrieved_parent_docs = []
for score, parent_doc_id in chunk_scores:
if parent_doc_id not in candidate_doc_ids:
candidate_doc_ids.add(parent_doc_id)
retrieved_parent_docs.append(self.parent_documents[parent_doc_id])
if len(retrieved_parent_docs) >= top_k:
break
return retrieved_parent_docs
# 示例使用
if __name__ == "__main__":
mv_db = MultiVectorDatabase()
doc_content_1 = "人工智能的未来发展趋势包括通用人工智能、AI伦理和可解释性AI。通用人工智能旨在实现与人类智能相当或超越人类智能的AI系统。AI伦理关注AI技术带来的社会、道德和法律问题。可解释性AI则致力于让AI决策过程透明化。"
chunks_1 = [
"人工智能的未来发展趋势包括通用人工智能、AI伦理和可解释性AI。",
"通用人工智能旨在实现与人类智能相当或超越人类智能的AI系统。",
"AI伦理关注AI技术带来的社会、道德和法律问题。",
"可解释性AI则致力于让AI决策过程透明化。"
]
mv_db.add_document_with_chunks("doc_ai_future", doc_content_1, chunks_1)
doc_content_2 = "机器学习是人工智能的一个分支,专注于让计算机从数据中学习。常见的机器学习算法包括监督学习、无监督学习和强化学习。监督学习通过带标签数据进行训练,如分类和回归。无监督学习处理无标签数据,如聚类。强化学习则通过与环境互动学习最优策略。"
chunks_2 = [
"机器学习是人工智能的一个分支,专注于让计算机从数据中学习。",
"常见的机器学习算法包括监督学习、无监督学习和强化学习。",
"监督学习通过带标签数据进行训练,如分类和回归。",
"无监督学习处理无标签数据,如聚类。",
"强化学习则通过与环境互动学习最优策略。"
]
mv_db.add_document_with_chunks("doc_ml_intro", doc_content_2, chunks_2)
user_query = "可解释性AI"
retrieved_docs = mv_db.search(user_query, top_k=1)
print("\n--- Multi-Vector Retrieval 召回结果 (父文档内容) ---")
for i, content in enumerate(retrieved_docs):
print(f"Top {i+1}: {content[:100]}...")
4.3 拓展方案三:Graph-based Retrieval------"关系网"式召回

4.3.1 专业解释
Graph-based Retrieval(基于图的检索)利用知识图谱(Knowledge Graph, KG)来增强RAG系统的召回能力。知识图谱以实体(Entity)和关系(Relation)的形式组织知识,能够清晰地表达实体之间的复杂语义关联。在召回阶段,系统可以根据用户查询,在知识图谱中进行路径查找、关系推理,从而不仅召回与查询直接相关的文档,还能召回通过知识图谱中隐含关系连接的、间接相关的文档或信息片段。这种方法特别适用于需要深层语义理解和多跳推理的复杂查询。
4.3.2 大白话解读
你问:"爱因斯坦的导师是谁?"如果只是简单的文本匹配,可能只会找到提到"爱因斯坦"的文档。但如果有一个"知识图谱",它知道"爱因斯坦"有一个"导师"关系指向"赫尔曼·闵可夫斯基",而"赫尔曼·闵可夫斯基"又有一篇关于"狭义相对论"的论文。那么,当你问"爱因斯坦的导师对狭义相对论有什么贡献?"时,知识图谱就能通过"爱因斯坦 -> 导师 -> 闵可夫斯基 -> 论文 -> 狭义相对论"这条"关系网",帮你找到最准确的答案。它就像一个"关系户",能帮你找到那些"七拐八弯"也能找到的有用信息。
4.3.3 示例Python代码:Graph-based Retrieval模拟
python
from typing import List, Dict, Tuple
class KnowledgeGraph:
def __init__(self):
# 存储实体和关系:{entity_id: {relation_type: [target_entity_id, ...]}}
self.graph: Dict[str, Dict[str, List[str]]] = {}
# 存储实体到文档的映射:{entity_id: [doc_id, ...]}
self.entity_to_docs: Dict[str, List[str]] = {}
# 存储文档内容:{doc_id: content}
self.document_store: Dict[str, str] = {}
def add_entity(self, entity_id: str):
if entity_id not in self.graph:
self.graph[entity_id] = {}
def add_relation(self, head_entity: str, relation: str, tail_entity: str):
self.add_entity(head_entity)
self.add_entity(tail_entity)
if relation not in self.graph[head_entity]:
self.graph[head_entity][relation] = []
self.graph[head_entity][relation].append(tail_entity)
def add_document(self, doc_id: str, content: str, entities: List[str]):
self.document_store[doc_id] = content
for entity in entities:
self.add_entity(entity) # 确保实体存在于图中
if entity not in self.entity_to_docs:
self.entity_to_docs[entity] = []
self.entity_to_docs[entity].append(doc_id)
def find_related_entities(self, start_entity: str, max_hops: int = 2) -> List[str]:
visited = set()
queue = [(start_entity, 0)]
related_entities = []
while queue:
current_entity, hops = queue.pop(0)
if current_entity in visited: # 避免循环
continue
visited.add(current_entity)
related_entities.append(current_entity)
if hops < max_hops and current_entity in self.graph:
for relation_type, targets in self.graph[current_entity].items():
for target_entity in targets:
if target_entity not in visited:
queue.append((target_entity, hops + 1))
return related_entities
def retrieve(self, query_entities: List[str], max_hops: int = 1, top_k: int = 3) -> List[str]: # 返回文档内容
print(f"Retrieving documents for query entities: {query_entities} using Graph-based Retrieval...")
candidate_doc_ids = set()
for entity in query_entities:
related_entities = self.find_related_entities(entity, max_hops)
for rel_entity in related_entities:
if rel_entity in self.entity_to_docs:
for doc_id in self.entity_to_docs[rel_entity]:
candidate_doc_ids.add(doc_id)
retrieved_contents = []
for doc_id in list(candidate_doc_ids)[:top_k]: # 简单取前k个
retrieved_contents.append(self.document_store[doc_id])
return retrieved_contents
# 示例使用
if __name__ == "__main__":
kg = KnowledgeGraph()
# 添加实体和关系
kg.add_relation("爱因斯坦", "导师", "赫尔曼·闵可夫斯基")
kg.add_relation("赫尔曼·闵可夫斯基", "贡献", "狭义相对论")
kg.add_relation("爱因斯坦", "提出", "相对论")
kg.add_relation("相对论", "包含", "狭义相对论")
kg.add_relation("相对论", "包含", "广义相对论")
# 添加文档并关联实体
kg.add_document("doc_einstein_bio", "爱因斯坦生平简介,提到了他的教育背景和早期研究。", ["爱因斯坦"])
kg.add_document("doc_minkowski_math", "赫尔曼·闵可夫斯基在数学和物理学上的贡献,特别是闵可夫斯基空间。", ["赫尔曼·闵可夫斯基", "狭义相对论"])
kg.add_document("doc_relativity_theory", "详细解释了爱因斯坦的狭义相对论和广义相对论。", ["爱因斯坦", "相对论", "狭义相对论", "广义相对论"])
query_entities = ["爱因斯坦", "狭义相对论"]
retrieved_docs = kg.retrieve(query_entities, max_hops=2, top_k=2)
print("\n--- Graph-based Retrieval 召回结果 (文档内容) ---")
for i, content in enumerate(retrieved_docs):
print(f"Top {i+1}: {content[:100]}...")
五、总结与展望:RAG召回的"未来已来"
高效召回是RAG系统性能的基石。从Rerank模型的精细化排序,到双向改写的语义增强,再到Small-to-Big索引的分层处理,以及HyDE、Multi-Vector和Graph-based等前沿策略,我们看到了RAG召回技术在不断进化,力求在海量信息中实现"精准打击"。
未来,RAG召回将更加注重多模态信息的融合、实时性与个性化推荐,以及与Agentic AI的深度结合,让RAG系统不仅能"召"得准,还能"想"得深、"用"得活。各位RAG玩家们,准备好迎接这场召回技术的"军备竞赛"了吗?
六、互动环节:你的"召回秘籍"是什么?
读到这里,你是不是对RAG的高效召回有了更深的理解呢?在你的实际项目中,遇到过哪些召回难题?又是如何解决的?你有没有独家的"召回秘籍"想要分享?或者对文章中的某个技术点有不同的看法?
欢迎在评论区留言,和大家一起交流讨论!你的每一个点赞、收藏和评论,都是对我最大的支持!
转载声明
本文为原创文章,转载请注明出处。
七、Mermaid图表示例:RAG召回流程
为了更好地理解RAG召回的整体流程,我们用Mermaid流程图来直观展示:
是
否
是
否
用户查询
查询改写/扩展?
生成多个查询/假设文档
原始查询
向量检索/关键词检索
粗排召回结果
需要Rerank?
Rerank模型精排
最终召回结果
LLM生成答案