---
title: 万字详解 RAG 优化：从召回、重排到上下文工程的系统调优
description: 深入拆解 RAG 优化的系统工程方法，覆盖 Chunk 策略、Metadata、Hybrid Search、Query Rewrite、Rerank、上下文压缩、答案评估与生产排查路径。
category: AI 应用开发
head:
  - - meta
    - name: keywords
      content: RAG优化,RAG调优,Hybrid Search,Rerank,Query Rewrite,Context Compression,RAG评估,上下文工程,检索增强生成
---

<!-- @include: @article-header.snippet.md -->

第一次做 RAG 时，很多人的体验都差不多：文档切了，向量库建了，Top-K 也调大了，模型还是一本正经地胡说八道。

更难受的是，问题可能出在文档解析、Chunk 切分、上下文质量等多个环节，而不是单纯的 embedding 或 Top-K 参数。

调一个企业知识库问答时，很容易陷入一个误区：一开始疯狂换 embedding 模型，结果线上错误率没明显下降。把失败样本拆开看才发现，60% 的问题根本不是向量相似度不够，而是 PDF 表格被解析坏了、Chunk 把条件和结论切开了、重排前的候选池里没有正确片段。

RAG 优化的第一条经验是：**它本质上是数据、切分、索引、召回、重排、上下文、生成、评估共同组成的系统工程，不是单点调参。**

这篇文章就把这条链路上每个环节的优化方法拆开来讲。接近 1.5w 字，建议收藏。主要内容：

1. 为什么 RAG 优化不能只盯着 embedding、Top-K 和大模型参数
2. Chunk、Metadata、Hybrid Search、Query Rewrite、Rerank、上下文压缩、答案评估各环节的作用
3. 生产环境里遇到 RAG 效果差时，应该按什么路径排查和收敛

## RAG 优化到底在优化什么？

先把心智模型摆正。

RAG 更像一条证据加工流水线：原始资料先被解析、清洗、切块、打标签、建索引；用户问题进来后，再经过查询理解、召回、重排、上下文构建，最后才交给 LLM 生成答案。

这条链路里任何一环出问题，都会传染到下游。

| 环节       | 典型问题                             | 最终表现                           |
| ---------- | ------------------------------------ | ---------------------------------- |
| 文档解析   | 表格错位、标题丢失、页码缺失         | 答案引用不准，关键条件丢失         |
| Chunk 切分 | 块太大、太小、语义边界被切断         | 召回噪声大，或者召回片段缺上下文   |
| Metadata   | 没有保存来源、时间、权限、章节       | 无法过滤，无法引用，容易越权       |
| 召回       | 只用向量检索，忽略关键词和结构化条件 | 错过错误码、SKU、版本号、专有名词  |
| 重排       | 直接把 Top-K 塞给模型                | 正确片段排在后面，模型看不到重点   |
| 上下文     | 不去重、不压缩、不排序               | Token 浪费，模型被噪声干扰         |
| 生成       | Prompt 没有限定证据边界              | 答案看起来流畅，但引用和事实对不上 |
| 评估       | 只看主观体验，不建测试集             | 改动靠感觉，线上反复回退           |

**RAG 优化的目标是提高最终答案的可用性、可追溯性和稳定性，而不是让每个环节看起来高级。**

一个粗暴但好用的判断标准：

- 用户问的问题，正确证据有没有被召回？
- 正确证据有没有排在足够靠前的位置？
- 放进上下文的内容是否足够少、足够准？
- 模型有没有严格基于证据回答？
- 每次改动有没有通过固定样本集验证？

这 5 个问题，比“用哪个向量库更好”重要得多。

```mermaid
flowchart LR
    %% ========== classDef 配色声明 ==========
    classDef client fill:#00838F,color:#FFFFFF,stroke:none,rx:10,ry:10
    classDef business fill:#E99151,color:#FFFFFF,stroke:none,rx:10,ry:10
    classDef infra fill:#9B59B6,color:#FFFFFF,stroke:none,rx:10,ry:10
    classDef success fill:#4CA497,color:#FFFFFF,stroke:none,rx:10,ry:10

    %% ========== 节点声明 ==========
    Doc[/原始文档/]:::client
    Parse[文档解析]:::business
    Chunk[Chunk 切分]:::business
    Meta[Metadata 标注]:::infra
    Index[建索引]:::infra
    Query[用户 Query]:::client
    Recall[混合召回]:::business
    Rerank[Rerank 重排]:::business
    Compress[上下文压缩]:::business
    LLM[LLM 生成]:::business
    Answer[最终答案]:::success

    %% ========== 连线 ==========
    Doc --> Parse --> Chunk --> Meta --> Index
    Query --> Recall
    Index --> Recall
    Recall --> Rerank --> Compress --> LLM --> Answer

    linkStyle default stroke-width:2px,stroke:#333333,opacity:0.8
```

