当前位置: 首页 > news >正文

LLM——使用 LangGraph 构建 ReAct 智能体:多轮对话 + 工具调用 + 可视化流程图

本文将带大家构建一个具有 ReAct 能力(Reasoning + Acting)的多轮智能体,同时集成 LangChain 工具调用、OpenAI 或通义千问大模型、LangGraph 状态流程管理,并输出流程图,适用于旅行规划、业务辅助、智能客服等多种场景(文末附项目完整代码)。


一、什么是 ReAct 架构?

ReAct(Reason + Act)是一种大模型交互范式,强调以下流程的循环:

  1. Reasoning:模型分析用户输入和上下文,进行思考。
  2. Acting:模型选择并调用外部工具(API/函数)。
  3. Observation:观察工具执行结果,继续推理或输出答案。

它打破了单轮问答限制,使得模型可以多轮调用工具,直到获得满意的结果为止

二、项目技术栈

技术组件用途
LangGraph构建状态图、流程管理
LangChain定义工具 / 管理消息历史
Qwen/OpenAI提供大模型 API 接口
ToolNode自动处理工具调用
Mermaid 图可视化整个智能体调用流程

三、核心代码解析

1️⃣ 定义工具函数(Tools)

@tool()
def get_weather(location: str) -> str:"""Get the weather forecast for a given location.Args:location (str): The location for which to retrieve the weather forecast.Returns:str: The weather forecast for the specified location."""return f"The weather forecast for {location} is sunny and warm."@tool()
def get_travel_advise(destination: str, weather: str) -> str:"""Get travel advice for a given destination based on the weather conditions.Args:destination (str): The location for which to give the travel advice.weather (str): The weather at the destinationReturns:str: The travel advice for the specified destination"""return f"The travel advise for is city walk"

通过 @tool() 装饰器,工具可以被模型识别为“可调用动作”。在ReAct 中的 Action(Act) 阶段被触发。

2️⃣ 构建智能体代理(Agent)

def create_agent(llm, tools, system_message: str):"""创建一个代理。"""# 创建一个聊天提示模板prompt = ChatPromptTemplate.from_messages([SystemMessage("你是一个有帮助的AI助手,与其他助手合作。"" 使用提供的工具来推进问题的回答。"" 如果你不能完全回答,没关系,另一个拥有不同工具的助手"" 会接着你的位置继续帮助。执行你能做的以取得进展。"" 如果你或其他助手有最终答案或交付物,"" 在你的回答前加上FINAL ANSWER,以便团队知道停止。"" 你可以使用以下工具: {tool_names}。\n{system_message}",),# 消息占位符MessagesPlaceholder(variable_name="messages"),])# 传递系统消息参数prompt = prompt.partial(system_message=system_message)# 传递工具名称参数prompt = prompt.partial(tool_names=", ".join([tool.name for tool in tools]))# 绑定工具并返回提示模板return prompt | llm.bind_tools(tools)

该函数生成一个具有工具调用能力的 LLM 智能体,具备 ReAct 的 Reasoning 和 Action 触发逻辑。

3️⃣ 定义状态与路由器

class State(TypedDict):messages: Annotated[Sequence[BaseMessage], add_messages]def router(state) -> Literal["tools", "__end__", "chatbot"]:last = state["messages"][-1]if last.tool_calls:return "tools"elif "FINAL ANSWER" in last.content.upper():print("emerge FINAL ANSWER!!!")return "__end__"

其中,add_messages是LangGraph 提供的一个“合并函数(merge function)”,用于处理节点的增量返回。

messages 是消息的序列,每次节点执行后返回新消息时,系统会自动追加到这个字段中,这个过程就是通过 add_messages 完成的。

router() 函数起到 动态流程控制器的作用,是 LangGraph 中的核心条件跳转逻辑。
它实现了:

  • 检测是否需要执行工具
  • 检测是否已有最终答案(带有 “FINAL   ANSWER”)

