CRAG 架构解析:如何在生成器前修正错误检索结果

简介: CRAG是一种新型RAG框架,直面检索错误问题:引入轻量级评估器,对检索文档打分并按置信度分流——高分则精炼本地文档(句子级过滤重组),低分则转向网络搜索,中等分则双源协同。从架构上杜绝无关内容污染生成器,显著提升答案准确性与鲁棒性。

绝大多数 RAG 系统把检索当作不会出错的环节,无论拿到的文档是否真正切题,都会径直送入生成器。

"CRAG 提出了标准 RAG 从未追问的问题:如果检索器出错了,该怎么办?"

"不加甄别地引入检索文档,无论其是否相关,都会主动误导生成器,让 RAG 的表现甚至不如不做检索。

CRAG 详解

CRAG 引入了一个轻量级检索评估器,对给定查询所检索到的文档集合给出三种置信度判定之一。

得分达到 UPPER_TH 及以上时判定为"正确":至少有一篇文档具有足够相关性,进入知识精炼流程,将文档拆解为句子级片段,过滤掉无关内容,再将保留下来的部分重组为干净的知识(k_in)。得分在 LOWER_TH 以下则判定为"错误":所有本地文档均不相关,全部丢弃,查询被改写后转入网络搜索,以搜索结果作为外部知识(k_ex)。两个阈值之间的区域称为"模糊",质量不确定,同时使用 k_in 与 k_ex 作为上下文,在两个来源之间对冲风险。

模糊路径是论文中细节最丰富的部分,也是工程实现中最容易被跳过的部分。当评估器拿不准时,同时采信两个来源是最稳妥的策略,但这并不等同于"无条件双源并用"——触发条件明确限定在置信度真正偏低的场景。

分解-再重组算法

即便一篇文档被判定为"正确",其中仍可能夹带大量无关内容。以保险政策文件为例:某一页直接描述理赔程序,其余许多页却充斥着行政样板文字与其他险种的条款定义。CRAG 针对这一问题设计了句子级精炼算法:

  1. 分解:将每篇检索文档拆分为独立的句子(片段)。
  2. 过滤:对每个片段评估其与具体查询的相关性,丢弃低于阈值的片段。
  3. 重组:将保留下来的片段按原始顺序拼接为连贯的上下文字符串。

经过这道处理,送入生成器的上下文不仅体积更小,信息密度也更高——即便文档整体上是相关的,其中的无关句子也不会对生成过程构成干扰。

在原论文中,片段级别的评分由经过微调的 T5 模型完成。本文的代码实现中,片段评分与其他所有评估任务共用同一个 LLM,以少量评分精度的损失换取更简洁的运维结构和更少的模型依赖。

架构图

下图直接取自 CRAG 论文,看图是了解整个系统设计最直接的方式。

推理阶段的 CRAG 框架。对检索到的文档 d₁ 和 d₂ 进行评估,所得置信度触发三种知识检索动作之一,随后上下文才会被送入生成器。

逐步解读架构图

  • 顶行(检索):输入问题 x,检索器返回文档 d₁ 和 d₂,这是未经过滤的原始结果。
  • 中间左侧(检索评估器):评估器判断"检索到的文档对于 x 是否正确",给出置信度得分,分流至正确(绿色)、模糊(橙色)或错误(红色)路径。
  • 中间右侧,知识精炼(绿色框):正确路径下,d₁ 和 d₂ 被分解为 strip₁、strip₂……strip_ₙ;过滤器剔除无关片段(图中以 ✗ 表示),保留部分重组为 k_in(内部知识)。
  • 中间右侧,知识搜索(红色框):错误路径下,x 被改写为搜索查询 q(例如"Death of a Batman; screenwriter; Wikipedia"),网络搜索返回 k₁……k_ₙ,筛选后得到 k_ex(外部知识)。
  • 底行(生成):生成器接收 x 与对应知识,正确路径为 x + k_in,模糊路径为 x + k_in + k_ex,错误路径为 x + k_ex。原始未过滤的 d₁/d₂ 绝不会直接到达生成器。

生成器位于质量关卡的下游。送达它的知识,必然经过评估、过滤或外部来源的验证。上下文污染在正确实现的 CRAG 系统中,从架构层面便无从发生。

实现

构建 CRAG:逐节点解析

本文实现遵循论文中的架构:七个节点,一个条件分支,无循环。

已编译的 CRAG LangGraph 图直接由 app.get_graph().draw_mermaid_png() 生成。文档相关性检查节点是唯一的条件分支点,两条路径在上下文综合节点汇合,随后进入答案生成。

状态模式(State Schema)

LangGraph 需要一个 TypedDict 作为所有节点之间的共享内存,每个节点从中读取状态并返回部分更新。

 class State(TypedDict):  
    question: str            # 原始用户问题,始终不修改  
    docs: List[Document]     # 原始检索的 top-k 块  
    good_docs: List[Document]# 通过评估器的块  
    verdict: str             # CORRECT | INCORRECT  
    reason: str              # 评估器的推理过程(用于日志记录)  
    strips: List[str]        # 句子级别的分解片段  
    kept_strips: List[str]   # 通过相关性过滤的片段  
    refined_context: str     # 送入生成器的最终组合上下文  
    web_query: str           # 改写后用于 Google 搜索的查询  
    web_docs: List[Document] # Google Custom Search 的返回结果  
     answer: str              # 最终生成的答案

