以下是实现您要求的 LangGraph 案例的完整代码LangGraph 案例通过 Config 传入用户名 → 工具1存入 State → 工具2读取 State 并返回答案importosfromtypingimportTypedDict,Literalfromdotenvimportload_dotenvfromlangchain_openaiimportChatOpenAIfromlanggraph.graphimportStateGraph,ENDfromlangchain_core.runnablesimportRunnableConfigfromlangchain_core.messagesimportHumanMessage,AIMessage,ToolMessagefromlanggraph.prebuiltimportToolNodefromlangchain_core.toolsimporttool load_dotenv()# 1. 初始化模型阿里云百炼 QwenllmChatOpenAI(modelqwen-plus,temperature0.7,api_keyos.getenv(DASHSCOPE_API_KEY),base_urlos.getenv(DASHSCOPE_BASE_URL,https://dashscope.aliyuncs.com/compatible-mode/v1),model_kwargs{extra_body:{enable_thinking:False}})# 2. 定义 State动态状态classAgentState(TypedDict):messages:list# 对话历史用于与模型交互username:str# 存储从 config 中提取的用户名# 3. 定义工具 tooldefsave_username_to_state(username:str)-str: 工具1将用户名保存到状态中。 注意工具本身无法直接修改 State这里只返回结果 真正的 State 更新由节点函数完成。 returnf用户名 {username} 已接收。tooldefget_username_from_state()-str: 工具2从状态中获取用户名并生成问候语。 实际读取 State 在节点函数中完成工具仅返回一个占位符。 return等待从状态中读取用户名...# 工具列表tools[save_username_to_state,get_username_from_state]tool_nodeToolNode(tools)# 将工具绑定到 LLMllm_with_toolsllm.bind_tools(tools)# 4. 节点函数 defagent_node(state:AgentState,config:RunnableConfig)-AgentState: Agent 节点调用 LLM决定使用哪个工具。 同时从 config 中提取 username 并存入 state。 # 从 configurable 中获取 username静态配置user_configconfig.get(configurable,{})username_from_configuser_config.get(username,anonymous)# 将 username 存入 state覆盖或初始化# 注意这里直接返回更新LangGraph 会合并状态updated_state{username:username_from_config}# 调用 LLM传入历史消息messagesstate.get(messages,[])responsellm_with_tools.invoke(messages)updated_state[messages][response]returnupdated_statedefafter_tool_node(state:AgentState)-AgentState: 工具执行后的处理节点根据工具调用结果更新状态。 如果调用的是 save_username_to_state无需额外操作已在 agent_node 中存过。 如果调用的是 get_username_from_state则从当前 state 中读取 username 并生成最终回答。 last_messagestate[messages][-1]# 检查最后一条消息是否是 ToolMessageifisinstance(last_message,ToolMessage):# 如果工具是 get_username_from_state我们需要用真实的 username 替换其内容ifget_username_from_stateinlast_message.name:usernamestate.get(username,未知用户)# 构造新的 AIMessage 作为最终回复final_answerf您好{username}欢迎使用我们的系统。new_ai_msgAIMessage(contentfinal_answer)return{messages:[new_ai_msg]}return{}defshould_continue(state:AgentState)-Literal[tools,after_tool,__end__]:条件路由决定下一步last_messagestate[messages][-1]ifhasattr(last_message,tool_calls)andlast_message.tool_calls:returntools# 需要调用工具# 如果最后一条消息是 ToolMessage 且内容来自 get_username_from_state则去 after_toolifisinstance(last_message,ToolMessage)andget_username_from_stateinlast_message.name:returnafter_toolreturn__end__# 5. 构建图 builderStateGraph(AgentState)# 添加节点builder.add_node(agent,agent_node)builder.add_node(tools,tool_node)builder.add_node(after_tool,after_tool_node)# 设置入口builder.set_entry_point(agent)# 条件边agent → tools / after_tool / ENDbuilder.add_conditional_edges(agent,should_continue,{tools:tools,after_tool:after_tool,__end__:END})# tools 节点执行后回到 agent以便 LLM 处理工具结果builder.add_edge(tools,agent)# after_tool 节点结束后结束builder.add_edge(after_tool,END)# 编译图graphbuilder.compile()# 6. 测试运行 if__name____main__:# 通过 config 传入 usernameuser_config{configurable:{username:张三}}# 用户输入触发流程需要让 LLM 依次调用两个工具initial_state{messages:[HumanMessage(content请先保存我的用户名然后问候我。)],username:# 初始为空}print( 启动 LangGraph 工具流程演示)print(*60)final_stategraph.invoke(initial_state,configuser_config)print(\n最终状态:)print(f username:{final_state.get(username)})print(f 最后一条消息:{final_state[messages][-1].content})代码核心流程说明用户输入请先保存我的用户名然后问候我。Agent 节点agent_node从config[configurable][username]读取张三。将username写入 State{username: 张三}。调用 LLM绑定工具LLM 会决定调用save_username_to_state工具。工具节点tools执行save_username_to_state返回ToolMessage内容用户名 张三 已接收。。回到 Agent 节点LLM 看到工具结果后会判断下一步需要调用get_username_from_state工具。再次经过工具节点执行get_username_from_state返回一个占位ToolMessage。条件路由检测到该ToolMessage的名称包含get_username_from_state跳转到after_tool节点。After_tool 节点从 State 中读取username张三生成最终问候语您好 张三欢迎使用我们的系统。并作为AIMessage返回。结束。关键设计点状态更新agent_node返回{username: username_from_config}LangGraph 会自动与原有 State 合并覆盖username字段。工具无法直接修改 State工具只负责返回结果真正的状态变更由节点函数完成。条件路由根据最后一条消息的类型和内容决定走向实现了“工具1 → 工具2 → 生成答案”的序列。你可以直接运行此代码确保.env中配置了DASHSCOPE_API_KEY和DASHSCOPE_BASE_URL观察控制台输出和最终结果。✅ 修正后的完整代码Config → 工具1存State → 工具2读State → 生成最终答案importosfromtypingimportTypedDict,Literalfromdotenvimportload_dotenvfromlangchain_openaiimportChatOpenAIfromlanggraph.graphimportStateGraph,ENDfromlangchain_core.runnablesimportRunnableConfigfromlangchain_core.messagesimportHumanMessage,AIMessage,ToolMessagefromlanggraph.prebuiltimportToolNodefromlangchain_core.toolsimporttool load_dotenv()# 1. 初始化模型 llmChatOpenAI(modelqwen-plus,temperature0.7,api_keyos.getenv(DASHSCOPE_API_KEY),base_urlos.getenv(DASHSCOPE_BASE_URL,https://dashscope.aliyuncs.com/compatible-mode/v1),model_kwargs{extra_body:{enable_thinking:False}})# 2. 定义 State classAgentState(TypedDict):messages:list# 对话历史使用 add_messages reducer确保追加username:str# 存储从 config 中提取的用户名# 3. 定义工具 tooldefsave_username_to_state(username:str)-str:工具1将用户名保存到状态中实际由节点函数完成存储工具仅确认returnf用户名 {username} 已接收。tooldefget_username_from_state()-str:工具2从状态中获取用户名占位符实际由 after_tool 节点读取 Statereturn等待从状态中读取用户名...tools[save_username_to_state,get_username_from_state]tool_nodeToolNode(tools)llm_with_toolsllm.bind_tools(tools)# 4. 节点函数 defagent_node(state:AgentState,config:RunnableConfig)-dict: Agent 节点从 config 中提取 username 存入 state并调用 LLM 决定工具调用。 注意这里使用字典返回LangGraph 会与现有 state 合并。 # 提取静态配置中的用户名user_configconfig.get(configurable,{})username_from_configuser_config.get(username,anonymous)# 更新 state将用户名写入覆盖updates{username:username_from_config}# 调用 LLM 决定下一步messagesstate.get(messages,[])responsellm_with_tools.invoke(messages)updates[messages][response]# 追加新消息需要 reducer 支持returnupdatesdefafter_tool_node(state:AgentState)-dict: 工具2执行后的处理节点从 state 中读取 username生成最终回答。 usernamestate.get(username,未知用户)final_answerf您好{username}欢迎使用我们的系统。return{messages:[AIMessage(contentfinal_answer)]}# 5. 条件路由函数 defshould_continue(state:AgentState)-Literal[tools,__end__]:agent 节点后的条件路由是否需要调用工具last_messagestate[messages][-1]ifhasattr(last_message,tool_calls)andlast_message.tool_calls:returntoolsreturn__end__defroute_after_tools(state:AgentState)-Literal[agent,after_tool]:tools 节点后的条件路由根据执行的工具名称决定下一步last_messagestate[messages][-1]ifisinstance(last_message,ToolMessage)andget_username_from_stateinlast_message.name:# 第二个工具执行完毕直接去生成最终答案returnafter_tool# 第一个工具执行完毕继续回到 agent 让 LLM 决定下一步returnagent# 6. 构建图 builderStateGraph(AgentState)# 添加节点builder.add_node(agent,agent_node)builder.add_node(tools,tool_node)builder.add_node(after_tool,after_tool_node)# 设置入口builder.set_entry_point(agent)# agent → 条件边 → tools 或 ENDbuilder.add_conditional_edges(agent,should_continue,{tools:tools,__end__:END})# tools → 条件边 → agent 或 after_toolbuilder.add_conditional_edges(tools,route_after_tools,{agent:agent,after_tool:after_tool})# after_tool → ENDbuilder.add_edge(after_tool,END)# 编译图注意需要为 messages 字段指定 add_messages reducer否则会覆盖fromlanggraph.graphimportadd_messagesfromtypingimportAnnotatedclassFixedAgentState(TypedDict):messages:Annotated[list,add_messages]# 关键追加消息而非覆盖username:str# 重新使用修正后的 State 定义builderStateGraph(FixedAgentState)builder.add_node(agent,agent_node)builder.add_node(tools,tool_node)builder.add_node(after_tool,after_tool_node)builder.set_entry_point(agent)builder.add_conditional_edges(agent,should_continue,{tools:tools,__end__:END})builder.add_conditional_edges(tools,route_after_tools,{agent:agent,after_tool:after_tool})builder.add_edge(after_tool,END)graphbuilder.compile()# 7. 测试运行 if__name____main__:user_config{configurable:{username:张三}}initial_state{messages:[HumanMessage(content请先保存我的用户名然后问候我。)],username:}print( 启动修正后的 LangGraph 工具流程)print(*60)final_stategraph.invoke(initial_state,configuser_config)print(\n最终状态:)print(f username:{final_state.get(username)})print(f 最后一条消息:{final_state[messages][-1].content}) 关键修正点说明问题修正方案messages字段被覆盖导致历史丢失使用Annotated[list, add_messages]作为 reducer确保每次返回的{messages: [new_msg]}是追加而不是覆盖tools节点后无条件回到agent导致第二个工具后无法进入after_tool增加route_after_tools条件边根据最后执行的工具名称决定下一步- 如果是get_username_from_state→ 去after_tool- 否则save_username_to_state→ 回agentafter_tool节点从未被调用修正路由后第二个工具执行完会正确进入after_tool_node从state[username]读取用户名并生成问候语 修正后的执行流程用户输入 (HumanMessage) ↓ agent 节点 ├─ 从 config 读取 username → 写入 state.username └─ LLM 决定调用 save_username_to_state ↓ tools 节点 └─ 执行 save_username_to_state → 返回 ToolMessage ↓ route_after_tools → 返回 agent因为不是 get_username 工具 ↓ agent 节点第二次 └─ LLM 看到工具结果决定调用 get_username_from_state ↓ tools 节点第二次 └─ 执行 get_username_from_state → 返回占位 ToolMessage ↓ route_after_tools → 检测到工具名包含 get_username_from_state → 返回 after_tool ↓ after_tool 节点 └─ 从 state.username 读取 张三生成 AIMessage(您好 张三欢迎使用我们的系统。) ↓ END✅ 运行预期输出 启动修正后的 LangGraph 工具流程 最终状态: username: 张三 最后一条消息: 您好 张三欢迎使用我们的系统。现在代码完全符合原案例要求Config 传入 username → 工具1 保存到 State → 工具2 读取 State 并生成个性化答案。你可以直接复制运行确保.env中配置了阿里云百炼的 API Key 和 Base URL。LangGraph 中的边Edges详解在 LangGraph 中边定义了节点Node之间的连接关系决定了工作流的执行顺序。LangGraph 提供了两种边条件边和普通边。下面结合你提供的代码片段逐一解释。一、add_conditional_edges条件边builder.add_conditional_edges(agent,should_continue,{tools:tools,__end__:END})作用从源节点agent出发根据路由函数should_continue的返回值动态选择下一个要执行的节点。参数说明参数类型含义第一个参数str源节点的名称例如agent第二个参数Callable路由函数接收当前状态state返回一个字符串通常是节点的名称或__end__第三个参数dict映射表键是路由函数可能返回的值值是对应的目标节点名称或END在你的代码中源节点agent路由函数should_continue(state) - Literal[tools, __end__]如果模型回复包含tool_calls返回tools否则返回__end__映射表tools→ 下一步去tools节点__end__→ 下一步结束执行END是 LangGraph 内置的终止节点第二个条件边builder.add_conditional_edges(tools,route_after_tools,{agent:agent,after_tool:after_tool})源节点tools路由函数route_after_tools(state) - Literal[agent, after_tool]如果最后一条消息是ToolMessage且来自get_username_from_state工具返回after_tool否则返回agent映射表agent→ 回到agent节点继续处理after_tool→ 进入after_tool节点生成最终答案二、add_edge普通边 / 无条件边builder.add_edge(after_tool,END)作用从一个节点无条件地连接到另一个节点。执行完源节点后立即跳转到目标节点没有分支判断。在你的代码中源节点after_tool目标节点END结束含义当after_tool节点执行完毕后工作流直接结束。三、条件边 vs 普通边对比特性add_edgeadd_conditional_edges分支无分支固定走向有分支根据状态动态选择路由函数不需要需要提供路由函数和映射表适用场景确定性的顺序执行需要根据中间结果决策的流程如是否调用工具、是否结束四、完整流程示意图┌─────────────────────────────────────────┐ │ START │ └─────────────────┬───────────────────────┘ │ ▼ ┌───────────────┐ │ agent │ └───────┬───────┘ │ should_continue │ 条件边 │ ┌───────────┴───────────┐ │ │ 返回tools 返回__end__ │ │ ▼ ▼ ┌───────────────┐ ┌─────┐ │ tools │ │ END │ └───────┬───────┘ └─────┘ │ route_after_tools │ 条件边 │ ┌─────┴─────┐ │ │ 返回agent 返回after_tool │ │ ▼ ▼ 回到agent ┌───────────────┐ (循环) │ after_tool │ └───────┬───────┘ │ add_edge普通边 │ ▼ ┌─────┐ │ END │ └─────┘五、总结add_edge(A, B)固定从 A 走到 B。add_conditional_edges(A, func, mapping)A 执行完后调用func(state)获取结果然后根据mapping跳转到相应的节点。这两种边共同构成了 LangGraph 工作流的控制流让你能够实现顺序执行、条件分支和循环等复杂逻辑。