# LangGraph 实现 ReACT 架构 Agent
# 01. 预构建的 ReACT 智能体
在 LangGraph 中除了能使用基础组件(节点、边、数据状态)来构建 Agent 智能体,这也是 LangGraph 自由性高的一个优点,我们还可以使用 LangGraph 预构建的代理来快速创建智能体,例如: ReACT智能体 亦或者 工具调用智能体 。
使用技巧也非常简单,导入对应的 预构建函数 然后调用函数即可,例如:
from langgraph.prebuilt.chat_agent_executor import create_react_agent, create_tool_calling_executor
不过在 LangGraph 底层,目前预构建的 ReACT 智能体目前也是基于 函数调用 的,并且在 0.3.0 版本会被剔除(后续会更新优化预构建智能体,可以持续留意关注),但是其封装思路仍然非常值得借鉴。
完整实例如下:
import dotenv
from langchain_community.tools import GoogleSerperRun
from langchain_community.tools.openai_dalle_image_generation import OpenAIDALLEImageGenerationTool
from langchain_community.utilities import GoogleSerperAPIWrapper
from langchain_community.utilities.dalle_image_generator import DallEAPIWrapper
from langchain_core.pydantic_v1 import BaseModel, Field
from langchain_openai import ChatOpenAI
from langgraph.prebuilt.chat_agent_executor import create_react_agent
dotenv.load_dotenv()
class GoogleSerperArgsSchema(BaseModel):
query: str = Field(description="执行谷歌搜索的查询语句")
class DallEArgsSchema(BaseModel):
query: str = Field(description="输入应该是生成图像的文本提示(prompt)")
google_serper = GoogleSerperRun(
name="google_serper",
description=(
"一个低成本的谷歌搜索API。"
"当你需要回答有关时事的问题时,可以调用该工具。"
"该工具的输入是搜索查询语句。"
),
args_schema=GoogleSerperArgsSchema,
api_wrapper=GoogleSerperAPIWrapper(),
)
dalle = OpenAIDALLEImageGenerationTool(
name="openai_dalle",
api_wrapper=DallEAPIWrapper(model="dall-e-3"),
args_schema=DallEArgsSchema,
)
tools = [google_serper, dalle]
model = ChatOpenAI(model="gpt-4o-mini", temperature=0)
agent = create_react_agent(
model=model,
tools=tools
)
print(agent.invoke({"messages": [("human", "帮我绘制一幅鲨鱼在天上飞的图片")]}))
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
输出内容:
{'messages': [HumanMessage(content='帮我绘制一幅鲨鱼在天上飞的图片', id='1e1a56dc-2e17-47e9-ab45-1d6154079cde'), AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_RSRd2xhwjY6SOmiQv4LkCulc', 'function': {'arguments': '{"query":"A surreal scene of a shark flying in the sky, surrounded by fluffy clouds and a bright blue sky. The shark appears to be gliding smoothly, with sunlight reflecting off its scales, creating a whimsical and dreamlike atmosphere."}', 'name': 'openai_dalle'}, 'type': 'function'}]}, response_metadata={'token_usage': {'completion_tokens': 60, 'prompt_tokens': 168, 'total_tokens': 228}, 'model_name': 'gpt-4o-mini', 'system_fingerprint': 'fp_80a1bad4c7', 'finish_reason': 'tool_calls', 'logprobs': None}, id='run-2e105a61-6cef-4716-8b0a-df9629289a95-0', tool_calls=[{'name': 'openai_dalle', 'args': {'query': 'A surreal scene of a shark flying in the sky, surrounded by fluffy clouds and a bright blue sky. The shark appears to be gliding smoothly, with sunlight reflecting off its scales, creating a whimsical and dreamlike atmosphere.'}, 'id': 'call_RSRd2xhwjY6SOmiQv4LkCulc', 'type': 'tool_call'}], usage_metadata={'input_tokens': 168, 'output_tokens': 60, 'total_tokens': 228}), ToolMessage(content='https://dalleproduse.blob.core.windows.net/private/images/a1793f8c-a319-4b41-9c17-0530bdd7b996/generated_00.png?se=2024-08-23T06%3A39%3A10Z&sig=HoH1JnIJHtnxOHQqS2wAHHH%2FduLzQvh0nCN0FJBy7M8%3D&ske=2024-08-29T03%3A57%3A46Z&skoid=09ba021e-c417-441c-b203-c81e5dcd7b7f&sks=b&skt=2024-08-22T03%3A57%3A46Z&sktid=33e01921-4d64-4f8c-a055-5bdaffd5e33d&skv=2020-10-02&sp=r&spr=https&sr=b&sv=2020-10-02', name='openai_dalle', id='eabd6566-1494-4190-b25d-7d52f9a67600', tool_call_id='call_RSRd2xhwjY6SOmiQv4LkCulc'), AIMessage(content='这是您请求的画面:一只鲨鱼在天上飞翔的图片。您可以点击下面的链接查看这幅图。\n\n', response_metadata={'token_usage': {'completion_tokens': 266, 'prompt_tokens': 462, 'total_tokens': 728}, 'model_name': 'gpt-4o-mini', 'system_fingerprint': 'fp_80a1bad4c7', 'finish_reason': 'stop', 'logprobs': None}, id='run-728b9bab-383f-4437-9238-6077b185a495-0', usage_metadata={'input_tokens': 462, 'output_tokens': 266, 'total_tokens': 728})]}
# 02. LangGraph 其他预构建组件
除了 create_react_agent() 预构建组件,在 LangGraph 中其实还内置了一些高频使用的 预构建组件 ,涵盖了 MessagesState 、 ToolNode 、 ValidationNode 和 injectedState 等。
其中 MessagesState 就是我们一直在定义的自定义状态,在 LangGraph 内部已经帮我们封装好了,导入后可以直接使用,如果需要除了消息外的其他信息,可以继承该类进行重写:
class MessagesState(TypedDict):
messages: Annotated[list[AnyMessage], add_messages]
from langgraph.graph import MessagesState
2
3
4
5
而 ToolNode 节点其实就是工具执行节点,该节点会自动执行 数据状态 中最后一条消息中的工具调用信息,其实就是我们上节课所使用的 tool_executor() 函数,输出是一个 ToolMessages 列表,使用技巧也非常简单,实例化该类并传递工具列表即可。
该类等价于以下函数:
tools_by_name = {tool.name: tool for tool in tools}
def tool_node(state: dict):
result = []
for tool_call in state["messages"][-1].tool_calls:
tool = tools_by_name[tool_call["name"]]
observation = tool.invoke(tool_call["args"])
result.append(ToolMessage(content=observation, tool_call_id=tool_call["id"]))
return {"messages": result}
2
3
4
5
6
7
8
ValidationNode 这个类用来校验最后一个 AIMessage 中所有工具请求是否正确,通常用于校验 LLM 的结构化输出是否正确,该类在实例化的时候,需要传递 Pydantic模型 作为需要校验的类,使用示例如下:
class SelectNumber(BaseModel):
a: int
@validator("a")
def a_must_be_meaningful(cls, v):
if v != 37:
raise ValueError("Only 37 is allowed")
return v
builder = MessageGraph()
llm = ChatAnthropic(model="claude-3-haiku-20240307").bind_tools([SelectNumber])
builder.add_node("model", llm)
builder.add_node("validation", ValidationNode([SelectNumber]))
builder.add_edge(START, "model")
def should_validate(state: list) -> Literal["validation", "__end__"]:
if state[-1].tool_calls:
return "validation"
return END
builder.add_conditional_edges("model", should_validate)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
而 InjectedState 其实和我们在 LLMOps 项目中使用的 injector 依赖注入包非常接近,主要用于在 工具 上注入 数据状态 ,这样在工具中也可以实际获取到 图架构应用 的 数据状态 ,并且使用 Annotated 装饰的参数不会被视为是工具的参数,LLM 在执行函数调用的过程中,不会生成该参数,使用示例如下:
from typing_extensions import Annotated, TypedDict
class AgentState(TypedDict):
messages: List[BaseMessage]
foo: str
@tool
def state_tool(x: int, state: Annotated[dict, InjectedState]) -> str:
'''Do something with state.'''
if len(state["messages"]) > 2:
return state["foo"] + str(x)
else:
return "not enough messages"
2
3
4
5
6
7
8
9
10
11
12
13
# 最新版 LangChain 用法提示
新版 LangChain 更推荐用 LCEL、
Runnable、ChatModel.bind_tools()、结构化输出和 LangGraph 来组织复杂链路;老式Chain、部分AgentExecutor写法可以读懂,但新项目应优先选择更清晰的图或 Runnable 编排。工具调用相关代码要区分两层:模型是否原生支持 tool/function calling,以及业务侧如何定义工具 schema、参数校验、错误兜底和观测日志。
如果示例中的导入路径和你当前安装版本不同,优先查当前版本包内导出位置;常见迁移方向是从
langchain拆到langchain-core、langchain-community、langchain-openai、langgraph等包。
# 拓展
工具或插件不要只看能不能调通,更要看是否可观测、可限流、可重试、可审计。联网类工具还要处理超时、空结果、搜索噪声和结果时效性。
Agent 场景里,Prompt 只是调度策略的一部分;工具描述、参数 schema、历史状态、错误反馈、停止条件和人工介入点同样会影响最终稳定性。
# 常见问题
为什么模型没有调用工具?常见原因是工具描述不清晰、参数 schema 过宽或过窄、用户问题不需要工具、模型本身不支持工具调用,或者工具绑定位置不对。
为什么工具调用后回答仍然不准?先看工具返回是否正确,再看工具结果是否被放回模型上下文,最后检查输出解析、历史消息和异常兜底是否覆盖了真实错误。
# 面试题
解释函数调用、工具调用和 Agent 的区别。
LangChain 中 tool schema 的作用是什么?为什么参数校验对生产环境很重要?
ReACT Agent 和 tool-calling Agent 的核心差异是什么?分别适合什么场景?
LangGraph 相比 LCEL 更适合解决哪些复杂编排问题?
# 生产问题排查
| 问题 | 常见原因 | 处理方式 |
|---|---|---|
| 工具没有被调用 | 工具描述弱、绑定失败、模型不支持 | 打印绑定后的模型配置,补充工具描述,换用支持工具调用的模型 |
| 参数格式错误 | schema 设计不清晰,模型生成字段不稳定 | 使用 Pydantic/JSON Schema 校验,失败后把错误反馈给模型重试 |
| 联网结果不可用 | 搜索为空、接口超时、命中低质量页面 | 增加超时、重试、结果过滤、来源白名单和降级回答 |
| Agent 循环不停止 | 缺少终止条件或工具返回被误判 | 设置最大迭代次数,记录每轮 thought/action/observation,增加停止规则 |
| 线上难以复现 | 缺少输入、工具请求和模型响应日志 | 给每次调用加 trace id,记录工具入参、出参、耗时和异常 |