4.2 索引与分块
切分策略决定检索上限——从固定长度到语义切分,再到父子文档、多索引与嵌入选型的工程决策
分块:RAG 最被低估的环节
你可以把 RAG 想象成一条流水线:嵌入模型决定"距离",但切分策略决定了被嵌入的是什么。一个错误的切分——把表格的标题与数据分离、把条例的条件与结论切到两块——会让再强的检索器也无能为力。
90% 的 RAG "检索不准"问题其实是切分问题。在升级嵌入模型或加 reranker 之前,先检查你的 chunk 是否保留了完整的语义单元。
四种主流切分策略
1. 固定大小切分(Fixed-size)
最朴素的方案:每 N 个 token 切一块,相邻块之间保留 M 个 token 的重叠以避免边界问题。
- 推荐参数:中文 300-500 字符 / 英文 400-512 token;重叠 10-20%
- 优势:简单、可预测、易于批量处理
- 劣势:会切断句子、段落和语义单元
2. 递归字符切分(Recursive Character)
按层级分隔符逐级拆分——先按 \n\n,块太大则按 \n,再按 。、;、,,最后按字符。LangChain 的默认策略。
from langchain_text_splitters import RecursiveCharacterTextSplitter
splitter = RecursiveCharacterTextSplitter(
chunk_size=400,
chunk_overlap=60,
separators=["\n\n", "\n", "。", ";", ",", " ", ""],
length_function=len,
)
chunks = splitter.split_text(document)3. 语义切分(Semantic Chunking)
计算相邻句子的嵌入相似度,在相似度骤降处切分。直觉是:主题切换点会在向量空间留下"缺口"。
其中 是第 个句子的嵌入, 是滑窗内相似度的均值与标准差, 通常取 1.0-1.5。论文报告可带来约 9% 的召回提升,但计算成本也显著增加。
4. 父子文档索引(Parent-Child)
用小块检索、返回大块给 LLM——兼顾检索精度与上下文完整性。
对比基线可达 65% 胜率,是生产环境中强烈推荐的策略。
策略对比表
| 策略 | 切分单元 | 适用场景 | 典型代价 |
|---|---|---|---|
| Fixed-size | 固定 token | 短文本、纯文字段落 | 语义断裂 |
| Recursive | 自然分隔符 | 结构化文档(默认) | 仍可能切断长句 |
| Semantic | 语义边界 | 长文章、百科 | 需额外嵌入计算 |
| Parent-Child | 小块索引+大块召回 | 技术手册、规章 | 索引体积加倍 |
| Late Chunking | 先整文编码后切向量 | 上下文依赖极强 | Jina 2024 新方法 |
研究生手册、产品规章这类"层级清晰、条目结构化"的文档,先按章节结构拆分再做 Recursive 切分比任何"通用最优切法"都更有效。不要迷信算法,先研究文档本身。
嵌入模型选型
Embedding 模型的核心权衡是体积 vs. 质量 vs. 支持语言。主流选择:
# 小而快,适合课堂演示(GPU 非必需)
model_name = "BAAI/bge-small-zh-v1.5"
# 参数:约 24M;维度 512;单 V100 可千 QPS优势:下载快(<100MB)、CPU 可跑、免 API 配额。劣势:语义能力弱于大模型。
# 中文 MTEB 榜单常驻前三
model_name = "BAAI/bge-large-zh-v1.5"
# 参数:约 326M;维度 1024优势:中文 MTEB 综合分最高;短文本(<512 token)效果极佳。劣势:长文档需先切分。
# 同时支持稠密/稀疏/多向量的统一模型
model_name = "BAAI/bge-m3"
# 参数:约 568M;支持 8192 token 上下文,100+ 语言优势:单模型覆盖多语言、多粒度(稠密 + 稀疏 + ColBERT)。劣势:体积大、推理慢。
决策流程:先在 MTEB-CN 或 C-MTEB 上看中文综合分 → 检查是否支持目标语言 → 评估最大输入长度(应 ≥ chunk_size)→ 测试推理吞吐能否满足 SLA。
索引结构
平面向量索引
最基础的方案,所有向量存入单一索引,用 ANN 算法(HNSW、IVF-PQ)加速搜索。FAISS 是研究首选:
import faiss
import numpy as np
dim = 1024
index = faiss.IndexFlatIP(dim) # 暴力搜索,精度上限
# 大规模场景用分层索引:
# index = faiss.IndexHNSWFlat(dim, 32) # HNSW,百万级向量
index.add(embeddings.astype(np.float32))
scores, ids = index.search(query_emb, k=5)多索引策略
当文档有多种属性或视角时,为每个视角建独立索引并路由:
| 索引视角 | 嵌入对象 | 查询场景 |
|---|---|---|
| 原文块 | 段落原文 | 细节查询 |
| 摘要 | 每章/每条目的摘要 | 概览类问题 |
| 关键词 | 提取的实体和术语 | 精确术语匹配 |
| 问答对 | 预生成 QA 对的问题 | FAQ 式查询 |
在用户查询到来时,用一个 Router(可以是规则或 LLM)决定走哪个或哪几个索引。
分层索引(RAPTOR)
递归地对文本块聚类并生成摘要,形成树状索引,兼顾"全局概述"与"细节查询":
[全书摘要] ← Level 2(高层主题)
/ \
[章摘要 A] [章摘要 B] ← Level 1(子主题)
/ \ / \
[块1][块2] [块3][块4] ← Level 0(原始块)在查询时可并行检索多层,用跨层检索结果组合上下文,适合对长文档做概括类问答。
本节小结
| 维度 | 推荐默认 |
|---|---|
| 分块策略 | Recursive + 按章节预切 |
chunk_size | 中文 300-500 字符 / 英文 400-512 token |
chunk_overlap | 10-20% |
| 嵌入模型 | BAAI/bge-m3(多语言)或 bge-large-zh(纯中文) |
| 索引结构 | FAISS HNSW(百万级)或 Chroma(原型) |
| 进阶 | 父子文档 + 多索引路由 |