RAG 的 Demo 很容易做,生产环境却很容易失控。
一开始你可能只要:
- 一个 embedding 模型
- 一个向量库
- 一个聊天模型
但真到线上,问题会迅速变成:
- 更新怎么做
- 成本怎么控
- 延迟怎么降
- 数据怎么隔离
- 错误怎么排查
- 版本怎么回归
所以生产级 RAG 的重点是"工程稳定性",不只是"回答效果"。
这篇文章会讲:
- 生产环境最常见的坑
- 性能优化的思路
- 一个可执行的迭代路线
一、最常见的 10 个坑
坑 1:把所有文档放进一个大集合
现象:
公司有产品文档、技术规范、内部 wiki、销售话术、HR 制度……全部丢进一个向量库。
问题:
用户问"API 超时怎么配置",检索结果混入了销售话术(因为提到了"时间")、HR 制度(因为提到了"加班时间")。正确答案被噪声淹没。
原理:
向量检索基于语义相似度。“时间"这个概念在很多文档里都出现,如果检索范围太大,语义相似的噪声也会被召回。
解决:
按业务边界拆分 source,检索时指定来源。
知识库结构:
├── product_docs/ # 产品文档
├── api_specs/ # API 规范
├── internal_wiki/ # 内部 wiki
└── hr_policies/ # HR 制度
检索时:
filter = {"source": "api_specs"}
效果:
| 配置 | Recall@5 | 噪声比例 |
|---|---|---|
| 全量检索 | 58% | 62% |
| 按 source 过滤 | 84% | 18% |
坑 2:chunk 没有业务上下文
现象:
按段落切分后,某个 chunk 只剩一句话:“将旋钮调至 3 档,等待指示灯变绿。”
问题:
用户问"ZK-200 怎么校准”,这个 chunk 不会召回。因为产品名、设备类型、操作名称都在标题里——被切掉了。
原理:
Embedding 只能看到 chunk 内部的文字。如果 chunk 里没有"ZK-200"和"校准"这些关键词,向量就不会和这些问题相似。
解决:
把父级标题拼到 chunk 前面:
原始 chunk:
"将旋钮调至 3 档,等待指示灯变绿。"
补充上下文:
"【ZK-200 操作手册 > 2. 使用方法 > 2.3 校准流程】
将旋钮调至 3 档,等待指示灯变绿。"
效果:
| 配置 | Recall@5 |
|---|---|
| 原始 chunk | 41% |
| 补充上下文后 | 78% |
这是 RAG 里性价比最高的优化之一:成本几乎为零,效果翻倍。
坑 3:只做 dense search
现象:
用户问"错误码 E0028 是什么意思",向量检索召回的是"常见错误码及解决方案"这种泛泛而谈的文档,而不是精确包含"E0028"的那段。
原理:
向量检索擅长语义相似,不擅长精确匹配。“E0028"作为一个编号,在向量空间里没有特殊意义,它只是一个 token。
解决:
引入 BM25 或 sparse 检索,做混合检索。
混合检索:
向量检索 → 召回语义相似的文档
BM25 检索 → 召回包含精确关键词的文档
RRF 融合 → 合并两路结果
效果:
| 检索方式 | 精确匹配查询 Recall@5 | 语义查询 Recall@5 |
|---|---|---|
| 向量检索 | 47% | 82% |
| BM25 | 76% | 58% |
| 混合检索 | 84% | 85% |
坑 4:假 rerank
现象:
代码里写着"rerank”,但只是把向量分数重新排了一次序。
def rerank(candidates):
return sorted(candidates, key=lambda x: x['score'], reverse=True)
问题:
这不是真正的 rerank,只是把已有的分数重新排序,效果和没有 rerank 一样。
原理:
真正的 Rerank 需要用 Cross-Encoder:
- 把 Query 和每个候选 Document 拼在一起
- 丢进 Transformer,让模型学习两者的交互
- 输出新的相关性分数
这比双塔结构的向量检索更精确,因为 Query 和 Document 能充分交互。
解决:
使用真正的 Cross-Encoder 模型:
候选文档 → Cross-Encoder → 新分数 → 重新排序
效果:
| 配置 | Recall@5 | 正确答案平均排名 |
|---|---|---|
| 无 Rerank | 72% | 4.2 |
| 假 Rerank | 72% | 4.1 |
| 真 Rerank | 88% | 1.8 |
坑 5:top_k 和阈值写死
现象:
所有场景都用固定的 top_k=5 和 score_threshold=0.3。
问题:
- 简单问题:只需要 3 个候选就够了,多余的候选增加噪声
- 复杂问题:需要 10 个候选才能覆盖,5 个不够
原理:
检索参数取决于问题的复杂度、文档的分布、embedding 模型的特性。不同场景的最优参数不同。
解决:
参数可配置,甚至动态调整:
简单事实问题 → top_k=3, threshold=0.5(少而精)
复杂问题 → top_k=15, threshold=0(多召回)
对比类问题 → top_k=20(需要更多信息)
坑 6:没有 context budget 设计
现象:
检索到 15 个 chunk,全部塞进 prompt,总共 12000 tokens。
问题:
- 成本高:token 消耗大
- 延迟高:模型处理慢
- 效果差:内容太长,模型注意力分散(Lost in the Middle 问题)
原理:
LLM 的注意力机制在长文本上会分散。研究表明,当相关信息在 prompt 中间位置时,模型更容易忽略它。
解决:
设计 context budget:
预算:4000 tokens
组装策略:
1. 优先高分 chunk
2. 控制 token 总量
3. 必要时截断过长的 chunk
4. 保持来源多样性(不同 source 的 chunk 都有)
经验值:
| 场景 | 推荐 token 预算 |
|---|---|
| 简单问答 | 2000-3000 |
| 复杂问答 | 3000-5000 |
| 文档总结 | 5000-8000 |
坑 7:文档更新了,索引没同步
现象:
周一用户问"报销流程",答案正确。周三财务更新了报销规范,周五用户再问,答案还是旧的。
问题:
文档更新后,向量库没有同步,还在用旧版本。
解决:
建立版本管理机制:
方案 1:定时全量同步
- 每天凌晨重建索引
- 简单但成本高
方案 2:增量更新
- 监控文档变更
- 只更新变化的文档
- 复杂但成本低
方案 3:版本标记
- 每个文档有版本号
- 检索时检查版本是否最新
- 过期则重新索引
关键:保留文档的 hash 或时间戳,便于判断是否需要更新。
坑 8:没有证据引用
现象:
模型回答:“报销需要提交发票和审批单。”
用户:“这个答案依据是什么?”
系统无法回答。
问题:
答案和来源脱节,用户无法验证答案的可信度。
解决:
Prompt 中强制要求引用:
要求:
1. 答案必须基于参考资料
2. 回答时标注引用来源,格式:[来源: 文档名 > 章节]
3. 如果不确定,说明不确定
答案示例:
报销需要提交发票和审批单 [来源: 财务规范 > 3.2 报销流程]。
好处:
- 用户可以验证答案
- 系统可以追溯错误
- 增加答案可信度
坑 9:没有离线 benchmark
现象:
开发者说"效果提升了",产品说"感觉变差了",谁也说服不了谁。
问题:
没有客观的评价标准,优化靠主观感受。
解决:
建立固定 benchmark:
评测集:
- 50-100 个问题
- 每个问题有期望召回的 chunk
- 每个问题有参考答案
定期评测:
- 每次改动后跑 benchmark
- 对比历史版本
- 设置阈值(如 Recall@5 > 80%)
原则:
- 评测集要隔离,不参与训练
- 定期从线上采样真实问题,更新评测集
- 多指标综合评估
坑 10:没有 inspect 能力
现象:
答案错了,不知道是检索错、排序错、还是模型错。
问题:
无法定位问题,优化靠猜。
解决:
保留每次请求的 Trace:
Trace 包含:
- 用户问题
- 检索候选(top-10)
- Rerank 结果
- 最终 prompt
- 模型答案
有 Inspect 页面,可以查看每一步的中间结果。
好处:
- 快速定位问题
- 积累 bad cases
- 指导优化方向
二、性能优化的思路
延迟分析
一个典型 RAG 请求的延迟分布:
总延迟:400-600ms
├── Query Embedding:30-50ms(5-10%)
├── 向量检索:30-50ms(5-10%)
├── BM25 检索:10-20ms(2-5%)
├── RRF 融合:<5ms(<1%)
├── Rerank:150-200ms(30-50%)← 主要瓶颈
├── Prompt 组装:<10ms(<1%)
└── LLM 生成:150-300ms(30-50%)← 主要瓶颈
两个主要瓶颈:Rerank 和 LLM 生成。
Rerank 优化
思路 1:减少候选数量
Rerank 的计算量与候选数量成正比。
100 个候选 × Cross-Encoder ≈ 200ms
30 个候选 × Cross-Encoder ≈ 70ms
但不能减太少,否则会漏掉正确答案。建议:
- 召回 50-100 个
- Rerank 前 20-30 个
- 输出 top 5-10 个
思路 2:用更快的模型
| 模型 | 延迟(100 候选) | Recall@5 |
|---|---|---|
| bge-reranker-large | ~180ms | 88% |
| bge-reranker-base | ~95ms | 85% |
| ms-marco-MiniLM | ~45ms | 80% |
速度和效果的权衡。
思路 3:跳过简单问题
对于简单问题,可能不需要 Rerank:
简单问题(候选集中度高)→ 跳过 Rerank
复杂问题(候选分散)→ 需要 Rerank
判断标准:看 top-5 候选的分数分布。如果第一名分数远高于其他,说明检索已经足够准确。
LLM 优化
思路 1:用更快的模型
GPT-4o-mini 比 GPT-4o 快 3-5 倍,成本是 1/10。
对于大部分 RAG 场景,mini 版足够用。
思路 2:流式输出
不要等完整答案生成,边生成边返回:
传统模式:
用户等待 → 完整答案 → 一次返回(延迟高)
流式模式:
用户等待 → 第一个字 → 第二个字 → ...(感知延迟低)
流式不会减少实际延迟,但能改善用户体验。
思路 3:缓存热门 query
相同问题直接返回缓存:
热门问题 TOP 100:
"报销流程" → 缓存答案
"API 认证" → 缓存答案
...
命中率:10-30%
节省:10-30% 的 LLM 调用
检索优化
思路 1:缓存 query embedding
同一个问题的 embedding 可以缓存:
Query → Embedding(40ms)
Query(重复)→ 缓存(<1ms)
思路 2:预计算
某些场景可以预计算:
FAQ 场景:
- 预计算每个 FAQ 的 embedding
- 预计算每个 FAQ 的最佳答案
- 用户问题直接匹配 FAQ
适合问题集合固定的场景。
思路 3:减少召回数量
如果检索质量已经很高,可以减少召回数量:
top-50 → top-30
节省:向量检索和 Rerank 的时间
风险:可能漏掉正确答案
三、成本优化
Token 消耗分析
单次请求的 token 消耗:
输入 token:
- Query:20-50
- Chunks(5个):2000-5000
- System prompt:100-200
总输入:2000-6000 tokens
输出 token:
- Answer:100-500
按 GPT-4o-mini 定价:
输入:$0.15/1M tokens
输出:$0.60/1M tokens
单次请求成本:$0.0003-0.001
日均 10000 次 → 月成本:$100-300
成本优化思路
1. 控制上下文长度
上下文太长不仅成本高,效果还差。
经验值:3000-4000 tokens
超过预算时:
- 截断过长的 chunk
- 减少进入 prompt 的 chunk 数量
- 优先保留高分的 chunk
2. 用更便宜的模型
GPT-4o-mini 的成本是 GPT-4o 的 1/10,效果差距不大。
3. 缓存热门问题
10-30% 的请求是重复问题,直接返回缓存。
4. 优化 prompt
去掉冗余的 prompt 内容,精简指令。
差的 prompt(冗长):
"你是一个非常专业的技术文档助手,你的任务是根据用户提供的参考资料
来回答用户的问题。请注意,你的回答必须严格基于参考资料..."
好的 prompt(精简):
"根据参考资料回答问题。答案必须基于资料,标注来源[数字]。"
四、迭代路线图
V1:能跑通(1-2 周)
目标:回答对 60% 的核心问题
配置:
- 基础 chunking(按段落)
- 纯向量检索(top_k=5)
- 基础 prompt + 引用要求
验收:
- 能回答常见问题
- 有引用来源
- 能 demo 演示
不要做:
- 不要全量导入所有文档
- 不要追求复杂架构
- 不要过度优化
V2:能诊断(2-3 周)
目标:知道为什么对/错
配置:
- Inspect 页面(看检索候选)
- Metadata 过滤
- 参数可配置
- 离线评测集(50 条)
验收:
- 能看到检索的 top-10
- 能定位问题在哪一层
- 有 Recall@5 指标
V3:能优化(3-4 周)
目标:召回率 80%+
配置:
- Hybrid Retrieval(向量 + BM25)
- 真 Rerank
- Chunking 优化(补充上下文)
- 评测集扩展(100 条)
验收:
- Recall@5 > 80%
- 能回答复杂问题
- Bad cases 持续跟踪
V4:能运营(持续)
目标:持续监控、持续迭代
配置:
- Trace 存储
- 监控大盘
- 告警规则
- 用户反馈收集
- CI/CD 集成
验收:
- 有质量监控
- 能快速定位线上问题
- 每次改动有回归测试
收益递减规律
60% → 80%:基础优化(chunking、混合检索)
80% → 90%:系统优化(Rerank、prompt 调优)
90% → 95%:高级优化(query rewrite、模型微调)
95% → 99%:难度极大,投入产出比低
建议:先做到 85%,再决定是否值得继续投入。
五、团队协作
角色分工
产品/运营:
- 定义知识边界
- 收集 bad cases
- 标注评测集
- 用户反馈归类
算法/NLP:
- Embedding 模型选择
- Rerank 模型调优
- Chunking 方案
- 高级策略
后端工程师:
- Ingestion pipeline
- 向量库维护
- Trace/Inspect 系统
- 性能优化
- 监控告警
协作流程
日常迭代:
1. 产品收集 bad cases
2. 算法分析问题(定位到哪一层)
3. 算法 + 后端共同优化
4. 跑 benchmark 验证
5. 上线 + 监控
问题归属
| 问题 | 归属 | 方向 |
|---|---|---|
| 召不回正确内容 | 算法 | Chunking、检索策略 |
| 候选噪声多 | 算法 | Rerank、过滤 |
| 答案格式不对 | 算法 | Prompt |
| 延迟高 | 后端 | 缓存、异步 |
| 系统不稳定 | 后端 | 稳定性 |
| 模型幻觉 | 算法 | Prompt、模型选择 |
| 知识库没更新 | 产品/运营 | 文档维护流程 |
六、判断系统成熟度
如果满足以下条件,说明系统开始成熟:
□ 知道每次回答用了哪些证据
(有 Inspect,能追溯)
□ 能复现错误回答的完整链路
(有 Trace 存储)
□ 有固定 benchmark
(定期跑,有阈值)
□ 能对比不同版本的质量
(有版本管理,能 A/B)
□ 知道下一步优化哪一层
(有监控,不是盲调)
如果这 5 条还做不到,最应该投入的不是"换更强模型",而是把检索、Inspect 和评测体系补完整。
七、系列总结
这四篇文章回答了 RAG 的核心问题:
入门:
- RAG 的本质:把知识从参数里剥离出来
- Embedding 的原理:对比学习、向量空间
- 向量检索的局限:对精确匹配不稳定
进阶:
- Chunking:按语义切分 + 补充上下文
- 混合检索:向量 + BM25 + RRF 融合
- Rerank:Cross-Encoder 做真正的精排
评测:
- 分层评测:检索、重排、组装、生成
- LLM-as-Judge:让大模型评判答案质量
- Inspect:保留完整 Trace,定位单次问题
生产:
- 常见坑:上下文丢失、假 rerank、无引用
- 性能优化:缓存、模型选择、候选数量控制
- 迭代路线:从能跑到能运营
RAG 不是银弹,但它是目前让 LLM 接地气的最实用方案。关键在于:不要追求一步到位,而是建立可迭代的能力。
后续可以深入的方向:
- Query Rewrite 和 Query Decomposition
- Contextual Retrieval(Anthropic 的方法)
- 多租户与权限设计
- RAG 与 Agent 的结合
- 多模态 RAG(图片、表格)
