什么是工具使用?
您现有的工作流程依赖于许多不同的技术——项目管理软件、数据库、文件。Claude无法自行查看这些内容。相反,它依赖于工具,这些工具让Claude能够访问外部数据和执行操作。
什么是工具
简单来说,工具是您定义并暴露给Claude的函数。您描述它的功能和所需的输入,Claude决定何时调用它。
这里有一个需要内化的关键点:Claude不会执行工具——您的代码才会执行。流程如下:
- Claude请求调用工具。
- 您的代码执行该函数。
- 结果返回给Claude,它继续进行下去。
工具是如何定义的
工具是JSON模式(schema),包含三个部分:名称、描述和输入模式。您在请求体中以tools数组的形式将它们传递给Claude。
描述是Claude用来决定是否调用该工具的依据。如果您写的描述含糊不清,就会得到糟糕的工具使用效果。这是智能体(agent)误操作或不使用可用工具的头号原因。请务必具体明确。
以下是一个工具定义的样子:
{
"name": "lookup_building_code",
"description": "Look up a specific building code section by its identifier. Returns the full text of that code section.",
"input_schema": {
"type": "object",
"properties": {
"section": {
"type": "string",
"description": "The building code section to look up"
}
},
"required": ["section"]
}
}那么当我们使用它时会发生什么呢?假设我们向智能体发送一份合规报告。在第一轮中,Claude返回stop_reason: "tool_use"——这就是我们的信号。该响应如下所示:

我们的循环使用Claude请求的参数调用lookup_building_code,然后将结果作为工具结果反馈回去——这是一条包含与工具调用id绑定的tool_result块的用户消息:

然后Claude继续进行。此时,我们可以持续调用工具并将结果返回给Claude,直到它获得所需的一切。
多个工具:让Claude自行选择
一个工具很有用,但更有趣的部分在于给Claude提供多个工具,并观察它如何选择使用哪一个、以什么顺序使用。
设想这样一个场景:您正在为前往丹佛的三天旅行打包行李,您想同时了解今天的天气和未来几天的预报。于是我们声明两个工具而不是一个:
const tools = [
{
name: "get_weather",
description: "Get today's current weather for a city.",
input_schema: {
type: "object",
properties: {
city: { type: "string", description: "The city to check" }
},
required: ["city"]
}
},
{
name: "get_forecast",
description: "Get the weather forecast for the next few days for a city.",
input_schema: {
type: "object",
properties: {
city: { type: "string", description: "The city to check" }
},
required: ["city"]
}
}
];这个循环与我们之前见过的智能体循环完全相同。唯一新增的部分是一个runTool函数,它通过switch语句根据工具名称进行分派——这段代码正是您的代码实际运行的地方:
function runTool(name, input) {
switch (name) {
case "get_weather":
return getWeather(input.city);
case "get_forecast":
return getForecast(input.city);
}
}
while (true) {
const response = await client.messages.create({
model: "claude-sonnet-5",
max_tokens: 1024,
messages,
tools,
});
if (response.stop_reason !== "tool_use") {
// Claude 已完成——这是最终答案
break;
}
messages.push({ role: "assistant", content: response.content });
const toolResults = response.content
.filter((block) => block.type === "tool_use")
.map((block) => ({
type: "tool_result",
tool_use_id: block.id,
content: runTool(block.name, block.input),
}));
messages.push({ role: "user", content: toolResults });
}这就是整个模式。想要第三个工具?把它加入数组,在switch中加一个case,就完成了。
运行这段代码,您会看到Claude调用get_weather,然后调用get_forecast——有时在同一轮中,有时一前一后。然后它会给出答案:建议分层穿衣,预计今天有阵雪,本周气温会逐渐回暖。
现在请注意Claude是如何做出选择的。它读取了描述,将您的提示映射到"今天的天气"和"未来几天",并为每个需求选择了正确的工具。这就是为什么您的工具描述真的很重要。
工具运行器:跳过样板代码
您可能已经发现了我们刚才写的代码中的两个危险信号:
- 对于两个简单的查询来说,这是相当多的代码。
- 在真实的代码库中,您不会想为每个函数都手写JSON模式。这就像把代码写两遍一样。
这正是工具运行器(tool runner)发挥作用的地方。它已在Claude SDK中以测试版形式提供:TypeScript、Python、Ruby、C#、Go、Java和PHP。您只需定义每个工具一次,运行器会在内部处理整个工具使用/工具结果循环。
在TypeScript中,运行器推荐使用的辅助函数betaZodTool(),用一个以Zod(一个用于声明和验证数据结构的TypeScript库)编写的简短模式来描述每个工具。该辅助函数需要Zod 3.25.0或更高版本,全新安装即可满足此要求:
npm install zod您的代码将缩减为:描述工具、发送提示、等待结果。以下是通过工具运行器实现的相同双工具天气演示:
import { betaZodTool } from "@anthropic-ai/sdk/helpers/beta/zod";
import { z } from "zod";
// 封装我们手动执行过的两个相同查询,各配一个简短的 Zod schema
const weatherTool = betaZodTool({
name: "get_weather",
description: "Get today's current weather for a city.",
inputSchema: z.object({
city: z.string().describe("The city to check"),
}),
run: async ({ city }) => getWeather(city),
});
const forecastTool = betaZodTool({
name: "get_forecast",
description: "Get the weather forecast for the next few days for a city.",
inputSchema: z.object({
city: z.string().describe("The city to check"),
}),
run: async ({ city }) => getForecast(city),
});
const runner = client.beta.messages.toolRunner({
model: "claude-sonnet-5",
max_tokens: 1024,
messages: [
{
role: "user",
content:
"I'm packing for a three-day trip to Denver. What's the weather today and over the next few days?",
},
],
tools: [weatherTool, forecastTool],
});
// 等待 runner 返回,在所有工具往返调用都结束后获取最终消息
const finalMessage = await runner;同样的场景,代码量却大幅减少:
- 没有while循环,没有stop reason的switch判断,也不需要手动将工具结果推回消息中——运行器会处理所有这些。
- 没有手写的JSON模式,每个工具只需一个简短的Zod模式,它同时还会验证Claude发送的输入。
- 每个
run函数都封装了我们刚才手动运行的同一个查询。 - 等待运行器完成后,会在一切处理完毕时返回最终的助手消息。
运行它,您会得到相同的答案。
真实的工具封装了您现有的代码
在现实生活中,您的工具不会是硬编码的天气数据。它们会封装您应用程序中已经存在的实际函数。
以一个合规审查智能体为例:它的工具是围绕代码库中已经存在的lookup_building_code和search_building_code函数的薄封装层。借助工具运行器,您可以将这些函数各自封装进一个带有自己Zod模式的简短工具定义中,智能体在它撰写的每一条发现中都会引用具体的规范条款:

回顾
- 工具让Claude能够访问您的系统。 工具是您定义并暴露的函数;Claude决定何时调用它,而您的代码负责执行它。
- 工具是包含名称、描述和输入模式的JSON模式,在请求中以
tools数组的形式传递。 - 编写具体的描述。 含糊的描述是智能体误操作的头号原因。
stop_reason: "tool_use"是您运行工具并将结果作为工具结果反馈回去的信号。- 对于多个工具,根据工具名称进行分派。添加一个工具意味着在数组中添加一项,并添加一个case。
- SDK的工具运行器(在各个Claude SDK中以测试版形式提供)处理整个循环,在TypeScript中,每个工具都是一个带有Zod模式的简短定义。您也可以自行运行该循环。
- 您可以自己执行,也可以委托循环处理。在这个范围的最远端,托管智能体(managed agents)将整个智能体委托给Anthropic处理。