Gemini

GoでGemini APIを呼び出す実装手順|SDKとエンドポイントURL【2026年8月版】

GoでGemini APIを呼び出す実装手順|SDKとエンドポイントURL【2026年8月版】

GoからGemini APIを叩こうとして最初に迷うのは、検索で出てくるサンプルコードがどの世代のものか判別できない点です。github.com/google/generative-ai-go を使う記事はすでにサポートが終了したSDKを前提にしており、そのまま写しても最新モデルは呼べません。この記事では現行SDK google.golang.org/genai v1.66.0 の実コードと、SDKを介さずエンドポイントURLを直接叩く実装の両方を、2026年8月時点の一次情報にもとづいて整理します。

まとめ

結論として、GoでGemini APIを使うなら選択肢は現行SDK google.golang.org/genai の一択です。旧SDK github.com/google/generative-ai-go は公式READMEで2025年11月30日にサポート終了が明記されており、v0.20.1(2025年5月2日)を最後に更新が止まっています。

ただし2026年8月時点では、Googleが「新規開発の推奨」と位置づけるInteractions APIにGo SDKが追随していません。Interactions APIをGoから使うなら net/http でエンドポイントを直接叩く必要があり、SDKで完結する GenerateContent との使い分けが実装判断の分かれ目になります。エンドポイントURLの構造、モデルIDの選び方、goroutineで並行呼び出しするときの制約まで、順に見ていきます。

Gemini API自体の定義、できること、料金、始め方はGemini APIとは?できること・料金・始め方とInteractions APIの最小コードで整理しています。この記事はGoから呼ぶ実装に絞ります。

旧SDK generative-ai-go のサポート終了と移行先

日本語の検索結果に残るGo向けサンプルの多くは github.com/google/generative-ai-go/genai を import しています。このリポジトリのREADMEは冒頭が「[Deprecated] Google AI Go SDK for the Gemini API」で始まり、サポート方針として次の2点を明記しています。

  • Limited Maintenance:開発は重大なバグ修正のみに限定され、新機能は追加されない
  • End-of-Life Date:バグ修正を含むすべてのサポートが2025年11月30日で恒久的に終了する

この日付には注意が必要です。EOL日は当初2025年8月31日と告知され、2025年6月に9月30日へ、2025年8月に11月30日へと二度延長されています。pkg.go.devやモジュールのアーカイブが表示するREADMEは最終タグ(v0.20.1、2025年5月2日)時点の内容なので、そこを読むと廃止済みの「8月31日」を掴みます。現行の日付を確認したいときは、リポジトリのmainブランチのREADMEを直接見てください。

移行先は google.golang.org/genai(リポジトリは github.com/googleapis/go-genai)です。CHANGELOG上2026年7月28日付の v1.66.0 まで、週次に近いペースで更新が続いています。Gemini 2.0以降のモデル追加や新機能は、すべて新SDK側にしか入りません。

見分け方は import パスだけで足ります。github.com/google/generative-ai-go と書かれた記事は2025年前半以前の情報なので、モデルIDもAPI仕様も現行と一致しないと考えて読み飛ばして構いません。Go本体のバージョン管理と合わせて棚卸しする場合は、Go 1.27の新機能・変更点まとめで移行時の言語側の変更点も確認しておくと手戻りが減ります。

Gemini APIをGoから呼ぶ最小実装

SDKの導入とAPIキーの読み込み優先順位

導入はモジュールを追加するだけです。go-genai の go.mod は go 1.24 を要求するため、それより古いGoでは go get の時点で弾かれます。

go get google.golang.org/genai

APIキーは ClientConfig.APIKey に直接渡すほか、環境変数からも読めます。ここに実装上の注意点があります。SDKは GOOGLE_API_KEY と GEMINI_API_KEY の両方を参照し、両方がセットされている場合は警告ログを出したうえで GOOGLE_API_KEY を採用します。

func getAPIKeyFromEnv(envVars map[string]string) string {
    googleAPIKey := envVars["GOOGLE_API_KEY"]
    geminiAPIKey := envVars["GEMINI_API_KEY"]
    if googleAPIKey != "" && geminiAPIKey != "" {
        log.Printf("Warning: Both GOOGLE_API_KEY and GEMINI_API_KEY are set. Using GOOGLE_API_KEY.")
    }
    if googleAPIKey != "" {
        return googleAPIKey
    }
    return geminiAPIKey
}

