13273 字
66 分钟

LangGraph 源码深潜:Reflection 与评价-修改循环机制解剖

核心问题: LangGraph 如何把 Reflection 从“让模型再想想”的提示词技巧,变成由状态、评价节点、修改节点、条件边和循环保护共同约束的可控评价-修改循环?

源码主线: StateGraph.add_node() / add_conditional_edges() → BranchSpec → CompiledStateGraph → Pregel super-step → state update / route → revise loop / END

前置文章: 第 6 篇 StateGraph 源码解剖、第 7 篇 Conditional Edge 与 Router、第 8 篇 Reducer 与并行状态合并、第 9 篇 Plan-and-Execute 与 Subgraph

依赖基线: langgraph==1.2.7langchain-corelanggraph 依赖解析

源码基线: langchain-ai/langgraph tag 1.2.7,release commit 5931a5f

阅读边界: 本文只解释 Reflection / Evaluator-Optimizer 在 LangGraph Graph API 中如何落到节点、状态、条件边、循环和运行时保护;不展开 Reflexion 论文、LATS 搜索树、多 Agent 辩论、模型微调或 LangSmith 自动评估平台。


0. 本篇在源码学习主线中的位置#

前面几篇已经完成了 LangGraph 的三层基础:

StateGraph:节点如何读写共享 state
Conditional Edge:节点执行后如何根据 state 路由
Reducer:多个节点写同一个 key 时如何合并
Plan-and-Execute:planner / executor / subgraph 如何形成多步骤执行

本篇继续进入 Reflection:

生成草案
评价草案
根据分数与轮次判断是否修改
修改草案
再次评价
达到质量阈值或最大修改次数后结束

这篇要解决的是:

  • Reflection 如何从一句 prompt 变成图中的可控循环。
  • quality_scorecritiquerevision_countdraft_planfinal_plan 这些状态字段如何驱动条件边。
  • route_by_score() 返回 revise_draftfinal_response 时,LangGraph 运行时如何调度下一节点。
  • 如何通过 MAX_REVISIONrecursion_limit 双层机制避免无限执行。
  • 高风险任务中 Reflection 为什么不能替代规则、Policy、Verifier 和人工确认。

本篇不展开:

  • 复杂搜索式 Reflection,例如 Tree of Thoughts / LATS。
  • 训练式 Reflexion 或轨迹反馈微调。
  • 多 Agent Debate。
  • LangSmith Evaluation 的离线评估工作流。

1. 本篇问题、学习目标与能力边界#

1.1 核心问题#

如何用 LangGraph 的状态图、条件边和 Pregel 执行模型,把 Reflection 表达为一个可终止、可观测、可回滚的评价-修改循环?

1.2 学习目标#

完成本篇后,读者必须能够:

  1. 解释 Reflection 为什么应该拆成 generator nodeevaluator noderevise nodeconditional edge,而不是只在 prompt 里写“请再检查一下”。
  2. 设计 TravelState 中与反思相关的状态字段:draft_plancritiquequality_scorerevision_countfinal_plan
  3. 读懂 add_conditional_edges() 如何注册 route_by_score(),并理解返回节点名或 END 如何影响 Pregel 下一步调度。
  4. 解释 revision_count 应该由 revise_draft 或专门的状态更新节点递增,而不是在路由函数里偷偷修改 state。
  5. 说明 evaluator 为什么应该结构化输出,并能把 quality_scoreissuessuggestions 作为可测试字段。
  6. 区分业务质量优化、规则校验、Policy Gate、Verifier 和 Human-in-the-loop 的职责边界。
  7. 在旅行规划助手中判断哪些任务适合 Reflection,哪些任务不应该让 Reflection 自动放行。

1.3 能力边界#

能力本篇是否覆盖说明
Reflection 循环建模重点讲 generate → evaluate → route → revise → evaluate 的状态图结构
条件边源码机制复用第 7 篇的 BranchSpec / _route / _finish 机制,但聚焦循环场景
状态更新与 partial update解释 revision_countcritiquedraft_plan 如何更新
结构化评价输出讲 evaluator output schema 的工程必要性
模型选择讲 evaluator 和 generator 是否应使用不同模型的工程判断
高风险策略校验部分覆盖只说明边界,不展开 policy engine 设计
HITL由 Interrupt / Command Resume 专篇展开
多 Agent 反思由 Multi-Agent 专篇展开

2. 核心概念与最小心智模型#

2.1 一句话定义#

Reflection 在 LangGraph 中不是一个模型“自我反省”的神秘动作,而是一个由状态字段保存草案、评价结果和修改次数,再由条件边决定继续修改或终止的循环型 Workflow。

它负责:

  • 对生成结果做质量评价。
  • 根据评价结果触发修改。
  • 在达到阈值、最大修改次数或保护上限时终止。

它不负责:

  • 替代确定性业务规则。
  • 替代权限校验。
  • 自动批准高风险动作。
  • 保证事实正确性,除非评价节点有可靠证据输入。

2.2 最小心智模型#

user_request
generate_draft
evaluate_draft
route_by_score
├── revise_draft → evaluate_draft
└── final_response → END

关键不是“多调用一次模型”,而是每次循环都有显式状态:

draft_plan:当前草案
critique:评价意见
quality_score:质量分数
revision_count:已修改次数
final_plan:最终输出

2.3 核心术语#

术语源码对象语义不要误解为
Reflection LoopStateGraph + add_conditional_edges()有停止条件的评价-修改循环单个 prompt 中的“请反思”
Generator Nodeadd_node("generate_draft", fn)生成初稿负责最终验收的节点
Evaluator Nodeadd_node("evaluate_draft", fn)输出结构化评价规则引擎或安全网关
Revise Nodeadd_node("revise_draft", fn)根据 critique 修改草案并递增轮次无限制重写器
Route Functionroute_by_score(state)根据 score / count 选择下一节点修改 state 的地方
Stop Conditionroute_by_score → final_response / END业务级终止条件框架递归上限
Recursion Limitconfig={"recursion_limit": n}框架级保护上限质量评价规则

2.4 与相邻抽象的边界#

对象负责什么不负责什么与本篇对象的关系
StateGraph构建节点和边自动知道质量标准Reflection 的承载结构
add_conditional_edges()把路由函数接到节点后修改业务状态用于 route_by_score
Reducer合并并发状态写入判断是否要修改草案可用于追加 critique history
recursion_limit防止图无限运行证明结果质量足够Reflection 的安全保险丝
Policy / Verifier确定性校验和合规判断文案润色Reflection 不能替代它们

3. 完整执行链路#

3.1 高层链路#

用户旅行请求
StateGraph(TravelState)
注册 generate_draft / evaluate_draft / revise_draft / final_response
注册 route_by_score 条件边
compile 生成 CompiledStateGraph
graph.invoke(initial_state)
Pregel 按 super-step 执行节点、写入状态、计算路由
final_plan 写入 state 并到达 END

3.2 最小代码骨架#

观察目标:这段代码展示 Reflection 最小图结构,而不是完整业务实现。重点看状态字段、条件边和循环边。

from typing_extensions import TypedDict, Literal
from langgraph.graph import StateGraph, START, END
MAX_REVISION = 2
class TravelState(TypedDict):
user_request: str
draft_plan: str | None
critique: str | None
quality_score: float | None
revision_count: int
final_plan: str | None
def generate_draft(state: TravelState):
return {
"draft_plan": "东京 5 天旅行计划草案",
"revision_count": 0,
}
def evaluate_draft(state: TravelState):
draft = state["draft_plan"] or ""
if "交通" in draft and "预算" in draft:
return {
"quality_score": 0.9,
"critique": "计划较完整,可以输出。",
}
return {
"quality_score": 0.6,
"critique": "计划缺少交通或预算信息,需要修改。",
}
def route_by_score(state: TravelState) -> Literal["revise_draft", "final_response"]:
if (state["quality_score"] or 0.0) >= 0.8:
return "final_response"
if state["revision_count"] >= MAX_REVISION:
return "final_response"
return "revise_draft"
def revise_draft(state: TravelState):
return {
"draft_plan": (state["draft_plan"] or "") + "\n补充交通与预算信息。",
"revision_count": state["revision_count"] + 1,
}
def final_response(state: TravelState):
return {
"final_plan": state["draft_plan"]
}
builder = StateGraph(TravelState)
builder.add_node("generate_draft", generate_draft)
builder.add_node("evaluate_draft", evaluate_draft)
builder.add_node("revise_draft", revise_draft)
builder.add_node("final_response", final_response)
builder.add_edge(START, "generate_draft")
builder.add_edge("generate_draft", "evaluate_draft")
builder.add_conditional_edges("evaluate_draft", route_by_score)
builder.add_edge("revise_draft", "evaluate_draft")
builder.add_edge("final_response", END)
graph = builder.compile()

