AI Agents Claude

Claude工具调用:我如何赋予AI智能体真实能力

Alejandro Rioja
Alejandro Rioja
4 分钟阅读
TL;DR

Claude工具调用让您的智能体能够执行操作——而不仅仅是生成文本。您将工具定义为JSON模式,Claude决定何时调用它们,您的代码执行真实世界的操作。循环分三步:发送消息→接收tool_use块→执行并返回结果。我在Cloudflare Workers上超过15个生产智能体中部署了这一模式。故障点几乎从不在AI——而在于工具返回的模糊结果。

免费新闻通讯

每周三。28,400+ 读者。纯干货。

目录

2026年7月更新。

TL;DR: Claude工具调用让您的智能体能够执行操作——而不仅仅是生成文本。您将工具定义为JSON模式,Claude决定何时调用它们,您的代码执行真实世界的操作。循环分三步:发送消息→接收tool_use块→执行并返回结果。我在Cloudflare Workers上超过15个生产智能体中部署了这一模式。故障点几乎从不在AI——而在于工具返回的模糊结果。

[运营者视角] 我在一个咨询品牌和Pickleland(得克萨斯州普拉格维尔的匹克球中心)之间运营着30多个生产AI智能体。其中大约一半使用工具调用——这是Claude API的功能,让模型能够调用您代码中定义的函数。以下是我在生产部署和迭代后总结的模式。

为什么工具调用改变了智能体的能力

没有工具,智能体只能生成文本。这对摘要、起草和分类很有用——但这不是大多数业务自动化真正需要的。业务自动化需要查找信息、写入数据库、调用API、发送消息。

工具调用就是您给Claude这种访问权限的方式。您将一组工具定义为JSON模式。Claude读取模式,决定调用哪个工具以及使用什么参数,并返回一个结构化的tool_use内容块。您的代码执行实际函数。Claude获得结果并决定下一步做什么——包括调用另一个工具或生成最终文本响应。

关键点:Claude决定何时以及是否调用工具。 您定义能力。模型推理何时使用它们。

API流程如何工作

工具调用循环有三个步骤。根据模型发出的工具调用次数,您将经历这个循环一次或多次。

第一步:发送带有已定义工具的消息

typescript
const response = await anthropic.messages.create({
  model: "claude-haiku-4-5-20251001",
  max_tokens: 1024,
  tools: [
    {
      name: "check_court_availability",
      description:
        "Check if a court is available at a given date, time, and duration",
      input_schema: {
        type: "object",
        properties: {
          date: {
            type: "string",
            description: "Date in YYYY-MM-DD format",
          },
          time: {
            type: "string",
            description: "Start time in HH:MM format (24h)",
          },
          duration_minutes: {
            type: "number",
            description: "Duration of the booking in minutes",
          },
        },
        required: ["date", "time", "duration_minutes"],
      },
    },
  ],
  messages: [
    {
      role: "user",
      content: "Is a court available tomorrow at 2pm for 90 minutes?",
    },
  ],
});

第二步:检查Claude是否想调用工具

typescript
if (response.stop_reason === "tool_use") {
  const toolUseBlock = response.content.find(
    (block): block is Anthropic.ToolUseBlock => block.type === "tool_use"
  );

  if (!toolUseBlock) throw new Error("Expected tool_use block");

  // Run your actual function
  const toolResult = await checkCourtAvailability(
    toolUseBlock.input as CourtAvailabilityInput
  );

  // Step 3: Return the result to Claude
  const finalResponse = await anthropic.messages.create({
    model: "claude-haiku-4-5-20251001",
    max_tokens: 1024,
    tools: [
      /* same tools as before */
    ],
    messages: [
      {
        role: "user",
        content: "Is a court available tomorrow at 2pm for 90 minutes?",
      },
      { role: "assistant", content: response.content },
      {
        role: "user",
        content: [
          {
            type: "tool_result",
            tool_use_id: toolUseBlock.id,
            content: JSON.stringify(toolResult),
          },
        ],
      },
    ],
  });

  // finalResponse.content now has the text answer
}

这就是完整的模式。每次工具调用三次API交互:定义工具→接收tool_use块→返回结果。

真实示例:Pickleland可用性检查器

Pickleland是一个匹克球中心。我们在Facebook Messenger、评论区和聊天机器人上收到预订查询。问题几乎总是某种变体:“周六下午3点你们开门吗?“或”我能为8人小组预订场地吗?”

可用性检查智能体使用工具调用实时查询真实的预订系统,而不是给出罐头式回答。

以下是完整的智能体——简化但忠实于生产环境:

typescript
// workers/availability-checker.ts
import Anthropic from "@anthropic-ai/sdk";

const anthropic = new Anthropic();

