ReAct: 《ReAct: Synergizing Reasoning and Acting in Language Models》这是一篇出自谷歌团队在 2022 年的论文,核心思想是让模型像人类一样 “边想边做”,通过自然语言生成推理步骤,再结合外部工具(如搜索引擎、数据库)获取信息,最终完成任务。
1. ReAct 的核心逻辑
ReAct 流程可拆解为四步循环,形成一个动态决策闭环:
-
思考(Reason)
模型针对当前任务或问题,生成自然语言推理过程(类似 “自言自语”),梳理思路、明确下一步目标。例如:“用户问巴黎现在的气温,我需要先查今天的天气数据。” -
行动(Act)
根据推理结果,调用外部工具(如 API、数据库、搜索引擎)执行具体操作,获取必要信息。例如:调用天气 API 查询巴黎的实时气温。 -
观察(Observe)
接收工具返回的结果(即 “观察到的信息”),并整合到当前语境中。例如:“工具返回巴黎现在气温 22℃,多云。” -
迭代(Iterate)
基于新信息继续推理,判断是否需要进一步行动(如补充查询),或直接生成最终答案。若信息不足,则重复 “思考 - 行动 - 观察” 循环;若信息足够,则输出结果。
2. 需要用到的包
from typing import TypedDict,Annotated,Sequence
from dotenv import load_dotenv
from langchain_core.messages import BaseMessage
from langchain_core.messages import ToolMessage
from langchain_core.messages import SystemMessage
from langchain.chat_models import init_chat_model
from langchain_core.tools import tool
from langgraph.graph.message import add_messages
1. from typing import Annotated,Sequence
Annotated
-
作用:为类型添加元数据(附加信息),这些信息不会影响 Python 解释器的运行逻辑,但能被类型检查工具(如
mypy)、IDE 或框架(如 LangChain、Pydantic)识别和利用。 -
语法:
Annotated[type, metadata1, metadata2, ...],其中:type是基础数据类型(如int、str等)。- 后续参数是元数据,可以是任意类型(字符串、数字、类实例等),用于补充描述该类型的约束、含义或用途。
-
适用场景:
- 标记参数的业务含义(如单位、取值范围)。
- 为框架提供额外配置(如 LangChain 中标记工具参数的说明)。
- 生成文档时自动提取注释信息。
from typing import Annotated
# 表示“年龄”是整数类型,且元数据说明其范围是0-120
Age = Annotated[int, "年龄范围:0-120岁"]
def register(age: Age) -> None:
print(f"注册年龄:{age}")
Sequence
-
作用:表示有序的可迭代序列,是一种抽象的类型注解,涵盖了所有支持下标访问(
[])和长度计算(len())的序列类型,如list、tuple、str、bytes等。 -
特点:
- 比具体的序列类型(如
list)更通用,适合需要兼容多种序列的场景。 - 强调 “有序性” 和 “可迭代性”,但不强制要求是可变类型(如
tuple是不可变的,也属于Sequence)。
- 比具体的序列类型(如
-
适用场景:
- 函数参数需要接收列表、元组等多种序列类型时,用
Sequence提高灵活性。 - 明确表示 “需要一个有序的、可通过下标访问的集合”,而非无序的
set或dict。
- 函数参数需要接收列表、元组等多种序列类型时,用
from typing import Sequence
# 函数接受任何字符串序列(list、tuple等)
def print_items(items: Sequence[str]) -> None:
for i, item in enumerate(items):
print(f"第{i+1}项:{item}")
print_items(["苹果", "香蕉"]) # 合法(list[str] 是 Sequence[str] 的子类)
print_items(("猫", "狗")) # 合法(tuple[str] 是 Sequence[str] 的子类)
在 LangChain 等框架中,Annotated:为工具函数的参数添加描述,帮助智能体理解参数含义(例如在 @tool 装饰器中,元数据会被解析为工具调用的说明)。
from langchain_core.tools import tool
from typing import Annotated
@tool
def get_weather(
city: Annotated[str, "需要查询天气的城市名称,如'北京'"]
) -> str:
"""查询指定城市的天气"""
return f"{city}的天气是晴朗"
Sequence:标注消息列表等有序数据,例如对话历史(兼容 list、tuple 等存储形式)
from langchain_core.tools import tool
from typing import Annotated
@tool
def get_weather(
city: Annotated[str, "需要查询天气的城市名称,如'北京'"]
) -> str:
"""查询指定城市的天气"""
return f"{city}的天气是晴朗"
2. from langchain_core.messages import BaseMessage,ToolMessage,SystemMessage
BaseMessage(基础消息类)
- 定位:所有消息类型的抽象基类(父类),定义了消息的通用结构。
- 核心作用:统一所有消息的基础属性和接口,确保不同类型的消息能被框架一致处理。
- 主要属性:
content: str:消息的文本内容(如用户提问、AI 回复、工具结果等)。type: str:消息类型标识(由子类定义,如"human"、"ai"、"tool"等),用于区分消息来源。additional_kwargs: dict:存储附加元信息(如消息 ID、时间戳等)。
- 特点:无法直接实例化,只能通过其子类(如
ToolMessage、SystemMessage、HumanMessage)使用
ToolMessage(工具消息类)
- 定位:继承自
BaseMessage,专门用于封装外部工具的调用结果。 - 核心作用:在智能体调用工具(如 API、数据库、搜索引擎)后,将工具返回的结果标准化,以便智能体识别和解析。
- 独有属性:
tool_call_id: str:工具调用的唯一标识符,用于将结果与对应的工具调用指令关联(解决多工具并行调用时的匹配问题)。
- 典型场景:
智能体调用天气 API 后,API 返回的气温数据会被包装成ToolMessage,通过tool_call_id绑定到对应的调用指令,确保智能体能正确匹配 “调用→结果”。
SystemMessage(系统消息类)
3. from langchain_core.tools import tool
tool 装饰器的核心作用
- 定位:继承自
BaseMessage,用于传递系统级指令或配置。 - 核心作用:向大语言模型(LLM)传递背景信息、角色设定或行为规则,指导模型的回答风格和逻辑(类似 “给 AI 的隐形指令”)。
- 典型场景:
- 定义 AI 角色:
SystemMessage(content="你是专业的数学老师,用通俗语言讲解公式")。 - 设定回答约束:
SystemMessage(content="仅用中文回复,拒绝讨论无关话题")。
- 定义 AI 角色:
在 LangChain 中,“工具” 指的是智能体可以调用的外部功能(如查询数据库、调用 API、执行代码等)。tool 装饰器的作用是:
给普通函数添加标准化的元数据(如名称、描述、参数信息等),让智能体能够理解 “这个工具能做什么”“需要传入什么参数”,从而实现自动调用。
基本使用方法
使用 tool 装饰器定义工具的步骤非常简单:
- 定义一个普通 Python 函数,实现具体功能(如查询天气、计算数值等)。
- 用
@tool装饰该函数,并通过参数或文档字符串描述工具的功能和参数。 - 智能体即可识别该工具,并在需要时自动调用。
from langchain_core.tools import tool
# 用@tool装饰器定义一个计算加法的工具
@tool
def add(a: int, b: int) -> int:
"""计算两个整数的和。
Args:
a: 第一个整数
b: 第二个整数
"""
return a + b
4. from langgraph.graph.message import add_message
add_messages 的核心功能
在多轮对话或状态流转场景中,我们经常需要将新消息(如用户输入、AI 回复、工具结果)添加到已有的对话历史中。add_messages 的作用就是规范化这一过程,确保消息列表的合并逻辑安全、一致,避免因重复添加、类型错误或状态覆盖导致的问题。
主要特性与行为
add_messages 通常接收两个参数:
existing_messages:已有的消息列表(可能为空,或包含HumanMessage、AIMessage等类型)。new_messages:要添加的新消息(可以是单个消息对象,也可以是消息列表)。
它的核心行为是:
- 若
existing_messages为空(None或空列表),则直接返回new_messages作为初始列表。 - 若
existing_messages已存在,则将new_messages中的所有消息追加到existing_messages末尾,返回合并后的新列表(不会修改原列表,避免副作用)。 - 自动处理
new_messages的格式:无论传入的是单个消息还是列表,都会统一转换为列表后再合并。
3. 定义状态字典
class State(TypedDict):
message:Annotated[Sequence[BaseMessage],add_messages]
字段message的类型为序列类型(如:List, tuple等),元数据为add_message, 元数据可以是操作,也可以是字符串,用于补充描述该类型的约束、含义或用途,这里使用add_message操作,指明将将新消息(如用户输入、AI 回复、工具结果)添加到已有的对话历史中。
4. 定义tool
@tool
def add(a:int,b:int)->int:
"""这是一个加法函数,将两个数加起来"""
return a+b
@tool
def subtract(a:int,b:int)->int:
"""这是一个加法操作"""
return a-b
@tool
def multiply(a:int,b:int):
"""这是一个乘法操作"""
return a*b
其中 @tool 是工具修饰符,下面就是定义一个函数,函数中的"""说明""" 是不可或缺的,它是让大模型选择该工具的重要指示。
5. 给大模型装上工具
tools = [add,subtract,multiply]
#让大模型能够使用这些工具,相当于给大模型装上了手脚
llm = init_chat_model("deepseek-chat").bind_tools(tools)
6. 定义graph中的节点函数和工具节点
def model_call(x:State)->State:
Sys_prompt = SystemMessage(content="你是我的AI助手,请尽你所能回答我的问题")
response = llm.invoke([Sys_prompt] + x["message"])
x["message"] = [response]
return x
def should_continue(y:State)->str:
message = y["message"]
last_msg = message[-1]
if not last_msg.tool_calls:
return "end"
else:
return "continue"
tool_node = ToolNode(tools=tools)
其中SystemMessage用于设置系统提示词,llm.invoke触发大模型根据“系统提示词和参数中的状态消息”进行思考和反应,x['message']=[response] 让x记住更新后的消息。
message=y['message']读取参数中的消息,last_msg取出最后一条对话,如果last_msg中有tool_calls操作,说明这是一条AIMessage, 其中AIMessage是有tool_calls操作的,否则就不是AIMessage
tool_node = ToolNode(tools=tools), 将所有的工具封装成一个工具节点。
7. 创建graph, 给graph添加node 和 edge, 编译graph得到可执行任务的agent
graph = StateGraph(State)
graph.add_node("model",model_call)
graph.add_node("tool",tool_node)
graph.add_edge(START,"model")
graph.add_conditional_edges(
"model",
should_continue,
{
"continue":"tool", #继续依赖工具去解决
"end":END,
},
)
graph.add_edge("tool","model")
app = graph.compile()
图的结构:

8. 使用流式方式触发agent 工作
def print_stream(stream):
for s in stream:
# 引用状态中的messages字段(复数)
msg = s["messages"][-1] # 统一为message
msg.pretty_print()
# 输入参数使用(复数)messages(与状态定义一致)
inputs = {"message": [("user", "40 + 12 将求和结果与 6 相乘. 接下来再将一个笑话")]}
# inputs = {"messages": [HumanMessage(content=" 40 + 12 将求和结果与 6 相乘. 接下来再将一个笑话")]} # 统一为message
strm = app.stream(inputs, stream_mode="values")
print_stream(strm)
定义了一个print_stream函数,该函数的功能是输出参数stream中的信息,
for s in stream:迭代智能体产生的每一个状态更新(流式输出)。s["message"][-1]:获取当前状态中最新的一条消息("message"字段存储对话历史)。- 条件判断:如果消息是元组(特殊格式)则直接打印,否则调用
pretty_print()格式化输出(LangChain 消息对象的内置方法,会显示消息类型和内容)。
app.stream(...):以流式方式运行智能体,stream_mode="values" 表示返回简洁的状态值(仅包含 message 等核心字段)。
inputs:用户请求包含两个任务 —— 计算 (40 + 12) × 6,并讲一个笑话。消息格式是 ("user", "内容")(会被自动转换为 HumanMessage):HumanMessage(content="xxxx")
这里的user 角色还可以换成ai, 那么消息格式会自动转换成AIMessage : AIMessage(content="xxxx")
除此之外还可以换成system, tool , 自动转换成消息类型:SystemMessage, ToolMessage
最后的运行结果:
================================ Human Message
40 + 12 将求和结果与 6 相乘. 接下来再将一个笑话
================================== Ai Message
Tool Calls:
add (call_0_bf1be394-9817-43d5-bbb8-0a5fed71e1ac)
Call ID: call_0_bf1be394-9817-43d5-bbb8-0a5fed71e1ac
Args:
a: 40
b: 12
================================= Tool Message
Name: add
52
================================== Ai Message
Tool Calls:
multiply (call_0_a8af7994-43ea-4e9f-aca8-591c4b3c3c3e)
Call ID: call_0_a8af7994-43ea-4e9f-aca8-591c4b3c3c3e
Args:
a: 52
b: 6
================================= Tool Message
Name: multiply
312
================================== Ai Message
40 + 12 的结果是 52,再乘以 6 得到 312。
现在讲一个笑话:
**笑话:**
有一天,数学老师问小明:“如果你有5个苹果,我拿走3个,你还剩几个?”
小明回答:“我不知道,但我知道你肯定没拿走我的香蕉!”
LangGraph-agent之ReAct的原理与实践
247

被折叠的 条评论
为什么被折叠?



