Claude APIでtemperatureが400になる — 2026-10-21の破壊的変更3つ
2026-10-21では、assistantのdocument、userのcitations、temperature・top_p・top_kを含むリクエストが400になります。送信コード別の直し方と、旧版に残す判断を示します。
APIバージョンを 2026-10-21 に上げると、Messages APIのリクエストは次の3つの形で400を返します。assistantメッセージに直接置いた document ブロック、userメッセージの text ブロックに付いた citations、そして temperature・top_p・top_k です。3つ目は既定値を送っても落ちます。
この変更はMessages APIだけでなく、トークン数のカウントと、2026-10-21 で作ったMessage Batchの各リクエストにも効きます。SDKの新しいメジャーバージョンを入れた時点で 2026-10-21 で送信が始まるため、「SDKを上げたら急に400が出た」という形で表面化します。
ここでは、3つの変更を送信コードの種類別に直す手順を並べます。モデル単位で temperature が拒否される話はClaude Sonnet 5のtemperatureとtop_p・top_kが400になる理由にあります。本記事はAPIバージョンを上げたときに、どのモデルでも起きる側を扱います。
400になる3つを一覧で押さえる
2026-10-21で400になる送信内容
assistantのdocumentブロック
assistantメッセージの直下に
documentを置くと400です。userメッセージへ移します。userのtextに付いたcitations
textブロックのcitationsが空でない配列だと400です。search_resultの中も同じです。temperature・top_p・top_k
3つとも、既定値でも400です。リクエストから消します。
通るままのものも押さえておくと、直す範囲を絞れます。
| 送信内容 | 2026-10-21での扱い |
|---|---|
ツール結果の中に入れ子になった document(Claudeが返したweb fetchの結果など) | 2026-10-21での扱い通る |
assistantメッセージの text ブロックに付いた citations(Claudeの過去の返信をそのまま送り返す場合) | 2026-10-21での扱い通る |
ドキュメントや検索結果に付ける "citations": {"enabled": true} | 2026-10-21での扱い通る |
拒否されるのは「userが書いた側の引用」と「assistantが書いたことにした文書」で、Claudeが返した形をそのまま戻す使い方は残ります。会話履歴をそのまま保存して再送するアプリは、この違いで当たり外れが分かれます。
自分のコードが当たるかを、送信前の検査で洗い出す
3つとも、ステータスコード以外にドキュメントが文言を示していません。エラー文言の一致で判定するコードは書かず、送る前にペイロードを検査するほうが確実です。次のスクリプトは、保存してある会話履歴やテスト用のリクエストJSONを読み、3種類の該当箇所を位置つきで出します(requests.jsonl の1行に1リクエストが入っている想定です)。
python3 find_400_risks.py requests.jsonlimport json, sys
def walk_text_blocks(content):
"""userメッセージのcontentから、citations付きtextを探す(search_result内も含む)。"""
if not isinstance(content, list):
return
for block in content:
if not isinstance(block, dict):
continue
if block.get("type") == "text" and block.get("citations"):
yield "user text に citations"
if block.get("type") == "search_result":
yield from walk_text_blocks(block.get("content"))
def find_risks(req):
risks = [f"{k} を送っている" for k in ("temperature", "top_p", "top_k") if k in req]
for i, msg in enumerate(req.get("messages", [])):
content = msg.get("content")
if msg.get("role") == "assistant" and isinstance(content, list):
if any(b.get("type") == "document" for b in content if isinstance(b, dict)):
risks.append(f"messages[{i}]: assistant 直下の document")
if msg.get("role") == "user":
risks += [f"messages[{i}]: {r}" for r in walk_text_blocks(content)]
return risks
for n, line in enumerate(open(sys.argv[1]), 1):
for r in find_risks(json.loads(line)):
print(f"line {n}: {r}")検査対象はリクエスト本体なので、ログに落とした会話をそのまま流し込めます。バッチ用のJSONLなら、各行の params を取り出してから渡します。ツール結果の中に入れ子になった document は検出しません。そこは拒否されないためです。
assistantのdocumentは、userメッセージへ移す
assistantメッセージに document を置く実装は、たとえば「Claudeが整形した添付ファイルを、次のターンでもassistantの発話として保持する」ような履歴管理で生まれます。2026-10-21 では、この置き方そのものが400です。
直し方は、文書をuserメッセージに載せ替えることです。
documentの置き場所を変える
2023-06-01では通っていた形
{"role": "assistant",
"content": [
{"type": "document", "source": {"type": "text",
"media_type": "text/plain", "data": "議事録の本文"}}
]}2026-10-21で通る形
{"role": "user",
"content": [
{"type": "document", "source": {"type": "text",
"media_type": "text/plain", "data": "議事録の本文"}},
{"type": "text", "text": "この文書を踏まえて続けてください。"}
]}userメッセージ同士が連続しても構いません。履歴を組み立てる側で、assistantの発話として文書を保持していた箇所を、userのブロックへ移す処理を1か所に集めておくと漏れません。文書をassistantの発話として扱っていた意図が「Claudeの出力だった」ことなら、その文書をテキストとして引用する形に書き換える選択もあります。
userのcitationsは外す。enabledとassistant側は残す
citations には2つの顔があります。入力側の設定である "citations": {"enabled": true} と、応答に付いてくる引用の配列です。400になるのは後者を、userメッセージの text ブロックに載せた場合だけです。
起きやすいのは、検索結果やRAGの断片を search_result に包み、その中の text に過去の引用情報をそのまま付けて再送するコードです。Claudeの返信をコピーしてuserのテキストに貼る処理でも起きます。
def strip_user_citations(messages):
"""userメッセージのtextブロックから citations を落とす。assistant側は触らない。"""
def clean(blocks):
out = []
for b in blocks:
if not isinstance(b, dict):
out.append(b)
continue
b = dict(b)
if b.get("type") == "text":
b.pop("citations", None)
elif b.get("type") == "search_result":
b["content"] = clean(b.get("content", []))
out.append(b)
return out
return [
{**m, "content": clean(m["content"])}
if m["role"] == "user" and isinstance(m["content"], list) else m
for m in messages
]引用が必要なのは「Claudeが文書を引いて答える」場面で、その設定は enabled で行います。引用の返り方がドキュメント種別で変わる点はClaude Citationsのドキュメント種別ごとの返り方にまとめています。引用つきの返信を履歴に積むアプリは、assistantの返信を無加工で戻していれば影響を受けません。userの発話として組み直している箇所だけが対象です。
temperature・top_p・top_kは消す。消すと挙動が変わるモデルがある
3つ目は、実コードでいちばん多く当たります。2026-10-21 では、既定値を指定していても400です。SDKは新しいメジャーバージョンでこの3つの引数そのものを持たないため、古い呼び出しは型エラーやコンパイルエラーで先に気づけます。cURLや自作のHTTPクライアントでは、ペイロードのキーを自分で消す必要があります。
{
"model": "claude-opus-5-5",
"max_tokens": 1024,
"messages": [{"role": "user", "content": "Hello, Claude"}]
}上のように3つを抜いたリクエストは、2023-06-01 で3つを省いた場合と同じ既定値、つまり temperature は1.0で動きます。Claude 4.7以降のように、元から既定値しか受け付けないモデルは、これまでと同じサンプリングです。
注意が要るのは、既定値以外を受け付けていたモデルです。Claude Sonnet 4.6の例が、移行ガイドに載っています。出力を安定させるために temperature を下げていたリクエストは、パラメーターを消すと、その効果を失います。代わりになる別の設定はありません。
どうしても非既定値に頼るリクエストが残るなら、選択肢は2つです。
- パラメーターを送るのをやめて、既定値のサンプリングで受け入れる
- そのリクエストだけ
2023-06-01に留める
後者は、APIバージョンをリクエスト単位で選べる呼び出し経路に限った話です。SDKを新しいメジャーバージョンに上げると既定で 2026-10-21 を送るため、旧版を残すには、旧メジャーのSDKを使うか、anthropic-version ヘッダーを自分で指定します。Sonnet 4.6から5世代のモデルに替える予定があるなら、どのみち後でこのパラメーターは使えなくなります。モデルの違いはClaude Sonnet 4.6と5.5の違いで比べています。
切り替えの順番と、Bedrockで古いモデルを使う場合
SDKを使う場合、リクエストが送るAPIバージョンは、そのSDKのメジャーバージョンが出た時点の最新版に決まります。移行先になるのは、Pythonが anthropic 2.0.0、TypeScriptが @anthropic-ai/sdk 1.0.0、Goが github.com/anthropics/anthropic-sdk-go/v2 の2.0.0です。SDKを使わない場合は、anthropic-version ヘッダーに 2026-10-21 を指定します。
本番に出すまでの順番
- 1
SDKを上げ、送信コードを直す
SDKの更新と、上で扱った3つの修正は、同じリリースに載せます。SDKだけ先に出すと、直していない箇所が本番で400になります。
- 2
検査スクリプトで保存済みの会話を流す
履歴を保存して再送するアプリは、過去の会話に古い形が残っています。前節のスクリプトで洗い出します。
- 3
テスト環境で2026-10-21を指定して通す
モデルごと、トークン数カウント、バッチを含めて確認します。
Claude Codeを使っている場合は、/claude-api api-version でAPIバージョンの移行を手伝わせる手順がClaude Code v2.1.307のリリースで加わっています。コードの書き換え提案を受けたら、差分に3つのパラメーターが残っていないかを必ず見てください。
Amazon Bedrockでは、2026-10-21 を使えるのはClaude 4.7以降のモデルだけです。Opus 4.6以前など、Bedrockの旧モデルを呼んでいる場合は、今のSDKバージョンのままにするか、ヘッダーで版を指定している場合は今の値を送り続けます。その呼び出しを 2026-10-21 に移したいなら、先にClaude 4.7以降のモデルへ切り替えます。
400とは別に、SDKを上げると変わる2点
400ではないものの、SDKの更新と一緒に動いて原因の切り分けを難しくする変更が2つあります。
APIキーの送り方。SDKとCLIは、APIキーを X-Api-Key ではなく Authorization: Bearer <key> で送ります。プロキシやゲートウェイが X-Api-Key を読んで認証している構成では、読む場所を Authorization に変えます。ログのフィルターやテスト用のスタブも同じです。Claude APIはどちらのヘッダーでも受け付けるため、API側で拒否されることはありません。SDKにミドルウェアを挟んでヘッダーを制御する方法はClaude SDKのミドルウェアで共通処理を挟むの延長で書けます。
Admin APIとAnalytics APIの項目名。応答から旧名が消えます。RBACグループの roles は role_ids に、APIキーの workspace_id は scope に変わります。Analytics APIでは、ユーザー別の使用量・コストのレポートで actor.email が actor.email_address に、活動サマリーの summaries が data になります。これらは、リクエストではなく応答を読むコードの変更です。ダッシュボードやレポート用のスクリプトが突然空になったら、この改名を疑ってください。
まとめ
3つの変更のうち、コードの規模で効くのは temperature 系です。残る2つは、履歴を保存して組み直すアプリにだけ当たります。まず保存済みの会話とリクエストに検査をかけ、該当がなければSDKを上げるだけで済みます。該当があれば、assistantのdocumentはuserへ、userのcitationsは外す、サンプリング引数は消す、の3点を同じリリースで出します。サンプリングの値で出力を安定させていたリクエストだけは、消すと挙動が変わるため、旧版に残すかプロンプト側で受け止めるかを決めておきます。