Skip to content

工具(tool)扩展了智能体的能力——让它们获取实时数据、执行代码、查询外部数据库,并在真实世界中采取行动。

在底层,工具是具有良好定义的输入与输出的可调用函数,会被传给对话模型。模型根据对话上下文决定何时调用工具,以及提供什么输入参数。

TIP

有关模型如何处理工具调用的细节,参见工具调用。用 LangSmith 追踪工具调用并调试错误。按照追踪快速入门完成设置。

我们还建议你设置 LangSmith Engine,它会监控你的追踪、检测问题并提出修复建议。

创建工具

基础工具定义

创建工具最简单的方式是使用 @tool 装饰器。默认情况下,函数的 docstring 会成为工具的描述,帮助模型理解何时使用它:

python
from langchain.tools import tool

@tool
def search_database(query: str, limit: int = 10) -> str:
    """Search the customer database for records matching the query.

    Args:
        query: Search terms to look for
        limit: Maximum number of results to return
    """
    return f"Found {limit} results for '{query}'"

类型提示是必需的,因为它们定义了工具的输入 schema。docstring 应信息丰富且简洁,以帮助模型理解工具的用途。

创建工具最简单的方式是从 langchain 包导入 tool 函数。你可以使用 zod 定义工具的输入 schema:

ts
import * as z from "zod"
import { tool } from "langchain"

const searchDatabase = tool(
  ({ query, limit }) => `Found ${limit} results for '${query}'`,
  {
    name: "search_database",
    description: "Search the customer database for records matching the query.",
    schema: z.object({
      query: z.string().describe("Search terms to look for"),
      limit: z.number().describe("Maximum number of results to return"),
    }),
  }
);

INFO

服务端工具使用: 某些对话模型内置了在服务端执行的工具(网络搜索、代码解释器)。详情参见服务端工具使用

WARNING

工具名称建议使用 snake_case(例如用 web_search 而不是 Web Search)。某些模型提供商对包含空格或特殊字符的名称存在兼容性问题或直接拒绝并报错。坚持使用字母数字字符、下划线与连字符有助于提高跨提供商的兼容性。

自定义工具属性

自定义工具名称

默认情况下,工具名称来自函数名。当你需要更具描述性的名称时可以覆盖它:

python
@tool("web_search")  # 自定义名称
def search(query: str) -> str:
    """Search the web for information."""
    return f"Results for: {query}"

print(search.name)  # web_search

自定义工具描述

覆盖自动生成的工具描述,以便更清晰地指导模型:

python
@tool("calculator", description="Performs arithmetic calculations. Use this for any math problems.")
def calc(expression: str) -> str:
    """Evaluate mathematical expressions."""
    return str(eval(expression))

高级 schema 定义

使用 Pydantic 模型或 JSON schema 定义复杂输入:

python
from pydantic import BaseModel, Field
from typing import Literal

class WeatherInput(BaseModel):
    """Input for weather queries."""
    location: str = Field(description="City name or coordinates")
    units: Literal["celsius", "fahrenheit"] = Field(
        default="celsius",
        description="Temperature unit preference"
    )
    include_forecast: bool = Field(
        default=False,
        description="Include 5-day forecast"
    )

@tool(args_schema=WeatherInput)
def get_weather(location: str, units: str = "celsius", include_forecast: bool = False) -> str:
    """Get current weather and optional forecast."""
    temp = 22 if units == "celsius" else 72
    result = f"Current weather in {location}: {temp} degrees {units[0].upper()}"
    if include_forecast:
        result += "\nNext 5 days: Sunny"
    return result
python
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:
    """Get current weather and optional forecast."""
    temp = 22 if units == "celsius" else 72
    result = f"Current weather in {location}: {temp} degrees {units[0].upper()}"
    if include_forecast:
        result += "\nNext 5 days: Sunny"
    return result

保留参数名

以下参数名是保留的,不能用作工具参数。使用这些名称会导致运行时错误。

参数名用途
config保留用于在内部向工具传递 RunnableConfig
runtime保留用于 ToolRuntime 参数(访问状态、上下文、存储)

要访问运行时信息,请使用 ToolRuntime 参数,而不是把自己的参数命名为 configruntime

如果你使用了 InjectedStateInjectedStoreget_runtime()InjectedToolCallId,参见从旧式注入模式迁移

访问上下文

当工具能够访问对话历史、用户数据与持久记忆等运行时信息时,它们最为强大。本节介绍如何从工具内部访问和更新这些信息。

工具可以通过 ToolRuntime 参数访问运行时信息,它提供:

组件说明用例
状态(State)短期记忆——存在于当前对话期间的可变数据(消息、计数器、自定义字段)访问对话历史、跟踪工具调用次数
上下文(Context)在调用时传入的不可变配置(用户 ID、会话信息)根据用户身份个性化响应
存储(Store)长期记忆——跨对话存续的持久数据保存用户偏好、维护知识库
流写入器(Stream Writer)在工具执行期间发出实时更新为长时间运行的操作显示进度
执行信息(Execution Info)当前执行的标识与重试信息(线程 ID、运行 ID、尝试次数)访问线程/运行 ID、根据重试状态调整行为
服务器信息(Server Info)在 LangGraph Server 上运行时的服务器专属元数据(assistant ID、图 ID、已认证用户)访问 assistant ID、图 ID 或已认证用户信息
配置(Config)本次执行的 RunnableConfig访问回调、标签与元数据
工具调用 ID(Tool Call ID)当前工具调用的唯一标识符为日志与模型调用关联工具调用

短期记忆(状态)

状态表示在对话期间存在的短期记忆。它包含消息历史以及你在图状态中定义的任何自定义字段。

INFO

在工具签名中添加 runtime: ToolRuntime 以访问状态。该参数会被自动注入并对 LLM 隐藏——它不会出现在工具的 schema 中。

访问状态

工具可以使用 runtime.state 访问当前对话状态:

python
from langchain.tools import tool, ToolRuntime
from langchain.messages import HumanMessage

@tool
def get_last_user_message(runtime: ToolRuntime) -> str:
    """Get the most recent message from the user."""
    messages = runtime.state["messages"]

    # 查找最后一条人类消息
    for message in reversed(messages):
        if isinstance(message, HumanMessage):
            return message.content

    return "No user messages found"

# 访问自定义状态字段
@tool
def get_user_preference(
    pref_name: str,
    runtime: ToolRuntime
) -> str:
    """Get a user preference value."""
    preferences = runtime.state.get("user_preferences", {})
    return preferences.get(pref_name, "Not set")

WARNING

runtime 参数对模型是隐藏的。对于上面的示例,模型在工具 schema 中只能看到 pref_name

更新状态

使用 Command 更新智能体的状态。这对于需要更新自定义状态字段的工具很有用。 请在更新中包含一个 ToolMessage,这样模型才能看到工具调用的结果:

python
from langchain.agents import AgentState
from langchain.messages import ToolMessage
from langchain.tools import ToolRuntime, tool
from langgraph.types import Command

class CustomState(AgentState):
    user_name: str

@tool
def set_user_name(new_name: str, runtime: ToolRuntime[None, CustomState]) -> Command:
    """Set the user's name in the conversation state."""
    return Command(
        update={
            "user_name": new_name,
            "messages": [
                ToolMessage(
                    content=f"User name set to {new_name}.",
                    tool_call_id=runtime.tool_call_id,
                )
            ],
        }
    )

TIP

当工具更新状态变量时,考虑为这些字段定义一个 reducer。由于 LLM 可以并行调用多个工具,当同一状态字段被并发工具调用更新时,reducer 决定如何解决冲突。

上下文

上下文提供在调用时传入的不可变配置数据。把它用于用户 ID、会话详情或在对话期间不应改变的应用专属设置。

INFO

thread_id(通过 config={"configurable": {"thread_id": ...}} 传入)限定对话的作用域:消息历史与检查点;而 context 携带你的工具和中间件在调用时读取的每次运行数据。在生产环境中,你通常会一起传入两者:每个对话一个稳定的 thread_id,以及每次调用一个 context 对象。

通过 runtime.context 访问上下文。把它与 thread_id 一起传入,以便对话跨轮次持久化:

python
from dataclasses import dataclass

from langchain.agents import create_agent
from langchain.tools import tool, ToolRuntime
from langchain_core.utils.uuid import uuid7
from langchain_openai import ChatOpenAI

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(runtime: ToolRuntime[UserContext]) -> str:
    """Get the current user's account information."""
    user_id = runtime.context.user_id

    if user_id in USER_DATABASE:
        user = USER_DATABASE[user_id]
        return (
            f"Account holder: {user['name']}\n"
            f"Type: {user['account_type']}\n"
            f"Balance: ${user['balance']}"
        )
    return "User not found"

