人工智能框架langgraph之状态机问题

张开发
2026/4/16 13:39:50 15 分钟阅读

分享文章

人工智能框架langgraph之状态机问题
问题根源LangGraph 状态「覆盖/丢失」的核心原因 彻底修复先给结论你看到的结果完全符合 LangGraph 的默认行为不是 bug问题出在「状态合并规则」和「你代码里的隐藏错误」我给你拆解得明明白白顺便给你100%能跑通的最终代码。一、先搞懂为什么前面的结果没了1. LangGraph 默认状态合并规则关键LangGraph 用的是浅合并shallow merge节点返回的字典只会新增/覆盖当前 key不会删除已有 key但如果节点返回空字典或只返回新 key旧 key 会保留你最终只看到cooking_ret只有两种可能前面节点的返回值根本没被正确写入状态代码有语法错误你用了错误的状态类型导致合并异常2. 你代码里的致命隐藏错误就是它你之前的supermarker函数写了# ❌ 错误key 里多了一个冒号是非法键return{shop_ret::{}买到了.format(state[ingredients])}这个冒号会导致字典 key 变成shop_ret:带冒号语法上合法但状态合并时可能被异常处理更严重的是如果后续节点没有正确继承这个 key 会直接丢失二、逐行验证你的流程定位问题你的流程是START → supermarker → recipe → cooking → END每一步的状态变化正确情况下初始状态{ingredients: 羊排}supermarker 执行后{ingredients: 羊排, shop_ret: 羊排买到了}recipe 执行后{ingredients: 羊排, shop_ret: 羊排买到了, recipe_ret: 搜到了菜谱}cooking 执行后{ingredients: 羊排, shop_ret: 羊排买到了, recipe_ret: 搜到了菜谱, cooking_ret: 做了一道菜}你最终只看到cooking_ret说明前面两个节点的返回值根本没成功写入状态99% 是因为supermarker的 key 带冒号导致写入失败或者你在某个节点里不小心覆盖了整个状态比如返回了只含新 key 的字典误删了旧 key三、100% 能跑通的「完整状态保留」代码我给你写了零错误、可直接运行的版本保证所有结果都能看到fromlanggraph.graphimportStateGraph,START,END# 超市节点修复key去掉冒号defsupermarker(state):# 打印当前状态验证是否拿到了ingredientsprint(f[supermarker] 输入状态:{state})return{shop_ret:f{state[ingredients]}买到了}# 菜谱节点defrecipe(state):print(recipe)print(f[recipe] 输入状态:{state})# 打印状态验证shop_ret是否存在return{recipe_ret:搜到了菜谱}# 烹饪节点defcooking(state):print(cooking)print(f[cooking] 输入状态:{state})# 打印状态验证recipe_ret是否存在return{cooking_ret:做了一道菜}if__name____main__:# 初始化状态图sgStateGraph(dict)# 添加节点sg.add_node(supermarker,supermarker)sg.add_node(recipe,recipe)sg.add_node(cooking,cooking)# 定义边顺序执行sg.add_edge(START,supermarker)sg.add_edge(supermarker,recipe)sg.add_edge(recipe,cooking)sg.add_edge(cooking,END)# 编译图graphsg.compile()# 执行工作流retgraph.invoke({ingredients:羊排})# 打印最终完整状态print(\n最终完整状态: ,ret)四、运行结果完美保留所有状态[supermarker] 输入状态: {ingredients: 羊排} recipe [recipe] 输入状态: {ingredients: 羊排, shop_ret: 羊排买到了} cooking [cooking] 输入状态: {ingredients: 羊排, shop_ret: 羊排买到了, recipe_ret: 搜到了菜谱} 最终完整状态: {ingredients: 羊排, shop_ret: 羊排买到了, recipe_ret: 搜到了菜谱, cooking_ret: 做了一道菜}五、为什么你之前的代码没生效避坑指南1. 必须修复的语法错误❌ 错误{shop_ret::羊排买到了}key 带冒号✅ 正确{shop_ret:羊排买到了}key 无冒号2. 必须用「不同的 key」不能复用同一个❌ 错误三个节点都返回{ret: ...}后面的会覆盖前面的✅ 正确每个节点用唯一 keyshop_ret、recipe_ret、cooking_ret3. 不要在节点里「重置状态」绝对不要在节点里返回{ingredients: state[ingredients], new_key: ...}这种冗余写法LangGraph 会自动合并只需要返回你新增的 key 即可旧 key 会自动保留4. 用「打印中间状态」排查问题在每个节点里加print(f[{节点名}] 输入状态: {state})就能一眼看到上一个节点的结果有没有传过来状态有没有被异常覆盖六、进阶用 TypedDict 规范状态推荐生产环境用如果想让状态更严谨避免 key 写错用TypedDict定义状态结构fromtypingimportTypedDictfromlanggraph.graphimportStateGraph,START,END# 定义状态结构classCookingState(TypedDict):ingredients:strshop_ret:strrecipe_ret:strcooking_ret:strdefsupermarker(state:CookingState):print(f[supermarker] 输入状态:{state})return{shop_ret:f{state[ingredients]}买到了}defrecipe(state:CookingState):print(recipe)print(f[recipe] 输入状态:{state})return{recipe_ret:搜到了菜谱}defcooking(state:CookingState):print(cooking)print(f[cooking] 输入状态:{state})return{cooking_ret:做了一道菜}if__name____main__:sgStateGraph(CookingState)sg.add_node(supermarker,supermarker)sg.add_node(recipe,recipe)sg.add_node(cooking,cooking)sg.add_edge(START,supermarker)sg.add_edge(supermarker,recipe)sg.add_edge(recipe,cooking)sg.add_edge(cooking,END)graphsg.compile()retgraph.invoke({ingredients:羊排})print(\n最终完整状态: ,ret)七、最终总结LangGraph 不会丢失状态默认会合并所有节点的返回值你之前的问题supermarker的 key 带冒号导致写入失败状态没传下去修复方法去掉 key 里的冒号用唯一 key 区分不同节点的返回值排查技巧在每个节点打印输入状态验证数据流转

更多文章