关于 ai-patterns-tool-use-patterns
面向实现 LLM 工具调用和 Agent 循环的开发者技能,涵盖模式定义、有限循环、输入校验、错误恢复与工具选择控制。
主要功能
- 01JSON Schema 工具定义
- 02设定上限的工具调用循环
- 03输入校验与结构化错误
- 04工具选择控制
- 05并行调用与审批机制
- 06TypeScript 类型安全模式
适用场景
- 01实现跨提供方的工具调用
- 02构建多步骤 Agent 工作流
- 03处理并行工具调用
- 04为高风险操作添加审批
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ agents-inc/ skills --skill ai-patterns-tool-use-patterns安装后在 Claude Code 中输入/ai-patterns-tool-use-patterns确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
metadata.yamlreference.mdexamples/advanced.mdcore.md详细介绍
工具使用模式
快速指南: 工具使用(函数调用)允许大模型调用外部函数。通用模式为:定义工具架构(参数使用 JSON Schema)-> 将工具和消息发送给大模型 -> 检测响应中的 tool_use -> 在本地执行 -> 将结果返回给大模型 -> 重复此过程,直到模型以文本作答。每个循环都必须设置最大步数限制,执行前验证所有工具输入,并返回结构化错误,以便模型恢复。使用工具选择控制(
auto、required、none、指定工具)来引导模型行为。
<critical_requirements>
关键:使用此技能前
所有代码都必须遵循项目在 CLAUDE.md 中的约定(kebab-case、命名导出、导入顺序、
import type、命名常量)
(必须为每个工具循环设置最大步数限制——无界循环可能导致 API 调用无限进行和成本失控)
(必须在执行前验证所有工具输入参数——LLM 生成的参数是不可信输入)
(工具执行失败时,必须向模型返回结构化错误消息——绝不能悄无声息地吞掉错误或返回空结果)
(必须使用 JSON Schema 定义工具参数——所有主流提供商都要求使用此格式)
(必须将工具定义视为令牌成本——每次 API 调用都会发送每个工具架构,因此描述应简洁而准确)
</critical_requirements>
自动检测: 工具使用、函数调用、tool_calls、tool_use、工具调用循环、Agent 循环、工具定义、工具架构、toolChoice、tool_choice、并行工具调用、人在循环中、工具审批、Agentic 工作流、多步骤 Agent、工具结果、工具错误
适用场景:
- 在任何提供商中实现 LLM 工具调用/函数调用
- 构建迭代调用工具、直至任务完成的 Agent 循环
- 处理并行工具调用(同一响应中的多个工具)
- 将工具错误报告给模型,以便其恢复
- 控制工具选择(auto、required、none、强制指定工具)
- 在执行危险工具前添加人工审批关卡
- 流式处理包含工具调用的响应
涵盖的关键模式:
- 工具定义架构(参数使用 JSON Schema、描述)
- 核心工具调用循环(发送 -> 检测 -> 执行 -> 返回 -> 重新发送)
- 并行工具调用(处理同一响应中的多个调用)
- 错误处理(将工具执行失败报告给模型)
- 工具选择控制(auto、required、none、指定工具)
- 包含对话状态的多步骤 Agent 工作流
- 人在循环中的审批模式
- TypeScript 中类型安全的工具定义
- 安全性(输入验证、沙箱、最小权限)
- 包含工具调用的流式处理
不适用场景:
- 不涉及工具调用的简单文本生成——无需工具
- 结构化输出/JSON 提取——改用提供商的结构化输出功能
- 提供商特定的 SDK 模式——使用提供商的 SDK 技能获取特定于 SDK 的 API
详细资源:
- examples/core.md -- 工具定义、工具调用循环、错误处理、类型安全的工具
- examples/advanced.md -- 并行工具调用、多步骤 Agent、人在循环中、流式处理、安全性
- reference.md -- 决策框架、提供商比较、反模式检查清单
## 理念
工具使用是将大模型从文本生成器转变为 Agent 的机制。模型无法执行代码、查询数据库或调用 API——它只能通过发出结构化的工具调用来_请求_你的代码执行这些操作。你的代码负责执行,模型负责规划。
核心原则:
- 模型负责规划,你负责执行——大模型会发出带有结构化参数的工具调用请求。你的代码负责验证、执行并返回结果。切勿让模型直接执行任意代码。
- Agent 是循环——每个 Agent,无论是简单的天气机器人,还是复杂的编码助手,都遵循相同的循环:大模型做出决策 -> 系统执行 -> 结果反馈 -> 重复。复杂性来自工具和状态,而非循环本身。
- 工具是 Schema——工具定义是一个 JSON Schema,用于告知模型有哪些函数、函数接受哪些参数,以及何时使用。更好的描述能带来更好的工具选择和参数质量。
- 错误是信息——工具失败时,向模型返回结构化错误消息。模型通常可以通过使用不同参数重试、选择其他工具或向用户说明失败原因来恢复。
- 纵深防御——大模型生成的参数是不可信输入。验证 Schema、强制执行类型、限制参数范围、对执行进行沙箱隔离,并要求对危险操作进行审批。
何时使用工具调用:
- 任务需要模型不具备的现实世界数据(天气、数据库、API)
- 任务需要产生副作用(发送电子邮件、创建记录、文件操作)
- 任务需要结合中间数据查询进行多步推理
- 任务需要模型无法可靠完成的计算(数学、代码执行)
何时不应使用工具调用:
- 模型仅凭训练数据就能回答
- 你只需要结构化 JSON 输出(改用结构化输出功能)
- “工具”只是伪装成函数的提示词工程
- 你需要确定性行为(工具调用会因模型的决策引入不确定性)
核心模式
模式 1:工具定义 Schema
每个工具定义都包含三个部分:名称、描述和参数 Schema。描述最为重要——它会引导模型决定是否调用工具,以及如何构造参数。
// Provider-agnostic tool definition shape
interface ToolDefinition {
name: string; // ^[a-zA-Z0-9_-]{1,64}$
description: string; // When and why to use this tool
parameters: JsonSchema; // JSON Schema for input arguments
}
好的工具定义
// Good: precise description, constrained parameters, .describe() on each property
const VALID_UNITS = ["celsius", "fahrenheit"] as const;
const getWeatherTool: ToolDefinition = {
name: "get_current_weather",
description:
"Get the current weather conditions for a specific city. " +
"Returns temperature, humidity, and conditions. " +
"Use this when the user asks about current weather, NOT forecasts.",
parameters: {
type: "object",
properties: {
location: {
type: "string",
description: "City name and optional country code, e.g. 'London, UK'",
},
unit: {
type: "string",
enum: VALID_UNITS,
description: "Temperature unit. Defaults to celsius if not specified.",
},
},
required: ["location"],
},
};
为什么好: 描述说明了工具会返回什么、何时使用,以及何时不应使用。参数带有描述和约束(枚举)。有效值使用了命名常量。
不好的工具定义
// Bad: vague description, no parameter descriptions, no constraints
const weatherTool: ToolDefinition = {
name: "weather",
description: "Gets weather", // BAD: too vague
parameters: {
type: "object",
properties: {
loc: { type: "string" }, // BAD: no description, cryptic name
u: { type: "string" }, // BAD: no enum constraint
},
},
};
为什么不好: 描述含糊会导致工具选择错误;缺少参数描述会导致参数格式错误;缺少枚举约束会导致值无效;晦涩的参数名称会让模型困惑。
模式 2:核心工具调用循环
这是工具使用的基本模式。发送包含工具定义的消息,检查响应是否包含工具调用,执行这些调用,返回结果,然后让模型生成最终响应。完整实现请参阅 examples/core.md。
const MAX_TOOL_STEPS = 10;
for (let step = 0; step < MAX_TOOL_STEPS; step++) {
const response = await callLLM({ messages, tools });
if (!response.toolCalls?.length) return response.text;
messages.push({ role: "assistant", toolCalls: response.toolCalls });
for (const tc of response.toolCalls) {
messages.push({
role: "tool",
toolCallId: tc.id,
content: JSON.stringify(await executeTool(tc)),
});
}
}
要点: 始终使用带有命名常量(MAX_TOOL_STEPS)的有界 for 循环——切勿使用 while (true)。始终先追加助手消息(包含工具调用),再追加工具结果。达到步数上限时,确保能够正常终止。
工具执行失败时,应向模型返回结构化错误,而不是直接崩溃。模型通常可以通过重试、选择其他方法或说明失败原因来恢复。完整的验证流程请参阅 examples/core.md。
// Four-step validation: tool exists -> JSON parses -> schema validates -> execution succeeds
async function executeTool(toolCall: ToolCall): Promise<ToolResult> {
const handler = toolRegistry[toolCall.name];
if (!handler)
return { success: false, error: `Unknown tool: "${toolCall.name}"` };
const validated = handler.schema.safeParse(toolCall.arguments);
if (!validated.success)
return { success: false, error: `Invalid args: ${validated.error}` };
try {
return { success: true, data: await handler.execute(validated.data) };
} catch (error) {
return {
success: false,
error: `Tool failed: ${error instanceof Error ? error.message : "Unknown"}`,
};
}
}
要点: 始终验证工具是否存在、解析 JSON 参数、根据架构进行验证,并捕获执行错误。返回结构化的 { success, data?, error? } 结果,以便模型恢复。工具出错时,绝不要让循环崩溃。
模式 4:工具选择控制
控制模型是否以及如何使用工具。所有主流提供商都支持四种模式。
type ToolChoice =
| "auto" // Model decides whether to call tools (default)
| "required" // Model MUST call at least one tool
| "none" // Model MUST NOT call any tools
| { name: string }; // Model MUST call this specific tool
各模式的适用场景
// auto (default) -- let the model decide
const response = await callLLM({
messages,
tools,
toolChoice: "auto",
});
// required -- force tool use (e.g., first step of an agent must act)
const response = await callLLM({
messages,
tools,
toolChoice: "required",
});
// none -- disable tools (e.g., final response must be text only)
const response = await callLLM({
messages,
tools,
toolChoice: "none",
});
// specific tool -- force a particular tool (e.g., classification step)
const response = await callLLM({
messages,
tools,
toolChoice: { name: "classify_intent" },
});
选择建议:
| 模式 | 适用场景 |
|---|---|
auto | 通用 Agent——模型根据上下文自行决定 |
required | Agent 的第一步必须始终调用工具(例如,查询数据) |
none | 生成最终回复——不允许再调用工具 |
{ name } | 必须使用指定工具的工作流步骤(例如,提取、分类) |
适用场景: 需要在工作流的特定环节限制或确保工具行为时
模式 5:TypeScript 中类型安全的工具定义
使用带有 Zod 架构的类型化注册表模式,为工具定义提供编译时安全性,并在执行时进行运行时验证。完整的 ToolRegistry 类实现请参阅 examples/core.md。
import { z } from "zod";
// Define tool with schema + execute function -- TypeScript infers argument types
function defineTool<T extends z.ZodType>(config: {
name: string;
description: string;
schema: T;
execute: (args: z.infer<T>) => Promise<unknown>;
}) {
return config;
}
适用场景: 任何实现工具调用的 TypeScript 项目——Zod 会在运行时进行验证,TypeScript 则会在编译时发现架构与处理程序不匹配的问题
<decision_framework>
决策框架
你需要工具调用吗?
Does the task require information the model doesn't have?
+-- YES -> Tool calling (fetch data from APIs, databases, files)
+-- NO -> Does the task require side effects?
+-- YES -> Tool calling (send email, create record, execute code)
+-- NO -> Do you need structured JSON output?
+-- YES -> Use structured output features (NOT tool calling)
+-- NO -> Plain text generation, no tools needed
应选择哪种循环模式?
How many tools might the model call?
+-- Single tool call per request
| +-- Simple request-response with one tool execution
| +-- No loop needed, just one round-trip
+-- Multiple sequential tool calls
| +-- Use the bounded tool call loop (Pattern 2)
| +-- Set MAX_TOOL_STEPS based on task complexity
+-- Multiple parallel tool calls in one response
| +-- Execute all tool calls concurrently (Promise.all)
| +-- Then return all results and loop
+-- Complex multi-step agent
+-- Use the bounded loop with conversation state
+-- Add human-in-the-loop for dangerous operations
+-- Consider per-step tool filtering
如何处理工具错误?
Tool execution failed. What to do?
+-- Return structured error to the model
| +-- Include error message and context
| +-- Model can retry, choose alternative, or explain failure
+-- NEVER: silently return empty result
+-- NEVER: crash the loop
+-- NEVER: retry automatically without telling the model
(the model should decide whether to retry)
</decision_framework>
<red_flags>
危险信号
高优先级问题:
- 使用没有步骤计数器的无界工具循环(
while (true))——有无限调用 API 和成本失控的风险 - 未验证就执行工具参数——大模型生成的参数是不可信输入,应像处理用户输入一样处理
- 默默吞掉工具错误(返回
null或{})——模型不知道发生了故障,因此无法恢复 - 未进行沙箱隔离就允许通过工具参数执行任意代码——提示词注入可能升级为代码执行
- 工具描述写着“获取数据”——模糊的描述会导致工具选择错误、参数格式不正确
中优先级问题:
- 每次 API 调用都发送所有工具,而不是只发送相关工具——浪费令牌,还会使模型困惑
- 在工具结果之前,未将助手消息(包含工具调用)加入对话历史——会破坏消息顺序
- 使用
tool_choice: "required",却未提供没有合适工具时的回退方式——会强制进行无意义的工具调用 - 将原始数据库行或完整 API 响应作为工具结果返回——无关数据会淹没上下文;应进行摘要或截断
- 不记录工具调用和结果——无法调试生产环境中的 Agent 行为
常见错误:
- 忘记工具调用参数是 JSON 字符串,而不是已解析的对象——使用前务必调用
JSON.parse() - 认为只要有可用工具,模型就一定会调用——在
auto模式下,模型可能会直接以文本作答 - 将工具调用视为结构化输出——两者解决的是不同问题(执行操作与提取数据)
- 将业务逻辑写进工具描述,而不是工具实现中——描述用于引导工具选择,而非执行
注意事项与边缘情况:
- 每次 API 调用都要为工具定义消耗令牌——10 个包含详细架构的工具,每次请求可能消耗 1000 多个令牌
- 并行工具调用可能以任意顺序到达——切勿假定执行顺序与定义顺序一致
- 有些模型会臆造与任何定义都不匹配的工具名称或参数——务必验证工具名称是否存在于注册表中
- 包含工具调用的流式响应需要先累积部分 JSON 数据块,再进行解析——参数是逐步到达的,并非一次性完整到达
- 返回过大的工具结果(>4000 个令牌)可能导致对话超出上下文限制——应截断或摘要大型结果
- 模型会保留完整对话上下文,包括所有工具调用和结果——长时间运行的 Agent 会累积大量令牌用量
- 不同提供方的工具结果消息格式不同(
role: "tool"与内容块)——应在callLLM封装器中统一处理
</red_flags>
<critical_reminders>
关键提醒
所有代码都必须遵循 CLAUDE.md 中的项目约定(kebab-case、命名导出、导入顺序、
import type、命名常量)
(必须为每个工具循环设置最大步骤限制——无界循环有无限调用 API 和成本失控的风险)
(必须在执行前验证所有工具输入参数——大模型生成的参数是不可信输入)
(工具执行失败时,必须向模型返回结构化错误消息——绝不能默默吞掉错误或返回空结果)
(必须使用 JSON Schema 定义工具参数——所有主流提供方都要求采用此格式)
(必须将工具定义视为令牌成本——每次 API 调用都会发送每个工具架构,因此描述应简洁准确)
不遵循这些规则会导致 Agent 在无限循环中耗费大量 API 成本、执行未经验证的输入,或发生静默故障且模型无法恢复。
</critical_reminders>