代理工具构建器 是做什么的?
工具是 AI 代理与外界交互的方式。一个设计良好的工具,是区分能正常工作的代理与产生幻觉、无声失败或不必要消耗 10 倍 token 的代理的关键。
本技能涵盖从 Schema 到错误处理的工具设计。JSON Schema 最佳实践、真正能帮助 LLM 的描述编写、验证,以及正成为 AI 工具通用语言的新兴 MCP 标准。
关键见解:工具描述比工具实现更重要。LLM 从不会看到你的代码——它只看到 Schema 和描述。
原则
- 描述质量 > 实现质量,对 LLM 准确性至关重要
- 工具数量控制在 20 个以内——过多会导致混淆
- 每个工具都需要明确的错误处理——无声失败会毒害代理
- 返回字符串,而非对象——LLM 处理的是文本
- 验证先行,再执行——拒绝、修正或上报,绝不能无声失败
- 与 LLM 一起测试工具,而不仅仅是单元测试
能力
- agent-tools
- function-calling
- tool-schema-design
- mcp-tools
- tool-validation
- tool-error-handling
范围
- multi-agent-coordination → multi-agent-orchestration
- agent-memory → agent-memory-systems
- api-design → api-designer
- llm-prompting → prompt-engineering
工具
标准
- JSON Schema - 何时使用:所有工具定义。注意:工具 Schema 的通用格式
- MCP(模型上下文协议)- 何时使用:构建可复用、跨平台的工具。注意:Anthropic 的开放标准,已被广泛采用
框架
- Anthropic SDK - 何时使用:基于 Claude 的代理。注意:Beta 版本的工具运行器可处理大多数复杂性
- OpenAI Functions - 何时使用:基于 OpenAI 的代理。注意:使用严格模式确保 Schema 合规
- Vercel AI SDK - 何时使用:多提供商工具处理。注意:抽象了不同提供商之间的差异
- LangChain Tools - 何时使用:基于 LangChain 的代理。注意:将 MCP 工具转换为 LangChain 格式
模式
工具 Schema 设计
为工具创建清晰、无歧义的 JSON Schema
何时使用:为代理定义任何新的工具
工具 Schema 最佳实践:
1. 详细的描述(最重要)
错误 - 过于模糊:
{
"name": "get_stock_price",
"description": "获取股票价格",
"input_schema": {
"type": "object",
"properties": {
"ticker": {"type": "string"}
}
}
}
正确 - 全面:
{
"name": "get_stock_price",
"description": "获取给定股票代码的当前价格。股票代码必须是纽约证券交易所或纳斯达克等美国主要证券交易所上市公司的有效代码。返回最新交易价格(美元)。当用户询问当前或近期股票价格时使用。不提供历史数据、公司信息或预测。",
"input_schema": {
"type": "object",
"properties": {
"ticker": {
"type": "string",
"description": "股票代码,例如 Apple Inc. 的 AAPL"
}
},
"required": ["ticker"]
}
}
2. 参数描述
每个参数都需要:
- 它是什么
- 预期的格式
- 示例值
- 边界情况/限制
{
"location": {
"type": "string",
"description": "城市和州/国家。美国的格式为 '城市, 州'(例如 'San Francisco, CA'),国际格式为 '城市, 国家'(例如 'Tokyo, Japan')。请勿使用邮编或坐标。"
},
"unit": {
"type": "string",
"enum": ["celsius", "fahrenheit"],
"description": "温度单位。如果未指定,默认使用用户所在地区。美国用户使用 'fahrenheit',其他用户使用 'celsius'。"
}
}
3. 尽可能使用枚举
枚举将 LLM 约束在有效值内:
"priority": {
"type": "string",
"enum": ["low", "medium", "high", "critical"],
"description": "任务优先级"
}
"action": {
"type": "string",
"enum": ["create", "read", "update", "delete"],
"description": "要执行的 CRUD 操作"
}
4. 必需与可选
明确哪些是必需的:
{
"type": "object",
"properties": {
"query": {...}, // 必需
"limit": {...}, // 可选,有默认值
"offset": {...} // 可选
},
"required": ["query"],
"additionalProperties": false // 严格模式
}
带输入示例的工具
使用示例指导 LLM 使用工具
何时使用:具有嵌套对象或格式敏感输入的复杂工具
工具使用示例(Anthropic Beta 功能):
示例向 Claude 展示了 Schema 无法表达的具体模式。
可将复杂操作的准确率从 72% 提升至 90%。
{ "name": "create_calendar_event", "description": "创建日历事件,并可选添加参与者和提醒", "input_schema": { "type": "object", "properties": { "title": {"type": "string", "description": "事件标题"}, "start_time": { "type": "string", "description": "ISO 8601 日期时间,例如 2024-03-15T14:00:00Z" }, "duration_minutes": {"type": "integer", "description": "事件持续时间"}, "attendees": { "type": "array", "items": {"type": "string"}, "description": "参与者邮箱地址" } }, "required": ["title", "start_time", "duration_minutes"] }, "input_examples": [ { "title": "Team Standup", "start_time": "2024-03-15T09:00:00Z", "duration_minutes": 30, "attendees": ["[email protected]", "[email protected]"] }, { "title": "Quick Chat", "start_time": "2024-03-15T14:00:00Z", "duration_minutes": 15 }, { "title": "Project Review", "start_time": "2024-03-15T16:00:00-05:00", "duration_minutes": 60, "attendees": ["[email protected]"] } ] }
示例设计原则:
- 使用真实数据,而非占位符
- 展示最小、部分和完整规范模式
- 保持简洁:每个工具 1-5 个示例
- 关注有歧义的情况
工具错误处理
返回有助于 LLM 恢复的错误
何时使用:任何可能失败的工具
错误处理最佳实践:
返回信息丰富的错误
错误:
{"error": "Failed"}
{"error": true}
正确:
{
"error": true,
"error_type": "not_found",
"message": "在天气数据库中未找到位置 'Atlantis'。请提供真实城市名称,例如 'San Francisco, CA'。",
"suggestions": ["San Francisco, CA", "Los Angeles, CA"]
}
Anthropic 工具结果带错误
{
"type": "tool_result",
"tool_use_id": "toolu_01A09q90qw90lq917835lq9",
"content": "Error: 在天气数据库中未找到位置 'Atlantis'。请提供真实城市名称,例如 'San Francisco, CA'。",
"is_error": true
}
需要处理的错误类别
1. 输入验证错误
- 缺少必需参数
- 格式无效
- 值超出范围
2. 外部服务错误
- API 不可用
- 请求速率受限
- 超时
3. 业务逻辑错误
- 资源未找到
- 权限被拒绝
- 冲突/重复
4. 内部错误
- 意外异常
- 数据损坏
实现模式
from dataclasses import dataclass
from typing import Union
@dataclass
class ToolResult:
success: bool
content: str
error_type: str = None
suggestions: list[str] = None
def to_response(self) -> dict:
if self.success:
return {"content": self.content}
return {
"content": f"Error ({self.error_type}): {self.content}",
"is_error": True
}
def get_weather(location: str) -> ToolResult:
# 验证输入
if not location or len(location) < 2:
return ToolResult(
success=False,
content="位置必须至少包含 2 个字符",
error_type="validation_error"
)
try:
data = weather_api.fetch(location)
return ToolResult(
success=True,
content=f"温度: {data.temp}°F, 天气: {data.conditions}"
)
except LocationNotFound:
return ToolResult(
success=False,
content=f"未找到位置 '{location}'",
error_type="not_found",
suggestions=weather_api.suggest_locations(location)
)
except RateLimitError:
return ToolResult(
success=False,
content="天气服务请求速率受限。请在 60 秒后重试。",
error_type="rate_limit"
)
except Exception as e:
return ToolResult(
success=False,
content=f"意外错误: {str(e)}",
error_type="internal_error"
)
MCP 工具模式
使用模型上下文协议构建工具
何时使用:创建可复用、跨平台的工具
MCP 工具实现:
MCP(模型上下文协议)是 Anthropic 的开放标准,用于连接 AI 代理与外部系统。构建一次,到处使用。
基础 MCP 服务器(TypeScript)
import { Server } from "@modelcontextprotocol/sdk/server";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio";
const server = new Server({
name: "weather-server",
version: "1.0.0"
});
// 定义工具
server.setRequestHandler("tools/list", async () => ({
tools: [
{
name: "get_weather",
description: "获取某个位置的当前天气。返回温度、天气状况和湿度。用于查询特定城市的天气。",
inputSchema: {
type: "object",
properties: {
location: {
type: "string",
description: "城市和州,例如 'San Francisco, CA'"
},
unit: {
type: "string",
enum: ["celsius", "fahrenheit"],
default: "fahrenheit"
}
},
required: ["location"]
}
}
]
}));
// 处理工具调用
server.setRequestHandler("tools/call", async (request) => {
const { name, arguments: args } = request.params;
if (name === "get_weather") {
try {
const weather = await fetchWeather(args.location, args.unit);
return {
content: [
{
type: "text",
text: JSON.stringify(weather)
}
]
};
} catch (error) {
return {
content: [
{
type: "text",
text: `错误: ${error.message}`
}
],
isError: true
};
}
}
throw new Error(`未知工具: ${name}`);
});
// 启动服务器
const transport = new StdioServerTransport();
await server.connect(transport);
MCP 优势
- 跨 LLM 提供商的通用兼容性
- 可复用的工具库
- 支持流式和 SSE 传输
- 内置可观测性
- 工具访问控制
工具运行器模式
使用 SDK 工具运行器实现自动处理
何时使用:构建无需手动管理的工具循环
工具运行器(Anthropic SDK Beta):
工具运行器自动处理工具调用循环:
- 在 Claude 调用工具时执行它们
- 管理对话状态
- 处理错误重试
- 提供流式支持
Python 示例
import anthropic
from anthropic import beta_tool
client = anthropic.Anthropic()
@beta_tool
def get_weather(location: str, unit: str = "fahrenheit") -> str:
'''获取指定位置的当前天气。
参数:
location: 城市和州,例如 San Francisco, CA
unit: 温度单位,'celsius' 或 'fahrenheit'
'''
# 实现
return json.dumps({"temperature": "72°F", "conditions": "Sunny"})
@beta_tool
def search_web(query: str) -> str:
'''搜索网络信息。
参数:
query: 搜索查询
'''
# 实现
return json.dumps({"results": [...]})
# 工具运行器处理循环
runner = client.beta.messages.tool_runner(
model="claude-sonnet-4-5",
max_tokens=1024,
tools=[get_weather, search_web],
messages=[
{"role": "user", "content": "Paris 的天气如何?"}
]
)
# 处理每条消息
for message in runner:
print(message.content[0].text)
# 或只获取最终结果
final = runner.until_done()
TypeScript 与 Zod
import { Anthropic } from '@anthropic-ai/sdk';
import { betaZodTool } from '@anthropic-ai/sdk/helpers/beta/zod';
import { z } from 'zod';
const anthropic = new Anthropic();
const getWeatherTool = betaZodTool({
name: 'get_weather',
description: '获取指定位置的当前天气',
inputSchema: z.object({
location: z.string().describe('城市和州,例如 San Francisco, CA'),
unit: z.enum(['celsius', 'fahrenheit']).default('fahrenheit')
}),
run: async (input) => {
// 类型安全的输入!
return JSON.stringify({temperature: '72°F'});
}
});
const runner = anthropic.beta.messages.toolRunner({
model: 'claude-sonnet-4-5',
max_tokens: 1024,
tools: [getWeatherTool],
messages: [{ role: 'user', content: "Paris 的天气如何?" }]
});
for await (const message of runner) {
console.log(message.content[0].text);
}
并行工具执行
同时运行多个工具
何时使用:可以并行运行的独立工具调用
并行工具执行:
默认情况下,Claude 可以在一次响应中调用多个工具。
这对于独立操作大幅减少了延迟。
处理并行结果
# Claude 返回多个 tool_use 块:
response.content = [
{"type": "text", "text": "我将检查两个位置..."},
{"type": "tool_use", "id": "toolu_01", "name": "get_weather",
"input": {"location": "San Francisco, CA"}},
{"type": "tool_use", "id": "toolu_02", "name": "get_weather",
"input": {"location": "New York, NY"}},
{"type": "tool_use", "id": "toolu_03", "name": "get_time",
"input": {"timezone": "America/Los_Angeles"}},
{"type": "tool