工具 (Tools)
定义工具以允许 LangChain 智能体获取实时数据、执行代码、查询外部数据库并采取行动。
工具扩展了智能体的能力——让它们能够获取实时数据、执行代码、查询外部数据库并在现实世界中采取行动。
在底层,工具是具有明确定义输入和输出的可调用函数,它们会被传递给对话模型。模型会根据对话上下文决定何时调用工具,以及提供哪些输入参数。
TIP
有关模型如何处理工具调用的详细信息,请参阅工具调用。使用 LangSmith 追踪工具调用并调试错误。按照追踪快速入门进行配置。
我们建议你同时配置 LangSmith Engine,它可以监控你的追踪记录、检测问题并提出修复建议。
创建工具
基础工具定义
创建工具最简单的方法是从 langchain 包中导入 tool 函数。你可以使用 zod 来定义工具的输入模式(schema):
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"),
}),
}
);NOTE
服务端工具使用: 某些对话模型具备在服务端执行的内置工具(网络搜索、代码解释器)。详情请参阅服务端工具使用。
WARNING
建议工具名称使用 snake_case(蛇形命名法,例如使用 web_search 而非 Web Search)。某些模型提供商在处理包含空格或特殊字符的名称时可能会出现问题或报错拒绝。坚持使用字母数字字符、下划线和连字符有助于提高跨提供商的兼容性。
访问上下文
当工具能够访问诸如对话历史、用户数据和持久化记忆等运行时信息时,其功能最为强大。本节介绍如何从工具内部访问和更新这些信息。
上下文 (Context)
上下文(Context)提供了在调用时传入的不可变配置数据。可用于用户 ID、会话详情或在对话期间不应更改的特定于应用的设置。
NOTE
虽然 thread_id(通过 config={"configurable": {"thread_id": ...}} 传递)用于界定对话的作用域:消息历史和检查点,但 context 携带的是工具和中间件在调用时读取的单次运行(per-run)数据。持久化需要使用检查点保存器(checkpointer)。在生产环境中,你通常会在每次调用时同时传入每个对话的稳定 thread_id 和一个 context 对象。
工具可以通过 config 参数访问智能体的运行时上下文。配置一个检查点保存器(checkpointer)并在传入稳定的 thread_id 的同时传递 context,以便对话在多次调用之间保持持久化:
import * as z from "zod";
import { uuid7 } from "langsmith";
import { ChatOpenAI } from "@langchain/openai";
import { MemorySaver } from "@langchain/langgraph";
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: "gpt-5.5" }),
tools: [getUserName],
checkpointer: new MemorySaver(),
contextSchema,
});
const threadId = uuid7();
const threadConfig = {
configurable: { thread_id: threadId },
context: { user_name: "John Smith" },
};
let result = await agent.invoke(
{
messages: [{ role: "user", content: "What is my name?" }],
},
threadConfig,
);
console.log(result.messages.at(-1)?.content);
result = await agent.invoke(
{
messages: [{ role: "user", content: "What was my name again?" }],
},
threadConfig,
);
console.log(result.messages.at(-1)?.content);打开该示例的公开 LangSmith 运行记录。
长期记忆 (Store)
BaseStore 提供了能够跨会话持久存在的存储。与状态(短期记忆)不同,保存到 store 的数据在未来的会话中仍然可用。
通过 config.store 访问 store。store 使用命名空间/键(namespace/key)模式来组织数据:
TIP
对于生产部署,请使用持久化 store 实现(例如 PostgresStore、MongoDBStore 或 RedisStore)代替 InMemoryStore。配置详情请参阅记忆文档。
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" 的用户信息如下:
// - Name: Foo
// - Age: 25
// - Email: foo@langchain.dev流式写入器 (Stream writer)
在工具执行期间流式传输实时更新。这在长时间运行的操作中向用户提供进度反馈时非常有用。
使用 config.writer 发送自定义更新:
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(),
}),
}
);执行信息 (Execution info)
通过 runtime.execution_info 从工具内部访问线程 ID、运行 ID 和重试状态:
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({}),
}
);NOTE
需要 deepagents>=1.9.0(或 @langchain/langgraph>=1.2.8)。
服务端信息 (Server info)
当你的工具在 LangGraph Server 上运行时,可以通过 runtime.server_info 访问助手 ID(assistant ID)、图 ID(graph ID)和已认证用户:
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 上运行时,serverInfo 为 null。
NOTE
需要 deepagents>=1.9.0(或 @langchain/langgraph>=1.2.8)。
工具执行
在 LangChain 中,工具由智能体使用(例如通过 create_agent),工具错误处理则通过中间件进行配置。
对于 LangGraph 工作流,工具执行由 ToolNode 处理。有关 Graph API 的用法(包括工具如何访问当前图状态和运行作用域上下文),请参阅 ToolNode。
工具返回值
你可以为工具选择不同的返回值类型:
- 返回
string以获得人类可读的结果。 - 返回
object以获得供模型解析的结构化结果。 - 当需要写入状态时,返回带有可选消息的
Command。
返回字符串
当工具应提供纯文本供模型阅读并在其后续回复中使用时,返回字符串。
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() }),
});打开该示例的公开 LangSmith 运行记录。
行为特性:
- 返回值会被转换为
ToolMessage。 - 模型读取该文本并决定下一步操作。
- 除非模型或其他工具后续进行修改,否则不会更改智能体的任何状态字段。
适用场景:当结果本身就是自然的人类可读文本时,使用此方式。
返回对象
当你的工具生成模型需要检查的结构化数据时,返回一个对象(例如 dict)。
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() }),
},
);打开该示例的公开 LangSmith 运行记录。
行为特性:
- 对象会被序列化并作为工具输出返回。
- 模型可以读取特定字段并对其进行推理。
- 与字符串返回值类似,这不会直接更新图状态。
适用场景:当下游推理受益于显式字段而不是自由格式文本时,使用此方式。
返回多模态内容
工具不仅限于纯文本。当模型支持多模态工具结果时,工具可以返回标准内容块,从而使模型在单次工具结果中接收文本、图像和其他媒体。
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({}),
}
);行为特性:
- 返回值会被转换为带有多模态
content的ToolMessage。 - 在工具运行后,使用
message.content_blocks读取归一化后的内容块列表。 - 模型必须支持你返回的模态。在返回图像、音频或视频之前,请检查你的模型能力。
有关内容块类型和特定提供商的要求,请参阅多模态消息。
返回 Command
当工具需要更新图状态(例如设置用户偏好或应用状态)时,返回一个 Command。 当 Command 针对当前图时,在更新中包含一个 ToolMessage,其工具调用 ID 需与当前工具调用相匹配。 消息历史中的每一次工具调用都必须有对应的 ToolMessage。
在 tool_call_id 参数中使用 runtime.toolCallId。
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() }),
},
);打开该示例的公开 LangSmith 运行记录。
行为特性:
- 命令通过
update更新状态。 - 更新后的状态可在同一次运行的后续步骤中使用。
- 对于可能由并行工具调用更新的字段,请使用 reducer。
适用场景:当工具不仅返回数据,而且还修改智能体状态时,使用此方式。
从工具直接返回
在工具上设置直接返回(return direct)以短路智能体循环:智能体立即将工具的输出返回给调用方,而无需将其送回模型进行进一步处理。
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: "gpt-4o-mini" }),
tools: [fetchOrderStatus],
});
const result = await agent.invoke({
messages: [
{ role: "user", content: "What is the status of order #12345?" },
],
});
// 智能体直接返回工具输出,无需再次调用大模型:
// "Order 12345 is shipped and will arrive in 2 days."打开该示例的公开 LangSmith 运行记录。
行为特性:
- 工具正常执行,其输出被封装在
ToolMessage中。 - 智能体停止循环并将工具的输出作为最终响应返回,绕过任何额外的模型调用。
- 多个并行工具调用: 当模型在单一步骤中调用多个工具时,所有工具都会先执行。所有工具执行完成后,只有当该批次中的每一个工具都设置了
return_direct=True时,智能体才会路由到END。最终响应包含在该步骤中调用的每个工具的ToolMessage输出。
适用场景:
- 工具的输出是完整的、可直接面向用户的答案(例如返回立即可展示结果的查询操作)。
- 当不需要进一步推理时,你希望避免额外的模型调用。
- 你需要确定性、未修改的输出:模型不能改写、总结或根据工具结果采取行动。
WARNING
由于模型不处理工具的输出,因此 return_direct=True 不适用于结果需要进一步推理、总结或与其他工具调用链接的工具。
WARNING
混合并行调用: 如果模型在调用带有 return_direct=True 的工具的同时,还调用了未设置 return_direct=True 的工具,则智能体在这一步之后不会退出。它会将该批次中的每个 ToolMessage 路由回模型,以便模型可以对所有结果进行推理。只有在该步骤中的每个工具调用都具有 return_direct=True 时,return_direct 才会短路循环。
错误处理
使用 LangChain 智能体中间件来处理工具错误,以重试失败的工具调用或返回自定义错误消息:
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:gemini-3.6-flash",
tools: [],
middleware: [handleToolErrors],
});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],
});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-5",
tools: [],
middleware: [handleToolErrors],
});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:z-ai/glm-5.2",
tools: [],
middleware: [handleToolErrors],
});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],
});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],
});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 访问图状态。有关状态、上下文、store 和流式传输 API,请参阅访问上下文。
有关从工具中访问状态、上下文和长期记忆的更多详细信息,请参阅访问上下文。
动态工具选择
通过动态工具,智能体可用的工具集合可以在运行时进行修改,而不是全部预先定义。并非所有工具都适用于所有情况。工具过多可能会让模型不堪重负(导致上下文过载)并增加错误率;工具太少又会限制能力。动态工具选择能够根据身份验证状态、用户权限、功能开关(feature flags)或对话阶段来调整可用的工具集。
根据工具是否提前已知,有两种实现方法:
过滤预注册工具
当所有可能的工具在智能体创建时均已知,你可以预先注册它们,并根据状态、权限或上下文动态过滤向模型暴露哪些工具。
State(状态)
仅在达到某些对话里程碑后才启用高级工具:
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,
});Store
根据 Store 中的用户偏好或功能开关过滤工具:
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,
});运行时上下文 (Runtime Context)
根据运行时上下文中的用户权限过滤工具:
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) => {
// 从运行时上下文读取:获取用户角色
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,
});该方法最适合于以下情况:
- 所有可能的工具在编译/启动时已知
- 你希望根据权限、功能开关或对话状态进行过滤
- 工具本身是静态的,但其可用性是动态的
更多示例请参阅动态选择工具。
运行时工具注册
当工具是在运行时发现或创建的(例如,从 MCP 服务器加载、根据用户数据生成或从远程注册表获取),你既需要动态注册工具,又需要动态处理它们的执行。
这需要两个中间件钩子:
wrap_model_call- 将动态工具添加到请求中wrap_tool_call- 处理动态添加的工具的执行
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 服务器)
- 工具是根据用户数据或配置动态生成的
- 你正在与外部工具注册表集成
NOTE
运行时注册的工具需要 wrap_tool_call 钩子,因为智能体需要知道如何执行不在初始工具列表中的工具。如果没有它,智能体将不知道如何调用动态添加的工具。
Headless 工具
有些工具应该在用户应用程序运行的地方(通常是浏览器)运行,而不是在服务端进程内部。Headless 工具是工具定义(包括名称、描述和参数模式),你在服务端将其与智能体一起注册。而具体实现仅在客户端注册,并在短暂的中断/恢复(interrupt/resume)握手后执行。
这不同于函数体在服务端运行的普通工具,也不同于由模型提供商远程执行内置工具的服务端工具使用。
何时使用 Headless 工具
当工作依赖于仅存在于客户端的环境、设备或 UI 时,请使用它们。例如:
- 浏览器 API: 地理位置(Geolocation)、IndexedDB、剪贴板(Clipboard)、Canvas 2D、文件选择器、电池 API(Battery API)等。
- 隐私与本地化: 数据保留在设备上(例如 IndexedDB 中的本地“记忆”)。
- 延迟: 纯本地操作无需额外的服务器往返通信。
- 结构化、安全的副作用: 倾向于使用许多小型类型化工具(例如每个画布图元一个工具),而不是将任意代码发送到
eval。
该模式的工作原理
在两种运行时中,模型看到的都是一个可以调用的普通工具,但实际执行发生在服务端进程之外。
- 使用
langchain中的tool({ name, description, schema })定义工具,仅包含元数据和校验,无服务端运行器。 - 通过
.implement(async (args) => { ... })附加真实行为,这会返回一个 Headless 工具实现(定义 +execute函数)。 - 将步骤 1 中的定义与
createAgent或你的图进行注册,以便模型在其常用的工具调用循环中看到该工具。 - 将步骤 2 中的实现传递给你流式钩子(streaming hook)的
tools选项。
NOTE
将工具定义(tool({ name, description, schema }))和实现(.implement(...))放在独立的模块中。从你的服务端智能体和前端导入共享的定义文件,以保持名称和模式一致;将仅限客户端的执行逻辑保留在服务端从不加载的实现模块中。
当模型针对这些工具之一发起工具调用时,运行将中断,而不是在本地执行该工具。你的应用可以检查有效负载,在正确的环境(例如浏览器、其他服务或人工审核步骤)中执行操作,然后使用工具结果恢复图的执行。当你使用支持的 JS SDK 钩子时,它们可以检测 Headless 工具中断,运行匹配的客户端实现,并自动为你提交恢复命令。
使用可选的 onTool 回调来观察生命周期事件(start、success、error),以获取 UI 反馈(如加载微调器或浮动提示 toast)。
查看在客户端使用
useStream执行纯 schema 工具的端到端示例。
预置工具
LangChain 为网络搜索、代码解释、数据库访问等常见任务提供了大量预置工具和工具包。这些开箱即用的工具可以直接集成到你的智能体中,无需编写自定义代码。
有关按类别组织的可用工具的完整列表,请参阅工具与工具包集成页面。
来自 MCP 服务器的工具
模型上下文协议 (MCP) 是一项开放协议,它规范了应用程序向语言模型公开工具的方式。你无需手动编写工具,而是连接到 MCP 服务器,并将其宣告的工具适配为 LangChain 工具,以便像其他任何工具一样传递给智能体。
LangChain 智能体通过 @langchain/mcp-adapters 库调用定义在 MCP 服务器上的工具,该库可以发现服务器的工具并将其适配为 LangChain 工具。
有关安装配置、传输协议和身份验证,请参阅模型上下文协议 (MCP)。
服务端工具使用
某些对话模型具备由模型提供商在服务端执行的内置工具。其中包括网络搜索和代码解释器等功能,无需你定义或托管工具逻辑。
