RAG 文档切分、索引优化与 Reranker 学习笔记
记录一次询问GPT的完整过程 :从业务场景到工程落地的一次完整梳理
前言:这次讨论在解决什么问题
这次对话围绕 RAG 系统中的三个关键问题展开:
- 文档到底应该如何切分?
- 索引应该如何优化?
- 为什么还需要 Reranker,以及 Reranker 通常有哪些做法?
一开始讨论的是文档切分和索引优化,后来进一步引出了 RAG 检索链路中的排序问题,也就是:即使向量检索能找出一批看起来相关的 chunk,系统仍然需要判断哪些 chunk 最能回答用户问题。于是自然进入了 Reranker 的讨论。
这份笔记不是简单罗列概念,而是按照这次对话中的思考路径来整理:
text
文档切分与索引优化
↓
为什么需要 Reranker
↓
Reranker 是什么
↓
Reranker 通常有哪些做法
↓
如何把整个 RAG 检索链路串起来
一、文档切分到底应该怎么做
最开始关注的问题是:
在文档切分和索引优化部分,如何具体切分文档?如何根据不同业务场景切分?以及如何做索引优化?
这个问题的核心并不是简单问 chunk_size 应该设成多少,而是在问:
RAG 系统中的文档切分,如何从业务问题出发,设计出真正适合检索和问答的知识单元?
1. 为什么不能直接按固定长度切分
很多 RAG 初学者会直接采用固定长度切分,例如:
text
每 500 tokens 切一块,overlap 设置为 50 tokens
这种方式实现简单,但是问题也很明显:它不理解文档结构,也不理解业务语义。
例如一段退款规则:
text
退款条件在 chunk A
退款例外情况在 chunk B
退款到账时间在 chunk C
如果用户问:
text
退款多久能到账?
系统可能只召回 chunk C,却丢失了前面的退款条件和审核规则,最终导致回答不完整。
所以固定长度切分只能作为兜底方案,不应该作为所有业务文档的默认最优方案。
2. 文档切分的本质:让 chunk 粒度匹配用户问题粒度
文档切分的核心不是"把文档切碎",而是:
让每一个 chunk 尽可能成为一个独立、完整、可被检索的问题答案单元。
也就是说,chunk 的大小应该由用户问题决定,而不是由文档长度决定。
例如:
| 用户问题类型 | 合适的 chunk 粒度 |
|---|---|
| 怎么申请退款? | 一个 FAQ 问答对 |
| 合同里甲方有哪些义务? | 一个或多个合同条款 |
| 这个接口怎么调用? | 一个完整接口说明 |
| 这个 Java 方法为什么报错? | 一个方法体 + 类名 + 上下文 |
| 推荐几部高分国产剧情片 | 一部电影作为一个实体 chunk |
| 这篇论文方法是什么? | 一个章节或语义段 |
所以切分之前,应该先问:
text
用户通常会怎么问?
回答这个问题最小需要多少上下文?
这个上下文在原文中的自然边界是什么?
3. 切分文档时应该先看结构,再看语义,最后才看长度
比较合理的文档处理流程是:
text
原始文档
↓
文本清洗 / OCR / 表格解析 / Markdown 解析 / HTML 解析
↓
识别结构:标题、章节、段落、表格、代码块、列表、页码
↓
按业务语义切分
↓
控制 chunk_size 和 overlap
↓
补充 metadata
↓
embedding + index
换句话说,切分顺序应该是:
text
先看结构
再看语义
最后才看长度
不推荐这样做:
text
拿到文档 → 直接每 500 字切一次 → embedding → 入库
更推荐这样做:
text
拿到文档
↓
识别标题、章节、表格、代码块、条款、业务实体
↓
按自然语义边界切分
↓
过长的部分再用长度或语义切分兜底
4. 常见切分方式对比
4.1 固定长度切分
固定长度切分就是按字符数或 token 数硬切。
例如:
text
chunk_size = 500
chunk_overlap = 50
优点:
- 实现简单
- 成本低
- 对无明显结构的长文本比较方便
缺点:
- 容易切断语义
- 不理解标题、条款、表格、代码块
- 可能导致召回内容不完整
适合:
- 日志文本
- 流水记录
- 无结构长文本
- 粗粒度全文搜索
不适合:
- 合同
- 政策
- FAQ
- 技术文档
- 代码
- Excel / 表格数据
4.2 RecursiveCharacterTextSplitter 递归切分
递归切分的思路是:优先按照更自然的分隔符切分,如果切出来的块仍然太长,再继续使用更细粒度的分隔符。
例如:
text
先按章节切
如果太长,再按段落切
如果还太长,再按句子切
如果还太长,最后按字符切
中文场景下可以设置类似分隔符:
python
from langchain_text_splitters import RecursiveCharacterTextSplitter
splitter = RecursiveCharacterTextSplitter(
chunk_size=600,
chunk_overlap=80,
separators=[
"\n## ",
"\n### ",
"\n\n",
"。", "!", "?", ";",
"\n",
" ",
""
]
)
它适合:
- 普通知识库
- 博客文章
- 产品说明
- 课程讲义
- Markdown 文档
这个方法比固定长度切分更自然,因为它会尽量保留段落和句子的完整性。
4.3 按标题和章节切分
对于结构明显的文档,标题和章节通常就是最自然的切分边界。
例如:
text
1. 产品介绍
2. 登录流程
3. 权限说明
4. 常见问题
可以先按一级、二级、三级标题切成 parent chunk,再对过长的章节继续切成 child chunk。
结构可以是:
text
Parent Chunk:
第 3 章:权限说明
Child Chunk 1:
3.1 普通用户权限
Child Chunk 2:
3.2 管理员权限
Child Chunk 3:
3.3 内部成员权限
检索时可以采用 small-to-big retrieval:
text
用小 chunk 做精确检索
返回时带上 parent chunk 或相邻 chunk
这样做的好处是:
text
小 chunk 检索更精准
大 chunk 回答更完整
适合:
- 技术文档
- 项目文档
- 产品手册
- 安装说明
- 课程讲义
- Markdown / HTML 文档
4.4 语义切分 Semantic Chunking
语义切分不是按固定长度切,而是根据句子之间的语义相似度来判断哪里应该断开。
例如:
text
A 段讲登录
B 段讲注册
C 段讲密码重置
D 段讲支付
语义切分可能会把登录、注册、密码重置放在一组,把支付单独切出去。
适合:
- 长文章
- 学术论文
- 访谈稿
- 会议纪要
- 法律解释文档
- 业务分析报告
缺点:
- 成本更高
- 切分速度更慢
- 有时会切出过大的 chunk
- 需要设置最大 token 限制
比较稳妥的做法是:
text
先按标题 / 段落粗切
再对长段落做语义切分
最后加最大 token 限制
4.5 Sentence Window 切分
Sentence Window 的思路是:
text
embedding 时只嵌入一句话
生成答案时带上这句话前后的上下文
例如原文:
text
句子 1:用户可以上传资源。
句子 2:只有管理员可以删除资源。
句子 3:普通用户只能下载公开资源。
句子 4:内部成员可以访问本州资源。
索引时:
text
chunk = 句子 2
metadata.window = 句子 1 + 句子 2 + 句子 3
检索时用句子 2 精准命中,回答时把窗口一起交给 LLM。
适合:
- FAQ
- 客服知识库
- 制度问答
- 细粒度事实查询
它解决的是:
text
小粒度检索精准,但上下文不足
4.6 表格 / Excel 数据切分
表格和 Excel 不能简单当成普通长文本切。
例如电影数据:
text
电影名 | 类型 | 地区 | 上映时间 | 评分 | 简介 | 演员 | 评论
对于这种数据,更合理的方式是:
text
一行 = 一个业务实体 = 一个 chunk
例如:
text
chunk_id: movie_001
movie_name: 霸王别姬
genre: 剧情
score: 9.6
actors: 张国荣, 巩俐
summary: ...
如果简介、剧情、评论很长,可以继续拆成:
text
movie_001_summary
movie_001_plot_1
movie_001_plot_2
movie_001_review_summary
但是它们要共享同一组 metadata:
text
movie_id
movie_name
genre
year
country
score
也就是说,Excel / 数据库类 RAG 的重点不是 chunk_size,而是:
text
实体建模 + 字段 metadata + 过滤检索
二、不同业务场景下应该如何切分
1. 客服 FAQ / 售后知识库
典型问题:
text
怎么退款?
怎么修改手机号?
为什么订单被取消?
会员怎么续费?
推荐切分:
text
一个问题 + 一个答案 = 一个 chunk
示例:
text
Q: 如何申请退款?
A: 用户可以在订单详情页点击申请退款...
推荐配置:
| 项目 | 建议 |
|---|---|
| chunk 粒度 | 一个问答对 |
| chunk_size | 100 - 300 tokens |
| overlap | 基本不需要,或者很小 |
| 检索方式 | BM25 + Vector + Reranker |
推荐 metadata:
text
product
business_line
question_type
user_type
version
effective_date
客服场景里 BM25 很重要,因为用户的问题中经常有精确业务词,例如:
text
退款
发票
手机号
订单号
会员
2. 企业制度 / 政策 / 合同文档
典型问题:
text
试用期离职工资怎么算?
这个合同里违约责任是什么?
甲方有哪些义务?
年假规则是什么?
推荐切分:
text
按章、节、条、款切分
例如:
text
第二章 请假制度
第 5 条 年假规则
5.1 员工连续工作满一年...
5.2 年假不可折现...
推荐配置:
| 项目 | 建议 |
|---|---|
| chunk 粒度 | 一个条款 / 相邻条款 |
| chunk_size | 300 - 800 tokens |
| overlap | 50 - 120 tokens |
| 检索方式 | 条款级向量索引 + BM25 + parent-child retrieval |
推荐 metadata:
text
doc_type
chapter
article_no
clause_no
effective_date
company
department
version
page_no
合同和制度类文档特别需要上下文扩展,因为一个问题可能涉及多个条款:
text
违约责任条款
赔偿条款
解除合同条款
争议解决条款
所以推荐:
text
先召回条款级 chunk
再扩展同一章节下的相邻条款
最后让 LLM 基于证据回答
3. 技术文档 / API 文档 / 开发手册
典型问题:
text
这个接口怎么调用?
这个参数是什么意思?
如何配置 Redis?
这个错误码怎么解决?
推荐切分:
text
一个接口说明 = 一个完整 chunk
一个配置项说明 = 一个 chunk
一个错误码说明 = 一个 chunk
接口文档不应该被硬切开。比如下面内容应该尽量作为一个整体:
text
POST /api/user/login
描述:
用户登录接口
请求参数:
username
password
响应:
token
expireTime
错误码:
401
403
推荐 metadata:
text
module
api_path
method
version
language
framework
error_code
heading_path
技术文档一定要重视关键词检索,因为里面有大量精确符号:
text
RedisTemplate
MessageWindowChatMemory
@PostMapping
401
NullPointerException
/api/user/login
推荐策略:
text
精确符号 / API path / 类名 → BM25 权重大
自然语言问题 → 向量权重大
最后 reranker 重排
4. 源码 RAG / 代码问答
典型问题:
text
这个方法是干什么的?
这个类在哪里被调用?
为什么这里会空指针?
如何修改这段逻辑?
代码不能用普通文本切法。
推荐切分:
text
类级别
方法级别
函数级别
接口级别
配置文件级别
Java 项目可以这样切:
text
UserService.java
- class metadata
- method getLoginUser()
- method updateUser()
- method deleteUser()
每个方法 chunk 应该包含:
text
方法签名
注解
方法体
关键 import
所在类名
包名
调用关系 metadata
源码 RAG 最好不是单纯向量搜索,而是:
text
symbol index
+
BM25
+
vector search
+
AST / call graph
例如:
text
问题:getLoginUser 在哪里被调用?
这个问题应该优先走符号索引和引用关系,而不是向量搜索。
如果问题是:
text
用户登录后权限是怎么校验的?
这类问题才更适合:
text
向量检索 + 调用链扩展
5. 学术论文 / 长报告 / 项目文档
典型问题:
text
这篇论文的方法是什么?
实验结果说明了什么?
这个项目的风险有哪些?
报告里对市场趋势怎么看?
推荐切分:
text
标题层级切分
摘要单独索引
结论单独索引
图表说明单独索引
长段落语义切分
可以建立多个索引:
text
summary_index:
摘要、结论、章节摘要
detail_index:
正文段落
table_index:
表格、实验结果
figure_index:
图注、图表说明
推荐流程:
text
先查 summary_index 判断相关章节
再查 detail_index 找证据
最后查 table_index 补充数据
6. 商品、电商、招聘、简历匹配
典型问题:
text
推荐适合 Java 后端的岗位
找 3 年经验 Spring Boot 岗位
这款商品适合学生吗?
有哪些 500 元以内的耳机?
这类业务最重要的是:
text
结构化字段过滤 + 向量语义召回
不要把所有字段拼成一段文本后只做向量搜索。
例如岗位数据应该有结构化字段:
text
job_id
title
city
salary_min
salary_max
experience
skills
description
company
用户问:
text
重庆 Java 后端 15k 以上,要求 Spring Boot 和 Redis
应该先过滤:
text
city = 重庆
salary_max >= 15000
skills contains Java / Spring Boot / Redis
再做语义匹配。
7. 新闻 / 公告 / 时效性知识库
典型问题:
text
最近有什么政策变化?
最新公告是什么?
这个项目最近进展如何?
推荐切分:
text
一条新闻 / 一条公告 = 一个主 chunk
长公告按小标题切
metadata 必须包含:
text
publish_time
effective_time
source
region
topic
status
索引优化重点:
text
时间衰减排序
增量索引
旧版本归档
同主题聚合
否则用户问"最新政策",系统可能召回旧政策。
8. 扫描 PDF / 表格 / 图片型文档
这类文档最重要的问题不是 chunk,而是解析质量。
需要处理:
text
OCR
页眉页脚
脚注
双栏排版
表格
图片
图注
推荐流程:
text
1. OCR
2. 去除页眉页脚
3. 保留页码
4. 表格转 Markdown table 或 JSON
5. 图片生成 caption
6. 图表和正文建立引用关系
表格可以按以下方式切:
text
一个表格 = 一个 chunk
一行 = 一个 chunk
一组业务行 = 一个 chunk
选择哪种取决于用户怎么问。
例如财务报表:
text
用户问某一年营收 → 行级 chunk
用户问整体趋势 → 表级 chunk
三、chunk_size 和 overlap 应该怎么设置
经验表如下:
| 场景 | 推荐 chunk 粒度 | chunk_size | overlap |
|---|---|---|---|
| FAQ / 客服 | 一个问答对 | 100-300 tokens | 0-30 |
| 普通知识库 | 一个段落 / 小节 | 300-700 tokens | 50-100 |
| 技术文档 | 一个接口 / 一个配置项 / 一个小节 | 400-1000 tokens | 80-150 |
| 合同 / 制度 | 一个条款 / 相邻条款 | 300-800 tokens | 50-120 |
| 学术论文 | 一个小节 / 语义段 | 500-1200 tokens | 80-150 |
| 代码 | 一个函数 / 一个类片段 | 40-100 行 | 10-20 行 |
| Excel / 商品 / 岗位 | 一个实体 / 一行记录 | 不按固定 token | 通常不需要 |
| 新闻公告 | 一条公告 / 一个小标题 | 300-800 tokens | 50-100 |
需要注意:overlap 不是越大越好。
它的作用是防止语义断裂,但过大的 overlap 会带来:
text
索引膨胀
重复召回
成本增加
上下文里重复内容变多
推荐:
text
普通文档:10% - 20%
FAQ:0% - 10%
代码:按行 overlap
合同:按条款上下文 overlap
四、metadata 为什么比 chunk_size 更重要
一个高质量 chunk 不应该只有 text,还应该包含丰富的 metadata。
例如:
json
{
"chunk_id": "policy_2026_leave_005",
"doc_id": "policy_2026_leave",
"parent_id": "policy_2026_leave_chapter_2",
"title": "员工请假制度",
"heading_path": "第二章/第五条/年假规则",
"text": "员工连续工作满一年后,可享受带薪年假...",
"doc_type": "policy",
"department": "HR",
"effective_date": "2026-01-01",
"version": "v3",
"page": 12,
"tenant_id": "company_a",
"permission": ["employee", "hr_admin"]
}
metadata 可以解决很多问题:
text
权限过滤
租户隔离
版本过滤
时间过滤
业务线过滤
文档类型过滤
精确定位引用
在真实系统里,metadata 往往比 chunk_size 更关键。
五、索引优化应该怎么做
进一步讨论索引优化时,核心结论是:
索引优化不是只调向量数据库参数,而是优化整个检索链路。
可以分成四层:
text
数据层优化
检索层优化
排序层优化
上下文扩展
1. 数据层优化:让 chunk 更容易被召回
数据层优化包括:
text
清洗噪声
去重
补标题
补 metadata
补关键词
补摘要
补同义词
例如原始 chunk:
text
支持该操作。
这个 chunk 几乎没有检索价值。
可以改造成:
text
【资源管理 / 权限控制 / 删除资源】
管理员可以删除资源,普通用户只能查看和下载公开资源。
这样 embedding 后的语义会更明确,也更容易被召回。
2. 检索层优化:BM25 + Vector 混合检索
不要只做向量搜索。
更推荐:
text
BM25 / keyword
+
dense vector
+
metadata filter
原因是很多业务场景里有大量精确词:
text
类名
接口名
错误码
合同编号
产品型号
岗位技能
疾病名称
药品名称
纯向量搜索可能会召回语义相似但关键词不准确的内容。
例如技术文档里用户搜:
text
NullPointerException
/api/user/login
RedisTemplate
这些内容应该优先依赖 BM25 或符号索引。
3. 排序层优化:引入 Reranker
第一阶段召回通常是粗召回。
流程可以是:
text
BM25 top 50
Vector top 50
↓
合并去重
↓
RRF 融合
↓
Reranker 重排
↓
取 top 3 / top 5
Reranker 的作用是判断:
text
这个 chunk 是否真的能回答用户的问题?
它不是负责从全量知识库找内容,而是负责从候选内容中挑出最相关的部分。
4. 上下文扩展:小 chunk 检索,大 chunk 回答
推荐做法:
text
检索时:用小 chunk
回答时:扩展 parent / neighbor chunk
例如命中:
text
chunk_12
可以同时取:
text
chunk_11
chunk_12
chunk_13
parent_section
这样可以兼顾:
text
小 chunk 的精准召回
大上下文的完整回答
5. 向量索引本身如何优化
如果使用 Qdrant、Milvus、Weaviate、FAISS、Elasticsearch、OpenSearch,常见底层索引是 ANN 近似最近邻。
常见优化点:
text
HNSW 参数
向量维度
索引分片
payload filter
tenant 隔离
量化压缩
冷热数据分层
以 HNSW 为例,常见参数包括:
text
m
ef_construct
ef_search
大致理解:
text
m 越大:
图连接越多,召回更好,但内存更高
ef_construct 越大:
建索引更慢,但索引质量更好
ef_search 越大:
查询更准,但延迟更高
工程上的取舍:
text
低延迟场景:
topK 小
ef_search 低一些
reranker 处理较少候选
高准确率场景:
topK 大
ef_search 高一些
reranker 重排更多候选
多租户场景:
tenant_id 建 payload index
或者使用多 collection
强过滤场景:
常用 filter 字段必须建索引
六、为什么会进一步讨论 Reranker
在讨论完文档切分和索引优化后,自然出现了一个问题:
即使系统通过 BM25 和向量检索召回了一批 chunk,怎么保证真正有用的内容排在最前面?
这就是 Reranker 要解决的问题。
向量检索可以找到"语义相关"的内容,但它不一定能判断"哪个内容最能回答问题"。
例如用户问:
text
为什么 Redis 限流要用 Lua 脚本?
向量检索可能召回:
text
Redis 基础介绍
令牌桶算法
RedisTemplate 使用方式
Redis + Lua 原子性
分布式锁 setnx
这些都和 Redis 或限流有关系,但真正直接回答问题的是:
text
Redis + Lua 可以保证判断令牌和扣减令牌这两个操作的原子性。
Reranker 的价值就在于:把真正能回答问题的 chunk 排到前面。
七、Reranker 是什么
接着进一步讨论的问题是:
Reranker 是什么?
Reranker,中文通常叫"重排模型"或"重排序器"。
它的作用是:
第一阶段先粗略召回一批可能相关的文档,第二阶段再让 Reranker 重新判断这些文档和用户问题的真实相关性,把最有用的内容排到最前面。
Reranker 不是负责"从全库找资料"的,而是负责:
text
从已经召回的候选 chunk 里,挑出最适合回答问题的 chunk
1. Reranker 在 RAG 流程中的位置
完整流程:
text
用户问题
↓
第一阶段:粗召回
- 向量检索 top 50
- BM25 检索 top 50
↓
候选文档合并去重
↓
第二阶段:Reranker 重排
- 判断每个 chunk 和问题的相关性
- 重新打分
- 重新排序
↓
取 top 3 / top 5 给 LLM
↓
生成答案
可以理解成:
text
Retriever:海选
Reranker:复试
LLM:写最终答案
2. 向量检索和 Reranker 的区别
向量检索
向量检索通常是:
text
query → embedding 向量
chunk → embedding 向量
比较两个向量的相似度
特点:
text
速度快
适合大规模召回
能处理语义相似
但是判断比较粗
Reranker
Reranker 通常会把:
text
用户问题 + 候选 chunk
一起输入模型,让模型判断:
text
这个 chunk 能不能回答这个问题?
相关性有多高?
示例:
text
Query:
为什么 Redis 限流要用 Lua 脚本?
Document A:
Redis 是一种基于内存的 key-value 数据库。
Score: 0.31
Document B:
Redis + Lua 脚本可以保证令牌判断和扣减操作的原子性。
Score: 0.94
Reranker 会把 Document B 排到前面。
3. 为什么 Reranker 通常更准
普通向量检索多采用 bi-encoder 思路:
text
query 单独编码
document 单独编码
然后比较两个向量距离
它速度快,因为 document embedding 可以提前算好。
但它没有真正逐字逐句比较 query 和 document。
Reranker 常见方式是 cross-encoder:
text
输入 = [用户问题, 候选文档]
输出 = 相关性分数
它把问题和文档放在一起理解,所以判断更细。
类比:
text
向量检索:
看两个人的整体画像像不像
Reranker:
让两个人坐下来详细对话,看他们到底匹不匹配
4. Reranker 能解决哪些问题
4.1 解决"看起来相关,但不能回答"的问题
比如用户问:
text
Java 中 CompletableFuture 和 Future 有什么区别?
向量检索可能召回:
text
Future 基础介绍
线程池基础介绍
CompletableFuture 异步编排
Runnable 和 Callable 区别
Reranker 会更倾向于把真正比较 Future 和 CompletableFuture 区别的文档排到前面。
4.2 解决关键词相似但语义不匹配的问题
例如:
text
苹果公司的创始人是谁?
知识库里可能有:
text
苹果是一种水果...
苹果公司由 Steve Jobs 等人创立...
Reranker 会根据完整问题判断用户问的是 Apple Inc.,而不是水果。
4.3 解决长文档 chunk 排序不准的问题
长文档切成很多 chunk 后,向量搜索常常会召回一堆半相关片段。
Reranker 可以进一步判断:
text
哪个 chunk 最直接支持答案
哪个 chunk 只是背景信息
哪个 chunk 完全跑偏
5. Reranker 的局限性
Reranker 有用,但不能解决所有问题。
它的主要局限有:
text
更慢
更贵
不能替代第一阶段召回
最重要的一点是:
Reranker 只能重排已经召回的候选内容。
如果第一阶段根本没有召回正确 chunk,Reranker 也救不了。
所以要记住:
text
Retriever 负责"有没有"
Reranker 负责"排不排前"
八、Reranker 通常有哪些做法
接着讨论的问题是:
Reranker 通常有哪些做法?
常见做法可以分成六类:
text
1. 规则打分型 Reranker
2. RRF / 分数融合型 Reranker
3. Cross-Encoder Reranker
4. LLM-as-Reranker
5. Learning-to-Rank
6. Late Interaction,例如 ColBERT
1. 规则打分型 Reranker
规则打分型 Reranker 不一定使用模型,而是根据业务规则重新加权。
例如用户问:
text
重庆 Java 后端 15k 以上岗位
可以设计:
text
最终分数 =
语义相似度 * 0.4
+ 城市匹配 * 0.2
+ 技能匹配 * 0.2
+ 薪资匹配 * 0.1
+ 更新时间 * 0.1
候选岗位:
text
岗位 A:重庆 + Java + Spring Boot + 18k
岗位 B:上海 + Java + Spring Boot + 30k
岗位 C:重庆 + Python + 16k
如果只看向量相似度,岗位 B 可能也很高。
但业务规则会把岗位 A 排到最前面。
适合:
text
招聘系统
商品推荐
电影推荐
本地生活
企业知识库权限过滤
新闻公告时效排序
优点:
text
快
可控
容易解释
不需要额外模型
缺点:
text
需要人工设计规则
语义判断能力弱
规则多了维护成本高
2. RRF / 分数融合型 Reranker
RRF 全称 Reciprocal Rank Fusion,更准确地说,它是 rank fusion,但在工程里常被放进 rerank 流程。
假设有两路检索:
text
BM25 检索 top 10
向量检索 top 10
BM25 排名:
text
A, B, C, D
向量检索排名:
text
C, A, E, F
RRF 会综合两个排名,让同时在多个检索器中排名靠前的文档得分更高。
简单公式:
text
score(d) = Σ 1 / (k + rank_i(d))
其中:
text
rank_i(d) 表示文档 d 在第 i 个检索器里的排名
k 通常取 60 左右,用来平滑分数
适合:
text
技术文档
API 文档
代码检索
合同条款
FAQ
企业知识库
优点:
text
实现简单
速度快
不需要训练模型
比单独向量检索更稳定
缺点:
text
只是融合排名
不能真正理解 query 和 chunk 的细节关系
3. Cross-Encoder Reranker
这是 RAG 中最常见、效果也很好的做法。
输入:
text
用户问题 + 候选 chunk
输出:
text
相关性分数
示例:
text
Query:
为什么 Redis 限流要用 Lua 脚本?
Document A:
Redis 是一个高性能 key-value 数据库。
Document B:
Redis + Lua 可以保证令牌判断和扣减操作的原子性。
Cross-Encoder 输出:
text
Query + Document A → score = 0.31
Query + Document B → score = 0.94
适合:
text
通用 RAG 问答
企业知识库
技术文档
法律合同
论文问答
客服问答
优点:
text
相关性判断准确
效果通常明显好于纯向量检索
接入简单
缺点:
text
比向量检索慢
候选 chunk 太多时成本高
通常只适合重排 top 20 - top 100
典型流程:
text
向量 / BM25 先召回 top 50
↓
Cross-Encoder reranker 重排
↓
取 top 5 给 LLM
4. LLM-as-Reranker
也可以直接让大模型来做重排。
例如 prompt:
text
用户问题:
为什么 Redis 限流要用 Lua 脚本?
下面有 10 个候选文档,请判断哪些最能回答问题,
按照相关性从高到低排序,并说明原因。
LLM 可能输出:
text
1. chunk C:直接解释 Lua 保证原子性
2. chunk B:介绍令牌桶算法背景
3. chunk D:介绍 RedisTemplate,相关性较弱
适合:
text
复杂分析类问题
多文档综合判断
项目文档分析
法律条款综合判断
论文综述
商业报告分析
优点:
text
理解能力强
能处理复杂推理
可以解释为什么这样排序
缺点:
text
慢
贵
输出可能不稳定
候选数量不能太多
所以一般不建议直接让 LLM rerank 100 个 chunk。
更推荐:
text
BM25 + Vector 召回 top 50
普通 Reranker 排到 top 10
LLM 从 top 10 中选择最有用证据
5. Learning-to-Rank 排序模型
Learning-to-Rank 是搜索推荐系统里更传统、也更工程化的一类方法。
它会把多个特征放进排序模型:
text
向量相似度
BM25 分数
标题是否匹配
关键词命中数量
文档更新时间
用户权限
点击率
历史采纳率
业务类型匹配
chunk 长度
然后训练排序模型:
text
输入:query + document + features
输出:排序分数
常见模型:
text
LambdaMART
XGBoost Ranker
LightGBM Ranker
神经网络排序模型
适合:
text
搜索系统
电商推荐
招聘匹配
广告排序
内容推荐
大规模企业知识搜索
优点:
text
可以融合大量业务特征
适合大规模生产系统
可以通过用户反馈持续优化
缺点:
text
需要训练数据
工程复杂度高
冷启动比较难
6. Late Interaction Reranker,例如 ColBERT
Late Interaction 介于普通向量检索和 Cross-Encoder 之间。
普通向量检索是:
text
一个 query → 一个向量
一个 document → 一个向量
Late Interaction 的思路是:
text
query 里的每个 token 有自己的向量
document 里的每个 token 也有自己的向量
最后做细粒度匹配
可以理解成:
text
普通向量检索:
看整体像不像
Cross-Encoder:
把 query 和 document 放一起深度理解
Late Interaction:
保留 token 级别匹配能力,但尽量保持检索效率
适合:
text
大规模搜索
技术文档检索
长文档检索
需要兼顾速度和准确率的 RAG
优点:
text
比普通向量检索精细
比 Cross-Encoder 更容易扩展到大规模
缺点:
text
实现复杂
索引成本更高
工程接入门槛更高
7. 多阶段 Reranker 组合方案
真实业务里往往不是只用一种 Reranker,而是多阶段组合。
一个成熟链路可以是:
text
用户问题
↓
Query Rewrite / Query Expansion
↓
第一阶段粗召回
- BM25 top 50
- Vector top 50
- Metadata filter
↓
RRF 融合
↓
Cross-Encoder Reranker
↓
业务规则加权
↓
上下文扩展
- parent chunk
- neighbor chunk
↓
LLM 生成答案
最终分数可以设计成:
text
final_score =
reranker_score * 0.6
+ bm25_score * 0.1
+ vector_score * 0.1
+ business_score * 0.15
+ freshness_score * 0.05
不同业务场景权重不同。
九、不同业务场景应该选择哪种 Reranker
| 场景 | 推荐做法 |
|---|---|
| 普通知识库问答 | Vector / BM25 + Cross-Encoder |
| FAQ 客服 | BM25 + Cross-Encoder + 类目规则 |
| 技术文档 | BM25 + Vector + RRF + Cross-Encoder |
| 代码问答 | Symbol Search + BM25 + Vector + 规则重排 |
| 法律合同 | 条款召回 + Cross-Encoder + parent chunk 扩展 |
| 招聘匹配 | 结构化过滤 + 业务规则 + Learning-to-Rank |
| 商品推荐 | 结构化过滤 + 业务分数 + 向量语义 |
| 新闻公告 | 语义相关性 + 时间衰减 |
| 学术论文 | Summary index + detail index + Cross-Encoder |
| 复杂分析 | 普通 reranker 后再用 LLM reranker |
十、把整个 RAG 检索链路串起来
最终完整流程可以这样理解:
text
用户问题
↓
Query Rewrite / Query Expansion
↓
Metadata Filter
↓
Hybrid Retrieval
- BM25
- Vector Search
- Symbol Search
↓
Merge + Deduplicate
↓
RRF / Score Fusion
↓
Cross-Encoder Reranker
↓
Business Rule Reranker
↓
Context Expansion
- parent chunk
- neighbor chunk
- sentence window
↓
LLM Generate Answer
↓
Answer with citations / evidence
这条链路里,每一层职责不同:
| 模块 | 作用 |
|---|---|
| 文档切分 | 把知识变成适合检索的单元 |
| metadata | 支持过滤、权限、版本、溯源 |
| BM25 | 解决关键词和精确匹配 |
| Vector Search | 解决语义召回 |
| RRF | 融合多路召回结果 |
| Reranker | 判断哪些候选最能回答问题 |
| Context Expansion | 补充上下文,避免答案不完整 |
| LLM | 基于证据生成最终回答 |
十一、推荐的工程落地路线
如果从 0 到 1 做 RAG,可以按这个顺序升级。
第一版:Vector Search
text
vector search top 5
优点:简单。
问题:容易召回半相关内容,排序不稳定。
第二版:BM25 + Vector + RRF
text
BM25 top 20
Vector top 20
RRF 融合 top 10
效果通常比纯向量检索稳定很多。
第三版:加入 Cross-Encoder Reranker
text
BM25 top 50
Vector top 50
RRF top 30
Cross-Encoder rerank top 5
这是大多数 RAG 项目中性价比很高的方案。
第四版:加入业务规则和权限过滤
text
reranker_score
+
权限
+
时间
+
版本
+
业务字段
适合真实业务上线。
第五版:基于反馈做 Learning-to-Rank
当系统有用户点击、点赞、采纳、人工标注数据后,可以训练排序模型,让排序效果持续优化。
十二、结合电影 Excel RAG 场景的落地设计
前面对话中也结合了电影 Excel 数据进行说明。
这类数据通常长这样:
text
电影名 | 类型 | 地区 | 上映时间 | 评分 | 简介 | 演员 | 评论
不能简单把整个 Excel 拼成文本后固定切分。
1. summary_docs 应该如何设计
一部电影一个 summary chunk:
text
电影名:xxx
类型:剧情 / 喜剧
地区:中国
年份:2020
评分:8.7
主演:xxx
简介:xxx
适合回答:
text
推荐电影
找评分高的电影
某类型电影有哪些
某演员出演过哪些电影
metadata:
python
{
"movie_name": "xxx",
"genre": "剧情",
"country": "中国",
"year": 2020,
"score": 8.7,
"sheet_name": "xxx"
}
2. content_docs 应该如何设计
如果电影有长简介、剧情、影评,可以继续切:
text
movie_id = xxx
chunk_type = plot / review / analysis
例如:
text
霸王别姬_plot_1
霸王别姬_review_summary
霸王别姬_actor_info
适合回答:
text
这部电影讲了什么?
为什么评分高?
适合什么人看?
主题是什么?
3. 查询时如何结合 filter、vector search 和 reranker
如果用户问:
text
推荐几部 9 分以上的国产剧情片
不要只走向量搜索。
应该先做 metadata filter:
text
score >= 9
country = 中国
genre = 剧情
然后再结合向量搜索:
text
query = 推荐 高质量 电影
如果用户问:
text
有没有类似《霸王别姬》的电影?
可以:
text
先找到《霸王别姬》的 summary embedding
再向量搜索相似电影
再用 genre / country / theme 过滤
如果用户问:
text
这部电影为什么评分高?
应该:
text
先用 movie_name 精确过滤
再查 content_docs 里的 plot / review / analysis
如果用户问:
text
推荐几部适合学习英语口语的电影
Reranker 可以根据以下维度重新排序:
text
对话是否生活化
台词是否多
语速是否适中
发音是否清晰
是否适合模仿
而不是只看评分高低。
十三、最终总结
1. 文档切分的核心结论
文档切分不是把文本切碎,而是设计检索单元。
可以这样记:
text
FAQ:一个问答一块
合同:一个条款一块
技术文档:一个接口 / 一个配置项一块
代码:一个函数 / 一个类一块
Excel:一个业务实体 / 一行一块
论文:一个小节 / 一个语义段一块
新闻:一条新闻 / 一个公告一块
2. 索引优化的核心结论
不要只做 vector search。
更合理的是:
text
metadata filter
+
BM25 keyword search
+
dense vector search
+
RRF fusion
+
Reranker
+
parent / neighbor chunk expansion
3. Reranker 的核心结论
Reranker 负责把第一阶段召回出来的候选内容重新排序。
一句话:
text
Retriever 负责"有没有"
Reranker 负责"排不排前"
LLM 负责"怎么回答"
4. Reranker 常见做法
text
1. 规则重排
2. RRF / 分数融合
3. Cross-Encoder 重排
4. LLM 重排
5. Learning-to-Rank
6. Late Interaction,例如 ColBERT
工程上最常用、性价比最高的是:
text
BM25 + Vector 召回
↓
RRF 融合
↓
Cross-Encoder Reranker
↓
业务规则加权
↓
取 topK 给 LLM
5. RAG 工程化的整体认知
真正好的 RAG 不是:
text
文档 → 固定切分 → embedding → vectorDB
而是:
text
业务问题分析
↓
知识单元建模
↓
结构化切分
↓
metadata 设计
↓
多路索引
↓
混合检索
↓
重排
↓
上下文扩展
↓
可评估优化
最终可以用一句话概括:
切分决定知识有没有被正确表达,索引决定正确知识能不能被准确召回,重排决定正确知识能不能排到前面。