# LangGraph 实现 ReACT 架构 Agent

# 01. 预构建的 ReACT 智能体

在 LangGraph 中除了能使用基础组件(节点、边、数据状态)来构建 Agent 智能体,这也是 LangGraph 自由性高的一个优点,我们还可以使用 LangGraph 预构建的代理来快速创建智能体,例如: ReACT智能体 亦或者 工具调用智能体 。

使用技巧也非常简单,导入对应的 预构建函数 然后调用函数即可,例如:

from langgraph.prebuilt.chat_agent_executor import create_react_agent, create_tool_calling_executor
1

不过在 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", "帮我绘制一幅鲨鱼在天上飞的图片")]}))
1
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![鲨鱼在天上飞](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)', 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})]}
1

# 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
1
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}
1
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)
1
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"
1
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,记录工具入参、出参、耗时和异常