2.5 LangGraph 之 ReAct agent
一、ReAct 概念
ReAct 是一种大模型应用中的智能体架构,和流行的前端框架 react 不是一个东西,ReAct 是 Re (Reasoning,推理)和 Act(Action,行动)两个单词的简写,用通俗的话来说,它可以让大模型像人一样“思考”和“行动”,实现更强的任务处理能力。这里把它拆解为两个关键部分来说明:
Re(Reasoning,推理)
大模型通过“推理”步骤来理解任务或者问题。就像你面对一个复杂的问题时,先分析现状、列出条件,然后在脑海中一步步推导出答案一样,ReAct 中的“推理”部分会利用模型的语言理解能力,生成逻辑清晰的分析路径。
Act(Action,行动)
仅仅推理还不够,智能体还需要“行动”,也就是执行特定的操作。比如:
- 通过调用某个工具(例如计算器、数据库查询、外部api接口)来获取信息或解决问题。
- 根据现有信息做出决定并采取下一步行动。
在 ReAct 框架中,“行动”这一步和“推理”紧密结合。模型不会一次性给出答案,而是以“边想边做”的方式,循环地进行推理和行动,直到完成任务。
实际运作流程
- 模型先“想”一想:分析问题,给出可能需要的步骤。
- 模型再“做”一做:如果需要,它可以去查外部信息、调用工具,或者生成一个具体操作。
- 根据结果再“想”:拿到行动的反馈后,重新推理,调整步骤,直到问题解决。
一个简单的例子
问题:今天北京的天气怎么样?适合跑步吗?
-
模型首先推理:
- 我需要知道“北京的天气”。
- 再判断天气是否适合跑步。
-
模型行动:
- 先查天气预报,获取北京当前的温度、降雨量、风速等信息。
-
模型推理并回答:
- 如果天气晴朗、温度适中,就回答“适合跑步”;
- 如果下雨或者天气恶劣,则回答“不适合跑步”。
ReAct 的特点
- 动态灵活:不像传统模型“一问一答”,ReAct 模型会动态地调整策略。
- 能调用外部工具:通过执行操作(例如数据库查询或API调用),可以解决大模型本身无法直接处理的问题。
- 更接近人类思维:这种“想一步,做一步”的方式更像人类解决复杂问题的过程。
通俗来说,ReAct 就是让模型“像人一样,先动脑后动手,再动脑接着手”,在不断循环中完成任务。
二、工具调用
在之前的章节,我们使用工具调用完成了天气查询工具以及联网搜索工具的开发,回顾一下当时的流程图
chatbot 节点和tools 节点相互调用,如用户询问北京的天气,此时graph 的执行流程如下
- chatbot 节点无法直接回复问题,但是会根据大模型绑定的天气查询工具的参数,返回一个tool 调用的结构
- tools 节点根据大模型的返回信息,进行相应的工具调用,获取到北京的实时气象信息
- tools 节点之后会继续调用大模型chatbot节点,将工具调用的结果和之前的用户问题再次调用大模型,此时大模型已经知道了北京的天气,这时就可以正确回答用户的问题了。
这个流程和上面介绍的 ReAct 过程是一样的,大模型经过了 Reasoning 推理后,需要调用天气查询工具,之后tools 节点执行Action行动,再调用大模型进行推理,在大模型和工具进行了多次Reasoning 与 Action 以后,得到最终的答复内容。
回顾一下之前的代码
| import json
import os
from typing import Type, TypedDict, Annotated
import requests
from langchain_community.tools import TavilySearchResults
from langchain_core.messages import ToolMessage, HumanMessage, AIMessage
from langchain_core.tools import BaseTool
from langgraph.graph import add_messages, StateGraph, START, END
from pydantic import BaseModel, Field
from langchain_openai import ChatOpenAI
from langgraph.prebuilt import ToolNode
class State(TypedDict):
messages: Annotated[list, add_messages]
class GaoDeWeatherInput(BaseModel):
city: str = Field(description="要查询天气的城市名称")
class GaoDeWeather(BaseTool):
"""
高德天气查询
"""
name: str = "高德天气查询"
description: str = "高德天气查询,输入城市名,返回该城市的天气情况,例如:北京"
args_schema: Type[BaseModel] = GaoDeWeatherInput
api_key: str
def _run(self, city):
# 与之前代码一样,可参考之前文章
...
def _set_env(var: str, value):
if not os.environ.get(var):
os.environ[var] = value
_set_env("TAVILY_API_KEY", "tvly-xxxx")
tavily_tool = TavilySearchResults(max_results=2)
weather_tool = GaoDeWeather(api_key='xxxx')
llm = ChatOpenAI(
model_name="qwen-turbo",
temperature=0.7,
max_tokens=1024,
top_p=1,
openai_api_key="sk-xxxx",
openai_api_base="https://dashscope.aliyuncs.com/compatible-mode/v1"
)
tools = [weather_tool, tavily_tool]
llm_with_tools = llm.bind_tools(tools)
toolnode = ToolNode(tools)
# 大模型执行节点
def chatbot(state: State):
return {"messages": [llm_with_tools.invoke(state["messages"])]}
# 定义一个条件节点
def condition_tools(state: State):
ai_message = state["messages"][-1]
if ai_message.tool_calls:
return "tools"
return END
graph_builder = StateGraph(State)
# 添加节点
graph_builder.add_node("chatbot", chatbot)
graph_builder.add_node("tools", toolnode)
# 添加边
graph_builder.add_edge(START, "chatbot")
graph_builder.add_conditional_edges("chatbot", condition_tools)
graph_builder.add_edge("tools", "chatbot")
app = graph_builder.compile()
inputs = {"messages": [HumanMessage(content="我今天要去上海,我想知道那边的天气如何?")]}
for result in app.stream(inputs, stream_mode="values"):
message = result.get("messages")[-1]
if isinstance(message, tuple):
print(message)
else:
message.pretty_print()
|
这里 chatbot 绑定了两个工具 [weather_tool, tavily_tool]
,当询问天气相关的内容是,大模型会根据 weather_tool 工具的参数定义推理出对应的参数。
| ================================ Human Message =================================
我今天要去上海,我想知道那边的天气如何?
================================== Ai Message ==================================
Tool Calls:
高德天气查询 (call_0f6e5e03e3244180a7c14b)
Call ID: call_0f6e5e03e3244180a7c14b
Args:
city: 上海
================================= Tool Message =================================
Name: 高德天气查询
[
{
"date": "2024-12-25",
"week": "3",
"dayweather": "阴",
"daytemp_float": "11.0",
"daywind": "北",
"nightweather": "多云",
"nighttemp_float": "6.0"
},
{
"date": "2024-12-26",
"week": "4",
"dayweather": "晴",
"daytemp_float": "11.0",
"daywind": "北",
"nightweather": "晴",
"nighttemp_float": "3.0"
},
...
]
================================== Ai Message ==================================
明天(2024年12月26日)在上海,天气预计会从阴转为晴,白天温度大约在11摄氏度左右,风向是北方。夜间天气预测为多云,气温会降到3摄氏度。记得带件外套,晚上可能会有点冷。
|
三、LangGraph 中的 ReAct agent
再次观察下面的工具执行流程图
这种在 llm 和 tools 之间相互调用是一种很常见的需求,所以 LangGraph 内部直接集成了这个agent,通过 langgraph.prebuilt.create_react_agent
方法简化上面的代码
| # 引入 create_react_agent
from langgraph.prebuilt import create_react_agent
tavily_tool = TavilySearchResults(max_results=2)
weather_tool = GaoDeWeather(api_key='xxxx')
tools = [weather_tool, tavily_tool]
llm = ChatOpenAI(
model_name="qwen-plus",
temperature=0.7,
max_tokens=1024,
top_p=1,
openai_api_key="sk-xxxx",
openai_api_base="https://dashscope.aliyuncs.com/compatible-mode/v1"
)
app = create_react_agent(llm, tools=tools)
inputs = {"messages": [HumanMessage(content="我今天要去上海,我想知道那边的天气如何?")]}
for result in app.stream(inputs, stream_mode="values"):
message = result.get("messages")[-1]
if isinstance(message, tuple):
print(message)
else:
message.pretty_print()
|
create_react_agent
方法,可以将上面的构建图的流程简化,简单到只需要传入大模型client 和工具列表即可, 这个方法内部会将工具绑定到llm 上,并且构建图, 你不必执行以下操作
- 无需定义条件节点
- 无需调用 add_node
- 无需调用 add_edge
- 无需定义 State
你只需要关注工具的定义。
上面定义了两个工具,天气查询和tavily 搜索,这次我问一个复杂的问题,看看这个ReAct 智能体如何解决我的问题
我今天要去上海出差,我应该穿什么衣服? 那边的迪士尼后天营业吗? 有什么好玩的推荐吗?
这次我使用了 qwen-plus
模型,之前一直使用的 qwen-turbo
的推理能力不太好,我们来看一下得到的回复
| ================================ Human Message =================================
我今天要去上海出差,我应该传什么衣服? 那边的迪士尼后天营业吗? 有什么好玩的推荐吗?
================================== Ai Message ==================================
Tool Calls:
高德天气查询 (call_60c891f13fd9497fae4205)
Call ID: call_60c891f13fd9497fae4205
Args:
city: 上海
================================= Tool Message =================================
Name: 高德天气查询
[
{
"date": "2024-12-25",
"week": "3",
"dayweather": "阴",
"daytemp_float": "11.0",
"daywind": "北",
"nightweather": "多云",
"nighttemp_float": "6.0"
},
{
"date": "2024-12-26",
"week": "4",
"dayweather": "晴",
"daytemp_float": "11.0",
"daywind": "北",
"nightweather": "晴",
"nighttemp_float": "3.0"
},
...
]
================================== Ai Message ==================================
Tool Calls:
tavily_search_results_json (call_b3499a3e15674626b8b7fd)
Call ID: call_b3499a3e15674626b8b7fd
Args:
query: 上海迪士尼后天营业吗
================================= Tool Message =================================
Name: tavily_search_results_json
[{"url": "http://sh.bendibao.com/tour/20221124/263251.shtm", "content": "11月24日,上海迪士尼乐园发布消息称:上海迪士尼乐园将于2022年11月25日起恢复运营。 11月25日,上海迪士尼乐园的运营时间为8:30-20:30。 请通过上海迪士尼度假区官方网站、App、官方微信公众号、预订服务中心或联系度假区官方旅游合作伙伴以查询上海迪士尼"}, {"url": "https://baijiahao.baidu.com/s?id=1748183310036681067", "content": "上海迪士尼今起暂时关闭!恢 复营业时间待定,官方明确了票务退改细则 ... 通过上海迪士尼度假区官方网站、App、微信公众号、上海迪士尼度假区预订服务中心、度假区飞猪官方旗舰店或京东官方旗舰店购买门票的游客,可以通过上海迪士尼度假区官方App提交"}]
================================== Ai Message ==================================
Tool Calls:
tavily_search_results_json (call_c28412f8aa53444d81de51)
Call ID: call_c28412f8aa53444d81de51
Args:
query: 上海有什么好玩
================================= Tool Message =================================
Name: tavily_search_results_json
[{"url": "https://markandhazyl.com/must-visit-places-in-shanghai/", "content": "上海景點推薦 1. 東方明珠電視塔 東方明珠電視塔 Oriental Pearl TV Tower. 這座外觀融合了宇宙、火箭和原子元素的立柱球體肯定是上海最具標誌性的建築之一了,位於上海浦東陸家嘴金融貿易區上,搭乘至地鐵『陸家嘴站』出站即可抵達,整體氣勢雄偉有東方元素卻又不失現代風格,除了外觀吸睛外"}, {"url": "https://hk.trip.com/blog/shanghai-attractions/", "content": "上海,這座充滿活力和魅力的城市,絕對會讓你心動不已!想像一下,走在外灘的濱江大道上,迎面吹來黃浦江的微風,眼前是璀璨的東方明珠塔和現代化的摩天大樓群,簡直美得讓人驚歎!想要在2024年玩轉上海嗎?這份最新的上海必去景點清單帶你去探秘最火打卡地,附上詳細攻略和貼心建議"}]
================================== Ai Message ==================================
根据上海的天气预报,今天(2024年12月25日)上海将是阴天,最高气温约11℃,夜间转为多云,最低温度降至6℃。风向主要是北风。考虑到后几天气温会有所下降,建议携带一些适合春秋季节的衣物,例如长袖衬衫、薄毛衣和一件外套。
关于上海迪士尼乐园的信息,根据最新资料,它会在2024年11月25日起恢复运营,所以后天(12月27日)是营业的。不过在前往之前,建议再次确认官方公告或通过官方网站查询最新的运营时间,以保证出行顺利。
至于好玩的地方推荐,上海有很多值得一游的景点:
1. 东方明珠广播电视塔 - 这里不光可以俯瞰整个上海市区的美景,本身也是上海的标志性建筑之一。
2. 外滩 - 漫步于黄浦江边,欣赏对岸陆家嘴金融区的现代化高楼大厦。
3. 上海迪士尼度假区 - 如果你喜欢迪士尼的氛围和故事,这是一个不容错过的好地方。
除此之外,还有很多其他有趣的景点和活动等着你去发现。希望这些建议对你有帮助,祝你在上海出差愉快!
|
我们根据输出看一下这个agent 的执行流程
- 第一次调用大模型返回的是 Tool Calls,需要调用 『高德天气查询』 工具
- tools 节点调用完高德天气查询以后,返回上海的气象信息
- 带上上海的气象信息,再次请求大模型,这次依然返回 Tool Calls, 这次需要调用的是 tavily_search_results_json, 搜索词是 "上海迪士尼后天营业吗"
- tools 节点调用 tavily 工具进行搜索,得到检索结果
- 再次调用大模型,此时大模型推理出还需要调用 tavily_search_results_json, 搜索词是 "上海有什么好玩"
- tools 继续调用 tavily 工具执行搜索
- 再次调用大模型,这时大模型意识到,所有的问题都得到了答案,该进行最终的归纳总结了
- 最后大模型根据前面的问题和答案,生成了最终的回复内容,此时,没有再返回 Tool Calls ,langGraph 走到了
__end__
, 结束了整个图的调用
如何添加 system prompt ?
有时我我们需要添加系统消息,如让大模型使用英文回答,可以通过 state_modifier
参数将系统消息传进来。
app = create_react_agent(llm, tools=tools, state_modifier="请用英文回答")
注意问题
循环次数
虽然 ReAct agent 可以完成一些复杂的问题,但是由于需要在 llm 和 tools 反复调用,LangGraph 不允许无限次的调用,默认最多可以反复调用25次,如果超过25次还没有结束,则会抛出异常。
可以通过config 参数在执行graph 时限制递归次数
| graph.invoke(inputs, config={"recursion_limit": 5}})
|
总结
本文先介绍了大模型中的 ReAct 理论内容,之后通过LangGraph 实现了 ReAct 智能体,先使用自定义工具调用的方式,之后又使用 LangGraph 中内置的 create_react_agent
方法创建,create_react_agent 会极大的方便创建 ReAct 智能体。