RAG 系列(十四):Self-RAG——让模型决定要不要检索

传统 RAG 的一个隐藏问题

传统 RAG Pipeline 有一个从不质疑的假设:所有问题都需要检索

用户问"RAG 系统怎么评估"------检索。 用户问"1 + 1 等于几"------也检索。 用户问"帮我写一个求最大公约数的函数"------还是检索。

后两个问题完全不需要外部知识,强行检索不仅浪费资源,还可能把无关文档塞进上下文,干扰 LLM 的判断。

2023 年 Asai 等人提出的 Self-RAG,用一套"反思机制"解决了这个问题:模型在生成过程中会输出特殊的反思 token,自主决定何时检索、检索到的内容是否相关、最终答案是否有据可查。


Self-RAG 的四个反思 token

原始论文里,Self-RAG 训练了一个能输出四种特殊 token 的模型:

Token 含义 可能的值
[Retrieve] 是否需要检索? yes / no / continue
[IsRel] 检索到的文档与问题相关吗? relevant / irrelevant
[IsSup] 生成的内容有文档支撑吗? fully supported / partially supported / no support
[IsUse] 这个回答对用户有用吗? 1~5

这四个 token 贯穿整个生成过程,让模型在不同阶段做出自适应决策,而不是盲目地"总是检索,总是使用"。

工程实现上,不需要专门训练带这些特殊 token 的模型------用普通 LLM + Prompt 模拟这四个判断节点,已经能取得不错的效果。


用 LangGraph 实现 Self-RAG

整体流程

css 复制代码
用户问题
    ↓
[decide] 需要检索吗?
    ├─ yes → [retrieve] 向量检索 top-4
    │            ↓
    │         [filter] 逐篇判断相关性,过滤无关文档
    │            ↓
    │         [rag_generate] 基于相关文档生成答案
    │            ↓
    └─ no  → [direct_generate] 直接生成答案
                 ↓
             [support_check] 回答有文档支撑吗?
                 ↓
              输出最终答案

State 设计

LangGraph 的核心是 State------在节点之间流转的状态对象:

python 复制代码
class SelfRAGState(TypedDict):
    question: str
    need_retrieve: str           # "yes" | "no"
    retrieved_docs: list[Document]
    relevant_docs: list[Document]
    answer: str
    support_verdict: str         # "supported" | "unsupported"
    token_count: int
    path: list[str]              # 记录执行路径

关键节点实现

节点1:检索决策(decide)

python 复制代码
RETRIEVE_DECISION_PROMPT = ChatPromptTemplate.from_messages([
    ("system",
     "你是一个 RAG 系统的路由决策器。判断以下问题是否需要检索外部知识库。\n\n"
     "需要检索:涉及具体技术细节、参数、推荐选型等事实性内容\n"
     "不需要检索:简单常识、数学计算、逻辑推理、闲聊问候\n\n"
     "只输出 yes 或 no,不要解释。"),
    ("human", "问题:{question}"),
])

def make_decide_node(llm):
    chain = RETRIEVE_DECISION_PROMPT | llm | StrOutputParser()
    def decide(state):
        result = chain.invoke({"question": state["question"]})
        verdict = "yes" if "yes" in result.lower() else "no"
        return {**state, "need_retrieve": verdict}
    return decide

节点2:相关性过滤(filter)

python 复制代码
RELEVANCE_PROMPT = ChatPromptTemplate.from_messages([
    ("system", "判断以下文档是否与问题相关,能够帮助回答该问题。\n"
               "只输出 relevant 或 irrelevant,不要解释。"),
    ("human", "问题:{question}\n\n文档:{document}"),
])

def make_filter_node(llm):
    chain = RELEVANCE_PROMPT | llm | StrOutputParser()
    def filter_docs(state):
        relevant = []
        for doc in state["retrieved_docs"]:
            result = chain.invoke({
                "question": state["question"],
                "document": doc.page_content[:300],
            })
            if "relevant" in result.lower() and "irrelevant" not in result.lower():
                relevant.append(doc)
        # 兜底:全部过滤时保留原始结果
        return {**state, "relevant_docs": relevant or state["retrieved_docs"]}
    return filter_docs

条件路由:decide 后的分叉

python 复制代码
def route_after_decide(state) -> Literal["retrieve", "direct_generate"]:
    return "retrieve" if state["need_retrieve"] == "yes" else "direct_generate"

graph.add_conditional_edges(
    "decide",
    route_after_decide,
    {"retrieve": "retrieve", "direct_generate": "direct_generate"},
)