CI環境で他のGoogle系サービス用に GOOGLE_API_KEY を設定していると、GEMINI_API_KEY に正しいキーを入れたつもりでも別のキーが使われて401になります。安全なのは、どちらか一方だけを定義しておくことです。

GenerateContentの最小コード

テキスト生成の最小構成は次のとおりです。genai.Text() は文字列を []*genai.Content に包むヘルパー、resp.Text() は候補の先頭からテキストを取り出すヘルパーです。

package main

import (
    "context"
    "fmt"
    "log"

    "google.golang.org/genai"
)

func main() {
    ctx := context.Background()

    // APIKey を省略すると GOOGLE_API_KEY、無ければ GEMINI_API_KEY を読む
    client, err := genai.NewClient(ctx, &genai.ClientConfig{
        Backend: genai.BackendGeminiAPI,
    })
    if err != nil {
        log.Fatalf("client init: %v", err)
    }

    resp, err := client.Models.GenerateContent(ctx,
        "gemini-2.5-flash",
        genai.Text("Goの並行処理をエンジニア向けに3行で説明して"),
        nil,
    )
    if err != nil {
        log.Fatalf("generate: %v", err)
    }
    fmt.Println(resp.Text())
}

Backend には BackendUnspecified・BackendGeminiAPI・BackendVertexAI・BackendEnterprise の4つが定義されています。ゼロ値は BackendUnspecified、つまり Backend を書かなかった場合の既定値です。このとき環境変数 GOOGLE_GENAI_USE_VERTEXAI が 1 か true なら Vertex AI、それ以外なら Gemini API が選ばれます。BackendEnterprise はクライアント生成時に BackendVertexAI へ正規化される別名なので、実際に到達するバックエンドは2系統です。Google Cloud側の認証を使う場合のみ Vertex AI 系を選び、APIキー1本で動かすなら BackendGeminiAPI を明示してください。

Gemini APIのエンドポイントURL構造

generateContentとstreamGenerateContentのURL

SDKが内部で組み立てるURLは、ベースURL・APIバージョン・リソースパスの3要素です。go-genai のソース上では、Gemini APIバックエンドのベースURLが https://generativelanguage.googleapis.com/、APIバージョンの既定値が v1beta、リソースパスが {model}:generateContent と定義されており、モデル名には models/ 接頭辞が自動で付きます。組み上がる完全なURLは次の形です。

用途 メソッド URL
一括生成 POST https://generativelanguage.googleapis.com/v1beta/models/{model}:generateContent
ストリーミング POST https://generativelanguage.googleapis.com/v1beta/models/{model}:streamGenerateContent?alt=sse
Interactions API POST https://generativelanguage.googleapis.com/v1beta2/interactions
Vertex AI経由 POST https://{location}-aiplatform.googleapis.com/v1beta1/…

認証はクエリパラメータではなくヘッダで渡すのが現行の作法です。SDKも x-goog-api-key ヘッダにAPIキーをセットしています。URLに ?key= を付ける旧来の書き方はログやリファラにキーが残るため避けてください。

net/httpだけで叩く最小クライアント

SDKを入れずに標準ライブラリだけで完結させたい場合、必要なのは構造体2つとPOST1回です。バッチ処理やLambda相当の環境でバイナリサイズを抑えたいときはこちらが有利になります。

package main

import (
    "bytes"
    "context"
    "encoding/json"
    "fmt"
    "log"
    "net/http"
    "os"
    "time"
)

const endpoint = "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent"

type genReq struct {
    Contents []content `json:"contents"`
}
type content struct {
    Parts []part `json:"parts"`
}
type part struct {
    Text string `json:"text"`
}
type genResp struct {
    Candidates []struct {
        Content content `json:"content"`
    } `json:"candidates"`
}

