ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

LangGraph4j Agent循环踩坑:别让工具无限唠嗑

LangGraph4j Agent循环踩坑:别让工具无限唠嗑 文章目录1. 先把环接上1.1 三条边一个圈1.2 EdgeMappings能少写就少写2. 一段能跑的环3. 轨迹轮次和步数不是一回事4. recursionLimit上限与翻车现场5. 环上的条件路由补课5.1 返回值必须是映射表里的 key5.2 未知意图要落到明确出口5.3 Command选路顺便改个状态5.4 同形环重试与反省6. 常见错误大赏P.S. 无意间发现了一个巨牛的人工智能教程非常通俗易懂对AI感兴趣的朋友强烈推荐去看看 传送门https://blog.csdn.net/HHX_01搞 Agent 的都懂一句话图好画环难缠。你辛辛苦苦把节点搭好结果 agent 和 tools 俩节点一见面就唠上了你推我我推你谁都不肯先说行了就到这吧最后被框架一嗓子喊停。今天这篇就把这个唠嗑上限给你管明白。版本LangGraph4j 1.8.25。本篇不接真实模型用 needTool / rounds 把 ReACT 的形状先搭出来——先把形练对再谈神。1. 先把环接上多轮 Agent 的骨架就三个字来回跑。推理 → 调工具 → 拿着工具结果再推理画到图上就是一个圈。1.1 三条边一个圈边含义START → agent入口agent 条件边 → tools 或 END读状态决定下一跳tools → agent工具跑完回到推理注意一个坑条件边返回的是映射表的key不是节点 id 本人。框架拿着 key 去 mappings 里翻翻到节点 id 就跳翻到 END 就收工。你要是返回个表里没有的运行直接给你脸色看。.addConditionalEdges(agent,edge_async(afterAgent),Map.of(tools,tools,// key → 节点 idend,END))// key → 虚拟终点1.2 EdgeMappings能少写就少写嫌 Map.of 里 “tools” 写两遍费手指EdgeMappings 帮你省点腱鞘炎。importorg.bsc.langgraph4j.utils.EdgeMappings;.addConditionalEdges(agent,edge_async(afterAgent),EdgeMappings.builder().to(tools)// tools → tools.toEND(end)// end → END.build())记住互斥分支用条件边。同一个 source 上挂多条无条件边那叫扇出会并行跑效果堪比既要又要刺激得很。退出条件放哪放在条件边所读的状态键上。本例是 needTool接真实模型之后可以是结构化的 toolCalls。条件边只读状态你千万别指望它解析自然语言——让它听语气猜意图它只会原地懵圈给你看。2. 一段能跑的环直接上能跑的完整代码。agent 用 rounds 限制最多进几次 toolstools 只负责记账收不收工由 agent 改 needTool条件边再读这个键。分工明确各司其职比某些公司的职责边界清晰多了。importstaticorg.bsc.langgraph4j.StateGraph.END;importstaticorg.bsc.langgraph4j.StateGraph.START;importstaticorg.bsc.langgraph4j.action.AsyncEdgeAction.edge_async;importstaticorg.bsc.langgraph4j.action.AsyncNodeAction.node_async;importjava.util.ArrayList;importjava.util.List;importjava.util.Map;importorg.bsc.langgraph4j.CompileConfig;importorg.bsc.langgraph4j.CompiledGraph;importorg.bsc.langgraph4j.RunnableConfig;importorg.bsc.langgraph4j.StateGraph;importorg.bsc.langgraph4j.action.EdgeAction;importorg.bsc.langgraph4j.action.NodeAction;importorg.bsc.langgraph4j.state.AgentState;importorg.bsc.langgraph4j.state.Channel;importorg.bsc.langgraph4j.state.Channels;publicclassToolLoopGraph{publicstaticclassLoopStateextendsAgentState{publicstaticfinalStringNEED_TOOLneedTool;publicstaticfinalStringROUNDSrounds;publicstaticfinalStringLOGSlogs;publicstaticfinalMapSCHEMAMap.of(NEED_TOOL,Channels.base(()-false),ROUNDS,Channels.base(()-0),LOGS,Channels.appenderWithDuplicate(ArrayList::new));publicLoopState(MapinitData){super(initData);}}publicstaticvoidmain(String[]args)throwsException{NodeActionagentstate-{introundsstate.value(LoopState.ROUNDS).orElse(0);booleanneedstate.value(LoopState.NEED_TOOL).orElse(false);// rounds 到 2 就收工否则继续要工具if(!need||rounds2){returnMap.of(LoopState.NEED_TOOL,false,LoopState.LOGS,List.of(agent:end));}returnMap.of(LoopState.ROUNDS,rounds1,LoopState.NEED_TOOL,true,LoopState.LOGS,List.of(agent:call-tools));};NodeActiontoolsstate-Map.of(LoopState.LOGS,List.of(tools:done));EdgeActionafterAgentstate-Boolean.TRUE.equals(state.value(LoopState.NEED_TOOL).orElse(false))?tools:end;CompiledGraphcompilednewStateGraph(LoopState.SCHEMA,LoopState::new).addNode(agent,node_async(agent)).addNode(tools,node_async(tools)).addEdge(START,agent).addConditionalEdges(agent,edge_async(afterAgent),Map.of(tools,tools,end,END)).addEdge(tools,agent).compile(CompileConfig.builder().recursionLimit(25).build());varconfigRunnableConfig.builder().threadId(loop-1).build();compiled.stream(Map.of(LoopState.NEED_TOOL,true,LoopState.ROUNDS,0),config).forEachAsync(out-System.out.println(out.node() → out.state().data())).join();}}接模型的时候拓扑不用改agent 写出 toolCalls 或者布尔 needTool条件边读这个字段tools 执行调用把结果写回状态。就跟换演员不换剧本一样换个角儿照样演。3. 轨迹轮次和步数不是一回事很多人一上来就把轮次当步数调参调到怀疑人生。先给你算笔账。输入needTooltruerounds0。顺序节点状态要点下一跳1agentrounds1needTooltrue→ tools2toolslogs 追加 tools:done→ agent3agentrounds2needTooltrue→ tools4tools再记一笔→ agent5agentrounds 2needToolfalse→ END轨迹agent → tools → agent → tools → agent → END。看出门道没你嘴上说最多 2 轮代码里实际跑了 5 次业务节点。这跟相亲一个道理说好就见两面结果介绍人、男方、女方、连带七大姑八大姨前后碰了五回面。数学老师看了都要沉默原来程序员是这么算账的。概念本例谁维护业务轮次2rounds 加到 2状态字段进业务节点次数53×agent 2×tools图推进recursionLimit默认 25限制运行迭代CompileConfig粗算N 轮工具 ≈ 2N1 次进业务节点每轮 agenttools最后多一次 agent 收工。所以最多 8 轮工具千万别写成 recursionLimit(8)——8 轮工具实际要 17 步你给人留 8 步等于让人穿着拖鞋跑马拉松跑一半直接抽筋。4. recursionLimit上限与翻车现场默认 25听着挺大方直线图你基本碰不到一旦有环2N1 一算分分钟见底。CompileConfig.builder().recursionLimit(25)// 必须 0默认就是 25.build();迭代超限运行直接以错误结束报错长这样——翻译成人话就是你俩唠太嗨了裁判吹哨了。java.lang.IllegalStateException:Maximumnumber of iterations(N)reached!invoke 和 stream 都会撞上。把第 2 节那张图的 recursionLimit 改成 3同一个输入直接触顶CompiledGraphtightnewStateGraph(LoopState.SCHEMA,LoopState::new).addNode(agent,node_async(agent)).addNode(tools,node_async(tools)).addEdge(START,agent).addConditionalEdges(agent,edge_async(afterAgent),Map.of(tools,tools,end,END)).addEdge(tools,agent).compile(CompileConfig.builder().recursionLimit(3).build());try{tight.invoke(Map.of(LoopState.NEED_TOOL,true,LoopState.ROUNDS,0));}catch(Exceptione){// 根因IllegalStateException: Maximum number of iterations (3) reached!Throwableroote;while(root.getCause()!null){rootroot.getCause();}System.out.println(root.getMessage());}超限之后别急着调大先按顺序查三件事**1. 映射里有没有 “end” → END。**没有agent 想回家都找不到门只能一直转。**2. 退出条件会不会永远为真。**needTool / toolCalls 从没被清掉又没有 rounds 上限。这跟减肥一样口号喊得震天响明天开始说了八百遍跑断腿也瘦不下来。**3. recursionLimit 是不是小于实际需要的步数。**最简单2N1 拿计算器按一下就知道了。业务最多几轮用状态计数recursionLimit 做图级上限。调大之前先用 stream 打印 out.node()对着轨迹看它到底停在哪。别闭着眼调参你又不是在抽盲盒。5. 环上的条件路由补课5.1 返回值必须是映射表里的 keyafterAgent 返回 “tools” / “end”表里就必须有这两项。返回了个表里没有的运行直接失败。就好比你点菜报了个随便服务员只能端上来一份您走好。key 对不上翻车是必然的。5.2 未知意图要落到明确出口条件边要是还兼职意图分流未知标签就映射到 fallback 或 END千万别映射到带副作用的 tools。客人说随便来点你给人家上了十道菜还顺带把账结了这店还能开下去吗5.3 Command选路顺便改个状态下一跳和改键想一起做有 Command 等着你。importorg.bsc.langgraph4j.action.AsyncCommandAction;importorg.bsc.langgraph4j.action.Command;importorg.bsc.langgraph4j.action.CommandAction;CommandActionafterAgentCmd(state,config)-{booleanneedBoolean.TRUE.equals(state.value(LoopState.NEED_TOOL).orElse(false));if(need){returnnewCommand(tools);}returnnewCommand(end,Map.of(LoopState.NEED_TOOL,false));};// graph.addConditionalEdges(agent,// AsyncCommandAction.command_async(afterAgentCmd),// Map.of(tools, tools, end, END));不过讲真节点返回 partial Map、条件边只负责选路才是更常见的拆法。非要一边选路一边改状态不是不行就是有点既要又要代码评审的时候容易被追着问三遍。5.4 同形环重试与反省把节点换成 generate 和 critiquecritique 的条件边回到 generate或去 END退出条件可以是 score 达标或者 retries 用尽。边的接法跟本章一模一样。同一个模子换套演员就能演自我反省这种高级戏码。6. 常见错误大赏现象处理本想二选一两个分支都跑了互斥改成 addConditionalEdges环空转直到触顶查退出条件与 “end” → ENDstream 看停在哪Maximum number of iterations退出边是否生效或按 2N1 加大 recursionLimit条件边运行失败映射漏 key或返回值与 key 不一致addNode / 条件边类型不对同步逻辑用 node_async / edge_asyncrecursionLimit(8) 当 8 轮工具按节点步数估一轮大约 agenttools节点里 while 调模型改成图级环 条件边退出最后一条必须单拎出来说在节点里写 while 循环调模型等于在房间里原地转圈转吐了也没出过门。图级环才是正儿八经出门逛街。先把无模型的最小环 stream 跑通确认轨迹和步数再换成真实的 agent / tools 节点——先让空车跑一圈再上货这个道理开过车的人都懂。P.S. 无意间发现了一个巨牛的人工智能教程非常通俗易懂对AI感兴趣的朋友强烈推荐去看看传送门https://blog.csdn.net/HHX_01
返回列表