完整 Graph 构建

python 复制代码
graph = StateGraph(SelfRAGState)

graph.add_node("decide",          make_decide_node(llm))
graph.add_node("retrieve",        make_retrieve_node(retriever))
graph.add_node("filter",          make_filter_node(llm))
graph.add_node("rag_generate",    make_rag_generate_node(llm))
graph.add_node("direct_generate", make_direct_generate_node(llm))
graph.add_node("support_check",   make_support_node(llm))

graph.set_entry_point("decide")
graph.add_conditional_edges("decide", route_after_decide, {...})
graph.add_edge("retrieve",        "filter")
graph.add_edge("filter",          "rag_generate")
graph.add_edge("rag_generate",    "support_check")
graph.add_edge("direct_generate", "support_check")
graph.add_edge("support_check",   END)

self_rag_app = graph.compile()

实验设计

测试集包含两类问题:

  • 8 条 RAG 相关问题:涉及 Embedding 模型选型、向量数据库、分块策略等,需要检索知识库
  • 3 条无需检索的问题1 + 1 等于几今天天气怎么样用 Python 写一个求最大公约数的函数

第二类问题是关键对照------Self-RAG 能否正确识别并跳过检索?


实验结果

检索决策准确率

css 复制代码
Self-RAG 检索决策明细:
  [✓ 检索] 什么是 RAG 技术,它主要解决什么问题?
  [✓ 检索] 企业级应用应该选择哪种向量数据库?
  [✓ 检索] 中文场景应该选择哪个 Embedding 模型?
  [✓ 检索] 文档分块时 Chunk Size 一般推荐多少?
  [✓ 检索] RAG 系统如何进行评估?
  [✓ 检索] 混合检索相比纯向量检索有什么优势?
  [✓ 检索] Rerank 在 RAG 中的作用是什么?
  [✓ 检索] Parent-Child 分块策略解决了什么问题?
  [✗ 跳过] 1 + 1 等于几?
  [✗ 跳过] 今天天气怎么样?
  [✗ 跳过] 用 Python 写一个求最大公约数的函数

检索决策统计:需要检索 8 条,直接回答 3 条

11/11 全部判断正确。 8 条知识库问题全部触发检索,3 条无需检索的问题全部走直接生成路径。

相关性过滤效果

css 复制代码
执行路径详情(Self-RAG):
  Q1: decide→yes → retrieve → filter(4/4) → rag_generate → support→supported
  Q2: decide→yes → retrieve → filter(2/4) → rag_generate → support→supported
  Q3: decide→yes → retrieve → filter(2/4) → rag_generate → support→unsupported
  Q4: decide→yes → retrieve → filter(1/4) → rag_generate → support→supported
  Q5: decide→yes → retrieve → filter(2/4) → rag_generate → support→supported
  Q6: decide→yes → retrieve → filter(4/4) → rag_generate → support→unsupported
  Q7: decide→yes → retrieve → filter(1/4) → rag_generate → support→supported
  Q8: decide→yes → retrieve → filter(2/4) → rag_generate → support→supported

filter(1/4) 意味着 4 篇文档里只有 1 篇被判断为相关------过滤掉了 3 篇噪声。这正是 context_precision 提升的来源:给 LLM 的上下文更干净了。

support→unsupported 出现在 Q3 和 Q6,说明 LLM 在这两条问题上的回答超出了文档内容。完整版 Self-RAG 会在这里触发重新生成,本实验的简化版直接输出。

RAGAS 指标对比

diff 复制代码
======================================================================
  RAGAS 指标对比(知识库相关问题,8 条)
======================================================================

  指标                    固定检索     Self-RAG      变化
  ────────────────────────────────────────────────────────
  context_recall          0.625        0.625     →+0.000
  context_precision       0.583        0.688     ↑+0.104  ◀
  faithfulness            0.845        0.866     ↑+0.021
  answer_relevancy        0.404        0.401     →-0.003
======================================================================
  • context_precision +0.104:过滤节点的直接贡献。固定检索把 4 篇文档全部交给 LLM,其中可能有不相关的;Self-RAG 过滤后只保留真正相关的文档,排序自然更准。
  • context_recall 持平:过滤没有丢掉相关文档(兜底逻辑起了作用)。
  • faithfulness 小幅提升 +0.021:更干净的上下文带来更少的幻觉。

Token 消耗:反直觉的结果