model = ChatOpenAI(model="google_genai:gemini-3.6-flash")
agent = create_agent(
    model,
    tools=[get_account_info],
    context_schema=UserContext,
    system_prompt="You are a financial assistant.",
)

result = agent.invoke(
    {"messages": [{"role": "user", "content": "What's my current balance?"}]},
    config={"configurable": {"thread_id": str(uuid7())}},
    context=UserContext(user_id="user123"),
)
python
from dataclasses import dataclass

from langchain.agents import create_agent
from langchain.tools import tool, ToolRuntime
from langchain_core.utils.uuid import uuid7
from langchain_openai import ChatOpenAI

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(runtime: ToolRuntime[UserContext]) -> str:
    """Get the current user's account information."""
    user_id = runtime.context.user_id

    if user_id in USER_DATABASE:
        user = USER_DATABASE[user_id]
        return (
            f"Account holder: {user['name']}\n"
            f"Type: {user['account_type']}\n"
            f"Balance: ${user['balance']}"
        )
    return "User not found"

model = ChatOpenAI(model="openai:gpt-5.5")
agent = create_agent(
    model,
    tools=[get_account_info],
    context_schema=UserContext,
    system_prompt="You are a financial assistant.",
)

result = agent.invoke(
    {"messages": [{"role": "user", "content": "What's my current balance?"}]},
    config={"configurable": {"thread_id": str(uuid7())}},
    context=UserContext(user_id="user123"),
)
python
from dataclasses import dataclass

from langchain.agents import create_agent
from langchain.tools import tool, ToolRuntime
from langchain_core.utils.uuid import uuid7
from langchain_openai import ChatOpenAI

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(runtime: ToolRuntime[UserContext]) -> str:
    """Get the current user's account information."""
    user_id = runtime.context.user_id

    if user_id in USER_DATABASE:
        user = USER_DATABASE[user_id]
        return (
            f"Account holder: {user['name']}\n"
            f"Type: {user['account_type']}\n"
            f"Balance: ${user['balance']}"
        )
    return "User not found"

model = ChatOpenAI(model="anthropic:claude-sonnet-4-6")
agent = create_agent(
    model,
    tools=[get_account_info],
    context_schema=UserContext,
    system_prompt="You are a financial assistant.",
)

result = agent.invoke(
    {"messages": [{"role": "user", "content": "What's my current balance?"}]},
    config={"configurable": {"thread_id": str(uuid7())}},
    context=UserContext(user_id="user123"),
)
python
from dataclasses import dataclass

from langchain.agents import create_agent
from langchain.tools import tool, ToolRuntime
from langchain_core.utils.uuid import uuid7
from langchain_openai import ChatOpenAI

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(runtime: ToolRuntime[UserContext]) -> str:
    """Get the current user's account information."""
    user_id = runtime.context.user_id

    if user_id in USER_DATABASE:
        user = USER_DATABASE[user_id]
        return (
            f"Account holder: {user['name']}\n"
            f"Type: {user['account_type']}\n"
            f"Balance: ${user['balance']}"
        )
    return "User not found"

model = ChatOpenAI(model="openrouter:z-ai/glm-5.2")
agent = create_agent(
    model,
    tools=[get_account_info],
    context_schema=UserContext,
    system_prompt="You are a financial assistant.",
)

result = agent.invoke(
    {"messages": [{"role": "user", "content": "What's my current balance?"}]},
    config={"configurable": {"thread_id": str(uuid7())}},
    context=UserContext(user_id="user123"),
)
python
from dataclasses import dataclass

from langchain.agents import create_agent
from langchain.tools import tool, ToolRuntime
from langchain_core.utils.uuid import uuid7
from langchain_openai import ChatOpenAI

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(runtime: ToolRuntime[UserContext]) -> str:
    """Get the current user's account information."""
    user_id = runtime.context.user_id

    if user_id in USER_DATABASE:
        user = USER_DATABASE[user_id]
        return (
            f"Account holder: {user['name']}\n"
            f"Type: {user['account_type']}\n"
            f"Balance: ${user['balance']}"
        )
    return "User not found"

model = ChatOpenAI(model="fireworks:accounts/fireworks/models/glm-5p2")
agent = create_agent(
    model,
    tools=[get_account_info],
    context_schema=UserContext,
    system_prompt="You are a financial assistant.",
)

result = agent.invoke(
    {"messages": [{"role": "user", "content": "What's my current balance?"}]},
    config={"configurable": {"thread_id": str(uuid7())}},
    context=UserContext(user_id="user123"),
)
python
from dataclasses import dataclass

from langchain.agents import create_agent
from langchain.tools import tool, ToolRuntime
from langchain_core.utils.uuid import uuid7
from langchain_openai import ChatOpenAI

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(runtime: ToolRuntime[UserContext]) -> str:
    """Get the current user's account information."""
    user_id = runtime.context.user_id

    if user_id in USER_DATABASE:
        user = USER_DATABASE[user_id]
        return (
            f"Account holder: {user['name']}\n"
            f"Type: {user['account_type']}\n"
            f"Balance: ${user['balance']}"
        )
    return "User not found"

model = ChatOpenAI(model="baseten:zai-org/GLM-5.2")
agent = create_agent(
    model,
    tools=[get_account_info],
    context_schema=UserContext,
    system_prompt="You are a financial assistant.",
)

result = agent.invoke(
    {"messages": [{"role": "user", "content": "What's my current balance?"}]},
    config={"configurable": {"thread_id": str(uuid7())}},
    context=UserContext(user_id="user123"),
)
python
from dataclasses import dataclass

from langchain.agents import create_agent
from langchain.tools import tool, ToolRuntime
from langchain_core.utils.uuid import uuid7
from langchain_openai import ChatOpenAI

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(runtime: ToolRuntime[UserContext]) -> str:
    """Get the current user's account information."""
    user_id = runtime.context.user_id

    if user_id in USER_DATABASE:
        user = USER_DATABASE[user_id]
        return (
            f"Account holder: {user['name']}\n"
            f"Type: {user['account_type']}\n"
            f"Balance: ${user['balance']}"
        )
    return "User not found"

model = ChatOpenAI(model="ollama:north-mini-code-1.0")
agent = create_agent(
    model,
    tools=[get_account_info],
    context_schema=UserContext,
    system_prompt="You are a financial assistant.",
)

result = agent.invoke(
    {"messages": [{"role": "user", "content": "What's my current balance?"}]},
    config={"configurable": {"thread_id": str(uuid7())}},
    context=UserContext(user_id="user123"),
)

工具可以通过 config 参数访问智能体的运行时上下文。把 contextthread_id 一起传入,以便对话跨轮次持久化:

ts
import * as z from "zod";
import { ChatOpenAI } from "@langchain/openai";
import { createAgent, tool } from "langchain";

const getUserName = tool(
  (_, config) => {
    return config.context.user_name;
  },
  {
    name: "get_user_name",
    description: "Get the user's name.",
    schema: z.object({}),
  },
);

const contextSchema = z.object({
  user_name: z.string(),
});

const agent = createAgent({
  model: new ChatOpenAI({ model: "google-genai:gemini-3.6-flash" }),
  tools: [getUserName],
  contextSchema,
});

const result = await agent.invoke(
  {
    messages: [{ role: "user", content: "What is my name?" }],
  },
  {
    configurable: { thread_id: crypto.randomUUID() },
    context: { user_name: "John Smith" },
  },
);
ts
import * as z from "zod";
import { ChatOpenAI } from "@langchain/openai";
import { createAgent, tool } from "langchain";

const getUserName = tool(
  (_, config) => {
    return config.context.user_name;
  },
  {
    name: "get_user_name",
    description: "Get the user's name.",
    schema: z.object({}),
  },
);

const contextSchema = z.object({
  user_name: z.string(),
});

const agent = createAgent({
  model: new ChatOpenAI({ model: "openai:gpt-5.5" }),
  tools: [getUserName],
  contextSchema,
});

const result = await agent.invoke(
  {
    messages: [{ role: "user", content: "What is my name?" }],
  },
  {
    configurable: { thread_id: crypto.randomUUID() },
    context: { user_name: "John Smith" },
  },
);
ts
import * as z from "zod";
import { ChatOpenAI } from "@langchain/openai";
import { createAgent, tool } from "langchain";

const getUserName = tool(
  (_, config) => {
    return config.context.user_name;
  },
  {
    name: "get_user_name",
    description: "Get the user's name.",
    schema: z.object({}),
  },
);

const contextSchema = z.object({
  user_name: z.string(),
});

const agent = createAgent({
  model: new ChatOpenAI({ model: "anthropic:claude-sonnet-4-6" }),
  tools: [getUserName],
  contextSchema,
});