节点:知识检索

接收用户问题,查询 FAISS 向量存储,返回前 4 个块。

 # 数据摄入(在启动时运行一次)  
chunks = RecursiveCharacterTextSplitter(  
    chunk_size=900, chunk_overlap=150  
).split_documents(docs)  
retriever = FAISS.from_documents(chunks, embeddings)  
             .as_retriever(search_kwargs={'k': 4})  

def retrieve_node(state: State):  
    docs = retriever.invoke(state['question'])  
     return {'docs': docs}

节点:文档相关性检查

这是整个架构的核心节点。每篇检索文档都会获得一个连续置信度得分(0.0–1.0),得分超过 UPPER_TH(0.7)的文档进入 good_docs。若 good_docs 非空,则 verdict = CORRECT;否则 verdict = INCORRECT,流程升级至网络搜索。

 UPPER_TH = 0.7   # 论文推荐的"正确"阈值  
LOWER_TH = 0.3   # 低于此值 = 错误(中间值 = 模糊)  

class DocEvalScore(BaseModel):  
    score: float    # 0.0–1.0 置信度  
    reason: str     # 简短说明(记录用于调试)  

# system prompt 中的评分标准:  
# 1.0   → 用具体细节直接回答查询  
# 0.7-0.9 → 中度相关,有用的相关信息  
# 0.4-0.6 → 轻微相关  
# 0.0-0.3 → 不相关,与查询无关联  

def eval_each_doc_node(state: State):  
    good_docs = []  
    for i, doc in enumerate(state['docs'], start=1):  
        decision = doc_eval_llm.invoke(  
            doc_eval_prompt.format_messages(  
                question=state['question'], doc=doc.page_content  
            )  
        )  
        if decision.score >= UPPER_TH:  
            good_docs.append(doc)  
    verdict = 'CORRECT' if good_docs else 'INCORRECT'  
     return {'good_docs': good_docs, 'verdict': verdict}

为什么采用连续得分而非直接分类?阈值由此变为一个可调的配置项,调整判定标准无需动提示词。在保险这类高精度场景中,把 UPPER_TH 从 0.7 提到 0.85 只需改一个常量,策略与机制分离是这里更重要的设计原则。

节点:查询优化

仅在 INCORRECT 路径下激活,将用户的自然语言问题改写为关键词密集的网络搜索查询。改写逻辑具备明确的领域意识:展开缩写、补充专业术语、剥离口语化措辞。

 class WebQuery(BaseModel):  
    query: str  

# 提示词中内置的改写示例:  
# 'How does CI claims work?'  
#   → 'critical illness insurance claim settlement process requirements'  
# 'What are the benefits of life insurance?'  
#   → 'life insurance policy benefits coverage sum assured'  

def rewrite_query_node(state: State):  
    decision = rewrite_llm.invoke(  
        rewrite_prompt.format_messages(question=state['question'])  
    )  
     return {'web_query': decision.query}

节点:外部知识搜索

调用 Google Custom Search API,将每条摘要片段封装为 LangChain Document。统一的 Document 接口让下游的上下文综合节点得以用相同逻辑处理本地文档与网络来源——两条路径在此完全对称。

 def web_search_node(state: State):  
    params = {  
        'key': os.getenv('GOOGLE_API_KEY'),  
        'cx':  os.getenv('GOOGLE_CSE_ID'),  
        'q':   state['web_query']  
    }  
    r = requests.get('https://www.googleapis.com/customsearch/v1',  
                     params=params)  
    web_docs = [  
        Document(page_content=item['snippet'])  
        for item in r.json().get('items', [])  
    ]  
     return {'web_docs': web_docs}

节点:上下文综合

为生成器组装最终上下文。逻辑简单,影响深远。

 def refine(state: State):  
     # CORRECT 路径:使用本地评估通过的 good_docs  
     # INCORRECT 路径:使用 web_docs(此时 good_docs 为空)  
     # 完整 CRAG:模糊路径将同时使用两者  
     docs = state['good_docs'] or state['web_docs']  
     context = '\n\n'.join([d.page_content for d in docs])  
     return {'refined_context': context}

一个真实查询示例

测试查询:"SecureLife 重疾险的理赔结算流程是什么?"

[输入查询 → 知识检索] 通过 FAISS 从保险语料库中检索出 4 个块。

[文档相关性检查]

  • 文档 1 → 0.92(重疾险理赔程序——直接相关)
  • 文档 2 → 0.78(理赔材料要求——相关)
  • 文档 3 → 0.41(一般健康保险概述——轻微相关)
  • 文档 4 → 0.18(车险免责条款——不相关)

裁定:CORRECT。4 篇文档中有 2 篇通过,直接路由至上下文综合。