## RAG 优化闭环

生产级 RAG 一定要有闭环。没有评估和回放，再多技巧都是玄学。

```mermaid
flowchart LR
    Q["线上问题<br/>失败样本"]:::client --> E["离线评估<br/>指标拆分"]:::infra
    E --> L["定位瓶颈<br/>召回/重排/生成"]:::business
    L --> T["策略调整<br/>Chunk/Query/Rerank"]:::warning
    T --> G["灰度发布<br/>版本对比"]:::gateway
    G --> M["监控反馈<br/>人工复核"]:::success
    M --> Q

    classDef gateway fill:#7B68EE,color:#FFFFFF,stroke:none,rx:10,ry:10
    classDef business fill:#E99151,color:#FFFFFF,stroke:none,rx:10,ry:10
    classDef infra fill:#9B59B6,color:#FFFFFF,stroke:none,rx:10,ry:10
    classDef client fill:#00838F,color:#FFFFFF,stroke:none,rx:10,ry:10
    classDef success fill:#4CA497,color:#FFFFFF,stroke:none,rx:10,ry:10
    classDef warning fill:#F39C12,color:#FFFFFF,stroke:none,rx:10,ry:10
    linkStyle default stroke-width:2px,stroke:#333333,opacity:0.8
```

这张图的关键不是流程本身，而是两个字：**回放**。

每次调整 Chunk 大小、重写策略、Rerank 模型、Top-K 参数，都应该拿同一批问题跑一遍，比较 Context Recall、Context Precision、Faithfulness、Answer Relevancy、延迟和成本。

没有回放，就不知道变好了还是只是换了一种错法。

## 先做数据治理，再谈检索优化

很多 RAG 系统失败的原因是“被检索的数据一开始就不对”，而不是“检索不准”。

### 文档解析决定上限

PDF、Word、HTML、Markdown、数据库记录、工单日志，看起来都是文本，实际结构差异很大。尤其是 PDF 表格、图片、页眉页脚、脚注、跨页表格，如果只用普通文本抽取，常见结果是：

- 表格列关系丢失，价格、版本、条件混在一起。
- 页眉页脚被重复写入每个 Chunk，污染向量空间。
- 图片和流程图完全丢失，答案缺关键步骤。
- 标题层级消失，模型不知道一段话属于哪个章节。

对研发文档、政策文档、产品手册来说，**解析质量往往比换 embedding 模型更重要**。

一个实用建议：

| 文档类型        | 推荐处理方式                     | 核心目标       |
| --------------- | -------------------------------- | -------------- |
| Markdown / HTML | 保留标题层级、列表、代码块       | 不破坏天然结构 |
| PDF 文档        | 解析正文、表格、页码、图片说明   | 保住证据边界   |
| 表格型文档      | 转成结构化行记录或 Markdown 表格 | 保住字段关系   |
| 代码文档        | 按包、类、方法、注释分层         | 保住调用语义   |
| 工单/聊天记录   | 按会话、时间、角色切分           | 保住上下文顺序 |

如果数据源里有大量表格和图片，必要时可以引入 OCR 或多模态模型做结构化描述，但要注意成本和延迟。这里不要迷信“全都丢给视觉模型”，优先处理高价值文档和高频失败样本。

### Metadata 的作用

Metadata 不是给后台页面展示用的，它是检索的硬约束和答案的证据链。

至少建议为每个 Chunk 保存这些字段：

- `source_id`：原始文档 ID，便于回溯和去重。
- `source_type`：PDF、网页、工单、代码、数据库记录等。
- `title`：文档标题。
- `section_path`：章节路径，例如“退换货政策 / 售后范围 / 特殊商品”。
- `page`：页码或段落位置。
- `created_at` / `updated_at`：时间过滤和新旧版本判断。
- `tenant_id` / `acl`：多租户和权限控制。
- `business_tags`：产品线、语言、地区、版本、模块。

