> ## Documentation Index
> Fetch the complete documentation index at: https://langchain.idochub.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# 工具

<Warning>
  **Alpha 版本提示：** 本文档涵盖的是 **v1-alpha** 版本。内容尚不完整，且可能随时变更。

  如需查阅最新稳定版本，请参阅 v0 版本的 [LangChain Python 文档](https://python.langchain.com/docs/introduction/) 或 [LangChain JavaScript 文档](https://js.langchain.com/docs/introduction/)。
</Warning>

许多人工智能应用程序通过自然语言与用户交互。然而，某些用例要求模型能够直接与外部系统（例如 API、数据库或文件系统）进行交互，并使用结构化输入。

工具是[代理](/oss/python/langchain/agents)调用以执行操作的组件。它们通过允许模型通过明确定义的输入和输出与外部世界交互，从而扩展了模型的能力。工具封装了一个可调用函数及其输入模式。这些工具可以传递给兼容的[聊天模型](/oss/python/langchain/models)，使模型能够决定是否调用某个工具以及使用哪些参数。在这种情况下，工具调用使模型能够生成符合指定输入模式的请求。

## 创建工具

### 基本工具定义

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

```python wrap theme={null}
from langchain_core.tools import tool

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

    参数：
        query: 要查找的搜索词
        limit: 返回结果的最大数量
    """
    return f"找到 {limit} 个关于 '{query}' 的结果"
```

类型提示是**必需的**，因为它们定义了工具的输入模式。文档字符串应信息丰富且简洁，以帮助模型理解工具的目的。

### 自定义工具属性

#### 自定义工具名称

默认情况下，工具名称来自函数名称。当您需要更具描述性的名称时，请覆盖它：

```python wrap theme={null}
@tool("web_search")  # 自定义名称
def search(query: str) -> str:
    """在网络上搜索信息。"""
    return f"搜索结果: {query}"

print(search.name)  # web_search
```

#### 自定义工具描述

覆盖自动生成的工具描述，以便为模型提供更清晰的指导：

```python wrap theme={null}
@tool("calculator", description="执行算术计算。用于任何数学问题。")
def calc(expression: str) -> str:
    """计算数学表达式。"""
    return str(eval(expression))
```

### 高级模式定义

使用 Pydantic 模型或 JSON 模式定义复杂输入：

<CodeGroup>
  ```python wrap Pydantic 模型 theme={null}
  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
  ```

  ```python wrap JSON 模式 theme={null}
  weather_schema = {
      "type": "object",
      "properties": {
          "location": {"type": "string"},
          "units": {"type": "string"},
          "include_forecast": {"type": "boolean"}
      },
      "required": ["location", "units", "include_forecast"]
  }

  @tool(args_schema=weather_schema)
  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
  ```
</CodeGroup>

## 与代理一起使用工具

代理通过添加推理循环、状态管理和多步执行，超越了简单的工具绑定。

<Tip>要查看如何与代理一起使用工具的示例，请参阅 [代理](/oss/python/langchain/agents)。</Tip>

## 高级工具模式

### ToolNode

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

#### 配置选项

`ToolNode` 接受以下参数：

```python wrap theme={null}
from langchain.agents import ToolNode

tool_node = ToolNode(
    tools=[...],              # 工具或可调用对象的列表
    handle_tool_errors=True,  # 错误处理配置
    ...
)
```

<ParamField path="tools" required>
  此节点可以执行的工具列表。可以包括：

  * LangChain `@tool` 装饰的函数
  * 具有适当类型提示和文档字符串的可调用对象（例如函数）
</ParamField>

<ParamField path="handle_tool_errors">
  控制如何处理工具执行失败。
  可以是：

  * `bool`
  * `str`
  * `Callable[..., str]`
  * `type[Exception]`
  * `tuple[type[Exception], ...]`

  默认值：内部 `_default_handle_tool_errors`
</ParamField>

#### 错误处理策略

`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 请修复您的错误。"` 重新引发）

使用不同错误处理策略的示例：

```python wrap theme={null}
# 使用默认错误消息模板字符串重试所有异常类型
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() 一起使用

<Note>
  建议您在阅读本节之前先熟悉 `create_agent()`。[阅读有关代理的更多信息](/oss/python/langchain/agents)。
</Note>

将配置好的 `ToolNode` 直接传递给 `create_agent()`：

```python theme={null}
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, john@example.com, 活跃"

@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()` 时，代理将使用您的确切配置，包括错误处理、自定义名称和标签。当您需要对工具执行行为进行细粒度控制时，这非常有用。

### 状态、上下文和内存

<AccordionGroup>
  <Accordion title="在工具内部访问代理状态">
    <Info>
      **`state`**：代理在其执行过程中维护状态 - 这包括消息、自定义字段以及您的工具需要跟踪的任何数据。状态在图中流动，并且可以被工具访问和修改。
    </Info>

    <Info>
      **`InjectedState`**：一种注解，允许工具在不向 LLM 暴露的情况下访问当前图状态。这使得工具可以读取诸如消息历史或自定义状态字段之类的信息，同时保持工具模式的简洁性。
    </Info>

    工具可以使用 `InjectedState` 注解访问当前图状态：

    ```python wrap theme={null}
    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, "未设置")
    ```

    <Warning>
      状态注入的参数对模型隐藏。在上面的示例中，模型仅在工具模式中看到 `pref_name` - `preferences` **不**包含在请求中。
    </Warning>
  </Accordion>

  <Accordion title="在工具内部更新代理状态">
    <Info>
      **`Command`**：工具可以使用的一种特殊返回类型，用于更新代理的状态或控制图的执行流程。工具不仅可以返回数据，还可以返回 `Command` 来修改状态或将代理定向到特定节点。
    </Info>

    使用返回 `Command` 的工具更新代理状态：

    ```python wrap theme={null}
    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})
    ```
  </Accordion>

  <Accordion title="在工具内部访问运行时上下文">
    <Info>
      **`runtime`**：您的代理的执行环境，包含在代理执行过程中持续存在的不可变配置和上下文数据（例如，用户 ID、会话详细信息或应用程序特定配置）。
    </Info>

    工具可以通过 `get_runtime` 访问代理的运行时上下文：

    ```python wrap theme={null}
    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": "alice@example.com"
        },
        "user456": {
            "name": "Bob Smith",
            "account_type": "Standard",
            "balance": 1200,
            "email": "bob@example.com"
        }
    }

    @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")
    )
    ```
  </Accordion>

  <Accordion title="在工具内部访问长期记忆">
    <Info>
      **`store`**：LangChain 的持久层。代理的长期记忆存储，例如跨对话存储的用户特定或应用程序特定数据。
    </Info>

    工具可以通过 `get_store` 访问代理的存储：

    ```python wrap theme={null}
    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 "未知用户"
    ```
  </Accordion>

  <Accordion title="在工具内部更新长期记忆">
    要更新长期记忆，您可以使用 `InMemoryStore` 的 `.put()` 方法。以下是一个跨会话的持久内存完整示例：

    ```python wrap expandable theme={null}
    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: foo@langchain.dev"}]
    })

    # 第二个会话：获取用户信息
    agent.invoke({
        "messages": [{"role": "user", "content": "获取 ID 为 'abc123' 的用户信息"}]
    })
    # 以下是 ID 为 "abc123" 的用户信息：
    # - 姓名: Foo
    # - 年龄: 25
    # - 电子邮件: foo@langchain.dev
    ```
  </Accordion>
</AccordionGroup>
