跳至主要內容
技術

Agent SDK 入門:從 API 到 Agentic 應用

Agent SDK 入門:從 API 到 Agentic 應用
Claude API & Agent SDK 完全指南 第 9 / 15 篇

本篇是「Claude API & Agent SDK 完全指南」系列的第 9 / 15 篇。你可以從系列總覽開始閱讀,也可以直接接著看本文。

這一章標誌著這本書的轉折點。

前幾章我們學的是 Messages API——你問一個問題,Claude 給一個答案,任務結束。這個模式能解決很多問題,但它有一個根本限制:Claude 只能做一步

真實世界的很多任務需要多步驟:找資料、分析資料、根據分析做決定、執行動作、再根據執行結果調整。這類任務靠 Messages API 加上工具(tool use)可以實作,但你要自己寫的膠水代碼很多。

這一章,我們要把這些膠水代碼交給工具去處理,正式踏進 agentic 應用的世界。

先說清楚:Anthropic 沒有一個叫「Agent SDK」的套件

在開始之前,我要先誠實地破除一個常見誤會,免得你照著別的教學去 pip install 結果裝不到東西。

網路上你會看到很多「Agent / Runner / Handoff」風格的範例——定義一個 Agent 類別、用一個 Runner 跑它、再用 handoff() 把任務交接給另一個 agent。那是 OpenAI Agents SDK 的形狀,不是 Anthropic 的。 Anthropic 並沒有發布一個獨立的、長那個樣子的「Agent SDK」套件。如果你照抄,第一行 import 就會失敗。

那麼,用 Claude 到底要怎麼建 agent?真實的路徑有兩條:

  1. Claude API + Tool Use(你或 SDK 來跑 agentic loop) ← 這本書接下來主要走這條
    • 安裝官方 SDK:Python 是 anthropic、TypeScript 是 @anthropic-ai/sdk
    • 用官方 SDK 內建的 tool runner(beta):它自動幫你跑「呼叫工具 → 回灌結果 → 再問」的循環
    • 或自己手寫 agentic loop:當你要細控流程(人工審批、條件式執行、自訂 log)時,自己跑一個 while 迴圈
  2. Managed Agents(beta):Anthropic 在伺服器端幫你跑 loop、還幫你託管一個執行工具的 container(透過 client.beta.agents / client.beta.sessions)。適合「server 端有狀態、需要 workspace」的場景。

這本書的後續章節以路徑 1 為主軸——也就是用官方 SDK 的 tool runner 跟手寫 loop。Managed Agents 我們只在「何時用什麼」跟多 agent 的段落點到,因為它細節變動快,我不想教你寫到後來不能跑的程式碼。

口語上,我還是會把「帶 system prompt + 一組工具、跑在 agentic loop 裡的 Claude 呼叫」叫做一個 agent——這個詞沒問題,問題只在「不要把它想成某個 Agent(...) 類別」。

先說 Messages API + Tool Use 的痛點

第四章我們講了 Tool Use(工具呼叫)。回顧一下那個循環:

你 → Claude(送問題 + 工具定義)

     Claude(決定用什麼工具,回傳 tool_use block)

你的代碼(執行工具,得到結果)

你 → Claude(送工具結果,繼續對話)

     Claude(可能再用另一個工具,或直接回答)

     ...重複直到 Claude 不再呼叫工具

這個循環你需要自己實作。不難,但每個做 agentic 應用的人都要重新寫一遍。而且,隨著任務複雜度增加,你還需要:

  • 管理對話歷史(上下文)
  • 處理工具執行錯誤
  • 限制最大迴圈次數(防止無限循環)
  • 支援平行工具呼叫
  • 在你的程式碼裡做 agent 之間的路由(routing)
  • 在適當時機暫停等待人工確認(human-in-the-loop)

這些功能每個都不難,但全部加起來要寫幾百行代碼,而且容易出 bug。

這正是 tool runner 想幫你消掉的東西——它就是官方 SDK 幫你把那個 while 迴圈封裝好的工具。

真實的心智模型:工具 + tool runner + agentic loop

