CRAG 架构与置信度路由

12 阅读11分钟

系列导航
📍 第 1 篇:CRAG 架构与置信度路由(本文)
📄 第 2 篇:RRF 混合检索 + BGE 重排序
📄 第 3 篇:语义切片 + Ragas 评估体系
📄 第 4 篇:生产环境部署与优化

摘要

传统 RAG 系统的致命缺陷在于"盲目自信"——无法判断检索质量,即使返回的文档完全不相关也会硬着头皮生成答案。本文介绍 CRAG(Corrective RAG)架构,通过"置信度评估→动态路由"机制让 RAG 具备自我反思能力,在 Agentic RAG 知识库专家系统中实现 Context Recall +26%(0.62→0.78)、Faithfulness 0.85 的效果提升。

环境依赖:Python 3.11+, LangGraph 0.2.0+, OpenAI SDK 1.12.0+, Qdrant Client 1.7.0+


引言:一个让所有 RAG 开发者头疼的场景

你花了两周时间搭建 RAG 系统:精心调优 Embedding 模型、配置向量数据库、设计 Prompt 模板。上线第一天,用户问了一个问题:"公司的远程办公政策是什么?"

检索模块返回了 5 个文档:

  • 文档 1:2019 年的考勤制度(已过期)
  • 文档 2:会议室预订流程(完全无关)
  • 文档 3:2023 年远程办公政策(相关!)
  • 文档 4:某个员工的请假申请(噪音)
  • 文档 5:IT 设备借用规定(弱相关)

传统 RAG 会怎么做?把这 5 个文档全部塞进 Prompt,让 LLM 生成答案。结果?模型被噪音文档误导,输出了一个看似专业但实际错误的答案——混合了过期政策和无关信息。

这不是个例。在实际生产环境中,检索质量问题是 RAG 系统失败的主要原因之一。问题的本质在于:传统 RAG 缺少"评估"环节,无法判断检索结果是否可信,更无法采取补救措施

如何让 RAG 系统"知道它不知道"?这是本文要解决的核心问题。


传统 RAG 的"盲目自信"困境

传统 RAG 的工作流程是一条固定流水线:

用户提问 → 向量检索 → 拼接上下文 → LLM 生成答案

这个流程有三个致命缺陷:

1. 无质量评估
检索模块返回 Top-K 文档后,系统不会判断这些文档是否真的相关。即使相似度分数很低(比如 0.3),也会被当作"有效上下文"送入 LLM。

2. 无反馈机制
当检索失败时(比如知识库中根本没有相关文档),系统不会尝试其他策略,而是直接让 LLM 基于空洞的上下文"硬编"答案。

3. 无动态路由
所有问题都走同一条路径,无论是简单的事实查询("公司地址是什么?")还是复杂的推理问题("如何优化跨部门协作流程?"),都用相同的检索策略处理。

用架构图表示:

┌─────────┐     ┌─────────┐     ┌─────────┐     ┌─────────┐
│ 用户提问 │ ──→ │ 向量检索 │ ──→ │ 拼接上下文│ ──→ │ LLM 生成 │
└─────────┘     └─────────┘     └─────────┘     └─────────┘
                                                      ↓
                                                  返回答案

这是一条单向直线,没有分支、没有回退、没有自我修正。

量化这个问题:在我的测试数据集上,传统向量检索的 Context Recall 只有 0.62——意味着 38% 的相关信息根本没被检索到。更糟糕的是,Faithfulness(答案忠实度)只有 0.72,说明生成的答案经常偏离事实。


CRAG 登场:给 RAG 装上"自我反思"模块

CRAG(Corrective RAG)由 Shi-Qi Yan 等人在 2024 年提出(arXiv:2401.15884),核心思想只有一句话:

在检索和生成之间插入一个"相关性评估"节点,根据评估结果动态选择后续路径。

这个简单的改动带来了本质变化:RAG 从"流水线"变成了"状态机"。

CRAG 的完整流程:

                    ┌─────────────┐
                    │   用户提问   │
                    └──────┬──────┘
                           ↓
                    ┌─────────────┐
                    │  向量检索    │
                    └──────┬──────┘
                           ↓
                    ┌─────────────┐
                    │ 置信度评估   │ ← 核心创新
                    └──────┬──────┘
                           ↓
              ┌────────────┼────────────┐
              ↓            ↓            ↓
         置信度≥7      3≤置信度<7    置信度<3
              ↓            ↓            ↓
        ┌─────────┐  ┌─────────┐  ┌─────────┐
        │直接生成  │  │查询重写  │  │Web搜索  │
        └─────────┘  └────┬────┘  └────┬────┘
                          ↓            ↓
                     ┌─────────┐  ┌─────────┐
                     │二次检索  │  │外部知识  │
                     └────┬────┘  └────┬────┘
                          ↓            ↓
                     ┌─────────────────┐
                     │   LLM 生成答案   │
                     └─────────────────┘

