ローカルの Gemma とクラウドの Gemini 3.5 Flash、この2つをどう使い分けるかで、私はしばらく決めきれずにいました。
最初は「機密を含むコードはローカル、それ以外はクラウド」で切っていました。次に「速さが要るならローカル、賢さが要るならクラウド」に変えました。どちらも一見もっともらしいのですが、実際に手を動かすと判断が毎回ぶれます。同じファイルでも、その日の作業内容によって答えが変わってしまうのです。
ある晩、スケジュール実行に任せていたコード整形が、静かに壊れた差分を出したまま朝まで気づかれずに残っていました。出力そのものは自然な体裁で、パッと見では正しく見えます。そこでようやく腑に落ちました。私が本当に問うべきだったのは「このモデルは賢いか」ではなく、「この出力が間違っていたとき、私は安く気づけて、安く元に戻せるか」でした。
ここでまとめるのは、モデルを賢さやレイテンシではなく、検証しやすさと復旧コストで振り分ける設計です。判定ルール、動くルーター、そして振り分けが正しかったかを後から測るログまでを、個人開発で実際に回している形でお見せします。
なお、機密度を軸にした振り分けは別の判断軸として有効です。そちらはローカル LLM を併用して機密コードを外に出さない振り分け設計にまとめてありますので、本稿の検証軸と組み合わせてお使いいただければと思います。
なぜ「速さ」と「賢さ」だけでは決まらないのか
Gemini 3.5 Flash は、ほぼすべてのベンチマークで前世代の 3.1 Pro を上回りながら、他のフロンティアモデル比で4倍近く高速だとされています。数字だけを見れば、迷わずクラウドに寄せたくなります。
しかし個人開発の運用では、モデルの絶対性能はボトルネックになりにくいのです。詰まるのはたいてい別の場所です。出力が正しいかを人間が確かめる時間、間違っていたときに切り戻す手間、そしてその作業が他に波及したときの後始末。これらは、モデルがどれだけ賢くても消えません。
つまり、振り分けの問いを立て直す必要があります。「どちらのモデルが優秀か」ではなく、「このタスクは、失敗しても安全に吸収できるか」。安全に吸収できるタスクなら、多少精度が落ちてもローカルの Gemma で十分です。吸収できないタスクは、たとえ速くても、検証と承認の重いクラウド側に置くべきです。
ここで軸になるのが検証しやすさです。検証が自動化できて安いタスクは、間違いをすぐ捕まえられます。捕まえられるなら、そもそも精度の低いモデルを使っても損失は限定的です。逆に、検証が人手頼みで高いタスクは、間違いが下流まで流れます。
タスクを3つの軸で採点する
私は個々のタスクを、次の3軸で採点しています。いずれも「モデルの性質」ではなく「タスクの性質」を測っている点が肝心です。
| 軸 | 問い | 低い(ローカル寄り) | 高い(クラウド寄り) |
| 検証容易性 | 出力の正しさを自動で確かめられるか | 型チェック・テスト・リンタで即判定できる | 人間が読んで意味を判断するしかない |
| 可逆性 | 間違っていたとき安く戻せるか | コミット単位で即 revert できる | 外部送信・課金・不可逆な書き込みを伴う |
| 波及範囲 | 壊れたとき影響が及ぶ広さ | 単一ファイル・ローカル完結 | 本番・複数アプリ・ユーザー影響 |
3軸すべてが低いタスクは、失敗しても静かに吸収できます。ここはローカルの Gemma に任せてよい領域です。ローカルなら往復も速く、クラウドの利用上限も消費しません。
逆に、1軸でも高いタスクは、失敗の代償が大きくなります。ここはクラウドに寄せ、加えて人間の承認を挟みます。速さより、間違いを外に出さないことを優先します。
大切なのは、この採点をタスク種別ごとに一度決めて固定してしまうことです。毎回その場で悩むと、冒頭に書いたとおり判断がぶれます。判断を設計に移し替えることが、この振り分けの本質です。
ルーターを実装する
採点を人間の勘に置いておくと再現しません。そこで、タスクの属性から自動でルートを決める小さなルーターを用意します。Antigravity のエージェントやスケジュール実行から、この関数を通してモデルを選びます。
from dataclasses import dataclass
from enum import Enum
class Route(Enum):
LOCAL = "local" # Gemma via Ollama
CLOUD = "cloud" # Gemini 3.5 Flash
CLOUD_WITH_REVIEW = "cloud_review" # クラウド + 人間承認
@dataclass
class Task:
kind: str
auto_verifiable: bool # 型/テスト/リンタで判定できるか
reversible: bool # コミット単位で戻せるか
blast_radius: str # "local" | "repo" | "production"
def route(task: Task) -> Route:
# 波及が本番に及ぶものは、速さより安全。必ず承認を挟む
if task.blast_radius == "production":
return Route.CLOUD_WITH_REVIEW
# 不可逆な操作は、間違いを吸収できない。人間の目を通す
if not task.reversible:
return Route.CLOUD_WITH_REVIEW
# 自動検証でき、戻せて、範囲も狭い。失敗を安全に吸収できる
if task.auto_verifiable and task.blast_radius == "local":
return Route.LOCAL
# それ以外は、検証の重さに応じてクラウドへ
return Route.CLOUD
# 使用例
tasks = [
Task("format_diff", auto_verifiable=True, reversible=True, blast_radius="local"),
Task("release_note_translation", auto_verifiable=False, reversible=True, blast_radius="repo"),
Task("admob_config_change", auto_verifiable=False, reversible=False, blast_radius="production"),
]
for t in tasks:
print(f"{t.kind:28} -> {route(t).value}")
出力はこうなります。
format_diff -> local
release_note_translation -> cloud
admob_config_change -> cloud_review
コード整形はローカルへ、意味の正しさが問われる翻訳はクラウドへ、収益に直結する設定変更は承認付きクラウドへ。勘で毎回悩んでいた判断が、タスクの属性だけで一意に決まります。
判定を分岐でベタ書きしている点には理由があります。ここは頻繁に読み返し、運用しながら手で調整する場所です。抽象化して賢くするより、上から読んで意図がわかる形を選んでいます。エージェントに編集を任せるときも、変更した行の意図が差分で追える方が安全です。
なぜこの順序で判定するかも述べておきます。波及範囲と可逆性を先に見るのは、これらが「取り返しのつかなさ」を表すからです。検証容易性は損失を小さくする軸ですが、そもそも取り返せない操作の前では、検証が容易かどうかは二次的な問題になります。危険な条件から先に弾く、という並びです。
振り分けが正しかったかを、実測で確かめる
ルーターは仮説にすぎません。「ローカルで十分」と判断したタスクが、実は訂正だらけだったということは起こります。そこで、振り分けの結果を後から測るログを必ず添えます。
記録するのは、ルート・所要時間・そして最も大切な訂正率です。訂正率とは、そのルートが出した成果のうち、後から人間や検証器が直したものの割合です。
import json
import time
from pathlib import Path
LOG = Path.home() / ".antigravity" / "route_ledger.jsonl"
LOG.parent.mkdir(parents=True, exist_ok=True)
def run_with_ledger(task: Task, execute):
chosen = route(task)
started = time.monotonic()
result = execute(chosen) # 実際のモデル呼び出し
elapsed = time.monotonic() - started
record = {
"kind": task.kind,
"route": chosen.value,
"elapsed_s": round(elapsed, 2),
"verified": result.get("verified"), # 検証器が通ったか
"corrected": result.get("corrected"), # 後から直したか
"ts": time.time(),
}
with LOG.open("a") as f:
f.write(json.dumps(record, ensure_ascii=False) + "\n")
return result
一週間ほど回すと、ルートごとの傾向が見えてきます。私の手元での一例を挙げます。あくまで目安であり、モデルのバージョンやタスクの性質で大きく変わる点はご了承ください。
| ルート | 平均所要 | 訂正率 | 読み取り |
| local(整形・軽微修正) | 約 2〜4 秒 | 約 6% | 自動検証で捕まる範囲。妥当 |
| cloud(翻訳・要約) | 約 3〜6 秒 | 約 14% | 意味の訂正が残る。承認検討 |
| cloud_review(設定変更) | 承認込みで数分 | 約 2% | 人手承認が効いている |
この数字が閾値の再調整を導きます。もしローカルの訂正率が徐々に上がってきたら、それは「安全に吸収できる」という前提が崩れかけている合図です。自動検証がすり抜けを起こしているか、モデルが静かに劣化しているか、どちらかを疑います。逆にクラウドの訂正率が十分低いタスク種別が見つかれば、承認を外して速いルートへ移す判断ができます。
閾値を勘で動かさず、この台帳の推移で動かすこと。これが、振り分けを一度きりの設計で終わらせず、運用に載せ続けるための要になります。
運用で崩れやすい落とし穴
この設計には、静かに崩れる落とし穴がいくつかあります。あらかじめ見張る先を決め、崩れを回避します。
第一に、検証器そのものの陳腐化です。「自動検証できる」を根拠にローカルへ寄せたのに、その検証器が緩いままだと、間違いはすり抜けます。冒頭で私が朝まで気づけなかったのは、まさにこれでした。検証器は成果物と同じくらい真剣に育てる対象です。テストやリンタの網目を、ルートを追加するたびに見直します。
第二に、ローカルモデルの静かな劣化です。量子化の設定変更、コンテキスト長の伸び、プロンプトの肥大化。どれも精度をじわじわ削りますが、エラーとしては現れません。訂正率の緩やかな上昇だけが、それを教えてくれます。本番運用に載せる前に、台帳を週次で眺める習慣が、唯一の早期警報になります。
第三に、属性の詐称です。エージェントに採点まで任せると、「これは可逆だ」と楽観的に申告してしまう場面が出ます。可逆性や波及範囲は、モデルの自己申告ではなく、リポジトリの状態やデプロイ先といった観測可能な事実から機械的に決めるべきです。判断を主観に委ねないことが、この振り分けの信頼性を支えます。
個人的には、検証工程のように失敗を吸収しやすい作業から先にローカル化することを推奨します。いきなり全部を移すより、崩れても安い場所で手触りを確かめる方が、結果的に早く安定します。
明日からの一歩
まずは小さく始めてみてください。私自身がこの設計にたどり着くまでに踏んだ順序を、3つの手順に落とすとこうなります。
- 普段エージェントに投げているタスクを5種類ほど書き出す
- それぞれを3軸で採点し、3軸とも低いものだけをローカルの Gemma へ移す
- 移した分に台帳を添え、訂正率の推移を週次で眺める
おそらく半分近くは「自動検証でき、戻せて、範囲も狭い」に該当するはずです。そこだけをローカルへ移すだけでも、クラウドの利用上限と往復の待ち時間がはっきり軽くなります。
一方で、AdMob の収益設定や App Store の申請フローに触れる変更のように、壊れると外まで響くものは、迷わず承認付きクラウドへ残してください。速さを取りにいくのは、安全に吸収できると確かめた場所からで十分です。
そして、移した分には必ず台帳を添えてください。訂正率という一つの数字が、あなたの振り分けが正しかったかを、勘よりずっと正直に教えてくれます。同じ回り道を少しでも省いていただけたら嬉しいです。お読みいただきありがとうございました。