网易首页 > 网易号 > 正文 申请入驻

LangGraph.js硬编码25步递归上限

0
分享至

你的研究型Agent已经找了很久的答案。模型不断要求"再查一次",工具不断返回看似有用的东西,然后整个运行直接抛错。没有答案,没有中间笔记,只有一个 GraphRecursionError,以及一句提示你去调高某个配置键的消息。


在 LangGraph.js 里,这个限制数的是超级步(super-step)。它和 token 数无关,也和实际耗时无关。如果你不设置它,默认值就是 25。在 @langchain/langgraph 1.5.2 的当前源码里,这个数字是一个硬编码常量:const DEFAULT_RECURSION_LIMIT = 25;。官方 graph API 文档的说法一致。

超级步是什么,25 为什么很快用完

文档把递归限制定义为:图在一次执行中最多能跑多少个超级步。一个超级步就是对图的一次迭代——并行运行的节点共享一个超级步,先后串行运行的节点各占一个。

一个 ReAct 风格的 Agent 会先跑 agent,再跑 tools,再回到 agent。每一次工具往返要花掉两个超级步。所以 25 大约只够十几次工具调用,还得扣掉图里其他环节的开销。一个喜欢"再核对一个来源"的模型,很快就撞墙。

别信博客文章,包括这篇,去查你自己装的版本。Python 和 JavaScript 已经出现分歧:Python 包 main 分支的 _config.py 读取环境变量 LANGGRAPH_DEFAULT_RECURSION_LIMIT,回退值是 10007,而 JS 核心硬编码为 25。用这条命令翻你自己的 node_modules:

  • grep -rn "DEFAULT_RECURSION_LIMIT\|DEFAULT_LOOP_LIMIT" node_modules/@langchain/langgraph/dist | head

搭一个会撞墙的循环

先装包和它的 peer 依赖:npm i @langchain/langgraph @langchain/core。下面这个 agent 节点是一个"永远想再搜一次"的模型替身。真实图里你会在这里调用 model.bindTools([...]),并根据回复里有没有工具调用做路由。这个桩让示例不需要 API key 也能跑,图的形状是一样的。

示例用 Annotation.Root 定义了 question、notes、stepsUsed、route、answer 几个字段,其中 notes 带 reducer,会把结果拼接起来。naiveAgent 永远返回 route: "tools",search 节点每次往 notes 里追加一条"search result N"。图里只有 START→agent→tools→agent 这几条边,没有出口边,所以运行只会在步数耗尽时结束。

在 Pregel 循环源码里,每个 tick 检查 this.step > this.stop,一旦成立就把状态置为 "out_of_steps"。运行器把这个状态转成 GraphRecursionError,错误码是 GRAPH_RECURSION_LIMIT,消息以"Recursion limit of 25 reached without hitting a stop condition."开头。

用 try/catch 捕获它是文档里的被动方案。它能让进程活着,但不会给用户答案,工具收集到的每一条笔记都从返回值里消失了。

每次 invoke 都设 recursionLimit

recursionLimit 是你传给 invoke 或 stream 的 config 的顶层键,不放在 configurable 里面。写法是:await naive.invoke({ question: "Why is checkout slow?" }, { recursionLimit: 60 });

错误页建议对确实复杂的图调高它,并给出了 recursionLimit: 1000 的例子。对一个没有出口的循环来说,调高只是把墙往外挪,每次失败还要多烧几次模型调用。数字应该按任务来定:快速查询给小限制,长研究任务给大限制,每个调用点都写清楚自己是哪种。

自己数步数,先路由到总结

