Skip to content

Gemini API 接続:Google SDK をローカルゲートウェイに直接接続 ​

学習後、できること ​

  • Antigravity Tools が公開する Gemini ネイティブエンドポイント(/v1beta/models/*)を使用してクライアントを接続する
  • Google スタイルの :generateContent / :streamGenerateContent パスを使用してローカルゲートウェイを呼び出す
  • Proxy 認証を有効にした場合、x-goog-api-key が直接使用できる理由を理解する

現在の課題 ​

ローカルリバースプロキシは既に実行しましたが、Gemini に行き詰まります:

  • Google SDK はデフォルトで generativelanguage.googleapis.com を叩き、自分の http://127.0.0.1:<port> にどう変更すればよいか不明
  • Gemini のパスにはコロンが含まれ(models/<model>:generateContent)、多くのクライアントが連結すると 404 になる
  • プロキシ認証を有効にしたが、Google クライアントは x-api-key を送信しないため、常に 401 になる

いつこの手法を使うか ​

  • 「Gemini ネイティブプロトコル」を使用したい場合、OpenAI/Anthropic 互換層ではない
  • 手元に Google/サードパーティの Gemini スタイルクライアントがあり、最小コストでローカルゲートウェイに移行したい場合

🎒 始める前の準備 ​

前提条件

  • アプリに少なくとも1つのアカウントを追加済み(そうしないとバックエンドが上流 access token を取得できない)
  • ローカルリバースプロキシサービスを起動済み、リスニングポートを知っている(デフォルトは 8045)

コアな考え方 ​

Antigravity Tools はローカル Axum サーバー上で Gemini ネイティブパスを公開しています:

  • リスト:GET /v1beta/models
  • 呼び出し:POST /v1beta/models/<model>:generateContent
  • ストリーミング:POST /v1beta/models/<model>:streamGenerateContent

バックエンドは Gemini ネイティブリクエストボディを v1internal 構造でラップし(project、requestId、requestType などを注入)、Google の v1internal 上流エンドポイントに転送します(アカウント access token を添付)。(ソースコード:src-tauri/src/proxy/mappers/gemini/wrapper.rs、src-tauri/src/proxy/upstream/client.rs)

なぜチュートリアルの base URL は 127.0.0.1 を推奨するのか?

アプリのクイック統合例では 127.0.0.1 を推奨として固定しています。理由は「一部環境の IPv6 解析遅延問題を回避するため」。(ソースコード:src/pages/ApiProxy.tsx)

実践してみましょう ​

ステップ1:ゲートウェイがオンラインであることを確認する(/healthz) ​

なぜ まずサービスがオンラインであることを確認し、その後でプロトコル/認証問題をトラブルシューティングすると時間を節約できます。

bash
curl -s "http://127.0.0.1:8045/healthz"
powershell
Invoke-RestMethod "http://127.0.0.1:8045/healthz"

次のように見えるはずです:JSON が返され、{"status":"ok"} が含まれる(ソースコード:src-tauri/src/proxy/server.rs)。

ステップ2:Gemini モデルを一覧表示する(/v1beta/models) ​

なぜ まず「外部に公開されるモデル ID」が何かを確認する必要があり、後の <model> はこれに依存します。

bash
curl -s "http://127.0.0.1:8045/v1beta/models" | head

次のように見えるはずです:レスポンスに models 配列が含まれ、各要素の name は models/<id> のような形式です(ソースコード:src-tauri/src/proxy/handlers/gemini.rs)。

重要

モデル ID にどのフィールドを使用するか?

  • ✅ displayName フィールドを使用する(例:gemini-2.0-flash)
  • ✅ または name フィールドから models/ プレフィックスを削除
  • ❌ name フィールドの完全な値を直接コピーしない(パスエラーになる)

name フィールドをコピーした場合(例:models/gemini-2.0-flash)をモデル ID として使用すると、リクエストパスが /v1beta/models/models/gemini-2.0-flash:generateContent になり、これは間違っています。(ソースコード:src-tauri/src/proxy/common/model_mapping.rs)

重要

現在の /v1beta/models は「ローカル動的モデルリストを Gemini models リストに偽装して返す」実装で、上流からリアルタイムで取得するものではありません。(ソースコード:src-tauri/src/proxy/handlers/gemini.rs)

ステップ3:generateContent を呼び出す(コロンを含むパス) ​

なぜ Gemini ネイティブ REST API の要点は :generateContent のような「コロンを含む action」です。バックエンドは同じルートで model:method を解析します。(ソースコード:src-tauri/src/proxy/handlers/gemini.rs)

bash
curl -s \
  -H "Content-Type: application/json" \
  -X POST "http://127.0.0.1:8045/v1beta/models/<modelId>:generateContent" \
  -d '{
    "contents": [
      {"role": "user", "parts": [{"text": "こんにちは"}]}
    ]
  }'

次のように見えるはずです:レスポンス JSON に candidates が含まれる(または外層に response.candidates があり、プロキシはアンラップする)。

ステップ4:streamGenerateContent を呼び出す(SSE) ​

なぜ ストリーミングは「長い出力/大モデル」でより安定しています。プロキシは上流 SSE をクライアントに転送し、Content-Type: text/event-stream を設定します。(ソースコード:src-tauri/src/proxy/handlers/gemini.rs)

bash
curl -N \
  -H "Content-Type: application/json" \
  -X POST "http://127.0.0.1:8045/v1beta/models/<modelId>:streamGenerateContent" \
  -d '{
    "contents": [
      {"role": "user", "parts": [{"text": "短い話を教えて"}]}
    ]
  }'

次のように見えるはずです:ターミナルが継続的に data: {...} 形式の SSE 行を出力し、正常な場合は最後に data: [DONE] が表示されます(ストリーム終了を示す)。

注意

data: [DONE] は SSE の標準終了マークですが、必ずしも表示されません:

  • 上流が正常に終了し [DONE] を送信した場合、プロキシはそれを転送します
  • 上流が異常切断、タイムアウト、または他の終了信号を送信した場合、プロキシは [DONE] を補送しません

クライアントコードは SSE 標準に従って処理する必要があります:data: [DONE] または接続切断が発生したら、ストリーム終了とみなします。(ソースコード:src-tauri/src/proxy/handlers/gemini.rs)

ステップ5:Python Google SDK を使用してローカルゲートウェイに直接接続する ​

なぜ これはプロジェクト UI で提供される「クイック統合」例パスです:Google Generative AI Python パッケージを使用して api_endpoint をローカルリバースプロキシアドレスに向ける。(ソースコード:src/pages/ApiProxy.tsx)

python
#インストールが必要: pip install google-generativeai
import google.generativeai as genai

genai.configure(
    api_key="YOUR_PROXY_API_KEY",
    transport='rest',
    client_options={'api_endpoint': 'http://127.0.0.1:8045'}
)

model = genai.GenerativeModel('<modelId>')
response = model.generate_content("こんにちは")
print(response.text)

次のように見えるはずです:プログラムがモデル返信テキストを出力します。

チェックポイント ✅ ​

  • /healthz が {"status":"ok"} を返す
  • /v1beta/models がモデルを一覧表示できる(少なくとも1つ)
  • :generateContent が candidates を返す
  • :streamGenerateContent が Content-Type: text/event-stream を返し、継続的に出力できる

よくある落とし穴 ​

  • 401 がずっと通らない:認証を有効にしたが、proxy.api_key が空の場合、バックエンドはリクエストを直接拒否します。(ソースコード:src-tauri/src/proxy/middleware/auth.rs)
  • Header にどの key を持たせるか:プロキシは Authorization、x-api-key、x-goog-api-key を同時に認識します。したがって、「Google スタイルクライアントが x-goog-api-key のみを送信する」でも通過できます。(ソースコード:src-tauri/src/proxy/middleware/auth.rs)
  • countTokens 結果が常に 0:現在の POST /v1beta/models/<model>/countTokens は固定 {"totalTokens":0} を返し、プレースホルダー実装です。(ソースコード:src-tauri/src/proxy/handlers/gemini.rs)

この授業のまとめ ​

  • 接続するのは /v1beta/models/* であり、/v1/* ではない
  • 重要なパスの書き方は models/<modelId>:generateContent / :streamGenerateContent
  • 認証を有効にした場合、x-goog-api-key はプロキシが明確にサポートするリクエストヘッダー

次の授業の予告 ​

次の授業では、Imagen 3 画像生成:OpenAI Images パラメータ size/quality の自動マッピング を学習します。


付録:ソースコード参照 ​

クリックしてソースコードの場所を展開

更新日時:2026-01-23

機能ファイルパス行番号
Gemini ルーティング登録(/v1beta/models/*)src-tauri/src/proxy/server.rs170-181
モデル ID 解析とルーティング(なぜ models/ プレフィックスがルーティングエラーになるか)src-tauri/src/proxy/common/model_mapping.rs58-77
model:method 解析 + generate/stream メインロジックsrc-tauri/src/proxy/handlers/gemini.rs14-337
SSE 出力ロジック([DONE] を転送し、自動補送しない)src-tauri/src/proxy/handlers/gemini.rs161-183
/v1beta/models 返り構造(動的モデルリスト偽装)src-tauri/src/proxy/handlers/gemini.rs39-71
countTokens プレースホルダー実装(固定 0)src-tauri/src/proxy/handlers/gemini.rs73-79
---------
Google SDK Python 例(api_endpoint をローカルゲートウェイに向ける)src/pages/ApiProxy.tsx692-734
Gemini セッションフィンガープリント(スティッキ/キャッシュ用 session_id)src-tauri/src/proxy/session_manager.rs121-158
Gemini リクエスト v1internal ラッピング(project/requestId/requestType などを注入)src-tauri/src/proxy/mappers/gemini/wrapper.rs5-160
上流 v1internal エンドポイントと fallbacksrc-tauri/src/proxy/upstream/client.rs8-182

重要な定数:

  • MAX_RETRY_ATTEMPTS = 3:Gemini リクエスト最大ローテーション回数上限(ソースコード:src-tauri/src/proxy/handlers/gemini.rs)