const result = await agent.invoke(
  {
    messages: [{ role: "user", content: "What is my name?" }],
  },
  {
    configurable: { thread_id: crypto.randomUUID() },
    context: { user_name: "John Smith" },
  },
);
ts
import * as z from "zod";
import { ChatOpenAI } from "@langchain/openai";
import { createAgent, tool } from "langchain";

const getUserName = tool(
  (_, config) => {
    return config.context.user_name;
  },
  {
    name: "get_user_name",
    description: "Get the user's name.",
    schema: z.object({}),
  },
);

const contextSchema = z.object({
  user_name: z.string(),
});

const agent = createAgent({
  model: new ChatOpenAI({ model: "openrouter:openrouter:z-ai/glm-5.2" }),
  tools: [getUserName],
  contextSchema,
});

const result = await agent.invoke(
  {
    messages: [{ role: "user", content: "What is my name?" }],
  },
  {
    configurable: { thread_id: crypto.randomUUID() },
    context: { user_name: "John Smith" },
  },
);
ts
import * as z from "zod";
import { ChatOpenAI } from "@langchain/openai";
import { createAgent, tool } from "langchain";

const getUserName = tool(
  (_, config) => {
    return config.context.user_name;
  },
  {
    name: "get_user_name",
    description: "Get the user's name.",
    schema: z.object({}),
  },
);

const contextSchema = z.object({
  user_name: z.string(),
});

const agent = createAgent({
  model: new ChatOpenAI({ model: "fireworks:accounts/fireworks/models/glm-5p2" }),
  tools: [getUserName],
  contextSchema,
});

const result = await agent.invoke(
  {
    messages: [{ role: "user", content: "What is my name?" }],
  },
  {
    configurable: { thread_id: crypto.randomUUID() },
    context: { user_name: "John Smith" },
  },
);
ts
import * as z from "zod";
import { ChatOpenAI } from "@langchain/openai";
import { createAgent, tool } from "langchain";

const getUserName = tool(
  (_, config) => {
    return config.context.user_name;
  },
  {
    name: "get_user_name",
    description: "Get the user's name.",
    schema: z.object({}),
  },
);

const contextSchema = z.object({
  user_name: z.string(),
});

const agent = createAgent({
  model: new ChatOpenAI({ model: "baseten:zai-org/GLM-5.2" }),
  tools: [getUserName],
  contextSchema,
});

const result = await agent.invoke(
  {
    messages: [{ role: "user", content: "What is my name?" }],
  },
  {
    configurable: { thread_id: crypto.randomUUID() },
    context: { user_name: "John Smith" },
  },
);
ts
import * as z from "zod";
import { ChatOpenAI } from "@langchain/openai";
import { createAgent, tool } from "langchain";

const getUserName = tool(
  (_, config) => {
    return config.context.user_name;
  },
  {
    name: "get_user_name",
    description: "Get the user's name.",
    schema: z.object({}),
  },
);

const contextSchema = z.object({
  user_name: z.string(),
});

const agent = createAgent({
  model: new ChatOpenAI({ model: "ollama:north-mini-code-1.0" }),
  tools: [getUserName],
  contextSchema,
});

const result = await agent.invoke(
  {
    messages: [{ role: "user", content: "What is my name?" }],
  },
  {
    configurable: { thread_id: crypto.randomUUID() },
    context: { user_name: "John Smith" },
  },
);

长期记忆(存储)

BaseStore 提供跨对话存续的持久存储。与状态(短期记忆)不同,保存到存储中的数据在未来会话中仍然可用。

通过 runtime.store 访问存储。存储使用 namespace/key 模式组织数据:

TIP

对于生产部署,请使用 PostgresStoreMongoDBStoreRedisStore 等持久存储实现,而不是 InMemoryStore。设置细节参见记忆文档

python
from typing import Any
from langgraph.store.memory import InMemoryStore
from langchain.agents import create_agent
from langchain.tools import tool, ToolRuntime
from langchain_openai import ChatOpenAI

# 访问记忆
@tool
def get_user_info(user_id: str, runtime: ToolRuntime) -> str:
    """Look up user info."""
    store = runtime.store
    user_info = store.get(("users",), user_id)
    return str(user_info.value) if user_info else "Unknown user"

# 更新记忆
@tool
def save_user_info(user_id: str, user_info: dict[str, Any], runtime: ToolRuntime) -> str:
    """Save user info."""
    store = runtime.store
    store.put(("users",), user_id, user_info)
    return "Successfully saved user info."

model = ChatOpenAI(model="gpt-5.5")

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

# 第一次会话:保存用户信息
agent.invoke({
    "messages": [{"role": "user", "content": "Save the following user: userid: abc123, name: Foo, age: 25, email: foo@langchain.dev"}]
})

# 第二次会话:获取用户信息
agent.invoke({
    "messages": [{"role": "user", "content": "Get user info for user with id 'abc123'"}]
})
# 以下是 ID 为 "abc123" 的用户信息:
# - 姓名:Foo
# - 年龄:25
# - 邮箱:foo@langchain.dev

通过 config.store 访问存储。存储使用 namespace/key 模式组织数据:

ts
import * as z from "zod";
import { createAgent, tool } from "langchain";
import { InMemoryStore } from "@langchain/langgraph";
import { ChatOpenAI } from "@langchain/openai";

const store = new InMemoryStore();

// 访问记忆
const getUserInfo = tool(
  async ({ user_id }) => {
    const value = await store.get(["users"], user_id);
    console.log("get_user_info", user_id, value);
    return value;
  },
  {
    name: "get_user_info",
    description: "Look up user info.",
    schema: z.object({
      user_id: z.string(),
    }),
  }
);

// 更新记忆
const saveUserInfo = tool(
  async ({ user_id, name, age, email }) => {
    console.log("save_user_info", user_id, name, age, email);
    await store.put(["users"], user_id, { name, age, email });
    return "Successfully saved user info.";
  },
  {
    name: "save_user_info",
    description: "Save user info.",
    schema: z.object({
      user_id: z.string(),
      name: z.string(),
      age: z.number(),
      email: z.string(),
    }),
  }
);

const agent = createAgent({
  model: new ChatOpenAI({ model: "gpt-5.5" }),
  tools: [getUserInfo, saveUserInfo],
  store,
});

// 第一次会话:保存用户信息
await agent.invoke({
  messages: [
    {
      role: "user",
      content: "Save the following user: userid: abc123, name: Foo, age: 25, email: foo@langchain.dev",
    },
  ],
});

// 第二次会话:获取用户信息
const result = await agent.invoke({
  messages: [
    { role: "user", content: "Get user info for user with id 'abc123'" },
  ],
});

console.log(result);
// 以下是 ID 为 "abc123" 的用户信息:
// - 姓名:Foo
// - 年龄:25
// - 邮箱:foo@langchain.dev

流写入器

在工具执行期间流式输出实时更新。这对于在长时间运行的操作中向用户提供进度反馈很有用。

使用 runtime.stream_writer 发出自定义更新:

python
from langchain.tools import tool, ToolRuntime

@tool
def get_weather(city: str, runtime: ToolRuntime) -> str:
    """Get weather for a given city."""
    writer = runtime.stream_writer

    # 在工具执行时流式输出自定义更新
    writer(f"Looking up data for city: {city}")
    writer(f"Acquired data for city: {city}")

    return f"It's always sunny in {city}!"

INFO

如果你在工具内使用 runtime.stream_writer,该工具必须在 LangGraph 执行上下文内被调用。更多细节参见流式输出

使用 config.writer 发出自定义更新:

ts
import * as z from "zod";
import { tool, ToolRuntime } from "langchain";

const getWeather = tool(
  ({ city }, config: ToolRuntime) => {
    const writer = config.writer;

    // 在工具执行时流式输出自定义更新
    if (writer) {
      writer(`Looking up data for city: ${city}`);
      writer(`Acquired data for city: ${city}`);
    }

    return `It's always sunny in ${city}!`;
  },
  {
    name: "get_weather",
    description: "Get weather for a given city.",
    schema: z.object({
      city: z.string(),
    }),
  }
);

执行信息

通过 runtime.execution_info 在工具内访问线程 ID、运行 ID 与重试状态:

python
from langchain.tools import tool, ToolRuntime

@tool
def log_execution_context(runtime: ToolRuntime) -> str:
    """Log execution identity information."""
    info = runtime.execution_info
    print(f"Thread: {info.thread_id}, Run: {info.run_id}")  
    print(f"Attempt: {info.node_attempt}")
    return "done"
ts
import { tool } from "langchain";
import * as z from "zod";

const logExecutionContext = tool(
  async (_input, runtime) => {
    const info = runtime.executionInfo;
    console.log(`Thread: ${info.threadId}, Run: ${info.runId}`);  
    console.log(`Attempt: ${info.nodeAttempt}`);
    return "done";
  },
  {
    name: "log_execution_context",
    description: "Log execution identity information.",
    schema: z.object({}),
  }
);

