RAG 系列 02 — Advanced RAG
来源:互联网
时间:2026-06-10 07:23:09
# Advanced RAG:把基础工程做到极致,回报到底有多大?
这是 RAG 工程化系列的第二篇。上一篇我们拆了 Naive RAG 的 8 颗雷,得出一个反直觉的结论:**90% 的“模型不够好”,其实是“工程没做好”**。这一篇我们来看,当你愿意把基础工程做到极致,能换来多大的回报。
## 引言
2025 年 3 月,一家 toB SaaS 公司开技术评审会,主题是“RAG 系统下一阶段升级路线”。
技术负责人——一个刚从大厂跳过来的资深工程师——准备了 40 页 PPT,核心方案是:**上 Agentic RAG**。他列了一堆论据:多步推理、自我反思、工具调用、AutoGPT-like 架构……PPT 最后一页写着预算:**每月 LLM 成本 20 万,团队扩招 6 人**。
CEO 听完,问了一个问题:
> “我们现在的 RAG,hit@5 是多少?”
技术负责人愣住,转头问算法工程师。算法工程师翻了电脑,回答:“我们……没系统跑过这个指标。”
CEO 又问:
> “我们用了 Hybrid Search 吗?Reranker 用的什么?Query 改写做了吗?”
三个问题,三个“没有”。
会议室空气凝固。CEO 把 PPT 合上,说了一句话,整个团队记到现在:
> “**你连 Advanced RAG 都没做完,跟我谈什么 Agentic?把基础打完,我给你一半预算,赌一个月把准确率从 60% 干到 85%。**”
一个月后,那家公司的 RAG hit@5 从 62% 涨到了 87%。**没有上任何 Agent,没有换模型,纯靠三件事**:
1. Hybrid Search(BM25 + Vector)
2. Cross-Encoder Reranker
3. 轻量 Query Rewrite
LLM 成本不增反降(context 更精准了,prompt 缩短了)。
这就是 Advanced RAG 的真实威力——**它是整条 RAG 演进光谱上 ROI 最高的一站**,但也是最容易被跳过的一站。
这篇文章想把这件事讲透:
- Advanced RAG 到底加了什么、为什么有效
- 三件核心改造(Hybrid / Rerank / Rewrite)的工程细节
- 那些“看起来很美但生产里翻车”的进阶技术(HyDE、Multi-Query、Step-Back)
- 三层评测指标怎么建
- 何时该停在这一站,何时该继续升级
如果你正卡在“Naive 跑不动,但又不知道下一步走哪”——这 30 分钟会给你一张清晰的路线图。
---
## 一、Advanced RAG 到底是什么:不是堆 trick,是一种工程思维
### 1.1 严格定义
很多人对 Advanced RAG 的理解是“Naive RAG + 几个新组件”。这是错的。
**Advanced RAG 的本质,是把 RAG 从“一锤子买卖”重构为“多阶段管道”**。每一阶段:
- 有独立的优化目标
- 有独立的评测指标
- 有独立的失败模式
- 可以独立替换和升级
它在 Naive RAG 的“线性三段式”基础上,明确划分三个新阶段:**Pre-Retrieval(检索前)、Retrieval(检索)、Post-Retrieval(检索后)**。

**记住这张图,下面所有章节都在它的框架里展开**。
### 1.2 它解决的核心矛盾
Naive RAG 的失败本质上是**三个矛盾的总爆发**:
| 矛盾 | 表现 | Advanced RAG 的解法 |
|------|------|--------------------|
| **用户语言 ≠ 文档语言** | 同义词、口语化、指代 | Pre-Retrieval(Query 改写) |
| **语义召回 ≠ 关键词召回** | 型号、ID、专有名词召不到 | Hybrid Search |
| **粗排相似度 ≠ 真正相关性** | top-5 里夹杂无关 chunk | Post-Retrieval(Reranker) |
Advanced RAG 不是发明了什么新技术,而是**把这三个矛盾各自交给最适合的组件去解决**。
### 1.3 它的真实位置:被严重低估的“性价比王者”
一个粗略统计,把工业级 RAG 项目的真实收益按“工程改动 → 准确率提升”画出来:

**[HIGH CONFIDENCE]** 这张图的两个反直觉结论:
1. **换更贵的 embedding 模型,几乎是 ROI 最低的优化**
2. **Hybrid + Reranker 组合的收益,远大于单独使用任何一个**
这就是为什么说 Advanced RAG 是 RAG 演进光谱上“最值钱的一站”——投入最小,回报最大。
---
## 二、Pre-Retrieval:让用户的“烂问题”变成“好查询”
### 2.1 Query Rewrite:成本最低,ROI 最高的改造
用户的原始 query 通常有三个问题:
- **口语化**:“那个东西怎么搞”
- **不完整**:多轮对话中省略主语(“它的价格”)
- **不精确**:用日常词而非文档术语(“退货”vs“商品返还”)
**最小可行 Query Rewriter**:
```python
REWRITE_PROMPT = """将用户的对话最后一个问题改写成可独立检索的查询。
# 对话历史
{chat_history}
# 用户最新问题
{question}
# 改写规则
1. 消解代词("它"、"这个"、"那个"等)为具体名词
2. 补全省略的主语和上下文
3. 保留专有名词、型号、ID、错误码等原文
4. 如果用户用了口语,转为文档可能使用的正式词汇
5. 如果问题已经独立完整,原样返回
# 改写后的查询(单行,不超过 100 字)
"""
class QueryRewriter:
def __init__(self, llm_client, model="gpt-4o-mini"):
self.llm = llm_client
self.model = model
def rewrite(self, question: str, history: list) -> str:
if not history:
return question
prompt = REWRITE_PROMPT.format(
chat_history=self._format_history(history),
question=question,
)
rewritten = self.llm.complete(prompt, model=self.model, max_tokens=150)
return rewritten.strip()
```
**关键参数选择**:
| 参数 | 推荐值 | 理由 |
|------|--------|------|
| 模型 | gpt-4o-mini / claude-3-haiku / qwen-turbo | 改写不需要顶级模型 |
| max_tokens | 150 | 改写后 query 不应过长 |
| temperature | 0.1 | 改写需要稳定,不要发挥 |
| 触发条件 | 仅多轮对话 | 单轮跳过节省成本 |
**ROI 测算**:
- 成本:每次 ~50ms 延迟 + $0.0001
- 收益:多轮对话场景准确率提升 15-25%
- 结论:**几乎没有不做的理由**
### 2.2 Query Routing:让不同问题走不同路
当系统支持多种数据源时(文档 + SQL + API + KG),不同问题需要走不同检索路径。
**反模式**:用 GPT-5 当一等路由器,每个 query 500ms + $0.001,没必要。
**推荐:分层路由**