4️⃣ 构建 LangGraph 状态图

graph_builder = StateGraph(State)
graph_builder.add_node("chatbot", lambda state: {"messages": chat_agent.invoke(state)})
graph_builder.add_node("tools", ToolNode(tools))graph_builder.add_edge("tools", "chatbot")
graph_builder.add_conditional_edges("chatbot", router, {"tools": "tools", "__end__": END})
graph_builder.set_entry_point("chatbot")
graph = graph_builder.compile()

(1)lambda state: {"messages": chat_agent.invoke(state)}是自定义的推理节点,这个节点表示 “由 LLM 进行一次推理”,自定义

  1. 输入:整个 state(包含历史 messages)
  2. 输出:新的 messages 追加

这里 chat_agent.invoke(state) 是我们绑定的 LLM agent,它会根据当前对话历史,决定是否回复内容或调用工具。

(2)ToolNode(tools) 是工具执行节点,这是 LangGraph 提供的内置封装,用于处理大模型产生的 tool_call。

ToolNode的执行逻辑是:当 ToolNode 接收到一个 AIMessage(包含 tool_calls),它会

  1. 读取 tool_calls 列表,执行对应工具函数(比如我们使用@tool()装饰的函数)
  2. 将执行结果转换成 ToolMessage(…) 实例
  3. ToolNode 返回 {“messages”: [ToolMessage]}
  4. LangGraph 查到 messages 的合并策略是 add_messages,自动执行:state[“messages”] += [ToolMessage]

这段代码声明了 ReAct 的循环路径:

chatbot → 判断是否需要工具 → tools → chatbot → ...

直到输出包含 FINAL ANSWER,流程才终止。

5️⃣ 可视化输出:自动绘制 Mermaid 图

graph_png = graph.get_graph().draw_mermaid_png()
with open("ReAct(Travel).png", "wb") as f:f.write(graph_png)

这使得流程图可导出为 .png 文件,便于分析和设计系统。

6️⃣ 运行部分

events = graph.stream({"messages": [HumanMessage(content="Please give me a travel advice based on the weather in London.")]},{"recursion_limit": 10}
)for e in events:print(e)

这段代码启动一个基于消息历史的 ReAct 循环对话。模型会首先获取天气 → 然后再给出旅行建议 → 输出最终答案。

✅ 效果展示(终端日志)

{'chatbot': {'messages': AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_5edea821994449b0bf3b28', 'function': {'arguments': '{"location": "London"}', 'name': 'get_weather'}, 'type': 'function', 'index': 0}], 'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 17, 'prompt_tokens': 416, 'total_tokens': 433, 'completion_tokens_details': None, 'prompt_tokens_details': None}, 'model_name': 'qwen2.5-72b-instruct', 'system_fingerprint': None, 'finish_reason': 'tool_calls', 'logprobs': None}, id='run--1ed5eb6a-880d-45dc-8fc7-1f7a8496f01b-0', tool_calls=[{'name': 'get_weather', 'args': {'location': 'London'}, 'id': 'call_5edea821994449b0bf3b28', 'type': 'tool_call'}], usage_metadata={'input_tokens': 416, 'output_tokens': 17, 'total_tokens': 433, 'input_token_details': {}, 'output_token_details': {}})}}
{'tools': {'messages': [ToolMessage(content='The weather forecast for London is sunny and warm.', name='get_weather', id='2f510f25-87ce-4ed8-a238-2deab5d18537', tool_call_id='call_5edea821994449b0bf3b28')]}}
{'chatbot': {'messages': AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_58529302dae44a32a2a848', 'function': {'arguments': '{"destination": "London", "weather": "sunny and warm"}', 'name': 'get_travel_advise'}, 'type': 'function', 'index': 0}], 'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 30, 'prompt_tokens': 450, 'total_tokens': 480, 'completion_tokens_details': None, 'prompt_tokens_details': None}, 'model_name': 'qwen2.5-72b-instruct', 'system_fingerprint': None, 'finish_reason': 'tool_calls', 'logprobs': None}, id='run--864cef64-2167-4e8f-865b-96e0421686df-0', tool_calls=[{'name': 'get_travel_advise', 'args': {'destination': 'London', 'weather': 'sunny and warm'}, 'id': 'call_58529302dae44a32a2a848', 'type': 'tool_call'}], usage_metadata={'input_tokens': 450, 'output_tokens': 30, 'total_tokens': 480, 'input_token_details': {}, 'output_token_details': {}})}}
{'tools': {'messages': [ToolMessage(content='The travel advise for is city walk', name='get_travel_advise', id='5a35f924-bdef-406e-a75f-799c3eb18b48', tool_call_id='call_58529302dae44a32a2a848')]}}
emerge FINAL ANSWER!!!
{'chatbot': {'messages': AIMessage(content='FINAL ANSWER: Based on the sunny and warm weather in London, the travel advice is to go for a city walk. Enjoy exploring the vibrant streets and attractions on foot!', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 36, 'prompt_tokens': 495, 'total_tokens': 531, 'completion_tokens_details': None, 'prompt_tokens_details': None}, 'model_name': 'qwen2.5-72b-instruct', 'system_fingerprint': None, 'finish_reason': 'stop', 'logprobs': None}, id='run--1846b799-e725-4d23-99b1-b73be4ef9c6e-0', usage_metadata={'input_tokens': 495, 'output_tokens': 36, 'total_tokens': 531, 'input_token_details': {}, 'output_token_details': {}})}}