INFO

需要 deepagents>=0.5.0(或 langgraph>=1.1.5)。

INFO

需要 deepagents>=1.9.0(或 @langchain/langgraph>=1.2.8)。

服务器信息

当你的工具在 LangGraph Server 上运行时,可通过 runtime.server_info 访问 assistant ID、图 ID 与已认证用户:

python
from langchain.tools import tool, ToolRuntime

@tool
def get_assistant_scoped_data(runtime: ToolRuntime) -> str:
    """Fetch data scoped to the current assistant."""
    server = runtime.server_info
    if server is not None:
        print(f"Assistant: {server.assistant_id}, Graph: {server.graph_id}")  
        if server.user is not None:
            print(f"User: {server.user.identity}")  
    return "done"

当工具未在 LangGraph Server 上运行时(例如本地开发或测试期间),server_infoNone

ts
import { tool } from "langchain";
import * as z from "zod";

const getAssistantScopedData = tool(
  async (_input, runtime) => {
    const server = runtime.serverInfo;
    if (server != null) {
      console.log(`Assistant: ${server.assistantId}, Graph: ${server.graphId}`);  
      if (server.user != null) {
        console.log(`User: ${server.user.identity}`);  
      }
    }
    return "done";
  },
  {
    name: "get_assistant_scoped_data",
    description: "Fetch data scoped to the current assistant.",
    schema: z.object({}),
  }
);

当工具未在 LangGraph Server 上运行时,serverInfonull

INFO

需要 deepagents>=0.5.0(或 langgraph>=1.1.5)。

INFO

需要 deepagents>=1.9.0(或 @langchain/langgraph>=1.2.8)。

从旧式注入模式迁移

旧式示例使用了 InjectedStateInjectedStoreget_runtime()InjectedToolCallId。请改用 ToolRuntime,用统一的显式接口访问状态、上下文、存储与执行元数据。

旧式模式

python
from langchain.tools import tool, InjectedState

@tool
def summarize(state: InjectedState) -> str:
    """Summarize the conversation."""
    messages = state["messages"]
    return f"Conversation length: {len(messages)} messages."

推荐模式

python
from langchain.tools import tool, ToolRuntime

@tool
def summarize(runtime: ToolRuntime) -> str:
    """Summarize the conversation."""
    messages = runtime.state["messages"]
    return f"Conversation length: {len(messages)} messages."

针对智能体级的迁移(例如 create_react_agent 与自定义状态),参见 LangChain v1 迁移指南

工具执行

在 LangChain 中,工具由智能体使用(例如通过 create_agent),工具错误处理通过中间件配置。

对于 LangGraph 工作流,工具执行由 ToolNode 处理。Graph API 的用法参见 ToolNode,包括工具如何访问当前图状态与运行作用域的上下文。

工具返回值

你可以为工具选择不同的返回值:

  • 返回 string 以获得人类可读的结果。
  • 返回 object 以获得模型应解析的结构化结果。
  • 返回带可选消息的 Command 以写入状态。

返回字符串

当工具应为模型提供纯文本来阅读并用于下一次响应时,返回字符串。

python
from langchain.tools import tool

@tool
def get_weather(city: str) -> str:
    """Get weather for a city."""
    return f"It is currently sunny in {city}."
ts
import { tool } from "langchain";
import * as z from "zod";

const getWeather = tool(({ city }) => `It is currently sunny in ${city}.`, {
  name: "get_weather",
  description: "Get weather for a city.",
  schema: z.object({ city: z.string() }),
});

行为:

  • 返回值会被转换为 ToolMessage
  • 模型看到该文本并决定接下来做什么。
  • 除非模型或其他工具稍后修改,否则不会改变任何智能体状态字段。

当结果本身就是人类可读的文本时使用此方式。

返回对象

当你的工具产生模型应检查的结构化数据时,返回一个对象(例如 dict)。

python
from langchain.tools import tool

@tool
def get_weather_data(city: str) -> dict:
    """Get structured weather data for a city."""
    return {
        "city": city,
        "temperature_c": 22,
        "conditions": "sunny",
    }
ts
import { tool } from "langchain";
import * as z from "zod";

const getWeatherData = tool(
  ({ city }) => ({
    city,
    temperature_c: 22,
    conditions: "sunny",
  }),
  {
    name: "get_weather_data",
    description: "Get structured weather data for a city.",
    schema: z.object({ city: z.string() }),
  },
);

行为:

  • 对象会被序列化并作为工具输出发回。
  • 模型可以读取特定字段并对其推理。
  • 与字符串返回一样,这不会直接更新图状态。

当下游推理受益于显式字段而不是自由文本时使用此方式。

返回多模态内容

工具不限于纯文本。当模型支持多模态工具结果时,工具可以返回标准内容块,让模型在一个工具结果中接收到文本、图像与其他媒体。

python
from langchain.tools import tool

@tool
def capture_screenshot() -> list[dict]:
    """Capture a screenshot of the current page."""
    return [
        {"type": "text", "text": "Screenshot of the current page:"},
        {"type": "image", "url": "https://example.com/page.png"},
    ]
typescript
import { tool } from "langchain";
import { z } from "zod";

const captureScreenshot = tool(
  async () => [
    { type: "text", text: "Screenshot of the current page:" },
    { type: "image", url: "https://example.com/page.png" },
  ],
  {
    name: "capture_screenshot",
    description: "Capture a screenshot of the current page.",
    schema: z.object({}),
  }
);

行为:

  • 返回值会被转换为带多模态 contentToolMessage
  • 使用 message.content_blocks 在工具运行后读取规范化后的块列表。
  • 模型必须支持你返回的模态。在返回图像、音频或视频之前,请检查你的模型能力

块类型与提供商专属要求参见多模态消息。返回图像或混合内容的 MCP 工具会以相同方式转换;参见多模态工具内容

返回 Command

当工具需要更新图状态(例如设置用户偏好或应用状态)时,返回 Command。 你可以返回带或不带 ToolMessageCommand。 如果模型需要看到工具已成功(例如确认偏好变更),请在更新中包含一个 ToolMessage,并使用 runtime.tool_call_id 作为 tool_call_id 参数。

python
from langchain.messages import ToolMessage
from langchain.tools import ToolRuntime, tool
from langgraph.types import Command

@tool
def set_language(language: str, runtime: ToolRuntime) -> Command:
    """Set the preferred response language."""
    return Command(
        update={
            "preferred_language": language,
            "messages": [
                ToolMessage(
                    content=f"Language set to {language}.",
                    tool_call_id=runtime.tool_call_id,
                )
            ],
        }
    )
ts
import { tool, ToolMessage, type ToolRuntime } from "langchain";
import { Command } from "@langchain/langgraph";
import * as z from "zod";

const setLanguage = tool(
  async ({ language }, config: ToolRuntime) => {
    return new Command({
      update: {
        preferredLanguage: language,
        messages: [
          new ToolMessage({
            content: `Language set to ${language}.`,
            tool_call_id: config.toolCallId,
          }),
        ],
      },
    });
  },
  {
    name: "set_language",
    description: "Set the preferred response language.",
    schema: z.object({ language: z.string() }),
  },
);

行为:

  • 命令使用 update 更新状态。
  • 更新后的状态可供同一次运行的后续步骤使用。
  • 对于可能被并行工具调用更新的字段,请使用 reducer。

当工具不只是返回数据、还要修改智能体状态时使用此方式。

从工具直接返回

在工具上设置 return direct,可以短路智能体循环:智能体立即把工具输出返回给调用者,而不经过模型进行进一步处理。

python
from langchain.agents import create_agent
from langchain.tools import tool
from langchain_openai import ChatOpenAI

@tool(return_direct=True)
def fetch_order_status(order_id: str) -> str:
    """Fetch the current status of a customer order."""
    # In production, query your order management system here
    return f"Order {order_id} is shipped and will arrive in 2 days."

agent = create_agent(
    ChatOpenAI(model="google_genai:gemini-3.6-flash"),
    tools=[fetch_order_status],
)

result = agent.invoke({
    "messages": [{"role": "user", "content": "What is the status of order #12345?"}]
})
# The agent returns the tool output directly without another LLM call:
# "Order 12345 is shipped and will arrive in 2 days."
python
from langchain.agents import create_agent
from langchain.tools import tool
from langchain_openai import ChatOpenAI

@tool(return_direct=True)
def fetch_order_status(order_id: str) -> str:
    """Fetch the current status of a customer order."""
    # In production, query your order management system here
    return f"Order {order_id} is shipped and will arrive in 2 days."

agent = create_agent(
    ChatOpenAI(model="openai:gpt-5.5"),
    tools=[fetch_order_status],
)

