要約機能の出力を安定させたくて、数か月前に temperature: 0 を足していたとします。そのアプリを久しぶりに触り、依存パッケージをまとめて更新して再デプロイしたところ、要約だけが 400 エラーで返ってくるようになりました。モデルも、プロンプトも、触った覚えはありません。
原因として心当たりを探すなら、まず疑ってよいのが Claude API の新しいバージョン 2026-10-21 です。Anthropic の API バージョニングのページには、このバージョンで temperature・top_p・top_k が削除され、どれか一つでも送ったリクエストは 400 を返すと書かれています。
ただ、すべてのアプリが今すぐ壊れるわけではありません。壊れるのは「新しいバージョンを送り始めた」アプリだけです。ここでは、その条件の見分け方と、アプリ内の該当箇所を洗い出すスクリプト、直す順番をお伝えします。
壊れるのは、バージョンを切り替えた瞬間です
公式ページの確認できた範囲を整理します。API バージョンは 2023-01-01・2023-06-01・2026-10-21 の3つで、リクエストには anthropic-version ヘッダーを必ず付ける決まりです。公式 SDK を使っている場合、このヘッダーは SDK が自動で付けてくれます。
つまり、アプリがどのバージョンを送っているかは、どう呼び出しているかで決まります。
| 呼び出し方 | 送られるバージョン | temperature を指定していた場合 |
|---|---|---|
fetch で直接呼び、ヘッダーに 2023-06-01 を書いている | 自分で書き換えるまで旧版のまま | 今は動き続けます。ヘッダーを新版に変えた瞬間に 400 です |
公式 SDK 0.x 系(例: ^0.137.0) | 旧版 2023-06-01 | 今は動き続けます |
| 公式 SDK 1.0.0 以上(TypeScript) | 新版 2026-10-21 | 送った時点で 400 です |
3行目の根拠は、公式ページではなく、私が自分で確かめた結果です。npm から @anthropic-ai/sdk の 0.137.0 と 1.0.0 の中身を取り出して読み比べたところ、client.js の既定ヘッダーが 0.137.0 では 2023-06-01、1.0.0 では 2026-10-21 でした。1.0.0 の公開日は npm 上で 2026年10月21日、CHANGELOG には「add support for new API version 2026-10-21」とあります。
もう一つ、見落としやすい点があります。package.json に ^0.137.0 と書いてあっても、0.x 系のキャレット指定は 0.137.x の範囲に収まるため、npm update で 1.0.0 まで自動では上がりません。壊れるのは「メジャーを上げる」と決めた日です。逆に言えば、その日の前に洗い出しておけば、当日は慌てずに済みます。
洗い出しスクリプト
依存パッケージは不要で、Node.js だけで動きます。見るのは3つです。temperature・top_p・top_k の指定、anthropic-version ヘッダーの値、そして package.json の SDK のバージョン指定。他社 API の temperature まで拾わないよう、anthropic か claude の文字を含むファイルだけを対象にしています。
// scan-claude-api-params.mjs — API バージョン 2026-10-21 で 400 になる指定を洗い出す
// 使い方: node scan-claude-api-params.mjs ./my-app
import { readdirSync, readFileSync, statSync } from "node:fs";
import { join, relative, extname } from "node:path";
const root = process.argv[2] ?? ".";
const SKIP_DIRS = new Set(["node_modules", ".git", ".expo", "ios", "android", "dist", "build"]);
const EXTS = new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".json", ".sql"]);
const samplingRe = /\b(temperature|top_p|top_k)\b\s*[:=]/;
const versionRe = /anthropic-version['"]?\s*[:=,]\s*['"](\d{4}-\d{2}-\d{2})['"]/i;
const sdkRe = /"@anthropic-ai\/sdk"\s*:\s*"([^"]+)"/;
const claudeRe = /anthropic|claude/i;
const rows = [];
function walk(dir) {
for (const name of readdirSync(dir)) {
if (SKIP_DIRS.has(name)) continue;
const path = join(dir, name);
const st = statSync(path);
if (st.isDirectory()) { walk(path); continue; }
if (!EXTS.has(extname(name)) || st.size > 1_000_000) continue;
const text = readFileSync(path, "utf8");
const rel = relative(root, path);
const isClaudeFile = claudeRe.test(text); // Claude を呼ぶファイルだけ見る(他社 API の temperature を除く)
text.split("\n").forEach((line, i) => {
const at = `${rel}:${i + 1}`;
const sdk = line.match(sdkRe);
if (sdk) rows.push({ kind: "SDK", at, note: `@anthropic-ai/sdk ${sdk[1]}(メジャー 1 以上へ上げた時点で新バージョンを送る)`, bad: false });
const ver = line.match(versionRe);
if (ver) rows.push({ kind: "VERSION", at, note: `anthropic-version ${ver[1]}${ver[1] >= "2026-10-21" ? "(400 の対象)" : "(旧版を送信中)"}`, bad: false });
const s = isClaudeFile && line.match(samplingRe);
if (s) rows.push({ kind: "要対応", at, note: `${s[1]} を指定(2026-10-21 では送るだけで 400)`, bad: true });
});
}
}
walk(root);
for (const r of rows) console.log(`${r.kind} | ${r.at} | ${r.note}`);
const bad = rows.filter((r) => r.bad).length;
console.log(`\n検出 ${rows.length} 件 / 要対応 ${bad} 件`);
process.exit(bad > 0 ? 1 : 0);使い方は次のとおりです。
node scan-claude-api-params.mjs ./my-app動作確認は、5つのファイルを置いたダミーのプロジェクトで行いました。Edge Function 風の fetch(2023-06-01 と temperature: 0)、SDK を使う画面側のコード(temperature と top_p)、^0.137.0 を書いた package.json、他社 API を呼ぶだけのファイル、そして node_modules の中の同名の文字列です。実行結果は次のとおりでした。
SDK | package.json:1 | @anthropic-ai/sdk ^0.137.0(メジャー 1 以上へ上げた時点で新バージョンを送る)
要対応 | src/caption.ts:3 | temperature を指定(2026-10-21 では送るだけで 400)
VERSION | supabase/functions/summarize/index.ts:3 | anthropic-version 2023-06-01(旧版を送信中)
要対応 | supabase/functions/summarize/index.ts:4 | temperature を指定(2026-10-21 では送るだけで 400)
検出 4 件 / 要対応 2 件
他社 API のファイルと node_modules が拾われていないことも、この実行で確かめています。要対応が1件でもあると終了コードが 1 になるので、ビルド前の確認にも使えます。
限界も書いておきます。1行ずつ文字列で探しているだけなので、設定を別ファイルにまとめて ...options のように展開している場合は見つかりません。Python など他の言語で書いたサーバーも対象外です。「0 件だったから安全」と読むより、「見つかった分は確実に直す」ための道具と考えていただければと思います。
直す順番は、バージョンを上げる前です
temperature を外す変更は、旧バージョンのままでも問題なく動きます。付けていた引数を省くだけだからです。この性質を使うと、順番がきれいに決まります。
- 先に
temperature・top_p・top_kを外して、今のバージョンのままデプロイします。 - 出力の様子を見て、問題がなければそのまま運用します。
- 後日、SDK のメジャーを上げる、またはヘッダーを新版に書き換えます。
直し方は単純です。Before / After で並べます。
// Before: 要約の揺れを抑えたくて temperature: 0 を指定していた
body: JSON.stringify({ model: "claude-sonnet-5-5", max_tokens: 400, temperature: 0, messages }),
// After: サンプリングの指定を外し、形式はプロンプトで縛る
body: JSON.stringify({
model: "claude-sonnet-5-5",
max_tokens: 400,
system: "要約は3文以内、各文は80文字以内の敬体で書いてください。",
messages,
}),個人開発で私がこの手の変更を入れるときに気をつけているのは、temperature: 0 を「同じ入力なら同じ出力になる保証」として使っていた箇所です。外した後は、出力が毎回少しずつ変わる前提で作り直す必要があります。JSON を返してもらう機能なら、受け取った文字列を JSON.parse で検証し、失敗したら1回だけ再試行します。文字数に上限がある画面なら、返ってきた文章を表示側で切り詰めます。こうした受け手側の備えを先に入れておくと、外した後の挙動が読みやすくなります。
Anthropic の公式ページは、代わりの設定値を示していません。「何で置き換えるか」ではなく、「指定しなくても壊れない作りにする」と考えるのが近道かもしれません。
モデルの置き換えとは、別のデプロイにします
11月30日には Claude Sonnet 4.5 の API 提供終了が予定されています。モデル ID の洗い出しは Rork で作ったアプリが Sonnet 4.5 を呼んでいないか、11月30日の前に洗い出します に書きました。
ここで一つ、お勧めしたいことがあります。モデル ID の変更と、サンプリング指定の削除は、同じデプロイにしないことです。同時に変えて 400 や出力の違和感が出ると、どちらが原因か分からなくなります。サンプリング指定の削除(旧バージョンでも安全)を先に出し、翌日以降にモデル ID を差し替えます。この順番なら、問題が起きたときの切り分けが一手で済みます。
SDK のメジャーを上げる前の点検という意味では、SDK 58 で削除された React Native の API を、アップグレード前に洗い出すスクリプト と同じ考え方です。
今日できる最初の一歩
プロジェクトのフォルダでスクリプトを一度走らせて、「要対応」の件数と、VERSION の行に出た値を見てみてください。VERSION が 2023-06-01 のままで、SDK も 0.x 系であれば、今日壊れるものはありません。要対応が見つかった場合だけ、その行を外す変更を、旧バージョンのまま先に出しておきます。
公式ページの内容は更新されることがあります。切り替えの前に、API バージョニングのページを一度だけ見直しておくことをお勧めします。