📌 内容摘要

  • Tool Use(工具调用)是构建 Claude Agent 的核心机制,让 AI 能调用外部 API、查询数据库、执行代码。
  • 实现分四步:定义工具 → 发送请求 → 检测工具调用 → 执行并返回结果,形成完整 Agent Loop。
  • 本文含天气查询 Agent 完整可运行代码,以及并行工具调用、多轮对话的进阶实现。
  • 2026年新增 Programmatic Tool Calling,大幅降低多工具场景的延迟和 token 消耗。

一、Tool Use 是什么?为什么需要它?

Claude 本身是一个语言模型,它不能直接访问互联网、查询数据库或调用外部服务。Tool Use(也叫 Function Calling)解决了这个问题——你预先定义一组”工具”(本质是函数),告诉 Claude 每个工具能做什么,Claude 在需要时会告诉你”我要调用这个工具,参数是这些”,然后由你的代码实际执行,再把结果反馈给 Claude。

整个流程如下:

用户提问
  → 携带工具定义发送给 Claude
  → Claude 决定调用哪个工具,返回工具名+参数
  → 你的代码执行工具(调用 API/查数据库等)
  → 把执行结果发回给 Claude
  → Claude 根据结果生成最终回答

注意:Claude 本身不执行工具,只是告诉你该调用哪个工具。实际执行永远在你的代码里。

💡 2026年新特性:Programmatic Tool Calling
Anthropic 在2026年推出了 Programmatic Tool Calling,允许 Claude 在沙箱代码执行环境中直接编写调用工具的 Python 代码,而不是每次调用都需要一次完整的模型推理。对于需要调用 10 次以上工具的复杂 Agent,这可以显著降低延迟和 token 消耗。本文先讲基础 Tool Use,掌握后再进阶到 Programmatic Tool Calling。

二、工具定义的结构

每个工具由三个字段构成:name(工具名)、description(工具描述,Claude 靠这个判断何时调用)、input_schema(参数的 JSON Schema 定义)。

tools = [
    {
        "name": "get_weather",
        "description": "获取指定城市的当前天气信息,包括温度、湿度和天气状况。"
                       "当用户询问某个城市的天气时调用此工具。",
        "input_schema": {
            "type": "object",
            "properties": {
                "city": {
                    "type": "string",
                    "description": "城市名称,例如:北京、上海、广州"
                },
                "unit": {
                    "type": "string",
                    "enum": ["celsius", "fahrenheit"],
                    "description": "温度单位,默认使用 celsius(摄氏度)"
                }
            },
            "required": ["city"]
        }
    }
]
Description 写好是关键:Claude 完全依靠 description 来判断何时调用工具。描述越精准,工具被正确调用的概率越高。建议描述中明确说明:工具能做什么、在什么情况下应该调用、返回什么数据。

三、完整实现:天气查询 Agent

下面是一个完整可运行的天气查询 Agent,覆盖工具定义、工具执行、Agent Loop 的全流程:

import anthropic
import json

client = anthropic.Anthropic(api_key="你的API_KEY")

# ── 第一步:定义工具 ────────────────────────────────
tools = [
    {
        "name": "get_weather",
        "description": "获取指定城市的当前天气信息。当用户询问天气时调用此工具。",
        "input_schema": {
            "type": "object",
            "properties": {
                "city": {
                    "type": "string",
                    "description": "城市名称,例如:北京、上海"
                },
                "unit": {
                    "type": "string",
                    "enum": ["celsius", "fahrenheit"],
                    "description": "温度单位,默认 celsius"
                }
            },
            "required": ["city"]
        }
    },
    {
        "name": "search_database",
        "description": "在用户数据库中搜索信息。当需要查询用户资料或订单时调用。",
        "input_schema": {
            "type": "object",
            "properties": {
                "query": {
                    "type": "string",
                    "description": "搜索关键词"
                },
                "table": {
                    "type": "string",
                    "enum": ["users", "orders", "products"],
                    "description": "要搜索的数据库表"
                }
            },
            "required": ["query", "table"]
        }
    }
]


# ── 第二步:实现工具执行函数 ─────────────────────────
def get_weather(city: str, unit: str = "celsius") -> dict:
    """模拟天气 API 调用(实际使用时替换为真实 API)"""
    mock_data = {
        "北京": {"temp": 18, "humidity": 45, "condition": "晴朗"},
        "上海": {"temp": 22, "humidity": 70, "condition": "多云"},
        "广州": {"temp": 28, "humidity": 80, "condition": "阵雨"},
    }
    data = mock_data.get(city, {"temp": 20, "humidity": 60, "condition": "未知"})
    temp = data["temp"]
    if unit == "fahrenheit":
        temp = round(temp * 9/5 + 32, 1)
    return {
        "city": city,
        "temperature": f"{temp}°{'C' if unit == 'celsius' else 'F'}",
        "humidity": f"{data['humidity']}%",
        "condition": data["condition"]
    }