result = agent.invoke({
    "messages": [{"role": "user", "content": "What is the status of order #12345?"}]
})
# The agent returns the tool output directly without another LLM call:
# "Order 12345 is shipped and will arrive in 2 days."
python
from langchain.agents import create_agent
from langchain.tools import tool
from langchain_openai import ChatOpenAI

@tool(return_direct=True)
def fetch_order_status(order_id: str) -> str:
    """Fetch the current status of a customer order."""
    # In production, query your order management system here
    return f"Order {order_id} is shipped and will arrive in 2 days."

agent = create_agent(
    ChatOpenAI(model="anthropic:claude-sonnet-4-6"),
    tools=[fetch_order_status],
)

result = agent.invoke({
    "messages": [{"role": "user", "content": "What is the status of order #12345?"}]
})
# The agent returns the tool output directly without another LLM call:
# "Order 12345 is shipped and will arrive in 2 days."
python
from langchain.agents import create_agent
from langchain.tools import tool
from langchain_openai import ChatOpenAI

@tool(return_direct=True)
def fetch_order_status(order_id: str) -> str:
    """Fetch the current status of a customer order."""
    # In production, query your order management system here
    return f"Order {order_id} is shipped and will arrive in 2 days."

agent = create_agent(
    ChatOpenAI(model="openrouter:z-ai/glm-5.2"),
    tools=[fetch_order_status],
)

result = agent.invoke({
    "messages": [{"role": "user", "content": "What is the status of order #12345?"}]
})
# The agent returns the tool output directly without another LLM call:
# "Order 12345 is shipped and will arrive in 2 days."
python
from langchain.agents import create_agent
from langchain.tools import tool
from langchain_openai import ChatOpenAI

@tool(return_direct=True)
def fetch_order_status(order_id: str) -> str:
    """Fetch the current status of a customer order."""
    # In production, query your order management system here
    return f"Order {order_id} is shipped and will arrive in 2 days."

agent = create_agent(
    ChatOpenAI(model="fireworks:accounts/fireworks/models/glm-5p2"),
    tools=[fetch_order_status],
)

result = agent.invoke({
    "messages": [{"role": "user", "content": "What is the status of order #12345?"}]
})
# The agent returns the tool output directly without another LLM call:
# "Order 12345 is shipped and will arrive in 2 days."
python
from langchain.agents import create_agent
from langchain.tools import tool
from langchain_openai import ChatOpenAI

@tool(return_direct=True)
def fetch_order_status(order_id: str) -> str:
    """Fetch the current status of a customer order."""
    # In production, query your order management system here
    return f"Order {order_id} is shipped and will arrive in 2 days."

agent = create_agent(
    ChatOpenAI(model="baseten:zai-org/GLM-5.2"),
    tools=[fetch_order_status],
)

result = agent.invoke({
    "messages": [{"role": "user", "content": "What is the status of order #12345?"}]
})
# The agent returns the tool output directly without another LLM call:
# "Order 12345 is shipped and will arrive in 2 days."
python
from langchain.agents import create_agent
from langchain.tools import tool
from langchain_openai import ChatOpenAI

@tool(return_direct=True)
def fetch_order_status(order_id: str) -> str:
    """Fetch the current status of a customer order."""
    # In production, query your order management system here
    return f"Order {order_id} is shipped and will arrive in 2 days."

agent = create_agent(
    ChatOpenAI(model="ollama:north-mini-code-1.0"),
    tools=[fetch_order_status],
)

result = agent.invoke({
    "messages": [{"role": "user", "content": "What is the status of order #12345?"}]
})
# The agent returns the tool output directly without another LLM call:
# "Order 12345 is shipped and will arrive in 2 days."
ts
import { ChatOpenAI } from "@langchain/openai";
import { createAgent, tool } from "langchain";
import * as z from "zod";

const fetchOrderStatus = tool(
  ({ order_id }) => {
    return `Order ${order_id} is shipped and will arrive in 2 days.`;
  },
  {
    name: "fetch_order_status",
    description: "Fetch the current status of a customer order.",
    schema: z.object({ order_id: z.string() }),
    returnDirect: true,
  },
);

const agent = createAgent({
  model: new ChatOpenAI({ model: "google-genai:gemini-3.6-flash" }),
  tools: [fetchOrderStatus],
});

const result = await agent.invoke({
  messages: [
    { role: "user", content: "What is the status of order #12345?" },
  ],
});
// The agent returns the tool output directly without another LLM call:
// "Order 12345 is shipped and will arrive in 2 days."
ts
import { ChatOpenAI } from "@langchain/openai";
import { createAgent, tool } from "langchain";
import * as z from "zod";

const fetchOrderStatus = tool(
  ({ order_id }) => {
    return `Order ${order_id} is shipped and will arrive in 2 days.`;
  },
  {
    name: "fetch_order_status",
    description: "Fetch the current status of a customer order.",
    schema: z.object({ order_id: z.string() }),
    returnDirect: true,
  },
);

const agent = createAgent({
  model: new ChatOpenAI({ model: "openai:gpt-5.5" }),
  tools: [fetchOrderStatus],
});

const result = await agent.invoke({
  messages: [
    { role: "user", content: "What is the status of order #12345?" },
  ],
});
// The agent returns the tool output directly without another LLM call:
// "Order 12345 is shipped and will arrive in 2 days."
ts
import { ChatOpenAI } from "@langchain/openai";
import { createAgent, tool } from "langchain";
import * as z from "zod";

const fetchOrderStatus = tool(
  ({ order_id }) => {
    return `Order ${order_id} is shipped and will arrive in 2 days.`;
  },
  {
    name: "fetch_order_status",
    description: "Fetch the current status of a customer order.",
    schema: z.object({ order_id: z.string() }),
    returnDirect: true,
  },
);

const agent = createAgent({
  model: new ChatOpenAI({ model: "anthropic:claude-sonnet-4-6" }),
  tools: [fetchOrderStatus],
});

const result = await agent.invoke({
  messages: [
    { role: "user", content: "What is the status of order #12345?" },
  ],
});
// The agent returns the tool output directly without another LLM call:
// "Order 12345 is shipped and will arrive in 2 days."
ts
import { ChatOpenAI } from "@langchain/openai";
import { createAgent, tool } from "langchain";
import * as z from "zod";

const fetchOrderStatus = tool(
  ({ order_id }) => {
    return `Order ${order_id} is shipped and will arrive in 2 days.`;
  },
  {
    name: "fetch_order_status",
    description: "Fetch the current status of a customer order.",
    schema: z.object({ order_id: z.string() }),
    returnDirect: true,
  },
);

const agent = createAgent({
  model: new ChatOpenAI({ model: "openrouter:openrouter:z-ai/glm-5.2" }),
  tools: [fetchOrderStatus],
});

const result = await agent.invoke({
  messages: [
    { role: "user", content: "What is the status of order #12345?" },
  ],
});
// The agent returns the tool output directly without another LLM call:
// "Order 12345 is shipped and will arrive in 2 days."
ts
import { ChatOpenAI } from "@langchain/openai";
import { createAgent, tool } from "langchain";
import * as z from "zod";

const fetchOrderStatus = tool(
  ({ order_id }) => {
    return `Order ${order_id} is shipped and will arrive in 2 days.`;
  },
  {
    name: "fetch_order_status",
    description: "Fetch the current status of a customer order.",
    schema: z.object({ order_id: z.string() }),
    returnDirect: true,
  },
);

const agent = createAgent({
  model: new ChatOpenAI({ model: "fireworks:accounts/fireworks/models/glm-5p2" }),
  tools: [fetchOrderStatus],
});

const result = await agent.invoke({
  messages: [
    { role: "user", content: "What is the status of order #12345?" },
  ],
});
// The agent returns the tool output directly without another LLM call:
// "Order 12345 is shipped and will arrive in 2 days."
ts
import { ChatOpenAI } from "@langchain/openai";
import { createAgent, tool } from "langchain";
import * as z from "zod";

const fetchOrderStatus = tool(
  ({ order_id }) => {
    return `Order ${order_id} is shipped and will arrive in 2 days.`;
  },
  {
    name: "fetch_order_status",
    description: "Fetch the current status of a customer order.",
    schema: z.object({ order_id: z.string() }),
    returnDirect: true,
  },
);

const agent = createAgent({
  model: new ChatOpenAI({ model: "baseten:zai-org/GLM-5.2" }),
  tools: [fetchOrderStatus],
});

const result = await agent.invoke({
  messages: [
    { role: "user", content: "What is the status of order #12345?" },
  ],
});
// The agent returns the tool output directly without another LLM call:
// "Order 12345 is shipped and will arrive in 2 days."
ts
import { ChatOpenAI } from "@langchain/openai";
import { createAgent, tool } from "langchain";
import * as z from "zod";

