目录
[二、为什么需要 Conditional Edge?](#二、为什么需要 Conditional Edge?)
[(三)Conditional Edge 解决什么问题?](#(三)Conditional Edge 解决什么问题?)
[(一)Conditional Edge 的三要素](#(一)Conditional Edge 的三要素)
[1. Source Node](#1. Source Node)
[2. Router Function](#2. Router Function)
[3. Path Map](#3. Path Map)
[(二)_get_next_nodes(self, current_node, state)](#(二)_get_next_nodes(self, current_node, state))
[1. 路由结果必须命中 path_map](#1. 路由结果必须命中 path_map)
[2. LangGraph 不会自动检测死循环](#2. LangGraph 不会自动检测死循环)
[1. 场景一:简单二选一](#1. 场景一:简单二选一)
[2. 场景二:多路分支](#2. 场景二:多路分支)
[3. 场景三:循环重试](#3. 场景三:循环重试)
[(一)坑 1:返回值不在 path_map 中](#(一)坑 1:返回值不在 path_map 中)
[(二)坑 2:循环没有退出条件](#(二)坑 2:循环没有退出条件)
[(三)坑 3:在 Router 中修改 State](#(三)坑 3:在 Router 中修改 State)
[(四)坑 4:普通边与条件边混用](#(四)坑 4:普通边与条件边混用)
[1. 防死循环](#1. 防死循环)
[2. 路由全覆盖](#2. 路由全覆盖)
[3. 增加可观测性](#3. 增加可观测性)
[4. 设计默认分支](#4. 设计默认分支)
干货分享,感谢您的阅读!
本文是「LangGraph 实战」系列第 3 篇。前两篇我们已经掌握了:
- 如何构建线性执行流程(StateGraph)
- 如何利用 Reducer 合并状态(State)
但真正的 Agent 从来不会只走直线。它需要会分叉、会循环、会重试。这一篇,我们给图装上"大脑"------Conditional Edge(条件边)。
一、为什么你又卡住了?
回到系列开头的内容审核需求。现在你已经能写出这样的工作流:
php
提交文章 -> AI 评分 -> 发布
于是你信心满满地准备实现真正的业务逻辑:
评分 ≥ 60 分直接发布,否则退回修改;
调用外部 API 失败时自动重试,最多重试 5 次。
结果又发现一个问题:
python
graph.add_edge("evaluate", "publish")
普通 Edge 在构建图时就已经确定了目标节点。
它无法表达:"运行时根据评分决定下一步去哪儿。" 更无法表达:"失败后返回上一步重新执行。"
而这恰恰是 Agent 最核心的能力。普通 Edge 是:
perl
evaluate → publish
无论发生什么都往前走。
而你真正需要的是:
evaluate
├── publish
├── revise
└── retry
根据实际情况动态选择路径。这就是 Conditional Edge(条件边) 存在的意义。它让 LangGraph 从"脚本"进化成"Agent"。
二、为什么需要 Conditional Edge?

(一)条件分支决策
假设我们要构建一个内容审核 Agent:
1. 接收用户提交内容
2. AI 评估内容质量
3. 高质量 → 发布
4. 质量不足 → 退回修改
步骤 3 和步骤 4 显然是一个条件分支。下一步执行什么,取决于运行时的评分结果,普通 Edge 做不到这一点,因为它在构建图时已经把执行路径固定死了。Conditional Edge 则提供了:
运行时决策能力(Runtime Decision Making)
这正是 Agent 与普通工作流最大的区别。
(二)后端开发类比
普通 Edge:
java
methodA();
methodB();
执行顺序固定。
条件边:
java
switch (router(state)) {
case "approve":
publish();
break;
case "reject":
revise();
break;
}
目标节点由运行时数据决定。
如果某个分支再指回自己:
java
while (needRetry()) {
callApi();
}
那就形成了循环。而循环、重试、轮询,本质上都是同一个模式。
(三)Conditional Edge 解决什么问题?
| 场景 | 普通 Edge | Conditional Edge |
|---|---|---|
| 固定流程 | ✅ | ✅ |
| 运行时分支 | ❌ | ✅ |
| 多路径选择 | ❌ | ✅ |
| 循环重试 | ❌ | ✅ |
记住一个判断标准:
只要下一步取决于运行时的数据,就应该使用 Conditional Edge。
三、核心原理
(一)Conditional Edge 的三要素
python
graph.add_conditional_edges(
"evaluate", # 1. 从哪个节点出发
review_router, # 2. 路由函数:(State) -> str
{ # 3. 路由映射 path_map:路由值 → 目标节点
"approve": "publish",
"reject": "revise",
"retry": "evaluate", # 指回自己 → 形成循环
}
)
它由三个部分组成:
1. Source Node
"evaluate"
从哪个节点执行完后开始路由。
2. Router Function
review_router(state)
签名:
(State) -> str
根据当前 State 返回一个字符串。
例如:
python
def review_router(state):
if state["score"] >= 60:
return "approve"
return "reject"
3. Path Map
Dart
{
"approve": "publish",
"reject": "revise",
}
负责把路由结果映射到目标节点。
(二)执行流程
html
evaluate 执行完成
↓
State 更新
↓
review_router(state)
↓
返回字符串
↓
approve → publish
reject → revise
retry → evaluate
这里有一个非常关键的细节:
路由函数拿到的是节点执行完成并更新后的 State。
也就是说:
evaluate()
刚刚计算出的结果,可以立即用于路由判断。
四、源码分析
(一)add_conditional_edges()
add_conditional_edges() 本质上会把路由信息封装成一个 Branch 对象保存起来。
简化后的源码:
python
def add_conditional_edges(
self,
source,
path,
path_map=None,
then=None
):
if source not in self.nodes and source != START:
raise ValueError(
f"Source node '{source}' not found"
)
if path_map is None:
path_map = {n: n for n in self.nodes}
self.branches[source].append(
Branch(
path=path,
ends=path_map,
then=then
)
)
(二)_get_next_nodes(self, current_node, state)
执行阶段:
python
# CompiledStateGraph 执行逻辑(简化)
def _get_next_nodes(self, current_node, state):
branches = self.branches.get(current_node, [])
if branches:
for branch in branches:
route_result = branch.path(state) # 调用路由函数
if route_result in branch.ends:
return [branch.ends[route_result]] # 命中映射 → 跳转
raise ValueError( # ← 没命中就直接报错!
f"Route returned '{route_result}', not in {list(branch.ends.keys())}"
)
return self._get_static_next(current_node) # 没有条件边 → 走普通边
(三)两个必须记住的细节
1. 路由结果必须命中 path_map
否则直接抛异常:
Route returned 'xxx'
不会静默忽略。这是新手最常见的问题之一。
2. LangGraph 不会自动检测死循环
它依赖:
recursion_limit
作为兜底机制。默认值:25。超过后抛出:
GraphRecursionError
因此循环逻辑必须自己维护计数器。
五、实战示例
(一)完整设计代码
python
"""Demo 03: Conditional Edges --- 条件边与路由函数。
演示 LangGraph 的条件路由核心:
1. add_conditional_edges() 定义条件边
2. 路由函数根据 State 决定下一节点
3. 多分支选择(多路径路由)
4. 循环 + 条件边实现 Agent 循环
运行方式:
python stages/stage1_fundamentals/03_conditional_edges/main.py
"""
from __future__ import annotations
import operator
import random
import sys
from pathlib import Path
from typing import Annotated, TypedDict
from langgraph.graph import END, START, StateGraph
sys.path.insert(0, str(Path(__file__).resolve().parent.parent.parent.parent))
from shared import get_logger, log_step, log_warning
logger = get_logger("demo.03_cond_edges")
random.seed(42)
# ============================================================
# 场景一:简单条件分支(通过 / 拒绝)
# ============================================================
class ReviewState(TypedDict):
content: str
score: int
feedback: str
path_taken: str
def evaluate_node(state: ReviewState) -> dict:
"""评估节点 --- 对内容打分。"""
score = len(state["content"]) * 3 % 100
log_step(logger, "evaluate", f"内容长度={len(state['content'])}, 评分={score}")
return {"score": score}
def approve_node(state: ReviewState) -> dict:
"""审批通过节点。"""
log_step(logger, "approve", f"内容通过审核!评分: {state['score']}")
return {"feedback": f"✅ 审核通过,评分 {state['score']} 分", "path_taken": "approve"}
def reject_node(state: ReviewState) -> dict:
"""拒绝节点。"""
log_step(logger, "reject", f"内容未通过审核,评分: {state['score']}")
return {"feedback": f"❌ 审核未通过,评分 {state['score']} 分", "path_taken": "reject"}
def review_router(state: ReviewState) -> str:
"""路由函数:根据评分决定通过还是拒绝。"""
if state["score"] >= 50:
log_step(logger, "路由决策", f"评分 {state['score']} ≥ 50 → 走 approve 路径")
return "approve"
else:
log_step(logger, "路由决策", f"评分 {state['score']} < 50 → 走 reject 路径")
return "reject"
def demo_simple_branch():
"""演示简单的条件分支。"""
print("\n--- 场景一:简单条件分支(通过/拒绝)---")
graph = StateGraph(ReviewState)
graph.add_node("evaluate", evaluate_node)
graph.add_node("approve", approve_node)
graph.add_node("reject", reject_node)
graph.add_edge(START, "evaluate")
graph.add_conditional_edges("evaluate", review_router, {
"approve": "approve",
"reject": "reject",
})
graph.add_edge("approve", END)
graph.add_edge("reject", END)
app = graph.compile()
for content in ["LangGraph 入门教程,这是一篇高质量的内容", "短"]:
print(f"\n 输入: '{content}'")
result = app.invoke({
"content": content,
"score": 0,
"feedback": "",
"path_taken": "",
})
print(f" 结果: {result['feedback']}")
print(f" 路径: evaluate → {result['path_taken']}")
# ============================================================
# 场景二:多分支路由(优秀 / 合格 / 不合格)
# ============================================================
class GradeState(TypedDict):
student_name: str
score: int
grade: str
message: str
def grade_router(state: GradeState) -> str:
"""多分支路由:根据分数划分等级。"""
score = state["score"]
if score >= 90:
return "excellent"
elif score >= 60:
return "pass"
else:
return "fail"
def demo_multi_branch():
"""演示多分支路由。"""
print("\n--- 场景二:多分支路由(优秀/合格/不合格)---")
def excellent_node(state: GradeState) -> dict:
return {"grade": "优秀", "message": f"🏆 {state['student_name']} 成绩优秀!"}
def pass_node(state: GradeState) -> dict:
return {"grade": "合格", "message": f"✅ {state['student_name']} 成绩合格"}
def fail_node(state: GradeState) -> dict:
return {"grade": "不合格", "message": f"⚠️ {state['student_name']} 需要补考"}
graph = StateGraph(GradeState)
graph.add_node("excellent", excellent_node)
graph.add_node("pass", pass_node)
graph.add_node("fail", fail_node)
graph.add_conditional_edges(START, grade_router, {
"excellent": "excellent",
"pass": "pass",
"fail": "fail",
})
graph.add_edge("excellent", END)
graph.add_edge("pass", END)
graph.add_edge("fail", END)
app = graph.compile()
students = [
{"student_name": "小明", "score": 95},
{"student_name": "小红", "score": 72},
{"student_name": "小刚", "score": 45},
]
for student in students:
result = app.invoke({**student, "grade": "", "message": ""})
print(f" {result['message']} (分数: {student['score']}, 等级: {result['grade']})")
# ============================================================
# 场景三:循环 + 条件边(重试机制)
# ============================================================
class RetryState(TypedDict):
task: str
attempt: int
max_attempts: int
result: str
log: Annotated[list[str], operator.add]
def attempt_node(state: RetryState) -> dict:
"""执行节点 --- 模拟一个可能失败的操作。"""
attempt = state["attempt"] + 1
success = random.random() > 0.88
status = "成功" if success else "失败"
log_step(logger, "attempt", f"第 {attempt} 次尝试... {status}")
return {
"attempt": attempt,
"result": "success" if success else "fail",
"log": [f"第 {attempt} 次尝试: {status}"],
}
def retry_router(state: RetryState) -> str:
"""路由函数:判断是否需要重试。"""
if state["result"] == "success":
log_step(logger, "路由决策", "任务成功 → 完成")
return "done"
elif state["attempt"] >= state["max_attempts"]:
log_warning(logger, f"已达最大尝试次数 {state['max_attempts']} → 强制结束")
return "give_up"
else:
log_step(logger, "路由决策", f"任务失败,第 {state['attempt']}/{state['max_attempts']} 次 → 重试")
return "retry"
def success_node(state: RetryState) -> dict:
return {"log": [f"🎉 任务在第 {state['attempt']} 次尝试后成功完成!"]}
def give_up_node(state: RetryState) -> dict:
return {"log": [f"😞 经过 {state['attempt']} 次尝试后放弃"]}
def demo_retry_loop():
"""演示循环 + 条件边实现重试机制。"""
print("\n--- 场景三:循环 + 条件边(重试机制)---")
graph = StateGraph(RetryState)
graph.add_node("attempt", attempt_node)
graph.add_node("success", success_node)
graph.add_node("give_up", give_up_node)
graph.add_edge(START, "attempt")
graph.add_conditional_edges("attempt", retry_router, {
"retry": "attempt",
"done": "success",
"give_up": "give_up",
})
graph.add_edge("success", END)
graph.add_edge("give_up", END)
app = graph.compile()
result = app.invoke({
"task": "调用外部 API",
"attempt": 0,
"max_attempts": 5,
"result": "",
"log": ["开始执行任务: 调用外部 API"],
})
print(" 执行日志:")
for entry in result["log"]:
print(f" {entry}")
def run_demo() -> dict:
"""运行 Conditional Edges Demo。"""
print("=" * 60)
print(" Demo 03: Conditional Edges --- 条件边与路由函数")
print("=" * 60)
demo_simple_branch()
demo_multi_branch()
demo_retry_loop()
print()
print("=" * 60)
print(" 关键概念回顾")
print("=" * 60)
print(" 1. add_conditional_edges(): 定义条件边(三个参数)")
print(" 2. 路由函数: 接收 State,返回字符串(目标节点名)")
print(" 3. path_map: 路由值 → 目标节点的映射字典")
print(" 4. 循环: 条件边可以指回自身(retry → attempt)")
print(" 5. 安全: 必须设置最大循环次数防止死循环!")
print()
return {}
if __name__ == "__main__":
run_demo()
(二)关键代码说明
本 Demo 展示了三种典型使用方式。
1. 场景一:简单二选一
python
def review_router(state):
return (
"approve"
if state["score"] >= 50
else "reject"
)
graph.add_conditional_edges(
"evaluate",
review_router,
{
"approve": "approve",
"reject": "reject",
}
)
2. 场景二:多路分支
甚至可以直接从 START 开始路由:
python
def grade_router(state):
score = state["score"]
return (
"excellent"
if score >= 90
else "pass"
if score >= 60
else "fail"
)
graph.add_conditional_edges(
START,
grade_router,
{
"excellent": "excellent",
"pass": "pass",
"fail": "fail",
}
)
3. 场景三:循环重试
条件边指回自身。
python
def retry_router(state):
if state["result"] == "success":
return "done"
if state["attempt"] >= state["max_attempts"]:
return "give_up"
return "retry"
python
graph.add_conditional_edges(
"attempt",
retry_router,
{
"retry": "attempt",
"done": "success",
"give_up": "give_up",
}
)
形成:
attempt
↓
retry
↓
attempt
↓
retry
↓
attempt
↓
success
(三)运行效果
实际运行 main.py:
============================================================
Demo 03: Conditional Edges --- 条件边与路由函数
============================================================
--- 场景一:简单条件分支(通过/拒绝)---
输入: 'LangGraph 入门教程,这是一篇高质量的内容'
结果: ✅ 审核通过,评分 75 分
路径: evaluate → approve
输入: '短'
结果: ❌ 审核未通过,评分 3 分
路径: evaluate → reject
--- 场景二:多分支路由(优秀/合格/不合格)---
🏆 小明 成绩优秀! (分数: 95, 等级: 优秀)
✅ 小红 成绩合格 (分数: 72, 等级: 合格)
⚠️ 小刚 需要补考 (分数: 45, 等级: 不合格)
--- 场景三:循环 + 条件边(重试机制)---
执行日志:
开始执行任务: 调用外部 API
第 1 次尝试: 失败
第 2 次尝试: 失败
第 3 次尝试: 成功
🎉 任务在第 3 次尝试后成功完成!
如何理解这三个场景?
场景一:运行时决策
同一张图:
app.invoke(...)
因为输入内容不同:
75 分
3 分
最终进入不同节点:
approve
reject
这就是运行时路由。
场景二:入口即分流
Conditional Edge 不一定挂在普通节点后。它甚至可以直接挂在:
START
图一启动就决定走哪条路径。
场景三:重试机制
这是最重要的应用场景。路由函数不断判断:
成功?
↓
否
达到上限?
↓
否
继续重试
于是形成循环。
这里配合:
Annotated[list, operator.add]
把每次尝试日志都保留下来。另外 Demo 使用:
random.seed(42)
保证每次运行结果一致,方便学习和调试。
六、常见坑与排查
(一)坑 1:返回值不在 path_map 中
现象
Route returned 'xxx'
原因
返回了未覆盖的字符串。例如:
return "aprove"
拼写错误。
解决
确保所有返回值都被覆盖。
python
graph.add_conditional_edges(
"eval",
router,
{
"approve": "publish",
"reject": "revise",
}
)
(二)坑 2:循环没有退出条件
现象
GraphRecursionError:
Recursion limit of 25 reached
原因
永远返回:
"retry"
导致无限循环。
解决
维护计数器:
python
def retry_router(state):
if state["result"] == "success":
return "done"
if state["attempt"] >= state["max_attempts"]:
return "give_up"
return "retry"
(三)坑 3:在 Router 中修改 State
错误示例
python
def bad_router(state):
state["count"] += 1
return "next"
路由函数应该是纯函数。
正确写法
python
def good_router(state):
return (
"next"
if state["count"] < 3
else "stop"
)
所有状态修改都应该在 Node 中完成。
(四)坑 4:普通边与条件边混用
错误
同一个节点:
python
graph.add_edge(...)
graph.add_conditional_edges(...)
同时存在。
正确
二选一:
- 普通边
- 条件边
不要混用。
七、工程化实践与生产级建议
(一)工程化实践
1. 防死循环
State 中维护计数器:
attempt
max_attempts
同时配置:
recursion_limit
作为最后保险。
2. 路由全覆盖
确保:
router()
所有返回值都能在:
path_map
中找到对应目标。
3. 增加可观测性
路由决策最好打日志:
当前评分: 72
路由结果: approve
线上排障时非常有用。
4. 设计默认分支
例如:
"default": END
避免边界输入直接导致异常。
(二)生产级方案
python
result = app.invoke(
state,
config={
"recursion_limit": 50
}
)
将阈值配置化:
python
APPROVE_THRESHOLD = 60
def review_router(state):
return (
"approve"
if state["score"] >= APPROVE_THRESHOLD
else "reject"
)
重试策略配置化:
max_attempts
backoff_strategy
后续再结合 Checkpointer:
checkpoint
resume
实现长任务断点续跑。监控路由命中率:
approve: 70%
reject : 25%
retry : 5%
如果某个分支突然异常升高:
reject: 90%
往往意味着模型、Prompt 或业务逻辑出了问题。
八、总结
Conditional Edge 是 LangGraph 区别于传统线性 Chain 的核心能力之一。
掌握它之后,你就拥有了构建 Agent 决策流程的关键工具:
- 条件分支
- 多路路由
- 循环重试
- 审批流
- 状态机
再结合前两篇介绍的:
- State
- Reducer
你已经具备搭建绝大多数 Agent 工作流的基础能力。
下一篇预告
下一篇我们进入 Agent 最常见的场景:对话状态管理(Conversation State)