def search_database(query: str, table: str) -> dict:
    """模拟数据库查询(实际使用时替换为真实数据库)"""
    mock_results = {
        "users": [{"id": 1, "name": "张三", "email": "zhang@example.com"}],
        "orders": [{"id": 1001, "user": "张三", "total": 299, "status": "已发货"}],
        "products": [{"id": 101, "name": "Claude API 教程书", "price": 99}],
    }
    return {
        "query": query,
        "table": table,
        "results": mock_results.get(table, []),
        "count": len(mock_results.get(table, []))
    }


def execute_tool(tool_name: str, tool_input: dict) -> str:
    """根据工具名称执行对应函数"""
    if tool_name == "get_weather":
        result = get_weather(**tool_input)
    elif tool_name == "search_database":
        result = search_database(**tool_input)
    else:
        result = {"error": f"未知工具:{tool_name}"}
    return json.dumps(result, ensure_ascii=False)


# ── 第三步:Agent Loop ───────────────────────────────
def run_agent(user_message: str, max_iterations: int = 10) -> str:
    """
    完整的 Agent Loop:
    循环调用 Claude,检测工具调用,执行工具,返回结果,直到 Claude 生成最终回答。
    """
    messages = [{"role": "user", "content": user_message}]
    print(f"\n用户:{user_message}")

    for iteration in range(max_iterations):
        # 调用 Claude API
        response = client.messages.create(
            model="claude-sonnet-4-6",
            max_tokens=1024,
            tools=tools,
            messages=messages
        )

        # 检查停止原因
        if response.stop_reason == "end_turn":
            # Claude 已生成最终回答,提取文本内容
            final_text = ""
            for block in response.content:
                if hasattr(block, "text"):
                    final_text += block.text
            print(f"Claude:{final_text}")
            return final_text

        elif response.stop_reason == "tool_use":
            # Claude 要调用工具,提取所有工具调用块
            tool_results = []
            assistant_content = response.content

            for block in response.content:
                if block.type == "tool_use":
                    tool_name = block.name
                    tool_input = block.input
                    tool_use_id = block.id

                    print(f"  → 调用工具:{tool_name},参数:{tool_input}")

                    # 执行工具
                    result = execute_tool(tool_name, tool_input)
                    print(f"  ← 工具返回:{result}")

                    tool_results.append({
                        "type": "tool_result",
                        "tool_use_id": tool_use_id,
                        "content": result
                    })

            # 把 Claude 的回复和工具结果都加入消息历史
            messages.append({"role": "assistant", "content": assistant_content})
            messages.append({"role": "user", "content": tool_results})

        else:
            # 其他停止原因(max_tokens 等)
            break

    return "Agent 达到最大迭代次数"


# ── 测试运行 ─────────────────────────────────────────
if __name__ == "__main__":
    run_agent("北京今天天气怎么样?")
    run_agent("帮我查一下订单表里的数据")

四、理解 Agent Loop 的核心逻辑

上面代码的核心是 stop_reason 的判断:

stop_reason 含义 你应该做什么
end_turn Claude 完成了回答 提取文本内容,返回给用户,结束循环
tool_use Claude 需要调用工具 执行工具,把结果加入消息,继续循环
max_tokens 输出达到 max_tokens 上限 增大 max_tokens 或截断处理
pause_turn 服务端工具执行达到迭代上限 把 response 加入消息,继续发送让 Claude 处理

五、并行工具调用

Claude 4 系列支持在一次响应中同时调用多个工具(并行调用),大幅提升效率。例如用户问”北京和上海的天气”,Claude 会一次性返回两个 tool_use 块,你需要同时执行两个工具:

elif response.stop_reason == "tool_use":
    tool_results = []

    # 收集所有工具调用(可能有多个)
    tool_calls = [b for b in response.content if b.type == "tool_use"]
    print(f"  → 并行调用 {len(tool_calls)} 个工具")

    # 可以用 concurrent.futures 并行执行,这里简化为顺序执行
    for block in tool_calls:
        result = execute_tool(block.name, block.input)
        tool_results.append({
            "type": "tool_result",
            "tool_use_id": block.id,
            "content": result
        })

    messages.append({"role": "assistant", "content": response.content})
    messages.append({"role": "user", "content": tool_results})
📌 并行调用注意事项
所有工具结果必须一次性全部返回给 Claude,不能一个一个分开返回。tool_results 数组里需要包含本次 Claude 请求里所有 tool_use 块对应的结果,缺少任何一个都会导致 API 报错。

