专栏第 10 篇

目标:不是“把 RAG 接上”,而是“在可控成本下稳定提升正确率与可解释性”。


一、问题描述:为什么很多团队做了 RAG,效果却不升反降?

你可能见过这种情况:

  • 接入 RAG 之后,回答变长了,但正确率没有明显提高;
  • 召回了很多“看起来相关”的片段,最终答案仍然答非所问;
  • 时延从 2 秒涨到 6 秒,用户体验变差;
  • token 成本持续上涨,团队开始怀疑 RAG 的价值。

这不是 RAG 技术本身的问题,核心是工程落地顺序错了:

  1. 没有先判断接入时机;
  2. 没有明确检索目标(召回什么、为什么召回);
  3. 没有评测基线(改前改后无法对比);
  4. 没有成本治理(只追质量,不控成本)。

结论:RAG 不是“一个组件”,而是一条需要评测闭环的生产链路。


二、何时该加 RAG?先做“接入时机判断”

如果你的系统满足以下条件中的 3 条以上,再进入 RAG 接入阶段:

  • 问题高度依赖私域知识(内部文档、流程规范、知识库)
  • 纯 Prompt 已到瓶颈,幻觉率明显影响业务
  • 知识更新频繁(每周/每天),无法靠静态 Prompt 维护
  • 已有基础可观测能力(日志、指标、错误归因)
  • 有最小维护能力(文档清洗、索引更新、评测回归)

如果不满足,建议优先做:

  • Prompt 版本化 + 评测基线;
  • 工具层稳定性;
  • 上下文治理。

不要为“追热点”而接 RAG。


三、最小可用 RAG 架构(MVP)

建议先用下面这条标准链路:

Query -> Query Rewrite -> Retrieve -> Rerank -> Context Pack -> Generate -> Verify

各环节职责:

  1. Query Rewrite(查询改写)
    • 目的:减少歧义、补全实体、统一术语。
  2. Retrieve(召回)
    • 目的:尽可能不漏掉候选信息(高召回)。
  3. Rerank(重排)
    • 目的:把“真正有用”的片段排到前面(高精度)。
  4. Context Pack(上下文打包)
    • 目的:在 token 预算内注入最有效信息。
  5. Generate(生成)
    • 目的:基于上下文回答,并给出来源线索。
  6. Verify(可选校验)
    • 目的:关键场景下做事实一致性检查。

四、知识入库:先把“脏文档”变“可检索资产”

RAG 效果的 50% 取决于数据质量,而不是模型大小。

4.1 入库前处理建议

  • 文档去噪:去掉导航、页脚、重复模板
  • 结构保留:保留标题层级(H1/H2/H3)
  • 语义切分:按自然段与小节切分,而非硬按字符
  • 元数据补齐:source, title, section, updated_at, version

4.2 Chunk 策略(起步参数)

  • chunk 长度:300~500 tokens
  • overlap:60~100 tokens
  • 每个 chunk 保留可追溯源信息

过小 chunk:上下文不完整;过大 chunk:噪音增加、成本上升。


五、检索策略:混合检索比单一路径更稳

实战推荐:向量检索 + 关键词检索(BM25)混合

  • 向量检索:擅长语义相似
  • BM25:擅长命中关键词、ID、错误码、专有名词

5.1 典型流程

  1. 向量召回 top-k(如 20)
  2. BM25 召回 top-k(如 20)
  3. 合并去重(union)
  4. 重排 top-n(如 8)
  5. 最终注入 top-3~5

5.2 为何必须重排?

没有重排时,常见问题是“召回相关,但不够关键”。
重排能显著提升答案稳定性,尤其在多文档场景。


六、上下文打包:决定生成质量与成本上限

RAG 不是“塞越多越好”。建议规则:

  • 只注入 top-3~5 片段;
  • 每个片段限制长度(如 <= 220 tokens);
  • 同源片段做合并去重;
  • 强制保留 source 字段用于可解释引用。

6.1 推荐 Prompt 模板片段

你将基于检索上下文回答用户问题。
要求:
1) 仅使用上下文中的事实,不得臆测;
2) 若上下文不足,明确说明“信息不足”;
3) 输出时附上来源标识(source/title/section)。

七、评测体系:没有评测,就没有优化

先建立一个 30~80 条的评测集,覆盖:

  1. 核心高频问题
  2. 易混淆问题(近义概念)
  3. 边界问题(长 query、模糊问法)
  4. 时效问题(更新后的知识)

7.1 核心指标(建议)

  • Recall@k:正确片段是否被召回
  • MRR / nDCG:正确片段排位是否靠前
  • Faithfulness:答案是否忠于检索内容
  • Answer Success Rate:用户目标是否达成
  • Cost per Request:单请求综合成本
  • Latency p95:线上体验关键指标

7.2 评测门禁示例

只有同时满足以下条件才允许上线:

  • Faithfulness 提升 >= 5%
  • Success Rate 不下降
  • p95 增幅 <= 20%
  • 单请求成本增幅 <= 25%

八、成本控制:RAG 的关键不是便宜,而是“单位收益最大化”

成本治理建议按优先级做:

  1. 减少无效召回:优化 query rewrite + minScore
  2. 减少无效注入:top-3~5,限制 chunk 长度
  3. 缓存高频问题:query 归一化后缓存
  4. 分层模型:检索环节用轻模型,生成环节按场景升级
  5. 异步化非关键步骤:降低前台等待时延

九、常见报错与排查(高频)

1)“召回命中,但回答仍错”

  • 可能原因:重排质量低,优质片段没进上下文
  • 处理:引入 rerank + 调整 top-n

2)“回答总说信息不足”

  • 可能原因:chunk 过碎,上下文断裂
  • 处理:增大 chunk 或改善切分策略

3)“延迟过高”

  • 可能原因:召回量过大 + 重排过重
  • 处理:降低候选量、做缓存、优化并行度

4)“更新文档后效果不稳定”

  • 可能原因:索引未及时刷新或混入旧版本
  • 处理:索引版本化 + 增量更新 + 定时全量校验

十、实现伪代码(简化版)

def rag_answer(query, kb, retriever, reranker, llm):
    q = rewrite_query(query)

    v_hits = retriever.vector_search(q, top_k=20)
    b_hits = retriever.bm25_search(q, top_k=20)

    merged = dedup_union(v_hits, b_hits)
    ranked = reranker.rank(q, merged)[:8]

    context = pack_context(ranked, max_chunks=5, max_tokens=900)

    prompt = build_prompt(query, context)
    answer = llm.generate(prompt)

    return {
        "answer": answer,
        "sources": [x.meta for x in ranked[:5]],
        "debug": {
            "retrieved": len(merged),
            "used": len(context)
        }
    }

十一、本篇总结

RAG 的价值不在“接了没有”,而在“能否稳定提升正确率,并且成本可控”。

一句话总结:先判时机,再做最小链路,用评测和成本指标驱动迭代。

Logo

欢迎加入DeepSeek 技术社区。在这里,你可以找到志同道合的朋友,共同探索AI技术的奥秘。

更多推荐