首页 > 教程攻略 > ai教程 >RAG 系列 02 — Advanced RAG

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(检索后)**。 ![Advanced RAG 的三阶段管道架构.png](https://developer.qcloudimg.com/http-sa ve/yehe-7660620/fd777710d882b3fd24314cda15673d12.png) **记住这张图,下面所有章节都在它的框架里展开**。 ### 1.2 它解决的核心矛盾 Naive RAG 的失败本质上是**三个矛盾的总爆发**: | 矛盾 | 表现 | Advanced RAG 的解法 | |------|------|--------------------| | **用户语言 ≠ 文档语言** | 同义词、口语化、指代 | Pre-Retrieval(Query 改写) | | **语义召回 ≠ 关键词召回** | 型号、ID、专有名词召不到 | Hybrid Search | | **粗排相似度 ≠ 真正相关性** | top-5 里夹杂无关 chunk | Post-Retrieval(Reranker) | Advanced RAG 不是发明了什么新技术,而是**把这三个矛盾各自交给最适合的组件去解决**。 ### 1.3 它的真实位置:被严重低估的“性价比王者” 一个粗略统计,把工业级 RAG 项目的真实收益按“工程改动 → 准确率提升”画出来: ![工程改动 → 准确率提升.png](https://developer.qcloudimg.com/http-sa ve/yehe-7660620/95ccdd9eb69275f051fda1b18df2c79a.png) **[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,没必要。 **推荐:分层路由** ![分层路由架构.png](https://developer.qcloudimg.com/http-sa ve/yehe-7660620/26c0bea4d9936c57b9f5018d10e24f69.png) **代码骨架**: ```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 的失败模式很具体: ![失败 Case .png](https://developer.qcloudimg.com/http-sa ve/yehe-7660620/e6ff255084f4ce58b98d465929fea805.png) **[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 完整数据流 ![Hybrid Search 的完整执行流程.png](https://developer.qcloudimg.com/http-sa ve/yehe-7660620/528774a530f0c13406727ab9897d6e74.png) ### 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 和 Cross-encoder.png](https://developer.qcloudimg.com/http-sa ve/yehe-7660620/87d40663a84d1c681736272011b17cd2.png) **关键理解**: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 数据流 ![Post-Retrieval 阶段的完整处理流程.png](https://developer.qcloudimg.com/http-sa ve/yehe-7660620/d2a940c12ed22fdf8ad54077b791a1b9.png) ### 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 完整架构 讲完所有组件,看一下它们组合起来的完整架构: ![Advanced RAG 的完整端到端架构.png](https://developer.qcloudimg.com/http-sa ve/yehe-7660620/ac879d5254b8de5d8d8b4db4ab006daf.png) **这就是当前(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 会被骗**。看一个真实例子: ![RAG三层评估体系隐蔽性分析架构图.png](https://developer.qcloudimg.com/http-sa ve/yehe-7660620/4150093dc3ef5337d3c3c554dee630ef.png) **[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:当一条流水线装不下你的业务》