3.3 对象流转#

阶段输入类型核心函数输出类型状态变化
构图TravelState schemaStateGraph.__init__()builder注册状态字段和 channel
注册节点Python callableadd_node()builder保存 node spec
注册循环路由callableadd_conditional_edges()builder保存 branch spec
编译buildercompile()CompiledStateGraph生成可执行 Pregel 图
生成草案TravelStategenerate_draft()partial update写入 draft_plan
评价草案TravelStateevaluate_draft()partial update写入 quality_score / critique
路由判断TravelStateroute_by_score()node name决定修改或终止
修改草案TravelStaterevise_draft()partial update更新 draft_plan / revision_count
最终输出TravelStatefinal_response()partial update写入 final_plan

3.4 时序链路#

Caller
│ graph.invoke(initial_state)
CompiledStateGraph / Pregel
│ 执行 generate_draft
State: draft_plan, revision_count
│ 执行 evaluate_draft
State: critique, quality_score
│ 执行 route_by_score
├── revise_draft
│ │ 更新 draft_plan + revision_count
│ └── 回到 evaluate_draft
└── final_response
│ 写入 final_plan
END

3.5 正常结束条件#

一次 Reflection 正常结束必须满足以下条件之一:

quality_score >= threshold
revision_count >= MAX_REVISION
route_by_score 返回 END / final_response

最终结果保存在:

state["final_plan"]

如果没有业务级停止条件,循环可能一直运行,最终由 LangGraph 的 recursion_limit 触发 GraphRecursionError


4. 源码地图、关键文件与阅读顺序#

4.1 核心目录#

langgraph/
├── graph/
│ ├── state.py
│ ├── graph.py
│ └── branch.py
├── pregel/
│ ├── __init__.py
│ ├── loop.py
│ ├── runner.py
│ └── algo.py
├── channels/
└── errors.py

4.2 关键文件#

优先级文件核心对象阅读目的
1langgraph/graph/state.pyStateGraph / CompiledStateGraph理解节点、边、条件边如何注册和编译
2langgraph/graph/branch.pyBranchSpec理解 route function 如何包装、调用和映射结果
3langgraph/pregel/__init__.pyPregel理解编译图为什么可执行
4langgraph/pregel/loop.pyPregel loop理解 super-step、写入和停止条件
5langgraph/pregel/runner.pyrunner理解节点任务如何被调度执行
6langgraph/errors.pyGraphRecursionError理解循环保护异常

4.3 推荐阅读顺序#

1. StateGraph.add_node()
2. StateGraph.add_edge()
3. StateGraph.add_conditional_edges()
4. BranchSpec.from_path()
5. StateGraph.compile()
6. CompiledStateGraph.attach_branch()
7. BranchSpec.run() / _route() / _finish()
8. Pregel.invoke() / stream()
9. Pregel loop 的 super-step 调度
10. recursion_limit / GraphRecursionError

4.4 不建议的阅读顺序#

不建议直接从 pregel/loop.py 开始。Pregel 是运行时调度内核,如果没有先理解 StateGraph 如何把节点、边、branch 和 channel 编译成 Pregel 可执行对象,会把 Reflection 误解为普通 while 循环。

也不建议只读 branch.pyBranchSpec 只解释“路由函数如何返回下一步”,但 Reflection 的关键还包括状态字段如何更新、循环如何终止、运行时如何限制步数。


5. 对象模型、继承关系与协议边界#

5.1 核心对象关系#

StateGraph
↓ compile()
CompiledStateGraph
↓ inherits / wraps Pregel-style executable graph
Pregel runtime
↓ executes nodes and branches by super-step
Final state

5.2 对象职责#

对象生命周期输入输出核心职责
StateGraph构建期state schema、node、edgebuilder保存图定义,不直接执行
BranchSpec构建期 + 运行时route functionbranch runnable封装条件边逻辑
CompiledStateGraph运行时initial statefinal state / stream chunks执行已编译图
Pregel运行时channels、nodes、configstate updates调度 super-step
TravelState全生命周期state dictstate dict保存 draft、评价和轮次

5.3 协议边界#

Node 协议
负责:读取完整 state,返回 partial state update
不负责:决定图如何跳转,除非返回 Command
Conditional Edge 协议
负责:读取 state,返回下一节点名、多个节点名或 END
不负责:修改 state
Pregel 协议
负责:执行节点、收集写入、推进 super-step
不负责:理解 quality_score 的业务含义
Reflection 业务协议
负责:定义质量指标、最大修改次数和保底策略
不负责:替代框架循环保护

5.4 稳定接口与内部实现#

类型对象文章中的使用原则
公共 APIStateGraphadd_node()add_edge()add_conditional_edges()compile()可用于工程示例
公共 APIgraph.invoke()graph.stream()config={"recursion_limit": n}可用于运行时控制
扩展接口context_schemaRunnableConfigCommandSend说明契约和约束
内部实现BranchSpec、Pregel loop、channel writes只用于解释,不建议业务代码直接依赖

6. 源码阅读策略与证据标准#

6.1 本篇阅读策略#

先确认 Reflection 的公开构图方式
沿 StateGraph.add_conditional_edges 追到 BranchSpec
沿 compile 追到 CompiledStateGraph / Pregel
看一次 invoke 如何执行节点并应用 state update
看 route_by_score 如何返回 revise 或 final
检查 recursion_limit 和业务 MAX_REVISION 如何共同防死循环
回到 Reflection 工程边界

6.2 证据等级#

标记含义写作要求
源码事实可以由 langgraph==1.2.7 源码证明附 tag / commit 源码链接
官方契约官方文档或 API Reference 明确承诺附官方链接
简化伪代码对真实控制流的压缩表达明确标注“不是源码逐字复制”
作者推断根据调用关系得出的设计理解明确使用“从调用关系可以推断”
工程建议面向旅行规划助手实践的建议说明适用条件

6.3 本篇证据清单#

结论证据类型文件或文档定位
LangGraph 适合用节点、边和 state 表达 workflow官方契约Graph API / Thinking in LangGraphnodes、edges、shared state
Evaluator-Optimizer 是“生成-评价-反馈-再生成”循环官方契约Workflows and agentsEvaluator-optimizer
条件边可以返回一个或多个节点,返回 END 会停止官方契约StateGraph.add_conditional_edges APIpath 返回值
循环需要终止条件,否则会触发 recursion limit官方契约Graph API / GRAPH_RECURSION_LIMITrecursion limit
Reflection 状态应显式保存 score、critique 和 revision_count工程建议本文状态设计旅行规划助手实践

7. 构建期源码解剖#

本章回答:

用户写下 generate_draft → evaluate_draft → route_by_score → revise_draft / final_response 后,LangGraph 在构建期如何把这些函数注册成可编译的图结构?

7.1 构建期职责#

输入归一化动作构建结果
TravelState解析 state schema 和 channelsstate 字段与更新规则
generate_draft 等 callable转换为 runnable node spec节点注册表
route_by_score转换为 branch spec条件边注册表
START / END特殊边界节点图入口和终止点
compile()校验图结构并 attach 节点/边/branchCompiledStateGraph

7.2 构建期总链路#

StateGraph(TravelState)
_add_schema 解析状态字段
add_node 注册 generator / evaluator / revise / final
add_edge 注册固定边
add_conditional_edges 注册 route_by_score
compile 校验图并 attach nodes / edges / branches
CompiledStateGraph

7.3 StateGraph.__init__() 源码解剖#

职责与所处阶段#

StateGraph.__init__() 处于构建期,负责接收用户定义的 state schema,并初始化节点表、边表、分支表、channel 表等构图数据结构。Reflection 的 draft_planquality_scorerevision_count 等字段能成为运行时状态,第一步就在这里发生。