对比传统 RAG,CRAG 的本质变化是:

维度传统 RAGCRAG
架构模式固定流水线动态状态机
质量评估置信度打分(0-10)
失败处理硬着头皮生成查询重写 / Web 回退
路由策略单一路径3 条分支(高/中/低置信度)
自我修正不支持支持(最多 2 次重试)

这就是"自我反思"的含义:系统能够评估自己的检索结果,并根据评估结果调整行为


置信度路由:3/7 阈值是怎么来的?

CRAG 的核心是"置信度评估器"。但如何设计这个评估器?阈值怎么定?

评估器设计

我使用 gpt-4o-mini 作为评估器(而不是 gpt-4o),原因有三:

  1. 分类任务足够:判断文档相关性是分类问题,不需要强推理能力
  2. 延迟降低 10-20 倍:gpt-4o-mini 平均延迟 200ms,gpt-4o 需要 2-3s
  3. 成本降低 50 倍:评估器会被频繁调用,成本敏感

评估器的输入是"用户问题 + 检索到的文档",输出是:

  • 置信度分数(0-10):文档与问题的相关性
  • 推理理由:为什么给这个分数(用于调试)

代码实现

from openai import OpenAI
from typing import List, Dict

client = OpenAI()

def evaluate_relevance(query: str, documents: List[str]) -> Dict:
    """
    评估检索文档与查询的相关性
    
    Args:
        query: 用户查询
        documents: 检索到的文档列表
    
    Returns:
        {
            "confidence": float,  # 0-10 的置信度分数
            "reasoning": str      # 评估理由
        }
    """
    # 构建评估 prompt
    docs_text = "\n\n".join([f"文档 {i+1}:\n{doc}" for i, doc in enumerate(documents)])
    
    prompt = f"""你是一个文档相关性评估专家。请评估以下检索文档与用户查询的相关性。

用户查询:{query}

检索到的文档:
{docs_text}

请按以下格式输出:
1. 置信度分数(0-10):
   - 0-3: 文档完全不相关或严重偏离主题
   - 4-6: 文档部分相关但信息不完整
   - 7-10: 文档高度相关且包含足够信息

2. 评估理由:简要说明为什么给出这个分数

输出格式:
分数: [0-10的数字]
理由: [你的评估理由]"""

    response = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[{"role": "user", "content": prompt}],
        temperature=0  # 降低随机性,保证评估稳定
    )
    
    # 解析输出
    content = response.choices[0].message.content
    lines = content.strip().split('\n')
    
    confidence = 5.0  # 默认值
    reasoning = ""
    
    for line in lines:
        if line.startswith("分数:"):
            try:
                confidence = float(line.split(":")[1].strip())
            except:
                pass
        elif line.startswith("理由:"):
            reasoning = line.split(":", 1)[1].strip()
    
    return {
        "confidence": confidence,
        "reasoning": reasoning
    }

# 使用示例
query = "Transformer 的注意力机制是什么?"
documents = [
    "Transformer 使用自注意力机制来处理序列数据...",
    "CNN 是一种卷积神经网络..."
]

result = evaluate_relevance(query, documents)
print(f"置信度: {result['confidence']}")
print(f"理由: {result['reasoning']}")

阈值选择的消融实验

论文建议的阈值是 (3, 7),但我不想盲目照搬。我在 50 个测试问题上尝试了三组阈值:

阈值组合直接生成率查询重写率Web 回退率Context Recall平均延迟
(5, 8)12%64%24%0.713.8s
(4, 7)38%48%14%0.752.9s
(3, 7)58%32%10%0.782.3s

为什么 (3, 7) 最优?

  • (5, 8) 的问题:高置信度区间过窄(8-10),导致大量中等质量的检索被降级到"查询重写",效率低下。比如"公司地址是什么?"这种简单问题,检索到的文档置信度是 7.5,本应直接生成,却被强制重写。

  • (4, 7) 的问题:效果中等,但低置信度阈值 4 太高,一些"勉强相关"的文档(置信度 3.5)被直接回退到 Web 搜索,浪费了知识库中的有效信息。

  • (3, 7) 的优势

    • 简单问题(事实查询)99% 的置信度≥7,直接生成,节省 token
    • 边界问题(模糊查询)触发查询重写,成功率高
    • 只有真正"检索不到"的问题(置信度<3)才回退 Web,避免过度依赖外部搜索