const AVAILABILITY_TOOLS: Anthropic.Tool[] = [
  {
    name: "check_availability",
    description:
      "Check court availability for a date, time, and group size. Returns available courts and their prices.",
    input_schema: {
      type: "object",
      properties: {
        date: { type: "string", description: "YYYY-MM-DD" },
        start_time: { type: "string", description: "HH:MM (24h)" },
        duration_minutes: { type: "number" },
        players: { type: "number", description: "Number of players" },
      },
      required: ["date", "start_time", "duration_minutes"],
    },
  },
  {
    name: "get_pricing",
    description:
      "Get current pricing for court rentals and open play sessions",
    input_schema: {
      type: "object",
      properties: {
        session_type: {
          type: "string",
          enum: ["court_rental", "open_play", "clinics"],
        },
      },
      required: ["session_type"],
    },
  },
];

export async function handleInquiry(
  userMessage: string,
  env: Env
): Promise<string> {
  const messages: Anthropic.MessageParam[] = [
    { role: "user", content: userMessage },
  ];

  // Agentic loop — keep going until stop_reason is "end_turn"
  while (true) {
    const response = await anthropic.messages.create({
      model: "claude-haiku-4-5-20251001",
      max_tokens: 512,
      system:
        "You are the booking assistant for Pickleland, a pickleball facility in Pflugerville, TX. " +
        "Use the tools to look up real availability and pricing. Never make up availability or prices. " +
        "If the customer wants to book, direct them to pickleland.com/book.",
      tools: AVAILABILITY_TOOLS,
      messages,
    });

    // Push the assistant's response into message history
    messages.push({ role: "assistant", content: response.content });

    if (response.stop_reason === "end_turn") {
      const textBlock = response.content.find(
        (b): b is Anthropic.TextBlock => b.type === "text"
      );
      return (
        textBlock?.text ??
        "I wasn't able to answer that — please call us directly."
      );
    }

    if (response.stop_reason === "tool_use") {
      // Process ALL tool calls in this response (Claude can request multiple at once)
      const toolResults: Anthropic.ToolResultBlockParam[] = [];

      for (const block of response.content) {
        if (block.type !== "tool_use") continue;

        let result: unknown;

        switch (block.name) {
          case "check_availability":
            result = await checkAvailability(
              block.input as AvailabilityInput,
              env
            );
            break;
          case "get_pricing":
            result = await getPricing(block.input as PricingInput, env);
            break;
          default:
            result = { error: `Unknown tool: ${block.name}` };
        }

        toolResults.push({
          type: "tool_result",
          tool_use_id: block.id,
          content: JSON.stringify(result),
        });
      }

      // Return all tool results in a single user message
      messages.push({ role: "user", content: toolResults });
    }
  }
}

这里有两点值得注意。

智能体循环。 我持续运行直到stop_reason === "end_turn"。Claude可能调用check_availability,决定还需要价格,调用get_pricing,然后产生最终答案——这是一条用户消息触发的三次API调用。循环无需任何特殊逻辑即可处理。

每轮多次工具调用。 Claude可以在单个响应中返回多个tool_use块。我处理所有这些块,并在单个user消息中返回所有结果。如果逐一处理并单独返回,您会破坏对话流程并浪费令牌。

真实示例:潜在客户研究智能体

我的咨询品牌使用研究智能体在与潜在客户交谈之前对其进行丰富分析。当有人填写联系表格时,智能体会研究其公司并提取我在通话前需要了解的信息。

该智能体的工具定义包括一个写入工具——这正是模式变得有趣的地方:

typescript
const RESEARCH_TOOLS: Anthropic.Tool[] = [
  {
    name: "search_company",
    description: "Search for information about a company",
    input_schema: {
      type: "object",
      properties: {
        company_name: { type: "string" },
        website: { type: "string", description: "Company website if known" },
      },
      required: ["company_name"],
    },
  },
  {
    name: "save_research",
    description:
      "Save the completed research summary to Airtable. Call this when all research is complete.",
    input_schema: {
      type: "object",
      properties: {
        company_summary: { type: "string" },
        estimated_size: {
          type: "string",
          enum: ["1-10", "11-50", "51-200", "200+"],
        },
        likely_use_case: { type: "string" },
        priority: { type: "string", enum: ["high", "medium", "low"] },
        notes: { type: "string" },
      },
      required: [
        "company_summary",
        "estimated_size",
        "likely_use_case",
        "priority",
      ],
    },
  },
];

save_research是我所说的写入工具——其目的不是获取信息,而是以结构化形式将Claude的输出提交到数据库。我使用这种模式,而不是尝试从文本响应中解析JSON。Claude知道研究何时完成,并使用正确类型的字段调用save_research。我从不编写解析器。

这是工具调用最简洁的应用:用您想要的确切模式定义一个”最终操作”工具,Claude将通过工具调用提供结构化输出。无需文本解析,无需正则表达式,无需对自由文本输出进行JSONSchema验证。

一个工具还是多个

开始使用工具调用时,本能反应是构建一个做所有事情的巨型工具。请抵制这种冲动。小型、专注的工具更好,原因有三:

  1. Claude对小工具的推理更好。 名为get_court_status的工具返回可用性,比名为manage_facility的工具(接受mode参数并在内部分支)更容易让模型处理。

  2. 小工具更容易测试。 每个工具都是TypeScript函数,您可以独立于LLM进行单元测试。您应该这样做——工具中的错误在实时对话中很难调试。

  3. Claude可以并行化小工具。 如果两个工具不相互依赖,Claude可以在同一个响应中调用它们,您可以并行处理。这仅在工具真正独立时才有效。

