Skip to main content
Alpha 版本提示: 本文档涵盖的是 v1-alpha 版本。内容尚不完整,且可能随时变更。如需查阅最新稳定版本,请参阅 v0 版本的 LangChain Python 文档LangChain JavaScript 文档
许多人工智能应用程序通过自然语言与用户交互。然而,某些用例要求模型能够直接与外部系统(例如 API、数据库或文件系统)进行交互,并使用结构化输入。 工具是代理调用以执行操作的组件。它们通过允许模型通过明确定义的输入和输出与外部世界交互,从而扩展了模型的能力。工具封装了一个可调用函数及其输入模式。这些工具可以传递给兼容的聊天模型,使模型能够决定是否调用某个工具以及使用哪些参数。在这种情况下,工具调用使模型能够生成符合指定输入模式的请求。

创建工具

基本工具定义

创建工具最简单的方法是使用 @tool 装饰器。默认情况下,函数的文档字符串将成为工具的描述,帮助模型理解何时使用该工具:
from langchain_core.tools import tool

@tool
def search_database(query: str, limit: int = 10) -> str:
    """在客户数据库中搜索匹配查询的记录。

    参数:
        query: 要查找的搜索词
        limit: 返回结果的最大数量
    """
    return f"找到 {limit} 个关于 '{query}' 的结果"
类型提示是必需的,因为它们定义了工具的输入模式。文档字符串应信息丰富且简洁,以帮助模型理解工具的目的。

自定义工具属性

自定义工具名称

默认情况下,工具名称来自函数名称。当您需要更具描述性的名称时,请覆盖它:
@tool("web_search")  # 自定义名称
def search(query: str) -> str:
    """在网络上搜索信息。"""
    return f"搜索结果: {query}"

print(search.name)  # web_search

自定义工具描述

覆盖自动生成的工具描述,以便为模型提供更清晰的指导:
@tool("calculator", description="执行算术计算。用于任何数学问题。")
def calc(expression: str) -> str:
    """计算数学表达式。"""
    return str(eval(expression))

高级模式定义

使用 Pydantic 模型或 JSON 模式定义复杂输入:
from pydantic import BaseModel, Field
from typing import Literal

class WeatherInput(BaseModel):
    """天气查询的输入。"""
    location: str = Field(description="城市名称或坐标")
    units: Literal["celsius", "fahrenheit"] = Field(
        default="celsius",
        description="温度单位偏好"
    )
    include_forecast: bool = Field(
        default=False,
        description="是否包含5天预报"
    )

@tool(args_schema=WeatherInput)
def get_weather(location: str, units: str = "celsius", include_forecast: bool = False) -> str:
    """获取当前天气及可选预报。"""
    temp = 22 if units == "celsius" else 72
    result = f"{location} 当前天气: {temp}{units[0].upper()}"
    if include_forecast:
        result += "\n未来5天: 晴天"
    return result

与代理一起使用工具

代理通过添加推理循环、状态管理和多步执行,超越了简单的工具绑定。
要查看如何与代理一起使用工具的示例,请参阅 代理

高级工具模式

ToolNode

ToolNode 是一个预构建的 LangGraph 组件,用于处理代理工作流中的工具调用。它与 create_agent() 无缝协作,提供高级工具执行控制、内置并行性和错误处理。

配置选项

ToolNode 接受以下参数:
from langchain.agents import ToolNode

tool_node = ToolNode(
    tools=[...],              # 工具或可调用对象的列表
    handle_tool_errors=True,  # 错误处理配置
    ...
)
tools
required
此节点可以执行的工具列表。可以包括:
  • LangChain @tool 装饰的函数
  • 具有适当类型提示和文档字符串的可调用对象(例如函数)
handle_tool_errors
控制如何处理工具执行失败。 可以是:
  • bool
  • str
  • Callable[..., str]
  • type[Exception]
  • tuple[type[Exception], ...]
默认值:内部 _default_handle_tool_errors

错误处理策略

ToolNode 通过其 handle_tool_errors 属性提供内置的工具执行错误处理。 要自定义错误处理行为,您可以将 handle_tool_errors 配置为布尔值、字符串、可调用对象、异常类型或异常类型的元组:
  • True:捕获所有错误并返回包含异常详细信息的默认错误模板的 ToolMessage。
  • str:捕获所有错误并返回包含此自定义错误消息字符串的 ToolMessage。
  • type[Exception]:仅捕获指定类型的异常并返回其默认错误消息。
  • tuple[type[Exception], ...]:仅捕获指定类型的异常并返回它们的默认错误消息。
  • Callable[..., str]:捕获与可调用对象签名匹配的异常并返回调用它时的字符串结果。
  • False:完全禁用错误处理,允许异常传播。