六、强制工具调用与禁用工具调用

默认情况下 Claude 自行决定是否调用工具。你也可以通过 tool_choice 参数干预这个行为:

# 自动决定(默认)
tool_choice={"type": "auto"}

# 强制调用某个工具
tool_choice={"type": "tool", "name": "get_weather"}

# 必须调用至少一个工具(但 Claude 自选哪个)
tool_choice={"type": "any"}

# 禁止调用任何工具,只生成文本
tool_choice={"type": "none"}

七、用工具实现结构化输出

Tool Use 还有一个巧妙用法:强制 Claude 以固定格式输出数据,而不需要解析自由文本。定义一个”虚拟工具”,Claude 调用它时的参数就是你想要的结构化数据:

extract_tool = {
    "name": "extract_article_info",
    "description": "提取文章的结构化信息",
    "input_schema": {
        "type": "object",
        "properties": {
            "title": {"type": "string", "description": "文章标题"},
            "author": {"type": "string", "description": "作者姓名"},
            "publish_date": {"type": "string", "description": "发布日期 YYYY-MM-DD"},
            "summary": {"type": "string", "description": "100字以内摘要"},
            "tags": {
                "type": "array",
                "items": {"type": "string"},
                "description": "文章标签列表"
            }
        },
        "required": ["title", "summary", "tags"]
    }
}

# 配合 strict: true 确保输出严格匹配 schema
extract_tool["strict"] = True

response = client.messages.create(
    model="claude-sonnet-4-6",
    max_tokens=1024,
    tools=[extract_tool],
    tool_choice={"type": "tool", "name": "extract_article_info"},
    messages=[{"role": "user", "content": f"请提取以下文章的信息:{article_text}"}]
)

# 直接获取结构化数据,无需解析文本
for block in response.content:
    if block.type == "tool_use":
        article_data = block.input  # 已经是 Python dict
        print(article_data)

八、工具调用的最佳实践

  • 描述要详细:description 是 Claude 判断是否调用工具的唯一依据,写清楚”在什么情况下调用”、”能返回什么数据”
  • 工具数量控制在 20 个以内:工具定义消耗 token,工具太多会显著增加成本;需要大量工具时使用 2026年新推出的 Tool Search 功能
  • 错误要优雅返回:工具执行失败时,返回包含 is_error: true 的结果,让 Claude 知道出错了,而不是让程序崩溃
  • 设置最大迭代次数:防止 Agent 无限循环,建议 max_iterations 设为 10–20
  • 使用 strict: true:在工具定义中加入 strict 参数,确保 Claude 的工具参数严格匹配 schema,避免类型错误
# 工具执行错误时的返回格式
tool_results.append({
    "type": "tool_result",
    "tool_use_id": tool_use_id,
    "content": json.dumps({"error": "API 调用失败:连接超时"}),
    "is_error": True  # 告诉 Claude 工具执行出错
})

九、常见问题

Q:Claude 一直不调用工具,直接用自己的知识回答怎么办?
检查工具的 description 是否足够明确,说明了”在什么情况下应该调用”。可以在 system prompt 中补充指令,如”遇到需要实时数据的问题,必须使用工具获取,不要依赖自身知识”。也可以使用 tool_choice: {"type": "any"} 强制 Claude 至少调用一个工具。

Q:工具参数类型总是不对,怎么保证 schema 匹配?
在工具定义中加入 "strict": true,开启严格 schema 验证后,Claude 的工具调用参数会严格匹配你的 schema,消除类型不匹配或字段缺失的问题。

Q:如何处理工具调用超时?
execute_tool 函数中加入超时控制(如 Python 的 requests 库的 timeout 参数),超时后返回错误信息给 Claude,Claude 会根据错误决定是否重试或告知用户。

Q:Claude 4 之前的模型支持并行工具调用吗?
Claude 3.7 Sonnet 对并行工具调用支持较弱,即使未禁用也可能不会并行调用。官方建议升级到 Claude 4 系列(Sonnet 4.6 或 Opus 4.6),内置了改进的并行工具调用能力,无需额外配置。

总结

Tool Use 是构建 Claude Agent 的基础能力。掌握了工具定义、Agent Loop、并行调用这三个核心概念,你就能让 Claude 连接任何外部系统——天气 API、数据库、搜索引擎、代码执行环境,乃至更复杂的多步骤自动化工作流。

下一步建议:在本文代码基础上,把 get_weather 替换成你的真实 API(如 OpenWeatherMap),把 search_database 替换成你的数据库查询,就能快速构建一个实用的业务 Agent。