📈 最终生成流程图(ReAct 结构)

我们将会得到如下图所示结构(ReAct(Travel).png):

在这里插入图片描述

🧩 总结:项目已实现的功能

能力说明
✅ 实现 ReAct 模式基于 LangChain 工具 + LLM 推理 + LangGraph 状态循环
✅ 工具调用自动执行ToolNode 封装调用逻辑,自动执行 tool_calls
✅ 动态流程管理router() 控制工具/结束状态跳转
✅ 状态可视化支持导出 Mermaid 图,分析智能体推理路径

🔜 读者可进一步扩展

  • ✅ 引入多个 agent,每个 agent 拥有独立工具集
  • ✅ 增加 memory/history 持久化支持,实现更长上下文理解
  • ✅ 接入真实 API(如天气 API、推荐 API)替代 mock 工具函数
  • ✅ 接入 LangSmith 实现可视化追踪和调试

附项目源代码

from typing import Annotated, Sequence
from typing import Literalfrom langchain_core.messages import HumanMessage, SystemMessage, BaseMessage
# 导入聊天提示模板和消息占位符
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.tools import tool
from langchain_openai import ChatOpenAI
from langgraph.graph import StateGraph, END
from langgraph.graph.message import add_messages
from langgraph.prebuilt import ToolNode
from typing_extensions import TypedDict# 定义一个函数,用于创建代理
def create_agent(llm, tools, system_message: str):"""创建一个代理。"""# 创建一个聊天提示模板prompt = ChatPromptTemplate.from_messages([SystemMessage("你是一个有帮助的AI助手,与其他助手合作。"" 使用提供的工具来推进问题的回答。"" 如果你不能完全回答,没关系,另一个拥有不同工具的助手"" 会接着你的位置继续帮助。执行你能做的以取得进展。"" 如果你或其他助手有最终答案或交付物,"" 在你的回答前加上FINAL ANSWER,以便团队知道停止。"" 你可以使用以下工具: {tool_names}。\n{system_message}",),# 消息占位符MessagesPlaceholder(variable_name="messages"),])# 传递系统消息参数prompt = prompt.partial(system_message=system_message)# 传递工具名称参数prompt = prompt.partial(tool_names=", ".join([tool.name for tool in tools]))# 绑定工具并返回提示模板return prompt | llm.bind_tools(tools)@tool()
def get_weather(location: str) -> str:"""Get the weather forecast for a given location.Args:location (str): The location for which to retrieve the weather forecast.Returns:str: The weather forecast for the specified location."""return f"The weather forecast for {location} is sunny and warm."@tool()
def get_travel_advise(destination: str, weather: str) -> str:"""Get travel advice for a given destination based on the weather conditions.Args:destination (str): The location for which to give the travel advice.weather (str): The weather at the destinationReturns:str: The travel advice for the specified destination"""return f"The travel advise for is city walk"tools = [get_weather, get_travel_advise]llm = ChatOpenAI(base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",api_key="your_secret_key",model="qwen2.5-72b-instruct")chat_agent = create_agent(llm, tools, system_message="You are a helpful assistant.")class State(TypedDict):messages: Annotated[Sequence[BaseMessage], add_messages]def router(state) -> Literal["tools", "__end__", "chatbot"]:last = state["messages"][-1]if last.tool_calls:return "tools"elif "FINAL ANSWER" in last.content.upper():print("emerge FINAL ANSWER!!!")return "__end__"graph_builder = StateGraph(State)
graph_builder.add_node("tools", ToolNode(tools))
graph_builder.add_node("chatbot", lambda state: {"messages": chat_agent.invoke(state)})
graph_builder.add_edge("tools", "chatbot")
graph_builder.add_conditional_edges("chatbot", router, {"tools": "tools", "__end__": END}
)
graph_builder.set_entry_point("chatbot")
graph = graph_builder.compile()# 将生成的图片保存到文件
graph_png = graph.get_graph().draw_mermaid_png()
with open("ReAct(Travel).png", "wb") as f:f.write(graph_png)events = graph.stream({"messages": [HumanMessage(content="Please give me a travel advice based on the weather in London.")]},{"recursion_limit": 10}
)for e in events:print(e)
http://www.lryc.cn/news/606205.html

相关文章:

  • 编译Openssl
  • Linux软件包管理器深度解析:从概念到实战
  • PyTorch L2范数详解与应用
  • system.conf linux用于启动和管理系统进程的初始化系统和服务管理器的配置文件
  • MySQL 8.0 OCP 1Z0-908 题目解析(38)
  • NFLSOI 7.25 题解
  • 2025电赛e题:openmv识别过程丢失矩形
  • laravel下phpunit的使用
  • Web开发-PHP应用Cookie脆弱Session固定Token唯一身份验证数据库通讯
  • 分享低功耗单火线开关语音识别方案
  • Python 程序设计讲义(49):组合数据类型——字典类型:字典的方法
  • Linux/Ubuntu 系统中打开火狐firefox、chromium浏览器失败
  • 33.【.NET8 实战--孢子记账--从单体到微服务--转向微服务】--单体转微服务--财务服务--记账
  • Python Day20 os模块 和 文件操作 及 例题分析
  • 智能文本抽取技术:精准识别、定位并提取出关键信息
  • 学以致用——用Docker搭建ThinkPHP开发环境
  • linux线程互斥和同步
  • 在处理大数据列表渲染时,React 虚拟列表是提升性能的关键技术,但在实际实现中常遇到渲染抖动和滚动定位偏移等问题。
  • 大语言模型信息抽取系统解析
  • Tomcat,WebLogic等中间件漏洞实战解析
  • C++异常处理的成本:理解与优化
  • MySQL转PostgreSQL迁移实战:从语法错误到完美兼容
  • AI学习笔记三十三:基于Opencv的单目标跟踪
  • vue3 v-html绑定数据,点击sub实现popover效果
  • STM32 USB 设备中间件 tinyusb
  • 超宽带测距+测角+无线通信一体化模组:智能门锁、智能遥控器、AR头戴、智能穿戴
  • 融媒体中心网络安全应急预案(通用技术框架)
  • Vmvare虚拟机的网络不可达问题
  • Spring Boot 异常处理:从全局捕获到优化用户体验!
  • 爱心烟花浪漫立方体轮播图 - 用代码表达爱意