三条路由详解

路由 1:置信度≥7 → 直接生成
适用场景:检索到的文档高度相关,可以直接回答问题。
示例:"公司的年假政策是什么?" → 检索到《员工手册-休假制度》,置信度 9.2 → 直接生成。

路由 2:3≤置信度<7 → 查询重写
适用场景:检索结果中等质量,可能是用户问题表述不清或关键词不匹配。
示例:"怎么申请在家办公?" → 检索到《考勤管理办法》(弱相关),置信度 5.1 → 重写为"远程办公申请流程" → 二次检索到《远程办公管理规定》,置信度 8.7 → 生成答案。

路由 3:置信度<3 → Web 搜索
适用场景:知识库中没有相关信息,需要外部知识补充。
示例:"2026 年劳动法对加班的最新规定是什么?" → 知识库中没有最新法规,置信度 1.8 → 回退到 Web 搜索 → 获取外部信息 → 生成答案。

这三条路由的设计哲学是:优先使用内部知识(快且可控),必要时才求助外部(慢但全面)


实验对比:CRAG 到底好在哪里?

我在 Agentic RAG 知识库专家系统中进行了对比实验,测试数据集包含 120 个真实用户问题(涵盖事实查询、推理问题、时效性问题)。

策略Context RecallFaithfulness说明
纯向量检索0.620.72Baseline(传统 RAG)
混合检索(RRF)0.700.78+13% / +8%
混合 + Reranker0.740.82+19% / +14%
完整 CRAG0.780.85+26% / +18%

为什么 CRAG 在 Context Recall 上提升最大?

Context Recall 衡量的是"相关信息的召回率"——系统是否找到了所有应该找到的信息。CRAG 的提升来自两个机制:

  1. 查询重写:当初次检索失败时,重写查询可以用不同的关键词再试一次。比如用户问"如何请假",初次检索用"请假"作为关键词,可能匹配不到《休假管理制度》;重写为"休假申请流程"后,成功匹配。

  2. Web 回退:当知识库中确实没有信息时,Web 搜索可以补充外部知识。这直接解决了"检索不到"的问题——传统 RAG 会返回空结果,CRAG 会去外部找答案。

Faithfulness 的提升则来自"高质量上下文":通过置信度过滤,低质量文档不会进入生成阶段,减少了 LLM 被噪音误导的概率。


实践中的踩坑与优化

坑 1:评估器延迟过高

问题:最初使用 gpt-4o 作为评估器,每次评估耗时 2-3s,导致整体延迟飙升到 5-6s。
解决:换成 gpt-4o-mini,延迟降低到 200ms,效果几乎无损(分类任务不需要强推理)。

坑 2:查询重写陷入死循环

问题:某些问题重写后仍然检索不到相关文档,系统会不断重写→检索→重写,陷入死循环。
解决:设置最大重试次数为 2。如果两次重写后置信度仍<7,直接回退到 Web 搜索。

坑 3:Web 搜索结果格式不统一

问题:Web 搜索返回的是 HTML 片段,格式与知识库文档不一致,导致后续处理出错。
解决:封装统一的 Document 结构,所有外部来源的内容都转换为标准格式(包含 page_content、metadata、source 字段)。

坑 4:置信度分数不稳定

问题:同一个问题多次评估,置信度分数波动较大(比如 6.8 → 7.2 → 6.5),导致路由不稳定。
解决:在 Prompt 中增加 few-shot 示例,明确"7 分代表什么程度的相关性",稳定输出格式。同时在评估器中加入温度参数 temperature=0,减少随机性。


总结与下期预告

CRAG 的核心创新在于"评估→路由"机制,让 RAG 系统具备了自我反思能力:

  • 能评估:通过置信度打分判断检索质量
  • 能修正:通过查询重写和 Web 回退补救失败
  • 能决策:根据置信度动态选择最优路径

在 Agentic RAG 知识库专家系统中,CRAG 实现了:

  • Context Recall +26%(0.62 → 0.78)
  • Faithfulness 0.85
  • P95 延迟 2.3s

但 CRAG 只是第一步。置信度评估依赖的是"检索到的文档是否相关",但如何提升检索本身的质量?下期我将深入解析:

  • RRF 混合检索:如何融合向量检索和关键词检索的优势
  • BGE 重排序:如何用 Reranker 模型进一步过滤噪音

开源地址github.com/Yunzenn/age…
欢迎 Star / Issue / PR,一起探索 Agentic RAG 的更多可能性。


本文是"从零开始理解 Agentic RAG"系列的第 1 篇,聚焦 CRAG 核心架构。后续文章将深入检索优化、评估体系、生产部署等话题。