func main() {
    body, err := json.Marshal(genReq{Contents: []content{{Parts: []part{{Text: "ping"}}}}})
    if err != nil {
        log.Fatal(err)
    }

    ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
    defer cancel()

    req, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint, bytes.NewReader(body))
    if err != nil {
        log.Fatal(err)
    }
    req.Header.Set("Content-Type", "application/json")
    req.Header.Set("x-goog-api-key", os.Getenv("GEMINI_API_KEY"))

    resp, err := http.DefaultClient.Do(req)
    if err != nil {
        log.Fatal(err)
    }
    defer resp.Body.Close()

    if resp.StatusCode != http.StatusOK {
        log.Fatalf("gemini: status %d", resp.StatusCode)
    }
    var out genResp
    if err := json.NewDecoder(resp.Body).Decode(&out); err != nil {
        log.Fatal(err)
    }
    if len(out.Candidates) == 0 || len(out.Candidates[0].Content.Parts) == 0 {
        log.Fatal("gemini: no content (safety filter などで空になる)")
    }
    fmt.Println(out.Candidates[0].Content.Parts[0].Text)
}

末尾の空チェックを省くと、安全性フィルタでブロックされた応答を受けたときに index out of range でpanicします。HTTPステータスは200で返るため、ステータスコードの確認だけでは防げません。候補が0件になるパターンと、候補は1件返るのに Parts が空になるパターンの両方があるので、条件は2つ必要です。

GA済みInteractions APIとGo SDK未対応というギャップ

2026年8月時点で、Gemini APIの公式ドキュメントは全ページ共通のバナーで「Interactions APIが一般提供(GA)になった。最新の機能とモデルを使うにはこのAPIを推奨する」と告知しています。移行ガイドも「generateContent は引き続き完全にサポートされるが、新規開発にはInteractions APIを推奨する」と明言しています。

問題は、この推奨がGoにそのまま適用できない点です。公式のGetting startedページは「Interactions APIはPythonとJavaScriptのSDK、およびRESTで利用できる」と対応言語を明示しており、Goは含まれていません。同ページの全文に google.golang.org/genai という文字列は一度も登場しません。移行ガイドのサンプルもPython・JavaScript・RESTの3種類だけです。go-genai v1.66.0 の Client 構造体が公開しているサービスは Models・Chats・Files・Batches など10種ですが、そのなかにInteractionsに相当するサービスはありません。

したがってGoの実装判断は明確です。会話履歴のサーバー側保持や複数ステップのツール実行が要件でないなら、当面は GenerateContent のままで問題ありません。SDKの型と再試行・認証の面倒を捨ててまでInteractions APIへ移る理由は、Go単体では現時点で薄いからです。逆に、履歴をアプリ側のDBに持たせたくない場合は自前で叩く価値があります。Interactions APIは previous_interaction_id を渡すだけでサーバー側が文脈を引き継ぐため、contents 配列を毎回組み直すコードが不要になります。ただし引き継がれるのは入出力の履歴だけです。tools・system_instruction・generation_config はインタラクション単位の指定なので、継続ターンでも毎回送り直す必要があります。

package main

import (
    "bytes"
    "context"
    "encoding/json"
    "fmt"
    "io"
    "log"
    "net/http"
    "os"
    "time"
)

const interactionsURL = "https://generativelanguage.googleapis.com/v1beta2/interactions"

type interactionReq struct {
    Model                 string `json:"model"`
    Input                 string `json:"input"`
    PreviousInteractionID string `json:"previous_interaction_id,omitempty"`
}

type interactionResp struct {
    ID     string `json:"id"`
    Status string `json:"status"`
    Steps  []struct {
        Type    string `json:"type"`
        Status  string `json:"status"`
        Content []struct {
            Type string `json:"type"`
            Text string `json:"text"`
        } `json:"content"`
    } `json:"steps"`
}

func (r interactionResp) outputText() string {
    var buf bytes.Buffer
    for _, s := range r.Steps {
        if s.Type != "model_output" {
            continue
        }
        for _, c := range s.Content {
            if c.Type == "text" {
                buf.WriteString(c.Text)
            }
        }
    }
    return buf.String()
}