真实源码签名#

以下签名来自当前正式版源码的公开构造入口,细节以 langgraph==1.2.7 为准:

class StateGraph(Graph):
def __init__(
self,
state_schema: type[StateT],
context_schema: type[ContextT] | None = None,
*,
input_schema: type[InputT] | None = None,
output_schema: type[OutputT] | None = None,
**kwargs: Any,
) -> None:
...

调用方与被调用方#

用户代码 StateGraph(TravelState)
StateGraph.__init__
_add_schema / _get_channels / Graph.__init__

输入、输出与状态变化#

项目类型说明
输入type[TravelState]TypedDict state schema
输出StateGraph builder构建期对象
状态变化self.schemasself.channelsself.nodesself.branches初始化图定义容器
副作用无外部副作用只修改 builder 内部结构

细粒度伪代码#

以下为保留关键控制流的简化伪代码,不是源码逐字复制:

class StateGraph(Graph):
def __init__(self, state_schema, context_schema=None,
input_schema=None, output_schema=None):
# 1. 初始化 Graph 基类中的节点、边、分支容器
super().__init__()
# 2. 保存 schema 形态
self.schema = state_schema
self.input_schema = input_schema or state_schema
self.output_schema = output_schema or state_schema
self.context_schema = context_schema
# 3. 初始化状态相关容器
self.schemas = {}
self.channels = {}
self.managed = {}
# 4. 解析 state schema
self._add_schema(state_schema)
# 5. 如果 input / output schema 不同,也单独解析
if self.input_schema is not state_schema:
self._add_schema(self.input_schema)
if self.output_schema is not state_schema:
self._add_schema(self.output_schema)

逐段解释#

第 1 段 super().__init__() 初始化普通图结构:节点、普通边、条件边都会先存到 builder 的内部容器中。此时还没有任何运行时调度。

第 2 段保存 schema。Reflection 场景中,TravelState 既是输入、运行时状态,也是输出 schema 的默认来源。

第 3 段初始化 channels。LangGraph 的 state 不是普通 dict,每个 key 在运行时都对应 channel;后续更新、覆盖、聚合都由 channel 处理。

第 4 段 _add_schema(state_schema) 解析 draft_plancritiquequality_scorerevision_countfinal_plan 这些字段。没有 reducer 时,大多是覆盖式更新。

正常路径#

TravelState
_add_schema
channels: draft_plan / critique / quality_score / revision_count / final_plan
StateGraph builder ready

关键分支与异常路径#

条件行为结果
未传 input/output schema默认使用 state schema简化普通 workflow
state key 使用 Annotated[..., reducer]解析为聚合 channel支持追加式更新
schema 无法解析构建期报错图无法 compile

设计原因与工程影响#

State schema 必须在构建期解析,因为运行时 Pregel 需要提前知道每个 state key 的更新规则。Reflection 中 revision_count 应该是覆盖式字段;critique_history 如果存在,则应该用 reducer 追加。

源码证据#

7.4 add_node() 注册 Reflection 节点源码解剖#

职责与所处阶段#

add_node() 处于构建期,负责把普通 Python 函数注册成 LangGraph 节点。Reflection 中的 generate_draftevaluate_draftrevise_draftfinal_response 都通过它进入图结构。

真实源码签名#

def add_node(
self,
node: str | StateNode[NodeInputT] | None = None,
action: StateNode[NodeInputT] | None = None,
*,
defer: bool = False,
metadata: dict[str, Any] | None = None,
input_schema: type[NodeInputT] | None = None,
retry_policy: RetryPolicy | Sequence[RetryPolicy] | None = None,
cache_policy: CachePolicy | None = None,
destinations: dict[str, str] | tuple[str, ...] | None = None,
**kwargs: Any,
) -> Self:
...

调用方与被调用方#

builder.add_node("evaluate_draft", evaluate_draft)
StateGraph.add_node
coerce_to_runnable / node spec 保存

输入、输出与状态变化#

项目类型说明
输入str + callable节点名与节点函数
输出Self返回 builder,支持链式调用
状态变化self.nodes[name]保存节点 spec
副作用无外部副作用只修改 builder

细粒度伪代码#

以下为保留关键控制流的简化伪代码,不是源码逐字复制:

def add_node(self, node=None, action=None, **options):
# 1. 解析调用形式
if isinstance(node, str):
node_name = node
node_action = action
else:
node_action = node
node_name = infer_name_from_callable(node_action)
# 2. 校验节点名
if node_name in RESERVED_NAMES:
raise ValueError("Node name conflicts with reserved names")
if node_name in self.nodes:
raise ValueError("Node already exists")
# 3. 把 callable 转成 runnable-like 节点
runnable = coerce_to_runnable(
node_action,
name=node_name,
)
# 4. 保存节点 spec
self.nodes[node_name] = StateNodeSpec(
runnable=runnable,
metadata=options.get("metadata"),
input_schema=options.get("input_schema"),
retry_policy=options.get("retry_policy"),
cache_policy=options.get("cache_policy"),
destinations=options.get("destinations"),
defer=options.get("defer", False),
)
return self

逐段解释#

第 1 段支持两种写法:add_node("name", fn)add_node(fn)。Reflection 建议显式命名,这样 trace、可视化和条件边都更清晰。

第 2 段防止节点名冲突。STARTEND 是特殊边界,不应该作为普通节点名。

第 3 段把普通函数转成可运行对象。运行时 Pregel 不直接关心它是不是普通函数,而是按 runnable-like 节点统一调度。

第 4 段保存 retry_policycache_policydefer 等运行时配置。Reflection 中 evaluator 可以设置 retry,但 revise 节点如果包含外部副作用则要谨慎。

正常路径#

evaluate_draft function
add_node
StateNodeSpec
self.nodes["evaluate_draft"]

关键分支与异常路径#

条件行为结果
节点名重复抛出构建期错误防止覆盖已有节点
节点名为保留名抛出错误防止破坏图边界
未传显式名称从 callable 推断可读性可能变差

设计原因与工程影响#

Reflection 循环至少有 4 个节点,如果不显式命名,条件边返回值和可视化图会难以维护。建议生产代码明确使用 generate_draftevaluate_draftrevise_draftfinal_response

源码证据#

7.5 add_conditional_edges() 注册 route_by_score 源码解剖#

职责与所处阶段#

add_conditional_edges() 处于构建期,负责把 evaluate_draft 之后的动态路由逻辑注册为 branch。Reflection 的核心循环不是 while,而是这里注册的条件边。

真实源码签名#

def add_conditional_edges(
self,
source: str,
path: Callable[..., Hashable | Sequence[Hashable]]
| Callable[..., Awaitable[Hashable | Sequence[Hashable]]]
| Runnable[Any, Hashable | Sequence[Hashable]],
path_map: dict[Hashable, str] | list[str] | None = None,
) -> Self:
...

调用方与被调用方#

builder.add_conditional_edges("evaluate_draft", route_by_score)
StateGraph.add_conditional_edges
BranchSpec.from_path
self.branches[source][branch_name] = branch

输入、输出与状态变化#

项目类型说明
输入source node + route callable条件边定义
输出Self返回 builder
状态变化self.branches["evaluate_draft"]保存 branch spec
副作用无外部副作用构建期注册

细粒度伪代码#

以下为保留关键控制流的简化伪代码,不是源码逐字复制:

def add_conditional_edges(self, source, path, path_map=None):
# 1. 校验 source
if source not in self.nodes:
# 允许稍后校验,也可能立即报错,具体以源码为准
record_pending_source(source)
# 2. 把 route function 转成 runnable
path_runnable = coerce_to_runnable(path, name=None)
# 3. 推断 branch 名称
branch_name = path_runnable.name or "condition"
# 4. 防止同一个 source 下 branch 名冲突
if branch_name in self.branches[source]:
raise ValueError("Branch already exists")
# 5. 从 path / path_map / Literal 返回类型构造 BranchSpec
branch = BranchSpec.from_path(
path=path_runnable,
path_map=path_map,
infer_schema=True,
)
# 6. 保存到 source 的 branch 表
self.branches[source][branch_name] = branch
return self

逐段解释#

第 2 段说明 route function 也会被视为可运行对象。它和普通 node 一样可以同步或异步执行。

