自動運用のログを見ていて、いちばん厄介なのは「モデルは生きているのに、リクエストの一部だけが失敗する」タイプの障害です。2026-07-24 に予定されている Opus 4.7 の fast モード廃止は、まさにこの形で表面化します。claude-opus-4-7 というモデルIDはその後も有効なまま、speed: "fast" を指定したリクエストだけがエラーを返すようになります。
私自身、個人開発で複数のサイトの自動投稿を毎日回していて、以前モデルIDの引退には監査スクリプトで気づけたのに、速度指定の変更で危うく取りこぼしかけたことがあります。モデル移行のチェックリストは「使っているモデルIDは生きているか」だけを見ていることが多く、その一段下の「そのモデルで、その速度指定が今も使えるか」までは見ていないからです。
ここでは、モデルと速度を1つの「能力ペア」として扱い、起動前に実際のプローブで検証してから本処理に入る preflight を実装します。7/24 を過ぎても無人パイプラインを止めないための、能力ペア単位の防御です。
何が起きるのか — モデルは残り、速度指定だけが失効する
まず事実関係を整理します。今回廃止されるのは Opus 4.7 の fast モードであって、モデルそのものではありません。
要素 7/24 以前 7/24 以降
claude-opus-4-7(速度指定なし)利用可 利用可(変化なし)
claude-opus-4-7 + speed: "fast"利用可 エラー
claude-opus-4-8 + speed: "fast"利用可 利用可(移行先)
つまり、speed を付けずに Opus 4.7 を呼んでいるコードは何も変わりません。影響を受けるのは、speed: "fast" を明示的に付けている呼び出しだけです。低レイテンシを狙って fast モードを常用しているバッチほど、静かに壊れます。
ここが取りこぼしを生む理由です。「引退したモデルIDを使っていないか」を検査する監査は、claude-opus-4-7 を有効と判定して素通りさせます。速度指定は監査の視野の外にあるのです。
なぜモデルID監査では気づけないのか
多くの移行監査は、こんな形をしています。
# よくあるモデルID監査 — これでは今回の廃止を取りこぼす
RETIRED_MODEL_IDS = {
"claude-3-opus-20240229" ,
"claude-3-5-sonnet-20240620" ,
# ... 引退したモデルIDの一覧
}
def audit_model_id (model: str ) -> None :
"""使用中のモデルIDが引退リストに載っていないか確認する。"""
if model in RETIRED_MODEL_IDS :
raise RuntimeError ( f "引退済みモデルを使用しています: { model } " )
# claude-opus-4-7 は引退していないので、ここは通過する
この監査は「モデルID」という単位でしか世界を見ていません。claude-opus-4-7 は生きているので合格になります。しかし実際に落ちるのは (claude-opus-4-7, fast) という組み合わせです。単位がずれているのです。
必要なのは、検査の粒度を「モデルID」から「モデル × 速度」に一段細かくすることです。ここから先は、この能力ペアを軸に組み立てていきます。
能力ペアをレジストリとして持つ
まず、使ってよい組み合わせと、廃止期日つきの組み合わせを1か所にまとめます。ポイントは、期日を「有効期限つきの事実」として持つことです。日付をコードに散らかすと、7/24 の朝に慌てて grep する羽目になります。
from dataclasses import dataclass
from datetime import date
from typing import Optional
@dataclass ( frozen = True )
class Capability :
"""モデルと速度指定の組み合わせ(=能力ペア)と、その失効期日。"""
model: str
speed: Optional[ str ] # None は速度指定なし
retires_on: Optional[date] # None は失効予定なし
successor: Optional[ "Capability" ] = None # 移行先
# 能力ペアのレジストリ。事実(期日・移行先)を1か所に集約する
OPUS_48_FAST = Capability( "claude-opus-4-8" , "fast" , None )
CAPABILITIES = {
( "claude-opus-4-7" , "fast" ): Capability(
model = "claude-opus-4-7" ,
speed = "fast" ,
retires_on = date( 2026 , 7 , 24 ),
successor = OPUS_48_FAST ,
),
( "claude-opus-4-7" , None ): Capability( "claude-opus-4-7" , None , None ),
( "claude-opus-4-8" , "fast" ): OPUS_48_FAST ,
}
def lookup (model: str , speed: Optional[ str ]) -> Optional[Capability]:
return CAPABILITIES .get((model, speed))
レジストリにしておくと、「いま使っている能力ペアが、いつ・何に移行すべきか」を1回の参照で答えられます。日付比較のロジックが1か所に閉じるので、他のモデルで似た廃止が起きたときも同じ骨格で足せます。
起動前に「期日」で警告する
レジストリがあれば、実際に呼び出す前に静的な判定ができます。無人パイプラインの起動直後、いちばん最初にこれを通します。
from datetime import date
def check_deadline (model: str , speed: Optional[ str ],
today: Optional[date] = None ,
warn_within_days: int = 14 ) -> None :
"""能力ペアの失効期日を確認する。期日超過は例外、接近は警告。"""
today = today or date.today()
cap = lookup(model, speed)
if cap is None :
# レジストリ外=未知の組み合わせ。無人運用では明示的に止める
raise RuntimeError ( f "未登録の能力ペアです: model= { model } , speed= { speed } " )
if cap.retires_on is None :
return # 失効予定なし
if today >= cap.retires_on:
raise RuntimeError (
f " { model } + speed= { speed } は { cap.retires_on } に失効済みです。"
f "移行先: { cap.successor.model } + speed= { cap.successor.speed } "
)
remaining = (cap.retires_on - today).days
if remaining <= warn_within_days:
print ( f "⚠️ { model } + speed= { speed } は残り { remaining } 日で失効します "
f "( { cap.retires_on } )。移行先: { cap.successor.model } " )
# 使用例(2026-07-20 に実行したと仮定)
check_deadline( "claude-opus-4-7" , "fast" , today = date( 2026 , 7 , 20 ))
# → ⚠️ claude-opus-4-7 + speed=fast は残り 4 日で失効します(2026-07-24)。移行先: claude-opus-4-8
これで「気づいたら 7/24 を過ぎていた」を防げます。ただし静的な期日チェックだけでは足りません。期日はあくまで予告であって、前倒しや当日のロールアウトのずれはあり得ます。だからこそ、実際のプローブで裏を取ります。
実プローブで裏を取る preflight
最も確実なのは、本処理と同じ能力ペアで極小のリクエストを1回だけ投げてみることです。成功すればその日その環境でその組み合わせが生きている、と確定できます。予告日を信じ込まず、実際の応答で判断するのが preflight の要点です。
import anthropic
client = anthropic.Anthropic()
def probe_capability (model: str , speed: Optional[ str ]) -> bool :
"""本処理と同じ能力ペアで最小リクエストを投げ、生死を確かめる。"""
kwargs = {
"model" : model,
"max_tokens" : 1 , # 課金を最小化する
"messages" : [{ "role" : "user" , "content" : "ok" }],
}
if speed is not None :
kwargs[ "speed" ] = speed
try :
client.messages.create( ** kwargs)
return True
except anthropic.BadRequestError:
# 速度指定が無効になった場合はここに落ちる(400系)
return False
except anthropic.APIStatusError as e:
# 一時的な 5xx 等は「不明」として扱い、呼び出し側で再試行させる
raise RuntimeError ( f "プローブ判定不能(一時障害の可能性): { e } " ) from e
# max_tokens=1 のプローブは、本処理を1回走らせるより桁違いに安い
400系のエラー(BadRequestError)と、5xx の一時障害を分けて扱うのが肝です。速度指定の失効は前者として返ってくるので「移行すべき」と確定できます。後者を「失効」と誤判定すると、単なるネットワークの揺れで不要な移行を走らせてしまいます。判定不能なものは判定不能として扱い、上位のリトライに委ねます。
失効を検知したら移行して自己修復する
期日チェックと実プローブを組み合わせ、失効していたらレジストリの successor へ切り替えます。無人運用では、切り替えたことを必ずログに残して後から追跡できるようにします。
def resolve_capability (model: str , speed: Optional[ str ]) -> tuple[ str , Optional[ str ]]:
"""使うべき能力ペアを解決する。失効していれば移行先を返す。"""
cap = lookup(model, speed)
if cap is None :
raise RuntimeError ( f "未登録の能力ペア: { model } / { speed } " )
# まず静的な期日で判定(プローブより速く・無料)
if cap.retires_on and date.today() >= cap.retires_on:
if cap.successor is None :
raise RuntimeError ( f " { model } / { speed } は失効済みだが移行先が未定義です" )
print ( f "↪ 期日超過につき移行: { model } / { speed } → "
f " { cap.successor.model } / { cap.successor.speed } " )
return cap.successor.model, cap.successor.speed
# 期日前でも、実プローブが落ちたら前倒し失効とみなす
if not probe_capability(cap.model, cap.speed):
if cap.successor is None :
raise RuntimeError ( f " { model } / { speed } が失効。移行先が未定義です" )
print ( f "↪ プローブ失敗につき移行: { model } / { speed } → "
f " { cap.successor.model } / { cap.successor.speed } " )
return cap.successor.model, cap.successor.speed
return cap.model, cap.speed
# パイプラインの入口で1回だけ解決する
MODEL , SPEED = resolve_capability( "claude-opus-4-7" , "fast" )
# 以降の本処理は MODEL / SPEED を使う
こう組むと、7/24 をまたいでも人手を介さずに Opus 4.8 の fast モードへ滑らかに移ります。期日を過ぎても、当日ロールアウトがずれてプローブがまだ通るなら本来の組み合わせを使い続けます。予告と実測の両輪で判断するのが、無人運用での安心につながります。
同じ発想を「モデルIDそのものの引退」に広げたい場合は、使用中モデルIDの引退監査の実装 も併せて設計すると、モデルID・能力ペアの両レイヤーを守れます。廃止予定を暦として一元管理する考え方はリクエスト要素の廃止カレンダーと preflight にまとめています。
fail-closed か fail-open か — 無人運用の判断
移行先すら使えない、あるいはプローブが判定不能を返し続ける場合、どう振る舞うべきかは処理の性質で変わります。
状況 推奨 理由
課金・決済に絡む処理 fail-closed(停止) 誤ったモデルで走らせるより、止めて通知する方が安全
記事下書き生成などの再実行可能な処理 速度指定を外して継続 fast が使えなくても標準速度で成果物は出せる
期日前でプローブが一時障害 上位のリトライへ委譲 ネットワーク揺れを失効と誤判定しない
私の場合、再実行が効く下書き生成のジョブでは「fast が失効したら speed 指定を外して標準速度で続行し、移行のログだけ残す」という緩い倒し方にしています。レイテンシは伸びますが、パイプライン全体を止めるより実害が小さいからです。判断の分岐点は「そのジョブは、間違ったまま完走させるより止めた方が安全か」の一問に集約できます。決済系は迷わず fail-closed に倒すことを推奨します。
移行後に確認しておきたい差分
Opus 4.8 の fast モードへ移ったあと、動いたことだけで満足せず、コストとレイテンシの実測を1回は取っておくことをお勧めします。速度指定の挙動やトークン単価はモデル世代で変わり得るため、予算アラートの閾値がずれることがあります。
import time
def measure (model: str , speed: Optional[ str ], prompt: str , n: int = 5 ) -> dict :
"""移行前後の比較用に、レイテンシと出力トークンを軽く実測する。"""
latencies, out_tokens = [], []
for _ in range (n):
kwargs = { "model" : model, "max_tokens" : 256 ,
"messages" : [{ "role" : "user" , "content" : prompt}]}
if speed is not None :
kwargs[ "speed" ] = speed
t0 = time.perf_counter()
resp = client.messages.create( ** kwargs)
latencies.append(time.perf_counter() - t0)
out_tokens.append(resp.usage.output_tokens)
latencies.sort()
return {
"p50_latency_s" : round (latencies[n // 2 ], 3 ),
"avg_output_tokens" : round ( sum (out_tokens) / n, 1 ),
}
# 移行前後で同じプロンプトを流し、閾値の再調整が必要か判断する
数値を1回取っておけば、「fast にしたのに思ったより速くない」「単価が変わって月次予測がずれた」といった二次障害を、静かに進行する前に捕まえられます。移行そのものより、移行後の観測を省かないことが、無人運用では効いてきます。
よくある落とし穴
speed を環境変数やジョブ定義に散らかす : 各ジョブが独自に speed="fast" を持っていると、失効時に全ジョブを横断修正する羽目になります。能力ペアの解決は入口の1関数に集約してください。
プローブを本処理と違う能力ペアで投げる : プローブだけ標準速度で投げて「生きている」と判定し、本処理で fast を使って落ちる、という取り違えが起きます。プローブは必ず本処理と同じ (model, speed) で投げます。
一時障害を失効と誤判定する : 5xx やタイムアウトを「失効」に丸めると、ネットワークの揺れのたびに不要な移行が走ります。400系だけを失効の根拠にしてください。
期日を過ぎたら即停止に倒す : 当日ロールアウトのずれで、期日直後もしばらく旧組み合わせが通ることがあります。期日は警告に使い、停止の最終判断は実プローブに委ねると取りこぼしと過剰停止の両方を避けられます。
移行後の実測を省く : 動いた=最適、ではありません。移行後に一度だけでもレイテンシと単価を測ると、予算アラートの誤発報を防げます。
次の一手
いま動いているコードで speed="fast" を grep してみてください。1件でもヒットして、そのモデルが claude-opus-4-7 なら、この記事の resolve_capability をパイプラインの入口に1つ差し込むところから始められます。期日を待つのではなく、能力ペアという単位で先に守っておく——それが、7/24 の朝を平穏に迎えるいちばん確実な準備だと考えています。