例外情况:需要访问大量共享内部状态的工具。如果函数需要来自同一数据源的10个变量,一个具有更丰富模式的工具优于每个单独访问数据库的10个工具。

我的经验法则:每个不同的能力从一个工具开始。只有当您看到Claude在每个请求中都将它们一起调用时,才合并工具。

成本影响

工具调用会增加令牌数量。每个工具定义都会进入系统提示上下文。每个tool_usetool_result块都会在对话历史中消耗令牌。对于多轮智能体循环,这会迅速累积。

对于Pickleland可用性检查器,一次典型对话总共执行3-4次API调用(初始消息+1-2次工具调用+最终答案),每次处理600-900个令牌。按Haiku价格,每次查询费用不到$0.001。正如我在AI智能体成本计算文章中解释的,Haiku可靠地处理定义明确的工具调用任务,对于相同的令牌量比Sonnet便宜10倍。

潜在客户研究智能体运行在Sonnet上,因为判断决策——优先处理潜在客户、评估契合度——需要比Haiku在开放性输入上可靠提供的更强推理能力。这个计算仍然有效,因为它运行不频繁(每周几次,而不是每天数千次)。模型选择遵循任务复杂性,而非个人偏好。

没人谈论的故障点

我在生产工具调用中看到的最常见故障点不是Claude调用错误的工具。而是工具返回Claude无法清晰推理的内容。

如果您的工具返回包含40个字段的原始数据库对象,Claude会对哪些字段重要感到困惑。如果您的工具抛出异常(表现为Worker崩溃而不是工具结果),循环会静默中断。如果您的工具在表示”无结果”时返回null,Claude不知道是重试还是放弃。

工具结果的三条规则:

返回简洁、明确的结果。 { available: true, courts: ["Court 3", "Court 5"], price_per_hour: 20 }——而不是完整的数据库行。

在工具函数内捕获错误并将其作为结构化结果返回。 { error: "booking system timeout", retry: true }——而不是导致Worker崩溃的抛出异常。

明确表示”无结果”。 { available: false, next_available: "2026-07-23T14:00:00Z" }——而不是没有上下文的null或空数组。

Claude对清晰信号的推理远好于对模糊返回值的推理。我在生产中调试工具调用所花的每一小时都是关于不清晰的结果,而不是模型的推理。

运营者结语

工具调用是将Claude从文本生成器转变为运营者的功能。定义具有清晰输入模式的专注工具。在单个响应中处理所有tool_use块。运行智能体循环直到stop_reason === "end_turn"。从工具函数返回干净、简洁的结果——不要返回原始数据对象、抛出的异常或模糊的null。

模型处理推理。您的代码处理真实世界的操作。保持这两项工作明确分离,即使您添加工具,架构也保持可维护性。

如果您正在构建第一个工具调用智能体,请从上面的可用性检查器模式开始——一个工具,一个目的,一个智能体循环。先部署那个。然后添加第二个工具。


相关文章: 我用来运行30个以上生产智能体的技术栈 · Haiku与Sonnet:智能体任务的成本计算 · 事件触发与定时智能体:哪种模式适合哪种工作

正在构建工具调用智能体并遇到困难? 联系我——我为运营团队设计和构建生产智能体架构。

常见问题

Claude工具调用适用于所有模型吗?

是的——工具调用支持所有当前的Claude模型。Claude Haiku可靠地处理具有清晰模式的定义明确的工具,是高容量任务类型的最便宜选项。Sonnet更好地处理更模糊或开放式的工具调用决策。从Haiku开始;如果输出质量不足,则升级。

Claude工具调用与OpenAI函数调用有何区别?

机制上完全相同。OpenAI创造了”function calling”;Anthropic称之为”tool use”。两者都是:您定义JSON模式,模型返回结构化调用,您的代码执行函数。API形式不同,但概念相同。

Claude能在单个响应中调用多个工具吗?

可以。Claude可以在单个assistant响应中返回多个tool_use块。处理所有这些块,并在单个user消息中返回所有结果。请参阅上面Pickleland示例中的智能体循环模式——response.content上的for循环可以正确处理这个问题。

每个智能体应该定义多少个工具?

我每个智能体保持在8-10个工具以下。超过这个数量,我看到Claude偶尔在第一次尝试时选择错误的工具,这会在纠正循环中浪费令牌。如果您需要超过10个能力,请将智能体拆分为具有专业工具集的多个智能体,而不是构建一个知道一切的智能体。

我应该使用工具调用获取结构化输出吗?

是的——save_research写入工具模式比要求Claude在文本块中返回JSON然后解析它更简洁。定义一个具有您想要的确切模式的”最终操作”工具。Claude完成后将使用正确类型的字段调用它。无需解析器。

继续阅读

相关文章

继续阅读

将AI实战手册发送到您的邮箱

每周三。28,400+ 读者。纯干货。

↵ 查看全部结果 esc esc 关闭