第 5 段是关键:BranchSpec.from_path() 会处理 path_map,并尝试根据返回类型里的 Literal["revise_draft", "final_response"] 推断可视化目标。

第 6 段把 branch 挂到 evaluate_draft 后面。运行时只有当 evaluate_draft 完成并写入 state 后,route function 才会被调用。

正常路径#

evaluate_draft 执行完成
state 包含 quality_score / revision_count
route_by_score(state)
revise_draft 或 final_response

关键分支与异常路径#

条件行为结果
route 返回 revise_draft调度 revise 节点进入修改循环
route 返回 final_response调度 final 节点准备结束
route 返回未知节点运行时报错路由目标无效
route 返回多个节点多目标写入可能并行执行
route 返回 END终止图不再调度后继节点

设计原因与工程影响#

Reflection 的循环控制应该放在 route function 中,而不是让 evaluator 或 revise 节点直接调用下一节点。这样每个节点只负责状态更新,条件边负责控制流,图结构可视化也更清晰。

源码证据#

7.6 compile() 组装 Reflection 可执行图源码解剖#

职责与所处阶段#

compile() 是构建期到运行时的边界。它把 builder 中保存的节点、普通边和条件边转换成 CompiledStateGraph,使图具备 invoke()stream()ainvoke() 等运行能力。

真实源码签名#

def compile(
self,
checkpointer: Checkpointer = None,
*,
cache: BaseCache | None = None,
store: BaseStore | None = None,
interrupt_before: All | list[str] | None = None,
interrupt_after: All | list[str] | None = None,
debug: bool = False,
name: str | None = None,
) -> CompiledStateGraph:
...

调用方与被调用方#

builder.compile()
StateGraph.compile
validate graph
CompiledStateGraph(...)
attach_node / attach_edge / attach_branch

输入、输出与状态变化#

项目类型说明
输入builder 内部图定义nodes / edges / branches
输出CompiledStateGraph可执行图
状态变化compiled graph 的 channels / nodes / triggers运行时结构被构造
副作用可选 checkpointer/cache/store 注入运行时能力增强

细粒度伪代码#

以下为保留关键控制流的简化伪代码,不是源码逐字复制:

def compile(self, checkpointer=None, **options):
# 1. 校验构建期图结构
self.validate(
interrupt_before=options.get("interrupt_before"),
interrupt_after=options.get("interrupt_after"),
)
# 2. 创建 CompiledStateGraph
compiled = CompiledStateGraph(
builder=self,
channels=self.channels,
nodes={},
input_channels=START,
output_channels=self.output_channels,
checkpointer=checkpointer,
cache=options.get("cache"),
store=options.get("store"),
debug=options.get("debug", False),
name=options.get("name"),
)
# 3. attach START、普通节点
compiled.attach_node(START, None)
for node_name, node_spec in self.nodes.items():
compiled.attach_node(node_name, node_spec)
# 4. attach 普通边
for start, end in self.edges:
compiled.attach_edge(start, end)
# 5. attach 条件分支
for source, branches in self.branches.items():
for branch_name, branch in branches.items():
compiled.attach_branch(source, branch_name, branch)
# 6. 校验 compiled graph
return compiled.validate()

逐段解释#

第 1 段校验图结构,确保节点、边和中断点合法。Reflection 中如果忘记给 final_response 连到 END,或 route 返回不存在节点,这里或运行时会暴露问题。

第 2 段创建可执行图。StateGraph 是 builder,CompiledStateGraph 才是运行时对象。

第 5 段把 route_by_score 这种 branch attach 到 compiled graph。后续 Pregel 执行到 evaluate_draft 后,会触发这个 branch。

正常路径#

StateGraph builder
compile
CompiledStateGraph
可 invoke / stream / ainvoke

关键分支与异常路径#

条件行为结果
图结构合法返回 compiled graph可运行
节点不存在校验失败构建期暴露错误
interrupt 配置非法校验失败防止运行时悬空
checkpointer 存在注入持久化能力支持恢复和 HITL

设计原因与工程影响#

Reflection 循环在 builder 阶段只是声明。只有 compile 后,节点、边、条件分支才被转成 Pregel 可调度结构。生产代码中不应在 compile 后继续修改 builder 并期待已编译图同步变化。

源码证据#

7.7 构建期产物#

产物保存的信息运行时用途
self.nodesgenerate_draft / evaluate_draft / revise_draft / final_responsePregel 调度节点
self.edgesSTART → generate_draftgenerate_draft → evaluate_draft固定拓扑
self.branchesevaluate_draft → route_by_score动态循环控制
self.channelsdraft_planquality_scorerevision_count合并 partial updates
CompiledStateGraphattach 后的 nodes / edges / branches对外执行入口

8. 运行时主链源码解剖#

本章回答:

构建完成后,一次 graph.invoke() 如何执行生成、评价、修改、再评价,直到 final_plan 产出?

8.1 运行时入口#

调用方式公开入口核心内部入口返回类型
同步调用graph.invoke(input, config=...)Pregel stream/invoke 主链final state
异步调用graph.ainvoke(input, config=...)async Pregel 主链final state
流式调用graph.stream(input, ...)Pregel streamstep chunks / values
批量调用graph.batch([...])Runnable batchlist[final state]

8.2 运行时总链路#

graph.invoke(initial_state)
归一化 input / config
初始化 Pregel loop 与 state channels
执行 START 触发的 generate_draft
收集 partial update 并写入 state
执行 evaluate_draft
写入 critique / quality_score
执行 route_by_score branch
根据返回值调度 revise_draft 或 final_response
如果 revise_draft:更新 draft_plan / revision_count 后回到 evaluate_draft
如果 final_response:写入 final_plan 并到达 END

8.3 输入归一化源码解剖#

职责与所处阶段#

输入归一化发生在运行时入口。它把用户传入的初始 state 变成 Pregel 可以写入 state channels 的初始值。

真实源码签名#

公开入口遵循 Runnable 协议,简化签名如下:

def invoke(
self,
input: InputT,
config: RunnableConfig | None = None,
*,
context: ContextT | None = None,
stream_mode: StreamMode | None = None,
**kwargs: Any,
) -> OutputT:
...

调用方与被调用方#

graph.invoke(initial_state)
CompiledStateGraph / Pregel.invoke
stream loop
return latest output

输入、输出与状态变化#

项目类型说明
输入dict初始 TravelState
输出dict最终 state
状态变化state channels 初始化写入初始值
副作用可选 checkpoint / trace取决于 config

细粒度伪代码#

以下为保留关键控制流的简化伪代码,不是源码逐字复制:

def invoke(self, input_value, config=None, context=None, **kwargs):
# 1. 归一化 RunnableConfig
config = ensure_config(config)
# 2. 把 context 放入 runtime,上下文不是 state
runtime = create_runtime_context(
config=config,
context=context,
)
# 3. 调用 stream 主链,但只保留最终结果
latest_output = None
for chunk in self.stream(
input_value,
config=config,
context=context,
**kwargs,
):
latest_output = extract_output(chunk)
# 4. 返回最终 state 或 output schema 对应对象
return latest_output

逐段解释#

第 1 段确保 recursion_limit、metadata、callbacks 等配置被统一处理。

第 2 段区分 context 和 state。Reflection 的 quality_score 属于 state;模型实例、用户 ID、数据库连接更适合 context。

第 3 段说明 invoke 可以理解为对 stream 的封装:运行时不断产生 step 输出,invoke 返回最终值。

正常路径#

initial TravelState
state channels 初始化
Pregel 执行
final TravelState

关键分支与异常路径#

条件行为结果
初始 state 缺少必需字段输入校验或节点访问时报错运行失败
recursion_limit 太低可能提前触发错误需要调高或优化循环
context 缺失依赖 context 的节点报错需要 context_schema

设计原因与工程影响#

Reflection 循环推荐初始化所有关键字段,尤其是 revision_count=0。如果依赖节点内自行处理缺省值,会使状态语义不稳定。

源码证据#

8.4 Config、Context 与 State 传播源码解剖#

数据边界#

数据来源生命周期下游消费者
Configgraph.invoke(..., config=...)单次运行Pregel loop、节点、tracing、recursion limit
Contextgraph.invoke(..., context=...)单次运行节点函数、路由函数、工具/模型选择
State初始输入 + 节点 update整个图运行所有节点和 route function

