Tool Use API 架构
Computer Use 与 Tool Use 的关系
Anthropic 的 Tool Use(工具调用)是一项通用能力,允许 Claude 调用开发者定义的任意函数。Computer Use 工具(computer、text_editor、bash)实际上是 Anthropic 预定义的特殊工具。理解这一点,就能把两者有机结合:
内置工具(Computer Use)
由 Anthropic 预定义,通过
type: "computer_20241022" 等方式声明。处理截图、鼠标键盘操作、文件编辑、Shell 命令。Claude 知道如何使用这些工具,无需开发者描述。自定义工具(Custom Tools)
由开发者用 JSON Schema 定义的业务函数,例如"查询数据库"、"发送通知"、"获取天气"。Claude 根据工具描述自主决定何时调用、传入什么参数。
混合工具集
同一个 Agent 可以同时拥有 Computer Use 工具和自定义工具。Claude 会根据任务需要灵活选择:需要操作屏幕就用 computer,需要查数据就用自定义工具,避免"截图查数据"的低效操作。
工具调用流程
Claude 在回复中输出
tool_use 类型的内容块,包含工具名称和参数。开发者执行对应函数后,将结果以 tool_result 形式返回给 Claude,Claude 继续思考并可能再次调用工具。Tool Use 消息流架构
┌─────────────────────────────────────────────┐
│ 用户消息:"帮我查一下北京的天气,然后截图桌面" │
└──────────────────────┬──────────────────────┘
│
▼
┌─────────────────┐
│ Claude 推理 │
└────────┬────────┘
│ 决定调用工具
┌────────────┼────────────┐
▼ ▼ ▼
get_weather screenshot (等待结果)
(自定义工具) (内置工具)
│ │
▼ ▼
返回天气数据 返回截图 base64
│ │
└──────┬─────┘
▼
Claude 综合信息
生成最终回复
自定义工具定义
JSON Schema 工具定义格式
# 自定义工具的完整定义格式
from typing import Any
# 工具定义列表(传给 API 的 tools 参数)
CUSTOM_TOOLS = [
{
"name": "get_weather",
"description": """获取指定城市的实时天气信息。
返回温度、湿度、风速和天气状况描述。
当用户询问天气时使用此工具,比截图天气网站更高效。""",
"input_schema": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "城市名称,如 '北京'、'Shanghai'、'New York'"
},
"unit": {
"type": "string",
"enum": ["celsius", "fahrenheit"],
"description": "温度单位,默认 celsius"
}
},
"required": ["city"]
}
},
{
"name": "query_database",
"description": """执行数据库查询并返回结果。
只支持 SELECT 查询,不允许 INSERT/UPDATE/DELETE。
当需要查询业务数据时使用,避免通过截图 Web 界面获取数据。""",
"input_schema": {
"type": "object",
"properties": {
"sql": {
"type": "string",
"description": "要执行的 SQL SELECT 语句"
},
"limit": {
"type": "integer",
"description": "返回记录数上限,默认 100",
"default": 100
}
},
"required": ["sql"]
}
},
{
"name": "send_notification",
"description": """发送通知到指定渠道(Slack/邮件/钉钉)。
在任务完成或需要人工确认时调用此工具。""",
"input_schema": {
"type": "object",
"properties": {
"channel": {
"type": "string",
"enum": ["slack", "email", "dingtalk"]
},
"message": {
"type": "string",
"description": "通知内容"
},
"urgency": {
"type": "string",
"enum": ["low", "normal", "high"],
"default": "normal"
}
},
"required": ["channel", "message"]
}
}
]
工具执行循环
完整的工具执行引擎
import anthropic
import json
from typing import Callable, Optional
class ToolExecutor:
"""工具执行引擎:管理工具注册和执行"""
def __init__(self):
self._handlers: dict[str, Callable] = {}
self._tool_defs: list[dict] = []
def register(self, tool_def: dict, handler: Callable):
"""注册工具定义和对应的处理函数"""
self._tool_defs.append(tool_def)
self._handlers[tool_def["name"]] = handler
async def execute(self, tool_name: str, tool_input: dict) -> dict:
"""执行指定工具并返回结果"""
handler = self._handlers.get(tool_name)
if not handler:
return {"error": f"Unknown tool: {tool_name}"}
try:
result = await handler(**tool_input)
return {"success": True, "result": result}
except Exception as e:
return {"success": False, "error": str(e)}
@property
def tool_definitions(self) -> list[dict]:
return self._tool_defs
async def run_agent_with_tools(
client: anthropic.Anthropic,
executor: ToolExecutor,
user_message: str,
computer_use_tools: list[dict], # Computer Use 内置工具
max_iterations: int = 30
) -> str:
"""
混合工具 Agent 主循环:
同时支持 Computer Use 内置工具和自定义工具。
"""
# 合并内置工具和自定义工具
all_tools = computer_use_tools + executor.tool_definitions
messages = [{"role": "user", "content": user_message}]
for iteration in range(max_iterations):
response = client.beta.messages.create(
model="claude-opus-4-5",
max_tokens=4096,
tools=all_tools,
messages=messages,
betas=["computer-use-2024-10-22"]
)
# 收集工具调用
tool_use_blocks = [
block for block in response.content
if block.type == "tool_use"
]
# 没有工具调用,任务完成
if not tool_use_blocks or response.stop_reason == "end_turn":
# 提取最终文本回复
text_blocks = [b.text for b in response.content if b.type == "text"]
return "\n".join(text_blocks)
# 将 Claude 的回复加入 messages
messages.append({"role": "assistant", "content": response.content})
# 执行所有工具调用(支持并行)
tool_results = []
import asyncio
tasks = [
executor.execute(block.name, block.input)
for block in tool_use_blocks
]
results = await asyncio.gather(*tasks)
for block, result in zip(tool_use_blocks, results):
tool_results.append({
"type": "tool_result",
"tool_use_id": block.id,
"content": json.dumps(result, ensure_ascii=False)
})
# 将工具结果作为 user 消息返回
messages.append({"role": "user", "content": tool_results})
return "[超过最大迭代次数]"
并行工具调用
让 Claude 同时调用多个工具
当任务中有多个相互独立的操作时,Claude 会在单次回复中输出多个 tool_use 块。正确处理这种情况可以显著提升效率:
import asyncio
from dataclasses import dataclass
@dataclass
class ToolCallResult:
tool_use_id: str
tool_name: str
success: bool
result: Any
error: Optional[str] = None
duration_ms: float = 0.0
async def execute_tools_in_parallel(
tool_use_blocks: list,
executor: ToolExecutor,
computer_controller # Computer Use 控制器
) -> list[ToolCallResult]:
"""
并行执行所有工具调用。
Computer Use 工具(截图等)和自定义工具同步并行执行。
"""
async def execute_one(block) -> ToolCallResult:
start = asyncio.get_event_loop().time()
try:
# 判断是 Computer Use 内置工具还是自定义工具
if block.name in ("computer", "str_replace_editor", "bash"):
result = await computer_controller.execute_action(
block.input.get("action"),
**{k: v for k, v in block.input.items() if k != "action"}
)
else:
result = await executor.execute(block.name, block.input)
duration = (asyncio.get_event_loop().time() - start) * 1000
return ToolCallResult(
tool_use_id=block.id,
tool_name=block.name,
success=True,
result=result,
duration_ms=duration
)
except Exception as e:
duration = (asyncio.get_event_loop().time() - start) * 1000
return ToolCallResult(
tool_use_id=block.id,
tool_name=block.name,
success=False,
result=None,
error=str(e),
duration_ms=duration
)
tasks = [execute_one(block) for block in tool_use_blocks]
return await asyncio.gather(*tasks)
def build_tool_results_message(call_results: list[ToolCallResult]) -> dict:
"""将工具执行结果构建为 API 所需的 message 格式"""
content = []
for r in call_results:
if r.success:
# Computer Use 截图需要特殊格式
if (r.tool_name == "computer" and
isinstance(r.result, dict) and
r.result.get("type") == "image"):
content.append({
"type": "tool_result",
"tool_use_id": r.tool_use_id,
"content": [r.result] # 截图需要包装成列表
})
else:
content.append({
"type": "tool_result",
"tool_use_id": r.tool_use_id,
"content": json.dumps(r.result, ensure_ascii=False)
})
else:
content.append({
"type": "tool_result",
"tool_use_id": r.tool_use_id,
"is_error": True,
"content": f"Error: {r.error}"
})
return {"role": "user", "content": content}
工具结果错误处理
优雅地处理工具失败
from enum import Enum
class ToolErrorStrategy(Enum):
# 将错误返回给 Claude,让它决定如何处理
REPORT_TO_CLAUDE = "report"
# 重试最多 N 次
RETRY = "retry"
# 使用备用工具
FALLBACK = "fallback"
# 直接终止任务
ABORT = "abort"
class ResilientToolExecutor(ToolExecutor):
"""带弹性策略的工具执行器"""
def __init__(self):
super().__init__()
self._retry_config: dict[str, int] = {} # tool_name -> max_retries
self._fallbacks: dict[str, str] = {} # tool_name -> fallback_tool
def set_retry(self, tool_name: str, max_retries: int = 3):
"""为指定工具配置重试次数"""
self._retry_config[tool_name] = max_retries
def set_fallback(self, tool_name: str, fallback_tool: str):
"""为指定工具配置备用工具"""
self._fallbacks[tool_name] = fallback_tool
async def execute(self, tool_name: str, tool_input: dict) -> dict:
max_retries = self._retry_config.get(tool_name, 1)
for attempt in range(max_retries):
result = await super().execute(tool_name, tool_input)
if result.get("success"):
return result
# 最后一次重试失败,尝试备用工具
if attempt == max_retries - 1:
fallback = self._fallbacks.get(tool_name)
if fallback:
print(f"[工具失败] {tool_name} 切换到备用工具 {fallback}")
return await super().execute(fallback, tool_input)
# 等待后重试
await asyncio.sleep(0.5 * (attempt + 1))
return result # 返回最后一次失败的结果给 Claude
工具链:串联工具输出
将一个工具的输出作为另一个的输入
工具链(Tool Chain)是指通过提示词引导 Claude 在多轮对话中依次调用工具,每一步的输出成为下一步的输入:
"""
工具链示例:数据提取 → 处理 → 入库 → 通知
任务:从网页截图中提取订单信息,写入数据库,发送确认通知
工具链:
1. computer(screenshot) → 获取页面截图
2. extract_order_data(image) → 解析订单字段
3. insert_order(order_data) → 写入数据库
4. send_notification(order_id) → 通知相关人员
"""
async def handle_order_workflow(page_url: str):
# 定义工具
tools = [
# Computer Use 内置工具
{"type": "computer_20241022", "name": "computer", "display_width_px": 1280, "display_height_px": 800},
# 自定义工具
{
"name": "extract_order_data",
"description": "从 HTML 或文本内容中解析订单数据,返回结构化 JSON",
"input_schema": {
"type": "object",
"properties": {
"raw_content": {"type": "string", "description": "原始页面内容"}
},
"required": ["raw_content"]
}
},
{
"name": "insert_order",
"description": "将订单数据插入数据库,返回新订单的 ID",
"input_schema": {
"type": "object",
"properties": {
"order_data": {"type": "object", "description": "订单 JSON 数据"}
},
"required": ["order_data"]
}
},
{
"name": "send_notification",
"description": "发送订单确认通知",
"input_schema": {
"type": "object",
"properties": {
"order_id": {"type": "string"},
"channel": {"type": "string"}
},
"required": ["order_id", "channel"]
}
}
]
task = f"""
请按照以下步骤处理订单:
1. 打开浏览器导航到 {page_url},截图查看页面
2. 使用 extract_order_data 工具从页面内容中提取订单信息
3. 使用 insert_order 工具将订单写入数据库
4. 使用 send_notification 工具通过 slack 发送确认通知
5. 报告整个流程的执行结果
注意:每一步都要确认上一步成功后再进行下一步。
"""
# 运行 Agent
result = await run_agent_with_tools(client, executor, task, tools)
return result
Computer Use + 自定义工具混合使用
最优工具选择策略
合理的系统提示词可以引导 Claude 在正确的场景下选择正确的工具,避免过度依赖截图操作:
SYSTEM_PROMPT = """你是一个智能自动化助手,拥有以下工具:
【Computer Use 工具】用于屏幕操作:
- computer: 截图、鼠标点击、键盘输入(当需要操作 GUI 时使用)
【自定义工具】用于直接获取数据(优先使用这些工具):
- query_database: 当需要查询业务数据时直接调用,不要截图 Web 界面
- send_notification: 发送通知,不要手动打开 Slack/邮件客户端
- get_file_content: 读取服务器文件,不要截图文件内容
工具选择原则:
1. 如果有专用工具可以完成任务,优先使用专用工具
2. 只有当必须通过 GUI 操作(如遗留系统没有 API)时才使用 computer 工具
3. 需要查看操作结果时,再使用截图确认
4. 不要重复截图,每次截图都有 Token 成本"""
# 使用示例:查询订单后截图确认
task_example = """
查询最近 10 笔订单,然后打开内部系统的订单页面截图确认数据一致性。
期望行为:
- 先调用 query_database 获取数据库中的订单(高效)
- 再用 computer 工具打开浏览器,截图系统页面
- 对比两者数据是否一致
"""
工具版本管理
处理 API 工具版本变更
from enum import Enum
class ComputerUseVersion(Enum):
# 2024年10月发布的版本(当前稳定版)
V_20241022 = "computer_20241022"
def build_computer_use_tools(
version: ComputerUseVersion = ComputerUseVersion.V_20241022,
display_width: int = 1280,
display_height: int = 800
) -> list[dict]:
"""构建 Computer Use 工具列表,支持版本切换"""
computer_type = version.value
# text_editor 和 bash 的版本号与 computer 对应
version_suffix = computer_type.replace("computer_", "")
return [
{
"type": computer_type,
"name": "computer",
"display_width_px": display_width,
"display_height_px": display_height,
"display_number": 1
},
{
"type": f"text_editor_{version_suffix}",
"name": "str_replace_editor"
},
{
"type": f"bash_{version_suffix}",
"name": "bash"
}
]
# 自定义工具版本管理:通过工具名称后缀区分版本
def get_tool_registry_v2() -> list[dict]:
"""v2 版本工具集:新增 batch_query 支持"""
return [
{"name": "query_database_v2", "description": "支持批量查询的新版数据库工具", "input_schema": {
"type": "object",
"properties": {
"queries": {"type": "array", "items": {"type": "string"}},
"parallel": {"type": "boolean", "default": True}
},
"required": ["queries"]
}}
]
工具描述质量决定调用准确率
工具的 description 字段是 Claude 决定是否调用该工具的关键依据。好的描述应该:明确说明工具的用途和适用场景;指出何时应该用、何时不该用;说明输入输出格式;提供使用限制(如只读/只允许特定操作)。描述越清晰,Claude 的工具调用决策就越准确。
工具调用的内部机制
Claude 如何决定是否调用工具
了解 Claude 在工具调用背后的决策逻辑,有助于你设计更有效的工具和系统提示词:
工具选择的依据
Claude 在看到用户消息后,会根据工具的
description 评估每个工具与当前任务的相关性。如果某个工具的描述模糊或与当前需求不匹配,Claude 可能会忽略它,或者用截图替代(因为截图是"万能"工具)。工具参数的推断
Claude 根据
input_schema 中的字段描述来推断应该传什么参数。如果字段描述不够清晰(如只写 "city" 没有说明格式),Claude 可能会传入错误格式("北京市" vs "beijing"),导致工具调用失败。工具调用时机
默认情况下(tool_choice 未设置),Claude 会根据任务需要自主决定何时调用工具。如果想强制 Claude 必须调用某个工具,可以设置
tool_choice={"type": "tool", "name": "your_tool"}。多工具同时调用
Claude 可以在单次响应中输出多个
tool_use 块。这发生在多个工具调用相互独立时(如同时查询天气和查询数据库)。你的执行器必须支持并行执行这些调用。tool_choice 参数详解
import anthropic
client = anthropic.Anthropic()
# tool_choice 的三种模式
# 1. auto(默认):Claude 自主决定是否调用工具
response = client.beta.messages.create(
model="claude-opus-4-5",
max_tokens=1024,
tools=tools,
# tool_choice 不设置,等同于 tool_choice={"type": "auto"}
messages=[{"role": "user", "content": "北京天气怎么样?"}]
)
# 2. any:强制 Claude 必须调用至少一个工具(但可以选择哪个)
response = client.beta.messages.create(
model="claude-opus-4-5",
max_tokens=1024,
tools=tools,
tool_choice={"type": "any"}, # 必须调用工具
messages=[{"role": "user", "content": "我需要你帮我做一些事"}]
)
# 3. tool:强制调用指定的工具
response = client.beta.messages.create(
model="claude-opus-4-5",
max_tokens=1024,
tools=tools,
tool_choice={
"type": "tool",
"name": "get_weather" # 强制调用 get_weather,无论用户说什么
},
messages=[{"role": "user", "content": "查询北京天气"}]
)
# 4. Computer Use 场景的 tool_choice
# Computer Use 工具(computer/bash/text_editor)不能与 tool_choice="tool" 一起使用
# 它们必须依赖 Claude 的自主判断(auto 模式)
自定义工具的最佳设计模式
# 优秀的工具描述示例
GOOD_TOOL_EXAMPLE = {
"name": "search_products",
"description": """在产品数据库中搜索产品。
适用场景:
- 用户需要查找特定产品时
- 需要获取产品 ID 用于后续操作时
- 不适合用于修改产品信息(请用 update_product)
返回值:最多 10 条产品记录的列表,每条包含 id、名称、价格、库存。
示例调用:
- 搜索名称含"手机"的产品:{"keyword": "手机"}
- 按分类搜索:{"category": "electronics", "max_price": 5000}
- 搜索低库存产品:{"min_stock": 0, "max_stock": 10}""",
"input_schema": {
"type": "object",
"properties": {
"keyword": {
"type": "string",
"description": "产品名称关键词,支持模糊匹配(如 '手机' 会匹配 '苹果手机')"
},
"category": {
"type": "string",
"enum": ["electronics", "clothing", "food", "other"],
"description": "产品分类,精确匹配"
},
"max_price": {
"type": "number",
"description": "最高价格(元),仅返回价格不超过此值的产品"
},
"max_stock": {
"type": "integer",
"description": "最大库存量,用于查找库存不足的产品"
},
"limit": {
"type": "integer",
"description": "返回结果数量上限,默认 10,最大 50",
"default": 10
}
},
"required": [] # 所有参数可选,至少传入一个
}
}
# 差的工具描述(反例)
BAD_TOOL_EXAMPLE = {
"name": "search",
"description": "搜索", # 太模糊,Claude 不知道搜索什么
"input_schema": {
"type": "object",
"properties": {
"q": {"type": "string"} # 参数名和描述都太简单
}
}
}
章节小结
本章深入讲解了 Tool Use 的架构原理和自定义工具设计。核心要点:
- Computer Use 内置工具本质上是 Anthropic 预定义的特殊 Tool Use,可以与自定义工具混用
- 工具的
description是 Claude 调用决策的核心依据,应包含适用场景、不适用场景和示例 tool_choice可控制工具调用模式:auto(默认)/ any(强制调用)/ tool(强制指定工具)- 自定义工具优先于截图操作:查数据用 query_database,不要截图数据界面
- 并行工具调用需要
asyncio.gather并发执行,最后构建包含所有结果的tool_result消息 - 工具执行失败时,将错误信息(
is_error: true)返回给 Claude,让 Claude 自主决定如何处理