Claude Media
Claude Go SDKの実装ガイド — リクエストユニオンとファイルアップロード

Claude Go SDKの実装ガイド — リクエストユニオンとファイルアップロード

Claude API向けGo SDKのOf接頭辞によるユニオン型表現、ファイルアップロード、自動ページング、リクエストオプションの実装パターンを解説します。

Go SDKを導入して最初のリクエストを送る

Claude API向けのGo SDKは go get で導入します。Go 1.24以降が必要です。

go get github.com/anthropics/anthropic-sdk-go

クライアントは anthropic.NewClient(...) に option.WithAPIKey(...) を渡して作ります。省略時は ANTHROPIC_API_KEY 環境変数が使われます。

client := anthropic.NewClient(
    option.WithAPIKey("my-anthropic-api-key"),
)
message, err := client.Messages.New(context.TODO(), anthropic.MessageNewParams{
    MaxTokens: 1024,
    Messages: []anthropic.MessageParam{
        anthropic.NewUserMessage(anthropic.NewTextBlock("What is a quaternion?")),
    },
    Model: anthropic.ModelClaudeOpus5_5,
})

応答本文は message.Content のスライスとして返ってきます。中身はテキストなのかツール呼び出しなのか型が定まらないため、.AsAny() で実際の型に変換してから型スイッチで分岐します。

for _, block := range message.Content {
    if textBlock, ok := block.AsAny().(anthropic.TextBlock); ok {
        fmt.Println(textBlock.Text)
    }
}

すべてのメソッドが context.Context を第一引数に取るため、キャンセルやタイムアウトはGo標準の context パッケージの流儀でそのまま制御できます。AnthropicOkHttpClient.fromEnv() やビルダーでクライアントを組み立てるJava SDKと比べると、設定の渡し方は違いが目立つ部分です。Goでは関数オプションをそのまま並べます。

リクエストのユニオン型はOf接頭辞のフィールドで表現される

Go言語にはユニオン型がないため、SDKは「複数の選択肢のうちどれか1つだけが値を持つ構造体」として表現します。各フィールド名は Of で始まり、非ゼロ値を持てるのは1つだけです。値が設定済みかどうかは param.IsOmitted() で確かめます。

type AnimalUnionParam struct {
    OfCat *Cat `json:",omitzero,inline"`
    OfDog *Dog `json:",omitzero,inline"`
}
 
animal := AnimalUnionParam{
    OfCat: &Cat{
        Name: "Whiskers",
        Owner: PersonParam{
            Address: AddressParam{Street: "3333 Coyote Hill Rd", ZipCode: 0},
        },
    },
}
 
if address := animal.GetOwner().GetAddress(); address != nil {
    address.ZipCode = 94304
}

ユニオンのサブプロパティは GetOwner() GetAddress() のようなメソッド経由でアクセスします。これらは値が存在するときだけ、その値を指すポインタを返します。ツール定義を配列で渡すときの anthropic.ToolUnionParam{OfTool: &toolParam} も同じ仕組みで、公式ツールを表す OfBashTool20250124 のようなフィールド名の末尾の日付は、ツールのバージョン接尾辞をそのままGoの型名に写したものです。この接尾辞の意味はClaude APIツールのバージョン管理で扱っています。

必須フィールドはゼロ値でも常にシリアライズされ、`json:"...,required"` タグが付きます。任意のプリミティブ型は param.Opt[T] でラップされ、anthropic.String(...) や anthropic.Int(...) のようなヘルパーで値を設定します。構造体やスライス、文字列enumも含め、omitzero タグが付いたフィールドはゼロ値なら送信されません。この挙動はGo 1.24の encoding/json が持つ omitzero の意味づけを使っていて、値ではなく明示的な null を送りたい場合は param.Null[T]()(スカラー用)や param.NullStruct[T]()(構造体用)を使います。

paramの往復変換とレスポンス側のユニオン型

MessageNewParams のような Param で終わる型はリクエスト送信専用の設計で、JSONへのマーシャリングはできますが、生のJSONをそのままアンマーシャルしても OfBashTool20250124 のような型付きユニオンフィールドは nil のままになります。データベースやミドルウェア経由でparamを一度シリアライズし、あとで復元して再送したいケースでは、この制限を回避する必要があります。

b, err := json.Marshal(original)
 
var params anthropic.MessageNewParams
if err := params.UnmarshalJSON(b); err != nil {
    panic(err)
}
param.SetJSON(b, &params) // v1.20.0以降
 
