月末が近づくと、AI Ultra の利用上限がじりじりと迫ってきます。残り少ないクォータを前に、これまで私はサーキットブレーカーを入れて「上限に達したら止める」運用にしていました。安全ではあります。けれど止めた瞬間、その夜に出るはずだった成果はゼロになります。
止めるか、全力で走るか。この二択しか持っていなかったのが間違いでした。本当に必要だったのは、その中間——能力を一段ずつ落としながら、価値のある成果を出し続ける という選択肢です。これは「段階的劣化(graceful degradation)」と呼ばれる設計思想で、停電時に非常灯だけは点ける、あの考え方に近いものです。
「止める」「配る」「落とす」は別の設計
クォータ対策には、目的の違う3つの設計思想があります。混同すると、止めるべきでない場面で止め、落とすべき場面で配ろうとして破綻します。
設計思想 何をする 向く場面
サーキットブレーカー 上限到達で実行を止める 誤作動・暴走の被害を断ちたいとき
予算配分 複数ジョブへ枠を事前に割り当てる 並行ジョブ間の取り合いを防ぎたいとき
段階的劣化 能力を落として走り続ける 残量が細っても成果はゼロにしたくないとき
3つは排他ではありません。私自身の個人開発の運用では、暴走検知にはブレーカーを、並行ジョブには予算配分を、そして「予算を使い切りかけた1ジョブの内側」で段階的劣化を効かせています。今回掘り下げるのは、この最後のひとつです。
劣化を「段」として定義する
段階的劣化の核心は、残量に応じた**離散的な能力段(tier)**をあらかじめ決めておくことです。連続的に少しずつ削るのではなく、はっきりした段にすると、挙動が予測でき、検証もしやすくなります。
私は4段で運用しています。
フル段 — 残量に余裕。高性能モデルで全サブタスクを実行します。
倹約段 — 残量が中程度。非必須のサブタスク(装飾的な推敲、二重チェック)を省きます。
降格段 — 残量が少ない。モデルを軽量・高速なものへ降格し、必須サブタスクだけを通します。
最小段 — 残量が僅か。新規生成は止め、すでに作りかけのものを完成・保存することだけに集中します。
段の良いところは、「今どの段にいるか」をログに残せば、後から「あの夜は降格段だった」と一目で分かることです。連続的な絞り方では、この事後の説明可能性が失われます。
残量から段を選ぶポリシー
段を決めるロジックを、エージェント本体から切り離して一箇所に置きます。残量の割合と、月末までの残り日数の両方を見るのがコツです。同じ残量でも、月末まで日があるなら早めに倹約に入るべきだからです。
# degrade.py — 残量と残り日数から能力段を選ぶ
from dataclasses import dataclass
@dataclass
class Tier :
name: str
model: str # 使用モデル
skip_optional: bool # 非必須サブタスクを省くか
new_work: bool # 新規生成を許すか
TIERS = {
"full" : Tier( "full" , "gemini-3.5-pro" , False , True ),
"thrifty" : Tier( "thrifty" , "gemini-3.5-pro" , True , True ),
"demoted" : Tier( "demoted" , "gemini-3.5-flash" , True , True ),
"minimal" : Tier( "minimal" , "gemini-3.5-flash" , True , False ),
}
def choose_tier (remaining_ratio: float , days_left: int ) -> Tier:
"""remaining_ratio: 月内クォータの残り割合 0.0-1.0 / days_left: 月末までの日数"""
# 残量を「月末までに均すと1日あたりどれだけ使えるか」で正規化
daily_budget = remaining_ratio / max (days_left, 1 )
if daily_budget >= 0.04 : # 余裕(25日で使い切るペース以上の残り)
return TIERS [ "full" ]
if daily_budget >= 0.02 :
return TIERS [ "thrifty" ]
if remaining_ratio >= 0.03 : # 細いが、まだ必須は通せる
return TIERS [ "demoted" ]
return TIERS [ "minimal" ] # ほぼ枯渇 → 作りかけの完成だけ
daily_budget という正規化が効きます。残量そのものではなく「均したペース」で見ることで、月初の浪費と月末の節約を自然に分けられます。
ジョブ本体に段を反映する
選んだ段を、エージェントの実行に落とし込みます。ここで大事なのは、非必須を省くのと、必須を軽く作るのを分けて扱う ことです。
tier = choose_tier(get_remaining_ratio(), days_until_month_end())
log( f "running at tier= { tier.name } model= { tier.model } " )
if not tier.new_work:
finalize_in_progress_only() # 作りかけを完成・保存して終了
else :
run_required_subtasks( model = tier.model)
if not tier.skip_optional:
run_optional_subtasks( model = tier.model) # 余裕があるときだけ
非必須サブタスクとは何か——ここの線引きが運用の質を決めます。私の記事生成ジョブでは、本文の生成と保存は必須、表現の二重推敲やメタデータの追加最適化は非必須に分類しています。倹約段ではまず推敲を落とし、降格段ではモデルも落とす。読者に届く最低限の価値(読める本文)は最小段まで守る、という設計です。
劣化を観測可能にする
段階的劣化の落とし穴は、静かに効きすぎて気づかないことです。気づいたら何週間も降格段のまま低品質な成果を出し続けていた、という事故が起こり得ます。だから段の遷移は必ず記録し、月をまたいで眺められるようにします。
# 段が変わった瞬間だけ記録する(毎回ではなく遷移を残す)
def log_tier_change (prev: str , cur: str ):
if prev != cur:
append_jsonl( "tier_history.jsonl" ,
{ "from" : prev, "to" : cur, "ts" : now()})
この履歴を週次で見ると、「毎月末の3日だけ降格段に入る」のような癖が見えてきます。それが許容範囲なら放置し、品質に響いているなら本当の対処——上限の引き上げか、生成本数の見直し——に進みます。劣化はあくまで緩衝材であって、恒常的な低品質運用を正当化する道具ではない、という前提を忘れないことを強く推奨します。
どこから入れ、どこで止めるか
最初の一歩としては、いきなり4段を作る必要はありません。実体験として効いたのは、まず「フル段」と「最小段」の2段だけを用意し、残量が一定を切ったら新規生成を止めて作りかけの完成に切り替える、という最小構成でした。これだけでも「月末に全部ゼロ」は避けられます。
そのうえで、本番運用で品質の落ち方が急すぎると感じたら、あいだに倹約段・降格段を足していきます。段は増やすほど滑らかになりますが、検証の手間も増えます。Dolice Labs のように個人開発で回す規模なら、2〜4段のあいだで十分でした。
止めることと走り切ることの二択をやめ、残量に応じて能力を一段ずつ譲る。クォータは制約ですが、譲り方を設計しておけば、制約のなかでも成果をゼロにせずに済むと考えています。