handle_tool_errors 默认为可调用对象 _default_handle_tool_errors,该对象:
  • 捕获工具调用错误 ToolInvocationError(由于模型提供的无效参数)并返回描述性错误消息
  • 忽略工具执行错误(它们将使用模板字符串 TOOL_CALL_ERROR_TEMPLATE = "错误: {error}\n 请修复您的错误。" 重新引发)
使用不同错误处理策略的示例:
# 使用默认错误消息模板字符串重试所有异常类型
tool_node = ToolNode(tools=[my_tool], handle_tool_errors=True)

# 使用自定义消息字符串重试所有异常类型
tool_node = ToolNode(
    tools=[my_tool],
    handle_tool_errors="我遇到了问题。请尝试重新表述您的请求。"
)

# 仅在 ValueError 时使用自定义消息重试,否则引发异常
def handle_errors(e: ValueError) -> str:
    return "提供的输入无效"

tool_node = ToolNode([my_tool], handle_tool_errors=handle_errors)

# 仅在 ValueError 和 KeyError 时使用默认错误消息模板字符串重试,否则引发异常
tool_node = ToolNode(
    tools=[my_tool],
    handle_tool_errors=(ValueError, KeyError)
)

与 create_agent() 一起使用

建议您在阅读本节之前先熟悉 create_agent()阅读有关代理的更多信息
将配置好的 ToolNode 直接传递给 create_agent()
from langchain_openai import ChatOpenAI
from langchain.agents import ToolNode, create_agent
import random

@tool
def fetch_user_data(user_id: str) -> str:
    """从数据库中获取用户数据。"""
    if random.random() > 0.7:
        raise ConnectionError("数据库连接超时")
    return f"用户 {user_id}: John Doe, [email protected], 活跃"

@tool
def process_transaction(amount: float, user_id: str) -> str:
    """处理金融交易。"""
    if amount > 10000:
        raise ValueError(f"金额 {amount} 超过最大限额 10000")
    return f"为用户 {user_id} 处理了 ${amount}"

def handle_errors(e: Exception) -> str:
    if isinstance(e, ConnectionError):
        return "数据库当前过载,但可以安全重试。请使用相同的参数再试一次。"
    elif isinstance(e, ValueError):
        return f"错误: {e}. 尝试以较小的金额处理交易。"
    return f"错误: {e}. 请再试一次。"

tool_node = ToolNode(
    tools=[fetch_user_data, process_transaction],
    handle_tool_errors=handle_errors
)

agent = create_agent(
    model=ChatOpenAI(model="gpt-4o"),
    tools=tool_node,
    prompt="您是一位金融助手。"
)

agent.invoke({
    "messages": [{"role": "user", "content": "为 user123 处理一笔 15000 美元的付款。生成收据电子邮件并发送给用户。"}]
})
当您将 ToolNode 传递给 create_agent() 时,代理将使用您的确切配置,包括错误处理、自定义名称和标签。当您需要对工具执行行为进行细粒度控制时,这非常有用。

状态、上下文和内存

state:代理在其执行过程中维护状态 - 这包括消息、自定义字段以及您的工具需要跟踪的任何数据。状态在图中流动,并且可以被工具访问和修改。
InjectedState:一种注解,允许工具在不向 LLM 暴露的情况下访问当前图状态。这使得工具可以读取诸如消息历史或自定义状态字段之类的信息,同时保持工具模式的简洁性。
工具可以使用 InjectedState 注解访问当前图状态:
from typing_extensions import Annotated
from langchain.agents.tool_node import InjectedState

# 访问当前对话状态
@tool
def summarize_conversation(
    state: Annotated[dict, InjectedState]
) -> str:
    """总结到目前为止的对话。"""
    messages = state["messages"]

    human_msgs = sum(1 for m in messages if m.__class__.__name__ == "HumanMessage")
    ai_msgs = sum(1 for m in messages if m.__class__.__name__ == "AIMessage")
    tool_msgs = sum(1 for m in messages if m.__class__.__name__ == "ToolMessage")

    return f"对话包含 {human_msgs} 条用户消息,{ai_msgs} 条 AI 响应,以及 {tool_msgs} 条工具结果"