细粒度伪代码#

以下为保留关键控制流的简化伪代码,不是源码逐字复制:

def execute_superstep(loop, config, runtime):
# 1. 从 channels 读取当前 state 快照
current_state = loop.read_state()
# 2. 根据当前触发条件选择待执行 tasks
tasks = loop.prepare_tasks(current_state)
# 3. 给每个 task 注入 config / runtime / state
for task in tasks:
task_input = build_node_input(
state=current_state,
config=config,
runtime=runtime,
)
task.schedule(task_input)
# 4. task 返回 partial update
writes = collect_task_writes(tasks)
# 5. 应用 writes 到 channels
loop.apply_writes(writes)

逐段解释#

第 1 段读取的是当前 super-step 开始时的 state 快照。同一个 super-step 中并行节点通常看到的是同一轮输入。

第 3 段说明 node 不只接收 state,还可能接收 RunnableConfigRuntime。如果 evaluator 和 generator 用不同模型,可以通过 runtime context 传入模型选择。

第 5 段把节点返回的 partial update 写回 state。revision_count 的递增必须通过节点返回 update,而不是在路由函数中原地修改。

8.5 generate_draft 节点运行源码解剖#

职责与所处阶段#

generate_draft 是 Reflection 主链的第一个业务节点,负责生成初稿并初始化修改次数。

真实源码签名#

用户节点签名遵循 State -> Partial[State]

def generate_draft(state: TravelState) -> dict[str, Any]:
...

调用方与被调用方#

Pregel runner
generate_draft runnable
node function
state channel writes

输入、输出与状态变化#

项目类型说明
输入TravelState初始请求状态
输出dictpartial update
状态变化draft_planrevision_count写入草案和计数
副作用不建议有生成节点应尽量纯

细粒度伪代码#

以下为保留关键控制流的简化伪代码,不是源码逐字复制:

def run_generate_draft_task(state, config, runtime):
# 1. Pregel 构造节点输入
node_input = state
# 2. 调用用户函数
update = generate_draft(node_input)
# 3. 校验返回值是 mapping / Command 等合法写入
writes = normalize_node_update(update)
# 4. 将每个 key 转成 channel write
for key, value in writes.items():
emit_write(channel=key, value=value)
# 5. 当前 super-step 结束时统一应用 write
return writes

逐段解释#

第 2 段调用的是你的 Python 函数。LangGraph 不知道它是不是调用了 LLM,只关心它返回了哪些 state 更新。

第 3 段保证节点输出符合协议。节点应该返回 partial update,而不是直接修改 state 原对象。

第 4 段把 draft_planrevision_count 变成 channel writes。后续合并逻辑由 channel 处理。

正常路径#

user_request
generate_draft
{"draft_plan": ..., "revision_count": 0}
state 更新

关键分支与异常路径#

条件行为结果
节点返回 dict应用 state update正常
节点返回非法类型报错运行失败
LLM 调用失败异常向上传播或由 retry 处理取决于 retry policy

设计原因与工程影响#

generate_draft 应该只负责生成,不应同时决定是否合格。否则 evaluator 失去独立性,Reflection 会退化为普通“自说自话”。

源码证据#

8.6 evaluate_draft 节点运行源码解剖#

职责与所处阶段#

evaluate_draft 是 Reflection 的评价节点。它读取当前草案,输出结构化评价字段:quality_scorecritique,必要时也可以输出 issuesrisk_levelsuggested_changes

真实源码签名#

def evaluate_draft(state: TravelState) -> dict[str, Any]:
...

调用方与被调用方#

Pregel runner
evaluate_draft runnable
LLM / rule-based evaluator
state channel writes

输入、输出与状态变化#

项目类型说明
输入TravelState包含 draft_plan
输出dict评价 update
状态变化quality_scorecritique写入本轮评价
副作用不建议有evaluator 应可重复运行

细粒度伪代码#

以下为保留关键控制流的简化伪代码,不是源码逐字复制:

def run_evaluate_draft_task(state, config, runtime):
# 1. 读取当前草案
draft = state.get("draft_plan")
if not draft:
return {
"quality_score": 0.0,
"critique": "没有可评价的草案。",
}
# 2. 构造评价请求
evaluation_input = {
"draft_plan": draft,
"criteria": runtime.context.criteria,
}
# 3. 调用 evaluator,推荐结构化输出
evaluation = evaluator_model.with_structured_output(
EvaluationResult
).invoke(evaluation_input)
# 4. 返回 partial update
return {
"quality_score": evaluation.quality_score,
"critique": evaluation.critique,
}

逐段解释#

第 1 段处理空草案。这不是源码强制逻辑,而是工程上必须处理的异常状态。

第 3 段建议 evaluator 使用结构化输出。不要只让模型返回“还不错”,否则 route_by_score 没有稳定字段可读。

第 4 段只返回评价字段,不修改 revision_count。计数应该由 revise 节点递增。

正常路径#

draft_plan
evaluator
quality_score + critique
route_by_score

关键分支与异常路径#

条件行为结果
草案为空返回低分或抛出错误进入 revise / fail path
evaluator 输出结构非法结构化解析失败retry / fallback / fail
evaluator 调用失败抛异常由 retry policy 或上层处理

设计原因与工程影响#

Evaluator 应该尽量独立于 generator。对于质量要求高的节点,可以使用更强模型做 evaluator、便宜模型做 generator;但如果 evaluator 没有明确评价指标,使用更强模型也无法保证可靠。

源码证据#

  • libs/langgraph/langgraph/pregel/runner.py
  • langchain_core structured output 由上一篇 OutputParser / Structured Output 覆盖

8.7 route_by_score 分支运行源码解剖#

职责与所处阶段#

route_by_score 是运行时分支函数,在 evaluate_draft 节点完成、state 更新后执行。它读取 quality_scorerevision_count,决定进入 revise_draft 还是 final_response

真实源码签名#

用户侧签名:

def route_by_score(state: TravelState) -> Literal["revise_draft", "final_response"]:
...

内部 branch 运行可抽象为:

class BranchSpec:
def run(self, writer, reader=None) -> Runnable:
...

调用方与被调用方#

evaluate_draft 完成
BranchSpec.run
BranchSpec._route
route_by_score(state)
BranchSpec._finish
写入目标节点 channel

输入、输出与状态变化#

项目类型说明
输入TravelState已包含最新 quality_score
输出strrevise_draftfinal_response
状态变化route function 不应修改 state
副作用应保持纯函数

细粒度伪代码#

以下为保留关键控制流的简化伪代码,不是源码逐字复制:

def branch_route_after_evaluate(state, config, runtime):
# 1. 读取 state 快照
branch_input = state
# 2. 调用用户路由函数
route_result = route_by_score(branch_input)
# 3. 归一化返回值
if isinstance(route_result, (list, tuple)):
destinations = list(route_result)
else:
destinations = [route_result]
# 4. 处理 END
normalized = []
for dest in destinations:
if dest == END:
normalized.append(END)
else:
normalized.append(resolve_path_map(dest))
# 5. 将目的地写入 Pregel channel
for dest in normalized:
if dest is END:
emit_end_signal()
else:
emit_branch_write(target_node=dest)
return normalized

逐段解释#

第 2 段调用 route_by_score。这里不应该递增 revision_count,因为 route function 是控制流函数,不是状态更新节点。

第 3 段说明 route function 可以返回单个节点,也可以返回多个节点。Reflection 通常返回单个节点。

第 4 段处理 path_mapEND。本例没有显式 path_map,因为 Literal 返回值已经是节点名。

第 5 段把路由结果转换为 Pregel 可调度的目标节点写入。

正常路径#

quality_score >= 0.8
final_response
quality_score < 0.8 and revision_count < MAX_REVISION
revise_draft

关键分支与异常路径#

条件行为结果
quality_score >= 0.8返回 final_response正常结束路径
revision_count >= MAX_REVISION返回 final_response保底结束路径
二者都不满足返回 revise_draft继续循环
返回未知节点运行时报错路由错误
返回多个节点并行触发多个节点Reflection 一般不建议这样做

设计原因与工程影响#