一个高频盲区是：**先向量检索，再做权限过滤**。

这很危险。假设向量库返回 Top-10，其中 8 条用户无权限，过滤后只剩 2 条，系统就会以为“只召回了 2 条相关内容”。更糟的是，如果过滤逻辑写错，还可能把越权内容塞进上下文。

更稳的做法是：**能预过滤就预过滤**。先用 Metadata 缩小检索范围，再做向量或混合检索。比如先限制 `tenant_id`、文档类型、版本范围、更新时间，再进入相似度计算。

## Chunk 策略：别把知识切碎了

Chunking 是 RAG 的地基。地基歪了，后面再重排也很难救。

### Chunk 大小没有万能值

很多教程喜欢给一个默认值：512、800、1000 Token。这个值只能当起点，不能当结论。

Chunk 太小，容易丢上下文。比如一句“以上情况不适用七天无理由退货”被切到下一块，前一块就会变成误导性证据。

Chunk 太大，又会把很多无关内容一起带进来。检索分数可能因为某一句话很相关而很高，但模型读到的是一整段混杂内容，信噪比反而下降。

Guide 的经验是：

- FAQ、短政策、接口说明：可以从 200 到 500 Token 起步。
- 技术文档、教程、方案文档：可以从 400 到 800 Token 起步。
- 法规、合同、金融政策：更关注条款完整性，优先按标题、条、款、项切。
- 代码类知识库：不要只按 Token 切，优先按文件、类、函数、注释块切。

真正的答案还是评估集给的。把 3 到 5 组 Chunk 参数建成不同索引，用同一批问题比较 Context Recall、Context Precision、答案正确率和平均上下文 Token。

### 语义切分适合稳定文档

语义切分的思路是：不机械按字符数截断，而是根据标题、段落、句子相似度或语义边界来切。

它适合这些场景：

- 文档主题混杂，一页里连续讲多个概念。
- 用户问题更偏概念型，而不是查某个字段。
- 知识库更新频率不高，可以接受较复杂的离线预处理。

它不适合这些场景：

- 文档频繁增量更新，每次重新聚类成本高。
- 文档结构本身已经很清晰，例如 Markdown 标题层级。
- 查询主要是精确查编号、字段、状态、配置项。

语义切分不一定越智能越好。如果你的知识库是接口文档，按 OpenAPI path、method、参数表切，通常比句子 embedding 聚类更可靠。

### Parent-Child Chunk 是很实用的折中

一个常用模式是：**小块负责召回，大块负责生成**。

比如把文档切成 300 Token 的子 Chunk 用于向量检索，但每个子 Chunk 都挂到一个 1200 Token 的父段落上。检索时先命中小块，再把对应父段落放入上下文。

好处很明显：

- 小块更容易精确命中问题。
- 父块保留必要上下文，减少断章取义。
- 比盲目扩大 Top-K 更可控。

适合长文档、教程、政策解读、故障手册等场景。

### 给 Chunk 增加语义入口

有些用户问题和文档原文的表达差异很大。用户问“钱怎么退”，文档写的是“退款申请路径”。这时可以在索引阶段增加额外表示：

- 给每个 Chunk 生成摘要，摘要和正文都入索引。
- 给每个 Chunk 生成可能回答的问题，用问题向量辅助召回。
- 给章节生成标题向量，让概念型问题先命中主题。
- 对代码或表格生成结构化描述，避免原文难以嵌入。

这类方法本质上是在给 Chunk 多开几个入口。代价是建库成本增加，所以建议优先用在高价值知识库，而不是全量无脑开启。

## 召回优化：不要只靠向量相似度

朴素 RAG 的召回通常是：把用户问题转 embedding，然后向量库 Top-K。这个方案能跑 demo，但生产里很快会遇到边界。

### Hybrid Search 是生产默认项

向量检索擅长语义相似，BM25 擅长精确词匹配。两者是互补关系，不是替代关系。

| 查询类型                  | 向量检索表现         | BM25 表现      | 建议               |
| ------------------------- | -------------------- | -------------- | ------------------ |
| “如何取消订阅”            | 能匹配“关闭自动续费” | 可能匹配不到   | 保留向量召回       |
| “错误码 E1027”            | 可能召回泛化故障     | 精确命中错误码 | 必须保留关键词召回 |
| “ABX-4421 型号参数”       | 容易找相似型号       | 精确命中 SKU   | 必须保留关键词召回 |
| “Java 线程池拒绝策略区别” | 语义理解较好         | 能匹配关键词   | 混合更稳           |
| “最新 v3.2 价格政策”      | 需要语义和时间条件   | 可匹配版本号   | Metadata + Hybrid  |

