Antigravity 2.0 で Gemini 3.5 Flash がデフォルトの Flash モデルに切り替わった週、月初のトークン使用量グラフが、いつもより少し早く立ち上がっていることに気づきました。
コードの体感は速いままです。けれど、内訳を開くと「思考トークン(thinking tokens)」の比率が以前より明確に増えていました。Flash は速いモデルという認識のまま使っていたので、この内訳のズレが少し引っかかりました。
個人開発で4つのサイトを並行運用していると、エージェントを回す回数は1日で数百回に達します。1回あたりの差はわずかでも、月末にはまとまった額になります。そこで腰を据えて、thinking_level をタスクごとに測り直すことにしました。本記事はその実測と、最終的に落ち着いた運用設定の記録です。
thinking_level が効くのは「考える必要のないタスク」での無駄
Gemini 3 系で導入された thinking_level は、モデルが応答前にどれだけ内部推論に予算を割くかを段階で指定するパラメータです。low と high を中心に、Antigravity 側のモデル設定や SDK の thinking_config から制御します。2.5 世代までの thinking_budget(トークン数での直接指定)も併存しており、用途によって使い分けます。
ここで見落としやすいのは、思考トークンも課金対象の出力トークンとして計上される点です。最終的な回答が3行でも、その前にモデルが2,000トークン分「考えて」いれば、その分は請求に乗ります。
つまり最適化の余地が一番大きいのは、難しいタスクではありません。「変数名を一括で直す」「import 文を並べ替える」といった、本来は考える必要がほとんどないタスクで、モデルが過剰に思考しているケースです。ここを low に寄せるだけで、品質を一切落とさずに消費だけが下がります。
同じタスクを level 別に流して測る
感覚で語っても再現性がないので、代表的な3種類のタスクを、同じ入力で low と high の両方に流し、思考トークンと候補トークンを記録しました。計測は google-genai SDK の usage_metadata から取得しています。
from google import genai
from google.genai import types
client = genai.Client() # GEMINI_API_KEY を環境変数から読み込みます
def measure (prompt: str , level: str ) -> dict :
response = client.models.generate_content(
model = "gemini-3.5-flash" ,
contents = prompt,
config = types.GenerateContentConfig(
thinking_config = types.ThinkingConfig( thinking_level = level),
),
)
usage = response.usage_metadata
return {
"level" : level,
"thinking_tokens" : usage.thoughts_token_count or 0 ,
"output_tokens" : usage.candidates_token_count or 0 ,
"text" : response.text,
}
# 同一プロンプトを2水準で計測
for lv in ( "low" , "high" ):
r = measure( "この関数の変数名を camelCase に統一して" , lv)
print (lv, r[ "thinking_tokens" ], r[ "output_tokens" ])
私の環境で繰り返し測った平均値は、おおよそ次のようになりました(数字は入力やモデル更新で動くため、参考値として扱ってください)。
機械的な整形タスク(変数名の統一)では、low の思考トークンが平均で約110、high が約1,650でした。比にして15倍ほどですが、出力されたコードはどちらも同一でした。考える必要がないタスクに high を使うのは、ほぼそのまま無駄になります。
中程度のリファクタリング(責務の分割を伴う書き換え)では、low が約340、high が約1,900。ここでは出力に差が出ました。low は分割の粒度がやや粗く、2回に1回は手直しが必要でした。high は一度で意図に近い形にまとまりました。
設計判断を含むタスク(データ取得層の境界をどう引くか)では、low は表層的な提案にとどまり、high が約2,400トークンを使って前提条件への言及まで返してきました。ここは消費が増えても high の価値が明確に上回ります。
結論はシンプルで、消費と精度のトレードオフは「タスクの種類」でほぼ決まり、level を一律にする運用は、どちらの方向にも損をします。
タスクを3層に分けて level を自動で割り当てる
毎回手で level を選ぶのは続きません。そこで自分のタスクを3層に分類し、ルーターで自動的に割り当てる形にしました。判断軸は「モデルが間違えたときの手戻りコスト」です。
第1層: 取りこぼしても安いタスク(low 固定)
整形、リネーム、定型コードの生成、コメント追加など。出力をその場で目視できて、間違っても1秒で気づけるものを置きます。ここは速度が体験を支配するので、思考を最小にします。
第2層: 手直しが面倒なタスク(中庸)
複数ファイルにまたがる小規模リファクタリング、テストの追加、型定義の整理など。low だと2回に1回直す、high だと過剰、という領域です。私は thinking_budget で中間値(512トークン前後)を明示し、振れ幅を抑えています。
第3層: 間違えると後戻りが高いタスク(high 固定)
アーキテクチャの境界設計、状態管理の方針決め、データ整合性に関わる変更など。ここで思考トークンをケチると、誤った前提のまま実装が進み、結局その何十倍も消費します。
この3層をコードに落とすと、次のようになります。
from google import genai
from google.genai import types
client = genai.Client()
# タスク種別 → 思考設定のプロファイル
PROFILES = {
"format" : { "level" : "low" },
"rename" : { "level" : "low" },
"boilerplate" : { "level" : "low" },
"refactor" : { "budget" : 512 }, # 中庸: トークン数で明示
"test" : { "budget" : 512 },
"design" : { "level" : "high" },
"state" : { "level" : "high" },
"data-model" : { "level" : "high" },
}
def build_config (task_type: str ) -> types.GenerateContentConfig:
profile = PROFILES .get(task_type, { "level" : "low" }) # 未知のタスクは安全側の low
if "budget" in profile:
thinking = types.ThinkingConfig( thinking_budget = profile[ "budget" ])
else :
thinking = types.ThinkingConfig( thinking_level = profile[ "level" ])
return types.GenerateContentConfig( thinking_config = thinking)
def run (task_type: str , prompt: str ) -> str :
response = client.models.generate_content(
model = "gemini-3.5-flash" ,
contents = prompt,
config = build_config(task_type),
)
return response.text
Antigravity のエディタ単体で使う場合は、SDK を挟まずワークフロー設定側で「軽い操作のデフォルトを low に寄せる」だけでも効果があります。SDK を使うのは、スケジュール実行や自動投稿パイプラインのように、タスク種別が事前に分かっている場面です。私の場合は後者の比率が高いので、ルーター方式に寄せています。
本番で踏んだ落とし穴
計測のときには見えず、運用に乗せてから気づいた点が2つありました。
ひとつは、マルチターンのエージェントループで thought_signatures を取り違えると、後続ターンの思考の連続性が崩れることです。Gemini 3 系では思考の状態が署名として返り、次のリクエストに戻す前提の設計になっています。level を動的に切り替えるルーターを書くと、ターンごとに設定オブジェクトを作り直すため、署名の引き回しを忘れがちでした。エージェントの精度がじわじわ落ちる症状で、原因にたどり着くまで時間がかかりました。会話を継続するセッションでは、署名を含むレスポンス全体を履歴に積み、設定だけを差し替えるようにして回避しています。
もうひとつは、第1層を low に寄せすぎて、まれに「複数箇所を直す指示で1箇所しか直さない」取りこぼしが出たことです。整形でも、対象が広範囲に散っているときは最小限の思考すら効いてきます。対策として、第1層でも「対象が3ファイル以上にまたがる」と判定したら、その回だけ第2層の budget に格上げする条件分岐を足しました。固定プロファイルに、入力の規模で一段上げる例外を1つ持たせるだけで、安定しました。
エディタ単体と SDK、絞り込みをどちらに置くか
Antigravity のエディタで対話的に触れているときは、level を1操作ごとに切り替える運用は現実的ではありません。ここはワークフロー設定で「軽い操作の既定を low に寄せる」一手に留め、迷う場面だけ手動で high に上げる形が落ち着きます。
一方、スケジュール実行や自動投稿のように、タスク種別が事前に確定している処理では、ルーターで機械的に割り当てたほうが安定します。私自身、定期ジョブはすべて後者へ寄せていて、対話セッションでは絞り込みをほとんど意識していません。
同じ thinking_level でも、その判定をエディタ側に置くか、コード側に置くかで運用の手触りは変わります。手で触る回数が多い人はエディタ既定の調整から、自動化の比率が高い人はルーターから始めると、無理なく入れます。
私が落ち着いた運用設定
最終的に、日々のエージェント運用で消費の中心を占めていた第1層を low に寄せたことで、思考トークンの合計は体感で4割ほど下がりました。一方で、第3層を high で保護したため、設計まわりの手戻りはむしろ減っています。トータルの請求は下がり、品質の体感は上がるという、珍しく両取りができた変更でした。
判断に迷ったときの私の基準は、「この出力が間違っていたら、気づくのに何秒かかるか」です。秒で気づけるなら low、気づくのに数分かかり手戻りが重いなら high。この一文を頭の片隅に置くだけで、level の選択はかなり機械的になります。
数字はモデル更新のたびに動くので、ここに書いた値をそのまま信じず、まずはご自身の代表タスクを3つ選んで low と high で測ってみてください。15分ほどの計測が、その後の毎月の消費をずっと軽くしてくれるはずです。お読みいただきありがとうございました。