func createInteraction(ctx context.Context, in interactionReq) (interactionResp, error) {
    var out interactionResp
    body, err := json.Marshal(in)
    if err != nil {
        return out, err
    }
    req, err := http.NewRequestWithContext(ctx, http.MethodPost, interactionsURL, bytes.NewReader(body))
    if err != nil {
        return out, err
    }
    req.Header.Set("Content-Type", "application/json")
    req.Header.Set("x-goog-api-key", os.Getenv("GEMINI_API_KEY"))

    resp, err := http.DefaultClient.Do(req)
    if err != nil {
        return out, err
    }
    defer resp.Body.Close()
    if resp.StatusCode != http.StatusOK {
        b, _ := io.ReadAll(resp.Body)
        return out, fmt.Errorf("interactions: status %d: %s", resp.StatusCode, b)
    }
    return out, json.NewDecoder(resp.Body).Decode(&out)
}

func main() {
    ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
    defer cancel()

    first, err := createInteraction(ctx, interactionReq{
        Model: "gemini-3.6-flash",
        Input: "私の名前は山田です",
    })
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(first.outputText())

    // 履歴はサーバー側が保持する。contents 配列を組み直す必要はない
    second, err := createInteraction(ctx, interactionReq{
        Model:                 "gemini-3.6-flash",
        Input:                 "私の名前は?",
        PreviousInteractionID: first.ID,
    })
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(second.outputText())
}

レスポンスは generateContent の candidates 構造ではなく、実行ステップが時系列で並ぶ steps 配列です。ここで取り違えやすいのが、POSTの応答に入るのは model_output や function_call といったモデル側のステップだけという点です。ユーザー入力を含む全履歴が欲しい場合は GET /v1beta2/interactions/{id} を呼びます。テキストの取り出しは model_output 型のステップだけを走査すれば足ります。

なお steps 配列は2026年5月の破壊的変更で旧 outputs 配列を置き換えたものです。旧スキーマは2026年6月8日に削除され、同時に response_mime_type も response_format へ統合されました。移行期は Api-Revision リクエストヘッダでスキーマを固定できますが、これから書くコードは新スキーマ前提で構いません。SDKが対応していない以上この変換は自作するしかないので、上記のような小さなアダプタ関数に閉じ込めておくと、後日SDKが対応したときの差し替えが1箇所で済みます。

モデルIDの選定と2026年8月時点の提供状況

モデルIDは文字列で渡すため、存在しないIDを書いてもコンパイルは通り、実行時に404で落ちます。公式の廃止情報ページから、シャットダウン日が未告知=当面使える安定版を抜き出すと次のとおりです。

モデルID リリース日 シャットダウン日(最短) 推奨移行先
gemini-3.6-flash 2026-07-21 未告知 –
gemini-3.5-flash 2026-05-19 未告知 –
gemini-3.5-flash-lite 2026-07-21 未告知 –
gemini-2.5-pro 2025-06-17 未告知 –
gemini-2.5-flash 2025-06-17 未告知 –
gemini-2.5-flash-lite 2025-07-22 未告知 –
gemini-3.1-flash-lite 2026-05-07 2027-05-07 gemini-3.5-flash-lite
gemini-2.0-flash 2025-02-05 2026-06-01(経過済み) gemini-3.6-flash
gemini-2.0-flash-lite 2025-02-25 2026-06-01(経過済み) gemini-3.1-flash-lite

実装の起点としては、SDK公式サンプルが既定値に採用している gemini-2.5-flash が扱いやすく、レスポンス構造の互換情報も揃っています。最新世代の推論性能が要るなら gemini-3.6-flash へ切り替えます。注意すべきは gemini-2.0-flash 系です。公式が示すシャットダウン日2026年6月1日をすでに過ぎています。この日付は公式ページが明記するとおり「廃止されうる最も早い日」で確定日ではないため、現時点でまだ応答が返る可能性はありますが、いつ停止されてもおかしくない状態です。古いサンプルからこのモデルIDをそのまま写すのは避けてください。世代ごとの位置づけを整理したい場合はGemini 3のラインアップと3.1への進化、API単価まで含めて比較するならGemini 3.1 Proの料金とAPI単価が参考になります。

ストリーミング応答のrange-over-func受信

長文生成で体感待ち時間を減らすなら、GenerateContentStream を使います。戻り値は iter.Seq2[*genai.GenerateContentResponse, error] で、Go 1.23で入ったrange-over-funcにそのまま渡せます。旧SDKのイテレータのように iterator.Done を判定するコードは不要になりました。

