
检索增强生成(RAG)已成为将大型语言模型的专业知识、实时性与事实准确性相结合的经典架构。其核心思想直白而有力:当用户提问时,首先从一个庞大的知识库(如公司文档、技术手册、最新新闻等)中检索出最相关的信息片段,然后将这些片段与用户问题一同交给大模型,指令其基于所提供的上下文进行回答。这完美解决了大模型的幻觉问题、知识陈旧和无法溯源等痛点。
然而,一个RAG系统的性能高度依赖于一个简单却残酷的准则:“垃圾进,垃圾出”(Garbage in, Garbage out)。如果我们提供给大模型的上下文材料本身就是不相关、不准确或不完整的,那么无论后续的生成模型多么强大,它都难以产生高质量的回答,甚至可能因为错误上下文而产生更危险的幻觉。
因此,召回(Retrieval) 阶段,即从知识库中精准找出相关文档的过程,成为了整个RAG系统的基石与核心瓶颈。高效召回的目标是在毫秒级的时间内,从可能包含数百万条文档的知识库中,找到真正能回答用户问题的那些黄金片段。

通俗的理解,现在市中心发生了一起珠宝失窃案,来了一个超级侦探,非常聪明,上知天文下知地理。但凡事都有规矩,侦探破案必须基于案卷库里的证据,不能靠自己瞎猜。现在,来了个初级助手帮着一起来找案卷,侦探问助手:“昨天的失窃案,有什么线索”,助手跑去巨大的档案室,根据“盗窃”、“珠宝”、“市中心”这几个关键词,抱回来三本厚厚的、相关的案卷,于是侦探开始阅读这些案卷,试图找出答案。但案卷太厚了!里面可能包含了“去年城东的失窃案”、“珠宝保养手册”、“市中心城市规划”等各种无关信息。侦探也要花大量时间从头读到尾,才能找到一点点真正有用的线索。效率极低,而且很容易被无关信息干扰,导致破案方向错误。
在这个故事中,超级侦探好比是大语言模型,破案就是回答问题,而案卷库就是知识库,查案卷就好比大模型回答问题必须基于知识库,助手就是初级的RAG系统,档案室就是向量数据库,总结就是初级的RAG系统接收到问题后去向量数据库中检索上下文内容,结果取回了与案卷本身关联度不高的卷宗,导致信息匹配度低,没有得到想要的效果,对破案起不到决定性的作用,助手白忙活了一场,RAG系统也并没有吹嘘的那么神奇高效。
至此毫无悬念的引出了高效召回,就是给侦探换一个超级聪明的得力助手。 这个新助手不会傻乎乎地抱回整本案卷,而是会用各种高级方法来找到最精炼、最相关的信息,从而达到高准确度、事半功倍的效果。
此时相比应该都基本理解了高效召回的本质原因了,RAG系统的性能严重依赖于召回阶段的质量,核心问题是如果检索到的文档片段不包含回答问题所需的信息,那么再强大的大模型也无法生成高质量的答案,这就是开篇就提到的所谓的“垃圾进,垃圾出”。
同时,初级的RAG系统召回也会遇到很多问题和瓶颈:
因此,“高效召回”的核心目标就是:打破这些瓶颈,确保检索系统能够精准、全面地将最相关的信息传递给大模型,为生成高质量答案奠定坚实基础。
下面我们详细解析三种方法的概念、差异和实现逻辑。
在标准的RAG流程中,我们通常将文档切分成大小均匀的片段(chunks),然后为每个片段创建向量嵌入(embeddings)。检索时,将用户查询也转换为向量,并通过向量数据库找到与查询最相关的几个片段,最后将这些片段连同查询一起喂给大模型生成答案。
这是一种“分而治之”的策略。它在索引阶段创建两种颗粒度的文本块,主要在于块大小的权衡:
关键机制是建立从小块到其源大块的映射关系,它的精髓就在于:它巧妙地规避了这个权衡,做到了鱼和熊掌兼得。
小检索:
大投喂:
流程总结:查询 -> 用查询向量检索最相关的小块 -> 通过映射找到这些小块对应的大块 -> 将大块去重后作为上下文发送给大模型生成答案
这种方法在以下场景中尤其有效:
import requests
import json
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_community.vectorstores import FAISS
from langchain_community.embeddings import HuggingFaceEmbeddings
from langchain.schema import Document
import warnings
warnings.filterwarnings('ignore')
import os
# 1. 文档加载和预处理
fake_document_text = """
机器学习是人工智能的一个子领域,它使计算机系统能够从数据中学习并改进,而无需显式编程。
机器学习算法通常分为三类:监督学习、无监督学习和强化学习。
监督学习使用标记数据来训练模型,例如用于图像分类。无监督学习在未标记数据中寻找隐藏模式,例如客户细分。强化学习则通过与环境交互并获得奖励来学习最佳策略,例如AlphaGo。
深度学习是机器学习的一个分支,它使用称为神经网络的多层模型。这些网络能够从大量数据中学习复杂的特征层次结构。
卷积神经网络(CNN)特别适用于图像处理任务,而循环神经网络(RNN)则擅长处理序列数据,如文本或时间序列。
"""
documents = [Document(page_content=fake_document_text, metadata={"source": "ml_textbook_chapter1"})]
# 2. 定义文本分割器
# 创建"大"块的分割器
big_size = 300
big_overlap = 50
big_splitter = RecursiveCharacterTextSplitter(
chunk_size=big_size,
chunk_overlap=big_overlap,
)
# 创建"小"块的分割器
small_size = 100
small_overlap = 20
small_splitter = RecursiveCharacterTextSplitter(
chunk_size=small_size,
chunk_overlap=small_overlap,
)
# 3. 切分文档并建立映射关系
all_small_chunks = []
all_big_chunks = []
mapping_dict = {} # 用于存储小块ID到父大块的映射
# 首先,将文档切分成"大"块
big_chunks = big_splitter.split_documents(documents)
for big_chunk_index, big_chunk in enumerate(big_chunks):
# 将每个"大"块进一步切分成"小"块
small_chunks_from_big = small_splitter.split_documents([big_chunk])
# 为每个"小"块创建唯一ID并存储映射关系
for small_chunk in small_chunks_from_big:
# 给小块一个ID(这里用内容哈希简化演示)
small_chunk_id = hash(small_chunk.page_content)
mapping_dict[small_chunk_id] = {
"big_chunk_content": big_chunk.page_content,
"big_chunk_index": big_chunk_index
}
all_small_chunks.append(small_chunk)
all_big_chunks.append(big_chunk)
print(f"切分出 {len(all_big_chunks)} 个大块")
print(f"切分出 {len(all_small_chunks)} 个小块")
# 4. 为"小"块创建向量库(Faiss)
# 选择嵌入模型
model_name = "sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2"
embeddings = HuggingFaceEmbeddings(model_name=model_name)
# 使用所有"小"块构建向量索引
vector_db = FAISS.from_documents(all_small_chunks, embeddings)
# 5. 定义QWen API调用函数
def call_qwen_api(prompt, api_key, model="qwen-max", temperature=0.1):
"""
调用通义千问API
参数:
prompt: 输入的提示文本
api_key: 你的API密钥
model: 使用的模型名称,默认为qwen-max
temperature: 生成温度,控制创造性
"""
url = "https://dashscope.aliyuncs.com/api/v1/services/aigc/text-generation/generation"
headers = {
"Content-Type": "application/json",
"Authorization": f"Bearer {api_key}"
}
data = {
"model": model,
"input": {
"messages": [
{
"role": "user",
"content": prompt
}
]
},
"parameters": {
"temperature": temperature,
"top_p": 0.8,
"result_format": "text"
}
}
try:
response = requests.post(url, headers=headers, data=json.dumps(data))
response.raise_for_status()
result = response.json()
return result["output"]["text"]
except Exception as e:
print(f"API调用出错: {e}")
return None
# 6. 检索和生成过程
def rag_query(query, api_key, k=3):
# a) 使用查询检索最相关的"小"块
retrieved_small_docs = vector_db.similarity_search(query, k=k)
print("\n--- 检索到的最相关'小'块 ---")
for i, doc in enumerate(retrieved_small_docs):
print(f"[Small Chunk {i+1}]: {doc.page_content}\n")
# b) 根据映射字典,找到这些"小"块对应的父"大"块
retrieved_big_contents = set() # 使用集合自动去重
for small_doc in retrieved_small_docs:
small_id = hash(small_doc.page_content)
if small_id in mapping_dict:
retrieved_big_contents.add(mapping_dict[small_id]["big_chunk_content"])
else:
# 如果找不到映射,使用小块本身
retrieved_big_contents.add(small_doc.page_content)
# 将去重后的大块内容合并为上下文
context = "\n\n---\n\n".join(retrieved_big_contents)
# c) 构建Prompt,调用QWen API生成答案
prompt_template = f"""
请根据以下上下文信息回答问题。如果上下文不包含答案,请如实告知。
上下文:
{context}
问题:{query}
请给出准确、简洁的回答:
"""
print("\n--- 发送给QWen API的Prompt ---")
print(prompt_template)
# 调用API
answer = call_qwen_api(prompt_template, api_key)
return answer
# 7. 使用示例
if __name__ == "__main__":
# 替换为你的API密钥
API_KEY = os.environ.get("DASHSCOPE_API_KEY", "")
# 查询示例
query = "CNN神经网络主要用于什么任务?"
# 执行RAG查询
result = rag_query(query, API_KEY)
print("\n--- 最终答案 ---")
print(result)切分出 1 个大块
切分出 4 个小块
--- 检索到的最相关'小'块 ---
[Small Chunk 1]: 卷积神经网络(CNN)特别适用于图像处理任务,而循环神经网络(RNN)则擅长处理序列数据,如文本或
时间序列。
[Small Chunk 2]: 深度学习是机器学习的一个分支,它使用称为神经网络的多层模型。这些网络能够从大量数据中学习复杂
的特征层次结构。
[Small Chunk 3]: 机器学习是人工智能的一个子领域,它使计算机系统能够从数据中学习并改进,而无需显式编程。
机器学习算法通常分为三类:监督学习、无监督学习和强化学习。
--- 发送给QWen API的Prompt ---
请根据以下上下文信息回答问题。如果上下文不包含答案,请如实告知。
上下文:
机器学习是人工智能的一个子领域,它使计算机系统能够从数据中学习并改进,而无需显式编程。
机器学习算法通常分为三类:监督学习、无监督学习和强化学习。
监督学习使用标记数据来训练模型,例如用于图像分类。无监督学习在未标记数据中寻找隐藏模式,例如客户细分。强化学习
则通过与环境交互并获得奖励来学习最佳策略,例如AlphaGo。
深度学习是机器学习的一个分支,它使用称为神经网络的多层模型。这些网络能够从大量数据中学习复杂的特征层次结构。
卷积神经网络(CNN)特别适用于图像处理任务,而循环神经网络(RNN)则擅长处理序列数据,如文本或时间序列。
深度学习是机器学习的一个分支,它使用称为神经网络的多层模型。这些网络能够从大量数据中学习复杂的特征层次结构。
深度学习是机器学习的一个分支,它使用称为神经网络的多层模型。这些网络能够从大量数据中学习复杂的特征层次结构。
卷积神经网络(CNN)特别适用于图像处理任务,而循环神经网络(RNN)则擅长处理序列数据,如文本或时间序列。
问题:CNN神经网络主要用于什么任务?
请给出准确、简洁的回答:
--- 最终答案 ---
CNN神经网络主要用于图像处理任务。import requests # 用于发送HTTP请求到QWen API
import json # 用于处理JSON数据
from langchain.text_splitter import RecursiveCharacterTextSplitter # 文本分割工具
from langchain_community.vectorstores import FAISS # 向量数据库
from langchain_community.embeddings import HuggingFaceEmbeddings # 文本嵌入模型
from langchain.schema import Document # 文档数据结构
import warnings # 警告管理
warnings.filterwarnings('ignore') # 忽略警告
import os # 操作系统接口,用于读取环境变量fake_document_text = """机器学习是人工智能的一个子领域...""" # 示例文档内容
documents = [Document(page_content=fake_document_text, metadata={"source": "ml_textbook_chapter1"})]这里创建了一个包含机器学习相关内容的示例文档,并将其包装成 LangChain 的 Document 对象,附带元数据标识来源。
# 创建"大"块的分割器 (用于生成上下文)
big_splitter = RecursiveCharacterTextSplitter(chunk_size=300, chunk_overlap=50)
# 创建"小"块的分割器 (用于检索)
small_splitter = RecursiveCharacterTextSplitter(chunk_size=100, chunk_overlap=20)这里定义了两个不同尺寸的文本分割器: 大块分割器 (300字符,重叠50字符):用于生成富含上下文的文本块 小块分割器 (100字符,重叠20字符):用于精确检索相关文本
# 首先将文档切分成"大"块
big_chunks = big_splitter.split_documents(documents)
# 为每个大块创建对应的小块,并建立映射关系
for big_chunk_index, big_chunk in enumerate(big_chunks):
small_chunks_from_big = small_splitter.split_documents([big_chunk])
for small_chunk in small_chunks_from_big:
small_chunk_id = hash(small_chunk.page_content) # 使用哈希值作为小块ID
mapping_dict[small_chunk_id] = {
"big_chunk_content": big_chunk.page_content,
"big_chunk_index": big_chunk_index
}
all_small_chunks.append(small_chunk)
all_big_chunks.append(big_chunk)这是 Small-to-Big 方法的核心部分:
# 选择嵌入模型
model_name = "sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2"
embeddings = HuggingFaceEmbeddings(model_name=model_name)
# 使用所有"小"块构建向量索引
vector_db = FAISS.from_documents(all_small_chunks, embeddings)这里使用了一个多语言句子嵌入模型,将所有小块转换为向量,并使用 FAISS 构建高效的向量索引,便于快速相似性搜索。
def call_qwen_api(prompt, api_key, model="qwen-max", temperature=0.1):
url = "https://dashscope.aliyuncs.com/api/v1/services/aigc/text-generation/generation"
headers = {
"Content-Type": "application/json",
"Authorization": f"Bearer {api_key}"
}
data = {
"model": model,
"input": {"messages": [{"role": "user", "content": prompt}]},
"parameters": {"temperature": temperature, "top_p": 0.8, "result_format": "text"}
}
try:
response = requests.post(url, headers=headers, data=json.dumps(data))
response.raise_for_status()
result = response.json()
return result["output"]["text"]
except Exception as e:
print(f"API调用出错: {e}")
return None这个函数封装了与通义千问 API 的交互,用于发送提示并获取生成的文本响应。
def rag_query(query, api_key, k=3):
# a) 使用查询检索最相关的"小"块
retrieved_small_docs = vector_db.similarity_search(query, k=k)
# b) 根据映射字典,找到这些"小"块对应的父"大"块
retrieved_big_contents = set() # 使用集合自动去重
for small_doc in retrieved_small_docs:
small_id = hash(small_doc.page_content)
if small_id in mapping_dict:
retrieved_big_contents.add(mapping_dict[small_id]["big_chunk_content"])
else:
retrieved_big_contents.add(small_doc.page_content) # 回退方案
# 将去重后的大块内容合并为上下文
context = "\n\n---\n\n".join(retrieved_big_contents)
# c) 构建Prompt,调用QWen API生成答案
prompt_template = f"""请根据以下上下文信息回答问题..."""
# 调用API
answer = call_qwen_api(prompt_template, api_key)
return answer这是 RAG 查询的核心函数:
if __name__ == "__main__":
API_KEY = os.environ.get("DASHSCOPE_API_KEY", "") # 从环境变量获取API密钥
query = "CNN神经网络主要用于什么任务?" # 用户查询
result = rag_query(query, API_KEY) # 执行RAG查询
print("\n--- 最终答案 ---")
print(result)这部分展示了如何使用整个系统,包括设置 API 密钥、提出查询并获取答案。
在标准的RAG流程中,用户的原始查询被直接用于向量数据库中搜索最相似的文档片段。这种方法简单直接,但当用户的查询表述简短、模糊或与文档中的措辞差异较大时,效果会大打折扣。
索引扩展的核心思想是不直接使用原始查询进行检索,而是先对原始查询进行扩展,生成多个与之相关的、从不同角度或用不同表述的查询,然后用这一组扩展后的查询去向量库中检索,最后合并所有检索结果,剔除重复项,并将最相关的结果返回给大模型进行答案生成。

