載入中

什麼是工具使用?

第 5 課8 分鐘
載入中

什麼是工具使用?

您現有的工作流程依賴許多不同的技術——專案管理軟體、資料庫、檔案。Claude 無法自行檢查這些東西。相反地,它依賴工具,這些工具讓 Claude 能夠存取外部資料和執行動作。

什麼是工具

簡單來說,工具是您定義並公開給 Claude 的函式。您描述它的功能以及它接受的輸入,而 Claude 決定何時呼叫它。

這裡有個關鍵要內化的概念:Claude 不會執行工具——您的程式碼才會執行。流程如下:

  1. Claude 請求呼叫工具。
  2. 您的程式碼執行該函式。
  3. 結果回傳給 Claude,然後它繼續進行。

工具如何定義

工具是JSON 結構(schema),包含三個部分:名稱、描述和輸入結構。您在請求主體中以 tools 陣列的形式將它們傳遞給 Claude。

描述是 Claude 用來決定是否呼叫該工具的依據。如果您寫的描述含糊不清,就會得到不佳的工具使用結果。這是代理程式(agent)誤判或無法抓取可用工具的首要原因。請具體明確。

以下是工具定義的樣子:

json
{
  "name": "lookup_building_code",
  "description": "Look up a specific building code section by its identifier. Returns the full text of that code section.",
  "input_schema": {
    "type": "object",
    "properties": {
      "section": {
        "type": "string",
        "description": "The building code section to look up"
      }
    },
    "required": ["section"]
  }
}

那麼當我們使用這個工具時會發生什麼事呢?假設我們向代理程式發送一份合規報告。在第一輪,Claude 會回傳 stop_reason: "tool_use"——這就是我們的信號。以下是該回應的樣子:

一個 API 回應,stop_reason 設為 tool_use,包含一個 tool_use 內容區塊,指明工具名稱以及 Claude 想要用來呼叫它的輸入

我們的迴圈會以 Claude 請求的參數呼叫 lookup_building_code,然後將結果作為工具結果回傳——這是一則使用者訊息,包含一個與工具呼叫 id 綁定的 tool_result 區塊:

一則使用者訊息,包含一個 tool_result 區塊,其中有 tool_use_id 以及查詢到的建築法規文字作為其內容

然後 Claude 會繼續進行。此時,我們可以持續呼叫工具並將結果回傳給 Claude,直到它獲得所需的資訊為止。

多個工具:讓 Claude 自行選擇

一個工具很有用,但有趣的部分在於給 Claude 多個工具,並觀察它選擇使用哪一個、以什麼順序使用。

想像這個情境:您正在為前往丹佛(Denver)的三天旅行打包行李,您想知道今天的天氣以及接下來幾天的預報。因此我們宣告兩個工具,而不是一個:

typescript
const tools = [
  {
    name: "get_weather",
    description: "Get today's current weather for a city.",
    input_schema: {
      type: "object",
      properties: {
        city: { type: "string", description: "The city to check" }
      },
      required: ["city"]
    }
  },
  {
    name: "get_forecast",
    description: "Get the weather forecast for the next few days for a city.",
    input_schema: {
      type: "object",
      properties: {
        city: { type: "string", description: "The city to check" }
      },
      required: ["city"]
    }
  }
];

這個迴圈與我們之前看過的代理程式迴圈完全相同。唯一新增的部分是一個 runTool 函式,它使用 switch 陳述式根據工具名稱進行分派——這段程式碼正是您的程式碼實際執行的地方:

typescript
function runTool(name, input) {
  switch (name) {
    case "get_weather":
      return getWeather(input.city);
    case "get_forecast":
      return getForecast(input.city);
  }
}

while (true) {
  const response = await client.messages.create({
    model: "claude-sonnet-5",
    max_tokens: 1024,
    messages,
    tools,
  });

  if (response.stop_reason !== "tool_use") {
    // Claude 已完成——這是最終答案
    break;
  }

  messages.push({ role: "assistant", content: response.content });

  const toolResults = response.content
    .filter((block) => block.type === "tool_use")
    .map((block) => ({
      type: "tool_result",
      tool_use_id: block.id,
      content: runTool(block.name, block.input),
    }));

  messages.push({ role: "user", content: toolResults });
}

這就是整個模式。想要第三個工具?把它加入陣列,在 switch 中加入一個 case,就完成了。

執行這段程式碼,您會看到 Claude 呼叫 get_weather,然後呼叫 get_forecast——有時在同一輪,有時一個接著一個。然後它會回答:打包分層衣物,今天預期會有小雪,整週會逐漸回暖。