```mermaid
flowchart LR
    %% ========== classDef 配色声明 ==========
    classDef client fill:#00838F,color:#FFFFFF,stroke:none,rx:10,ry:10
    classDef business fill:#E99151,color:#FFFFFF,stroke:none,rx:10,ry:10
    classDef cache fill:#3498DB,color:#FFFFFF,stroke:none,rx:10,ry:10
    classDef success fill:#4CA497,color:#FFFFFF,stroke:none,rx:10,ry:10
    classDef warning fill:#F39C12,color:#FFFFFF,stroke:none,rx:10,ry:10

    %% ========== 节点声明 ==========
    Query[用户 Query]:::client
    Vec[向量检索<br/>语义相似]:::cache
    BM25[BM25 召回<br/>精确匹配]:::cache
    RRF[RRF 融合]:::warning
    Dedupe[去重合并]:::business
    Rerank[Rerank]:::business
    Final[Top-N 候选]:::success

    %% ========== 连线 ==========
    Query --> Vec
    Query --> BM25
    Vec --> RRF
    BM25 --> RRF
    RRF --> Dedupe --> Rerank --> Final

    linkStyle default stroke-width:2px,stroke:#333333,opacity:0.8
```

Hybrid Search 常见做法是两路召回后融合：

- 向量检索返回语义相似候选。
- BM25 或稀疏向量返回关键词候选。
- 用 RRF 或归一化加权分数合并。
- 对合并后的候选去重，再进入 Rerank。

Microsoft Azure AI Search、Google Vertex AI Vector Search、Weaviate 等官方文档都把 Hybrid Search 和 RRF 作为常见融合方式。RRF 的好处是不用强行比较 BM25 分数和向量余弦分数，按排名位置做融合，调参负担更低。

但别把 Hybrid Search 神化。

如果你的文档高度结构化、关键词很少，Hybrid 带来的增益可能有限；如果你的查询大量包含错误码、产品型号、配置项、专有名词，纯向量检索很容易翻车。

### Query Rewrite：先把问题变得可检索

用户的问题通常不是为检索系统写的。

他们会说：

- “这个报错咋整？”
- “钱能退吗？”
- “线上那个限流问题是不是又来了？”

这些问题对人来说有上下文，对检索系统来说却很模糊。Query Rewrite 的目标是：**不改变用户意图，把问题改写成更适合召回的表达**。

常见策略如下：

| 策略                | 适用场景                   | 例子                                                        |
| ------------------- | -------------------------- | ----------------------------------------------------------- |
| 规范化改写          | 口语化、缩写、上下文缺失   | “钱能退吗”改成“退款政策、退款条件、退款流程”                |
| Multi-Query         | 表达可能有多种说法         | 同时检索“取消订阅”“关闭自动续费”“停止会员计划”              |
| Query Decomposition | 问题包含多个子问题         | 把“对比 Stripe 和 Square 的手续费和争议处理”拆成 4 个子问题 |
| Step-back Query     | 问题太细，缺背景           | 先检索“订阅计费规则”，再回答具体取消问题                    |
| HyDE                | 查询太短，和文档形态差异大 | 先生成假设答案，再用假设答案向量检索真实文档                |
| Self-Query          | 问题里包含过滤条件         | 从“查 2025 年 Java 相关政策”提取年份和类别过滤              |

LangChain 的 MultiQueryRetriever、SelfQueryRetriever 等组件就是这类思路的工程化实现。

这里有个坑：**Query Rewrite 必须保留原始问题**。不要只用改写后的查询。工程上可以让原始 query 和改写 query 一起召回，然后融合结果。否则改写模型一旦理解错意图，后面召回全偏。

### Top-K 不是越大越好

盲目扩大 Top-K 是 RAG 调优里最常见的动作，也是最容易制造噪声的动作。

Top-K 变大，确实可能提高召回率。但它也会带来 3 个副作用：

- 候选变多，Rerank 延迟上升。
- 上下文变长，Token 成本上升。
- 无关内容变多，模型更容易被干扰。

更合理的做法是分层设置：

