Skip to content

从零开始创建 ReAct 代理(函数式 API)

前提条件

本指南假定您熟悉以下内容:

本指南演示如何使用 LangGraph 的 函数式 API 实现一个 ReAct 代理。

ReAct 代理是一种 工具调用代理,其操作如下:

  1. 向聊天模型发出查询;
  2. 如果模型未生成任何 工具调用,我们返回模型的响应。
  3. 如果模型生成了工具调用,我们使用可用工具执行这些工具调用,将它们作为 工具消息 添加到我们的消息列表中,并重复该过程。

这是一个简单且多功能的设置,可以通过添加记忆、人机协作功能和其他特性进行扩展。有关示例,请参阅专门的 如何操作指南

设置

首先,让我们安装所需的包并设置我们的 API 密钥:

pip install -U langgraph langchain-openai
import getpass
import os


def _set_env(var: str):
    if not os.environ.get(var):
        os.environ[var] = getpass.getpass(f"{var}: ")


_set_env("OPENAI_API_KEY")

设置 LangSmith 以获得更好的调试体验

注册 LangSmith 可以快速发现并解决您 LangGraph 项目中的问题,提升性能。LangSmith 允许您使用追踪数据来调试、测试和监控使用 LangGraph 构建的 LLM 应用 —— 有关如何入门的更多信息,请阅读 文档

创建 ReAct 代理

现在你已经安装了所需的包并设置了环境变量,我们可以创建我们的代理。

定义模型和工具

首先,我们定义将用于示例的工具和模型。在此示例中,我们将使用一个占位符工具,该工具可以获取某个位置的天气描述。

对于这个示例,我们将使用一个 OpenAI 聊天模型,但任何支持工具调用的模型 支持工具调用 都可以满足需求。

API Reference: ChatOpenAI | tool

from langchain_openai import ChatOpenAI
from langchain_core.tools import tool

model = ChatOpenAI(model="gpt-4o-mini")


@tool
def get_weather(location: str):
    """Call to get the weather from a specific location."""
    # This is a placeholder for the actual implementation
    if any([city in location.lower() for city in ["sf", "san francisco"]]):
        return "It's sunny!"
    elif "boston" in location.lower():
        return "It's rainy!"
    else:
        return f"I am not sure what the weather is in {location}"


tools = [get_weather]

定义任务

接下来,我们定义将要执行的 任务。这里有两个不同的任务:

  1. 调用模型:我们希望使用消息列表来查询我们的聊天模型。
  2. 调用工具:如果模型生成了工具调用,我们希望执行这些工具调用。

API Reference: ToolMessage | entrypoint | task

from langchain_core.messages import ToolMessage
from langgraph.func import entrypoint, task

tools_by_name = {tool.name: tool for tool in tools}


@task
def call_model(messages):
    """Call model with a sequence of messages."""
    response = model.bind_tools(tools).invoke(messages)
    return response


@task
def call_tool(tool_call):
    tool = tools_by_name[tool_call["name"]]
    observation = tool.invoke(tool_call["args"])
    return ToolMessage(content=observation, tool_call_id=tool_call["id"])

定义入口点

我们的 entrypoint 将负责处理这两个任务的编排。如上所述,当我们的 call_model 任务生成工具调用时,call_tool 任务将为每个调用生成响应。我们将所有消息追加到一个消息列表中。

Tip

请注意,由于任务返回类似 future 的对象,以下实现将以并行方式执行工具。

API Reference: add_messages

from langgraph.graph.message import add_messages


@entrypoint()
def agent(messages):
    llm_response = call_model(messages).result()
    while True:
        if not llm_response.tool_calls:
            break

        # Execute tools
        tool_result_futures = [
            call_tool(tool_call) for tool_call in llm_response.tool_calls
        ]
        tool_results = [fut.result() for fut in tool_result_futures]

        # Append to message list
        messages = add_messages(messages, [llm_response, *tool_results])

        # Call model again
        llm_response = call_model(messages).result()

    return llm_response

用法

要使用我们的代理,我们通过消息列表来调用它。根据我们的实现,这些可以是 LangChain message 对象或 OpenAI 风格的字典:

user_message = {"role": "user", "content": "What's the weather in san francisco?"}
print(user_message)