b2, _ := json.Marshal(params)
fmt.Println(string(b) == string(b2)) // true

UnmarshalJSON でスカラーフィールドを復元したあと、param.SetJSON で元の生バイト列を紐付けておくと、再シリアライズ時に元のJSONが復元されます。汎用の param.Override[T](any) でも代用できますが、型パラメータを書かずに済み意図も明確な param.SetJSON のほうが、この用途には向いています。

レスポンス側の構造体はポインタやラッパーを使わない素の値型で、各フィールドには .Valid() で値の有無を確認できる JSON メタデータが付きます。レスポンスのユニオンは逆に、各バリアントの全フィールドを1つの構造体へ平坦化する設計です。.AsAny() で実際のバリアントの型へ変換して分岐できます。

switch variant := animal.AsAny().(type) {
case Dog:
case Cat:
default:
    panic("unexpected type")
}

SDKにまだ無いレスポンスフィールドは JSON.ExtraFields マップに残るので、新しいAPIプロパティにSDKの更新を待たずアクセスできます。

ファイルをio.Readerとして送る

ファイルアップロードのパラメータは io.Reader として型付けされています。素の io.Reader をそのまま渡すと、ファイル名は anonymous_file、Content-Typeは application/octet-stream として送られるため、実用上は anthropic.File(reader, filename, contentType) ヘルパーで明示的に指定します。

file, err := os.Open("/path/to/file.json")
anthropic.FileUploadParams{
    File: anthropic.File(file, "custom-name.json", "application/json"),
}
 
// 文字列からアップロードする場合
anthropic.FileUploadParams{
    File: anthropic.File(strings.NewReader("my file contents"), "custom-name.json", "application/json"),
}

io.Reader の実行時型が Name() string や ContentType() string を実装していれば、ファイル名・Content-Typeはそこからも取得されます。os.File はすでに Name() string を実装しているため、os.Open で開いたファイルはディスク上のファイル名でそのまま送信されます。

ページングを自動化する

一覧系のレスポンスは、全ページを自動でたどる方法と、1ページずつ手動で扱う方法の両方をサポートします。

くらべる

ページングの2通りの書き方

全件を1ループで

ListAutoPaging()

件数を気にせず全件を1つのループで処理したいときに使います。足りなくなると次のページを自動で取りに行きます。

ページ単位で

List() + GetNextPage()

ページ単位で処理を区切りたいときや、途中で打ち切る可能性があるときに使います。GetNextPage() が nil を返すまでループします。

iter := client.Messages.Batches.ListAutoPaging(context.TODO(), anthropic.MessageBatchListParams{
    Limit: anthropic.Int(20),
})
for iter.Next() {
    messageBatch := iter.Current()
    fmt.Println(messageBatch.ID)
}
if err := iter.Err(); err != nil {
    panic(err.Error())
}

手動の場合も、エラーはループの外でまとめて見ます。バッチ処理そのものをコードでなくシェルから叩きたい場合は、Go SDKでコンパイルする代わりにant CLIでAPIリソースをスクリプトで自動化するの一覧・出力連携が使えるので、用途に応じて使い分けます。

リクエストオプションでヘッダーやデバッグログを差し込む

Go SDKは関数オプションパターンを使っています。option パッケージの各関数が RequestOption(RequestConfig を書き換えるクロージャ)を返し、クライアント生成時にも個別リクエスト時にも渡せます。

client := anthropic.NewClient(
    option.WithHeader("X-Some-Header", "custom_header_info"),
)
 
client.Messages.New(context.TODO(), params,
    option.WithHeader("X-Some-Header", "some_other_custom_header_info"),
    option.WithJSONSet("some.json.path", map[string]string{"my": "object"}),
)

クライアント側のオプションはリクエスト側の同名オプションで上書きできるので、既定値をクライアントに設定しつつ特定のリクエストだけ挙動を変える、という使い方ができます。応答が期待と違うときは option.WithDebugLog(nil) を挟むとデバッグ情報が出力されます。

ミドルウェアでリクエストとレスポンスに割り込む

ロギング・独自リトライ・リクエストへの注釈付け・拒否応答時のフォールバックといった横断的な処理は、option.WithMiddleware で挟むミドルウェアに書きます。ミドルウェアは送信前のリクエストと next 関数を受け取り、next を呼ぶ前のコードが送信時、呼んだあとのコードが受信時に走ります。