主动的修法是让图看得见自己的预算。节点能拿到运行配置,文档 PR 指出 config.recursionLimit 在那里总是存在的,默认 25(docs PR #1166)。在 state 里维护一个步数计数器,当"再来一轮往返加一次总结"已经放不下时,路由到一个写出当前最优答案的节点。

示例里设了 MARGIN = 3。agent 节点先算 used = (state.stepsUsed ?? 0) + 1,再取 limit = config.recursionLimit ?? 25。判断条件是 used + 2 + 1 + MARGIN 是否还小于 limit——多一次搜索花 2 个超级步(tools、agent),总结花 1 个,MARGIN 覆盖记账开销。放得下就走 tools,放不下就走 summarize,summarize 连到 END。图用 MemorySaver 做 checkpointer 编译。

接真实模型时,agent 会有第三条路由:回复里没有工具调用就说明模型做完了,直接去 END。预算检查只决定"模型没做完而步数快没了"时怎么办。summarize 节点里通常会对着笔记做最后一次模型调用,示例里用字符串保持离线可跑。

这个余量是故意的。输入步附近的精确记账属于循环内部实现,所以宁可差几步到限制,也不要去卡最后一步。MARGIN = 3 是起点,不是实测值。

带 checkpoint 的线程:预算每次调用都重置

为什么用 state 里的计数器,而不是文档建议读的 config.metadata.langgraph_step?因为限制在线程上的计算方式不一样。循环源码里,每次运行都从保存的 checkpoint 的步数开始:const step = (checkpointMetadata.step ?? 0) + 1; const stop = step + (config.recursionLimit ?? DEFAULT_LOOP_LIMIT) + 1;

这两行推出两件事。第一,限制是按每次 invoke 算的,不是按线程算的。同一个 thread_id 上的每次调用都从线程当前所在位置起算一份新预算。一个捕获 GraphRecursionError 后在同一线程上再次 invoke 的重试包装器,等于每次都给循环中的 Agent 发一份全新预算。要在整段对话上设上限,得自己写在代码或 state 里。

第二,langgraph_step 是线程的绝对步数,来自同一个循环计数器。对话第二轮开始时,它已经远超第一轮结束的位置。拿 langgraph_step 去判断"到 recursionLimit 的 80% 就停",在新线程上能用,到第二轮会触发得太早。在输入里重置的计数器只度量当前这次运行:调用时传 { question: ..., stepsUsed: 0 },stepsUsed 没有 reducer,输入里的 0 会覆盖 checkpoint 里上一轮留下的值;notes 有 reducer,前几轮的笔记会留在线程上,这通常正是研究型 Agent 想要的行为。

下次部署前,把每个 invoke 和 stream 调用点都 grep 一遍,看有没有漏掉 recursionLimit,再检查有没有哪个重试包装器在 GraphRecursionError 之后对同一个 thread_id 重新 invoke。

特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。

Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.

相关推荐
热点推荐
当年许家印对手下有多疯狂?

当年许家印对手下有多疯狂?

流苏晚晴
2026-10-20 12:09:52
有一次,管虎很晚才下班回家,刚一进家门,就看到家里到处脏衣服

有一次,管虎很晚才下班回家,刚一进家门,就看到家里到处脏衣服

共情故事会
2026-10-23 15:26:04
随着中国女足3-2美国冲上第1,U17世界杯排名乱了,16强对阵如下

随着中国女足3-2美国冲上第1,U17世界杯排名乱了,16强对阵如下

侃球熊弟
2026-10-26 04:47:13
“早餐已经输在起跑线上了”,学生碳水伙食,家长:不到10g蛋白

“早餐已经输在起跑线上了”,学生碳水伙食,家长:不到10g蛋白

熙熙说教
2026-10-26 13:57:35
69岁张瑜将上亿资产,上海江景大平层、北京四合院全都转给外甥,一句我蛮可怜的,格外戳人心

69岁张瑜将上亿资产,上海江景大平层、北京四合院全都转给外甥,一句我蛮可怜的,格外戳人心

背包旅行
2026-09-07 14:55:30
蓝盈莹乌镇戏剧节被偶遇,和男友同游,路遇聋哑人求助暖心帮忙

蓝盈莹乌镇戏剧节被偶遇,和男友同游,路遇聋哑人求助暖心帮忙

萧狡科普解说
2026-10-26 16:47:37
提升性生活时长,这些食物要多吃

提升性生活时长,这些食物要多吃

精彩分享快乐
2026-10-25 21:00:08
欧盟刚拒绝给270亿,泽连斯基就翻脸,不给钱就让乌克兰人都回国

欧盟刚拒绝给270亿,泽连斯基就翻脸,不给钱就让乌克兰人都回国

深层奥秘
2026-10-25 06:35:47
张本美和为何惨遭横扫?赛后采访陈熠直指赢球原因,句句实在话!

张本美和为何惨遭横扫?赛后采访陈熠直指赢球原因,句句实在话!

田先生篮球
2026-10-25 22:22:09
断腿重伤!亚历山大哭了!

断腿重伤!亚历山大哭了!

篮球大图
2026-10-26 11:27:39
全场灾难!巴萨头号核心彻底拉胯!一己之力葬送德比

全场灾难!巴萨头号核心彻底拉胯!一己之力葬送德比

澜归序
2026-10-26 08:25:23
医生发现:每天早起后先排便的人,用不了半年,身体或迎来几改变

医生发现:每天早起后先排便的人,用不了半年,身体或迎来几改变

侯医生谈健康
2026-09-21 07:20:15
46岁董洁1年5部戏加直播养1大家子,憔悴得让人心疼

46岁董洁1年5部戏加直播养1大家子,憔悴得让人心疼

南万说娱26
2026-08-28 09:40:39
克内克特交易告吹真因!湖人与雄鹿没谈拢:拒绝满足选秀权补偿条件

克内克特交易告吹真因!湖人与雄鹿没谈拢:拒绝满足选秀权补偿条件

罗说NBA
2026-10-26 05:21:34
谷爱凌穿旗袍逛苏博、拙政园,感叹“苏州园林太美了”:十年前在苏州买了第一件旗袍,这次如愿再添新旗袍

谷爱凌穿旗袍逛苏博、拙政园,感叹“苏州园林太美了”:十年前在苏州买了第一件旗袍,这次如愿再添新旗袍

极目新闻
2026-10-24 21:09:22
加勒万河谷:9万边防警察已整装待命,随时给中国士兵一个教训!

加勒万河谷:9万边防警察已整装待命,随时给中国士兵一个教训!

蒋南强读历史
2026-10-22 08:10:08
同事造谣我在食堂打饭插队,我连续6天点外卖,第7天主管慌忙跑来敲桌子:几十号员工全没饭吃了,算我求你

同事造谣我在食堂打饭插队,我连续6天点外卖,第7天主管慌忙跑来敲桌子:几十号员工全没饭吃了,算我求你

晓艾故事汇
2026-09-23 10:36:15
终于有人敢说实话,养老金绝不允许走向强者通吃

终于有人敢说实话,养老金绝不允许走向强者通吃

王二哥老搞笑
2026-10-26 14:09:42
纯钛杯、纯钛餐具的第一批受害者出现了…

纯钛杯、纯钛餐具的第一批受害者出现了…

新住家居
2026-10-24 18:06:53
黑色透视连衣裙+超厚恨天高!伊藤新造型 日本网友:优秀成熟女性

黑色透视连衣裙+超厚恨天高!伊藤新造型 日本网友:优秀成熟女性

劲爆体坛
2026-10-26 08:19:05
2026-10-26 20:00:49
固件更新中
固件更新中
有态度网友ytd
184文章数 83关注度
往期回顾 全部

科技要闻

K3.1 Pro发布才两天,Kimi道歉补额度!

头条要闻

外媒称"中印边境发生冲突印度指责中国军队" 中方回应

头条要闻

外媒称"中印边境发生冲突印度指责中国军队" 中方回应

体育要闻

13年后重回诺坎普,穆里尼奥又滑跪了

娱乐要闻

胡歌杨幂刘诗诗袁弘陈龙结伴去埃及

财经要闻

卢锋:全球失衡背后 大国博弈与多边治理

汽车要闻

云辇-M上车 第二代海豹07让驾驶者也能爱上的家用车

态度原创

艺术
亲子
家居
数码
公开课

艺术要闻

中国最高(在建)摩天轮与全球最深潜水馆综合体,面纱揭开!

亲子要闻

大家觉得潮州潮韩新时代月子中心体验怎么样?

家居要闻

2026建博会(广州) 公装联探展交流活动

数码要闻

中国家电供应链加速“出海建厂” TCL、追觅等企业跟进

公开课

李玫瑾:为什么性格比能力更重要?

无障碍浏览 进入关怀版