for step in agent.stream([user_message]):
    for task_name, message in step.items():
        if task_name == "agent":
            continue  # Just print task updates
        print(f"\n{task_name}:")
        message.pretty_print()
{'role': 'user', 'content': "What's the weather in san francisco?"}

call_model:
================================== Ai Message ==================================
Tool Calls:
  get_weather (call_tNnkrjnoz6MNfCHJpwfuEQ0v)
 Call ID: call_tNnkrjnoz6MNfCHJpwfuEQ0v
  Args:
    location: san francisco

call_tool:
================================= Tool Message =================================

It's sunny!

call_model:
================================== Ai Message ==================================

The weather in San Francisco is sunny!

太棒了!图表正确地调用了 get_weather 工具,并在从工具接收信息后向用户做出了响应。查看 LangSmith 追踪 这里

添加线程级持久化

添加 线程级持久化 可让我们通过我们的代理支持对话式体验:后续调用将追加到之前的消息列表中,保留完整的对话上下文。

要为我们的代理添加线程级持久化:

  1. 选择一个 检查点器(checkpointer):这里我们将使用 MemorySaver,这是一个简单的内存检查点器。
  2. 更新我们的入口点以接受前一条消息状态作为第二个参数。此处,我们只需将消息更新追加到之前的消息序列中。
  3. 选择从工作流返回哪些值,并通过 entrypoint.final 指定哪些值将被检查点器保存为 previous(可选)

API Reference: MemorySaver

from langgraph.checkpoint.memory import MemorySaver

checkpointer = MemorySaver()


@entrypoint(checkpointer=checkpointer)
def agent(messages, previous):
    if previous is not None:
        messages = add_messages(previous, messages)

    llm_response = call_model(messages).result()
    while True:
        if not llm_response.tool_calls:
            break

        # Execute tools
        tool_result_futures = [
            call_tool(tool_call) for tool_call in llm_response.tool_calls
        ]
        tool_results = [fut.result() for fut in tool_result_futures]

        # Append to message list
        messages = add_messages(messages, [llm_response, *tool_results])

        # Call model again
        llm_response = call_model(messages).result()

    # Generate final response
    messages = add_messages(messages, llm_response)
    return entrypoint.final(value=llm_response, save=messages)

我们现在在运行应用程序时需要传入一个配置。该配置将指定会话线程的标识符。

Tip

有关线程级持久化的更多信息,请阅读我们的概念页面操作指南

config = {"configurable": {"thread_id": "1"}}

我们以与之前相同的方式启动一个线程,这次传入配置:

user_message = {"role": "user", "content": "What's the weather in san francisco?"}
print(user_message)

for step in agent.stream([user_message], config):
    for task_name, message in step.items():
        if task_name == "agent":
            continue  # Just print task updates
        print(f"\n{task_name}:")
        message.pretty_print()
{'role': 'user', 'content': "What's the weather in san francisco?"}

call_model:
================================== Ai Message ==================================
Tool Calls:
  get_weather (call_lubbUSdDofmOhFunPEZLBz3g)
 Call ID: call_lubbUSdDofmOhFunPEZLBz3g
  Args:
    location: San Francisco

call_tool:
================================= Tool Message =================================

It's sunny!

call_model:
================================== Ai Message ==================================

The weather in San Francisco is sunny!

当我们在进行后续对话时,模型会利用之前的上下文推断我们是在询问天气:

user_message = {"role": "user", "content": "How does it compare to Boston, MA?"}
print(user_message)

for step in agent.stream([user_message], config):
    for task_name, message in step.items():
        if task_name == "agent":
            continue  # Just print task updates
        print(f"\n{task_name}:")
        message.pretty_print()
{'role': 'user', 'content': 'How does it compare to Boston, MA?'}

call_model:
================================== Ai Message ==================================
Tool Calls:
  get_weather (call_8sTKYAhSIHOdjLD5d6gaswuV)
 Call ID: call_8sTKYAhSIHOdjLD5d6gaswuV
  Args:
    location: Boston, MA

call_tool:
================================= Tool Message =================================

It's rainy!

call_model:
================================== Ai Message ==================================

Compared to San Francisco, which is sunny, Boston, MA is experiencing rainy weather.

LangSmith trace中,我们可以看到每次模型调用都保留了完整的对话上下文。