const fetchOrderStatus = tool(
  ({ order_id }) => {
    return `Order ${order_id} is shipped and will arrive in 2 days.`;
  },
  {
    name: "fetch_order_status",
    description: "Fetch the current status of a customer order.",
    schema: z.object({ order_id: z.string() }),
    returnDirect: true,
  },
);

const agent = createAgent({
  model: new ChatOpenAI({ model: "ollama:north-mini-code-1.0" }),
  tools: [fetchOrderStatus],
});

const result = await agent.invoke({
  messages: [
    { role: "user", content: "What is the status of order #12345?" },
  ],
});
// The agent returns the tool output directly without another LLM call:
// "Order 12345 is shipped and will arrive in 2 days."

行为:

  • 工具正常执行,其输出被包装在 ToolMessage 中。
  • 智能体停止循环,把工具输出作为最终响应返回,跳过任何额外的模型调用。
  • 如果模型在单轮中调用多个工具,只有当所有被调用的工具都有 return_direct=True 时,return_direct 才会生效。

在以下情况使用此方式:

  • 工具的输出就是完整的、可直接交给用户的答案(例如返回可直接显示的查询结果)。
  • 在不需要额外推理时,你想避免额外的模型调用。
  • 你需要确定性的、未修改的输出——模型不能改写、总结或对工具结果采取行动。

WARNING

由于模型不会处理工具的输出,return_direct=True 不适用于其结果需要进一步推理、总结或与其他工具调用串联的工具。

错误处理

使用 LangChain 智能体中间件处理工具错误,以重试失败的调用或返回自定义错误消息:

python
from collections.abc import Callable

from langchain.agents import create_agent
from langchain.agents.middleware import wrap_tool_call
from langchain.messages import ToolMessage
from langchain.tools.tool_node import ToolCallRequest

@wrap_tool_call
def handle_tool_errors(
    request: ToolCallRequest,
    handler: Callable[[ToolCallRequest], ToolMessage],
) -> ToolMessage:
    """Convert tool exceptions into ToolMessages the model can handle."""
    try:
        return handler(request)
    except Exception as e:
        return ToolMessage(
            content=f"Tool error: Please check your input and try again. ({e})",
            tool_call_id=request.tool_call["id"],
        )

agent = create_agent(
    model="google_genai:gemini-3.6-flash",
    tools=[],
    middleware=[handle_tool_errors],
)
python
from collections.abc import Callable

from langchain.agents import create_agent
from langchain.agents.middleware import wrap_tool_call
from langchain.messages import ToolMessage
from langchain.tools.tool_node import ToolCallRequest

@wrap_tool_call
def handle_tool_errors(
    request: ToolCallRequest,
    handler: Callable[[ToolCallRequest], ToolMessage],
) -> ToolMessage:
    """Convert tool exceptions into ToolMessages the model can handle."""
    try:
        return handler(request)
    except Exception as e:
        return ToolMessage(
            content=f"Tool error: Please check your input and try again. ({e})",
            tool_call_id=request.tool_call["id"],
        )

agent = create_agent(
    model="openai:gpt-5.5",
    tools=[],
    middleware=[handle_tool_errors],
)
python
from collections.abc import Callable

from langchain.agents import create_agent
from langchain.agents.middleware import wrap_tool_call
from langchain.messages import ToolMessage
from langchain.tools.tool_node import ToolCallRequest

@wrap_tool_call
def handle_tool_errors(
    request: ToolCallRequest,
    handler: Callable[[ToolCallRequest], ToolMessage],
) -> ToolMessage:
    """Convert tool exceptions into ToolMessages the model can handle."""
    try:
        return handler(request)
    except Exception as e:
        return ToolMessage(
            content=f"Tool error: Please check your input and try again. ({e})",
            tool_call_id=request.tool_call["id"],
        )

agent = create_agent(
    model="anthropic:claude-sonnet-4-6",
    tools=[],
    middleware=[handle_tool_errors],
)
python
from collections.abc import Callable

from langchain.agents import create_agent
from langchain.agents.middleware import wrap_tool_call
from langchain.messages import ToolMessage
from langchain.tools.tool_node import ToolCallRequest

@wrap_tool_call
def handle_tool_errors(
    request: ToolCallRequest,
    handler: Callable[[ToolCallRequest], ToolMessage],
) -> ToolMessage:
    """Convert tool exceptions into ToolMessages the model can handle."""
    try:
        return handler(request)
    except Exception as e:
        return ToolMessage(
            content=f"Tool error: Please check your input and try again. ({e})",
            tool_call_id=request.tool_call["id"],
        )

agent = create_agent(
    model="openrouter:z-ai/glm-5.2",
    tools=[],
    middleware=[handle_tool_errors],
)
python
from collections.abc import Callable

from langchain.agents import create_agent
from langchain.agents.middleware import wrap_tool_call
from langchain.messages import ToolMessage
from langchain.tools.tool_node import ToolCallRequest

@wrap_tool_call
def handle_tool_errors(
    request: ToolCallRequest,
    handler: Callable[[ToolCallRequest], ToolMessage],
) -> ToolMessage:
    """Convert tool exceptions into ToolMessages the model can handle."""
    try:
        return handler(request)
    except Exception as e:
        return ToolMessage(
            content=f"Tool error: Please check your input and try again. ({e})",
            tool_call_id=request.tool_call["id"],
        )

agent = create_agent(
    model="fireworks:accounts/fireworks/models/glm-5p2",
    tools=[],
    middleware=[handle_tool_errors],
)
python
from collections.abc import Callable

from langchain.agents import create_agent
from langchain.agents.middleware import wrap_tool_call
from langchain.messages import ToolMessage
from langchain.tools.tool_node import ToolCallRequest

@wrap_tool_call
def handle_tool_errors(
    request: ToolCallRequest,
    handler: Callable[[ToolCallRequest], ToolMessage],
) -> ToolMessage:
    """Convert tool exceptions into ToolMessages the model can handle."""
    try:
        return handler(request)
    except Exception as e:
        return ToolMessage(
            content=f"Tool error: Please check your input and try again. ({e})",
            tool_call_id=request.tool_call["id"],
        )

agent = create_agent(
    model="baseten:zai-org/GLM-5.2",
    tools=[],
    middleware=[handle_tool_errors],
)
python
from collections.abc import Callable

from langchain.agents import create_agent
from langchain.agents.middleware import wrap_tool_call
from langchain.messages import ToolMessage
from langchain.tools.tool_node import ToolCallRequest

@wrap_tool_call
def handle_tool_errors(
    request: ToolCallRequest,
    handler: Callable[[ToolCallRequest], ToolMessage],
) -> ToolMessage:
    """Convert tool exceptions into ToolMessages the model can handle."""
    try:
        return handler(request)
    except Exception as e:
        return ToolMessage(
            content=f"Tool error: Please check your input and try again. ({e})",
            tool_call_id=request.tool_call["id"],
        )

agent = create_agent(
    model="ollama:north-mini-code-1.0",
    tools=[],
    middleware=[handle_tool_errors],
)
ts
import { createAgent, createMiddleware, ToolMessage } from "langchain";

const handleToolErrors = createMiddleware({
  name: "HandleToolErrors",
  wrapToolCall: async (request, handler) => {
    try {
      return await handler(request);
    } catch (error) {
      return new ToolMessage({
        content: `Tool error: Please check your input and try again. (${error})`,
        tool_call_id: request.toolCall.id!,
      });
    }
  },
});

const agent = createAgent({
  model: "google-genai:gemini-3.6-flash",
  tools: [],
  middleware: [handleToolErrors],
});
ts
import { createAgent, createMiddleware, ToolMessage } from "langchain";

const handleToolErrors = createMiddleware({
  name: "HandleToolErrors",
  wrapToolCall: async (request, handler) => {
    try {
      return await handler(request);
    } catch (error) {
      return new ToolMessage({
        content: `Tool error: Please check your input and try again. (${error})`,
        tool_call_id: request.toolCall.id!,
      });
    }
  },
});

const agent = createAgent({
  model: "openai:gpt-5.5",
  tools: [],
  middleware: [handleToolErrors],
});
ts
import { createAgent, createMiddleware, ToolMessage } from "langchain";

const handleToolErrors = createMiddleware({
  name: "HandleToolErrors",
  wrapToolCall: async (request, handler) => {
    try {
      return await handler(request);
    } catch (error) {
      return new ToolMessage({
        content: `Tool error: Please check your input and try again. (${error})`,
        tool_call_id: request.toolCall.id!,
      });
    }
  },
});

const agent = createAgent({
  model: "anthropic:claude-sonnet-4-6",
  tools: [],
  middleware: [handleToolErrors],
});
ts
import { createAgent, createMiddleware, ToolMessage } from "langchain";

