欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 财经 > 创投人物 > 智能体模式篇(上)- 深入 ReAct:LangGraph构建能自主思考与行动的 AI

智能体模式篇(上)- 深入 ReAct:LangGraph构建能自主思考与行动的 AI

2025/6/9 14:01:30 来源:https://blog.csdn.net/m0_53827889/article/details/148495877  浏览:    关键词:智能体模式篇(上)- 深入 ReAct:LangGraph构建能自主思考与行动的 AI

在上一篇博文中,我们成功为 LangGraph 智能体赋予了“记忆”,使其能够维护对话历史。然而,一个仅能“记住”过去的聊天机器人,本质上仍是一个被动的文本生成器。它无法与外部世界交互,更无法执行需要多步骤推理和外部工具协助的复杂任务,其能力边界清晰可见。

想象一下这个场景:

  • 你问:“帮我查一下明天北京的天气,并根据气温推荐我穿什么衣服。”
  • 你问:”请计算 (123 * 456) - 789 的结果。“

一个标准的 LLM 可能会因为无法访问实时天气 API 而“幻觉”出一个答案,或者在稍复杂的数学计算中出错。这是因为信息检索和精确计算并非其核心优势。要突破这层壁垒,我们必须让 AI 不仅能“说”,更能“做”。

今天,我们将深入探讨一种赋予 AI 自主规划与执行能力的强大模式——ReAct (Reasoning and Acting)。通过 LangGraph,我们将构建一个能够**思考(Reasoning)行动(Acting)**的智能体,教会它如何利用外部工具来达成复杂目标。

1. ReAct 模式的哲学:思维与行动的优雅循环

ReAct 模式的核心,在于模仿人类解决复杂问题时的思维过程:观察环境 -> 形成思路 -> 采取行动 -> 观察结果 -> 调整思路 -> 再次行动。这个持续的反馈循环,是智能的体现。

在 AI 智能体中,这个过程被具体化为:

  1. 推理 (Reasoning):当接收到用户请求时,LLM 首先进行“内部独白”。它会分析任务,评估现有信息,并决定下一步是直接回答,还是需要调用工具来获取额外数据。这个“思考”过程是 ReAct 的灵魂。
  2. 行动 (Acting):如果 LLM 决定需要外部协助,它会生成一个结构化的**工具调用(Tool Call)**请求,明确指定要使用的工具名称和所需参数。
  3. 观察 (Observation):外部工具被执行,并将结果(无论是数据、计算结果还是错误信息)返回给智能体。这个结果成为了新的“观察”信息。
  4. 循环 (Loop):LLM 接收到工具返回的结果,将其作为新的上下文,再次进入推理阶段。它会评估新信息是否足够,并决策是基于现有信息生成最终答案,还是需要调用另一个工具,或是以不同参数再次调用同一工具。

这个“思考 -> 行动 -> 观察”的循环不断迭代,直至 LLM 判断任务已圆满完成,最终输出一个高质量、有事实依据的答案。

这就像你委托一位能干的助理一项任务。他不会盲目行动,而是先规划步骤。如果发现需要查阅资料或使用计算器,他会(行动)去执行,然后带着结果(观察)回来,继续思考如何整合信息,直到为你呈现最终的完美方案。

2. 构建 ReAct 智能体:LangGraph 中的架构与实现

在 LangGraph 中实现 ReAct 模式,需要我们精巧地设计其核心组件:状态管理、节点定义、工具集成和图的条件流控制

2.1 增强 AgentState:构建完整的“推理轨迹”

在之前的智能体中,我们的状态仅存储了对话消息。但在 ReAct 模式下,状态需要记录的远不止于此。它必须成为一个完整的推理轨迹(Reasoning Trace),包含:

  • 用户的原始请求 (HumanMessage)
  • AI 的中间思考与最终回答 (AIMessage)
  • AI 发出的工具调用请求 (包含在 AIMessagetool_calls 属性中)
  • 工具执行后返回的结果 (ToolMessage)

为了实现这一点,LangGraph 提供了一种强大且优雅的状态更新机制:

from typing import Annotated, Sequence, TypedDict
from langchain_core.messages import BaseMessage
from langgraph.graph.message import add_messagesclass AgentState(TypedDict):"""AgentState 定义了 ReAct 智能体的状态结构。Attributes:messages: 一个可累加的消息序列,用于存储完整的交互历史。"""messages: Annotated[Sequence[BaseMessage], add_messages]