**代码骨架**:
```python
class HierarchicalRouter:
def __init__(self, classifier, llm_client):
self.classifier = classifier # 训练好的 BGE-small 分类器
self.llm = llm_client
def route(self, query: str) -> str:
# Layer 1: 规则
if self._matches_sql_pattern(query):
return "sql"
if self._matches_code_pattern(query):
return "code"
if self._matches_faq_pattern(query):
return "faq"
# Layer 2: 小模型
intent = self.classifier.predict(query)
if intent.confidence > 0.8:
return intent.label
# Layer 3: LLM
return self._llm_classify(query)
def _matches_sql_pattern(self, q):
keywords = ['多少', '总数', '平均', '排名', 'top', 'count']
return any(kw in q.lower() for kw in keywords)
```
**训练 Layer 2 分类器的最小成本**:
- 从 Golden Set 标注 500 条意图
- 用 BGE-small 提 embedding + 一个 softmax 头
- 训练 10 分钟,准确率通常 > 92%
### 2.3 那些被吹过头的“进阶 Query 技术”
> 这一节泼冷水。下面三个技术在论文和教程里被吹得神乎其神,但工业实测的收益远不如宣传。
#### HyDE(Hypothetical Document Embeddings)
**原理**:让 LLM 先“幻想”一个理想答案,用这个假答案的 embedding 去检索。
**为什么被吹**:理论上能弥合 query 和 doc 的 embedding 分布差异。
**实际工业表现**:
| 场景 | HyDE 收益 |
|------|----------|
| 学术问答(NQ、HotpotQA) | +5-8%(论文数据) |
| 通用领域问答 | +2-3% |
| 企业领域问答 | **-2% 到 +3%(高方差)** |
| 长尾问题 | **常常变差** |
**[MODERATE CONFIDENCE]** 经验表明:HyDE 在领域 LLM 知识较多时有效(如通用编程问题),但在领域 LLM 完全不懂时(如内部业务、新产品),**LLM 幻想出来的“假答案”会把检索带偏**。
**代价**:每次 query +1 次 LLM 调用(+300-800ms)
**建议**:除非 A/B 测试明确证明收益,否则不要默认上。
#### Multi-Query
**原理**:让 LLM 生成 3-5 个 query 变体,并行检索后合并。
**实际收益**:
- 通用场景:+5-10%
- 但代价是 N 倍 token + N 倍延迟
- 大部分收益可以通过“Hybrid Search + 好 chunker”达到
**何时值得**:召回明显不足且预算充裕时。
#### Step-Back Prompting
**原理**:把具体问题先抽象成上层概念,检索抽象概念,再回答具体问题。
**实际收益**:
- 在知识层级清晰的领域(如教科书)有效
- 在企业平面文档中**几乎没收益**
**建议**:在 90% 的工业场景,把 Step-Back 列入“知道但不用”清单。
### 2.4 Pre-Retrieval 阶段的工程取舍总结
| 技术 | 必做 | 视情况 | 不推荐 |
|------|------|--------|--------|
| 多轮 Query 改写 | ✅ | | |
| Query Routing(多数据源时) | ✅ | | |
| 同义词扩展(有领域黑话时) | | ✅ | |
| HyDE | | A/B 后定 | |
| Multi-Query | | 预算允许时 | |
| Step-Back | | | ❌(除非教科书类语料) |
---
## 三、Retrieval:Hybrid Search 是底线,不是选项
### 3.1 为什么单纯 Vector Search 不够
Naive RAG 用纯 dense vector search 的失败模式很具体:

**[HIGH CONFIDENCE]** 2024 年之后,**生产环境只用 dense vector search 是不负责任的**。Hybrid Search 是底线,不是选项。
### 3.2 Hybrid Search 的三个工程坑
#### 坑 1:双引擎的数据一致性灾难
最常见反模式:
- BM25 用 Elasticsearch
- Vector 用 Pinecone
- 自己写代码同步
这种架构的真实问题:
- 一边写成功一边写失败 → 数据不一致
- 增量同步延迟不一致 → 同一文档两边状态不同
- 删除传播不同步 → 一边能搜到,一边搜不到
- 没法做原子重建
**解法(按推荐度排序)**:
| 方案 | 适合 | 坑 |
|------|------|---|
| **Elasticsearch 8.x / OpenSearch** 单引擎双能力 | 新项目首选 | ES 向量性能不如专业向量库,但够用 |
| **Vespa** | 大规模、需要细粒度控制 | 学习曲线陡 |
| **Qdrant + payload BM25** | 中等规模 | BM25 实现不如 ES 成熟 |
| **Wea viate hybrid** | 已经在用 Wea viate | 锁定 |
| 双引擎自己同步 | **不推荐** | 除非有强 ops 团队 |
**默认推荐**:新项目用 **Elasticsearch 8.x** 或 **OpenSearch**。理由:
- 单引擎一致性问题消失
- BM25 是 ES 的看家本领
- 向量能力虽然不顶尖,但够用
- 运维生态成熟,招人容易
#### 坑 2:中文 BM25 的 tokenizer 默认是错的
ES 默认 tokenizer 对中文是**单字切分**,召回质量极差:
```bash
# 默认 standard analyzer
POST /_analyze
{
"analyzer": "standard",
"text": "退款政策怎么申请"
}
# 输出:["退", "款", "政", "策", "怎", "么", "申", "请"]
# 这种 token 等于没切,BM25 完全失效
```
**必须配中文分词器**:
```json
PUT /docs
{
"settings": {
"analysis": {
"analyzer": {
"ik_smart_analyzer": {
"type": "custom",
"tokenizer": "ik_max_word",
"filter": ["lowercase"]
}
}
}
},
"mappings": {
"properties": {
"content": {
"type": "text",
"analyzer": "ik_max_word",
"search_analyzer": "ik_smart"
},
"vector": {
"type": "dense_vector",
"dims": 1024,
"index": true,
"similarity": "cosine"
}
}
}
}
```
**关键细节**:
- 写入用 `ik_max_word`(细粒度切分,召回更全)
- 查询用 `ik_smart`(粗粒度切分,精度更高)
- 这种“非对称分词”是 ES 中文搜索的标准实践
**验证分词效果**:
```bash
POST /docs/_analyze
{
"analyzer": "ik_max_word",
"text": "退款政策怎么申请"
}
# 应该看到:["退款", "政策", "怎么", "申请"]
```
#### 坑 3:RRF 融合的常数 k 不调就用 60
RRF(Reciprocal Rank Fusion)公式:
```
score(d) = Σ 1 / (k + rank_i(d))
```
`k=60` 是原论文值,但**不一定是你场景的最优值**。建议在 Golden Set 上 grid search:
```python
import numpy as np
def rrf_fuse(rankings_list, k=60, top_n=20):
"""
rankings_list: [
[doc_id_1, doc_id_2, ...], # vector ranking
[doc_id_3, doc_id_1, ...], # bm25 ranking
]
"""
scores = {}
for ranking in rankings_list:
for rank, doc_id in enumerate(ranking, start=1):
scores[doc_id] = scores.get(doc_id, 0) + 1.0 / (k + rank)
sorted_docs = sorted(scores.items(), key=lambda x: -x[1])
return [doc_id for doc_id, _ in sorted_docs[:top_n]]
# 在 Golden Set 上 grid search 最优 k
def find_best_k(golden_set, k_candidates=[10, 20, 30, 60, 100]):
best_k, best_recall = 60, 0
for k in k_candidates:
recall = evaluate_rrf(golden_set, k)
if recall > best_recall:
best_k, best_recall = k, recall
return best_k
# 经验值:中文短 query 通常 k=20-30 表现更好
```
### 3.3 Hybrid Search 完整数据流

### 3.4 Hybrid 调优的实战参数
```python
class HybridRetriever:
def __init__(self, es_client, embedder):
self.es = es_client
self.embedder = embedder
# 这些参数都需要在 Golden Set 上调优
self.vector_top_k = 100 # 单路召回数
self.bm25_top_k = 100 # 单路召回数
self.rrf_k = 30 # RRF 常数
self.final_top_k = 50 # 给 reranker 的数量
def search(self, query: str, filters: dict = None) -> list:
# 并行执行两路
with ThreadPoolExecutor(max_workers=2) as executor:
vector_future = executor.submit(
self._vector_search, query, filters
)
bm25_future = executor.submit(
self._bm25_search, query, filters
)
vector_results = vector_future.result()
bm25_results = bm25_future.result()
# RRF 融合
fused = rrf_fuse(
[vector_results, bm25_results],
k=self.rrf_k,
top_n=self.final_top_k,
)
return fused
```
**关键工程细节**:
- **必须并行**:串行会让总延迟 = vector + bm25,并行后 = max(vector, bm25)
- **超时控制**:任何一路超时(如 500ms)应该返回部分结果而不是整体失败
- **降级路径**:BM25 挂了走纯 vector,反之亦然
---
## 四、Post-Retrieval:Reranker 是单点 ROI 最高的改造
### 4.1 为什么需要 Reranker
Hybrid Search 输出的 top-50 里,通常有 5-10 个真正相关的,其他是“看起来相关但不是”。问题是:
- 直接送给 LLM 50 个 chunk → token 爆炸 + lost in the middle
- 只送前 5 个 → 漏掉真正相关的
**Reranker 的作用**:用更精细但更慢的模型,对粗排结果重新打分。
**Bi-encoder vs Cross-encoder 的本质差异**:

**关键理解**:Bi-encoder(vector search 用的)只能做“近似相关”,Cross-encoder(reranker 用的)能做“精确相关性判断”。两者必须配合,不能互相替代。
### 4.2 Reranker 选型对比
| Reranker | 类型 | 中文 | 延迟 (top-50) | 成本 | 推荐场景 |
|----------|------|------|--------------|------|---------|
| **Cohere Rerank v3** | API | ⭐⭐⭐⭐ | ~300ms | $2/1000 调用 | 不想运维 |
| **Cohere Rerank Multilingual v3** | API | ⭐⭐⭐⭐⭐ | ~300ms | $2/1000 调用 | 多语言 |
| **bge-reranker-v2-m3** | 自托管 | ⭐⭐⭐⭐⭐ | ~160ms (A10 GPU) | GPU 成本 | 中文项目首选 |
| **bge-reranker-large** | 自托管 | ⭐⭐⭐⭐ | ~120ms (A10) | GPU 成本 | 资源紧 |
| **jina-reranker-v2** | API/自托管 | ⭐⭐⭐⭐ | ~200ms | 中等 | 多语言 |
| **mxbai-rerank-large-v1** | 自托管 | ⭐⭐⭐ | ~150ms | GPU 成本 | 英文为主 |
**默认推荐**:
- 中文项目 + 能部署 GPU:**bge-reranker-v2-m3**
- 不想运维:**Cohere Rerank Multilingual v3**
- 资源极紧:**bge-reranker-large**
### 4.3 Reranker 的三个工程坑
#### 坑 1:Rerank 输入数 → 延迟爆炸
Cross-encoder 延迟随 candidate 数线性增长:
| Rerank 输入数 | Cohere v3 | bge-reranker-v2-m3 (A10 GPU) |
|--------------|-----------|------------------------------|
| 20 | ~80ms | ~50ms |
| 50 | ~200ms | ~160ms |
| 100 | ~400ms | ~320ms |
| 200 | ~800ms | ~640ms |
**实践参数**:
```python
# 粗排取 top-50 → rerank 到 top-5
# 再多收益边际递减,延迟却线性涨
retriever_top_k = 50 # 给 reranker 的输入
reranker_top_k = 5 # 给 LLM 的输出
```
**[HIGH CONFIDENCE]** 见过最离谱的配置:retriever_top_k=500,导致单次 query 延迟 4 秒。改成 50 后延迟降到 350ms,**召回质量基本不变**。
#### 坑 2:Rerank score 不归一 → 阈值过滤失效
不同 reranker 输出的 score 范围完全不同:
- Cohere 返回 0-1 概率
- BGE 返回 logit(可能 -10 到 +10)
如果要做“低于 X 直接丢弃”的过滤,必须先归一:
```python
import numpy as np
def normalize_scores(raw_scores, method="sigmoid"):
if method == "sigmoid":
# 适合 BGE 这种 logit 输出
return 1 / (1 + np.exp(-np.array(raw_scores)))
elif method == "minmax":
# 适合任意 score 分布
s = np.array(raw_scores)
return (s - s.min()) / (s.max() - s.min() + 1e-9)
elif method == "softmax":
# 强制总和=1,适合"竞争性"场景
s = np.array(raw_scores)
exp_s = np.exp(s - s.max())
return exp_s / exp_s.sum()
```
#### 坑 3:Rerank 完了就送 LLM,浪费了置信度信号
加一层“置信度感知”的输出:
```python
@dataclass
class RerankedChunk:
chunk: Chunk
score: float
rank: int
def post_rerank_with_confidence(reranked: list, threshold: float = 0.5):
above = [r for r in reranked if r.score >= threshold]
if len(above) >= 3:
return above[:5], "high"
elif len(above) >= 1:
return above + reranked[len(above):3], "medium"
else:
# 没有高置信度结果,全部低分
return reranked[:3], "low"
# 下游 prompt 根据 confidence 调整措辞
def build_prompt_by_confidence(question, chunks, confidence):
if confidence == "high":
return HIGH_CONF_PROMPT.format(question=question, context=chunks)
elif confidence == "low":
return LOW_CONF_PROMPT.format(question=question, context=chunks)
# LOW_CONF_PROMPT 要求 LLM 更倾向于 "无法确定" 而非给出确定答案
```
### 4.4 完整的 Post-Retrieval 数据流