- `recall_top_k`：粗召回候选池，例如 30 到 100。
- `rerank_top_n`：重排后保留，例如 5 到 10。
- `context_top_n`：最终进入上下文，例如 3 到 6。

```mermaid
flowchart TB
    %% ========== classDef 配色声明 ==========
    classDef client fill:#00838F,color:#FFFFFF,stroke:none,rx:10,ry:10
    classDef business fill:#E99151,color:#FFFFFF,stroke:none,rx:10,ry:10
    classDef warning fill:#F39C12,color:#FFFFFF,stroke:none,rx:10,ry:10
    classDef success fill:#4CA497,color:#FFFFFF,stroke:none,rx:10,ry:10

    %% ========== 节点声明 ==========
    Start[用户 Query]:::client
    Recall{粗召回<br/>recall_top_k}:::warning
    Rerank{重排<br/>rerank_top_n}:::business
    Context{上下文<br/>context_top_n}:::success
    Candidates["30~100 条"]:::warning
    TopN["5~10 条"]:::business
    Final["3~6 条"]:::success

    %% ========== 连线 ==========
    Start --> Recall
    Recall -->|候选池| Candidates
    Candidates --> Rerank
    Rerank -->|精选| TopN
    TopN --> Context
    Context -->|进入 Prompt| Final

    linkStyle default stroke-width:2px,stroke:#333333,opacity:0.8
```

也就是说，Top-K 应该分阶段管理，而不是一个参数管到底。

## Rerank：把“相关”重新排成“可回答”

向量检索用的是双塔模型思路：query 和 document 分别编码，再算向量距离。它快，但不够细。

Rerank 通常使用 Cross-Encoder 或专用重排模型，把 query 和候选文档放在一起打分。它慢一些，但能更细粒度判断“这段文本是否真的能回答这个问题”。

### 为什么 Rerank 有用？

向量相似度更像“这两段话语义接近吗”，Rerank 更像“这段话能不能回答这个问题”。

举个例子：

用户问：“线程池为什么会触发拒绝策略？”

向量召回可能找出这些片段：

1. 线程池核心参数说明。
2. 拒绝策略枚举列表。
3. 队列满、线程数达到 maximumPoolSize 后触发拒绝策略的条件。
4. 线程池使用示例代码。

第 1、2 条语义很接近，但第 3 条才是答案核心。Rerank 的价值就是把第 3 条顶上来。

### Rerank 放在哪里？

推荐链路是：

1. Metadata 预过滤。
2. Hybrid Search 粗召回 30 到 100 条。
3. 去重和相邻片段合并。
4. Rerank 选出 5 到 10 条。
5. 上下文压缩后放入 Prompt。

如果候选池里没有正确答案，Rerank 也救不了。所以 Rerank 之前要先看 Context Recall。很多人直接上 reranker，发现没效果，根因是粗召回阶段就没把正确文档找出来。

### LLM Rerank 和专用 Reranker 怎么选？

| 方案                   | 优点                   | 缺点                             | 适用场景                     |
| ---------------------- | ---------------------- | -------------------------------- | ---------------------------- |
| Cross-Encoder Reranker | 相关性判断细，成本可控 | 需要选模型，可能有语言和领域偏差 | 通用生产链路                 |
| LLM 打分               | 可解释性强，规则灵活   | 慢、贵、稳定性受 Prompt 影响     | 小流量、高价值、复杂判断     |
| 规则重排               | 便宜、可控             | 只能处理明确规则                 | 时间、权限、版本、来源优先级 |
| 混合重排               | 灵活，适合复杂业务     | 工程复杂度高                     | 企业知识库、客服、合规场景   |

Guide 的建议：**默认用专用 reranker 做主链路，用规则补业务约束，用 LLM 打分做离线评估或高价值兜底。**

## 上下文工程：别把模型当垃圾桶

RAG 的最后一公里是上下文构建，而不是检索本身。

检索结果不是越多越好。LLM 的上下文窗口虽然越来越长，但注意力、延迟、成本和信噪比仍然是硬约束。无关上下文塞得越多，模型越容易出现以下问题：

- 抓错证据，把相似但不相关的段落当依据。
- 忽略中间位置的重要信息。
- 回答变长但不聚焦。
- 引用错来源。
- 成本和首字延迟明显上升。

**上下文工程的目标，是把有限 Token 留给最能回答问题的证据。**

