RAGadvanced22 分钟阅读

RAG 落地的 9 个坑:我踩过的和我从客户那儿见过的

从「跑通 demo」到「上生产」之间,99% 的 RAG 项目挂在这里

x
xaikey
更新于 2026/4/22
ragvector-dbevaluation
已验证作者于 2026-04-22 pgvector 0.8 / Claude Sonnet 4.6 / OpenAI text-embedding-3-large 环境下亲自跑通。文中所有代码、prompt、命令均为实测。

过去 18 个月我做过 4 个 RAG 项目(2 个内部知识库、1 个客服、1 个法律检索), 加上看过的客户系统超过 10 个。这篇是把所有让我们把整套系统推倒重做的坑总结成一份避雷指南。

TL;DR

  • RAG demo 跑通到上生产之间有一道评估鸿沟,95% 的项目挂在这里。
  • Embedding 模型、chunk 策略、reranker 时机是三个最贵的错——一旦选错,重建成本几乎都要 2-4 周。
  • 没有 eval 集就没有 RAG。先做 100 条标注样本再写代码。

坑 1 · embedding 模型选错

最常见错误:随手选了 all-MiniLM-L6-v2 因为「它免费」。然后 6 周后召回率上不去,才发现:

  • 它是 384 维老模型,2024 年后基本不该作为生产首选
  • 对中文性能尤其差(训练语料 EN 占绝大多数)
  • 不支持长 chunk(> 512 token 直接截断,悄悄丢内容)

我现在的默认选型决策:

语言预算紧预算宽本地部署
纯英文text-embedding-3-smalltext-embedding-3-largenomic-embed-text-v1.5
中文 / 中英混合text-embedding-3-largeBGE-M3 / Qwen3-EmbeddingBGE-M3
代码voyage-code-3voyage-code-3nomic-embed-code

坑 2 · chunk 切法想当然

「512 token 固定切」是教程标配,是生产毒药。

典型场景:

  • FAQ:每条问答应该是一个 chunk,固定切会把答案截断在中间
  • API 文档:方法签名、参数表、示例必须一起留,否则检索回来用户看不懂
  • 聊天记录:按消息块或按时间窗,固定 token 切会把上下文打散

我现在用一个分层策略:

def chunk(doc):
    # 1. structural chunking:按 markdown / HTML 标签拆
    sections = split_by_headings(doc, max_levels=2)

    # 2. 长度控制
    out = []
    for s in sections:
        if token_count(s) > 800:
            # 长 section 二次切,但用 semantic split(句号 / 段落优先)
            out.extend(semantic_split(s, target=400))
        else:
            out.append(s)

    # 3. 头尾拼回上下文(让 chunk 不孤立)
    return [
        f"# {doc.title}\n[Section: {s.heading}]\n{s.body}"
        for s in out
    ]

坑 3 · 上线前没有 eval 集

最常见死法:「先把 RAG 跑通,eval 上线再加」。结果上线后无法调优,因为没有可重复的指标。

最小可跑的 eval 集长这样:

# evals/v1.jsonl
{"q": "退款流程是什么?", "must_contain_doc_ids": ["doc-203", "doc-877"]}
{"q": "API 返回 429 怎么办?", "must_contain_doc_ids": ["doc-12"]}
{"q": "保修期多久", "must_contain_doc_ids": ["doc-55", "doc-9"]}

100 条手工标注花你 1.5 天。看着多,但它能让你做的每一次改动都可量化, 否则你只能凭感觉调,最后通常调到一个比初版更糟的状态。

坑 4 · 召回评估只看 top-K

团队最爱报的指标:「我们 top-5 召回 88%!」 然后上线后发现答案经常错——因为相关文档在 top-5 里,但不在前 2。 生成模型只读 top-2 拼 prompt,剩下的全浪费。

真正该看的:

  • MRR(平均倒数排名)——金标 doc 排在第 N 位的 1/N 的均值
  • top-1 hit——金标在第一位的比例(最严格)
  • nDCG@5——同时考虑命中和位置权重

坑 5 · 重新索引代价没算

某次客户问:「换 embedding 模型要多久?」 我说:「改一行代码的事。」 实际上:50 万 chunk × 0.05 美元/千 token × 平均 200 token = \$500 + 6 小时。 而且要做新旧模型并存的双写期,确保零宕机。

所以:

  • 选 embedding 模型时直接选生产意图,不要先用便宜的,万一上线用户多了你要重建
  • vector DB 选支持 namespace / collection 的(pgvector / Qdrant / Pinecone),方便 A/B
  • 预留「全量重建」预算 = chunk 数 × token 单价

坑 6 · 多语言混合

中文用户问「API 怎么集成?」,文档是英文的。BGE-M3 这种多语言模型才能跨语言召回; OpenAI text-embedding-3 表现也行;MiniLM、bge-base-en 这种只懂英文的会直接挂。

除了模型选对,还要做查询改写:检索前先用 LLM 把中文 query 翻译成英文, 或同时用中文 query + 英文翻译两版做检索后合并。

坑 7 · 元数据过滤太晚

权限敏感场景(用户 A 不能看用户 B 的数据),过滤必须发生在向量检索阶段而不是检索后。 否则 top-K 里全是别人的数据,过滤完空了,模型一脸懵。

# 错的写法:先查再过滤
candidates = db.search(query_vec, top_k=5)
visible = [c for c in candidates if c.user_id == current_user]  # 经常空

# 对的写法:检索时就带 filter
candidates = db.search(
    query_vec,
    top_k=5,
    filter={"user_id": current_user}
)

坑 8 · Reranker 时机错

Reranker(如 cohere-rerank、bge-reranker)应该用在检索之后、生成之前

  • 检索阶段拿 top-50(粗排,重召回)
  • Reranker 把它压到 top-5(精排,重精度)
  • 生成模型只看 top-5

常见错误:上来就用 reranker 替代 embedding 检索。reranker 是 N×N 比对,5 万条文档你跑不动。

坑 9 · 监控盲点

RAG 上线后必须监控的 3 个数:

  1. 「我不知道」率——模型说「无法回答」的比例。突然飙升说明检索质量塌了
  2. 引用 chunk 一致性——回答里引的 chunk 是不是检索 top 内的(防幻觉)
  3. 用户继续追问率——一次答不完,用户再问一次就是答得不好的信号

上线 checklist

  • [ ] 选了正确的 embedding 模型(看坑 1 表)
  • [ ] chunk 策略不是固定 token,是结构 + 语义混合
  • [ ] 有 ≥ 100 条标注 eval 集,能 CI 跑
  • [ ] 评估指标是 MRR + top-1 + nDCG@5,不只是 top-K hit
  • [ ] 算清楚全量重建成本
  • [ ] 多语言场景上 BGE-M3 或 text-embedding-3-large + query rewriting
  • [ ] 元数据过滤在检索阶段做
  • [ ] 引入 reranker,但放在检索后
  • [ ] 监控「不知道」率、chunk 一致性、追问率
作者后记

这篇 playbook 我手写后用 LLM 协助润色 / 校对,每一段技术结论都基于真实测试。如果你发现描述与你的环境有出入,欢迎提交 issue 或邮件 hello@xaikey.com。争议条目我会标注更新日期。

文档版本:v1 · 2026-04-22
不想错过下一篇

加入每周 AI 工程师 Brief

新 playbook 上线第一时间通知,附作者每周观察。永久免费。

相关 Playbook