很多人第一次做 RAG,默认流程都是:
- 文档切 chunk
- 生成 embedding
- 向量检索 top_k
- 拼 prompt
- 让模型回答
这条路径能跑,但往往不稳。真正让 RAG 质量提升一个台阶的,通常是三件事:
- chunking 做得更合理
- 召回从单路 dense 变成多路召回
- 用真正的 rerank 做精排
一、Chunking 不是“切短一点”这么简单
chunking 的目标不是为了凑 embedding 输入长度,而是为了让“一个 chunk 刚好能独立成为证据”。
一个好的 chunk,通常要满足:
- 语义相对完整
- 粒度不要太大,避免噪声多
- 粒度不要太小,避免信息不够
- 能保留来源上下文
1. 固定长度切分
优点:
- 简单
- 稳定
- 易于实现
缺点:
- 容易把一个完整段落切断
- 标题与正文可能被拆开
- 结构信息丢失
适合第一版系统,但不适合长期停留。
2. 按句子、段落、标题层级切分
这种方式通常比纯固定长度更好,因为它尽量保留了自然语言结构。
例如:
- FAQ 适合按问答对切
- 技术文档适合按标题和段落切
- 法规合同适合按条款切
3. 语义切分
如果文档结构复杂,可以进一步用语义切分,让 chunk 边界更贴近自然主题变化。
但语义切分的代价是:
- 实现复杂度更高
- 处理时间更长
- 不一定总比结构化切分更稳定
4. 做 chunk 时一定要补 context
实践里很常见的一个问题是:单个 chunk 本身信息不够。
例如正文里写着:
它的默认超时时间是 30 秒。
如果没有标题和对象上下文,这句话几乎没法独立检索。
所以 chunk 最好补上:
- 文档标题
- 小节标题
- 上级章节
- 关键实体
你可以把这些信息放进 metadata,也可以直接拼到 chunk 文本前面。
二、为什么单路 Dense Retrieval 经常不够
dense retrieval 很擅长语义相似,但它有几个短板:
- 对编号、术语、缩写、版本号不总是稳定。
- 对关键词精确匹配场景不够强。
- 对长尾实体和 OOV 内容不总是可靠。
这就是为什么现在越来越多系统都会走 hybrid retrieval。
三、Hybrid Retrieval 的思路
hybrid 的核心思想是:不要让一种检索方式决定全部结果。
常见组合:
- dense retrieval:负责语义相关性
- sparse retrieval:负责关键词、实体、字面匹配
- metadata filtering:负责业务边界收缩
也就是说,真正的召回往往是:
候选集 = dense召回 + sparse召回 + metadata过滤
然后再对这些候选做融合。
一个很实用的组合方式
- 先用 dense 召回一批候选
- 再用 BM25 或 sparse 向量召回一批候选
- 把两路结果做 fusion
- 进入 rerank
这种方式往往比单路 dense 更稳,尤其适合:
- 文档里有很多专业术语
- 用户会问具体字段、编号、参数名
- 文本既有自然语言,也有表格和半结构化内容
四、Rerank 为什么是关键
检索的第一阶段更像“召回”,目标是别漏掉。
但第一阶段的 top_k 里,往往会混进不少噪声。这个时候就需要 rerank。
rerank 的目标不是扩大候选,而是重新排序,优先保留真正与问题最相关的 chunk。
没有 rerank 时会发生什么
最典型的问题是:
- 看起来相关,但其实不回答这个问题的 chunk 被放得很前
- 关键词相似但主题不对的内容混进 prompt
- 真正答案在 top_20 里,但没有进入最终 prompt
真正的 rerank 和“假 rerank”的区别
很多系统会在代码里留一个 rerank 模块,但实际还是按原始向量分数排序。这种做法在工程结构上没错,但效果上不算真正的 rerank。
真正的 rerank 通常需要单独的语义相关性模型,输入是:
- query
- candidate chunk
输出是更适合排序的相关性分数。
五、推荐的多阶段检索架构
一个比较稳的架构通常长这样:
阶段 1:候选召回
- dense top_50
- sparse top_50
- metadata 过滤
阶段 2:结果融合
- reciprocal rank fusion
- 或者简单去重后拼接
阶段 3:rerank 精排
- rerank top_20
- 输出 top_5 或 top_8
阶段 4:上下文组装
- 去重
- 多 source 保持适度多样性
- 控制上下文总长度
六、top_k 和阈值怎么调
不要把 top_k=5、score_threshold=0.3 这种数字当成真理。
它们只是在某个数据集、某个 embedding 模型、某个向量库配置下的经验值。
更合理的做法是:
- 把候选召回数和最终注入数拆开。
- 阈值不要写死在所有 query 上。
- 用离线数据集去看 Recall@K 和最终答案质量。
推荐思路:
- 召回阶段:宁可多一点,先别漏
- 精排阶段:重点降噪
- 最终 prompt:严格控制预算
七、metadata 过滤是“低成本高收益”的优化
很多质量问题并不需要更强模型,只需要更聪明的过滤。
比如:
- 按知识源过滤
- 按租户过滤
- 按语言过滤
- 按时间版本过滤
- 按文档类型过滤
一旦 metadata 完整,检索空间会大幅缩小,噪声也会明显下降。
八、上下文组装时的 3 个建议
1. 不要无脑全拼
召回到 10 个 chunk,不代表 10 个都应该进 prompt。最终 prompt 应该更像“精选证据包”,而不是“召回结果 dump”。
2. 尽量保留来源信息
建议至少附带:
- 文档名
- chunk 序号
- 标题或章节
这对引用展示和后续排查都很重要。
3. 控制上下文预算
上下文不是越长越好。长上下文会带来:
- 成本更高
- 延迟更高
- 模型注意力更分散
更推荐的做法是“高质量少量上下文”。
九、一条可执行的升级路线
如果你当前还是单路 dense,建议按这个顺序升级:
- 先把 chunking 做合理
- 给 chunk 补 metadata
- 引入 sparse / BM25
- 做 hybrid fusion
- 接入真正的 rerank
- 最后再做 query rewrite、decomposition 等高级策略
十、本文小结
RAG 做得不好,很多时候不是模型不够强,而是:
- chunk 没切好
- 候选召回太单一
- rerank 没真正落地
- 上下文组装过于粗糙
如果你把 chunking、hybrid retrieval、rerank 这三步做好,RAG 的整体质量往往会有非常明显的提升。
下一篇会继续讲:如何做 RAG 的评测、Inspect 和可观测性,才能知道问题到底出在哪一层。
