9 月 30 日の夜、受信箱に Anthropic からの通知が一通届いておりました。件名は Claude Sonnet 4.5 の退役のお知らせで、本文には 11 月 30 日という日付と、推奨の置き換え先として claude-sonnet-5-5 が書かれています。
私が個人開発で運用している壁紙アプリでは、ストアの説明文やお知らせの文面を多言語に整える小さなスクリプトが、claude-sonnet-4-5-20250929 を日付付きの ID で固定したまま動いております。料金表を見れば、置き換え先の単価は入力 3 ドルから 2 ドルへ、出力 15 ドルから 10 ドルへ下がるはずです。安くなるのなら急ぐ理由はない——そう思ってメールを閉じかけたところで、手が止まりました。
安くなる、と決めたのは料金表の一列だけを見た私の側で、請求がどう変わるかはまだ何も確かめていなかったのです。
通知が示す三つの日付と、私が最初に読み違えたこと
公式の廃止表には、モデルごとに「Deprecated(非推奨)」と「Retirement(退役)」の二つの日付が並びます。claude-sonnet-4-5-20250929 は 2026 年 9 月 30 日に非推奨となり、退役は 2026 年 11 月 30 日です。非推奨のあいだはこれまで通り応答が返りますが、退役日を過ぎた呼び出しは失敗します。
私が最初に読み違えたのは、この「まだ動く」という状態の意味でした。日付付きの ID で固定している限り、エラーは一度も出ません。何も変わらないまま 11 月 30 日を迎え、その朝から急に止まるのです。別名(alias)で呼んでいれば新しい世代へ自動で乗り換わりますが、再現性のために日付を固定した箇所ほど、置き換えを忘れやすいのかもしれません。
棚卸しの入口は二つあります。一つは Claude Console の Usage ページで、Export を押すと API キーとモデルごとの使用量が CSV で落ちてきます。もう一つはコード側の grep です。前者は「実際に呼ばれているか」を、後者は「どこに書いてあるか」を教えてくれるので、両方を突き合わせます。
# 何を解決するか: Console から Export した usage の CSV を読み、
# 退役予定のモデル ID がどの API キーから呼ばれているかを一覧にします。
import csv
import sys
from collections import defaultdict
RETIRING = {"claude-sonnet-4-5-20250929": "2026-11-30"}
def summarize(path: str) -> None:
hits: dict[tuple[str, str], int] = defaultdict(int)
with open(path, newline="", encoding="utf-8") as f:
for row in csv.DictReader(f):
model = row.get("model", "")
if model in RETIRING:
key = row.get("api_key_name") or row.get("api_key", "unknown")
tokens = int(row.get("input_tokens", 0) or 0) + int(row.get("output_tokens", 0) or 0)
hits[(key, model)] += tokens
if not hits:
print("退役予定のモデルは呼ばれていません")
return
for (key, model), tokens in sorted(hits.items(), key=lambda x: -x[1]):
print(f"{key}\t{model}\t退役 {RETIRING[model]}\t{tokens:,} tokens")
if __name__ == "__main__":
summarize(sys.argv[1] if len(sys.argv) > 1 else "usage.csv")列名は Export の時期によって揺れることがあるので、row.get で取りこぼしを拾う書き方にしています。コード側は grep -rn "claude-sonnet-4-5" --include="*.py" --include="*.ts" --include="*.env*" . の一行で十分で、私のスクリプトでは設定ファイルと、テスト用のフィクスチャの二箇所に残っておりました。
単価表どおりなら三割引き、ところがトークンの数え方が変わっていました
料金表を素直に読むと、入力は 3 ドルから 2 ドル、出力は 15 ドルから 10 ドルですから、どちらも三分の一の値下げです。ところが同じページの下のほうに、見落としやすい一文があります。Claude 4.7 以降のモデルは新しいトークナイザを使い、同じ文章でおよそ 30% 多くトークンを生成する、という注記です。Sonnet 4.6 以前は旧トークナイザのままですから、4.5 から 5.5 への移行はまさにこの境界をまたぎます。
つまり、単価が 3 分の 2 になっても、数えられるトークンは 1.3 倍になるのです。掛け合わせると、請求はおよそ 13% の減少に落ち着きます。三割引きのつもりで予算を組み直すと、ひと月の終わりに首をかしげることになるかもしれません。
| 項目 | Sonnet 4.5 | Sonnet 5.5(単価) | Sonnet 5.5(トークン 1.3 倍で換算) |
|---|---|---|---|
| 入力 / MTok | $3.00 | $2.00 | 約 $2.60 相当 |
| 出力 / MTok | $15.00 | $10.00 | 約 $13.00 相当 |
| キャッシュ書き込み(5 分) | $3.75 | $2.50 | 約 $3.25 相当 |
| キャッシュ読み取り | $0.30 | $0.20 | 約 $0.26 相当 |
30% という数字は公式の目安であって、文章の種類で上下します。日本語の説明文と英語の説明文、見出しだけの短文と長い段落では、増え方が違うはずです。ですから置き換え先を決める前に、ご自身の原稿で一度数えていただきたいのです。Messages API には送信前にトークン数だけを返す count_tokens があり、同じ原稿を二つのモデル名で数えれば、増加率がその場で分かります。
# 何を解決するか: 自分の原稿で、旧モデルと新モデルの入力トークン数を比べます。
# 料金表の「約 30%」が、自分の文章では何 % なのかを確かめるためのものです。
import os
import anthropic
client = anthropic.Anthropic(api_key=os.environ.get("ANTHROPIC_API_KEY", "YOUR_API_KEY"))
OLD = "claude-sonnet-4-5-20250929"
NEW = "claude-sonnet-5-5"
def count(model: str, system: str, user: str) -> int:
res = client.messages.count_tokens(
model=model,
system=system,
messages=[{"role": "user", "content": user}],
)
return res.input_tokens
if __name__ == "__main__":
system = open("prompts/store_description_system.txt", encoding="utf-8").read()
user = open("samples/ja_release_note.txt", encoding="utf-8").read()
old_n = count(OLD, system, user)
new_n = count(NEW, system, user)
ratio = new_n / old_n if old_n else float("nan")
print(f"{OLD}: {old_n} tokens")
print(f"{NEW}: {new_n} tokens")
print(f"増加率: {ratio:.2f} 倍 -> 入力単価換算 ${2.0 * ratio:.2f} / MTok(旧 $3.00)")数える対象は、本番で実際に送っている system プロンプトと、代表的なユーザー入力の組にしてください。短いサンプルだけで測ると、system プロンプトの固定部分が占める割合が大きく出て、増加率を読み違えます。私はストア説明文の原稿を言語別に数本ずつ用意して、いちばん増え方の大きかった組を予算の根拠にしました。
単価より先に止まったのは temperature でした
数え終えて、試しに一本だけモデル名を書き換えて送ったところ、返ってきたのは応答ではなく 400 でした。本文を読むと、指摘されていたのは temperature です。
廃止表の下段には、モデルだけでなくリクエストパラメータの非推奨も載っています。temperature・top_p・top_k は Claude 4.7 以降で非推奨となり、既定値以外を指定すると 400 が返ります。さらに Python SDK は v1.0 以降でこれらの引数そのものを取り除いたため、SDK を上げると 400 ではなく TypeError として、送信前に落ちるようになります。
私のスクリプトは、訳文のばらつきを抑えたくて temperature=0.2 を長く付けておりました。4.5 では何事もなく通っていた一行が、5.5 では門前払いになる——料金表だけを見ていたら、退役日の朝にこの 400 と初めて向き合うことになっていたはずです。
公式の置き換え先は「パラメータを外し、プロンプトで振る舞いを導く」ことです。私の場合は「訳語は添付の用語表に従い、言い換えを足さない」という指示を system に一段落足したところ、ばらつきは 0.2 を付けていたころと体感で変わりませんでした。
# 何を解決するか: 旧モデルでは通っていた temperature を、新モデルでは自動で外します。
# モデル名を切り替える移行期間に、両方の呼び出しを一つの関数で受けるための書き方です。
import os
import anthropic
client = anthropic.Anthropic(api_key=os.environ.get("ANTHROPIC_API_KEY", "YOUR_API_KEY"))
# 旧トークナイザ世代(4.6 以前)だけがサンプリング引数を受け付けます
LEGACY_SAMPLING_MODELS = {"claude-sonnet-4-5-20250929", "claude-sonnet-4-6"}
def translate(model: str, system: str, text: str, temperature: float | None = 0.2) -> str:
kwargs = {
"model": model,
"max_tokens": 1024,
"system": system,
"messages": [{"role": "user", "content": text}],
}
if temperature is not None and model in LEGACY_SAMPLING_MODELS:
kwargs["temperature"] = temperature
try:
res = client.messages.create(**kwargs)
except anthropic.BadRequestError as e:
# 4.7 以降に temperature を渡したときはここに来ます。設定の見直し先を明示して止めます
raise RuntimeError(f"{model} は temperature を受け付けません。system プロンプトで代替してください: {e}") from e
return "".join(block.text for block in res.content if getattr(block, "type", "") == "text")
if __name__ == "__main__":
system = "あなたはアプリストア向けの翻訳者です。訳語は用語表に従い、言い換えを足しません。"
print(translate("claude-sonnet-5-5", system, "新しい壁紙を 30 点追加しました。"))なぜこう書くかと言えば、移行期間には旧 ID と新 ID が同じコードベースに混在するからです。モデル名で引数を出し分けておけば、設定ファイルの一行を書き換えるだけで切り替えられ、戻すときも同じ一行で済みます。SDK を v1.0 以降へ上げるときは LEGACY_SAMPLING_MODELS ごと消して、temperature の引数も削る——その順番を忘れないように、コメントで残しております。
置き換えの順番と、私が引いた線引き
通知を受けてから私が辿った順番は、こうなりました。
- Console の Usage を Export し、退役予定の ID がどのキーから呼ばれているかを確かめます。
- コードを grep し、設定・フィクスチャ・ドキュメントに残る日付付き ID を数えます。
count_tokensで本番相当の原稿を数え、増加率から請求の見込みを出します。temperatureなどのサンプリング引数を外し、代わりの指示を system に足します。- 一本だけ切り替えて数日運用し、訳文の品質と請求を見ます。
- 残りを切り替え、退役日より前に旧 ID をコードから消します。
一方で、急いで全部を書き換えなかった理由もあります。別名の claude-sonnet-5-5 を指すのか、その日付付き ID を改めて固定するのかは、再現性をどこまで求めるかで変わるからです。私は本番は日付付きで固定し、検証用のスクリプトだけ別名にして、次の世代が来たときの差分を早めに受け取れるようにしました。ID を一括で置き換える前に手元のカタログを引いておく手順は、モデルIDを一括置換する前に、手元の CLI が抱えるカタログを引きますに書き残しておりますので、置き換え箇所が多い方はあわせてご覧ください。
この一連の作業で私が引いた線引きは、一文にするとこうなります。
単価は料金表で、請求は自分の原稿で確かめる。
値下げの知らせは嬉しいものですが、トークンの数え方とパラメータの扱いまで含めて初めて「安くなった」と言えるのだと、400 を一度見て気づきました。
まずは今日、Console の Usage を Export して、モデルの列に claude-sonnet-4-5 が残っているかだけを見ていただければと思います。残っていれば、退役日までの残り日数が、そのまま皆さまの作業の締切です。私もその一行から始めました。