索引扩展在以下场景中尤为有效:
import numpy as np
import faiss
from sentence_transformers import SentenceTransformer
from typing import List
import dashscope
from dashscope import Generation
import os
# 1. 设置Key(请替换成你的实际API Key)
dashscope.api_key = os.environ.get("DASHSCOPE_API_KEY", "")
# 2. 加载嵌入模型(用于文本转向量)
embed_model = SentenceTransformer('GanymedeNil/text2vec-large-chinese') # 一个优秀的中文嵌入模型
# 3. 假设我们有一个简单的知识库文档(实际应用中应从文件加载)
knowledge_base = [
"牛顿第一定律,又称为惯性定律,指出:任何物体在没有外力作用时,总保持匀速直线运动状态或静止状态。",
"牛顿第二定律指出,物体的加速度与所受合外力成正比,与质量成反比,公式为 F=ma。",
"牛顿第三定律,又称作用与反作用定律,指出两个物体之间的作用力和反作用力总是大小相等,方向相反,作用在同一直线上。",
"爱因斯坦的质能方程是 E=mc²,其中E代表能量,m代表质量,c代表光速。",
"深度学习是机器学习的一个分支,它使用名为深度神经网络的模型。",
]
# 为知识库生成向量并构建Faiss索引
knowledge_vectors = embed_model.encode(knowledge_base)
dimension = knowledge_vectors.shape[1]
index = faiss.IndexFlatL2(dimension)
index.add(knowledge_vectors.astype('float32'))
# 4. 定义HyDE生成函数(使用Qwen)
def generate_hyde_query(original_query: str) -> str:
"""
使用Qwen根据用户问题生成一个假设性的答案。
这个答案可能不准确,但其表述方式更接近知识库中的文本。
"""
prompt = f"""请根据以下问题,生成一个假设性的、详细的答案。即使你不确定正确答案,也请模仿百科知识的风格和语气来写。
问题:{original_query}
假设性答案:"""
response = Generation.call(
model='qwen-max',
prompt=prompt,
seed=12345,
top_p=0.8
)
hyde_text = response.output['text'].strip()
print(f"原始查询: {original_query}")
print(f"HyDE生成: {hyde_text}")
return hyde_text
# 5. 定义检索函数
def retrieve_with_hyde(user_query: str, top_k: int = 3) -> List[str]:
"""
1. 使用HyDE生成假设答案。
2. 将假设答案编码为向量。
3. 用该向量在Faiss中检索最相似的文档。
"""
# 生成HyDE查询
hyde_query = generate_hyde_query(user_query)
# 将HyDE查询编码为向量
query_vector = embed_model.encode([hyde_query])
# 在Faiss中搜索
distances, indices = index.search(query_vector.astype('float32'), top_k)
# 返回检索到的文本
retrieved_docs = [knowledge_base[i] for i in indices[0]]
return retrieved_docs
# 6. 定义最终答案生成函数(使用Qwen)
def generate_final_answer(user_query: str, contexts: List[str]) -> str:
"""
将用户查询和检索到的上下文组合成Prompt,让Qwen生成最终答案。
"""
context_str = "\n".join([f"- {doc}" for doc in contexts])
prompt = f"""请根据以下提供的上下文信息,回答用户的问题。如果上下文信息不包含答案,请直接说你不知道。
上下文信息:
{context_str}
用户问题:{user_query}
请直接给出答案:"""
response = Generation.call(
model='qwen-max',
prompt=prompt,
seed=12345,
top_p=0.8
)
final_answer = response.output['text'].strip()
return final_answer
# 7. 主流程:完整的RAG with HyDE
def rag_with_hyde(user_query: str):
# 第一步:通过HyDE检索相关文档
retrieved_docs = retrieve_with_hyde(user_query)
print("\n检索到的相关文档:")
for i, doc in enumerate(retrieved_docs):
print(f"{i+1}. {doc}")
# 第二步:合成最终答案
final_answer = generate_final_answer(user_query, retrieved_docs)
print(f"\n最终答案:\n{final_answer}")
# 8. 测试
if __name__ == "__main__":
user_question = "牛顿第一定律是什么?"
rag_with_hyde(user_question)No sentence-transformers model found with name GanymedeNil/text2vec-large-chinese. Creating a new one with mean pooling.
原始查询: 牛顿第一定律是什么?
HyDE生成: 牛顿第一定律,也被称为惯性定律,是经典力学中的基础之一。这一定律由艾萨克·牛顿在17世纪提出,并收录于他著名的《自然哲学的数学原理》一书中。牛顿第一定律指出,在没有外力作用的情况下,一个物体将保持其静止状态或匀速直线运动的状态不变。
换句话说,如果一个物体处于静止,则它将继续保持静止;若该物体正在以恒定速度沿直线移动,则它将以相同的速度继续沿同一直线移动,除非受到外部力量的作用。这里的“外力”指的是任何能够改变物体当前运动状态的力量,比如摩擦力、重力等。
牛顿第一定律揭示了自然界中物体运动的基本规律之一——惯性。惯性是指物体抵抗其运动状态变化(即加速或减速)的一种性质。质量越大的物体,其惯性也就越大,因此需要更大的力才能改变它的运动状态。
这条定律不仅对于理解日常生活中物体的行为至关重要,而且也是现代物理学、工程学等多个领域研究的基础。通过牛顿的第一定律,我们可以更好地预测和解释周围世界的物理现象。
检索到的相关文档:
1. 牛顿第一定律,又称为惯性定律,指出:任何物体在没有外力作用时,总保持匀速直线运动状态或静止状态。
2. 牛顿第二定律指出,物体的加速度与所受合外力成正比,与质量成反比,公式为 F=ma。
3. 牛顿第三定律,又称作用与反作用定律,指出两个物体之间的作用力和反作用力总是大小相等,方向相反,作用在同一直线上。
最终答案:
牛顿第一定律,又称为惯性定律,指出:任何物体在没有外力作用时,总保持匀速直线运动状态或静止状态。使用了一个新的词向量模型GanymedeNil/text2vec-large-chinese,运行如果本地没有,则会先进行下载;