stream := client.Models.GenerateContentStream(ctx,
    "gemini-2.5-flash",
    genai.Text("Goのcontextパッケージの使いどころを箇条書きで"),
    nil,
)
for chunk, err := range stream {
    if err != nil {
        log.Fatalf("stream: %v", err)
    }
    fmt.Print(chunk.Text())
}

ループ内で break すると、SDK側は残りのレスポンスを読まずに接続を閉じます。ただし打ち切り時点までに生成されたトークンは課金対象になり得るため、途中打ち切りをコスト削減策として当てにしないでください。削るなら入力側です。トークン量を抑える考え方はコンテキストウィンドウのトークン上限と溢れ対策にまとめてあります。

goroutine並行呼び出しとレート制限の設計

Goを選ぶ理由の多くは並行処理です。ただしGemini API側のレート制限はRPM(1分あたりリクエスト数)・TPM(1分あたり入力トークン数)・RPD(1日あたりリクエスト数)の3軸で、いずれか1つを超えた時点でエラーになります。しかも制限はAPIキー単位ではなくプロジェクト単位なので、キーを複数発行して回避することはできません。RPDは太平洋時間の深夜にリセットされます。

したがって、スライスの要素数だけgoroutineを起動する素朴な実装は避けてください。同時実行数を固定する形にします。errgroup.SetLimit を使うと、ワーカープールを自前で書かずに上限を宣言できます。errgroup は標準ライブラリではないため go get golang.org/x/sync が別途必要です。

func summarizeAll(ctx context.Context, client *genai.Client, docs []string, limit int) ([]string, error) {
    results := make([]string, len(docs))

    g, ctx := errgroup.WithContext(ctx)
    g.SetLimit(limit) // RPM を超えないよう同時実行数を固定する

    for i, doc := range docs {
        g.Go(func() error {
            resp, err := client.Models.GenerateContent(ctx,
                "gemini-2.5-flash",
                genai.Text("次の文書を1行で要約して:\n"+doc),
                nil,
            )
            if err != nil {
                return fmt.Errorf("doc %d: %w", i, err)
            }
            results[i] = resp.Text() // インデックス指定なので排他制御は不要
            return nil
        })
    }
    if err := g.Wait(); err != nil {
        return nil, err
    }
    return results, nil
}

ループ変数 i・doc をクロージャ内でそのまま参照できるのは、Go 1.22でループ変数がイテレーションごとに新しい変数になったためです。go.mod の go ディレクティブが1.21以前のモジュールでは従来の共有セマンティクスが適用され、全goroutineが最後の要素を見る古典的なバグに戻ります。go-genai は go 1.24 を要求するので通常は問題になりませんが、既存モジュールへ後付けする場合は go.mod の宣言を確認してください。

同時実行数の具体値については、公式のレート制限ページにモデル別のRPM実数値の表は掲載されておらず、AI StudioのRate Limitページで各自の値を確認する案内になっています。ネット上の「無料枠は15 RPM」といった数値は世代ごとに変わるため、実装時は自分のプロジェクトの表示値を参照してください。加えて、支出ベースの制限が10分のローリング窓で別途かかります(Freeは対象外、Tier 1は10ドル、Tier 2と3は200ドル)。この上限に触れると 429 RESOURCE_EXHAUSTED が返るため、並行数を上げるほど詰まりやすくなります。

APIErrorの判定と再試行の実装

Gemini APIのエラーは、SDKが genai.APIError という構造体で返します。フィールドは Code(HTTPステータス)・Message・Status・Details の4つです。

ここに errors.As の落とし穴があります。SDKの内部実装は APIError{...} を値型で返しており、ポインタでは返しません。そのため var apiErr *genai.APIError のようにポインタ型で受けると errors.As は常に false を返し、リトライ判定が丸ごと無効化されます。値型で受けるのが正解です。

