记录一下那天晚上的排查过程。一个带工具循环的 LangGraph 图,编译时设了 recursion_limit=25,跑到第十八轮工具调用就抛 GraphRecursionError,日志里 step 只加到 18,实际循环次数远小于 25,换了个入口节点后数字又变了。

刚开始盯着「并行分支同时推进,每个分支都单独计步」,折腾了半个多小时,排掉了。

把日志和配置对完,能解释这个现象的基本就这几处。recursion_limit 统计的是 superstep 而不是工具调用轮次,一次循环占多步。也可能是条件边返回了节点名但状态没变化,形成无进展循环。

不容易想到的是,工具调用失败后路由回原节点,失败也计一步。

上来先用 stream 观察 superstep 的增长节奏

python -c "from app.graph import build;g=build();[print(i, list(ch.keys())) for i,ch in enumerate(g.stream({'messages':[]},stream_mode='updates'))]"

随后把上限调到 60 看是否只是计步口径问题

python -c "from app.graph import build;print(build().invoke({'messages':[]},config={'recursion_limit':60}))"

然后打印每步的节点名与状态摘要

python -c "import json;print(json.dumps({'node':'tool_node','state_keys':['messages','tool_calls']},ensure_ascii=False))"

给条件边加无进展保护后再跑

python -c "print({'stop_when': 'tool_calls equals previous tool_calls'})"

改完先别撤,确认这几项。图能在有限步内到达 END,stream 输出的 superstep 数小于上限,重复输入两次得到相同的节点序列。

容易忽略的是,计步单位是 superstep,一个节点里做多次工具调用也只算一步,判断死循环要看状态有没有变化而不是看步数。这类问题最好一次改到位,反复小改反而容易把原因搅混。

作者 9823189