把前面 OpenAI 風格的「Agent / Runner / Tool / Handoff」忘掉,換成 Claude 真正的三個概念:

1. 工具(Tool)

工具就是你給 Claude 使用的函式。官方 SDK 提供了裝飾器,讓你把一個普通函式直接變成工具,連 JSON Schema 都不用手寫:

from anthropic import beta_tool

@beta_tool
def get_weather(location: str, unit: str = "celsius") -> str:
    """Get current weather for a location.

    Args:
        location: City and state, e.g., San Francisco, CA.
        unit: Temperature unit, either "celsius" or "fahrenheit".
    """
    return f"72°F and sunny in {location}"

@beta_tool 會讀你的型別提示跟 docstring 裡的 Args:,自動生成這個工具的 input schema。你定義的是「Claude 能做什麼」,不是某個類別。

2. tool runner(取代你心中的 Runner)

tool runner 是官方 SDK 內建的東西,它負責跑那個工具呼叫循環——呼叫工具、把結果回灌給 Claude、再問下一步,直到 Claude 不再呼叫任何工具為止。你不用自己管那個 while 迴圈:

import anthropic
from anthropic import beta_tool

client = anthropic.Anthropic()  # 從 ANTHROPIC_API_KEY 讀金鑰

runner = client.beta.messages.tool_runner(
    model="claude-opus-4-8",
    max_tokens=16000,
    tools=[get_weather],
    messages=[{"role": "user", "content": "What's the weather in Paris?"}],
)

注意:tool_runnerclient.beta.messages 底下的一個方法,不是一個你要 import 的 Runner 類別。它回傳的東西可以直接迭代,每跑一圈 yield 一個 BetaMessage

3. agentic loop(那個「自動跑到底」的循環)

上面 tool_runner 之所以省事,就是因為它把 agentic loop 包起來了。所謂 agentic loop,就是「Claude 想用工具 → 跑工具 → 把結果還給 Claude → Claude 看了結果再決定」這個重複過程,自動跑到 Claude 講完話為止。

那「Handoff(交接給另一個 agent)」呢?Claude 沒有這個原語。 多 agent 系統在 Claude 的世界裡,是你在自己的程式碼裡做路由——orchestrator 依判斷去呼叫對應的 subagent 函式。這部分我們在第十一章(多 agent)會展開,這裡先知道「沒有 handoff() 這種魔法、是你自己 route」就好。

安裝

裝的是官方 SDK,不是什麼 agent 專用套件。

Python:

pip install anthropic

TypeScript / Node.js:

npm i @anthropic-ai/sdk

import 的方式:

# Python
import anthropic
from anthropic import beta_tool
// TypeScript
import Anthropic from "@anthropic-ai/sdk";
import { betaZodTool } from "@anthropic-ai/sdk/helpers/beta/zod";

確保你已設定 ANTHROPIC_API_KEY 環境變數,SDK 會自動讀取。

第一個 Agent:查天氣

讓我們從一個最小可跑的例子開始:一個能查天氣的 agent。這裡用 tool runner,讓 SDK 幫我們跑 loop。

import anthropic
from anthropic import beta_tool

client = anthropic.Anthropic()


@beta_tool
def get_weather(location: str, unit: str = "celsius") -> str:
    """Get current weather for a location.

    Args:
        location: City and state, e.g., San Francisco, CA.
        unit: Temperature unit, either "celsius" or "fahrenheit".
    """
    # 真實情況這裡會去打某個天氣 API;這裡先回傳假資料示範
    return f"72°F and sunny in {location}"


# tool runner 自動處理 agentic loop:
# 呼叫工具、回灌結果、再問,直到 Claude 不再呼叫工具
runner = client.beta.messages.tool_runner(
    model="claude-opus-4-8",
    max_tokens=16000,
    tools=[get_weather],
    messages=[{"role": "user", "content": "What's the weather in Paris?"}],
)

# 每個 iteration yield 一個 BetaMessage;Claude 講完就停
for message in runner:
    print(message)

