你现有的工作流依赖许多不同的技术——项目管理软件、数据库、文件。Claude 无法自己检查这些内容。相反,它依赖 工具,工具让 Claude 访问外部数据和操作。
简单来说,工具是你 定义并暴露给 Claude 的函数。你描述它做什么以及它接受什么输入,Claude 决定何时调用它。
需要内化的关键点是:Claude 不执行工具——你的代码执行。流程如下:
工具是包含三部分的 JSON 模式:名称、描述 和 输入模式。你在请求体中以 tools 数组的形式传递它们。
描述 是 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"]
}
}
那么当我们使用这个时会发生什么?假设我们向 agent 发送一份合规报告。在第一轮中,Claude 返回 stop_reason: "tool_use" — 这是我们的信号。这个响应是什么样子的:
我们的循环使用 Claude 请求的参数调用 lookup_building_code,然后将结果作为 工具结果 反馈——一个包含与工具调用 id 关联的 tool_result 块的用户消息。
然后 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"]
}
}
];
登录 参与讨论
循环与我们已经看到的 agent 循环相同。唯一新的是一个 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 如何选择。它读取描述,将你的提示映射到 "今天的天气" 和 "未来几天",并为每个选择正确的工具。这就是为什么你的工具描述真的很重要。
你可能已经发现我们刚才写的有两个问题:
这就是 工具运行器 的用武之地。它在 Claude SDK 中以测试版发布:TypeScript、Python、Ruby、C#、Go、Java 和 PHP。你定义每个工具一次,运行器在内部处理整个工具使用/工具结果循环。
你的代码缩减为:描述工具、发送提示、等待结果。这是通过工具运行器连接的相同两个工具的天气演示:
// 我们手动运行的相同两个查询——只是普通的 TypeScript 函数
function getWeather(city: string) {
// ...现有查询
}
function getForecast(city: string) {
// ...现有查询
}
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: [getWeather, getForecast],
});
// 等待运行器以在所有工具交互结束后获取最终消息
const finalMessage = await runner;
相同的场景,代码的一小部分:
运行它,你会得到相同的答案。
在现实生活中,你的工具不会是硬编码的天气数据。它们会包装 你应用程序中已有的实际函数。
以合规审查 agent 为例:它的工具是围绕 lookup_building_code 和 search_building_code 函数的薄包装器,这些函数已经存在于代码库中。使用工具运行器,你直接传递这些函数,agent 在它编写的每个发现中引用特定的代码部分——不需要编写模式。
tools 数组传递。stop_reason: "tool_use" 是你运行工具并将其结果作为工具结果反馈的信号。