# 访问自定义状态字段
@tool
def get_user_preference(
    pref_name: str,
    preferences: Annotated[dict, InjectedState("user_preferences")]  # InjectedState 参数对模型不可见
) -> str:
    """获取用户偏好值。"""
    return preferences.get(pref_name, "未设置")
状态注入的参数对模型隐藏。在上面的示例中,模型仅在工具模式中看到 pref_name - preferences 包含在请求中。
Command:工具可以使用的一种特殊返回类型,用于更新代理的状态或控制图的执行流程。工具不仅可以返回数据,还可以返回 Command 来修改状态或将代理定向到特定节点。
使用返回 Command 的工具更新代理状态:
from langgraph.types import Command
from langchain_core.messages import RemoveMessage
from langgraph.graph.message import REMOVE_ALL_MESSAGES
from langchain_core.tools import tool, InjectedToolCallId
from typing_extensions import Annotated

# 通过删除所有消息来更新对话历史
@tool
def clear_conversation() -> Command:
    """清除对话历史。"""

    return Command(
        update={
            "messages": [RemoveMessage(id=REMOVE_ALL_MESSAGES)],
        }
    )

# 更新代理状态中的 user_name
@tool
def update_user_name(
    new_name: str,
    tool_call_id: Annotated[dict, InjectedToolCallId]
) -> Command:
    """更新用户名。"""
    return Command(update={"user_name": new_name})
runtime:您的代理的执行环境,包含在代理执行过程中持续存在的不可变配置和上下文数据(例如,用户 ID、会话详细信息或应用程序特定配置)。
工具可以通过 get_runtime 访问代理的运行时上下文:
from dataclasses import dataclass
from langchain_openai import ChatOpenAI
from langchain.agents import create_agent
from langchain_core.tools import tool
from langgraph.runtime import get_runtime

USER_DATABASE = {
    "user123": {
        "name": "Alice Johnson",
        "account_type": "Premium",
        "balance": 5000,
        "email": "[email protected]"
    },
    "user456": {
        "name": "Bob Smith",
        "account_type": "Standard",
        "balance": 1200,
        "email": "[email protected]"
    }
}

@dataclass
class UserContext:
    user_id: str

@tool
def get_account_info() -> str:
    """获取当前用户的账户信息。"""
    runtime = get_runtime(UserContext)
    user_id = runtime.context.user_id

    if user_id in USER_DATABASE:
        user = USER_DATABASE[user_id]
        return f"账户持有人: {user['name']}\n类型: {user['account_type']}\n余额: ${user['balance']}"
    return "未找到用户"

model = ChatOpenAI(model="gpt-4o")
agent = create_agent(
    model,
    tools=[get_account_info],
    context_schema=UserContext,
    prompt="您是一位金融助手。"
)

result = agent.invoke(
    {"messages": [{"role": "user", "content": "我的当前余额是多少?"}]},
    context=UserContext(user_id="user123")
)
store:LangChain 的持久层。代理的长期记忆存储,例如跨对话存储的用户特定或应用程序特定数据。
工具可以通过 get_store 访问代理的存储:
from langgraph.config import get_store

@tool
def get_user_info(user_id: str) -> str:
    """查找用户信息。"""
    store = get_store()
    user_info = store.get(("users",), user_id)
    return str(user_info.value) if user_info else "未知用户"
要更新长期记忆,您可以使用 InMemoryStore.put() 方法。以下是一个跨会话的持久内存完整示例:
from typing import Any
from langgraph.config import get_store
from langgraph.store.memory import InMemoryStore
from langchain.agents import create_agent
from langchain_core.tools import tool

@tool
def get_user_info(user_id: str) -> str:
    """查找用户信息。"""
    store = get_store()
    user_info = store.get(("users",), user_id)
    return str(user_info.value) if user_info else "未知用户"

@tool
def save_user_info(user_id: str, user_info: dict[str, Any]) -> str:
    """保存用户信息。"""
    store = get_store()
    store.put(("users",), user_id, user_info)
    return "成功保存用户信息。"

store = InMemoryStore()
agent = create_agent(
    model,
    tools=[get_user_info, save_user_info],
    store=store
)

# 第一个会话:保存用户信息
agent.invoke({
    "messages": [{"role": "user", "content": "保存以下用户:userid: abc123, name: Foo, age: 25, email: [email protected]"}]
})

# 第二个会话:获取用户信息
agent.invoke({
    "messages": [{"role": "user", "content": "获取 ID 为 'abc123' 的用户信息"}]
})
# 以下是 ID 为 "abc123" 的用户信息:
# - 姓名: Foo
# - 年龄: 25
# - 电子邮件: [email protected]