一、前言
本篇文章不讲通俗的Rag理论,目的在于深度解析在Rag实现过程中的技术细节
二、特定情况下为什么选RAG而非微调呢?
1、RAG可实现模型知识库的迅速更新
Rag大体流程为:搭建向量数据库 -> BGE模型编码 -> 存储到向量数据库中如chroma -> 用户提问 -> BGE模型编码 -> 将向量化后的用户问题在向量数据库中进行相似度匹配 -> 返回相似度符合要求的内容 -> 模型根据返回的文档进行回答
刚才已经简单的描述了流程,大家会发现 "搭建向量数据库" 是第一步,而这就是RAG相较于微调而言可以快速更新知识的原因。
先看两段简单的代码:
build_db.py
python
import os
import chromadb
from docx import Document # 读取Word文档
from chromadb.utils import embedding_functions
# ---------------------- 配置 ----------------------
DOCS_FOLDER = "./docs" # Word文档存放的文件夹
CHROMA_PATH = "./chroma_db" # 向量库保存路径
COLLECTION_NAME = "word_rag_demo" # 集合名称
# ---------------------- 初始化Chroma ----------------------
client = chromadb.PersistentClient(path=CHROMA_PATH)
embedding_func = embedding_functions.SentenceTransformerEmbeddingFunction(
model_name="all-MiniLM-L6-v2"
)
# 删除旧集合(重新构建时用,避免重复数据)
try:
client.delete_collection(COLLECTION_NAME)
except:
pass
# 创建新集合
collection = client.create_collection(
name=COLLECTION_NAME,
embedding_function=embedding_func,
metadata={"description": "本地Word文档知识库"}
)
# ---------------------- 读取Word文档函数 ----------------------
def read_docx(file_path):
"""读取docx文件,返回纯文本"""
doc = Document(file_path)
# 提取所有段落文本,拼接
full_text = []
for para in doc.paragraphs:
if para.text.strip(): # 跳过空行
full_text.append(para.text.strip())
return "\n".join(full_text)
# ---------------------- 遍历docs文件夹,读取所有Word ----------------------
documents = [] # 存储文本内容
metadatas = [] # 存储元数据(来源文件名)
ids = [] # 唯一ID
doc_id = 0
# 检查文件夹是否存在
if not os.path.exists(DOCS_FOLDER):
os.makedirs(DOCS_FOLDER)
print(f"⚠️ 已创建 {DOCS_FOLDER} 文件夹,请将Word文档(.docx)放入后重新运行!")
exit()
# 遍历所有docx文件
for filename in os.listdir(DOCS_FOLDER):
if filename.endswith(".docx"):
file_path = os.path.join(DOCS_FOLDER, filename)
print(f"正在读取:{filename}")
# 提取文本
text = read_docx(file_path)
if not text:
continue
# 添加到数据列表
documents.append(text)
metadatas.append({"source": filename}) # 记录来源文件
ids.append(f"doc_{doc_id}")
doc_id += 1
# ---------------------- 存入向量库 ----------------------
if documents:
collection.add(
documents=documents,
metadatas=metadatas,
ids=ids
)
print(f"\n✅ 向量库构建完成!共导入 {len(documents)} 个Word文档")
else:
print(f"\n❌ {DOCS_FOLDER} 文件夹中没有找到.docx文件!")
rag_chat.py
python
import chromadb
import ollama
from chromadb.utils import embedding_functions
# ---------------------- 配置(和build_db保持一致) ----------------------
CHROMA_PATH = "./chroma_db"
COLLECTION_NAME = "word_rag_demo"
OLLAMA_MODEL = "qwen:0.5b" # 你本地Ollama的模型名
# ---------------------- 连接向量库 ----------------------
client = chromadb.PersistentClient(path=CHROMA_PATH)
embedding_func = embedding_functions.SentenceTransformerEmbeddingFunction(
model_name="all-MiniLM-L6-v2"
)
collection = client.get_collection(
name=COLLECTION_NAME,
embedding_function=embedding_func
)
# ---------------------- RAG核心流程 ----------------------
def rag_ask(question):
# 1. 向量检索:从Word库中找最相关的2段文本
results = collection.query(
query_texts=question,
n_results=2 # 可调整,返回更多参考内容
)
# 2. 拼接检索到的上下文
contexts = results["documents"][0]
sources = [meta["source"] for meta in results["metadatas"][0]]
context_str = "\n".join(contexts)
source_str = "、".join(set(sources))
print(f"🔍 参考文档:{source_str}")
print(f"📖 检索到的上下文:\n{context_str}\n")
# 3. 构造Prompt(给大模型的指令)
prompt = f"""
你是一个专业的问答助手,请严格根据下面的参考信息回答问题,不要编造内容。
如果参考信息中没有答案,请回答:"抱歉,文档中没有相关信息"。
参考信息:
{context_str}
用户问题:{question}
回答:
"""
# 4. 调用本地Ollama生成回答(核心!)
response = ollama.generate(
model=OLLAMA_MODEL,
prompt=prompt
)
return response["response"].strip()
# ---------------------- 测试问答 ----------------------
if __name__ == "__main__":
while True:
user_question = input("\n💬 请输入你的问题(输入q退出):")
if user_question.lower() == "q":
break
answer = rag_ask(user_question)
print(f"\n🤖 AI回答:\n{answer}")
在这里许多人会有疑问:"为什么数据库构建和rag检索要分成两个脚本?直接放在一个python文件中,然后在每次运行的时候判断一下是否存在向量数据库不就行了?(判断的目的是避免每次运行导致无效重构向量数据库)"
分成两个,目的就是为了可以实现在不关掉rag检索(rag_chat.py)的前提下通过运行build_db.py来实现模型知识库的更新,也就是为什么说rag相较于微调可以实现知识库迅速更新的原因。
ok,懂计算机原理的朋友会发现一个问题:"上面两个脚本,为了可以实现实时更新向量数据库,可以不可以定时运行build_db.py,那如果在rag_chat.py还在运行的情况下(即向量数据库被占用),那么还可以成功运行build_db.py吗?"
答案是不可以的,因为chromadb.PersistentClient(path="./chroma_db"),这是直接读写本地文件 的模式,底层是 SQLite 数据库,当 rag_chat.py 正在运行(持有数据库连接)时,build_db.py 去执行 delete_collection / add 等写操作 ,很可能会报错:database is locked(数据库被锁定)。
那如何解决这个问题呢?
改用 Chroma 的「客户端 / 服务器」模式:
核心思路:
- 启动一个 Chroma Server 后台服务(专门管理向量库)
build_db.py和rag_chat.py都通过网络连接到这个 Server,而不是直接碰文件- 由 Server 处理并发锁,你可以随时更新库,检索也不会断
具体步骤:
1.启动 Chroma Server
bash
# 先安装 chromadb 的 server 依赖
pip install chromadb uvicorn
# 启动 Chroma 服务(默认端口 8000,数据存 ./chroma_data)
chroma run --path ./chroma_data
注意:终端窗口不要关掉
2.把 PersistentClient 换成 HttpClient
build_db.py(HttpClient 版)
python
import os
import chromadb
from docx import Document
from chromadb.utils import embedding_functions
# ---------------------- 配置修改区 ----------------------
DOCS_FOLDER = "./docs"
COLLECTION_NAME = "word_rag_demo"
# 👇 这里改成连接 Server,不是本地文件了
CHROMA_HOST = "localhost"
CHROMA_PORT = 8000
# --------------------------------------------------------
# ---------------------- 初始化 Chroma HttpClient ----------------------
client = chromadb.HttpClient(host=CHROMA_HOST, port=CHROMA_PORT)
embedding_func = embedding_functions.SentenceTransformerEmbeddingFunction(
model_name="all-MiniLM-L6-v2"
)
# ---------------------- 读取word文档 ----------------------
# 删除旧集合(重新构建时用,避免重复数据)
try:
client.delete_collection(COLLECTION_NAME)
except:
pass
# 创建新集合
collection = client.create_collection(
name=COLLECTION_NAME,
embedding_function=embedding_func,
metadata={"description": "本地Word文档知识库"}
)
def read_docx(file_path):
doc = Document(file_path)
full_text = []
for para in doc.paragraphs:
if para.text.strip():
full_text.append(para.text.strip())
return "\n".join(full_text)
documents = []
metadatas = []
ids = []
doc_id = 0
if not os.path.exists(DOCS_FOLDER):
os.makedirs(DOCS_FOLDER)
print(f"⚠️ 已创建 {DOCS_FOLDER} 文件夹,请将Word文档(.docx)放入后重新运行!")
exit()
for filename in os.listdir(DOCS_FOLDER):
if filename.endswith(".docx"):
file_path = os.path.join(DOCS_FOLDER, filename)
print(f"正在读取:{filename}")
text = read_docx(file_path)
if not text:
continue
documents.append(text)
metadatas.append({"source": filename})
ids.append(f"doc_{doc_id}")
doc_id += 1
if documents:
collection.add(
documents=documents,
metadatas=metadatas,
ids=ids
)
print(f"\n✅ 向量库更新完成!共导入 {len(documents)} 个Word文档")
else:
print(f"\n❌ {DOCS_FOLDER} 文件夹中没有找到.docx文件!")
rag_chat.py(HttpClient 版)
python
import chromadb
import ollama
from chromadb.utils import embedding_functions
# ---------------------- 配置修改区 ----------------------
COLLECTION_NAME = "word_rag_demo"
OLLAMA_MODEL = "qwen:0.5b"
# 👇 这里改成连接 Server
CHROMA_HOST = "localhost"
CHROMA_PORT = 8000
# --------------------------------------------------------
# ---------------------- 初始化 Chroma HttpClient ----------------------
client = chromadb.HttpClient(host=CHROMA_HOST, port=CHROMA_PORT)
embedding_func = embedding_functions.SentenceTransformerEmbeddingFunction(
model_name="all-MiniLM-L6-v2"
)
collection = client.get_collection(
name=COLLECTION_NAME,
embedding_function=embedding_func
)
def rag_ask(question):
results = collection.query(
query_texts=question,
n_results=2
)
contexts = results["documents"][0]
sources = [meta["source"] for meta in results["metadatas"][0]]
context_str = "\n".join(contexts)
source_str = "、".join(set(sources))
print(f"🔍 参考文档:{source_str}")
print(f"📖 检索到的上下文:\n{context_str}\n")
prompt = f"""
你是一个专业的问答助手,请严格根据下面的参考信息回答问题,不要编造内容。
如果参考信息中没有答案,请回答:"抱歉,文档中没有相关信息"。
参考信息:
{context_str}
用户问题:{question}
回答:
"""
response = ollama.generate(
model=OLLAMA_MODEL,
prompt=prompt
)
return response["response"].strip()
if __name__ == "__main__":
while True:
user_question = input("\n💬 请输入你的问题(输入q退出):")
if user_question.lower() == "q":
break
answer = rag_ask(user_question)
print(f"\n🤖 AI回答:\n{answer}")
而微调则需要修改模型的权重,该过程耗时长,并且模型在此前见不能进行推理。
2、对于特定场景任务,RAG检索精确度很高
工厂需要一个大模型可以根据自己提供的操作手册来为工人进行解答,可以正确回答工人询问关于该厂机件相关信息。
RAG检索可以限制模型仅根据检索的内容回答用户的问题,也就是说模型只能根据工厂提供的信息回答用户的问题,几乎不会乱回答。
模型微调也可以实现上述任务,但是在要求极度准确的情况下,微调后的模型是不如RAG的。微调模型就是改变模型权重,在简单地讲就是将一个啥都知道的中学生变成某个专业的大学生,但模型的幻觉是不可能清除的,所以在有的时候模型就会发生答非所问,回答不对的情况。
三、BGE模型是什么?编码模型都有哪些?
前置知识:技术基线与核心概念
1、原生 BERT 的本质与核心局限
原生BERT是首个大规模预训练的Encoder-only(纯编码器)Transformer 架构 ,核心预训练任务是MLM(掩码语言模型):随机掩码句子中 15% 的 token,让模型通过双向上下文预测被掩码的内容,从而学会深度语义理解。
但原生 BERT 无法直接用作高质量句嵌入模型,这是绝大多数人都会踩的误区,核心局限:
- 原生 BERT 的 CLS 标记输出、或 token 向量平均得到的句向量,在语义相似度任务上效果极差,甚至不如传统 GloVe 词向量平均;
- 原生 BERT 仅针对单句分类、实体识别等 NLU 任务优化,没有学习「句子级的语义距离度量」能力;
- 原生 BERT 计算两句相似度需要同时输入两句话做推理,效率极低,无法满足向量检索的批量计算需求。
2、Sentence-BERT(SBERT)
该模型彻底解决了原生 BERT 的局限:
- 在 BERT 底座上加入孪生网络(Siamese Network) 结构,两个句子共享同一个模型权重,分别生成独立的句向量;
- 采用对比学习(三元组损失函数) 微调:让语义相似的句子(正例)向量距离尽可能近,语义无关的句子(负例)距离尽可能远;
- 推理时单句即可生成固定长度的句向量,两句相似度仅需计算向量距离,速度提升百倍以上。
3、编码模型的核心评判维度
- 语义准确率:语义相似度匹配精度,直接决定 RAG 检索效果;
- 上下文窗口:支持的最大文本长度,决定长文档处理能力;
- 语言适配:对中文 / 英文 / 多语言的优化程度;
- 推理效率:单句编码速度、显存 / 内存占用;
4、SBERT 原生及蒸馏优化系列(通用轻量型,英文主流)
该系列模型均基于 SBERT 范式,在 BERT/MPNet 底座上做蒸馏优化,主打轻量、高效、通用,是英文场景的入门标配。
(1). all-MiniLM-L6-v2
- 核心定位:轻量级通用句嵌入模型,RAG 入门基准模型
- 技术底座 :
- 底座基于 BERT 的蒸馏变体 MiniLM,通过知识蒸馏从 12 层的 BERT-base 压缩为 6 层 Transformer 结构;
- 在 SBERT 孪生网络 + 对比学习基础上,加入 MLM 预训练,同时优化语义理解和泛化能力;
- 相比原生 BERT:参数量仅为 BERT-base 的 1/6,推理速度快 3 倍以上,语义相似度效果接近 BERT-base 微调版。
- 核心参数:参数量 22M,输出 384 维向量,最大上下文 256token,纯英文优化
- 与 BERT 的核心区别:
- 优缺点:极致轻量、CPU 可流畅运行、稳定性极强、完全免费商用;缺点是中文语义效果差、上下文窗口小、长文本处理能力弱
- 适用场景:英文短文本 RAG、demo 测试、边缘设备部署、轻量级语义检索
(2). all-MPNet-base-v2
- 核心定位:SBERT 系列中效果最优的通用英文嵌入模型
- 技术底座:
- 底座基于 MPNet 架构(BERT 的优化版,融合了 BERT 的 MLM 和 XLNet 的置换语言模型 PLM,解决了原生 BERT 的位置信息偏差问题);
- 与 BERT 的核心区别:
-
- 训练语料规模远超 MiniLM 系列,在 SBERT 范式下做了全场景对比学习优化;
- 相比原生 BERT:语义理解精度更高,长文本上下文建模能力更强,所有英文嵌入任务效果全面超越 BERT-base。
- 核心参数:参数量 110M,输出 768 维向量,最大上下文 384token
- 优缺点:通用效果极强、稳定性好、泛化能力强;缺点是中文效果一般、体积更大、推理速度稍慢
- 适用场景:英文生产环境 RAG、高精度语义检索、文档聚类、通用 NLU 任务
(3). paraphrase-MiniLM-L12-v2
- 核心定位:专门针对释义 / 同义句匹配优化的轻量模型
- 技术底座:基于 MiniLM 架构,在 SBERT 范式下用百万级同义句对专项微调,针对 "不同表述但语义相同" 的场景深度优化;相比原生 BERT,同义句识别精度提升 40% 以上。
- 核心参数:参数量 33M,输出 384 维向量,最大上下文 256token
- 与 BERT 的核心区别:
- 优缺点:同义句匹配精度极高、轻量高效;缺点是通用检索效果不如 all-MiniLM 系列、中文效果差
- 适用场景:FAQ 问答匹配、客服对话检索、同义句识别
5、中文优化开源编码模型系列
该系列模型针对中文语料、中文语法特点深度优化,解决了 SBERT 系列中文效果差的问题,是国内中文 RAG 的事实标准。
(1). BGE 系列(BAAI General Embedding)
- 核心定位:国内中文嵌入模型标杆,中文 RAG 生产环境首选
- 技术底座:
- 底座基于RoBERTa-wwm(BERT 的最强优化版,去掉了原生 BERT 的 NSP 预训练任务,采用全词掩码 WWM,训练语料规模和 batch size 远超原生 BERT);
- 在 SBERT 对比学习基础上做了三大核心升级:采用难负例挖掘 大幅提升语义区分度、加入指令微调适配不同检索场景、用 TB 级中英双语语料(中文占比超 60%)训练;
- 与 BERT 的核心区别:
- 相比原生 BERT:中文语义检索精度提升 100% 以上,泛化能力极强,覆盖通用到垂直领域全场景。
- 核心版本体系:
| 版本 | 参数量 | 向量维度 | 最大上下文 | 核心特点 |
|---|---|---|---|---|
| BGE-small-zh-v1.5 | 27M | 384 维 | 512token | 轻量极速,体积与 all-MiniLM-L6-v2 相当,中文效果碾压后者 |
| BGE-base-zh-v1.5 | 110M | 768 维 | 512token | 平衡精度与速度,中文生产环境首选 |
| BGE-large-zh-v1.5 | 340M | 1024 维 | 512token | 高精度,适配法律 / 医疗 / 金融等专业垂直领域 |
- 优缺点:中文效果领先、开源免费商用、生态适配完美、版本覆盖全场景;轻量版上下文窗口偏小,超长文本需用 M3 版本
- 适用场景:全场景中文 RAG、中文语义检索、企业知识库、垂直领域专业检索
6. BGE-M3
- 核心定位:新一代多能力通用嵌入模型,突破传统稠密检索局限
- 技术底座:
- 底座基于 XLMRoBERTa(多语言版 RoBERTa),核心创新是Multi-Function、Multi-Lingual、Multi-Granularity三大能力;
- 训练范式升级:在对比学习基础上,同时加入稀疏检索、多向量检索预训练,一个模型同时支持稠密向量检索、稀疏词汇检索、多向量检索;
- 与 BERT 的核心区别:
- 相比原生 BERT:支持 100 + 语言、最大 8192token 超长上下文,解决了传统稠密检索的 "词汇外溢" 问题(无法匹配专业术语 / 专有名词),混合检索效果全面超越原生 BERT 系模型。
- 核心参数:参数量 560M,输出 1024 维稠密向量 + 稀疏向量,最大上下文 8192token
- 优缺点:超长文本支持、多语言、混合检索、全场景覆盖;缺点是体积较大,对显存要求更高
- 适用场景:长文档 RAG、多语言知识库、混合检索场景、多模态 RAG 前置处理
7. m3e 系列
- 核心定位:国内早期开源中文嵌入模型标杆,针对中文口语场景优化
- 技术底座:基于 Roberta-wwm-base 中文底座,在 SBERT 范式下用海量中文口语、问答、客服语料微调,针对中文短文本、口语化文本深度优化;相比原生 BERT,对中文口语、不规范文本的语义理解能力大幅提升。
- 核心参数:m3e-small(24M,384 维)、m3e-base(110M,768 维)
- 与 BERT 的核心区别:
- 优缺点:中文口语适配好、开源免费、轻量高效;缺点是长文本处理能力弱、通用效果不如 BGE 系列、更新迭代慢
- 适用场景:客服对话检索、FAQ 问答、口语化短文本 RAG
8. text2vec 系列
- 核心定位:阿里云开源的中文长文本嵌入模型
- 技术底座:基于 Roberta-wwm-large 中文底座,专门针对长文档、篇章级文本优化,训练语料以中文长文档、书籍、论文为主;相比原生 BERT,对长文本的全局语义建模能力更强,长文档检索精度更高。
- 核心参数:text2vec-large-chinese(330M,1024 维,最大 512token)
- 与 BERT 的核心区别:
- 优缺点:长文本语义建模能力强、中文专业文档适配好;缺点是体积大、推理速度慢
- 适用场景:长文档知识库、论文 / 专利检索、专业书籍 RAG
四、RAG的召回策略
召回是 RAG 系统的核心瓶颈环节,80% 以上的 RAG 回答错误(幻觉、答非所问、事实偏差),根源都在召回环节失效。
其本质是:从海量的知识库文档分块中,快速、精准地筛选出与用户 Query 语义最相关的 Top-K 个文档块,作为大模型生成回答的上下文参考 。召回的核心目标是平衡三大核心指标:高召回率(相关内容不遗漏)、高精准率(无关内容不混入)、低延迟。
1、召回的前置依赖:索引体系
召回的能力边界,完全由前置的索引构建环节决定。所有召回策略,都基于三类核心索引实现:
| 索引类型 | 核心作用 | 技术实现 | 适配召回策略 |
|---|---|---|---|
| 稠密向量索引 | 存储文档块的高维语义向量,支持语义相似度匹配 | 精确 KNN、HNSW、IVF、Annoy 等 | 向量召回、多向量召回 |
| 倒排索引(全文索引) | 存储文档的词项 - 文档映射关系,支持关键词匹配 | Lucene、Elasticsearch、BM25 算法 | 全文检索召回 |
| 元数据索引 | 存储文档的结构化标签(来源、时间、分类、权限等),支持条件过滤 | 关系型数据库、向量数据库内置元数据引擎 | 前置 / 后置过滤召回 |
2、核心单路召回策略(基础单元)
单路召回是指仅用一种检索逻辑完成召回,是多路召回的基础单元。每个策略都有明确的适用场景、优缺点和技术细节,没有万能策略,只有场景适配。
(1).向量召回(稠密向量检索):RAG 的核心
核心原理:
通过嵌入模型(编码模型),将用户 Query 和知识库的所有文档块,映射到同一个高维语义空间,转换为固定长度的稠密向量;再通过相似度算法,计算 Query 向量与全库文档向量的相似度,取相似度最高的 Top-K 个文档块作为召回结果。
核心技术细节:
1. 相似度计算算法(决定匹配逻辑)
相似度算法直接决定了向量匹配的规则,不同算法适配不同场景,嵌入模型的训练目标必须和相似度算法匹配,否则效果会大幅下降。
| 算法 | 核心原理 | 取值范围 | 适配场景 | 细节说明 |
|---|---|---|---|---|
| 余弦相似度(Cosine Similarity) | 计算两个向量的夹角余弦值,夹角越小,值越大,相似度越高 | [-1, 1] | 绝大多数文本检索场景,行业默认首选 | 不受向量模长影响,对文本长度不敏感,90% 以上的开源嵌入模型默认适配该算法,Chroma、LangChain 默认使用 |
| 点积相似度(Dot Product) | 两个向量对应元素相乘求和,值越大相似度越高 | 无固定范围 | 归一化后的向量检索 | 计算速度比余弦相似度更快;若向量已做 L2 归一化,点积结果与余弦相似度完全等价(OpenAI 嵌入模型输出均为归一化向量) |
| 欧氏距离(L2 距离) | 计算两个向量在高维空间的直线距离,值越小相似度越高 | [0, +∞) | 向量聚类、短文本精确匹配 | 受向量模长影响极大,文本长度差异大时效果极差,纯文本检索场景极少使用 |
| 曼哈顿距离(L1 距离) | 两个向量各维度差值的绝对值之和,值越小相似度越高 | [0, +∞) | 稀疏向量检索 | 文本稠密向量场景几乎不用 |
2.检索模式:精确检索 vs 近似检索
向量检索的核心矛盾是「检索精度」和「检索速度」的平衡,两种模式适配完全不同的数据规模
| 模式 | 核心原理 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|
| 精确 KNN 检索(暴力搜索) | 计算 Query 向量与全库所有向量的相似度,100% 遍历后取 Top-K | 无精度损失,召回率 100%,实现简单 | 数据量超过 10 万条后,延迟呈指数级增长,完全无法线上使用 | 小知识库(<10 万条文档块)、本地测试、对召回率要求 100% 的离线场景 |
| 近似最近邻检索(ANN) | 通过索引算法,牺牲极小的精度(通常 < 5%),换取数百倍的速度提升 | 百万 / 亿级数据量下,仍能保持毫秒级延迟 | 有极小的精度损失,需要构建和维护索引 | 生产环境、知识库规模 > 10 万条的所有线上场景 |
3. 主流 ANN 索引算法
ANN 是海量数据下向量召回的唯一可行方案,主流如下:
-
HNSW(层次化导航小世界):目前行业绝对主流,Milvus、Weaviate、FAISS 均默认支持
-
原理:构建多层有向图结构,上层是稀疏的高速导航通道,下层是稠密的全量数据节点;检索时从上到下逐层导航,快速锁定最近邻节点。
-
核心可调参数:
M(每个节点的邻居数,通常 16-64,越大精度越高,内存占用越大)、ef_construction(构建索引时的探索邻居数,越大构建越慢,索引精度越高)、ef_search(查询时的探索邻居数,越大查询越慢,精度越高)。 -
优势:查询速度极快、内存占用可控、支持动态增删数据、精度可调,适配绝大多数生产场景。
-
劣势:索引构建时间比 IVF 稍长。
-
-
IVF(倒排文件):FAISS 经典算法,适配静态大规模数据
-
原理:先对全量向量做 K-Means 聚类,生成 N 个聚类中心,构建倒排索引;检索时先找到与 Query 最近的 Top-N 个聚类中心,仅在这些聚类内做暴力搜索,大幅减少计算量。
-
优势:索引构建快、内存占用小、适配静态海量数据。
-
劣势:动态增删数据需要重建聚类、高维向量下精度下降快、对长尾数据召回效果差。
-
-
Annoy:Spotify 开源,适配嵌入式 / 低内存场景
-
原理:构建随机投影森林,将高维空间递归切分为二叉树,检索时遍历多棵树合并结果。
-
优势:内存占用极小、查询速度快、适配静态数据和嵌入式设备。
-
劣势:不支持动态增删数据,数据更新必须重建索引。
-
-
ScaNN:Google 开源,适配超大规模数据
-
原理:优化各向异性向量量化,在相同精度下,查询速度比 HNSW 提升 30% 以上。
-
优势:精度 - 速度平衡业界顶尖,适配亿级以上超大规模向量库。
-
劣势:配置复杂,生态兼容性不如 HNSW。
-
向量召回的优缺点与适配场景
-
优势:能捕捉深层语义、支持同义句 / 跨语言匹配、解决关键词匹配的语义鸿沟(比如用户问「怎么给猫驱虫」,能匹配到「宠物猫咪体内外驱虫方法」)。
-
劣势:对专业术语、专有名词、数字、日期的精确匹配能力弱(词汇外溢问题)、对嵌入模型质量依赖极强、分块不合理会导致语义严重丢失。
-
适配场景:通用语义检索、口语化 Query、同义句匹配、跨语言检索、长文本语义匹配。
4.全文检索召回(稀疏向量检索):向量召回的必备补充
全文检索是传统信息检索的核心技术,也是 RAG 系统中弥补向量召回短板的必备策略。
核心原理
基于倒排索引,将文档和 Query 拆分为最小语义单元(Token / 词),通过词频统计算法(BM25,替代传统 TF-IDF)计算 Query 与文档的关键词匹配度,取匹配度最高的 Top-K 个文档块。
核心技术细节
- 核心匹配算法:BM25
BM25 是目前全文检索的工业界标准算法,解决了 TF-IDF 的两大核心缺陷:词频无限放大、长文档权重偏差。
-
核心公式简化逻辑:
BM25分数 = 词频权重 × 逆文档频率 × 文档长度归一化因子 -
核心可调参数:
-
k1:词频饱和系数,默认 1.2-2.0,控制词频对分数的影响上限,避免高频词无限放大权重。 -
b:文档长度归一化系数,默认 0.75,控制长文档的权重惩罚,避免长文档因词频高过度占优。
-
-
优势:对短文本、精准 Query、专业术语的匹配精度极高,可解释性强,计算速度极快,无需嵌入模型。
- 中文场景的核心优化:分词策略
中文没有天然的空格分隔,分词器的质量直接决定全文检索的效果,是中文场景的核心细节。
-
主流分词器:Jieba(开源通用首选)、IK 分词器(Elasticsearch 标配)、HanLP(哈工大开源,专业场景首选)、THULAC(清华开源)。
-
必备优化项:
-
停用词过滤:过滤「的、了、啊、是」等无意义虚词,避免干扰匹配结果。
-
同义词扩展:将口语化词汇映射为专业术语(比如「拉肚子→腹泻」、「截屏→截图」),解决同义关键词不匹配的问题。
-
自定义词典:添加行业专有名词、产品名、人名等,避免专业术语被错误拆分。
-
全文检索召回的优缺点与适配场景
-
优势:精确匹配关键词、专业术语、数字、日期、专有名词,不会丢失关键信息;计算速度极快,无模型依赖,可解释性强。
-
劣势:无法捕捉深层语义,同义句匹配能力差,存在语义鸿沟(比如「iPhone 截图方法」无法匹配「苹果手机怎么截屏」)。
-
适配场景:精准 Query、专业术语检索、数字 / 日期匹配、法律 / 医疗 / 金融等专业文档检索,是向量召回的必备补充。
5.元数据过滤召回
元数据过滤是绝大多数 RAG 系统忽略,但能零成本大幅提升召回精度和速度的核心策略。
核心原理
在索引构建阶段,为每个文档块添加结构化元数据标签;召回时通过元数据条件,先缩小检索范围,再执行核心检索逻辑,避免无关内容混入。
元数据标签的常见类型:
-
基础属性:文档来源、文件名、页码、发布时间、更新时间、作者、文档类型(PDF/Word/PPT)。
-
业务属性:文档分类、所属部门、权限等级、产品版本、行业领域、保密级别。
-
内容属性:文档块字数、所属章节、是否包含表格 / 图片、核心实体类型。
两种过滤模式的细节对比:
| 模式 | 执行逻辑 | 优点 | 缺点 | 适配场景 |
|---|---|---|---|---|
| 前置过滤(Pre-Filtering) | 先通过元数据过滤出符合条件的文档子集,再在子集内执行向量 / 全文检索 | 减少检索数据量,大幅提升速度;从根源避免无关内容混入 | 过滤条件过严会导致召回率下降,甚至无结果返回 | 元数据体系完善的企业知识库、权限管控场景、分类清晰的文档库 |
| 后置过滤(Post-Filtering) | 先执行全量检索拿到 Top-K 结果,再通过元数据过滤掉不符合条件的内容 | 不会丢失相关内容,保证召回率兜底 | 检索速度无提升,过滤后可能剩余有效结果不足 | 过滤条件宽松、对召回率要求极高的场景 |
元数据过滤的优缺点与适配场景
-
优势:零额外成本、大幅提升检索精度和速度、实现企业级权限管控、避免过时 / 非授权内容混入。
-
劣势:依赖完善的元数据体系,过滤规则不合理会导致召回率下降。
-
适配场景:企业级知识库、多部门文档管理、权限管控场景、有时效性要求的文档(政策、产品手册)。
6.进阶单路召回策略(场景化优化)
6.1. 父子文档召回(Parent Document Retrieval)
解决的核心痛点:小分块检索精准但语义不完整,大分块语义完整但检索不精准的矛盾。
-
核心原理:分块时构建「父子层级」,将一篇文档拆分为大尺寸的父块 (用于生成,保证语义完整)和小尺寸的子块(用于检索,保证匹配精准),父子块通过元数据关联;召回时用子块做检索,匹配到相关子块后,返回其对应的完整父块给大模型。
-
示例实现:一篇 10000 字的文档,拆分为 10 个 1000 字的父块,每个父块再拆分为 5 个 200 字的子块;子块用于检索,命中后返回对应的 1000 字父块。
-
优势:同时兼顾检索精准度和上下文完整性,大幅减少大模型幻觉,是长文档 RAG 的标配优化。
-
适配场景:长文档、书籍、论文、政策文件、产品手册等结构化长文本。
6.2. 语义分块召回(Semantic Chunking)
解决的核心痛点:固定大小分块会强行切断完整语义,导致语义丢失、检索失效。
-
核心原理:抛弃固定 token 数的一刀切分块方式,通过嵌入模型计算相邻句子的向量相似度,当相似度低于预设阈值时,判定为语义边界,执行分块,保证每个块内是一个完整的语义单元。
-
核心可调参数:相似度阈值(通常 0.6-0.8,不同类型文档需微调)、最小 / 最大块长度(避免块过短或过长)。
-
优势:每个块语义完整,无边界信息丢失,检索精准度比固定分块提升 30% 以上。
-
适配场景:所有类型的文档,尤其是非结构化的散文、报告、口语化文本。
6.3. 多向量召回(Multi-Vector Retrieval)
解决的核心痛点:单一向量无法覆盖文档块的全部语义,导致召回遗漏。
-
核心原理:为同一个文档块生成多个不同维度的向量,召回时用多个向量同时检索,合并结果。常见的多向量类型:
-
文档块本身的全文向量
-
文档块的标题 / 所属章节向量
-
文档块的 AI 摘要向量
-
文档块的假设问题向量(HyDE)
-
-
优势:从多个维度捕捉文档语义,大幅提升召回率,解决单一向量的语义覆盖不足问题。
-
适配场景:复杂长文档、多主题文档块、专业领域文档。
6.4. 知识图谱召回
解决的核心痛点:多跳问题、实体关联问题的召回失效(比如「张三的公司的产品有哪些」)。
-
核心原理:先从文档中提取实体(人名、公司名、产品名、地名等)和实体间的关系,构建知识图谱;用户 Query 时,先提取 Query 中的实体,再从知识图谱中找到关联的实体、关系和对应的文档,完成召回。
-
优势:可解释性极强,能解决多跳推理问题,精准捕捉实体间的关联,是专业领域 RAG 的核心补充。
-
劣势:知识图谱构建和维护成本极高,需要实体抽取、关系抽取、实体链接等多个模型配合。
-
适配场景:金融、医疗、法律、政务等实体密集、逻辑关系复杂的专业领域。
3.多路召回策略
3.1 多路召回的核心逻辑
- 并行执行多个不同的召回通道(比如向量召回、BM25 全文召回、知识图谱召回),每个通道返回各自的 Top-K 结果;
- 通过融合算法,将多个通道的结果合并、去重、排序;
- 输出合并后的 Top-N 结果,送给重排环节或直接给大模型。
核心目标:用不同的召回通道,覆盖不同类型的相关内容 ------ 向量召回覆盖语义相关的内容,全文召回覆盖关键词精准匹配的内容,元数据过滤排除无关内容,最终保证所有相关内容都被召回。
3.2 主流多路召回组合方案
- 基础标配组合(90% 生产场景首选)
向量召回 + BM25 全文召回 + RRF 结果融合
-
实现逻辑:向量召回取 Top-20,BM25 全文召回取 Top-20,通过 RRF 算法合并去重,输出 Top-20 结果给重排环节。
-
优势:取长补短,彻底解决向量召回的词汇外溢问题和全文召回的语义鸿沟问题,实现简单,效果稳定,无需复杂调参,是行业通用的最优解。
-
适配场景:绝大多数通用 RAG 场景、中文知识库、企业级应用。
- 企业级进阶组合
元数据前置过滤 + 向量召回 + BM25 全文召回 + 父子文档召回
-
实现逻辑:先通过元数据过滤缩小检索范围,再并行执行向量召回和全文召回,合并结果后关联对应的父文档,输出完整的上下文。
-
优势:兼顾速度、精度、上下文完整性,同时满足企业级权限管控需求。
-
适配场景:企业内部知识库、多部门文档管理、权限敏感的 RAG 系统。
- 专业领域高级组合
多向量召回 + BM25 全文召回 + 知识图谱召回 + 元数据过滤
-
实现逻辑:多维度向量并行检索,配合全文关键词匹配、知识图谱实体关联检索,前置元数据过滤排除无关内容,多路结果融合后输出。
-
优势:全维度覆盖相关内容,召回率接近 100%,适配复杂专业场景。
-
适配场景:法律、医疗、金融、政务等专业领域、超大规模知识库、对召回率要求极高的场景。
3.3 多路结果融合核心算法
多路召回的核心难点是:不同通道的相似度分数不可比(向量的余弦相似度和 BM25 分数不是一个量级,无法直接加权),必须通过专门的融合算法处理。
- RRF(Reciprocal Rank Fusion,倒数排名融合):行业首选
-
核心原理:不依赖分数本身,仅通过结果的排名计算融合分数,公式为:
RRF分数 = Σ 1/(k + rank),其中rank是文档在对应通道中的排名,k是常数(行业通用默认值 60)。 -
示例:文档 A 在向量召回中排第 1 位,在全文召回中排第 3 位,RRF 分数 = 1/(60+1) + 1/(60+3) ≈ 0.0164 + 0.0159 = 0.0323。
-
优势:无需归一化、无需调参、效果稳定,彻底解决不同通道分数不可比的问题,是工业界多路融合的绝对首选。
-
劣势:无法体现不同通道的权重差异,对强相关通道的优势放大不足。
- 加权融合
-
核心原理:先将不同通道的分数做 0-1 归一化,再给每个通道设置权重,加权求和得到最终分数,按分数排序。
-
示例:向量召回权重 0.6,全文召回权重 0.4,文档 A 的归一化向量分数 0.9,归一化 BM25 分数 0.7,最终分数 = 0.9×0.6 + 0.7×0.4 = 0.82。
-
优势:可灵活调整不同通道的权重,适配特定场景(比如专业场景可提高全文召回的权重)。
-
劣势:需要大量标注数据调优权重,不同场景权重不通用,归一化不合理会导致结果偏差。
- 取并集 / 取交集
-
取并集:将所有通道的结果合并去重,最简单的融合方式,保证召回率最大化,适合兜底场景。
-
取交集:仅保留所有通道都召回的结果,精度极高,但召回率极低,仅适合极精准的 Query 场景,几乎不通用。
4.召回环节的 Query 优化策略
召回的输入是用户 Query,Query 的质量直接决定召回效果。用户的 Query 往往是口语化、不完整、有歧义、信息密度低的,直接检索效果极差,Query 优化是 RAG 优化中成本最低、效果提升最明显的手段。
4.1 Query 改写(Query Rewriting)
- 核心原理:用轻量大模型,将用户的原始 Query 改写成更适合检索的标准化 Query,不改变用户的核心意图。
- 常见改写类型:
- 上下文补全:多轮对话中补全指代信息,比如上一轮问「Python 怎么安装」,本轮问「那它怎么运行」,改写为「Python 怎么运行」。
- 歧义消除:明确 Query 的语义边界,比如「苹果多少钱一斤」改写为「新鲜水果苹果的市场零售价格」,避免匹配到苹果手机的内容。
- 关键词扩展:将口语化词汇扩展为专业术语 + 同义词,比如「猫拉肚子怎么办」扩展为「宠物猫咪腹泻的原因和治疗方法」。
- 复杂 Query 拆分:将多问题的 Query 拆分为多个单意图子 Query,分别检索后合并结果,比如「特斯拉 Model 3 的价格、续航、配置」拆分为 3 个独立子 Query。
- 实现要点:Prompt 必须严格约束大模型「仅改写 Query,不回答问题,不添加额外信息」,避免改写偏离用户意图。
4.2 多 Query 生成(Multi-Query Generation)
- 核心原理:用大模型,基于用户的原始 Query,生成 3-5 个不同表述、不同角度的同义子 Query,用所有子 Query 并行检索,合并所有结果。
- 示例:原始 Query「怎么给笔记本电脑清灰」,生成子 Query:
- 笔记本电脑清灰的详细操作步骤
- 如何清理笔记本电脑内部的灰尘
- 笔记本电脑除尘教程新手版
- 优势:覆盖不同的语义表述,大幅提升召回率,解决单一 Query 的语义覆盖不足问题,实现简单,效果稳定。
- 劣势:增加了多次检索调用,延迟略有提升,生成过多 Query 会导致无关内容混入。
4.3 HyDE(Hypothetical Document Embeddings,假设文档嵌入)
- 核心原理:用户的短 Query 信息密度低,直接编码成向量检索效果差;先用大模型基于 Query 生成一篇「假设的理想回答文档」,再将这篇假设文档编码成向量,用该向量做检索。
- 核心逻辑:假设的理想回答,和知识库中真实的相关文档,在语义空间中的距离,远短于原始 Query 和真实文档的距离,能大幅提升检索精准度。
- 实现步骤:
- 用户输入原始 Query;
- 大模型基于 Query 生成一篇假设的回答(无需事实正确,仅需语义相关、结构完整);
- 将假设回答编码成向量;
- 用该向量做向量召回,获取相关文档。
- 优势:大幅提升短 Query、模糊 Query、专业领域 Query 的召回率,效果提升明显。
- 劣势:增加一次大模型调用,延迟提升;若假设文档偏离用户意图,会导致召回完全失效。
5.召回策略的评估指标
召回优化必须基于量化指标,不能凭「回答感觉不错」判断效果。召回指标分为离线核心指标 (优化阶段评估)和在线业务指标(上线后评估)。
5.1 离线核心指标(召回环节专属)
需要先构建标注数据集:一批 Query,以及全库中与每个 Query 相关的所有文档的标准答案。
| 指标 | 核心定义 | 计算公式 | 优化目标 | 细节说明 |
|---|---|---|---|---|
| Recall@K(召回率) | 全库所有相关文档中,前 K 个召回结果里包含的相关文档比例 | Recall@K = 前 K 个结果中的相关文档数 / 全库相关文档总数 | 越高越好 | 召回环节的核心指标,代表「相关的内容有没有被找回来」,生产环境要求 Recall@10 ≥ 80% |
| Precision@K(精准率) | 前 K 个召回结果中,相关文档的占比 | Precision@K = 前 K 个结果中的相关文档数 / K | 越高越好 | 代表「召回的结果里有没有混入无关内容」,直接影响大模型的幻觉率 |
| Hit Rate@K(命中率) | 所有测试 Query 中,前 K 个结果里至少有 1 个相关文档的 Query 占比 | Hit Rate@K = 命中相关文档的 Query 数 / 总 Query 数 | 越高越好 | 召回环节的兜底指标,代表「有没有至少找到一条有用的内容」,生产环境要求 Hit Rate@10 ≥ 95% |
| MRR(平均倒数排名) | 所有 Query 中,第一个相关文档的排名的倒数的平均值 | MRR = AVG (1 / 第一个相关文档的排名) | 越高越好 | 代表「相关的内容能不能排在前面」,直接影响重排和大模型的效果 |
| NDCG@K(归一化折损累计增益) | 综合考虑相关文档的排名和相关程度(高度相关 / 中度相关 / 无关)的排序质量指标 | 基于累计增益做归一化计算 | 越高越好 | 最全面的排序质量指标,适配专业场景的分级相关标注 |
5.2 在线业务指标
| 指标 | 核心定义 | 优化目标 |
|---|---|---|
| 检索延迟 | 从用户输入 Query 到召回结果返回的耗时 | 线上要求≤100ms,最大不超过 500ms |
| 事实一致性 | 大模型生成的回答与召回上下文的符合程度,无幻觉的回答占比 | 越高越好,生产环境要求≥95% |
| 用户满意度 | 用户对回答的点赞 / 点踩比例 | 越高越好 |
| 回答拒答率 | 大模型回答「无相关信息」的 Query 占比 | 平衡在 5%-15%,过高说明召回率不足,过低说明无关内容混入 |
五、RAG重排序
重排序(也叫精排)是 RAG 系统中承接召回、决定生成质量的核心环节,是解决 "召回结果多但不精、关键信息沉底、大模型幻觉频发" 的最有效手段。
1、重排序的核心定位与本质价值
1.1. 重排序在 RAG 链路中的位置
RAG 的标准检索流程是 **「粗筛→精排」的两阶段漏斗架构 **,和数据库的「索引定位→条件过滤」优化逻辑完全一致:
- 召回阶段(粗排):用 Bi-Encoder(嵌入模型)从百万 / 千万级的全量知识库中,快速筛选出 Top50-Top100 的候选文档,核心目标是「宽进严出,不遗漏相关内容」,牺牲部分精度换取极致速度;
- 重排序阶段(精排):用更高精度的模型,对召回的几十条候选文档做逐一审定,重新计算与用户 Query 的真实相关性,按分数从高到低排序,最终只取 Top3-Top10 最相关的文档喂给大模型,核心目标是「优中选优,把最关键的内容排在最前面」。
1.2. 为什么召回之后必须加重排序?
很多人疑惑:召回已经按相似度排序了,为什么还要多一层重排序?核心原因有 4 个,也是召回阶段的天生短板:
- 召回的相似度是 "粗粒度匹配":召回用的 Bi-Encoder 是将 Query 和文档分别编码,两者在编码时完全 "看不见对方",只能捕捉整体语义相似度,无法捕捉细粒度的 token 级交互(比如关键词、否定词、数字、日期的精准匹配);
- 多路召回的分数不可比:向量召回、BM25 全文召回等多路结果的分数体系完全不同,无法直接加权排序,必须通过重排序做统一的相关性判定;
- 大模型的 "首因效应":大模型对上下文的注意力高度集中在最前面的内容,排在后面的相关信息大概率会被忽略,哪怕召回结果里有正确答案,只要沉底就等于无效;
- 近似检索的精度损失:生产环境用的 ANN 近似检索,为了速度牺牲了部分精度,召回结果里会混入大量 "语义相似但无关" 的内容,必须通过重排序做二次过滤。
1.3. 通俗类比
- 召回 = 招聘 HR 从 10 万份简历里,快速筛出 50 份符合岗位要求的候选简历;
- 重排序 = 业务部门负责人,对这 50 份简历做逐一面谈,评估真实的岗位匹配度,选出最优秀的 5 个人;
- 大模型 = 最终的 CEO,只需要看这 5 份最优简历做最终决策,不会被无关信息干扰。
2、重排序的核心技术原理:Cross-Encoder vs Bi-Encoder
重排序的核心技术底座是Cross-Encoder(交叉编码器) ,而召回的核心是Bi-Encoder(双编码器),两者的架构差异,决定了精度和速度的天壤之别,也是理解重排序的核心。
2.1. Bi-Encoder(召回用,双编码器)的工作原理
- 核心逻辑:Query 和文档分别输入同一个嵌入模型,独立编码生成各自的稠密向量,再通过余弦相似度 / 点积计算两者的匹配度;
- 核心优势:文档向量可以提前离线计算并存储到向量数据库,线上仅需对 Query 做一次编码,速度极快,可支持百万 / 亿级数据检索;
- 核心短板:Query 和文档在编码时无任何交互,模型无法捕捉两者之间的细粒度语义对应关系,比如 "2024 年北京社保缴费基数下限",Bi-Encoder 很容易把 "2023 年上海社保缴费基数" 判定为高相似度,因为整体语义接近,但关键信息完全不符。
2.2. Cross-Encoder(重排序用,交叉编码器)的工作原理
- 核心逻辑 :将 Query 和单条候选文档拼接成一个完整的文本序列,格式为
[CLS] 用户Query [SEP] 候选文档 [SEP],输入 Transformer 模型,让 Query 的每个 token 和文档的每个 token 做全量的双向注意力交互,最终通过 CLS 标记直接输出 0-1 之间的相关性分数,分数越高,相关性越强; - 核心优势:模型能完整捕捉 Query 和文档的细粒度匹配关系,包括关键词、否定词、数字、日期、实体的精准对应,甚至能判断文档是否能真正回答 Query,匹配精度远超 Bi-Encoder;
- 核心短板:每一对 Query + 文档都要单独过一遍模型做推理,无法提前离线计算,计算成本高、速度慢,无法用于全库检索,仅适合对召回的少量候选集做精排。
2.3. 两者核心差异对比表
表格:
| 对比维度 | Bi-Encoder(召回用) | Cross-Encoder(重排序用) |
|---|---|---|
| 编码方式 | Query 和文档独立编码,两次前向传播 | Query 和文档拼接后联合编码,一次前向传播 |
| 交互机制 | 编码时无交互,仅事后通过向量计算相似度 | 全层双向交叉注意力,token 级深度交互 |
| 核心目标 | 速度、扩展性,适配海量数据检索 | 精度、匹配准确度,适配小批量精排 |
| 离线计算 | 文档向量可提前离线计算,线上仅编码 Query | 无法离线计算,线上必须实时推理 |
| 推理速度 | 极快,单 Query 编码耗时 < 10ms,可支持百万级数据 | 较慢,单对 Query + 文档推理耗时 < 100ms,仅适合 Top50 以内的候选集 |
| 匹配精度 | 粗粒度,整体语义匹配,易忽略关键细节 | 细粒度,精准捕捉关键信息,匹配精度提升 25%+ |
| 适用场景 | 全库召回、粗排、海量数据检索 | 候选集精排、重排序、高精度相关性判定 |
3、重排序的完整工程实现流程
重排序的落地分为 5 个标准步骤
3.1:召回候选集准备
这是重排序的前置环节,核心原则是 「召回的候选集数量必须是最终给大模型数量的 3-10 倍」。
- 数量选择:如果最终给大模型 Top5 条文档,召回需要返回 Top20-Top50 条;如果最终给 Top10 条,召回需要返回 Top50-Top100 条;
- 下限要求:候选集数量不能低于最终输出的 3 倍,否则重排序没有优化空间;
- 上限要求:候选集数量不能超过 100 条,否则重排序的延迟会大幅上升,影响用户体验;
- 预处理:对召回结果做去重(避免重复文档)、无效内容过滤(过滤空文本、过短的无效块),再送入重排序模型。
3.2:重排序模型的输入构造
这是最容易踩坑的环节,输入格式错误会直接导致模型效果完全失效。
- 标准输入格式 :必须严格遵循
(Query, 文档)的成对输入,拼接格式为[CLS] 用户问题 [SEP] 候选文档内容 [SEP],主流的 Cross-Encoder 模型均默认适配该格式; - 最大长度限制(max_length) :
- 轻量模型默认设置为 512token,长文本模型可设置为 1024-8192token;
- 截断策略:当 Query + 文档总长度超过 max_length 时,必须完整保留 Query,仅截断文档的尾部内容,避免 Query 的关键信息丢失;
- 批量输入构造 :将 Query 和所有候选文档,批量构造成
[(Query, doc1), (Query, doc2), ..., (Query, docN)]的格式,送入模型做批量推理,提升速度。
3.3:批量推理与相关性分数计算
- 批量推理:将构造好的成对数据批量输入模型,一次性输出所有候选文档的相关性分数,避免单条循环推理,大幅提升推理效率;
- 分数范围:主流 Cross-Encoder 的输出分数通常在 0-1 之间,也有部分模型输出为 - logits 值,无需手动归一化,直接按分数大小排序即可;
- 硬件优化:GPU 环境下开启 FP16/INT8 量化,CPU 环境下开启 ONNX 加速,降低推理延迟。
3.4:结果排序与截断
- 按模型输出的相关性分数,从高到低对候选文档做降序排序;
- 按预设的 Top-K 值(通常 3-10 条)截断,只保留分数最高的前 K 条文档;
- 可选操作:设置最低分数阈值(比如 0.3),过滤掉分数低于阈值的无关文档,避免无效内容混入大模型上下文。
3.5:输出给大模型生成
将重排序后的 Top-K 文档,按相关性从高到低的顺序,拼接成上下文 Prompt,和用户 Query 一起喂给大模型,生成最终回答。
关键细节:必须保持重排序后的顺序,相关性越高的文档,放在上下文的最前面,最大化利用大模型的首因注意力效应。
4、主流重排序方案全解析
重排序方案分为两大类:基于 Cross-Encoder 的专用重排序模型(生产环境首选) 和 基于 LLM 的大模型重排序(复杂场景补充)。
4.1:基于 Cross-Encoder 的专用重排序模型(90% 生产场景首选)
这类模型是专门为相关性匹配任务微调的 Cross-Encoder 架构,平衡了精度、速度和部署成本,是 RAG 重排序的标配方案。
开源重排序模型(中文场景首选):
| 模型系列 | 出品方 | 核心版本 | 核心参数 | 核心优势 | 适配场景 |
|---|---|---|---|---|---|
| BGE-Reranker 系列 | 北京智源研究院 | bge-reranker-v2-m3bge-reranker-large-zhbge-reranker-base-zhbge-reranker-small-zh | 参数量 27M-560M,支持 512-8192token 上下文,输出 0-1 相关性分数 | 中文效果 SOTA,支持 100 + 语言,长文本适配好,开源免费商用,和 BGE 嵌入模型语义空间完全匹配,生态适配性拉满 | 中文 RAG 生产环境首选,全场景适配,是目前国内私有化部署的绝对主流 |
| CrossEncoder-MiniLM 系列 | 微软 | cross-encoder/ms-marco-MiniLM-L6-v2 | 参数量 22M,最大 512token,英文场景标杆 | 极致轻量,CPU 可流畅运行,推理速度极快,开源免费商用 | 英文轻量场景、demo 测试、边缘设备部署 |
| BCE-Reranker 系列 | 网易有道 | bce-reranker-base_v1 | 参数量 110M,最大 512token | 中英双语优化,在中文 BEIR 评测中接近 BGE-large,开源免费商用 | 中英双语混合场景,通用 RAG 生产环境 |
| Jina-Reranker 系列 | Jina AI | jina-reranker-v3 | 参数量 0.6B,默认 8k 上下文,支持 100 + 语言 | 超长文本支持,专为 Agentic-RAG 优化,listwise 交叉交互机制,长文档匹配精度高 | 长文档 RAG、多轮对话 RAG、Agent 场景 |
4.2:基于 LLM 的大模型重排序(复杂场景补充方案)
直接用生成式大模型(如 Ollama 本地模型、GPT-4o、通义千问)做重排序,通过 Prompt 工程让大模型直接判定文档与 Query 的相关性并排序,无需专用重排序模型。
1. 三种主流实现方式
(1)Pointwise 逐点打分
- 核心逻辑:给大模型 Prompt,让它对每一条候选文档,独立给出 0-5 分的相关性打分,最终按分数排序;
- prompt实例:
你是一个专业的相关性判定助手,请判断以下文档能否回答用户的问题,仅输出0-5的整数分数,分数越高相关性越强,不要输出其他内容。 用户问题:{user_query} 参考文档:{document} 分数:
- 优势:实现简单,对上下文窗口要求低,可解释性强;
- 劣势:需要多次调用大模型,延迟高,成本高。
(2)Listwise 列表排序
- 核心逻辑:把所有候选文档一次性喂给大模型,让它直接输出相关性从高到低的文档编号列表,一步完成排序;
- 核心优化:RankGPT 提出的滑动窗口策略,解决候选文档过多、超出大模型上下文窗口的问题,将候选文档分批次排序,逐步合并结果;
- 优势:一次调用完成排序,逻辑更贴合人类的排序习惯,能捕捉文档之间的相对相关性;
- 劣势:对大模型的推理能力要求高,小模型容易输出乱序,上下文窗口占用大。
(3)Pairwise 两两对比
- 核心逻辑:让大模型对两个候选文档做两两对比,判断哪一个和 Query 更相关,通过冒泡排序 / 锦标赛排序的方式,最终得到完整的排序列表;
- 优势:对小模型更友好,判断逻辑简单,排序稳定性强;
- 劣势:需要多次调用大模型,时间复杂度高,仅适合 10 条以内的候选集。
2. LLM 重排序的优缺点
- 优势:无需额外部署重排序模型,能理解复杂的多意图 Query、多跳问题,可融入业务规则做定制化排序,适配垂直领域的复杂场景;
- 劣势:推理延迟高、成本高,排序稳定性差,小模型容易出现排序错误,不适合高并发线上场景;
- 适用场景:复杂多意图 Query、垂直领域专业场景、低并发的内部知识库系统,通常作为 Cross-Encoder 重排序的补充,而非替代。
5、进阶重排序策略
基础的 Cross-Encoder 重排序只能解决相关性排序问题,以下进阶策略可针对性解决 RAG 的各类长尾问题。
5.1. 多样性重排序
解决的核心痛点:重排序后的结果高度同质化,只覆盖 Query 的单一维度,丢失其他相关信息。
- 核心原理:在排序时同时兼顾「相关性分数」和「文档间的多样性」,避免重复内容,覆盖 Query 的多个角度;
- 实现逻辑:通过公式
最终分数 = (1-多样性权重)×相关性分数 + 多样性权重×(1-与已选文档的最大相似度),逐步选择文档,平衡相关性和多样性; - 可调参数:多样性权重通常设置为 0.2-0.4,数值越大,多样性越强,相关性越弱;
- 适用场景:多意图 Query、宽泛的开放性问题、需要全面信息的问答场景。
5.2. 时效性重排序
解决的核心痛点:过时的文档内容排在前面,导致大模型输出过期的信息(比如旧版政策、历史价格)。
- 核心原理:在相关性分数的基础上,加入时间衰减因子,发布时间越新的文档,获得额外的分数加权;
- 实现逻辑:给文档添加发布时间元数据,通过时间衰减函数计算时间权重,最终分数 = 相关性分数 × 时间权重;
- 适用场景:政策文件、产品手册、新闻资讯、价格信息等有时效性要求的文档。
5.3. 元数据加权重排序
解决的核心痛点:不同来源、不同类型的文档,可信度和优先级不同。
- 核心原理:基于文档的元数据(来源、权限、分类、文档类型),给不同的文档设置基础权重,最终分数 = 相关性分数 × 元数据权重;
- 示例:官方发布的文档权重 1.2,部门文档权重 1.0,个人笔记权重 0.8;PDF 手册权重 1.1,Word 文档权重 1.0;
- 适用场景:企业级知识库、多来源文档混合、有明确权限和优先级划分的场景。
5.4. 分阶段重排序
解决的核心痛点:候选集过大导致重排序延迟高,候选集过小导致优化空间不足。
- 核心逻辑:采用「粗排→中排→精排」的三阶段架构,平衡速度和精度:
- 粗排:向量召回 + BM25 召回,返回 Top100 候选;
- 中排:轻量 Cross-Encoder 模型(如 BGE-reranker-small),快速筛选出 Top20 候选;
- 精排:高精度大模型(如 BGE-reranker-large/LLM),对 Top20 做最终排序,输出 Top5 给大模型;
- 适用场景:超大规模知识库、高并发线上场景、对延迟和精度都有高要求的生产环境。
5.5. 长文档分块重排序
解决的核心痛点:父子文档召回中,大尺寸父块的整体相关性分数无法体现局部内容的精准匹配。
- 核心原理:对父文档做滑动窗口分块,分别计算每个窗口与 Query 的相关性分数,取最大分数作为该父文档的最终相关性分数,避免整体语义模糊导致的排序错误;
- 适用场景:长文档 RAG、父子文档召回、书籍 / 论文 / 政策文件等大篇幅文档。
6、重排序的核心评估指标
重排序的优化必须基于量化指标,不能凭 "回答感觉不错" 判断效果,核心分为离线排序质量指标 和在线业务指标。
6.1. 离线核心指标(排序质量专属)
需要先构建标注数据集:一批 Query,以及每个 Query 对应的文档相关性标注(通常分为 3 级:高度相关、中度相关、无关)。
表格:
| 指标 | 核心定义 | 优化目标 | 适用场景 |
|---|---|---|---|
| NDCG@K(归一化折损累计增益) | 综合考虑文档的排序位置和相关程度的核心指标,是排序任务的黄金指标 | 越高越好,生产环境 NDCG@5 ≥ 0.85 | 所有重排序场景,尤其是有分级相关性标注的专业场景 |
| MRR(平均倒数排名) | 所有 Query 中,第一个高度相关文档的排名的倒数的平均值 | 越高越好,生产环境 MRR ≥ 0.9 | 核心目标是 "把最相关的文档排在第一位" 的问答场景 |
| MAP(平均精度均值) | 所有 Query 的平均精度的平均值,综合评估召回率和排序质量 | 越高越好 | 多相关文档的检索场景,全面评估排序效果 |
| Precision@K | 重排序后的 Top-K 文档中,高度相关文档的占比 | 越高越好,生产环境 Precision@5 ≥ 0.9 | 评估重排序的精准度,直接决定大模型的幻觉率 |
6.2. 在线业务指标
表格:
| 指标 | 核心定义 | 优化目标 |
|---|---|---|
| 重排序延迟 | 从重排序输入到输出的耗时 | 线上要求≤200ms,最大不超过 500ms |
| 事实一致性 | 大模型生成的回答与参考文档的符合程度,无幻觉的回答占比 | 越高越好,生产环境要求≥95% |
| 回答拒答率 | 大模型回答 "无相关信息" 的 Query 占比 | 平衡在 5%-15%,过高说明重排序过滤过度,过低说明无关内容混入 |
| 用户满意度 | 用户对回答的点赞 / 点踩比例 | 越高越好 |
7. 高频避坑指南
-
坑 1:用重排序替代召回,直接对全库文档做重排序后果:推理延迟爆炸,完全无法线上使用,Cross-Encoder 仅适合对少量候选集做精排,绝对不能用于全库检索。
-
坑 2:Query 和文档的拼接格式错误后果:模型效果完全失效,必须严格遵循
(Query, 文档)的成对输入,不能反过来,也不能省略分隔符。 -
坑 3:截断时优先截断了 Query后果:Query 的关键信息丢失,相关性判定完全错误,必须完整保留 Query,仅截断文档的尾部内容。
-
坑 4:重排序后的文档顺序被打乱后果:大模型忽略关键信息,产生幻觉,必须严格按照重排序的分数从高到低排列,相关性越高的文档放在上下文最前面。
-
坑 5:没有设置最低分数阈值后果:大量无关的低分数文档被喂给大模型,导致幻觉频发,必须设置合理的分数阈值,过滤掉无效内容。
-
坑 6:重排序模型的 max_length 设置过小后果:文档的关键信息被截断,相关性判定错误,max_length 至少要设置为 512token,长文档场景需设置为 1024token 以上。