Claude引路星,带你驾驭AI对话新境界

Tool Use 工具调用 入门教程

所属主题:Claude Tool Use 工具调用实战

Tool Use 工具调用 是一种让大型语言模型通过用户定义的函数与外部世界交互的能力——模型不再只能生成文本,还能执行代码、查询数据库、调用 API 或操作文件。本文将从零开始,带你走完完整的实践流程,并深入剖析那些容易忽略的细节。

如果你已经翻阅过 API 文档,却依然遭遇"模型就是不调用工具"或"工具返回了但模型不认"等问题,这篇文章将重点为你拆解这些边界情形,提供可复用的排查路径。


前置准备

开始 Tool Use 之前,请确保你已经准备好:

  • API 密钥:以 Anthropic Claude API 为例,你需要从 console.anthropic.com 获取有效的 API Key。本教程基于 Claude API 的 messages 接口,请求头中需包含 "anthropic-version": "2023-06-01"
  • 支持模型:并非所有模型都支持 Tool Use。截至本文撰写时,Claude 3 Sonnet、Claude 3 Opus、Claude 3.5 Sonnet 以及 Claude 3 Haiku 均支持此功能。若测试中发现模型对工具调用请求毫无反应,请首先核对 model 参数是否填写正确。
  • 运行环境:你可以使用 cURL、Python(requestsanthropic SDK)或 Node.js 来发送请求。本教程以 Python 3.10+ 配合 anthropic 库为例,推荐版本 >=0.25.0
# 最小化安装
pip install anthropic>=0.25.0

# 引入库
from anthropic import Anthropic
client = Anthropic(api_key="your-api-key")

常见陷阱:许多初学者忽略了版本检查。如果使用 0.20.0 之前的 SDK,tool_use 的响应格式会有所不同,后续解析代码可能失效。建议运行 pip show anthropic 确认版本号。


Tool Use 的核心机制与操作步骤

理解 Tool Use 的工作流程是避免后续踩坑的关键。整个过程并非"模型主动调用函数",而是分为四个阶段:

  1. 你定义工具(函数名、参数描述、输入格式)
  2. 你发送消息时附上工具定义
  3. 模型决定是否调用某个工具,并返回一个 tool_use 类型的 content block
  4. 你执行工具,并将结果以 tool_result 的形式发回给模型

步骤一:定义工具(Tool Definition)

工具定义使用 JSON Schema 来描述函数签名。这里有一处关键误区:参数描述不够具体,会导致模型不愿意调用或传错参数

假设我们要创建一个城市天气查询工具,理想的定义如下:

{
  "name": "get_weather",
  "description": "获取指定城市当前天气信息",
  "input_schema": {
    "type": "object",
    "properties": {
      "city": {
        "type": "string",
        "description": "城市名称,如'北京'、'上海'"
      },
      "unit": {
        "type": "string",
        "enum": ["celsius", "fahrenheit"],
        "description": "温度单位,默认摄氏度"
      }
    },
    "required": ["city"]
  }
}

注意这里的写法:input_schema 是固定字段名,不能写成 parameters(后者是 OpenAI 的格式,两套标准不同)。description 应包含示例值,以帮助模型理解数据格式。

工具定义字段速查表

字段 作用 注意点
name 工具名称 建议使用大写字母加下划线或驼峰命名
description 告诉模型何时使用此工具 写清楚触发条件和返回值含义
input_schema 参数定义(JSON Schema) 必须包含 typeproperties
required 必填参数列表 非必填字段不要放入此列表

步骤二:构造请求消息

在首次 API 请求中,你需要将工具定义放入 tools 数组中。下面是一个完整的工作示例,使用 Python SDK:

response = client.messages.create(
    model="claude-3-5-sonnet-20241022",
    max_tokens=1024,
    tools=[{
        "name": "get_weather",
        "description": "获取指定城市当前天气信息",
        "input_schema": {
            "type": "object",
            "properties": {
                "city": {
                    "type": "string",
                    "description": "城市名称,如'北京'、'上海'"
                },
                "unit": {
                    "type": "string",
                    "enum": ["celsius", "fahrenheit"]
                }
            },
            "required": ["city"]
        }
    }],
    messages=[{
        "role": "user",
        "content": "北京今天天气怎么样?"
    }]
)

步骤三:解析模型响应并执行工具

模型的响应会包含 stop_reason: "tool_use" 以及一个或多个 content block。你需要遍历 content,找到 typetool_use 的 block。

一个真实的 tool_use block 结构如下:

{
  "type": "tool_use",
  "id": "toolu_01AbCdEfGhIjKlMnOpQrStUv",
  "name": "get_weather",
  "input": {
    "city": "北京",
    "unit": "celsius"
  }
}

id 是此次调用请求的唯一标识,后续返回 tool_result 时必须携带同一个 id。

常见错误:新手最容易直接将整个 API 响应传给工具函数,而不提取 input。正确做法如下:

import json

for block in response.content:
    if block.type == "tool_use":
        # 提取参数
        city = block.input["city"]
        unit = block.input.get("unit", "celsius")
        # 调用你自己的函数
        weather_data = get_real_weather(city, unit)  # 这是你的业务函数
        # 构造 tool_result
        tool_result_block = {
            "type": "tool_result",
            "tool_use_id": block.id,
            "content": json.dumps(weather_data)
        }

步骤四:将工具结果返回模型

你需要将原始的 messages 数组、模型的 response 以及 tool_result 拼接成新的请求。注意顺序:

# 这是第二轮对话,将之前所有内容加上新结果一起发送
messages = [
    {"role": "user", "content": "北京今天天气怎么样?"},
    {"role": "assistant", "content": response.content},  # 包含 tool_use block
    {"role": "user", "content": [tool_result_block]}
]

second_response = client.messages.create(
    model="claude-3-5-sonnet-20241022",
    max_tokens=1024,
    tools=tools,  # 工具定义需要继续携带
    messages=messages
)

常见疑问:为什么需要将 assistant 的响应原封不动传回去?因为模型需要看到它之前发出的 tool_use 请求,才能将后续的 tool_result 关联起来。如果跳过这一步,模型会误认为这是一次全新的对话,继续要求调用工具而非利用结果。


典型场景与边界情况

场景一:模型一次请求调用多个工具

当用户请求涉及多个独立信息查询时,模型的响应中可能包含多个 tool_use block。例如,当用户要求"同时查询北京和上海的天气"时,你可能会收到两个 tool_use处理方法:并发执行所有工具调用,然后将所有 tool_result 一次性返回。注意,多个 tool_resulttool_use_id 必须与对应的 id 一一对应。

场景二:模型不调用工具

这是最令新手困惑的情况。如果模型在应该使用工具的任务中直接返回文本答案(例如它自己编造了一个天气数据),可能的原因包括:

  • 工具描述写得太窄:模型没有意识到这个任务应该调用工具。将 description 改为更明确的话术,例如"当用户询问天气时,必须使用此工具获取实时数据,不要编造答案"。
  • 模型不支持:检查 model 参数是否正确。Claude 3 Haiku 也支持,但旧版 Opus(如 claude-2)不支持。
  • max_tokens 太小:如果令牌数量限制不足,模型可能在生成完整的 tool_use 之前被截断。

场景三:工具执行失败

如果实际函数抛出异常(如网络超时或参数无效),不要在 tool_result 中传递异常对象。正确的做法是返回一个明确的错误信息字符串,让模型自主决定如何处理:

try:
    result = call_external_api(city)
except TimeoutError:
    result = "请求超时,请稍后重试或更换城市名称"
tool_result_block = {
    "type