### 上下文压缩

上下文压缩不是简单摘要，而是围绕当前 query 过滤证据。

常见方式有 3 种：

| 压缩方式     | 做法                       | 风险                 |
| ------------ | -------------------------- | -------------------- |
| 选择性抽取   | 只保留和问题相关的原句     | 可能漏掉隐含条件     |
| 查询相关摘要 | 把长片段压成围绕问题的摘要 | 可能引入改写偏差     |
| 结构化抽取   | 抽取字段、条件、结论、例外 | 依赖抽取 Schema 设计 |

LangChain 的 ContextualCompressionRetriever 就是“基础检索器 + 压缩器”的组合思路。实际落地时，可以先做便宜的规则过滤和去重，再对长片段做 LLM 压缩，避免每个 Chunk 都调用模型。

### 上下文排序也会影响答案

不要随便把检索结果按返回顺序拼接。

更合理的排序策略：

- 最相关证据放前面。
- 同一文档的相邻片段尽量保持原始顺序。
- 互相矛盾的片段标注更新时间和版本。
- 被引用的片段保留来源信息。
- 低置信度证据不要和高置信度证据混在一起。

如果问题需要跨文档对比，可以按“主题分组”组织上下文；如果问题需要按时间分析，可以按时间线组织上下文；如果问题是故障排查，可以按“现象、原因、处理步骤、注意事项”组织上下文。

这就是 Context Engineering 在 RAG 里的具体落点：**不仅决定检索什么，还决定检索结果以什么结构进入模型。**

### Prompt 要限制证据边界

RAG 生成 Prompt 至少要明确 4 条规则：

- 只基于给定上下文回答。
- 上下文不足时明确说无法判断。
- 每个关键结论尽量附来源。
- 不要把相似文档当成当前版本事实。

这几条看起来朴素，但很关键。很多幻觉不是模型不知道，而是 Prompt 没有告诉它“证据不足时可以拒答”。

## 评估：不做评估，优化就是玄学

RAG 评估要拆开看。只看最终答案分数，很难知道到底是哪一环坏了。

### 建一套最小评估集

不用一开始就搞几千条样本。先从 50 到 100 条高价值问题开始：

- 高频用户问题。
- 线上失败问题。
- 业务关键问题。
- 多跳推理问题。
- 精确匹配问题，例如错误码、版本号、SKU。
- 容易越权或过期的问题。
- 应该拒答的问题。

每条样本最好包含：

- `question`：用户原始问题。
- `golden_answer`：理想答案。
- `golden_context`：应该命中的证据片段或文档。
- `metadata_filter`：必要过滤条件。
- `answer_type`：事实问答、流程说明、对比、拒答、摘要等。

### 检索指标和生成指标分开

| 指标              | 衡量对象   | 说明                                  |
| ----------------- | ---------- | ------------------------------------- |
| Hit Rate@K        | 召回       | 正确证据是否出现在前 K 个结果里       |
| MRR               | 排序       | 第一个正确证据排得有多靠前            |
| Context Recall    | 召回完整性 | 回答所需证据是否被找全                |
| Context Precision | 上下文纯度 | 放入上下文的内容有多少是真的相关      |
| Faithfulness      | 生成忠实度 | 答案是否能被上下文支撑                |
| Answer Relevancy  | 回答相关性 | 答案是否真正回应用户问题              |
| Citation Accuracy | 引用准确性 | 引用位置是否支撑对应结论              |
| Latency / Cost    | 工程指标   | P95 延迟、Token、重排耗时、缓存命中率 |

RAGAS、DeepEval、LangSmith 等工具都支持围绕上下文相关性、忠实度、答案相关性做评估。RAGAS 文档里把 Context Precision、Context Recall、Faithfulness、Response Relevancy 等指标拆得比较清楚；DeepEval 也支持把检索和生成指标组合成端到端测试。

但要记住：**LLM-as-a-Judge 不是裁判真理，它只是辅助信号。**

上线前至少抽样人工复核一批结果，校准自动评估器是否偏向长答案、是否漏判引用错误、是否对中文领域术语不敏感。

### 每次改动都要版本化

建议记录这些版本：

- 文档解析器版本。
- Chunk 策略版本。
- Embedding 模型版本。
- 索引参数版本。
- Query Rewrite Prompt 版本。
- Rerank 模型版本。
- 生成 Prompt 版本。
- 评估集版本。

