6月30日の朝、前夜と同じはずの定期処理のログを開いて、少し手が止まりました。出力の傾向がわずかに違うのです。原因はすぐに分かりました。Claude Sonnet 5 が全プランの既定モデルになり、モデルを明示していなかったジョブが、私の知らないうちに新しい既定に乗り換えていたのです。
その数日前の7月1日には、輸出規制の解除で Fable 5 が全世界に提供再開されていました。手元の自動化から呼べる上位モデルが、Sonnet 5・Fable 5・Opus 4.8 と一気に3つ揃ったことになります。選択肢が増えるのはありがたいことですが、増えた選択肢を「どれが一番賢いか」で選び始めると、たいてい行き詰まります。個人開発で複数サイトの記事生成や監視を無人で回している立場では、この数日は「どれを使うか」ではなく「どれをどこに置くか」を考え直していました。持ち場の割り当てと、それをコードに落とすところまでを、順を追って共有します。
なぜ既定のモデルに任せきりにしないのか
冒頭の出来事が示すのは、モデルを明示しない運用の危うさです。既定モデルはプラットフォーム側の都合で変わります。6月30日の切り替えは告知のあった良性の変更でしたが、それでも「昨日と今日で出力の性格が変わる」という事実は、無人で回している側にとっては小さくない揺れです。
既定に任せるということは、自分の自動化の挙動を他者の意思決定に結びつけるということです。コスト、レイテンシ、出力の粒度——どれも既定モデルが変わればついてきます。だから最初の原則はシンプルです。本番の自動化では、モデルを必ず明示的に指定する。 そのうえで、どのモデルを指定するかを、タスクごとに意味を持って決める。ここからが本題です。
3つのモデルを優劣でなく持ち場で見る
3つを一列に並べて優劣をつけようとすると、ベンチマークの数字比べになり、自分のタスクとの接点を見失います。私は代わりに、それぞれの「一番効く場面」を一言で持っておくようにしています。
| モデル | 一番効く場面 | 指定文字列 |
|---|
| Sonnet 5 | 計画・ツール利用・自律実行を含む日常の作業馬。導入価格で常時回しても負担が軽い | claude-sonnet-5 |
| Fable 5 | 常時アダプティブ思考と128kトークン出力を活かした、長い成果物の単発生成・大きな文脈の一括処理 | claude-fable-5 |
| Opus 4.8 | 多段の難しい推論と、長時間連続作業での一貫性が問われる中枢の判断 | claude-opus-4-8 |
この3行は優劣ではなく役割分担です。Sonnet 5 は最もエージェント的な Sonnet として、ツールを呼びながら手順を進める日常処理の既定に据えます。Fable 5 は128k出力と常時思考が効く場面——たとえば一つの記事を最後まで一息に書かせる、大きなログをまとめて一巡させる——に限って呼びます。Opus 4.8 は、間違えると後段が全部ずれるような中枢の判断にだけ使い、コストに見合う場所を選びます。実際に横に並べて数日使った所感はFable 5 と Opus 4.8 を3日間並べて使った記録にも書いていますが、結論は「勝ち負けではなく持ち場」でした。
タスククラスで割り当てる — 私の自動化スタックの対応表
持ち場の言葉を、自分のタスクに翻訳します。私は自動化の処理を「タスククラス」という粒度でまとめ、クラス単位でモデルを決めています。個々のジョブごとに考えると必ずブレるので、クラスに畳むのが要点です。
| タスククラス | 具体例 | 割り当て | 理由 |
|---|
| 分類・抽出 | 差分の要約、タグ付け、短い判定 | Sonnet 5 | 短く速い。ここに上位モデルは過剰 |
| 長文の単発生成 | 一記事を通しで下書き、長いレポート | Fable 5 | 128k出力で分割継ぎ接ぎを避けられる |
| エージェント作業 | ツールを跨ぐ調査、複数ステップの実行 | Sonnet 5 | ツール利用と自律実行が既定で強い |
| 中枢の意思決定 | 公開可否の最終判断、整合性の検証 | Opus 4.8 | 長時間の一貫性と多段推論が要る |
大切なのは、この表が「私の」スタックに固有だという点です。読者の環境では分類が主役かもしれず、その場合は Opus 4.8 の出番はほとんどないでしょう。表そのものを写すのではなく、自分のタスクをクラスに畳み、各クラスに一言の理由を添える——この作り方だけを持ち帰っていただければ十分です。
単一モデルの直書きから、方針オブジェクトへ
割り当てが決まったら、それをコードのどこに置くかが次の問題です。やりがちなのは、呼び出し地点にモデル文字列を直書きすることです。
# Before: 呼び出しごとにモデル文字列が散らばる
resp = client.messages.create(
model="claude-sonnet-5", # ここに直書き
max_tokens=4096,
messages=[{"role": "user", "content": prompt}],
)
# ...別のファイルでは別の文字列が直書きされ、
# どのタスクがどのモデルを使っているのか全体像が追えなくなる
この形は、モデルを見直したいときに全ファイルを grep する羽目になり、しかも「タスククラスとモデルの対応」という判断がコードのどこにも書かれません。判断を一箇所の方針オブジェクトに集約することを推奨します。
# After: タスククラスからモデルを引く方針オブジェクト
from dataclasses import dataclass
# 指定文字列は一箇所だけで定義する
MODELS = {
"sonnet5": "claude-sonnet-5",
"fable5": "claude-fable-5",
"opus48": "claude-opus-4-8",
}
# タスククラス -> モデルの割り当て(前節の表がそのままここに来る)
TASK_MODEL = {
"classify": "sonnet5", # 分類・抽出
"longform": "fable5", # 長文の単発生成
"agentic": "sonnet5", # エージェント作業
"decide": "opus48", # 中枢の意思決定
}
@dataclass(frozen=True)
class Plan:
task_class: str
model_id: str
def plan_for(task_class: str) -> Plan:
if task_class not in TASK_MODEL:
# 未知のクラスは既定の作業馬へ倒す(沈黙で最強モデルに倒さない)
key = "sonnet5"
else:
key = TASK_MODEL[task_class]
return Plan(task_class=task_class, model_id=MODELS[key])
呼び出し地点は plan_for("longform").model_id を渡すだけになり、モデルの見直しは表を1つ書き換えれば済みます。未知のタスククラスを黙って最上位モデルに倒さない、という一行も地味に効きます。想定外の入力でコストと挙動が跳ねるのを防げるからです。
モデルが使えないときのフォールバック梯子
割り当てを決めても、指定したモデルが常に使えるとは限りません。レート上限、一時的な不availability、組織のモデル制限——理由はさまざまです。無人運用では、ここで例外を投げて止まるより、意味のある代替に降りるほうが望ましい場面が多くあります。ただし「降り方」には順序があります。
# フォールバック梯子: クラスごとに「降りてよい順序」を明示する
FALLBACK = {
"classify": ["sonnet5"], # 降りない(安いので待って再試行)
"longform": ["fable5", "sonnet5"], # 128k が無理なら分割前提で Sonnet 5
"agentic": ["sonnet5", "opus48"], # 上に逃がす(作業馬が詰まったら中枢へ)
"decide": ["opus48"], # 降りない(品質を落とさない)
}
class ModelUnavailable(Exception):
pass
def resolve(task_class: str, is_available) -> Plan:
ladder = FALLBACK.get(task_class, ["sonnet5"])
for key in ladder:
if is_available(MODELS[key]):
return Plan(task_class=task_class, model_id=MODELS[key])
# 梯子を降りきっても駄目なら、黙って別物を返さず明示的に失敗させる
raise ModelUnavailable(f"no model available for task_class={task_class}")
梯子の設計で私が大事にしているのは、降りてよいクラスと降りてはいけないクラスを分けることです。安い分類は降りる必要がなく、待って再試行すればいい。長文生成は128kが使えないなら分割前提で Sonnet 5 に降ろす。逆に中枢の意思決定は、品質を落とすくらいなら止めて後で人が見るほうがよいので、あえて梯子を1段しか用意しません。「とりあえず動くモデルで返す」を全クラスに適用すると、最も落としてはいけない判断が静かに劣化する落とし穴があります。この落とし穴を回避するために、クラスごとに梯子の深さを変えているわけです。
割り当てを記録して、後から検証できるようにする
冒頭の「既定が変わって出力が変わった」を二度と見逃さないために、実行のたびに「何をどのモデルで動かしたか」を残します。方針オブジェクトを通していれば、記録は数行で足ります。
import json, time
def run_logged(task_class: str, call_model, is_available):
plan = resolve(task_class, is_available)
started = time.time()
result = call_model(plan.model_id) # 実際の呼び出しは差し替え可能に
record = {
"ts": int(started),
"task_class": plan.task_class,
"resolved_model": plan.model_id, # 実際に使われたモデルを残す
"elapsed_ms": int((time.time() - started) * 1000),
}
print(json.dumps(record, ensure_ascii=False))
return result
resolved_model を残しておくと、既定モデルが変わった日でも「このタスクは意図どおりのモデルで動いた」と後から確認できます。私は日次のログをこの1行で突き合わせるようにしてから、モデル起因の挙動変化を朝一で気づけるようになりました。記録はフォールバックが働いた回数の可視化にもなり、どのクラスがよく降りているか——つまりどこに無理があるか——が見えてきます。
迷いやすい3つの線引き
最後に、割り当てを決めるときに私がよく迷い、そのたびに立ち返る3つの線引きを置いておきます。
第一に、Fable 5 の常時思考は、安い分類には過剰です。128k出力と常時アダプティブ思考は長い成果物にこそ効くもので、短い判定に回すと、得られる質の差以上にレイテンシとコストが増えます。分類は素直に Sonnet 5 に置くのが収まりがよいと感じています。
第二に、Opus 4.8 は「コストに見合う場所」を選んでこそです。導入価格の Sonnet 5 が安いからといって全部そちらに寄せると、たまに中枢の判断が甘くなります。逆に何でも Opus 4.8 にすると、効かない場所にコストを払い続けます。効くのは、間違えると後段が全部ずれる一点に絞ったときです。
第三に、安い導入価格は割り当てを変える理由にならないという線引きです。Sonnet 5 の導入価格は $2/百万入力・$10/百万出力(標準は $3/$15)で、標準価格でも Opus 4.8 の $5/$25 比で入出力とも約40%低く抑えられます。安いのは確かですが、導入価格には期限があります。価格でクラスの割り当てを動かすと、価格が戻ったときに設計を組み直すことになります。持ち場は能力で決め、価格は総量の見積もりで扱う——この2つを混ぜないほうが、あとで楽になります。
まとめと次の一歩
上位モデルが3つ揃った今、効くのは「どれが最強か」を追うことではなく、「どれをどの持ち場に置くか」を一度きちんと決めて、方針オブジェクトに畳んでおくことでした。今日できる一歩は小さくて構いません。
- 自分の自動化のタスクを4〜5個のクラスに畳む
- 各クラスに一言の理由を添えて、割り当てるモデルを決める
- その割り当て表をコードの一箇所(方針オブジェクト)に置く
これだけで、次に既定モデルが変わった日も、朝のログで慌てずに済みます。
私自身まだ最適な割り当てを探っている途中ですが、モデルを「明示して、記録して、意味を持って選ぶ」という三点だけは、揺れの多いこの時期に効いてくれています。お読みいただきありがとうございました。実装の土台として、少しでも参考になれば嬉しいです。