这里的关键在于 Annotated[Sequence[BaseMessage], add_messages]

  • BaseMessage: 作为所有消息类型的基类,它允许 messages 列表容纳 HumanMessage, AIMessage, ToolMessage 等所有类型的消息,保证了状态的通用性。
  • Sequence: 表明 messages 是一个序列(如列表),LangGraph 会智能地处理其更新。
  • add_messages (Reducer): 这是至关重要的“魔法”。它是一个 Reducer 函数,规定了状态更新的方式。默认情况下,节点返回的新状态会覆盖旧状态。但 add_messages 指示 LangGraph 将新消息**追加(append)到 messages 列表末尾,而不是覆盖。这确保了从用户输入到最终答案的每一步思考、行动和观察都被完整保留,为 LLM 的后续决策提供了完整的上下文。
2.2 定义工具:赋予 LLM 超越语言的能力

工具是智能体“行动”的物理延伸。它们是带有特定功能的 Python 函数,通过 @tool 装饰器暴露给 LLM。

from langchain_core.tools import tool@tool
def multiply_numbers(a: int, b: int) -> int:"""计算两个整数的乘积。当需要进行乘法运算时,请使用此工具。Args:a (int): 第一个整数。b (int): 第二个整数。"""return a * b@tool
def add_numbers(a: int, b: int) -> int:"""计算两个整数的和。当需要进行加法运算时,请使用此工具。Args:a (int): 第一个整数。b (int): 第二个整数。"""return a + b# 将所有可用工具整合到一个列表中
tools = [add_numbers, multiply_numbers]

关键点

  • @tool 装饰器: 它将一个普通 Python 函数转换成 LangChain 生态系统中的标准工具,使其可被 LLM 发现和调用。
  • 文档字符串 (Docstring) 的重要性: Docstring 不再仅仅是给开发者看的注释。它就是给 LLM 看的 API 文档! LLM 会仔细分析 Docstring 的内容(包括函数描述、参数说明)来理解该工具的功能、适用场景以及如何构建调用参数。一个清晰、准确的 Docstring 是 LLM 能否正确使用工具的决定性因素。
2.3 LLM 绑定工具:让模型“知晓”并“掌握”工具

定义工具后,我们必须让 LLM “意识”到这些工具的存在,并赋予它调用它们的能力。这通过 .bind_tools() 方法实现。

from langchain_openai import ChatOpenAI# 初始化一个支持工具调用的 LLM 模型
llm = ChatOpenAI(model="gpt-4o")# 将工具列表绑定到 LLM,创建一个新的、具备工具调用能力的 LLM 实例
llm_with_tools = llm.bind_tools(tools)

llm.bind_tools(tools) 的作用是创建一个“武装”过的新 LLM 实例。当这个实例被调用时,它的输出将不再局限于文本,而是可能包含一个结构化的 tool_calls 字段,这正是 ReAct 模式中“行动”信号的来源。

2.4 Agent 节点(大脑):推理与决策的核心

Agent 节点是 ReAct 智能体的“大脑中枢”。它封装了 llm_with_tools,负责接收当前完整的状态,进行推理,并决定下一步的走向。

def call_agent(state: AgentState) -> dict:"""Agent 节点,代表智能体的大脑。它调用 LLM 进行推理,并返回 LLM 的响应。"""messages = state["messages"]# 我们可以在此添加系统指令,引导 AI 的行为模式system_prompt = SystemMessage(content="你是一个强大的AI助手,能够分析用户问题并决定是直接回答还是使用工具来解决问题。")# 调用绑定了工具的 LLM,传入完整的历史消息response = llm_with_tools.invoke([system_prompt] + messages)# 返回的 response (AIMessage) 会被 add_messages 自动追加到状态中return {"messages": [response]}

此节点完美体现了 ReAct 的推理环节。它将迄今为止所有的交互历史(messages)提供给 LLM,让其在最充分的上下文中做出最明智的决策:是生成最终答案,还是发出工具调用请求?

2.5 ToolNode(执行器):将指令转化为行动

当 Agent 节点决定调用工具后,流程需要一个“执行者”来实际运行这些工具。LangGraph 为此提供了一个预构建的节点:ToolNode

from langgraph.prebuilt import ToolNode# 实例化 ToolNode,并传入我们定义的所有工具
tool_node = ToolNode(tools)

ToolNode 的工作流程非常高效:

  1. 它会自动检查状态中最新消息(AIMessage)的 tool_calls 字段。
  2. 解析出需要调用的工具名称和参数。
  3. 执行对应的 Python 函数。
  4. 将函数的返回值包装成一个 ToolMessage 对象,并添加到状态中。

ToolNode 完美地扮演了“行动”和“观察”的角色,将 AI 的意图转化为现实世界的操作和结果。

2.6 构建 ReAct 循环:用条件边实现智能流转