否则今天效果变好，明天一更新知识库又变差，你很难知道是哪一步引入了回归。

## 常见错误

### 错误一：只调 embedding

Embedding 很重要，但它不是全部。

如果 PDF 表格解析错了、Chunk 把条件切丢了、Metadata 没有过滤权限、召回候选里没有正确文档，换再贵的 embedding 模型也只是让错误更稳定。

正确做法：先用评估集判断是召回问题、排序问题、上下文问题还是生成问题，再决定要不要换 embedding。

### 错误二：不做评估

“我感觉好多了”不是指标。

RAG 的改动经常是局部变好、整体变差。比如 Top-K 变大后某些问题能答了，但另一些问题开始被噪声干扰。如果没有固定样本集，你只会记住变好的案例。

正确做法：建立最小评估集，至少覆盖高频问题、失败问题、精确匹配问题、拒答问题。

### 错误三：盲目扩大 Top-K

Top-K 变大不是免费的。

它会增加重排成本、Prompt Token、模型延迟，还会降低上下文信噪比。很多时候应该提高粗召回候选池，再用 Rerank 和压缩筛掉噪声，而不是把更多内容直接塞给模型。

正确做法：区分粗召回 Top-K、重排 Top-N、上下文 Top-N。

### 错误四：把无关上下文塞给模型

上下文窗口不是仓库，更不是垃圾桶。

无关上下文会稀释注意力，也会给模型制造错误依据。尤其是多个版本的政策、相似产品文档、相邻但无关段落混在一起时，模型很容易合成一个看似合理但事实错误的答案。

正确做法：去重、压缩、按证据强度排序，并明确版本和来源。

### 错误五：忽略拒答能力

RAG 不应该永远给答案。

当检索结果置信度低、证据互相矛盾、用户无权限访问关键文档时，系统应该拒答、追问或升级人工，而不是编一个流畅答案。

正确做法：在检索后增加证据质量判断，低置信度时触发重写查询、扩大范围、外部搜索或拒答。

## 一套可落地的排查路径

最后给一套 Guide 比较推荐的排查路径。线上 RAG 效果差时，不要一上来改 Prompt 或换模型，按下面顺序走。

```mermaid
flowchart TB
    %% ========== classDef 配色声明 ==========
    classDef client fill:#00838F,color:#FFFFFF,stroke:none,rx:10,ry:10
    classDef business fill:#E99151,color:#FFFFFF,stroke:none,rx:10,ry:10
    classDef danger fill:#C44545,color:#FFFFFF,stroke:none,rx:10,ry:10
    classDef success fill:#4CA497,color:#FFFFFF,stroke:none,rx:10,ry:10
    classDef warning fill:#F39C12,color:#FFFFFF,stroke:none,rx:10,ry:10

    %% ========== 节点声明 ==========
    Start[失败样本]:::danger
    Step1{正确证据<br/>进入候选池?}:::client
    Step2{正确证据<br/>排名靠前?}:::business
    Step3{上下文<br/>正确?}:::business
    Step4{模型<br/>正确回答?}:::business
    Step5[回归测试]:::success
    RecallFix[查召回]:::warning
    RerankFix[查排序]:::warning
    ContextFix[查上下文]:::warning
    PromptFix[查 Prompt]:::warning

    %% ========== 连线 ==========
    Start --> Step1
    Step1 -->|否| RecallFix
    Step1 -->|是| Step2
    Step2 -->|否| RerankFix
    Step2 -->|是| Step3
    Step3 -->|否| ContextFix
    Step3 -->|是| Step4
    Step4 -->|是| Step5
    Step4 -.->|否| PromptFix

    linkStyle default stroke-width:2px,stroke:#333333,opacity:0.8
```

### 第一步：把失败样本分类

先看 20 到 50 条失败问题，把它们分成几类：

- 完全没召回正确文档。
- 召回了正确文档，但排名靠后。
- 正确文档进入上下文，但答案没用上。
- 答案用了上下文，但理解错了。
- 引用了不存在或不相关来源。
- 应该拒答却强行回答。
- 权限、时间、版本过滤错误。

这一步的价值很高，因为每类问题对应的修复方向完全不同。

### 第二步：先看正确证据有没有进入候选池

如果粗召回 Top-50 里都没有正确证据，优先查：

