先记住这个答案
在 LangGraph 中,add_conditional_edges 把一个节点与一个路由函数绑定,节点执行后调用该函数,函数接收当前 state,返回下一步要去的节点名或 END。默认返回值就是节点名;也可以提供第三个参数 path_map,把返回值映射到实际节点名。条件边只做决策,状态更新应在节点内完成,避免在路由函数里改 state。
- 路由函数只读状态,返回下一节点名或 END
- 返回值可被字典映射,实现布尔到节点名转换
- 一次只能选一种路由机制,勿和静态边混用
条件边如何被触发与路由返回值解析
条件边通过 graph.add_conditional_edges(source_node, router_fn, path_map?) 注册。当 source_node 执行完成并提交状态更新后,框架进入下一个 super-step 前会调用 router_fn,传入当前状态(包含刚写入的更新)。router_fn 只是普通函数,不负责状态变更,它的返回值决定后续激活哪些节点。
默认情况下,返回值必须是一个存在的节点名或节点名列表,或者 END;如果传了 path_map,则返回值作为键去 path_map 查表,查到的值才是目标节点名(也支持列表)。LangGraph 不允许从同一源节点混合使用固定边和条件边,否则固定边无条件执行,加上条件边分叉会让执行路径不可控。
客服工单路由:按意图选择处理节点
假设一个工单分类工作流:触发节点 classify 用 LLM 提取用户意图,输出 intent 字段为 refund、technical 或 other。我们从 classify 连条件边:router_fn(state) 返回 state.intent,再提供 path_map = { "refund": "refund_handler", "technical": "tech_support", "other": "human_agent" }。这样 classify 后下一节点按字段动态选择。
这个模式的好处是路由逻辑薄而清晰,不污染节点业务。如果某天新增 billing 意图,只需在 path_map 加一项,不改 classify 节点本身。同时由于 path_map 与状态分离,测试路由函数时可以构造一个假 state 直接调用,无需驱动整图执行。
条件边易出错点与可操作判断
最容易失效的是路由函数返回了不存在的节点名,LangGraph 会抛出异常提示节点未定义。另一个坑是使用 path_map 时路由函数不能返回列表,因为列表不可哈希无法作为字典键;此时应返回单个值,由 path_map 映射到目标节点(或其列表)。此外,路由函数若内部捕获异常并静默返回默认节点,往往掩盖上游状态异常,应让错误直接暴露。
当路由逻辑依赖多个字段组合时,条件边仍可用,但推荐把复杂决策提炼成独立函数并写单元测试。还要注意:条件边与 Command 都能动态跳转,若节点需要同时更新状态并跳转,优先用返回 Command,避免在路由函数中变相改状态。过度使用条件边会降低图的可读性,应在节点内用决策表代替散落的条件分支。
容易答错的地方
- 路由函数内修改状态
- 有人试图在路由函数里直接给 state 字段赋值或调用状态更新 API,但条件边函数不支持状态写入,只能读取。正确做法是先让节点更新状态,再让条件边基于更新后的状态做路由,需要联合修改时用
Command。 - 强制所有分支都走条件边
- 条件边每次执行都要调用函数,比固定边多一层跳转。对绝对固定的前后关系用
add_edge;只有存在真实分叉时才用条件边。混用两种机制会让同一节点可能触发多条出边,执行结果不确定。
面试官还会怎么问?
条件边的路由函数能否返回多个节点名?
可以,返回字符串列表会让列表内节点并行执行,属于并行扇出。例如分割任务后同时进入多个处理节点。但注意这些节点会竞争状态写入,需要配合 reducer 合并更新。
条件边与 Command 的跳转有什么根本差异?
条件边是纯路由函数,返回值仅决定下一步,状态更新必须由前置节点完成;Command 允许节点在返回时既写状态又指定下一跳,适合需要立即更新路由上下文的场景。
如果路由函数抛异常,图的执行会怎样?
异常会向上传播,当前图执行被打断,不会有后续节点运行。若配置了节点级重试策略只对节点生效,不会重试路由函数。建议在路由函数内避免 IO 和易错逻辑,保持纯函数。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。