rust 复制代码
Token 消耗对比(全部 11 条问题,估算值):
  固定检索总消耗:~6,600 tokens
  Self-RAG 总消耗:~16,050 tokens
  Self-RAG 额外消耗:约 2.4x

Self-RAG 反而消耗了更多 token------这是本实验最反直觉的结果,值得仔细分析。

原因在于:Self-RAG 的每个反思节点都要额外调用 LLM。

节点 固定检索 Self-RAG
检索决策(decide) --- ✓ 每条问题 1 次
相关性过滤(filter) --- ✓ 每篇文档 1 次(最多 4 次)
生成
Support 评估 --- ✓ 每条问题 1 次

即便跳过了 3 条问题的检索,决策节点和过滤节点的开销仍然可观。在本实验里,"需要检索"的比例是 8/11 ≈ 73%,Self-RAG 的跳过收益无法覆盖额外的反思开销。

Self-RAG 节省 token 的条件:当"不需要检索"的问题占比足够高(通常 > 50%),且检索+生成的单次成本远高于决策节点时,Self-RAG 才能实现净节省。在 RAG 系统的真实使用场景中(用户大多数时候都在问知识库里的内容),这个条件并不容易满足。


Self-RAG 的真实价值

实验揭示了 Self-RAG 的价值边界:

真正的价值是质量提升,不是节省 token。

  • 相关性过滤(filter 节点)让 context_precision 提升 +0.104------比多数检索优化策略的提升都更直接
  • 路由决策保证了"不该检索的问题不检索",避免了无关文档干扰
  • Support 评估提供了可观测性------可以知道哪些回答是"有据可查"的,哪些可能需要兜底

Self-RAG 最适合的场景:

  • 混合型对话系统:有些问题需要检索知识库,有些是通用问题(天气、计算、闲聊)
  • 对回答质量有严格要求,愿意为额外的反思步骤付出 token 成本
  • 需要可观测性:想知道每条回答是否有文档支撑

不适合的场景:

  • 纯知识库问答系统(几乎所有问题都需要检索)
  • Token 预算极度敏感的场景
  • 响应延迟要求极高的实时系统(每个节点都增加延迟)

完整代码

代码已开源:

github.com/chendongqi/...

核心文件:

  • self_rag.py --- LangGraph 实现的完整 Self-RAG 流程

运行方式:

bash 复制代码
git clone https://github.com/chendongqi/llm-in-action
cd 14-self-rag
cp .env.example .env
pip install -r requirements.txt
python self_rag.py

小结

本文用 LangGraph 实现了简化版 Self-RAG,核心发现:

  1. 路由决策 11/11 全部正确:LLM 能准确区分"需要检索的技术问题"和"不需要检索的通用问题"
  2. 质量改善明显:filter 节点过滤无关文档,context_precision 提升 +0.104
  3. token 消耗反增 2.4x:反思节点(decide + filter + support)的开销超过了跳过检索的收益------这是真实工程中需要权衡的核心问题

Self-RAG 的本质是把"盲目执行"升级为"有意识的决策"。代价是复杂度和成本上升,收益是更精准的上下文和更可控的生成质量。在对话型、混合型 RAG 系统中,这个权衡往往是值得的。


参考资料

相关推荐
chatexcel1 小时前
AI工具里的知识库是什么?定义、原理、场景与ChatExcel示例解析
人工智能
冬奇Lab1 小时前
一天一个开源项目(第99篇):AiToEarn - 用 AI 把内容变成收入的一站式平台
人工智能·开源·资讯
千叶风行1 小时前
Text-to-SQL 技术设计与注意事项
前端·人工智能·后端
夜郎king1 小时前
Spring AI 对接大模型开发易错点总结与实战解决办法
java·人工智能·spring
从孑开始1 小时前
manyspeech-cli 语音识别命令行工具
人工智能·语音识别·工具·asr
hans汉斯1 小时前
计算机科学与应用|基于大模型深度语义理解的智能内容纠错系统
人工智能·计算机视觉·视觉检测·数据·病虫害检测
Mr数据杨1 小时前
【CanMV K210】视觉识别 颜色阈值分割与色块检测实验
人工智能·硬件开发·canmv k210
Bruce_Liuxiaowei2 小时前
OpenClaw 网关启动失败:配置文件权限错误的排查与修复
人工智能·智能体
kobesdu2 小时前
【ROS2实战笔记-18】ROS2 通信的隐秘控制:DDS 配置参数如何决定系统性能
网络·人工智能·笔记·机器人·开源·ros·人形机器人