- 文档是否入库。
- 文档解析是否正确。
- Chunk 是否切断关键事实。
- Metadata 过滤是否过严。
- Query 是否需要改写、分解或 HyDE。
- 是否需要 BM25 或 Hybrid Search。

这时不要先上 Rerank。候选池里没有答案，重排只是重新排列错误。

### 第三步：正确证据在候选池里但没进上下文

如果正确证据在 Top-50，但不在最终上下文，重点查：

- Rerank 模型是否适配语言和领域。
- Rerank 输入是否过长被截断。
- 分数融合是否让关键词结果被压下去。
- 相邻 Chunk 合并是否把噪声一起带入。
- `rerank_top_n` 是否过小。

这类问题通常通过重排、融合权重、候选池大小和去重策略解决。

### 第四步：上下文正确但答案错误

如果正确证据已经放进 Prompt，模型还是答错，重点查：

- Prompt 是否要求基于上下文回答。
- 上下文是否有互相冲突的版本。
- 证据是否在上下文中间位置被淹没。
- 问题是否需要多跳推理或对比表。
- 是否需要结构化输出和引用约束。
- 是否需要先压缩再生成。

这时才应该重点调 Prompt、上下文排序、压缩和生成模型。

### 第五步：建立回归测试

每修一个失败样本，就把它加入评估集。

RAG 系统最怕“修 A 坏 B”。只有失败样本持续沉淀，系统才会越调越稳。

## 生产调优建议

如果你要从零搭一套企业 RAG，Guide 建议按这个优先级落地：

1. 先做数据治理：保证文档解析、去噪、标题层级、页码、表格、Metadata 正确。
2. 建立最小评估集：先用 50 条真实问题跑通回放流程。
3. 调 Chunk 策略：对比固定长度、结构化切分、Parent-Child、语义切分。
4. 引入 Hybrid Search：向量召回负责语义，BM25 或稀疏向量负责精确词。
5. 加入 Query Rewrite：优先处理口语化、缩写、多意图和多跳问题。
6. 加 Rerank：粗召回扩大候选池，重排后只保留高质量证据。
7. 做上下文压缩：去重、裁剪、摘要、结构化抽取，控制 Token 和噪声。
8. 完善生成约束：证据不足就拒答，关键结论带引用。
9. 灰度和监控：按版本记录指标，持续收集失败样本。

这套路径不花哨，但能收敛。

## 要点回顾

RAG 优化不是“换一个更强 embedding 模型”这么简单。真正有效的调优，必须沿着完整链路拆：

- **数据决定上限**：解析、清洗、结构保留、Metadata 是地基。
- Chunk 决定召回粒度：不要迷信默认大小，要用评估集选参数。
- Hybrid Search 提升稳健性：向量负责语义，BM25 负责精确匹配。
- Query Rewrite 解决表达差异：改写、分解、HyDE、Self-Query 都是让问题更可检索。
- Rerank 决定证据顺序：粗召回要全，重排要准。
- 上下文工程决定信噪比：压缩、去重、排序、引用比盲目塞内容更重要。
- 评估决定能否持续优化：没有测试集、没有回放、没有指标，就只能靠感觉调参。

最后记住一句话：**RAG 的瓶颈通常不在某一个参数，而在证据从原始文档走到最终答案的整条路径上。**

## 参考资料

- [Production RAG: The Five Decisions Behind Every System That Works](https://www.bestblogs.dev/article/899eff0a)
- [RAG 优化字典：20 种 RAG 优化方法全解析](https://cloud.tencent.com/developer/article/2634637)
- [Weaviate Hybrid Search Documentation](https://docs.weaviate.io/weaviate/concepts/search/hybrid-search)
- [Microsoft Azure AI Search: Hybrid Search RRF](https://learn.microsoft.com/en-us/azure/search/hybrid-search-ranking)
- [Google Vertex AI Vector Search: Hybrid Search](https://docs.cloud.google.com/vertex-ai/docs/vector-search/about-hybrid-search)
- [Cohere Rerank Documentation](https://docs.cohere.com/docs/rerank-overview)
- [LangChain Retriever API Documentation](https://api.python.langchain.com/en/latest/langchain/retrievers.html)
- [RAGAS Metrics Documentation](https://docs.ragas.io/en/stable/concepts/metrics/available_metrics/context_precision/)
- [DeepEval RAG Evaluation Guide](https://deepeval.com/guides/guides-rag-evaluation)