執行這段代碼,背後發生的事是:

  1. Claude 收到問題,判斷需要查天氣,回傳一個 tool_use(要呼叫 get_weather
  2. tool runner 自動執行你的 get_weather 函式,把結果回灌給 Claude
  3. Claude 看到天氣資料,生成一段給人看的回答,不再呼叫工具
  4. 因為沒有更多 tool_use,loop 自動結束

整個工具呼叫循環你一行都沒寫——這就是 tool runner 的價值。

如果你的工具是 I/O 密集(要打外部 API),可以改用 async 版:把 import 換成 from anthropic import beta_async_tool,工具寫成 async def,其餘形狀一樣。

想自己掌控每一步?手寫 agentic loop

tool runner 很方便,但有時候你需要細控——例如某些工具要先經人工審批、要依條件決定跑不跑、要記自訂 log。這時就自己跑 loop。這也是看清楚「tool runner 到底幫你做了什麼」的最好方式:

import anthropic

client = anthropic.Anthropic()
tools = [...]   # 工具的 JSON 定義(見下一段)
messages = [{"role": "user", "content": user_input}]

MAX_ITERATIONS = 10   # 防無限循環:自己加計數器
for _ in range(MAX_ITERATIONS):
    response = client.messages.create(
        model="claude-opus-4-8",
        max_tokens=16000,
        tools=tools,
        messages=messages,
    )
    if response.stop_reason == "end_turn":
        break  # Claude 講完了

    tool_use_blocks = [b for b in response.content if b.type == "tool_use"]
    messages.append({"role": "assistant", "content": response.content})

    tool_results = []
    for tool in tool_use_blocks:
        result = execute_tool(tool.name, tool.input)   # 你的實作
        tool_results.append({
            "type": "tool_result",
            "tool_use_id": tool.id,      # 必須對應 tool_use 的 id
            "content": result,
        })
    messages.append({"role": "user", "content": tool_results})

final_text = next(b.text for b in response.content if b.type == "text")

幾個一定要知道的點:

  • stop_reason 是 loop 的方向盤。可能的值:end_turn(講完了,跳出)、max_tokens(被 max_tokens 截斷)、tool_use(要呼叫工具,繼續跑)、pause_turn(server 端工具暫停,可續跑)、refusal(安全拒答,看 stop_details)。
  • 工具錯誤:在那筆 tool_result 裡加 "is_error": True,把錯誤訊息塞進 content,讓 Claude 知道工具掛了、自己想辦法。
  • 防無限循環:手寫 loop 一定要加 MAX_ITERATIONS 計數器跳出。tool runner 則是「沒有 tool_use 就自動停」,不會無止盡跑。

那這個手寫 loop 用的工具 JSON 定義長怎樣?沒有裝飾器幫你時,就自己寫:

tools = [{
    "name": "get_weather",
    "description": "Get current weather for a location",
    "input_schema": {
        "type": "object",
        "properties": {
            "location": {"type": "string", "description": "City and state"},
            "unit": {"type": "string", "enum": ["celsius", "fahrenheit"]},
        },
        "required": ["location"],
    },
}]
# 強制這一回合只能用某工具:tool_choice={"type": "tool", "name": "get_weather"}
# 要嚴格結構化:工具裡加 "strict": True,並讓 input_schema 有 additionalProperties: False

TypeScript 版本

TypeScript 這邊一樣有 tool runner,搭配 betaZodTool 用 Zod 定義工具的 input schema,型別安全又不用手寫 JSON Schema:

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

const client = new Anthropic();

const getWeather = betaZodTool({
  name: "get_weather",
  description: "Get current weather for a location",
  inputSchema: z.object({
    location: z.string().describe("City and state, e.g., San Francisco, CA"),
    unit: z.enum(["celsius", "fahrenheit"]).optional(),
  }),
  run: async (input) => `72°F and sunny in ${input.location}`,
});

const finalMessage = await client.beta.messages.toolRunner({
  model: "claude-opus-4-8",
  max_tokens: 16000,
  tools: [getWeather],
  messages: [{ role: "user", content: "What's the weather in Paris?" }],
});

console.log(finalMessage.content);

client.beta.messages.toolRunner(...) 跟 Python 的 tool_runner 是同一件事的 TS 版——一樣自動跑 agentic loop、跑到 Claude 不再呼叫工具為止,回傳最終的 message。

如果你要細控,TS 也能手寫 loop,要點跟 Python 一樣(看 stop_reason === "end_turn" 跳出,把 tool_result 串回 messages):

import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic();

let messages: Anthropic.MessageParam[] = [{ role: "user", content: userInput }];
const MAX_ITERATIONS = 10;
for (let i = 0; i < MAX_ITERATIONS; i++) {
  const response = await client.messages.create({
    model: "claude-opus-4-8",
    max_tokens: 16000,
    tools,
    messages,
  });
  if (response.stop_reason === "end_turn") break;

  const toolUseBlocks = response.content.filter(
    (b): b is Anthropic.ToolUseBlock => b.type === "tool_use",
  );
  messages.push({ role: "assistant", content: response.content });

  const toolResults: Anthropic.ToolResultBlockParam[] = [];
  for (const t of toolUseBlocks) {
    toolResults.push({
      type: "tool_result",
      tool_use_id: t.id,
      content: await executeTool(t.name, t.input),
    });
  }
  messages.push({ role: "user", content: toolResults });
}

Agent vs Assistant:Agency 的概念

很多人分不清「AI 助手」和「AI Agent」的差異。我的理解是:Agency(代理能力)= 自主決定下一步的能力

特性AI 助手AI Agent
執行步驟一步(問一答一)多步(自主決定做什麼)
工具使用手動觸發自主選擇和調用
目標導向回答問題完成任務
錯誤恢復需要人干預可以自主嘗試不同方法
適用場景問答、諮詢研究、執行、自動化

從「建立助手」升級到「建立代理」,技術上的關鍵差別其實就一句話:有沒有那個 agentic loop。助手是一次 messages.create 拿一個答案;代理是把 messages.create 放進一個會根據工具結果繼續跑的循環裡。tool runner 幫你跑這個循環,你只要把目標跟工具給它,它會想辦法達成,而不是每一步都要你告訴它怎麼做。

何時手寫 loop、何時用 tool runner、何時上 Managed Agents?

這個問題我被問過很多次,我的判斷框架是這樣的:

用 tool runner 當(多數情況的預設):

  • 任務需要動態決策(不知道幾步才能完成),但你不需要在每一步插手
  • 你只是想「給目標 + 一組工具,讓它自己跑到底」
  • 你想少寫膠水代碼、快速做出可跑的 agent

自己手寫 agentic loop 當:

  • 你需要在某些工具執行前插入人工審批
  • 你要依條件決定跑哪些工具、或自訂每一步的 log / 中斷邏輯
  • 你要做很客製的錯誤處理或重試策略
  • 講白了:當 tool runner 的「全自動」不夠細,你需要方向盤

考慮 Managed Agents 當:

  • 你需要 server 端有狀態的、長時間執行的 agent,還要一個託管的 workspace / container 來實際執行工具
  • 你不想自己維運那個執行環境

而最基本的——只需要一到兩步、不需要任何工具循環的任務(純文字生成、分類、翻譯),根本不用 agent,直接一次 messages.create 最省事、延遲最低。

我的實踐原則:先用最單純的 messages.create;當工具循環邏輯開始出現,就用 tool runner;只有當你發現需要細控每一步時,才退回手寫 loop。 不要一開始就過度工程化。

與 LangChain、LlamaIndex 的比較

如果你之前用過 LangChain 或 LlamaIndex,你可能在想:既然有官方 SDK 的 tool runner,為什麼還會有人用這些框架?

我的觀點:

直接用官方 Anthropic SDK(+ tool runner)的優勢:

  • 官方出品,對 Claude 的所有功能(extended thinking、prompt caching 等)支援最直接、最新
  • 抽象層少,你看得到底層的 messages / tool_use / tool_result,debug 容易
  • 跟 Claude 功能同步更新,不用等第三方框架跟進
  • 學習曲線平:tool runner 就是把你已經懂的 Messages API 包了一層 loop

LangChain 的優勢:

  • 生態系豐富,有大量現成 integrations(向量資料庫、各種 API)
  • 支援多種 LLM(不限於 Claude),抽象層幫你抹平 provider 差異
  • 有更成熟的 RAG pipeline 工具
  • 社群更大

LlamaIndex 的優勢:

  • 專注於資料索引和 RAG,這方面深度更好
  • 對結構化資料的支援更豐富

我的建議:如果你的應用 100% 用 Claude,從官方 SDK + tool runner 開始,因為它最直接、抽象最少、最容易 debug。如果你需要豐富的第三方整合,或者未來可能換模型,LangChain 這類框架是合理的選擇。

設計時要記得的幾個原則

在繼續往下蓋之前,幾個會讓你的 agent 程式碼更乾淨、更好維護的原則:

1. 工具盡量寫成純函式、可重複呼叫

agentic loop 裡 Claude 可能因為前一次結果不理想而再呼叫同一個工具,所以你的工具實作最好是無副作用的(或至少冪等),能承受重複呼叫。會「動到外部世界」的工具(送錢、寄信、刪資料)要特別小心,這類最適合放在手寫 loop 裡加人工審批。

2. 狀態在 messages,不在某個物件

Claude 這條路沒有什麼神祕的「agent 記憶體物件」。整段對話的狀態——使用者問了什麼、Claude 呼叫過哪些工具、工具回傳了什麼——全部就是那串 messages 歷史。要做「記憶」,就是把相關歷史串進 messages,或用 memory 類工具把長期記憶外掛出去。這也是為什麼手寫 loop 裡我們一直在 messages.append(...)

3. async 優先(尤其 I/O 密集)

工具大多是去打外部 API 的 I/O 操作,寫成 async 能讓多個工具呼叫平行跑。Python 用 AsyncAnthropic + beta_async_tool,TypeScript 本來就是 async。

4. 可觀測性:用 SDK log + request id,不要幻想有 tracing 開關

Claude 這邊沒有一個「打開就有漂亮 trace」的開關。要看 agent 每一步在幹嘛,務實的做法是:

# 1) 環境變數開 SDK 詳細 log
#    ANTHROPIC_LOG=debug

# 2) 直接印出訊息歷史,看 Claude 呼叫了哪些工具、拿到什麼
print(messages)

# 3) 每個 response 都有 request id,回報問題時附上它
response = client.messages.create(model="claude-opus-4-8", max_tokens=16000,
                                  messages=messages)
print(response._request_id)

手寫 loop 的好處之一,就是你想在哪一步插 log、插 metric、插 breakpoint,完全自由。

5. 結構化輸出與錯誤處理(順手補上)

要 Claude 回一個你能直接用的物件,可以用 parse 搭配 Pydantic:

from pydantic import BaseModel

class Insight(BaseModel):
    title: str
    detail: str

resp = client.messages.parse(
    model="claude-opus-4-8",
    max_tokens=16000,
    messages=[...],
    output_format=Insight,
)
data = resp.parsed_output   # 已驗證的 Insight

SDK 也有一整組例外類別可以 catch:anthropic.BadRequestError(400)、AuthenticationErrorPermissionDeniedErrorNotFoundErrorRateLimitErrorRequestTooLargeError(413)、APIStatusErrorAPIConnectionErrorAPITimeoutError。其中 429 跟 5xx,SDK 預設會自動重試(max_retries 預設 2),所以多數暫時性錯誤你不用自己處理。


理解了「工具 + tool runner + agentic loop」這套真實心智模型之後,是時候動手建一個真正有用的 agent 了。

下一章是這本書技術深度最高的一章:我們要從頭打造一個能搜尋網路、查詢資料庫、生成報告的 Research Agent,涵蓋工具設計、用 messages 管狀態、錯誤處理、測試策略的完整代碼。如果你一直想知道「真實的 agent 應用長什麼樣」,下一章就是答案。

留言討論

esc
輸入關鍵字搜尋文章...
查看收藏 →