Reflection 的停止条件必须明确写在 route function 里。如果只依赖 recursion_limit,图会以异常方式结束,而不是业务可解释的“已达到最大修改次数”。

源码证据#

8.8 revise_draft 节点运行源码解剖#

职责与所处阶段#

revise_draft 是修改节点。它读取当前草案和 critique,生成新版草案,并递增 revision_count

真实源码签名#

def revise_draft(state: TravelState) -> dict[str, Any]:
...

调用方与被调用方#

route_by_score → revise_draft
Pregel runner
revise_draft callable
state update
evaluate_draft

输入、输出与状态变化#

项目类型说明
输入TravelState包含 draft、critique、revision_count
输出dict新草案和新计数
状态变化draft_planrevision_count覆盖草案,递增计数
副作用不建议有修改应可重放

细粒度伪代码#

以下为保留关键控制流的简化伪代码,不是源码逐字复制:

def run_revise_draft_task(state, config, runtime):
# 1. 读取旧草案与评价意见
old_draft = state["draft_plan"]
critique = state["critique"]
old_count = state["revision_count"]
# 2. 调用 revise 模型或规则修改器
revised = revise_model.invoke({
"draft_plan": old_draft,
"critique": critique,
"user_request": state["user_request"],
})
# 3. 递增 revision_count
new_count = old_count + 1
# 4. 返回 partial update
return {
"draft_plan": revised.content,
"revision_count": new_count,
}

逐段解释#

第 1 段读取的是当前 state 中的最新草案和评价意见。它不需要知道上一次 route 是怎么判断的。

第 3 段递增轮次。revision_count 的更新应该和一次实际修改绑定,不能只因为 evaluator 认为质量不够就递增。

第 4 段覆盖 draft_plan。如果需要回滚,应该额外设计 draft_history 并使用 reducer 追加。

正常路径#

critique
revise_draft
new draft_plan + revision_count + 1
evaluate_draft

关键分支与异常路径#

条件行为结果
revise 成功覆盖 draft_plan继续评价
revise 生成更差结果需要 evaluator 识别可能继续循环或最终输出较差版本
revise 失败抛异常或 fallback取决于 retry policy
达到最大轮次route 不再进入 revise保底结束

设计原因与工程影响#

如果新版本更差,系统应能回滚到旧版本。工程上可以保存:

draft_history: Annotated[list[DraftVersion], add]
best_draft: str | None
best_score: float | None

这样 final node 可以选择最高分版本,而不是盲目选择最后一次修改结果。

源码证据#

  • libs/langgraph/langgraph/pregel/runner.py
  • libs/langgraph/langgraph/channels/ state channel update 机制

8.9 final_response 节点运行源码解剖#

职责与所处阶段#

final_response 是 Reflection 主链的出口节点。它把当前草案或最佳草案写入 final_plan

真实源码签名#

def final_response(state: TravelState) -> dict[str, Any]:
...

调用方与被调用方#

route_by_score → final_response
Pregel runner
final_response callable
state update
END

输入、输出与状态变化#

项目类型说明
输入TravelState当前 draft / score / critique
输出dictfinal update
状态变化final_plan写入最终计划
副作用输出节点不应执行高风险动作

细粒度伪代码#

以下为保留关键控制流的简化伪代码,不是源码逐字复制:

def run_final_response_task(state, config, runtime):
# 1. 优先选择 best_draft,如果没有则选择当前 draft
if "best_draft" in state and state["best_draft"]:
selected = state["best_draft"]
else:
selected = state["draft_plan"]
# 2. 附加质量说明
final_text = format_final_plan(
plan=selected,
score=state.get("quality_score"),
critique=state.get("critique"),
revision_count=state.get("revision_count"),
)
# 3. 写入 final_plan
return {
"final_plan": final_text
}

逐段解释#

第 1 段是工程建议:如果保存了最佳版本,则应该选择最佳版本,而不是最后版本。

第 2 段可以把质量说明保留给内部 trace,但面向用户的最终回答不一定展示 evaluator 的所有 critique。

第 3 段写入 final_plan,随后普通边进入 END

正常路径#

final_response
{"final_plan": ...}
END

关键分支与异常路径#

条件行为结果
有 best_draft输出最佳版本更稳
无 best_draft输出当前草案简化实现
final_plan 格式化失败抛异常运行失败

设计原因与工程影响#

Reflection 的最终节点应该只做输出整理,不应该执行“创建订单、退款、发邮件”等高风险动作。高风险动作需要 Policy / Verifier / HITL。

源码证据#

  • libs/langgraph/langgraph/pregel/runner.py
  • libs/langgraph/langgraph/graph/state.py compiled graph update path

8.10 运行时主链总结#

initial state
generate_draft 写 draft_plan
evaluate_draft 写 quality_score / critique
route_by_score 读 score / count
├── revise_draft 写 draft_plan / revision_count
│ ↓
│ evaluate_draft
└── final_response 写 final_plan
END

9. 关键分支、异常与边界#

本章回答:

当 Reflection 循环质量不达标、模型输出异常、轮次耗尽或图无限循环时,框架和业务代码分别如何处理?

9.1 分支矩阵#

分支类型触发条件核心函数结果
质量达标quality_score >= 0.8route_by_score()final_response
轮次耗尽revision_count >= MAX_REVISIONroute_by_score()final_response
继续修改分数低且轮次未耗尽route_by_score()revise_draft
框架保护超过 recursion_limitPregel loopGraphRecursionError
评价失败evaluator 输出非法evaluator node / parserretry / fallback / error
修改失败revise 模型异常revise noderetry / fallback / error

9.2 同步与异步分支#

维度同步路径异步路径
入口graph.invoke()graph.ainvoke()
节点普通 defasync def
调度方式同步 Pregel runner异步 Pregel runner
适用简单本地逻辑或同步模型调用多个异步 I/O、异步模型服务
风险阻塞线程异步异常传播更复杂

9.3 Batch、Stream、Parallel 或路由分支#

Reflection 支持 stream() 观察每一轮:

for chunk in graph.stream(initial_state, config={"recursion_limit": 10}):
print(chunk)

这对调试非常关键,因为你可以看到:

generate_draft 输出什么
evaluate_draft 给了多少分
route_by_score 走向哪里
revise_draft 修改了什么

Reflection 通常不建议让 route_by_score() 同时返回多个修改节点,因为多个修改分支会并行写 draft_plan,如果没有 reducer 或版本字段,会产生覆盖冲突或语义混乱。

9.4 异常分类#

异常类别抛出位置是否可恢复处理策略是否反馈上层
evaluator 结构化输出失败evaluate_draftretry / fallback evaluator
revise 模型调用失败revise_draftretry / 使用旧 draft
route 返回未知节点branch finish修复代码
无限循环Pregel loop部分可恢复捕获 GraphRecursionError,返回最佳已有草案
业务高风险未校验final / action 前不应自动恢复Policy / HITL

9.5 异常路径源码解剖#

职责与所处阶段#

异常路径横跨运行时节点执行、branch 路由和 Pregel 循环保护。Reflection 必须同时有业务停止条件和框架停止条件。

真实源码签名#

相关对象:

class GraphRecursionError(RecursionError):
...

调用方与被调用方#

Pregel loop
检查 super-step / recursion_limit
超过限制
raise GraphRecursionError

输入、输出与状态变化#

项目类型说明
输入当前 step counterPregel 运行时元数据
输出异常超限时抛出
状态变化无正常 final state可通过 checkpoint 获取中间状态
副作用可选 checkpoint如果启用持久化可恢复

细粒度伪代码#

以下为保留关键控制流的简化伪代码,不是源码逐字复制:

def pregel_loop_tick(loop, config):
# 1. 读取 recursion_limit
limit = config.get("recursion_limit", DEFAULT_LIMIT)
# 2. 检查当前 super-step
if loop.step >= limit:
raise GraphRecursionError(
"Graph reached recursion limit before stop condition"
)
# 3. 执行本轮可运行任务
tasks = loop.prepare_next_tasks()
if not tasks:
return DONE
# 4. 调度任务并收集写入
writes = run_tasks(tasks)
# 5. 应用写入并进入下一轮
loop.apply_writes(writes)
loop.step += 1

逐段解释#

