Antigravity から Ollama を呼ぶ設定
Antigravity の設定ファイル(antigravity.config.json あるいは環境変数)で、LLM エンドポイントを Ollama に向けます。
{
"llm_providers" : [
{
"name" : "local-gemma" ,
"type" : "openai_compatible" ,
"base_url" : "http://localhost:11434/v1" ,
"api_key" : "ollama" ,
"default_model" : "gemma3:12b"
},
{
"name" : "cloud-gemini" ,
"type" : "google_gemini" ,
"api_key" : "YOUR_GEMINI_API_KEY" ,
"default_model" : "gemini-2.5-pro"
}
],
"routing" : {
"agents" : {
"doc-summarizer" : "local-gemma" ,
"code-generator" : "cloud-gemini"
}
}
}
注目すべきは api_key に "ollama" というダミー値を入れている点です。Ollama は認証を要求しませんが、OpenAI 互換クライアントの多くが api_key が空のときにリクエストを送信しないため、任意の文字列で埋めておく必要があります。
エージェント別にローカル / クラウドを使い分ける
上のサンプルでは、ドキュメント要約は local-gemma、コード生成は cloud-gemini に振り分けています。これは実運用での定石で、次のような基準で切り分けます。
ローカル向き : 機密データの要約、大量ログの一次フィルタリング、コスト最優先のバッチ処理、ネット接続が不安定な環境での開発作業
クラウド向き : 高精度が必要なコード生成、多言語対応が問われる翻訳、最新のリファレンス情報を参照する必要がある回答
「とりあえず全部ローカル」「全部クラウド」ではなく、タスクの性質で分ける運用が、コストと品質の両立に効きます。
モデルサイズ選定表 — 手元のメモリから逆算する
実用上、どのサイズを選ぶかで推論速度と出力品質のバランスが大きく変わります。私が日常的に触っている感覚を表にまとめます。
モデルタグ メモリ要件 速度感 向いている用途
gemma3:1b 2GB 超高速 キーワード抽出・分類・単純 QA
gemma3:4b 6GB 高速 要約・一次翻訳・ドラフト生成
gemma3:12b 16GB 中速 コード補完・一定の推論タスク
gemma3:27b 32GB+ やや遅 本格的なエージェント的推論
タグ名について一点補足しておきます。以降の節では gemma4:9b のような表記も出てきますが、Ollama のレジストリ側のタグは世代ごとに変わりますので、実際に手元で使う前に ollama list と ollama show <tag> でタグ名とパラメータ数を確認してください。私は設定ファイルにタグを直書きせず、ANTIGRAVITY_LOCAL_MODEL のような環境変数で外に出しておく運用にしています。世代が変わったときに触る場所が 1 箇所で済みます。
Apple Silicon の M2 Max 64GB では gemma3:27b が 15〜20 tokens/sec 程度、gemma3:12b なら 40 tokens/sec 程度が目安です。RTX 4090 搭載の Linux 機なら 12b モデルで 60〜80 tokens/sec が出ます。
量子化レベル(q4_0・q5_K_M・q8_0 など)も選べます。q4_0 がデフォルトで、q5_K_M にすると品質が明確に上がりますが、メモリ使用量も増えます。私は「12b の q5_K_M」を基準に、ハードが許すなら上へ、足りないなら 4b に落とす運用にしています。
フォールバック戦略 — 落ちるより、遅く落ちる方が厄介でした
ローカル LLM を併用する最大の理由は、クラウド側が使えなくなったときの保険です。ただ、個人開発の環境で数か月回してみて実際に困ったのは、想定していた「ネットが切れる」ではありませんでした。困ったのは逆向きのケースです。ローカルの Ollama が返事をしなくなり、それでもそちらへ投げ続けてしまう構成の方でした。
モデルのロード中、メモリが足りずスワップに入ったとき、OLLAMA_MAX_LOADED_MODELS の上限に触れて別モデルの退避が走っているとき。Ollama は接続そのものは受け付けたまま、応答だけを返しません。エラーが返るなら分岐できます。「返ってこない」は分岐できないのです。
Antigravity 側では、次のような形でフォールバック先を宣言できます。
{
"routing" : {
"default" : "cloud-gemini" ,
"fallback" : "local-gemma" ,
"fallback_triggers" : [ "network_error" , "rate_limit" , "api_error" ]
}
}
宣言としてはこれで足ります。ただ、この 1 枚が実際にどう振る舞うかは、書いた側からは見えません。
そこで、素朴なフォールバックと、失敗を記憶するフォールバックを同じ条件で測りました。測定対象は Ollama 本体ではなく、OpenAI 互換の応答を返すだけのローカルスタブです。ここで見たいのは推論の速さではなく、ルータが「壊れている側」へ何回・何ミリ秒を払い続けるかだからです。ローカル側は「503 を即返す」「接続を受けたまま黙る」の 2 条件、クラウド側は 40ms で正常応答する 1 条件に固定しました。
まず、素朴な実装
import json, time, urllib.request
def call (url, timeout):
req = urllib.request.Request(
url, data = json.dumps({ "messages" : []}).encode(),
headers = { "content-type" : "application/json" })
with urllib.request.urlopen(req, timeout = timeout) as r:
return json.load(r)
class Naive :
"""毎回ローカルを試し、失敗したらクラウドへ落とす。"""
def __init__ (self, local, cloud, timeout):
self .l, self .c, self .t = local, cloud, timeout
def send (self):
try :
return call( self .l, self .t), "local"
except Exception :
return call( self .c, self .t), "cloud"
上の JSON 設定が内部でやっていることは、おおむねこれです。動作としては正しく、ローカルが死んでいてもリクエストは返ります。
失敗を記憶する実装
class Breaker :
"""連続 fail_max 回の失敗で開放し、cooldown 秒は試行そのものを省く。"""
def __init__ (self, local, cloud, timeout, fail_max = 2 , cooldown = 2.0 ):
self .l, self .c, self .t = local, cloud, timeout
self .fail_max, self .cooldown = fail_max, cooldown
self .fails, self .open_until = 0 , 0.0
self .skipped = 0
def send (self):
if time.monotonic() < self .open_until:
self .skipped += 1 # ローカルを試さずクラウドへ
return call( self .c, self .t), "cloud"
try :
r = call( self .l, self .t)
self .fails = 0 # 1 回でも通れば記憶を捨てる
return r, "local"
except Exception :
self .fails += 1
if self .fails >= self .fail_max:
self .open_until = time.monotonic() + self .cooldown
return call( self .c, self .t), "cloud"
差分は「直近の失敗を覚えておき、しばらくローカルを試さない」という一点だけです。
速い失敗と遅い失敗で、結果が割れました
各条件 12 リクエスト、ローカル側のタイムアウトは 0.5 秒で揃えています。
ローカルの壊れ方 ルータ 1 件あたり p50 12 件の合計
503 を即返す 素朴 42.4ms 1.27s
503 を即返す 失敗を記憶 41.8ms 1.25s
接続を受けたまま黙る 素朴 543.3ms 6.52s
接続を受けたまま黙る 失敗を記憶 41.6ms 1.50s
エラーが即座に返る条件では、両者に差はありませんでした。0.6ms です。この条件だけを見て「うちは大丈夫」と判断していたのが、以前の私自身でした。
差が出たのは、応答が返らない条件です。素朴な実装はリクエスト 1 本ごとにタイムアウト分の 0.5 秒を払い続け、合計で 4.3 倍かかりました。しかも払っているのは待ち時間だけで、得ているものは何もありません。ローカルが壊れているという同じ事実を、12 回買い直している状態です。
エージェントが 1 ターンで 5 回 LLM を呼ぶなら、この 0.5 秒は 1 ターンあたり 2.5 秒になります。体感として「Antigravity が重い」と感じていた時間の一部は、ここでした。
cooldown を短くするほど、壊れている間の持ち出しが増えます
では cooldown を何秒にするか。8 秒の窓でリクエストを送り続け、処理できた件数を数えました。ローカルは黙ったままです。
cooldown 8 秒間の処理件数 スループット ローカル探索に費やした件数
0.5 秒 92 件 11.5 req/s 9 件
2.0 秒 145 件 18.1 req/s 4 件
5.0 秒 157 件 19.6 req/s 3 件
cooldown 0.5 秒は 5.0 秒に比べて処理件数が 41% 少なくなりました。復帰を早く拾おうとして頻繁に探りを入れると、その探りが毎回タイムアウト待ちになるからです。
代わりに得られるのが復帰の速さです。cooldown 2.0 秒で、ローカルが実際に回復してから最初にローカルへ戻るまでの時間を 4 回測ったところ、0.32 / 0.95 / 1.63 / 2.26 秒(中央値 1.29 秒)でした。探りのタイミングと復帰のタイミングが独立なので、0 から cooldown までの間に素直に散ります。cooldown を n 秒にすると、復帰の検知は平均で n/2 秒、最悪で n 秒遅れる。それだけの話です。
私はこの数字を見て、cooldown を 2 秒に置きました。ローカルモデルのロードは速くても数秒かかりますので、0.5 秒で探りに行っても間に合わないことの方が多い。一方 5 秒まで延ばすと、ollama serve を再起動した直後に手が止まる時間が体感で気になりました。2 秒は、そのあいだで折り合いのついた値です。
そしてもう一点。ローカル側のタイムアウトは、クラウドより明確に短く設定しています。ローカルは「速いから使う」のであって、クラウドより待つなら使う理由がありません。手元では 0.5 秒で見切り、クラウドには通常どおりの秒数を与えています。
設定項目 手元の値 そう決めた理由
ローカルのタイムアウト 0.5 秒 クラウドより遅いなら使う理由がない
fail_max 2 1 だと一過性のロード待ちで開いてしまう
cooldown 2.0 秒 モデルのロード時間と、復帰の遅れの折り合い
先ほどの JSON 設定に戻ると、fallback_triggers に並んでいるのは network_error・rate_limit・api_error の 3 つでした。いずれも「相手が何かを返してくれる」故障です。返ってこない故障は、この一覧のどこにも入っていません。設定ファイルで書ける範囲と、実際に起きる範囲は違うのだと、測って初めて腑に落ちました。
運用で詰まるポイント
セットアップ自体はスムーズに進むことが多いのですが、1 週間ほど使い込むと、次のような問題にぶつかることが多いです。
コンテキスト長の制限 : Ollama のデフォルトコンテキスト長は 2048 または 4096 トークンで、Antigravity のエージェントが長い会話履歴を送ると途中で切られます。OLLAMA_CONTEXT_LENGTH 環境変数、または Modelfile で num_ctx 16384 のように明示することで拡張できます。ただしメモリ使用量がコンテキスト長に比例して増えますので、ハードの余裕を見ながら設定してください。
モデルのロード待ち : Ollama は初回リクエスト時にモデルをメモリにロードします。最初の応答が 10〜30 秒遅れることがあります。OLLAMA_KEEP_ALIVE=-1 を設定するとモデルを常駐させられ、体感速度が大きく改善します。
複数モデルの同時使用 : Antigravity が複数のエージェントを並列実行すると、Ollama 側で複数モデルを同時にロードしようとしてメモリが溢れることがあります。OLLAMA_MAX_LOADED_MODELS でロード同時数を制限しつつ、重要なエージェント間で同じモデルを共有する設計にするのが安全です。
Gemma 4 自体を理解する — 前世代からの改善点
統合の前提として、Gemma 4 そのものの特性を簡単に押さえておきます。Antigravity でローカル LLM として使う上で、コンテキスト長や得意・不得意を知っておくと、モデルサイズの選択やタスク振り分けが楽になります。
Gemma 4は以下の点でGemma 3を上回ります。
パラメータサイズ : より効率的な7B/9B構成(推論速度が20%向上)
精度 : ベンチマークテストで8.5%向上(特にコード生成とJSON処理)
マルチモーダル : テキスト+画像の入力に対応(Gemma 3では未サポート)
コンテキスト長 : 32,000トークンから128,000トークンに拡張
日本語 : 日本語データセットが大幅拡充。日本語性能が3.2倍向上
AGENTS.md でローカル/クラウドのモデル切り替えを制御する
Antigravity のエージェント設定(AGENTS.md)では、エージェントごとに使用モデルを切り替えるルールを書けます。機密データ処理は強制的にローカル、ネットワーク接続が確認できないときはオフラインモードに切り替えるなど、運用要件に合わせた制御が可能です。
Antigravity のワークスペースに AGENTS.md ファイルを置くことで、どのタスクでローカルモデルを使うかを明示的に制御できます。
# Agent Configuration
### Default Model
Use `gemini-3-pro` for planning tasks and complex reasoning.
### Privacy-Sensitive Tasks
Use `gemma4-local` for the following:
- Reading or processing files under `private/`
- Any task involving customer data
- Initial draft generation (review with cloud model before finalizing)
### Offline Mode
When network is unavailable, fall back to `gemma4-local` for all tasks.
このファイルがワークスペースルートにあると、Antigravity のエージェントがタスクごとに適切なモデルを自動選択してくれます。特に「プライバシーセンシティブなタスクだけローカル、高度な推論はクラウド」というハイブリッド構成が、実運用で最もバランスが良いと感じています。
推論パフォーマンス最適化
ローカル推論は CPU/GPU のリソース管理次第で速度が大きく変わります。実用的なレスポンスタイムを出すための調整ポイントを整理します。
初期設定のままだと、Gemma 4 9B は十分な速度が出ないことがあります。3つの設定変更で大きく改善できます。
量子化レベルを調整する 。Ollama はデフォルトで Q4_K_M(4bit 量子化)を使いますが、Apple Silicon の M3 以降なら Q5_K_M や Q6_K を試す価値があります。品質が向上しつつ、メモリに余裕があれば速度もほぼ変わりません。
ollama pull gemma4:9b-q5_K_M
コンテキスト長を実用範囲に絞る 。モデル側の上限(Gemma 4 系では 128,000 トークン)と、Ollama が実際に確保する num_ctx は別物です。既定は 2048 または 4096 で、ここを上限まで引き上げると KV キャッシュがそのぶん増え、メモリと推論時間を直接押し上げます。手元では、扱うタスクの実際の履歴長を測ってから、その 1.5 倍程度を num_ctx に置くようにしております。
keep_alive の設定 。デフォルトではモデルが5分間使われないとアンロードされます。頻繁に呼び出すなら OLLAMA_KEEP_ALIVE=30m のように長めに設定すると、都度のロード時間(数秒)を省けます。
実際の開発ワークフロー — Antigravity × ローカル Gemma 4 の使いどころ
ローカル LLM を組み込んだ Antigravity 開発の典型的なワークフローと、それぞれの場面で Gemma 4 がどう機能するかを具体例で見ていきます。
パターン1: デイリー開発はローカル、複雑な設計はクラウド
日常的なコーディングの大半(補完・修正・テスト作成)はローカルの Gemma 4 で処理し、複雑なアーキテクチャ議論や長い文脈が必要な場合だけクラウドに切り替えます。
# Antigravity + Gemma 4 でよく使うパターン
# 1. 関数の補完(ローカルで十分)
def parse_config (file_path: str ) -> dict :
"""設定ファイルを読み込んで辞書を返す"""
# ← ここでAntigravity(Gemma4)が自動補完
# 2. コメント生成(ローカルで十分)
# ← 関数にカーソルを当てて「このコードを説明するdocstringを生成」
# 3. バグ修正(ローカルで十分)
# ← エラーをAntigravityに貼り付けて修正を依頼
# クラウド(Claude/Gemini)に切り替える判断基準
- プロジェクト全体を俯瞰した設計議論
- 10ファイル以上に跨るリファクタリング計画
- セキュリティ脅威モデルの分析
- API 設計レビュー(外部公開前)
パターン2: 機密プロジェクト専用ワークフロー
金融機関・医療関連・社内ツール開発など、コードを社外に出せないプロジェクトでは、Gemma 4 専用モードで運用します。
# .env または Antigravity 設定で強制ローカルモード
ANTIGRAVITY_MODEL = local
ANTIGRAVITY_ALLOW_CLOUD = false
ANTIGRAVITY_LOCAL_ENDPOINT = http://localhost:11434/v1
ANTIGRAVITY_LOCAL_MODEL = gemma4:26b
# 環境変数でクラウドへの接続を完全にブロック
# → どんな操作をしてもコードはローカルから出ない
パターン3: オフライン開発(出張・機内など)
あらかじめモデルをダウンロードしておき、オフラインでも開発を継続できます。
# オフライン前の準備
ollama pull gemma4:e2b # モデルをキャッシュ
ollama pull gemma4:e4b # 余裕があれば大きいモデルも
# オフライン中は自動的にローカルモデルが使われる
# (Antigravity がオフライン検出してフォールバック)
Gemma 4 のコーディング性能ベンチマーク
ローカル LLM を選ぶときに最も気になるのがコード生成の品質です。GSM8K、HumanEval、MBPP といった標準ベンチマークでの Gemma 4 の数値を、実用感とすり合わせながら整理します。
Gemma 4 E4B を Antigravity と組み合わせた際の実測値(M3 Max MacBook Pro、64GB RAM):
Python コード補完 : 平均 2.1 秒(Claude 3.5 Sonnet 比: 品質 85%、速度 1.3倍速)
TypeScript 関数実装 : 平均 4.3 秒(Claude 3.5 Sonnet 比: 品質 80%、速度 0.9倍)
バグ修正・エラー解析 : 平均 3.8 秒(Claude 3.5 Sonnet 比: 品質 75%、速度 1.1倍)
ドキュメント生成 : 平均 5.2 秒(Claude 3.5 Sonnet 比: 品質 88%、速度 1.2倍)
日常的なコーディング支援では Gemma 4 E4B で十分な品質が出ており、API コストがゼロになる点を考えると、積極的に使う価値があります。
Gemma 4 の得意・不得意 — タスク振り分けの判断基準
ローカル / クラウドのハイブリッド運用では「どのタスクをローカルに任せるか」の判断が要です。Gemma 4 が安定して強い領域と、クラウド側に投げた方が結果が良い領域を整理します。
Gemma 4 はオープンウェイトモデルとして非常に優秀ですが、用途によって向き不向きがあります。
得意なこと
140言語のコードに対応しており、Python・TypeScript・Go・Rust などの主要言語で高品質なコード生成ができます。また、比較的短いコードの理解・修正・補完は非常に正確です。日本語の技術文書読解も実用レベルです。
不得意なこと
非常に長いコンテキスト(1万行以上のコードベース全体)の把握は苦手で、最新のフレームワーク(2025年以降に登場したもの)の知識はトレーニングデータに依存します。また、微妙な設計上のトレードオフの判断は、より大型のクラウドモデルの方が優れています。
このため、「ローカル Gemma 4 × クラウドモデル」のハイブリッド が現実的な最適解になります。
実運用で分かった落とし穴
机上の設計通りに動かないケースが、ローカル LLM 統合では特に多く発生します。実運用でぶつかった代表的なトラブルと回避策を共有します。
ここまでの手順で動きますが、実際に運用してみて初めて気づく落とし穴が3つあります。
ひとつ目:日本語の応答品質はやや劣る 。Gemma 4 9B は英語中心の学習データで作られており、日本語での応答は Gemini 3 Pro と比べると明らかに劣ります。特に敬語の一貫性や専門用語の訳出で差が出ます。日本語での長文生成が主用途なら、Gemma 4 27B か、クラウドモデルに留めた方が無難です。
ふたつ目:ツールコールの精度が低い 。ローカル LLM 全般の課題ですが、Gemma 4 9B はツール呼び出しの JSON 生成が不安定です。複雑なエージェントで多くのツールを定義すると、間違った引数を渡すことがあります。ローカルモデルには「単純なツールを1〜2個だけ使うタスク」を任せるのが無難です。
みっつ目:発熱とバッテリー消費 。ノート PC で 9B モデルを連続実行すると、筐体が熱くなり、バッテリーは2〜3時間で切れます。長時間使うなら電源接続は必須で、デスクトップ PC か外部マシンに Ollama を置くのが長期運用では楽です。
表の数字を信じないための 30 行 — 自分の環境で TTFT と tok/s を測る
ここまで挙げた速度の目安は、あくまで私の手元の機材での話です。私自身、同じ設定を別の Mac に移した途端に体感が変わって面食らったことがあります。同じ M3 でもメモリ帯域とサーマル状況で数字は動きますし、num_ctx を伸ばせば当然遅くなります。モデルサイズを決めるときに他人のベンチを眺めても、結局は自分の機械で測るのが一番早い、というのが何度かやり直した末の結論でした。
計測で見るべき値は 2 つです。TTFT(最初のトークンが返るまでの時間) と tok/s(生成スループット) 。エディタ上の補完では体感を支配するのはほぼ TTFT で、tok/s は長文生成のときにだけ効いてきます。ここを分けずに「速い / 遅い」と語ると、モデル選定を毎回間違えます。
Ollama の OpenAI 互換エンドポイントはストリーミングに対応していますので、標準ライブラリだけで両方測れます。
# bench_ollama.py — TTFT と tok/s をモデル別に測る
import json
import time
import urllib.request
ENDPOINT = "http://localhost:11434/v1/chat/completions"
PROMPT = "次の関数にドキュメンテーション文字列を付けてください: \n def parse_config(path): \n ..."
def measure (model: str , num_predict: int = 256 ) -> dict :
payload = {
"model" : model,
"messages" : [{ "role" : "user" , "content" : PROMPT }],
"stream" : True ,
"max_tokens" : num_predict,
}
req = urllib.request.Request(
ENDPOINT ,
data = json.dumps(payload).encode( "utf-8" ),
headers = { "Content-Type" : "application/json" , "Authorization" : "Bearer ollama" },
)
started = time.perf_counter()
first_token_at = None
tokens = 0
with urllib.request.urlopen(req) as res:
for raw in res:
line = raw.decode( "utf-8" ).strip()
if not line.startswith( "data: " ):
continue
body = line[ len ( "data: " ):]
if body == "[DONE]" :
break
delta = json.loads(body)[ "choices" ][ 0 ][ "delta" ]
if not delta.get( "content" ):
continue
if first_token_at is None :
first_token_at = time.perf_counter()
tokens += 1
finished = time.perf_counter()
ttft = (first_token_at or finished) - started
gen_seconds = finished - (first_token_at or started)
return {
"model" : model,
"ttft_sec" : round (ttft, 2 ),
"tokens" : tokens,
"tok_per_sec" : round (tokens / gen_seconds, 1 ) if gen_seconds > 0 else 0.0 ,
}
if __name__ == "__main__" :
for tag in ( "gemma3:4b" , "gemma3:12b" , "gemma3:27b" ):
# 1 回目はモデルのロード時間を含むため捨てて、2 回目以降を採用します
measure(tag, num_predict = 32 )
print (measure(tag))
意図的に 2 回呼んでいるところが要点です。1 回目にはモデルをメモリに載せる時間が丸ごと乗りますので、そのまま記録すると TTFT が数十秒という無意味な数字になります。前述の OLLAMA_KEEP_ALIVE を効かせた状態の値こそが、実際に毎日体感する数字です。
トークン数はストリームのチャンク数で近似しています。厳密なトークン数ではありませんが、モデル間の相対比較には十分実用に足ります。
測った数字をどう判断に落とすか
数字が出たあとの判断基準を、私は次のように置いています。
用途 許容できる TTFT 必要な tok/s 下回ったときの手当て
エディタ内のインライン補完 0.6 秒以下 20 以上 1 段小さいモデルへ落とす
チャット形式の質問応答 1.5 秒以下 15 以上 量子化を 1 段軽くする
バッチ要約・ログの一次フィルタ 制約なし 8 以上 そのまま可(並列度で稼ぐ)
エージェントのツール呼び出し 1.0 秒以下 — クラウドへ振り替える
この表を作ってから、モデル選定で迷う時間がほぼ消えました。特に効いたのは、tok/s が高いという理由だけで大きいモデルを選ばなくなったことです。私の M3 Max では、12B と 27B の tok/s は 2 倍ほどの差でしたが、TTFT の差は 3 倍以上ありました。補完用途では 12B の方が明確に「速い」と感じます。数字を分けて測らなければ、この逆転には気づけませんでした。
もう一点。計測は必ず電源に繋いだ状態と、バッテリー駆動の状態の両方で回してください。ノート PC では省電力設定によって tok/s が半分近くまで落ちることがあり、机の上での検証が外出先で再現しません。
次に検討すること
ローカル LLM 統合が安定したら、次のステップとして「ローカル専用のエージェント」を設計することをおすすめします。たとえば機密度の高いコードレビュー、社内ドキュメントの要約、個人メモの構造化など、クラウドに一度も情報を送らないことを保証できるエージェントです。
Antigravity のマルチエージェント構成については Antigravity マルチエージェント・オーケストレーション入門 で詳しく扱っています。ローカル LLM と組み合わせる設計例も近々アップデート予定ですので、定期的に覗いてみてください。