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,
})

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

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

すべてのメソッドが context.Context を第一引数に取るため、キャンセルやタイムアウトはGo標準の context パッケージの流儀でそのまま制御できます。ここが、環境変数とビルダーで設定し CompletableFuture で非同期化するJava SDKとの体感の違いとしていちばん大きい部分です。

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

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

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 タグが付いたフィールドはゼロ値なら送信されません。値ではなく明示的な 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() stringContentType() string を実装していれば、ファイル名・Content-Typeはそこからも取得されます。os.File はすでに Name() string を実装しているため、os.Open で開いたファイルはディスク上のファイル名でそのまま送信されます。

ページングを自動化する

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

方法向く場面
ListAutoPaging()向く場面件数を気にせず全件を1つのループで処理したいとき
List() + GetNextPage()向く場面ページ単位で処理を区切りたい、または途中で打ち切る可能性があるとき
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())
}

List() を使う場合は、返ってきたページの .GetNextPage()nil になるまでループを回します。バッチ処理そのものをコードでなくシェルから叩きたい場合は、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つ目のミドルウェアの送信前コードが最初に走り、受信後コードは最後に走ります。クライアントに登録したミドルウェアは、リクエスト単位で渡したミドルウェアより先に実行されます。Go SDKでは option.WithMiddleware を複数回呼ぶと連結される点が、配列で渡す他言語SDKとの違いです。登録順序やHTTPクライアント差し替えなど、SDK共通のミドルウェア設計はClaude SDKのミドルウェアで扱っています。

HTTPクライアント自体を差し替える(プロキシ設定・独自TLS・コネクションプーリング目的)ことも可能で、1クライアントにつき1つだけ使われ、ミドルウェアがすべて実行されたあとにカスタムHTTPクライアントへ渡されます。なお、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)))
}

リトライは既定2回、短い指数バックオフ付きで、接続エラー・408・409・429・5xx系のみが対象です。option.WithMaxRetries(n) でクライアント全体またはリクエスト単位の回数を変更できます。

非ストリーミングのMessagesリクエストは既定で10分のタイムアウトを持ち、それ以外のリクエストには既定のタイムアウトがありません。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)
Amazon Bedrockパッケージanthropic-sdk-go/bedrockエントリーポイントbedrock.NewMantleClient(新規実装はこちら)
Claude Platform on AWSパッケージanthropic-sdk-go/awsエントリーポイントanthropicaws.NewClient(ctx, cfg)

Foundry経由は、Go SDKでは対応していません。Claude Platform on AWSはベータ提供で、anthropicaws.NewClient を使うには WorkspaceID を設定に渡すか、ANTHROPIC_AWS_WORKSPACE_ID 環境変数を設定しておく必要があります。

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

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

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

まとめ

Claude Go SDKは、ユニオン型を Of 接頭辞の構造体で表現し、context.Context によるキャンセル・タイムアウト制御とGoらしい関数オプションパターンでリクエストを組み立てる設計です。ファイルは io.Reader として渡し、一覧取得は ListAutoPaging() と手動ページングを場面に応じて使い分けます。ロギングや独自リトライのような横断処理はミドルウェアへ切り出し、クラウド越しにClaudeを呼ぶ構成ではバックエンドのパッケージを差し替えるだけで済みます。paramの往復変換に param.SetJSON が必要になる場面と、SetExtraFields の信頼できる入力限定という2点を押さえておくと、実装時の事故を避けられます。

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