Appearance
条件边 Conditional Edge
上一篇的普通边只能"确定地走某条路"。但真实场景里你常常需要"看情况"——根据当前状态决定走哪条分支。这就是条件边(ConditionalEdge)的用武之地,它也是构建循环(如 ReAct 智能体)的关键。
一、条件边是什么
条件边:执行完 source 节点后,调用一个路由函数读取当前 state,返回"下一节点名",框架据此跳转。
API:
python
graph.add_conditional_edges(
source, # 从哪个节点出发
router_fn, # 路由函数:(state) -> str | list[str]
path_map, # 可选:返回值 -> 节点名 的映射
)mermaid
graph LR
A[节点A] --> R{router 函数}
R -->|返回 'yes'| Y[节点Y]
R -->|返回 'no'| N[节点N]
R -->|返回 'end'| E([END])二、路由函数签名
python
def router(state: State) -> str:
# 读 state,决定下一步
if state["score"] > 60:
return "pass"
return "fail"- 输入:当前 state。
- 输出:字符串(下一节点的名字)或字符串列表(多路分发,见后文)。
- 不修改 state:路由函数只读不写。
返回的字符串有两种解释方式:
- 直接是节点名:返回
"pass",框架去找名为pass的节点。 - 通过 path_map 映射:返回
"pass",path_map 把它映射到节点celebrate_node。
三、path_map 的作用
path_map 是可选的,把路由函数返回值映射到实际节点名:
python
def router(state) -> str:
return "yes" if state["ok"] else "no"
g.add_conditional_edges(
"decide",
router,
{
"yes": "celebrate", # 返回 "yes" 时去 celebrate 节点
"no": "retry", # 返回 "no" 时去 retry 节点
},
)path_map 的好处:
- 解耦:路由函数返回业务语义("yes"/"no"),不直接耦合到节点名("celebrate"/"retry")。改名节点不用动路由函数。
- 可读性:图代码一眼看清分支。
- 可校验:编译时框架会检查 path_map 里的值是否对应已注册的节点。
如果不用 path_map,路由函数直接返回节点名也行:
python
def router(state) -> str:
return "celebrate" if state["ok"] else "retry"
g.add_conditional_edges("decide", router)新手推荐用 path_map,更清晰。
四、经典示例:基于状态字段的 if-else
python
# cond_edge_demo.py
from typing import TypedDict
from langgraph.graph import StateGraph, START, END
class State(TypedDict):
user_input: str
is_question: bool
answer: str
def classify(state: State) -> dict:
"""判断用户输入是不是问题。"""
text = state["user_input"]
is_q = text.endswith("?") or text.endswith("?")
return {"is_question": is_q}
def answer_question(state: State) -> dict:
return {"answer": f"这是个问题:{state['user_input']}"}
def echo(state: State) -> dict:
return {"answer": f"你说的是:{state['user_input']}"}
def route(state: State) -> str:
return "answer" if state["is_question"] else "echo"
g = StateGraph(State)
g.add_node("classify", classify)
g.add_node("answer", answer_question)
g.add_node("echo", echo)
g.add_edge(START, "classify")
g.add_conditional_edges(
"classify",
route,
{"answer": "answer", "echo": "echo"},
)
g.add_edge("answer", END)
g.add_edge("echo", END)
app = g.compile()
print(app.invoke({"user_input": "什么是 LangGraph?"})["answer"])
# 输出:这是个问题:什么是 LangGraph?
print(app.invoke({"user_input": "你好"})["answer"])
# 输出:你说的是:你好图结构:
mermaid
graph LR
S([START]) --> C[classify 分类]
C --> R{route 路由}
R -->|问题| A[answer 回答]
R -->|非问题| E[echo 复述]
A --> END1([END])
E --> END2([END])注意两个分支都连到 END——条件边后每个分支要单独连到 END(或下一节点)。
五、进阶示例:基于 LLM 判断走不同分支
实际场景:用户问问题时,判断是否需要先检索知识库。让 LLM 来做这个判断:
python
# llm_router.py
import os
from typing import TypedDict
from typing_extensions import Annotated
from operator import add
from dotenv import load_dotenv
from langgraph.graph import StateGraph, START, END
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage, SystemMessage
load_dotenv()
class State(TypedDict):
user_query: str
needs_retrieval: bool
docs: list[str]
answer: str
llm = ChatOpenAI(
model=os.getenv("MODEL_NAME", "gpt-4o-mini"),
api_key=os.getenv("OPENAI_API_KEY"),
base_url=os.getenv("OPENAI_BASE_URL"),
)
def decide(state: State) -> dict:
"""用 LLM 判断是否需要检索。"""
sys = "你是路由器。用户问题需要查资料才能准确回答就输出 YES,否则输出 NO。只输出 YES 或 NO。"
resp = llm.invoke([
SystemMessage(content=sys),
HumanMessage(content=state["user_query"]),
])
needs = "YES" in resp.content.upper()
return {"needs_retrieval": needs}
def route(state: State) -> str:
return "retrieve" if state["needs_retrieval"] else "generate"
def retrieve(state: State) -> dict:
"""模拟检索。"""
return {"docs": [f"关于《{state['user_query']}》的资料"]}
def generate(state: State) -> dict:
"""生成回答。"""
docs = state.get("docs", [])
if docs:
return {"answer": f"基于资料 {docs} 回答:..."}
return {"answer": f"直接回答:{state['user_query']} 是 ..."}
g = StateGraph(State)
g.add_node("decide", decide)
g.add_node("retrieve", retrieve)
g.add_node("generate", generate)
g.add_edge(START, "decide")
g.add_conditional_edges(
"decide",
route,
{"retrieve": "retrieve", "generate": "generate"},
)
g.add_edge("retrieve", "generate")
g.add_edge("generate", END)
app = g.compile()
result = app.invoke({"user_query": "LangGraph 0.2 的 interrupt 怎么用?", "docs": []})
print(result["answer"])图结构:
mermaid
graph LR
S([START]) --> D[decide LLM 判断]
D --> R{route}
R -->|需要检索| RT[retrieve 检索]
R -->|不需要| G[generate 生成]
RT --> G
G --> E([END])这是经典的"路由式 RAG"骨架。真实项目里把 retrieve 接到向量库即可。
六、条件边是构建循环的关键
普通边只能向前走,条件边可以根据状态决定"回头"。这是 ReAct 智能体的核心机制:
mermaid
graph LR
S([START]) --> A[agent 思考]
A --> C{是否需要工具?}
C -->|是| T[tools 执行]
T --> A
C -->|否| E([END])伪代码:
python
def should_continue(state) -> str:
last_msg = state["messages"][-1]
# 如果 LLM 说要调工具,就去 tools 节点
if last_msg.tool_calls:
return "tools"
return "end"
g.add_edge(START, "agent")
g.add_conditional_edges("agent", should_continue, {"tools": "tools", "end": END})
g.add_edge("tools", "agent") # tools 执行完回到 agent,形成循环LangGraph 内置的 tools_condition 就是干这事的快捷函数(见 智能体 模块)。
七、多路分发(返回列表)
路由函数可以返回字符串列表,表示"同时去多个节点"(并行扇出):
python
def route(state) -> list[str]:
return ["search_web", "search_db", "search_cache"]
g.add_conditional_edges("dispatch", route)注意:返回列表时不能用 path_map(path_map 只支持单值)。这种"扇出"更推荐用 Send 对象(见 进阶特性 模块),它能给每个目标传不同的输入。
八、返回的节点名必须存在
这是新手最常踩的坑:路由函数返回了 "retrieve",但图里没注册这个节点——运行时报错。
python
def route(state) -> str:
return "retrive" # 拼错了,少了 e
# 实际注册的是 "retrieve"
g.add_node("retrieve", retrieve)
g.add_conditional_edges("decide", route)运行时:路由函数返回 "retrive",框架找不到这个节点,报错。
防御方法:
用 path_map:path_map 里没列出的返回值会触发明确错误,便于排查。
路由函数里用常量而非裸字符串:
pythonNODE_PASS = "pass" NODE_FAIL = "fail" def route(state) -> str: return NODE_PASS if state["ok"] else NODE_FAIL加单测覆盖路由函数所有分支。
九、与 LangGraph4j 对比
| 维度 | Python | Java |
|---|---|---|
| API | add_conditional_edges(src, router, path_map) | addConditionalEdges(src, router, pathMap) |
| 路由签名 | (state) -> str | list[str] | (state) -> String | List<String> |
| path_map 类型 | dict[str, str] | Map<String,String> |
| 多路分发 | 返回 list 或用 Send | 返回 List 或用 Send |
完全对应。
十、常见踩坑
- 路由函数返回的节点名拼错:运行时找不到节点。用 path_map 或常量防御。
- 忘了 path_map 导致路由到不存在的节点:返回
"yes"但没有名为"yes"的节点。要么用 path_map 映射,要么节点直接叫这名字。 - 路由函数有副作用:路由应该纯读 state。在里面改 state、调 LLM 都不规范(虽然能调 LLM,但通常拆成独立"决策节点"更清晰)。
- 条件分支后忘记连 END:
A -> 条件 -> B 或 C,但 B/C 都没连 END——图会卡住或循环到死。 - 循环没有退出条件:
agent -> tools -> agent但条件函数永远返回 "tools"——死循环直到recursion_limit。一定要有"够了就 end"的判断。 - 多个条件边叠加:一个节点同时有普通出边和条件出边会冲突。一个节点要么一条普通出边,要么一个条件边。
- 路由返回 None:路由函数应返回字符串或列表,不要返回 None。必要时显式
return "default"。 - path_map 漏了某分支:路由可能返回三种值,path_map 只列了两种——第三种触发 KeyError。
- 依赖状态字段未设置:路由函数读
state["score"]但前面节点没设这个字段——KeyError。要么初始 state 设默认值,要么state.get("score", 0)。 - 条件边后想再分支:可以——条件边到的节点本身也能再连条件边,形成多层决策树。
十一、可视化检查
条件边在 mermaid 里会画成菱形决策节点:
python
print(app.get_graph().draw_mermaid())肉眼检查分支是否符合预期。
十二、小结
- 条件边
add_conditional_edges(source, router, path_map)让图根据状态动态选下一节点。 - 路由函数
(state) -> str只读不写,返回下一节点名(或语义标签 + path_map 映射)。 - 推荐用 path_map,业务语义和节点名解耦。
- 经典用法:if-else 分支、LLM 路由、构建循环(ReAct)。
- 多路分发可返回列表,但更推荐用
Send。 - 防御路由返回错节点名:用 path_map、常量、单测。
下一篇讲 StateGraph 状态图——把节点和边装起来的核心容器。