client := anthropic.NewClient(
    option.WithMiddleware(func(req *http.Request, next option.MiddlewareNext) (*http.Response, error) {
        start := time.Now()
        slog.Info("sending request", "method", req.Method, "url", req.URL)
 
        res, err := next(req)
        if err != nil {
            return nil, err
        }
 
        slog.Info("received response", "status", res.StatusCode, "duration", time.Since(start))
        return res, nil
    }),
)
手順

リクエストが通る順番

  1. 1

    クライアントに登録したミドルウェア

    登録した順に、next を呼ぶ前のコードが走ります。

  2. 2

    リクエスト単位で渡したミドルウェア

    クライアント側がすべて済んでから実行されます。

  3. 3

    HTTPクライアント

    ミドルウェアがすべて実行されたあとに、差し替えたHTTPクライアントへ渡ります。応答は逆順で戻ります。

Go SDKでは option.WithMiddleware を複数回呼ぶと連結される点が、配列で渡す他言語SDKとの違いです。登録順序やHTTPクライアント差し替えなど、SDK共通のミドルウェア設計はClaude SDKのミドルウェアで扱っています。

HTTPクライアントの差し替えはプロキシ設定・独自TLS・コネクションプーリングの用途で、option.WithHTTPClient を使います。1クライアントにつき1つだけ使われます。なお、SDK群にはClaude Fable 5が拒否した応答をフォールバックモデルへ自動的にリトライする組み込みミドルウェアが用意されています。利用するには公式の「フォールバックモデルでの拒否検知とリトライ」の手順に沿って設定します。

生のHTTPレスポンスにアクセスする

ステータスコードやレスポンスヘッダーを直接見たいときは、option.WithResponseInto() にポインタ変数を渡します。呼び出し後、そのポインタに *http.Response が格納されます。

var response *http.Response
message, err := client.Messages.New(
    context.TODO(),
    params,
    option.WithResponseInto(&response),
)
if err != nil {
    // handle error
}
fmt.Printf("Status Code: %d\n", response.StatusCode)
fmt.Printf("Headers: %+#v\n", response.Header)

レート制限のヘッダーを見て動的にリクエスト間隔を調整したい場合や、CDN・プロキシ経由の応答でキャッシュ状況を確認したい場合など、SDKが構造化して返さない情報を扱うときに使います。

エラーハンドリング・リトライ・タイムアウト

APIがエラーステータスを返すと、SDKは *anthropic.Error 型のエラーを返します。ステータスコード・リクエスト・レスポンス・エラーボディのJSONに加えて、Anthropicサポートへの問い合わせに使える RequestID を保持しています。

var apierr *anthropic.Error
if errors.As(err, &apierr) {
    println("Request ID:", apierr.RequestID)
    println(string(apierr.DumpRequest(true)))
    println(string(apierr.DumpResponse(true)))
}
数字

Go SDKの既定値

  • リトライ回数

    2回

    短い指数バックオフ付き

  • Messages非ストリーミングのタイムアウト

    10分

    他のリクエストは既定なし

リトライの対象は、接続エラー・408・409・429・5xx系のみです。option.WithMaxRetries(n) でクライアント全体またはリクエスト単位の回数を変更でき、0 を渡せば無効にできます。HTTP通信そのものが失敗したときは *anthropic.Error ではなく、*url.Error のような素のエラーが返ります。

context.WithTimeout で設定した期限はリトライをまたいで通しで適用され、リトライのたびにリセットされるわけではない点に注意します。1回あたりのリトライにだけ短いタイムアウトを掛けたい場合は option.WithRequestTimeout() を別途指定します。

// リクエスト全体(全リトライ込み)のタイムアウト
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
defer cancel()
 
client.Messages.New(
    ctx,
    params,
    // 1回あたりのリトライにだけ適用されるタイムアウト
    option.WithRequestTimeout(20*time.Second),
)

この2つを混同すると、「全体で5分」のつもりが「1回の試行が20秒を超えたら失敗」という別の制約になっていた、という事故につながります。両方を組み合わせて使う場合は、context.WithTimeout が上限、WithRequestTimeout がその中でのリトライ単位という関係になります。

クラウド経由でホストされたClaudeを使う

自社のクラウド環境からClaudeを呼びたい場合、Go SDKはバックエンドをインポートし直すだけで済むように分かれています。

まとめ