[上下文综合] 仅由文档 1 和文档 2 组装上下文,车险与通用健康险内容被丢弃,生成器收到的是 2 个聚焦的相关块。

[答案生成 → 最终回复] 生成结果列出所需材料(病历报告、诊断证明、主治医生证明)、结算时间线(完整材料提交后 90 天内)以及两阶段审批流程。

没有文档过滤时,生成器会收到车险免责条款块(文档 4,得分 0.18)作为上下文。行为稳定的模型可能将其忽略,但压力下的模型往往会尝试从中提取信息,最终输出一个将重疾险与车险理赔程序混为一谈的错误答案。CRAG 在架构层面堵死了这条路。

本文代码:
https://avoid.overfit.cn/post/1a4ba7fc989f4c7f9ca4c61179cf5656s

by Bhavya Meghnani

目录
相关文章
|
2月前
|
人工智能 IDE 编译器
Karpathy的LLM Wiki:一种将RAG从解释器模式升级为编译器模式的架构
Andrej Karpathy提出的LLM Wiki,摒弃传统RAG“每次查询重检索”的模式,转而让大模型将原始资料**编译为结构化、可链接、自更新的Markdown Wiki**,实现知识的持久沉淀与复利增长——Obsidian为IDE,LLM为程序员,Wiki即代码库。
2905 7
Karpathy的LLM Wiki:一种将RAG从解释器模式升级为编译器模式的架构
|
3月前
|
机器学习/深度学习 文字识别 数据挖掘
BookRAG:面向层级文档的树-图融合RAG框架
BookRAG是专为书籍类层级文档设计的新型RAG框架,首创“树+图+链接+Agent”四元结构:构建融合版面层级树与知识图谱的BookIndex,通过GT-Link双向映射实现结构与语义统一;引入信息觅食启发的Agent,动态规划检索路径,支持单跳、多跳及全局聚合查询,在精度、覆盖率与效率上显著优于传统文本/版面优先方法。
529 5
BookRAG:面向层级文档的树-图融合RAG框架
|
3月前
|
数据库
高级 RAG 技术:查询转换与查询分解
RAG准确性受限于查询质量,易因表述模糊导致检索偏差。主流优化方向为查询转换(如Fan-Out并行检索、RRF重排、HyDE假设文档嵌入)与查询分解(高抽象后退提示、低抽象思维链检索),二者常协同提升召回率与答案质量。
190 1
高级 RAG 技术:查询转换与查询分解
|
3月前
|
机器学习/深度学习 人工智能 算法
更大的上下文窗口为什么让RAG变得更重要而非更多余
大上下文窗口(如1M tokens)并未淘汰RAG,反而凸显其价值:LLM注意力易被噪声稀释,“迷失在中间”效应导致性能下降。实验证明,相关性筛选比单纯扩容更关键。RAG+大上下文协同——先精准检索重排序,再注入高密度片段——才是生产级AI的可靠范式。
404 0
|
3月前
|
存储 人工智能 API
【保姆级教程】阿里云/本地部署 OpenClaw 配置大模型api +医疗领域 AI 应用场景解析+FAQ
2026年初,一只红色龙虾图标席卷全球科技圈与医疗行业:GitHub星标数飙升至28万,深圳市龙岗区政府专门出台支持政策,开放医疗、城市治理等高质量脱敏公共数据,对相关应用项目给予最高100万元奖励——这只名为OpenClaw的开源AI智能体,正以“真正能干活”的核心优势,从通用场景渗透到医疗科研、临床辅助、产业转化等专业领域,成为驱动医疗行业效率革新的关键力量。
913 6
|
3月前
|
人工智能 Linux API
不辞职、不烧钱!从零搭建AI一人公司(OPC):OpenClaw落地+全平台部署+免费模型一站式教程
2026年,AI一人公司(OPC)已经不是概念风口,而是普通人最低成本、最低风险的创业新模式。不辞职、不烧钱、不组建团队,只用业余时间,就能依靠OpenClaw这类AI智能体搭建属于自己的自动化数字员工体系,完成内容生产、客户服务、数据处理、营销获客、研报产出等全流程工作。但行业数据显示,AI创业失败率高达80%~90%,核心原因是盲目跟风、没有验证闭环、忽略合规与风险、缺乏系统化执行路径。
2107 8
|
3月前
|
设计模式 数据采集 人工智能
构建生产级 AI Agent 系统的4大主流技术:反思、工具、规划与多智能体协作
本文深入解析Agentic AI四大核心设计模式:Reflection(自我反思)、Tool Use(工具调用)、Planning(任务规划)与Multi-Agent协作。它们共同赋予AI思考、行动、校验与协同能力,突破单轮问答局限,构建真正可落地的自主智能系统。
771 3
|
3月前
|
机器学习/深度学习 人工智能 自然语言处理
别再说“AI听不懂人话”:从0到1手把手搭一个意图识别 + 槽位提取系统
别再说“AI听不懂人话”:从0到1手把手搭一个意图识别 + 槽位提取系统
728 11