const handleToolErrors = createMiddleware({
  name: "HandleToolErrors",
  wrapToolCall: async (request, handler) => {
    try {
      return await handler(request);
    } catch (error) {
      return new ToolMessage({
        content: `Tool error: Please check your input and try again. (${error})`,
        tool_call_id: request.toolCall.id!,
      });
    }
  },
});

const agent = createAgent({
  model: "openrouter:openrouter:z-ai/glm-5.2",
  tools: [],
  middleware: [handleToolErrors],
});
ts
import { createAgent, createMiddleware, ToolMessage } from "langchain";

const handleToolErrors = createMiddleware({
  name: "HandleToolErrors",
  wrapToolCall: async (request, handler) => {
    try {
      return await handler(request);
    } catch (error) {
      return new ToolMessage({
        content: `Tool error: Please check your input and try again. (${error})`,
        tool_call_id: request.toolCall.id!,
      });
    }
  },
});

const agent = createAgent({
  model: "fireworks:accounts/fireworks/models/glm-5p2",
  tools: [],
  middleware: [handleToolErrors],
});
ts
import { createAgent, createMiddleware, ToolMessage } from "langchain";

const handleToolErrors = createMiddleware({
  name: "HandleToolErrors",
  wrapToolCall: async (request, handler) => {
    try {
      return await handler(request);
    } catch (error) {
      return new ToolMessage({
        content: `Tool error: Please check your input and try again. (${error})`,
        tool_call_id: request.toolCall.id!,
      });
    }
  },
});

const agent = createAgent({
  model: "baseten:zai-org/GLM-5.2",
  tools: [],
  middleware: [handleToolErrors],
});
ts
import { createAgent, createMiddleware, ToolMessage } from "langchain";

const handleToolErrors = createMiddleware({
  name: "HandleToolErrors",
  wrapToolCall: async (request, handler) => {
    try {
      return await handler(request);
    } catch (error) {
      return new ToolMessage({
        content: `Tool error: Please check your input and try again. (${error})`,
        tool_call_id: request.toolCall.id!,
      });
    }
  },
});

const agent = createAgent({
  model: "ollama:north-mini-code-1.0",
  tools: [],
  middleware: [handleToolErrors],
});

状态注入

工具通过 ToolRuntime 访问图状态。状态、上下文、存储与流式 API 参见访问上下文

python
from langchain.tools import tool, ToolRuntime

@tool
def get_message_count(runtime: ToolRuntime) -> str:
    """Get the number of messages in the conversation."""
    messages = runtime.state["messages"]
    return f"There are {len(messages)} messages."

有关从工具访问状态、上下文与长期记忆的更多细节,参见访问上下文

动态工具选择

使用动态工具时,智能体可用的工具集在运行时被修改,而不是一开始就全部定义。并非每个工具都适合每种情况。工具太多可能让模型不堪重负(上下文过载)并增加错误;太少则限制能力。动态工具选择让可用工具集能够根据认证状态、用户权限、功能开关或对话阶段进行调整。

根据工具是否预先已知,有两种方法:

过滤预注册工具

当所有可能的工具在智能体创建时都已知道,你可以预先注册它们,并根据状态、权限或上下文动态过滤哪些工具暴露给模型。

状态

    在达到某些对话里程碑后才启用高级工具:
python
from langchain.agents import create_agent
from langchain.agents.middleware import wrap_model_call, ModelRequest, ModelResponse
from typing import Callable

@wrap_model_call
def state_based_tools(
    request: ModelRequest,
    handler: Callable[[ModelRequest], ModelResponse]
) -> ModelResponse:
    """Filter tools based on conversation State."""
    # 从 State 读取:检查用户是否已认证
    state = request.state
    is_authenticated = state.get("authenticated", False)
    message_count = len(state["messages"])

    # 仅在认证后启用敏感工具
    if not is_authenticated:
        tools = [t for t in request.tools if t.name.startswith("public_")]
        request = request.override(tools=tools)
    elif message_count < 5:
        # 在对话早期限制工具
        tools = [t for t in request.tools if t.name != "advanced_search"]
        request = request.override(tools=tools)

    return handler(request)

agent = create_agent(
    model="gpt-5.5",
    tools=[public_search, private_search, advanced_search],
    middleware=[state_based_tools]
)
typescript
import { createMiddleware, tool } from "langchain";
import { createDeepAgent } from "deepagents";

const stateBasedTools = createMiddleware({
    name: "StateBasedTools",
    wrapModelCall: (request, handler) => {
        // 从 State 读取:检查认证状态和对话长度
        const state = request.state as typeof request.state & {
            authenticated?: boolean;
        };
        const isAuthenticated = state.authenticated ?? false;
        const messageCount = state.messages.length;

        let filteredTools = request.tools;

        // 仅在认证后启用敏感工具
        if (!isAuthenticated) {
            filteredTools = request.tools.filter(
                (t: any) => typeof t.name === "string" && t.name.startsWith("public_"),
            );
        } else if (messageCount < 5) {
            filteredTools = request.tools.filter(
                (t: any) => typeof t.name === "string" && t.name !== "advanced_search",
            );
        }

        return handler({ ...request, tools: filteredTools });
    },
});

const agent = await createDeepAgent({
    model: "claude-sonnet-4-6",
    tools: tools,
    middleware: [stateBasedTools] as any,
});

存储

    根据存储中的用户偏好或功能开关过滤工具:
python
from dataclasses import dataclass
from langchain.agents import create_agent
from langchain.agents.middleware import wrap_model_call, ModelRequest, ModelResponse
from typing import Callable
from langgraph.store.memory import InMemoryStore

@dataclass
class Context:
    user_id: str

@wrap_model_call
def store_based_tools(
    request: ModelRequest,
    handler: Callable[[ModelRequest], ModelResponse]
) -> ModelResponse:
    """Filter tools based on Store preferences."""
    user_id = request.runtime.context.user_id

    # 从 Store 读取:获取用户已启用的功能
    store = request.runtime.store
    feature_flags = store.get(("features",), user_id)

    if feature_flags:
        enabled_features = feature_flags.value.get("enabled_tools", [])
        # 仅包含该用户已启用的工具
        tools = [t for t in request.tools if t.name in enabled_features]
        request = request.override(tools=tools)

    return handler(request)

agent = create_agent(
    model="gpt-5.5",
    tools=[search_tool, analysis_tool, export_tool],
    middleware=[store_based_tools],
    context_schema=Context,
    store=InMemoryStore()
)
typescript
import { createMiddleware } from "langchain";
import { createDeepAgent, StoreBackend } from "deepagents";
import * as z from "zod";
import { InMemoryStore } from "@langchain/langgraph";

const contextSchema = z.object({
  userId: z.string(),
});

const storeBasedTools = createMiddleware({
  name: "StoreBasedTools",
  contextSchema,
  wrapModelCall: async (request, handler) => {
    const userId =
      (request.runtime?.context as { userId?: string } | undefined)?.userId ??
        "user-123";

    // 从 Store 读取:获取用户已启用的功能
    const runtimeStore = request.runtime?.store as InMemoryStore | undefined;
    const rawFlags = (await runtimeStore?.get(
      ["features"],
      userId as string,
    )) as unknown;
    const featureFlags = rawFlags as FeatureFlags | undefined;

    let filteredTools = request.tools;

    if (featureFlags) {
      const enabledFeatures = featureFlags.enabledTools || [];
      filteredTools = request.tools.filter((t) =>
        enabledFeatures.includes(t.name as string)
      );
    }

    return handler({ ...request, tools: filteredTools });
  },
});

const agent = await createDeepAgent({
  model: "claude-sonnet-4-6",
  backend: new StoreBackend(),
  store,
  checkpointer,
  tools,
  middleware: [storeBasedTools] as any,
});

运行时上下文

    根据运行时上下文中的用户权限过滤工具:
python
from dataclasses import dataclass
from langchain.agents import create_agent
from langchain.agents.middleware import wrap_model_call, ModelRequest, ModelResponse
from typing import Callable

@dataclass
class Context:
    user_role: str

@wrap_model_call
def context_based_tools(
    request: ModelRequest,
    handler: Callable[[ModelRequest], ModelResponse]
) -> ModelResponse:
    """Filter tools based on Runtime Context permissions."""
    # 从 Runtime Context 读取:获取用户角色
    if request.runtime is None or request.runtime.context is None:
        # 如果未提供上下文,默认视为 viewer(限制最严格)
        user_role = "viewer"
    else:
        user_role = request.runtime.context.user_role

    if user_role == "admin":
        # 管理员获得所有工具
        pass
    elif user_role == "editor":
        # 编辑者不能删除
        tools = [t for t in request.tools if t.name != "delete_data"]
        request = request.override(tools=tools)
    else:
        # 查看者获得只读工具
        tools = [t for t in request.tools if t.name.startswith("read_")]
        request = request.override(tools=tools)

    return handler(request)