第 1 段读取框架保护上限。它不是 Reflection 的业务轮次,而是图的 super-step 上限。

第 2 段说明如果业务逻辑没有走到 END,框架会保护性报错。

第 3 段如果没有任务可执行,图自然完成。

第 5 段说明每个 super-step 后 state 才推进。Reflection 中一次“修改轮”可能包含多个 super-step:revise_draftevaluate_draft 至少各一步。

正常路径#

业务 stop condition 先触发
final_response
END

关键分支与异常路径#

条件行为结果
达到业务质量阈值route 到 final正常结束
达到 MAX_REVISIONroute 到 final保底结束
未达到 END 且超过 recursion_limitGraphRecursionError异常结束

设计原因与工程影响#

Reflection 必须有 MAX_REVISION,不能只依赖 recursion_limit。前者是业务可解释的停止条件,后者是框架保险丝。

源码证据#

9.6 Retry、Fallback 与恢复边界#

机制适用条件不适用条件幂等要求
Retryevaluator 输出格式偶发错误评价指标本身错误节点应无副作用
Fallbackevaluator 模型不可用高风险自动放行fallback 也必须遵守 schema
Repaircritique 格式不合法事实错误或权限错误只修格式,不改业务判断
回滚revise 后质量下降没有保存历史版本需要 draft_historybest_draft

9.7 停止条件与保护上限#

正常结束:quality_score >= threshold
保底结束:revision_count >= MAX_REVISION
提前结束:evaluator 判定不可修复或需要人工
人工中断:高风险任务进入 interrupt / HITL
框架保护:recursion_limit 超限抛 GraphRecursionError
异常失败:route 返回非法目标、节点输出非法、模型调用失败

9.8 能力边界#

容易误判的能力实际提供者Reflection 的真实职责
安全放行Policy / Guardrail / HITL只能提出修改意见
合规判断规则引擎 / Verifier不能替代确定性规则
工具执行确认Tool layer / human approval不能自动确认高风险 action
事实真实性RAG evidence / external tools只能检查文本一致性,除非有证据输入
无限循环保护MAX_REVISION + recursion_limit必须显式设计停止条件

10. 扩展机制与框架协作#

本章回答:

Reflection 循环如何与结构化输出、Reducer、Checkpoint、Subgraph、Command、HITL 和评估系统协作?

10.1 扩展点总览#

扩展点扩展方式执行时机可修改内容约束
结构化 evaluatorPydantic schema / with_structured_outputevaluate_draft 内部score、issues、suggestions只能保证结构,不保证事实
revision historyreducerrevise_draft 写入时追加版本历史注意 token 和状态膨胀
checkpointcompile(checkpointer=...)每个 super-step保存中间状态需要 thread_id
subgraph子图作为节点revise 或 evaluate 内部封装复杂评价流程需要 state 映射
Command节点返回控制指令节点执行后同时更新 state 和跳转控制流更强但更复杂
HITLinterrupt()高风险判断处暂停等待人工需要 checkpoint

10.2 结构化 Evaluator 源码协作解剖#

职责与所处阶段#

结构化 Evaluator 是 Reflection 的关键扩展。它不属于 LangGraph 核心,但与 LangGraph state 强协作:evaluator 输出对象中的字段会被拆成 partial state update。

真实源码签名#

用户侧结构示例:

from pydantic import BaseModel, Field
from typing import Literal
class EvaluationResult(BaseModel):
quality_score: float = Field(ge=0, le=1)
critique: str
should_revise: bool
risk_level: Literal["low", "medium", "high"]

调用方与被调用方#

evaluate_draft node
model.with_structured_output(EvaluationResult)
parsed EvaluationResult
partial state update

输入、输出与状态变化#

项目类型说明
输入draft_plan当前草案
输出EvaluationResult结构化评价
状态变化quality_scorecritique写入 LangGraph state
副作用模型调用可追踪、可 retry

细粒度伪代码#

以下为保留关键控制流的简化伪代码,不是源码逐字复制:

def evaluate_draft(state, runtime):
# 1. 构造结构化 evaluator
evaluator = runtime.context.evaluator_model.with_structured_output(
EvaluationResult
)
# 2. 调用 evaluator
result = evaluator.invoke({
"user_request": state["user_request"],
"draft_plan": state["draft_plan"],
"criteria": runtime.context.criteria,
})
# 3. 将结构化对象映射为 state update
return {
"quality_score": result.quality_score,
"critique": result.critique,
}

逐段解释#

第 1 段把 evaluator 的输出约束成结构化对象,避免 route function 解析自然语言。

第 3 段只写入 route 所需字段。如果需要更完整的诊断,可以额外写入 issuescritique_history

正常路径#

LLM evaluation
EvaluationResult
quality_score / critique
route_by_score

关键分支与异常路径#

条件行为结果
schema 校验成功写入 state正常路由
schema 校验失败抛解析错误retry / fallback
evaluator 分数偏差路由错误需要 eval dataset 校准

设计原因与工程影响#

Evaluator 输出应该结构化,因为 route function 是代码,不应该解析“这个计划还行,但……”这样的自然语言。

源码证据#

  • langchain-core structured output 机制见第 3 篇
  • LangGraph node 只要求返回 partial state update

10.3 Revision History 与 Reducer 协作源码解剖#

职责与所处阶段#

如果担心新版更差,需要保留历史版本。这个能力可以通过 reducer 实现:每次 revise_draft 追加一个版本,而不是只覆盖 draft_plan

真实源码签名#

用户 state 示例:

from typing import Annotated
from operator import add
class TravelState(TypedDict):
draft_plan: str | None
draft_history: Annotated[list[str], add]
best_draft: str | None
best_score: float | None

调用方与被调用方#

revise_draft
return {"draft_history": [new_draft]}
channel reducer add
state["draft_history"] 追加

输入、输出与状态变化#

项目类型说明
输入draft_history历史版本列表
输出list[str] update新版本
状态变化追加reducer 合并
副作用状态内记录

细粒度伪代码#

以下为保留关键控制流的简化伪代码,不是源码逐字复制:

def revise_draft(state):
new_draft = revise_model.invoke(...).content
new_count = state["revision_count"] + 1
return {
"draft_plan": new_draft, # 覆盖当前版本
"draft_history": [new_draft], # 追加历史版本
"revision_count": new_count,
}

逐段解释#

draft_plan 是覆盖式字段,始终代表当前版本。

draft_history 是追加式字段,用 reducer 保存所有历史版本。

best_draftbest_score 可以由 evaluator 或专门节点维护,防止最终输出最后一版但不是最好一版。

正常路径#

revise_draft
覆盖 draft_plan
追加 draft_history
evaluate_draft

关键分支与异常路径#

条件行为结果
使用 reducer历史版本保留可回滚
不使用 reducer只保留当前版本无法比较旧版本
历史无限增长状态膨胀需要裁剪或 checkpoint 策略

设计原因与工程影响#

Reflection 常见问题是“越改越差”。保存历史版本可以让 final node 选择最佳版本,而不是最后版本。

源码证据#

  • libs/langgraph/langgraph/graph/state.py schema / channel 解析
  • libs/langgraph/langgraph/channels/ reducer channel

10.4 与相邻框架组件的协作#

相邻组件输入协议输出协议协作边界
Structured Outputprompt / messages / schemaPydantic object生成 evaluator 结构化结果
Reducerstate key updatemerged state保存 critique / draft history
Checkpointthread_id / state snapshotpersisted state支持回放和恢复
Interruptinterrupt payloadCommand resume高风险场景人工确认
Subgraphparent state 映射subgraph output封装复杂 evaluator 或 revise 流程
LangSmithtraceseval / debugging观察每轮修改质量

10.5 公共扩展接口与内部实现#

业务代码可以依赖:
StateGraph
add_node
add_edge
add_conditional_edges
compile
graph.invoke / graph.stream
recursion_limit
reducer annotation
业务代码避免依赖:
BranchSpec 内部字段
PregelLoop 内部 step 结构
私有 channel 名称
未文档化的 task 元数据

10.6 自定义扩展示例#

示例只展示扩展契约,不重新实现框架:

class ReflectionConfig(TypedDict):
max_revision: int
score_threshold: float
def route_by_score(state: TravelState, config: RunnableConfig):
threshold = config.get("configurable", {}).get("score_threshold", 0.8)
max_revision = config.get("configurable", {}).get("max_revision", 2)
if (state["quality_score"] or 0.0) >= threshold:
return "final_response"
if state["revision_count"] >= max_revision:
return "final_response"
return "revise_draft"

说明:

  1. 扩展点接收当前 state 和 config。
  2. 扩展点允许修改路由决策。
  3. 扩展点必须返回合法目标节点。
  4. 异常会导致图运行失败。
  5. 不应在 route function 内修改 state。

10.7 选择扩展还是重写流程#

条件选择扩展点选择更底层框架
只调整分数阈值
增加 critique history
evaluator 变成多节点流程视复杂度是,考虑 subgraph
引入人工审批是,使用 interrupt
多个专家并行评价是,使用并行分支 + reducer
需要复杂搜索树是,单独建搜索图

11. 工程决策与适用场景#

11.1 适用场景#

场景是否推荐原因
旅行计划质量优化有明确用户偏好和可迭代改进空间
文案润色质量指标主观但可评价
报告生成可通过结构化评价逐步改进
代码修复可结合测试结果做 verifier
退款审批高风险动作,不能靠反思自动放行
权限判断应使用确定性 policy / RBAC
合规决策需要规则、证据和审计

11.2 工程决策表#

决策点推荐选择前提风险
evaluator 输出结构化输出需要稳定 routeschema 设计过复杂会失败
修改轮次MAX_REVISION 2–3质量优化任务过高导致成本失控
循环保护同时设置 recursion_limit图中有循环过低可能误杀复杂流程
草案保存当前版本 + 可选 history需要回滚状态膨胀
模型选择generator 可便宜,evaluator 可更强评价重要成本上升
高风险动作Reflection 后仍进 verifier/HITL有副作用自动放行风险

11.3 性能、可靠性与安全边界#

性能:每次 revise 都至少增加一次 generator 和一次 evaluator 调用。
可靠性:主要失败模式是 evaluator 标准不清、循环无法结束、越改越差。
安全:Reflection 不能替代 policy、权限、合规和人工确认。
可观测性:必须记录 draft、critique、score、revision_count、route decision。

12. 常见误区与源码纠正#

12.1 误区:Reflection 就是在 prompt 里写“再检查一下”#

错误原因:

很多教程把 Reflection 写成单次模型调用后的二次提示,没有状态字段,也没有可测试的停止条件。

源码事实:

LangGraph 中可控 Reflection = state 字段 + evaluator node + route branch + revise loop + stop condition。

工程影响:

如果只靠 prompt,无法稳定知道模型是否真的检查过,也无法做 trace、评估和回滚。

12.2 误区:route function 可以顺便修改 revision_count#

错误原因:

route function 能拿到 state,容易误以为可以直接原地修改。

源码事实:

LangGraph 的状态更新协议是 node 返回 partial update;route function 的职责是返回路径。

工程影响:

在 route 中修改 state 会破坏数据流清晰性,也可能不会被框架作为正式 state update 追踪。

12.3 误区:只设置 recursion_limit 就能防止反思循环失控#

错误原因:

recursion_limit 看起来能限制循环步数。

源码事实:

recursion_limit 是框架级 super-step 上限;MAX_REVISION 是业务级停止条件。

工程影响:

只依赖 recursion limit 会让任务以异常结束,用户和日志都难以解释为什么停止。

12.4 误区:evaluator 输出自然语言也可以#

错误原因:

人类读 critique 时自然语言很舒服。

源码事实:

route_by_score 需要稳定读取 quality_score / should_revise 等结构化字段。

工程影响:

自然语言评价会导致路由逻辑脆弱,后续测试也难以量化。

12.5 误区:Reflection 可以替代 Verifier#

错误原因:

evaluator 看起来也在“检查”。

源码事实:

Reflection 主要优化质量;Verifier / Policy 负责确定性规则和高风险边界。

工程影响:

让 Reflection 自动批准退款、下单、发邮件等动作,会产生严重安全风险。


13. 最终心智模型与掌握检查#

13.1 构建期心智模型#

TravelState
add_node(generator / evaluator / revise / final)
add_conditional_edges(evaluate_draft, route_by_score)
add_edge(revise_draft, evaluate_draft)
compile
CompiledStateGraph

13.2 运行时心智模型#

initial state
generate_draft
evaluate_draft
route_by_score
├── revise_draft → evaluate_draft
└── final_response → END

13.3 分支与异常心智模型#

正常路径 → quality_score 达标 → final_response
保底路径 → revision_count 达上限 → final_response
修改路径 → 分数低且未达上限 → revise_draft
可恢复异常 → evaluator / revise 失败 → retry 或 fallback
不可恢复异常 → 路由目标非法 / state 输出非法 → 抛出
保护上限 → recursion_limit 超限 → GraphRecursionError

13.4 一句话总结#

Reflection 通过 StateGraph 构建 generator、evaluator、revise 和 final 节点,运行时沿 evaluate → route → revise 循环推进 state,使用 quality_scorerevision_count 作为业务停止条件,并通过 recursion_limit、structured evaluator、reducer history、checkpoint 和 verifier 协作,形成可观测、可终止、可回滚的评价-修改机制。

13.5 掌握检查#

  • 能说清 Reflection 和普通二次 prompt 的区别。
  • 能画出 generate → evaluate → route → revise → evaluate 的图结构。
  • 能解释 route_by_score 为什么不应修改 state。
  • 能说明 revision_count 应该在哪里递增。
  • 能说明 evaluator 为什么应该结构化输出。
  • 能区分 MAX_REVISIONrecursion_limit
  • 能说明新版本更差时如何回滚。
  • 能说明 Reflection 为什么不能替代 Policy / Verifier。
  • 能判断旅行规划助手哪些节点适合 Reflection。

14. 参考资料与下一篇衔接#

14.1 官方概念文档#

  1. LangGraph Workflows and agents:Evaluator-optimizer
    https://docs.langchain.com/oss/python/langgraph/workflows-agents

  2. LangGraph Graph API overview
    https://docs.langchain.com/oss/python/langgraph/graph-api

  3. LangGraph Thinking in LangGraph
    https://docs.langchain.com/oss/python/langgraph/thinking-in-langgraph

  4. LangGraph Recursion Limit Error
    https://docs.langchain.com/oss/python/langgraph/errors/GRAPH_RECURSION_LIMIT

14.2 官方 API Reference#

  1. StateGraph
    https://reference.langchain.com/python/langgraph/graphs/#langgraph.graph.state.StateGraph

  2. StateGraph.add_conditional_edges
    https://reference.langchain.com/python/langgraph/graph/state/StateGraph/add_conditional_edges

  3. CompiledStateGraph
    https://reference.langchain.com/python/langgraph/graphs/#langgraph.graph.state.CompiledStateGraph

14.3 官方源码#

  1. langgraph/graph/state.py::StateGraph
    https://github.com/langchain-ai/langgraph/blob/5931a5f/libs/langgraph/langgraph/graph/state.py

  2. langgraph/graph/branch.py::BranchSpec
    https://github.com/langchain-ai/langgraph/blob/5931a5f/libs/langgraph/langgraph/graph/branch.py

  3. langgraph/pregel/loop.py
    https://github.com/langchain-ai/langgraph/blob/5931a5f/libs/langgraph/langgraph/pregel/loop.py

  4. langgraph/errors.py::GraphRecursionError
    https://github.com/langchain-ai/langgraph/blob/5931a5f/libs/langgraph/langgraph/errors.py

14.4 下一篇衔接#

下一篇进入:

第 11 篇:Checkpoint / Thread / Durable Execution 源码解剖

需要继续回答:

thread_id 如何定位一次长期执行?
checkpoint 在每个 super-step 如何保存 state snapshot?
get_state / get_state_history 如何读取历史?
interrupt / resume 为什么必须依赖 checkpointer?
生产级 Agent 如何用 checkpoint 支撑恢复、回放、HITL 和长期记忆?
LangGraph 源码深潜:Reflection 与评价-修改循环机制解剖
https://jupiter-ws.cn/posts/agent-frameworks/langgraph-reflection-loop-deep-dive/
作者
Jupiter
发布于
2026-03-14
许可协议
CC BY-NC-SA 4.0