传统的RAG召回是直接将用户查询编码成向量,然后去向量数据库中搜索最相似的文档向量。但问题在于,用户的查询通常很短、很口语化,和通常很长、很正式文档中的语言在表达方式上存在巨大差异,这会导致即使语义相关,向量相似度也不高,从而召回失败。这种方法的核心思想是通过改写来弥合用户查询(Query)和文档(Document)之间的“语义鸿沟”,从而在向量空间中进行更精准的匹配。
查询 -> 文档改写 (Query2Doc):
文档 -> 查询改写 (Doc2Query):
双向:指的是这两种方法分别从查询端和文档端相向而行,共同改善召回效果。
import numpy as np
import faiss
from sentence_transformers import SentenceTransformer
import requests
import json
import os
# 初始化嵌入模型
embedding_model = SentenceTransformer('BAAI/bge-small-zh-v1.5')
# Qwen API配置
QWEN_API_KEY = os.environ.get("DASHSCOPE_API_KEY", "") # 替换为您的实际API密钥
QWEN_API_URL = "https://dashscope.aliyuncs.com/api/v1/services/aigc/text-generation/generation"
def call_qwen(prompt):
"""调用Qwen API"""
headers = {
"Content-Type": "application/json",
"Authorization": f"Bearer {QWEN_API_KEY}"
}
payload = {
"model": "qwen-turbo",
"input": {
"messages": [
{
"role": "user",
"content": prompt
}
]
},
"parameters": {
"temperature": 0.7
}
}
try:
response = requests.post(QWEN_API_URL, headers=headers, json=payload)
response.raise_for_status()
result = response.json()
return result['output']['text']
except Exception as e:
print(f"API调用失败: {e}")
return None
# 1. 准备文档数据
documents = [
"员工报销需要提供发票和审批单,15个工作日内完成报销。",
"请假需提前在OA系统申请,紧急情况可事后补办手续。",
"密码必须包含字母、数字和特殊字符,且长度至少8位。",
"新产品发布流程包括需求评审、设计、开发、测试和发布五个阶段。"
]
# 2. 生成查询问题 (Doc2Query)
print("为文档生成查询问题...")
doc_queries = []
for doc in documents:
prompt = f"请为以下文本生成3个用户可能会提出的问题:\n\n文本: {doc}\n\n生成的问题:"
response = call_qwen(prompt)
if response:
queries = [q.strip() for q in response.split('\n') if q.strip()]
doc_queries.append(queries[:3])
print(f"文档: {doc[:20]}...")
print(f"生成的问题: {queries[:3]}")
else:
# 如果API调用失败,使用简单的问题
doc_queries.append([f"关于{doc[:10]}...", f"如何{doc[:10]}...", f"{doc[:10]}有什么要求..."])
print()
# 3. 创建FAISS索引
# 合并文档和生成的问题
all_texts = documents.copy()
for queries in doc_queries:
all_texts.extend(queries)
# 生成嵌入向量
embeddings = embedding_model.encode(all_texts)
# 创建FAISS索引
dimension = embeddings.shape[1]
index = faiss.IndexFlatL2(dimension)
index.add(np.array(embeddings).astype('float32'))
# 4. 查询改写函数 (Query2Doc)
def rewrite_query(query):
prompt = f"请根据以下问题生成一段详细的答案文档:\n\n问题: {query}\n\n生成的答案文档:"
response = call_qwen(prompt)
return response if response else query
# 5. 检索函数
def search(query):
print(f"原始查询: {query}")
# 策略1: 直接检索
query_embedding = embedding_model.encode([query])
distances, indices = index.search(np.array(query_embedding).astype('float32'), 3)
print("直接检索结果:")
for i, idx in enumerate(indices[0]):
if idx < len(all_texts):
print(f" {i+1}. {all_texts[idx]}")
# 策略2: 查询改写后检索
expanded_query = rewrite_query(query)
if expanded_query != query:
print(f"改写后的查询: {expanded_query}")
expanded_embedding = embedding_model.encode([expanded_query])
distances, indices = index.search(np.array(expanded_embedding).astype('float32'), 3)
print("改写后检索结果:")
for i, idx in enumerate(indices[0]):
if idx < len(all_texts):
print(f" {i+1}. {all_texts[idx]}")
print("-" * 50)
# 6. 测试查询
queries = ["怎么报销", "如何请假", "密码要求", "发布流程"]
for query in queries:
search(query)为文档生成查询问题...
文档: 员工报销需要提供发票和审批单,15个工作...
生成的问题: ['1. 员工报销需要哪些必备的材料?', '2. 报销流程需要多长时间?', '3. 如果超过15个工作日还没收到报
销款怎么办?']
文档: 请假需提前在OA系统申请,紧急情况可事后...
生成的问题: ['1. 请假必须提前在OA系统申请吗?', '2. 如果有紧急情况,是否可以先请假再补办手续?', '3. 事后补办
请假手续需要哪些流程?']
文档: 密码必须包含字母、数字和特殊字符,且长度...
生成的问题: ['1. 密码需要满足哪些要求?', '2. 特殊字符包括哪些类型?', '3. 如果密码只有7位,是否符合要求?']
文档: 新产品发布流程包括需求评审、设计、开发、...
生成的问题: ['1. 新产品发布流程有哪些主要阶段?', '2. 需求评审在新产品发布中起到什么作用?', '3. 测试阶段在新
产品发布流程中的重要性是什么?']
原始查询: 怎么报销
直接检索结果:
1. 2. 报销流程需要多长时间?
2. 3. 如果超过15个工作日还没收到报销款怎么办?
3. 1. 员工报销需要哪些必备的材料?
改写后的查询: **报销流程说明文档**---(中间省略8000字)---**备注:具体执行以公司最新通知为准。**
改写后检索结果:
1. 2. 报销流程需要多长时间?
2. 员工报销需要提供发票和审批单,15个工作日内完成报销。
3. 1. 员工报销需要哪些必备的材料?
--------------------------------------------------
原始查询: 如何请假
直接检索结果:
1. 3. 事后补办请假手续需要哪些流程?
2. 2. 如果有紧急情况,是否可以先请假再补办手续?
3. 1. 请假必须提前在OA系统申请吗?
改写后的查询: **如何请假**---(中间省略8000字)---**备注**:本文档仅供参考,具体请假流程请以所在单位或学校的规定为准。
改写后检索结果:
1. 3. 事后补办请假手续需要哪些流程?
2. 请假需提前在OA系统申请,紧急情况可事后补办手续。
3. 1. 请假必须提前在OA系统申请吗?
--------------------------------------------------
原始查询: 密码要求
直接检索结果:
1. 1. 密码需要满足哪些要求?
2. 密码必须包含字母、数字和特殊字符,且长度至少8位。
3. 3. 如果密码只有7位,是否符合要求?
改写后的查询: **密码要求文档**---(中间省略8000字)---**更新日期:2025年4月5日**
改写后检索结果:
1. 1. 密码需要满足哪些要求?
2. 密码必须包含字母、数字和特殊字符,且长度至少8位。
3. 3. 如果密码只有7位,是否符合要求?
--------------------------------------------------
原始查询: 发布流程
直接检索结果:
1. 1. 新产品发布流程有哪些主要阶段?
2. 新产品发布流程包括需求评审、设计、开发、测试和发布五个阶段。
3. 3. 测试阶段在新产品发布流程中的重要性是什么?
改写后的查询: **发布流程**---(中间省略8000字)---不断优化和迭代发布流程,以适应快速变化的市场需求。
改写后检索结果:
1. 新产品发布流程包括需求评审、设计、开发、测试和发布五个阶段。
2. 1. 新产品发布流程有哪些主要阶段?
3. 3. 测试阶段在新产品发布流程中的重要性是什么?
--------------------------------------------------使用了一个新的词向量模型BAAI/bge-small-zh-v1.5,运行如果本地没有,则会先进行下载;

特性 | Small-to-Big | 索引扩展 (HyDE) | 双向改写 |
|---|---|---|---|
核心思想 | 分治策略:检索用小块,生成用大块 | 引导策略:用假设答案引导检索真实答案 | 桥梁策略:让查询和文档的表述更接近 |
主要处理阶段 | 索引阶段(定义块大小和映射) | 检索阶段(生成假设文档) | 检索阶段(查询改写)或索引阶段(文档增强) |
解决的核心问题 | 块大小的权衡(精度 vs上下文) | 语义/词汇不匹配、查询信息不足 | 词汇不匹配、查询多样性低 |
计算开销 | 索引阶段开销大,检索阶段开销小 | 检索阶段开销大(每次检索需额外调用一次LLM) | 查询扩展:检索开销大;文档增强:索引开销大 |
适用场景 | 长篇、结构化文档 | 短查询、零样本、冷启动 | 搜索系统、开放域问答 |
这三种高效召回方法从不同角度破解了RAG的检索瓶颈:
在实际应用中,这些方法并非互斥,而是可以组合使用的。例如,可以为采用Small-to-Big策略索引的文档,在检索时同时采用HyDE和查询扩展,构建一个极其强大的RAG系统。我们可以根据自己的具体场景、数据特点和性能要求,选择合适的策略组合。
大模型对语言都有难以跨越的鸿沟,我们总结出以下问题,从而更精细的寻找解决办法:
尽管实现高效召回也面临诸多挑战,但厘清了问题的本质,了解其核心思想,防止那个笨助手直接抱着一堆冗长又充满噪声的原始材料给你,而是让他用各种聪明的方法(Small-to-Big, HyDE, 双向改写)先对这些材料进行预处理、精炼和联想,最终只把那些最核心、最相关、质量最高的内容呈到你面前。这样一来,大模型就能更快、更准、更轻松地利用这些内容生成高质量的答案了,这就是高效召回的价值所在。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。