agent = create_agent(
    model="gpt-5.5",
    tools=[read_data, write_data, delete_data],
    middleware=[context_based_tools],
    context_schema=Context
)
typescript
import * as z from "zod";
import { createMiddleware } from "langchain";
import { createDeepAgent } from "deepagents";

const contextSchema = z.object({
  userRole: z.string(),
});

const contextBasedTools = createMiddleware({
  name: "ContextBasedTools",
  contextSchema,
  wrapModelCall: (request, handler) => {
    // 从 Runtime Context 读取:获取用户角色
    const userRole = request.runtime.context.userRole;

    let filteredTools = request.tools;

    if (userRole === "admin") {
      // 管理员获得所有工具
    } else if (userRole === "editor") {
      filteredTools = request.tools.filter((t) => t.name !== "delete_data");
    } else {
      filteredTools = request.tools.filter(
        (t) => (t.name as string).startsWith("read_"),
      );
    }

    return handler({ ...request, tools: filteredTools });
  },
});

const agent = await createDeepAgent({
  model: "claude-sonnet-4-6",
  store,
  checkpointer,
  tools,
  middleware: [contextBasedTools] as any,
});
这种方法最适合以下情况:
- 所有可能的工具在编译/启动时都已知道
- 你想根据权限、功能开关或对话状态进行过滤
- 工具是静态的,但它们的可用性是动态的

更多示例参见[动态选择工具](/oss/langchain/middleware/custom#dynamically-selecting-tools)。

运行时工具注册

当工具在运行时被发现或创建(例如从 MCP 服务器加载、根据用户数据生成、或从远程注册表获取)时,你需要同时注册这些工具并动态处理它们的执行。

这需要两个中间件钩子:
1. `wrap_model_call` —— 把动态工具添加到请求中
2. `wrap_tool_call` —— 处理动态添加的工具的执行
python
from langchain.tools import tool
from langchain.agents import create_agent
from langchain.agents.middleware import AgentMiddleware, ModelRequest, ToolCallRequest

# 一个将在运行时动态添加的工具
@tool
def calculate_tip(bill_amount: float, tip_percentage: float = 20.0) -> str:
    """Calculate the tip amount for a bill."""
    tip = bill_amount * (tip_percentage / 100)
    return f"Tip: ${tip:.2f}, Total: ${bill_amount + tip:.2f}"

class DynamicToolMiddleware(AgentMiddleware):
    """Middleware that registers and handles dynamic tools."""

    def wrap_model_call(self, request: ModelRequest, handler):
        # 将动态工具添加到请求中
        # 这可以从 MCP 服务器、数据库等加载
        updated = request.override(tools=[*request.tools, calculate_tip])
        return handler(updated)

    def wrap_tool_call(self, request: ToolCallRequest, handler):
        # 处理动态工具的执行
        if request.tool_call["name"] == "calculate_tip":
            return handler(request.override(tool=calculate_tip))
        return handler(request)

agent = create_agent(
    model="gpt-5.5",
    tools=[get_weather],  # 这里只注册静态工具
    middleware=[DynamicToolMiddleware()],
)

# 智能体现在可以使用 get_weather 和 calculate_tip
result = agent.invoke({
    "messages": [{"role": "user", "content": "Calculate a 20% tip on $85"}]
})
typescript
import { createAgent, createMiddleware, tool } from "langchain";
import * as z from "zod";

// 一个将在运行时动态添加的工具
const calculateTip = tool(
  ({ billAmount, tipPercentage = 20 }) => {
    const tip = billAmount * (tipPercentage / 100);
    return `Tip: $${tip.toFixed(2)}, Total: $${(billAmount + tip).toFixed(2)}`;
  },
  {
    name: "calculate_tip",
    description: "Calculate the tip amount for a bill",
    schema: z.object({
      billAmount: z.number().describe("The bill amount"),
      tipPercentage: z.number().default(20).describe("Tip percentage"),
    }),
  }
);

const dynamicToolMiddleware = createMiddleware({
  name: "DynamicToolMiddleware",
  wrapModelCall: (request, handler) => {
    // 将动态工具添加到请求中
    // 这可以从 MCP 服务器、数据库等加载
    return handler({
      ...request,
      tools: [...request.tools, calculateTip],
    });
  },
  wrapToolCall: (request, handler) => {
    // 处理动态工具的执行
    if (request.toolCall.name === "calculate_tip") {
      return handler({ ...request, tool: calculateTip });
    }
    return handler(request);
  },
});

const agent = createAgent({
  model: "gpt-5.5",
  tools: [getWeather], // 这里只注册静态工具
  middleware: [dynamicToolMiddleware],
});

// 智能体现在可以使用 getWeather 和 calculateTip
const result = await agent.invoke({
  messages: [{ role: "user", content: "Calculate a 20% tip on $85" }],
});
这种方法最适合以下情况:
- 工具在运行时被发现(例如来自 MCP 服务器)
- 工具根据用户数据或配置动态生成
- 你正在与外部工具注册表集成

INFO

运行时注册的工具需要 wrap_tool_call 钩子,因为智能体需要知道如何执行原始工具列表中没有的工具。没有它,智能体将不知道如何调用动态添加的工具。

无头工具(Headless tools)

有些工具应该在你用户的应用所运行的地方(通常是浏览器)运行,而不是在进程内。无头工具是工具定义,包含名称、描述与参数 schema,你在服务器上与智能体一起注册。实现只在客户端注册,并在一次简短的中断/恢复握手后执行。

这与函数体在服务器上运行的普通工具不同,也与服务端工具使用(模型提供商远程执行内置工具)不同。

何时使用无头工具

当工作依赖于只存在于客户端的环境、设备或 UI 时使用它们。例如:

  • 浏览器 API: 地理位置、IndexedDB、剪贴板、Canvas 2D、文件选择器、Battery API 等。
  • 隐私与本地性: 数据保留在设备上(例如 IndexedDB 中的本地“记忆”)。
  • 延迟: 纯本地操作无需额外的服务器往返。
  • 结构化、安全的副作用: 优先使用许多小而类型化的工具(例如每个 canvas 原语一个工具),而不是把任意代码发送到 eval

该模式如何工作

在两种运行时中,模型都会看到一个它可以正常调用的工具,但实际执行发生在服务器进程之外。

  1. 定义一个无头工具,使用 langchain.tools 中的 tool(name=..., description=..., args_schema=...)。无头工具仅含 schema,没有进程内实现。
  2. 注册该工具到 create_agent 或你的 LangGraph 图,这样模型可以正常调用它。
  3. 处理工具被调用时的中断载荷。图不会在本地运行,而是暂停并携带一个形如 {"type": "tool", "tool_call": {"id", "name", "args"}} 的载荷。
  4. 恢复图,在你的应用、另一个服务或人工步骤执行完操作之后。对于基于浏览器的流程,你可以在前端镜像 schema 并用 .implement(...) 附加实现。

INFO

如果你在 Python 中只以 namedescriptionargs_schema 调用 tool(...),LangChain 会返回一个 HeadlessTool。Python 端没有 .implement() API。

  1. 定义工具,使用 langchain 中的 tool({ name, description, schema }),仅元数据与校验,无服务器端执行器。
  2. 附加真实行为,使用 .implement(async (args) => { ... }),它会返回一个无头工具实现(定义 + execute 函数)。
  3. 注册第 1 步的定义到 createAgent 或你的图,这样模型在其常规工具调用循环中能看到该工具。
  4. 传递第 2 步的实现给你的流式钩子的 tools 选项。

INFO

工具定义tool({ name, description, schema }))与实现.implement(...))放在不同的模块中。在你的服务器智能体和前端中都导入共享的定义文件,使名称与 schema 保持一致;把仅客户端的执行逻辑放在服务器永远不会加载的实现模块中。

当模型为这些工具之一发出工具调用时,运行会中断,而不是在本地执行工具。你的应用可以检查载荷、在正确的环境中(例如浏览器、另一个服务或人工审查步骤)执行操作,然后用工具结果恢复图。当你使用受支持的 JS SDK 钩子时,它们可以检测无头工具中断、运行匹配的客户端实现,并为你提交恢复命令。

使用可选的 onTool 回调来观察生命周期事件(startsuccesserror),用于旋转指示器或 toast 等 UI 反馈。

预置工具

LangChain 提供了大量针对常见任务(如网络搜索、代码解释、数据库访问等)的预置工具与工具包。这些现成可用的工具可以直接集成到你的智能体中,而无需编写自定义代码。

按类别组织的完整工具列表参见工具与工具包集成页面。

服务端工具使用

某些对话模型内置了由模型提供商在服务端执行的工具。这些包括网络搜索与代码解释器等能力,你无需定义或托管工具逻辑。

启用与使用这些内置工具的细节,请参阅各个对话模型集成页面以及工具调用文档