写在前面

过去一年深度参与了三个生产级 RAG 系统的搭建,从最初的 demo 到稳定服务百万级查询,踩过的坑足够写本书。这里挑几条最有共鸣的经验。

1. 文档切分策略远比你想的重要

很多人直接 RecursiveCharacterTextSplitter 切 500 字一段就完事了,效果当然不好。

真正有效的切分原则

  • 按语义边界切:标题、段落、列表项是天然分割点。Markdown 文档优先按标题层级切(H1/H2/H3),保留上下文层级
  • 保留元数据:每个 chunk 至少要带:文档来源、章节标题路径、原始位置。检索时这些都是重要信号
  • 重叠策略:chunk 之间保留 10-20% 重叠,避免把关键信息切在边界
  • chunk 大小自适应:FAQ 类用 200-400 token,技术文档用 500-800 token,叙述性文档用 1000+ token

实测:从"无脑切 500"换到"按 Markdown 标题切 + 携带 H1>H2>H3 路径",准确率提升约 15%。

2. 单纯向量检索不够,混合检索才是基线

Embedding 擅长语义相似,但对精确匹配(产品代号、人名、专有术语)非常弱。生产环境基本都是混合检索:

最终得分 = α × 向量相似度 + (1-α) × BM25 得分

进阶玩法:

  • 多路召回 + RRF 合并:BM25、向量、ColBERT、关键词扩展各召回一批,用 Reciprocal Rank Fusion 合并
  • Query 重写:用小模型把用户口语化提问改写成检索友好的形式
  • HyDE:让 LLM 先针对问题"幻觉"出一个假设答案,用这个假设答案的 embedding 去检索(适合长尾问题)

3. Rerank 是性价比最高的优化点

向量检索召回 50-100 条,再用 Cross-Encoder 模型对这些候选打分排序,只把 Top 5 喂给 LLM。

为什么有效:向量检索是"双塔"结构,query 和 doc 独立编码,丢失了交互信息;Cross-Encoder 把 query 和 doc 拼起来一起编码,能捕捉细粒度匹配。

推荐:BAAI 的 bge-reranker-large 或 Cohere 的 rerank-multilingual-v3就这一步能把答案准确率从 70% 提到 85%+

唯一代价是 latency 增加 100-300ms,但对大部分场景完全可接受。

4. 评估必须在第一天就建立

最大的坑:没有评估集就开始调优。改了三个参数,每个都"感觉好像变好了",但你根本不知道整体在前进还是后退。

最小可行评估集

  • 50-100 条人工标注的 (问题, 标准答案 / 标准文档) 对
  • 至少覆盖:高频问题、长尾问题、需要多文档综合的问题、不该回答的问题(拒答场景)

关键指标

  • Retrieval:Hit Rate @ K、MRR、NDCG
  • Generation:基于 LLM-as-a-judge 的事实性、相关性、完整性打分
  • 拒答:能否在没有相关文档时拒答而不是胡编

每次改动跑一遍评估,看曲线移动方向再决定要不要保留。

5. 拒答能力比答对能力更重要

用户问的问题中相当比例是知识库覆盖不到的(业务范围外、过时信息、根本就答不了)。

LLM 默认会硬答,这是 RAG 的最大风险点。生产系统必须显式实现:

  • 检索结果分数低于阈值时直接走拒答路径
  • Prompt 明确指示"只能基于以下文档回答,无依据时说不知道"
  • 后置事实校验:让另一个 LLM 调用检查答案是否真的能从文档推出

我们的系统拒答率维持在 8-12%,比用户接受一个胡编答案要好得多。

6. 长上下文模型不能取代 RAG

经常有人说"GPT-4 现在 128K context,把所有文档塞进去就行了"。三个问题:

  1. 成本:每次调用塞满上下文,API 费用爆炸
  2. 延迟:长上下文 prefill 阶段非常慢,首 token 延迟高
  3. Lost in the Middle:注意力会衰减,文档放在中间位置会被忽略

RAG 的核心价值是精准提供相关信息,这个价值在长上下文时代依然成立,只是切分粒度可以更粗、Top K 可以更大。

总结

做 RAG 的关键认知:这是一个检索问题,不是生成问题。把检索做好,生成几乎是免费的;检索做不好,再强的 LLM 也救不回来。

下一篇打算写 Agentic RAG 和 Self-Reflection 的实战。