func generateWithRetry(ctx context.Context, client *genai.Client, prompt string) (string, error) {
    backoff := time.Second
    for attempt := 0; attempt < 4; attempt++ {
        resp, err := client.Models.GenerateContent(ctx, "gemini-2.5-flash", genai.Text(prompt), nil)
        if err == nil {
            return resp.Text(), nil
        }

        // APIError は値型で返る。*genai.APIError で受けると errors.As が一致しない
        var apiErr genai.APIError
        if !errors.As(err, &apiErr) {
            return "", err
        }
        if apiErr.Code != 429 && apiErr.Code != 503 {
            return "", fmt.Errorf("gemini %d %s: %s", apiErr.Code, apiErr.Status, apiErr.Message)
        }
        select {
        case <-ctx.Done():
            return "", ctx.Err()
        case <-time.After(backoff):
        }
        backoff *= 2
    }
    return "", errors.New("gemini: retry limit exceeded")
}

再試行の対象は429(レート制限・クォータ超過)と503(一時的な過負荷)に限定します。400番台の入力エラーや403の権限エラーは何度投げても同じ結果になるため、即座に返して呼び出し元にエラーを伝えるべきです。再試行しない判断を明示的に書いておくと、無駄な待ち時間とトークン消費を避けられます。503が継続する場合の切り分け手順は503エラーの原因切り分けと復旧が実務の手順として使えます。

よくある質問

Q. Gemini APIのエンドポイントURLは何ですか。
A. テキスト生成は https://generativelanguage.googleapis.com/v1beta/models/{model}:generateContent です。ストリーミングは末尾を :streamGenerateContent?alt=sse に変えます。Interactions APIだけはバージョンが異なり /v1beta2/interactions になります。認証は x-goog-api-key ヘッダで渡します。

Q. generative-ai-go はまだ使えますか。
A. パッケージ自体は公開されたままなので go get は通りますが、サポートは2025年11月30日で終了しており、最終リリースは2025年5月のv0.20.1です。なおEOL日は当初の2025年8月31日から二度延長された経緯があり、タグ時点のREADMEを引用している記事は古い日付のままになっています。いずれにせよ新しいモデルや機能は反映されないため、新規実装で選ぶ理由はありません。google.golang.org/genai へ移行してください。

Q. Goで指定するモデルIDはどれを選べばよいですか。
A. 迷うなら gemini-2.5-flash から始めるのが無難です。SDK公式サンプルの既定値であり、シャットダウン日も未告知です。最新世代が必要なら gemini-3.6-flash を指定します。gemini-2.0-flash 系はシャットダウン日の2026年6月1日を過ぎており、いつ停止されてもおかしくないため選ばないでください。

Q. APIキーは GOOGLE_API_KEY と GEMINI_API_KEY のどちらを設定すべきですか。
A. どちらか一方だけを設定してください。両方が設定されている場合、SDKは警告ログを出したうえで GOOGLE_API_KEY を優先します。他のGoogle系APIと環境変数を共用している環境では、意図しないキーが使われて認証エラーになる原因になります。

Q. goroutineで並列にリクエストしても問題ありませんか。
A. クライアントは並行利用できますが、レート制限がプロジェクト単位でかかるため同時実行数の上限は必ず設けてください。errgroup.SetLimit で上限を固定し、429と503はエクスポネンシャルバックオフで再試行する構成が基本です。

関連記事

お気に入りに入れた記事の一覧

この記事は以下の記事からリンクされています

資料請求

今日のトレンド記事 直近 24 時間で、いつもより多く読まれている記事

  1. 2026.07.21 テックブログ Apache Tomcatの脆弱性一覧【2026年10月】最新版11.0.27・10.1.61・9.0.123とCVE-2026-59083ほかの影響確認・対応手順
  2. 2026.10.21 テックブログ NEC委託先の不正アクセスとは|顧客システム暗号化・情報漏えいの経緯と委託元が点検する正規アカウント
  3. 2026.10.21 テックブログ ふるさとチョイス不正アクセスで445万件流出:対象確認・パスワード変更と再開時期
  4. 2026.10.15 テックブログ 銀のさら不正アクセス:最大650万件の対象確認と窓口、パスワード変更・退会の手順
  5. 2024.07.05 コラム XAMPPの脆弱性とは?危険性とCVE-2024-4577への対策をわかりやすく解説【2026年10月】

RELATED POSTS 関連記事

目次