### 4.5 Context Compression:被低估的“性价比补丁”
当 reranker 后的 top-5 chunk 总 token 仍然过大(如 > 4000)时,可以用 **LLMLingua** 这类工具压缩:
```python
from llmlingua import PromptCompressor
compressor = PromptCompressor(
model_name="microsoft/llmlingua-2-xlm-roberta-large-meetingbank",
use_llmlingua2=True,
)
result = compressor.compress_prompt(
context=[chunk.text for chunk in reranked_chunks],
question=question,
target_token=2000, # 目标 token 数
rate=0.5, # 或者按比例压缩
)
compressed_context = result['compressed_prompt']
```
**真实收益**:
- Token 节省:40-60%
- 准确率影响:通常 ±2% 以内
- 延迟代价:+100-200ms
**何时值得用**:
- LLM 调用成本占总成本主要部分时
- Context 经常超长时
- 不想换更便宜模型时
---
## 五、把所有组件拼起来:Advanced RAG 完整架构
讲完所有组件,看一下它们组合起来的完整架构:

**这就是当前(2024-2025)工业级 RAG 系统的“标配”**。所有 toC 的 AI 搜索产品(Perplexity、Glean、Notion AI 等),核心架构都是这一套,只是某些组件被替换或加强。
---
## 六、三层评测:Advanced RAG 必须有的“质量护栏”
Naive RAG 阶段只看 4 个指标(hit@k、fact_recall、forbidden_rate)。Advanced RAG 阶段必须升级到**三层**,因为现在有三个阶段可能出问题。
### 6.1 三层指标体系
```python
# Layer 1: Retrieval(直接评 retriever)
@dataclass
class RetrievalMetrics:
hit_at_3: float # top-3 命中
hit_at_5: float # top-5 命中
hit_at_50: float # 粗排召回(决定 reranker 输入质量)
mrr_at_10: float # 平均倒数排名
ndcg_at_10: float # 排序质量
# Layer 2: Context(评 reranker 后给 LLM 的输入)
@dataclass
class ContextMetrics:
context_precision: float # 给 LLM 的 chunk 中相关比例
context_recall: float # 必要信息是否都在 context 里
context_relevance: float # LLM-as-judge 评相关性
# Layer 3: Answer(评最终输出)
@dataclass
class AnswerMetrics:
faithfulness: float # 答案能否从 context 推出
answer_relevance: float # 答案是否回答了问题
citation_accuracy: float# 引用是否真实存在
hallucination_rate: float # 无引用 claim 的比例
```
### 6.2 为什么必须三层
**只看 Layer 3 会被骗**。看一个真实例子:

**[HIGH CONFIDENCE]** 任何架构变更必须**三层都不退化**才能上线。只看 Answer 指标会被“答得流畅但召回错”骗。
### 6.3 评测工具选型
| 工具 | 适合 | 优点 | 缺点 |
|------|------|------|------|
| **RAGAS** | 通用 RAG | 开源、指标全 | 默认 prompt 偏英文 |
| **TruLens** | 偏可观测性 | 实时监控集成好 | 学习曲线 |
| **Phoenix (Arize)** | 大规模生产 | 企业级 | 重 |
| **DeepEval** | 单元测试式 | pytest 集成 | 较新 |
| **自建** | 强定制需求 | 完全可控 | 维护成本 |
**建议**:起步用 RAGAS + 自建 Golden Set,规模上来后加 Phoenix 或 Langfuse 做生产监控。
### 6.4 把三层评测接入 CI
```yaml
# .github/workflows/advanced-rag-eval.yml
name: Advanced RAG Evaluation
on:
pull_request:
paths: ['rag/**', 'prompts/**', 'configs/**']
jobs:
three-layer-eval:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: pip install -r requirements.txt
# Layer 1
- name: Retrieval Eval
run: python -m rag.eval.retrieval --golden golden.yaml --report retrieval.json
# Layer 2
- name: Context Eval
run: python -m rag.eval.context --golden golden.yaml --report context.json
# Layer 3
- name: Answer Eval
run: python -m rag.eval.answer --golden golden.yaml --report answer.json
# Gate: 任何一层退化 5% 阻断
- name: Quality Gate
run: |
python -m rag.eval.gate \
--reports retrieval.json context.json answer.json \
--baseline baselines/ \
--threshold 0.05
```
---
## 七、Advanced RAG 的失败模式 → 对应解法
经过 Naive RAG 调优,进入 Advanced RAG 后,你会遇到一组新的失败模式。提前准备:
### 7.1 Hybrid Search 出现“BM25 主导,Vector 几乎无效”
**症状**:RRF 融合后结果几乎和纯 BM25 一样。
**根因**:embedding 模型在你的语料上表现差,score 都很低。
**解法**:
- 验证 embedding 选型(用 §3 的方法)
- 检查 vector top-k 是否设得太低(< 50 时 vector 信号容易被淹没)
- 调整 RRF 的 k 值(k 越大,rank 1 和 rank 100 差距越小)
### 7.2 Reranker 后 top-1 score 都很低
**症状**:rerank 后所有 chunk 的 score 都低于 0.3。
**根因**:粗排召回里根本没有相关内容。
**解法**:
- **不要继续在 reranker 上调**
- 回头看 Layer 1 hit@50 是不是太低
- 问题在 retrieval,不在 rerank
### 7.3 加了 Query Rewrite 反而准确率下降
**症状**:单轮 query 改写后效果变差。
**根因**:
- 改写 prompt 让 LLM “发挥过头”,加了原 query 没有的词
- 单轮 query 本来就完整,改写引入了噪声
**解法**:
```python
def smart_rewrite(query, history):
# 单轮 + query 已经完整 → 不改写
if not history and len(query) > 10 and not has_pronoun(query):
return query
return rewriter.rewrite(query, history)
```
### 7.4 Hybrid Search 延迟突然飙升
**症状**:之前 P95 200ms,突然涨到 800ms。
**常见根因**:
- ES 索引段(segment)过多没合并 → 强制 merge
- 向量索引到了重建临界点 → 检查 HNSW 参数
- 网络延迟(特别是双引擎架构)→ 切单引擎
### 7.5 LLM 开始“创造性回答”
**症状**:明明 context 里有答案,LLM 给出了 context 外的内容。
**根因**:
- Reranker 把不相关 chunk 排到前面
- Context 过长触发 lost in the middle
- Prompt 强约束被淹没在长 context 中
**解法**:
- 强约束 prompt 放在 context **之前 + 之后**(夹心结构)
- Context 压缩到 < 3000 token
- 提高 reranker 阈值
```python
SANDWICH_PROMPT = """{strict_rules}
# 文档
{context}
# 重申规则
{strict_rules}
# 问题
{question}
# 答案
"""
```
---
## 八、决策框架:何时停在 Advanced,何时升级 Modular
### 8.1 应该停留在 Advanced RAG 的场景
| 场景 | 理由 |
|------|------|
| 单一数据源(仅文档库) | 不需要路由多种检索器 |
| 问题模式可枚举(< 20 种) | 静态 pipeline 够用 |
| 用户量 < 10000 / 日 | 没必要分流 |
| 延迟敏感(P99 < 2s) | Modular 加分支会引入延迟 |
| **80% 工业场景** | Advanced RAG 是足够好的终点 |
**[HIGH CONFIDENCE]** 这是本文最重要的反共识结论之一:**80% 的企业 RAG 项目,Advanced RAG 就是终点**。Modular 和 Agentic 是为剩下 20% 准备的。
### 8.2 应该升级 Modular 的信号
满足任意两条:
- [ ] 数据源 ≥ 3 个异构源(文档 + SQL + KG + API)
- [ ] 可枚举出 ≥ 4 类 query,每类最优 pipeline 不同
- [ ] 需要在模块级别做 A/B 测试
- [ ] Advanced RAG 已稳定运行 ≥ 3 个月
- [ ] 三层指标都触到天花板(hit@5 > 85%, faithfulness > 0.9)
**不要因为“听起来更高级”升级**。Modular RAG 的运维复杂度是 Advanced 的 3-5 倍。
---
## 九、Advanced RAG 上线 Checklist
打勾才能上线。**这是 Naive RAG Checklist 的超集**——Naive 的全部要求依然适用,加上下面这些。
### Pre-Retrieval
- [ ] Query Rewriter 覆盖多轮对话(gpt-4o-mini 或同级)
- [ ] Query Router 分层实现(规则 → 小模型 → LLM)
- [ ] 单轮 + 完整 query 跳过改写(避免引入噪声)
### Retrieval
- [ ] Hybrid Search 在单引擎实现(避免双写一致性)
- [ ] BM25 tokenizer 适配语言(中文用 ik_max_word + ik_smart)
- [ ] Vector 和 BM25 并行执行(不要串行)
- [ ] RRF 的 k 值在 Golden Set 上 grid search 优化
- [ ] 单路超时降级(不要整体失败)
### Post-Retrieval
- [ ] Reranker 选型经过中文 Golden Set 验证
- [ ] retriever_top_k=50 → reranker_top_k=5(不要更多)
- [ ] Rerank score 归一化
- [ ] 置信度感知输出(low-conf 走更保守 prompt)
- [ ] Context 总 token 上限(避免 lost in the middle)
### Generation
- [ ] 强约束 prompt 用“夹心结构”
- [ ] Post-validation 检查引用 + 措辞
- [ ] LLM 调用超时 + 重试 + 兜底
### 评测
- [ ] 三层评测全部建立(Retrieval / Context / Answer)
- [ ] Golden Set ≥ 100 条(Advanced 阶段要扩充)
- [ ] CI 跑三层评测,任一层退化 5% 阻断
- [ ] A/B 框架(至少能按用户分桶)
### 可观测性
- [ ] 每个阶段独立 trace(不只是端到端)
- [ ] Pre/Retrieval/Post/Gen 延迟分别监控
- [ ] Reranker 输入数、置信度分布监控
- [ ] 单 query 成本告警
### 性能目标
- [ ] P95 latency ≤ 2s
- [ ] P99 latency ≤ 3s
- [ ] 单 query 成本 < $0.02
---
## 结语:先做好“无聊的工程”,再谈“性感的架构”
写到这里,必须重复第一篇的那句话:**RAG 工程师真正稀缺的能力,不是用最新模型,是知道什么时候该克制**。
Advanced RAG 不是一个“中间过渡阶段”。**对绝大多数企业场景,它就是终点**。
留给你三个观点:
**观点一:Hybrid + Reranker 是 RAG 工程光谱上 ROI 最高的两个改动。**
如果你只能做两件事,就做这两件。不要换更贵的 embedding,不要上 HyDE,不要研究 Multi-Query——这两个能解决工业语料 70-80% 的问题。
**观点二:三层评测是 Advanced RAG 的“质量护栏”,不是可选项。**
只看 Answer 指标的项目,迟早会被“流畅地答错”打脸。一旦答错变成习惯,整个项目的可信度就崩了,到时候再补评测已经晚了。
**观点三:80% 的 RAG 项目,终点就是 Advanced RAG。**
不要被会议室里“我们要不要上 Agentic”的讨论带偏。先问:
- 我们的 Hybrid Search 调好了吗?
- Reranker 阈值校准了吗?
- 三层指标稳定吗?
如果这三个问题有一个回答不上来,**所有关于 Modular 和 Agentic 的讨论都是空中楼阁**。
那个被 CEO 骂的技术负责人,半年后跟我喝酒时说了一句话,我觉得是这篇文章最好的总结:
> “我以前觉得,做 AI 工程师就是要追最新的论文、用最新的架构。后来才明白,**先把 Hybrid + Rerank 这种‘无聊的工程’做扎实,比追十个新框架都有用**。”
---
**留给读者的三个问题**:
1. 你的 RAG 系统有 **Hybrid Search** 吗?BM25 的 tokenizer 用对了吗?
2. 你的 Reranker 阈值是用 Golden Set 校准的,还是抄教程默认值?
3. 你能回答 “我们的 hit@5、context_precision、faithfulness 分别是多少” 吗?如果不能——**别想下一站,回来把这一站做完**。
---
> **下一篇预告**:《RAG 系列 03 — Modular RAG:当一条流水线装不下你的业务》