ReAct 模式的精髓在于其循环结构。我们使用**条件边(Conditional Edges)**在 Agent 节点和 ToolNode 之间构建一个动态的反馈回路。

ReAct 图的基本逻辑:

            (需要工具?)
START --> AgentNode ----------> ToolNode|          ^                   ||          | (返回结果)        ||          +-------------------+|+---- (无需工具/任务完成) ----> END

1. 定义决策函数:我们需要一个函数来检查 Agent 节点的输出,并决定下一步的路径。

def should_continue(state: AgentState) -> str:"""决策函数,用于路由。检查最后一条消息是否包含工具调用。"""last_message = state["messages"][-1]if last_message.tool_calls:# 如果有工具调用,则流程继续到 tool_nodereturn "continue"else:# 如果没有工具调用(意味着 AI 认为任务已完成),则结束流程return "end"

2. 组装计算图

from langgraph.graph import StateGraph, END# 初始化 StateGraph
workflow = StateGraph(AgentState)# 添加节点
workflow.add_node("agent", call_agent)
workflow.add_node("tools", tool_node)# 设置入口点
workflow.set_entry_point("agent")# 添加条件边
workflow.add_conditional_edges("agent",         # 从 "agent" 节点出发should_continue, # 使用此函数做决策{"continue": "tools", # 如果返回 "continue",则流向 "tools" 节点"end": END           # 如果返回 "end",则结束}
)# 添加从 ToolNode 返回 AgentNode 的普通边
workflow.add_edge("tools", "agent")# 编译图,生成可执行应用
app = workflow.compile()

通过这几步,我们便用 LangGraph 的声明式语法构建了一个完整的 ReAct 循环。add_conditional_edges 是实现这一动态流程的核心。

3. 运行 ReAct 智能体:见证思考与行动的共舞

现在,让我们通过一个实例来直观感受 ReAct 智能体的工作流程。

from langchain_core.messages import HumanMessage# 模拟用户输入一个需要多步计算的任务
query = "计算 123 乘以 456,然后将结果加上 789。"
inputs = {"messages": [HumanMessage(content=query)]}# 使用 stream 模式运行并观察每一步的状态变化
for event in app.stream(inputs, stream_mode="values"):event["messages"][-1].pretty_print()print("---")

当你运行上述代码时,你将清晰地看到 ReAct 的内部流转,这个过程完美展示了 ReAct 智能体的强大之处:它能够自主地将复杂任务分解为一系列“思考-行动”的子步骤,并动态地利用工具,直到问题被彻底解决。

4. ReAct 的价值与应用前景

ReAct 模式不仅仅是一个技术技巧,它是一种架构思想,从根本上提升了 LLM 的能力维度。

  • 突破固有局限:通过调用计算器、搜索引擎、各类 API,智能体得以执行精确计算、获取实时信息,弥补了 LLM 自身的短板。
  • 处理复杂任务:对于需要多步骤、多工具协作的复杂工作流(如“预定一张明天从上海到北京的机票,并将其添加到我的日历”),ReAct 提供了可靠的实现框架。
  • 鲁棒性与可解释性:由于每一步的“思考”(AIMessage)和“观察”(ToolMessage)都被记录下来,我们不仅能得到最终答案,还能完整地回溯智能体的决策路径,这对于调试和信任至关重要。

ReAct 是构建所有高级 AI 智能体的基石。掌握了它,你就开启了通往真正“智能”助理的大门。

总结与展望

在本篇博文中,我们深入探索了 ReAct 模式,并利用 LangGraph 将其付诸实践。你现在已经掌握了:

  • ReAct (Reasoning and Acting) 模式的核心哲学:一个由“思考-行动-观察”组成的智能循环。
  • 构建健壮 AgentState:利用 Annotatedadd_messages 记录完整的推理轨迹。
  • 定义和绑定工具:通过 @tool 装饰器和 .bind_tools() 方法,赋予 LLM 与外部世界交互的能力。
  • 构建 ReAct 循环:利用 AgentNodeToolNode条件边实现智能体的核心逻辑。

这只是智能体之旅的开始。在 智能体模式篇(中) 中,我们将在此基础上展示LangGraph如何用于构建人机协作的工作流。

下一步建议:动手为你的 ReAct 智能体添加一个网络搜索工具(例如使用 Tavily 或 DuckDuckGo Search API)。尝试向它提问需要实时信息的问题(“今天 AI 领域有什么大新闻?”),亲身体验它从语言模型到信息助理的蜕变。

完整代码

以下是 ReAct Agent 的完整实现,复制即可运行:

import os
from typing import Annotated, Sequence, TypedDict
from dotenv import load_dotenvfrom langchain_core.messages import BaseMessage, HumanMessage, ToolMessage, AIMessage, SystemMessage
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# --- 1. 准备环境:加载环境变量 ---
load_dotenv()# 检查 API 密钥是否已设置
if not os.getenv("OPENAI_API_KEY"):raise ValueError("请在 .env 文件中设置您的 OpenAI API 密钥 (OPENAI_API_KEY)")# --- 2. 定义 Agent 的状态 (AgentState) ---
class AgentState(TypedDict):messages: Annotated[Sequence[BaseMessage], add_messages]# --- 3. 定义可用的工具 (Tools) ---
# 使用 @tool 装饰器可以轻松地将任何函数转换为 LangChain 工具。
# LLM 会利用函数的名称、文档字符串和类型注解来决定何时以及如何调用它。
# !! 重要提示:清晰的文档字符串 (docstring) 对 LLM 正确使用工具至关重要。@tool
def add(a: int, b: int) -> int:"""计算两个整数的和。"""print(f"--- 工具执行: add({a}, {b}) ---")return a + b@tool
def subtract(a: int, b: int) -> int:"""计算两个整数的差。"""print(f"--- 工具执行: subtract({a}, {b}) ---")return a - b@tool
def multiply(a: int, b: int) -> int:"""计算两个整数的乘积。"""print(f"--- 工具执行: multiply({a}, {b}) ---")return a * b# 将所有工具放入一个列表中,以便后续使用
tools = [add, subtract, multiply]# --- 4. 创建 LLM 和工具执行器 ---# 4.1. 将工具绑定到 LLM
# 我们使用 .bind_tools() 方法,让 LLM “知道”我们有哪些工具可用。
# 这会返回一个新的、具备工具调用能力的 LLM 实例。
llm = ChatOpenAI(model="gpt-4o-mini")
llm_with_tools = llm.bind_tools(tools)# 4.2. 创建 ToolNode
# ToolNode 是一个预构建的 LangGraph 节点,它接收工具调用请求,
# 执行相应的工具,并返回结果。
tool_node = ToolNode(tools)# --- 5. 定义图的节点 (Nodes) ---# 5.1. 定义 Agent 节点 ("大脑")
# 这个节点负责调用 LLM,进行推理和决策。
def call_agent(state: AgentState):"""Agent 节点,代表智能体的大脑。"""print("--- 调用 Agent 大脑 ---")system_prompt = SystemMessage(content="你是一个AI助手,请根据我的查询给出最好的回答.")# 调用绑定了工具的 LLM,传入完整的历史消息# LLM 会根据上下文决定是直接回答,还是调用工具response = llm_with_tools.invoke([system_prompt] + state["messages"])# 返回一个包含 LLM 响应的字典,它将被 add_messages 追加到状态中return {"messages": [response]}# 5.2. 定义决策函数 (Conditional Edge Logic)
# 这个函数决定在 Agent 节点之后应该走向哪里。
def should_continue(state: AgentState) -> str:"""决策函数,用于路由。检查最后一条消息是否包含工具调用。"""last_message = state["messages"][-1]if not last_message.tool_calls:print(">>> 决策:无需工具或任务完成,流向 'END'。")return "end"else:print(">>> 决策:需要执行工具,流向 'tools' 节点。")return "continue"# --- 6. 构建并编译计算图 (Graph) ---# 6.1. 初始化 StateGraph
workflow = StateGraph(AgentState)# 6.2. 添加节点
workflow.add_node("agent", call_agent)
workflow.add_node("tools", tool_node)# 6.3. 设置入口点
workflow.set_entry_point("agent")# 6.4. 添加条件边
# 从 "agent" 节点出发,根据 should_continue 函数的返回值决定下一个节点。
workflow.add_conditional_edges("agent",should_continue,{"continue": "tools",  # 如果返回 "continue",则流向 "tools" 节点"end": END            # 如果返回 "end",则结束流程}
)# 6.5. 添加普通边
# 工具执行完毕后 ("tools" 节点),流程总是返回到 "agent" 节点,形成循环。
workflow.add_edge("tools", "agent")# 6.6. 编译图,生成可执行应用
app = workflow.compile()# --- 7. 运行 ReAct Agent ---def run_agent(query: str):"""运行 Agent 并打印每一步的详细过程。"""# 初始状态,只包含用户的输入inputs = {"messages": [HumanMessage(content=query)]}# 使用 stream 模式运行并观察每一步的状态变化for event in app.stream(inputs, stream_mode="values"):last_message = event["messages"][-1]last_message.pretty_print()if __name__ == "__main__":# 示例: 需要多步工具调用的复杂任务run_agent("计算 (40 + 12) 然后乘以 6. 最后告诉我一个关于程序员的笑话。")

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词