現在請注意 Claude 如何 做出選擇。它讀取了描述,將您的提示對應到「今天的天氣」和「接下來幾天」,並為每個情況選擇了正確的工具。這就是為什麼您的工具描述真的很重要。

工具執行器:跳過樣板程式碼

您可能已經發現我們剛寫的程式碼有兩個警訊:

  • 對於兩個簡單的查詢來說,這是大量的程式碼。
  • 在實際的程式碼庫中,您不會想為每個函式手寫 JSON 結構。這就像把您的程式碼寫兩次一樣。

這就是工具執行器(tool runner)發揮作用的地方。它以 beta 版形式內建於 Claude SDK 中:TypeScript、Python、Ruby、C#、Go、Java 和 PHP。您只需定義每個工具一次,執行器會在內部處理整個工具使用/工具結果的迴圈。

在 TypeScript 中,執行器推薦使用的輔助函式 betaZodTool(),以 Zod(一個用於宣告和驗證資料形狀的 TypeScript 函式庫)撰寫的簡短結構來描述每個工具。這個輔助函式需要 Zod 3.25.0 或更新版本,全新安裝即可取得:

bash
npm install zod

您的程式碼會縮減為:描述工具、傳送提示、等待結果。以下是透過工具執行器連接的相同雙工具天氣示範:

typescript
import { betaZodTool } from "@anthropic-ai/sdk/helpers/beta/zod";
import { z } from "zod";

// 將我們手動執行的相同兩個查詢包裝起來,各搭配一個簡短的 Zod schema
const weatherTool = betaZodTool({
  name: "get_weather",
  description: "Get today's current weather for a city.",
  inputSchema: z.object({
    city: z.string().describe("The city to check"),
  }),
  run: async ({ city }) => getWeather(city),
});

const forecastTool = betaZodTool({
  name: "get_forecast",
  description: "Get the weather forecast for the next few days for a city.",
  inputSchema: z.object({
    city: z.string().describe("The city to check"),
  }),
  run: async ({ city }) => getForecast(city),
});

const runner = client.beta.messages.toolRunner({
  model: "claude-sonnet-5",
  max_tokens: 1024,
  messages: [
    {
      role: "user",
      content:
        "I'm packing for a three-day trip to Denver. What's the weather today and over the next few days?",
    },
  ],
  tools: [weatherTool, forecastTool],
});

// 等待 runner 執行完畢,在所有工具來回呼叫都結束後取得最終訊息
const finalMessage = await runner;

相同的情境,程式碼卻少了許多:

  • 沒有 while 迴圈,沒有 stop reason 的 switch 判斷,也不需要手動將工具結果推回訊息中——執行器會處理所有這些事情。
  • 沒有手寫的 JSON 結構,每個工具只需一個簡短的 Zod 結構,它同時也會驗證 Claude 傳送的輸入。
  • 每個 run 函式都包裝了我們剛才手動執行的相同查詢。
  • 等待執行器(awaiting the runner)會在一切都完成後回傳最終的助理訊息。

執行它,您會得到相同的答案。

真正的工具會包裝您現有的程式碼

在實際情況中,您的工具不會是硬編碼的天氣資料。它們會包裝您應用程式中已經存在的實際函式。

以合規審查代理程式為例:它的工具是圍繞著程式碼庫中已存在的 lookup_building_code 和 search_building_code 函式的薄包裝層。使用工具執行器,您將這些函式各自包裝在一個帶有自己 Zod 結構的簡短工具定義中,代理程式就會在它撰寫的每個發現中引用具體的法規條文:

一個合規審查應用程式,顯示結構報告以及代理程式的發現,每個標記的項目都引用了它所檢查的具體建築法規條文

回顧

  • 工具讓 Claude 能夠存取您的系統。工具是您定義並公開的函式;Claude 決定何時呼叫它,而您的程式碼負責執行它。
  • 工具是 JSON 結構,包含名稱、描述和輸入結構,以 tools 陣列的形式在請求中傳遞。
  • 撰寫具體的描述。含糊的描述是代理程式誤判的首要原因。
  • stop_reason: "tool_use" 是您執行工具並將結果作為工具結果回傳的信號。
  • 對於多個工具,根據工具名稱進行分派。新增工具意味著在陣列中新增項目,並新增一個 case。
  • SDK 的工具執行器(在各個 Claude SDK 中以 beta 版提供)會處理整個迴圈,在 TypeScript 中,每個工具都是帶有 Zod 結構的簡短定義。或者您也可以自行執行這個迴圈。
  • 您可以自行執行,也可以委派這個迴圈。在這個範圍的另一端,受管理的代理程式(managed agents)會將整個代理程式委派給 Anthropic。