バックエンドごとのパッケージ

  • Google Cloud(Agent Platform)

    anthropic-sdk-go/vertex の vertex.WithGoogleAuth(ctx, region, projectID)。認証情報を自前で渡すなら vertex.WithCredentials です。

  • Amazon Bedrock

    anthropic-sdk-go/bedrock の bedrock.NewMantleClient。WithLoadDefaultConfig と WithConfig は、InvokeModel APIを使う既存アプリ向けに残っています。

  • Claude Platform on AWS(ベータ)

    anthropic-sdk-go/aws の anthropicaws.NewClient(ctx, cfg)。WorkspaceID か ANTHROPIC_AWS_WORKSPACE_ID が必要です。

  • Microsoft Foundry

    anthropic-sdk-go/foundry の foundry.NewClient(cfg)。APIKey(Microsoft Entra IDなら AzureADTokenProvider)と Resource を設定に渡すか、ANTHROPIC_FOUNDRY_API_KEY と ANTHROPIC_FOUNDRY_RESOURCE を環境変数で渡します。

bedrock パッケージをインポートすると、init() が application/vnd.amazon.eventstream のデコーダーをSDKのストリーミング層へ全体登録します。NewMantleClient を使わない経路でも同じです。anthropicaws のインポート別名は、aws-sdk-go-v2/aws と名前が衝突しないようにするためのものです。

複数のワークスペースにアクセスできる個人・サービスアカウントのAPIキーを使うなら、anthropic-workspace-id ヘッダーでワークスペースを指定します。option.WithHeader で付けられます。

SDKにまだ無いエンドポイントやパラメータを使う

型付きのメソッドが追いついていないAPIは、SDKを捨てずに呼べます。client.Get や client.Post などの汎用メソッドでも、クライアントのリトライ設定は効きます。

var result *http.Response
err := client.Post(context.Background(), "/unspecified", map[string]any{"key": "value"}, &result)

既存メソッドに未知のパラメータを足すなら option.WithJSONSet("data.last_name", "Doe") や option.WithQuerySet() を使います。未知のレスポンス項目は、result.JSON.RawJSON() で全体を、result.JSON.Foo.Raw() で特定項目の生JSONを取れます。パッケージはおおむねSemVerに従いますが、内部実装や影響が小さいと見込まれる非互換の変更は、マイナーバージョンで入ることがあります。

GitHub issuesに挙がっているつまずき

リポジトリのissueには、ドキュメントの例どおりに書いても引っかかる報告が上がっています。

  • bedrock.WithLoadDefaultConfigのpanic: 未知のプロファイルやSSOキャッシュなしといった通常のAWS設定エラーでpanicし、CLIがログインを促す代わりにスタックトレースを出す、という報告です(#416、open)。この関数が InvokeModel 経路の既存アプリ向けである点は、前節のとおりです
  • ToParam() の複数ターンでの崩れ: web_search_tool_resultとweb_fetch_tool_resultの ToParam() が複数ターンのやり取りで壊れる、という報告です(#346、closed)。結果ブロックを次のターンへ戻す実装が対象です

実装でつまずきやすい3つのポイント

いずれもコンパイルは通ってしまうため、レビューで見落としやすい種類のミスです。

  • SetExtraFields に信頼できないデータを渡す: このメソッドは構造体の同名フィールドを上書きしてリクエストボディへ任意のフィールドを送り込めるため、外部入力をそのまま渡すとリクエスト内容を書き換えられるリスクがあります
  • paramをJSONへ往復させて型付きユニオンが消える: Param 型は送信専用の設計なので、一度JSON化してから素の UnmarshalJSON だけで復元すると、ツール定義のような型付きユニオンフィールドが nil に戻ります。保存・再送する用途では param.SetJSON を必ず併用します
  • 大きな MaxTokens を非ストリーミングで指定する: アイドル接続を切るネットワーク環境では応答前にタイムアウトすることがあり、SDK自体も10分を超えると見込まれる非ストリーミングリクエストにはエラーを返します。長時間かかる想定なら Messages.NewStreaming() を使うか、タイムアウトを明示的に延長します

まとめ

Go SDKでまず押さえるのは、リクエスト側が Of 接頭辞のユニオン、レスポンス側が平坦化した構造体という非対称です。paramを保存して再送する設計なら param.SetJSON を、外部入力を扱うなら SetExtraFields を使わない判断を、先に決めておくと事故を避けられます。

この記事を共有:XはてブLinkedIn