動画で読む
同じ messages.create を 3 版から投げたら、ボディは 319 バイトで 1 バイトも変わらなかった
anthropic-sdk-python が 1 週間で 3 回上がった。1.6.0 の Retry-After の話は前回に書いた。その後 1.7.0(09-18)が anthropic-beta ヘッダの結合を ", " から "," に変え、1.8.0(09-22)は request param transform を丸ごと消して JSON encoder に置き換えた。commit 539422c は 80 ファイル、+2888 / -3120 行ある。
送るヘッダとボディが変わるなら、こっちのコードを直す前に見ておきたい。API キーは無いので、本物の API には投げられない。代わりに http.server で echo サーバを 127.0.0.1
client = anthropic.Anthropic(api_key="sk-ant-dummy", base_url=f"http://127.0.0.1:8765/{label}", max_retries=0)
msg = client.messages.create(
model="claude-sonnet-5",
max_tokens=64,
system="You are a test.",
messages=[{"role": "user", "content": "hi"}],
tools=[{"name": "get_weather", "description": "Get weather", "input_schema": {...}}],
metadata={"user_id": "u-1"},
stop_sequences=None, # 明示的な None
tool_choice=anthropic.NOT_GIVEN, # 明示的な NOT_GIVEN
)
結果から書く。
1.6.0 body_bytes=319
1.7.0 body_bytes=319
1.8.0 body_bytes=319
$ diff <(body 1.6.0) <(body 1.7.0); diff <(body 1.7.0) <(body 1.8.0)
(identical bytes 1.6.0 = 1.7.0)
(identical bytes 1.7.0 = 1.8.0)
$ diff <(headers 1.6.0) <(headers 1.8.0)
9c9
< User-Agent: Anthropic/Python 1.6.0
---
> User-Agent: Anthropic/Python 1.8.0
11c11
< X-Stainless-Package-Version: 1.6.0
---
> X-Stainless-Package-Version: 1.8.0
ボディは 3 版で同一。ヘッダは 19 本のうちバージョン番号を含む 2 本だけが違い、順序も同じだった。届いたボディはこれで、キーはアルファベット順、stop_sequences は null として送られ、NOT_GIVEN にした tool_choice は落ちている。この挙動も 3 版で同じだ。
{"max_tokens":64,"messages":[{"role":"user","content":"hi"}],"model":"claude-sonnet-5","metadata":{"user_id":"u-1"},"stop_sequences":null,"system":"You are a test.","tools":[{"name":"get_weather","description":"Get weather","input_schema":{"type":"object","properties":{"city":{"type":"string"}},"required":["city"]}}]}
458 行のモジュールが消えて 210 行のモジュールが増えたのに、この呼び出しでは何も変わらない。書く前は「キーの順序か None の扱いのどちらかは変わるだろう」と思っていた。変わらなかった。
その前に、3 版とも temperature で TypeError を出した
最初に書いたスクリプトには temperature=0.2 が入っていた。3 版とも同じ例外で止まった。
TypeError: Messages.create() got an unexpected keyword argument 'temperature'
inspect.signature で Messages.create の引数を数えると 3 版とも 22 個で、temperature も top_p も top_k も無い。1.6.0 の時点でもう消えていて、この 3 リリースの差分ではない。いつ消えたかは今回追っていない。手元のコードにサンプリング系の引数が残っていれば、1.6 系より前から動いていないはずなので、1.8.0 に上げて初めて壊れるものではない。
本文の比較は temperature を外してからのものだ。
anthropic-beta の "," 修正が効くのは、middleware と OAuth の経路だけだった
1.7.0 の 1680324 は "join multiple anthropic-beta values with a comma and no space" とある。読んだときは betas=["a", "b"] の結合が変わるのだと思った。
echo サーバに 4 パターンを投げて、届いた anthropic-beta の文字列を並べた。
| 呼び方 | 1.6.0 | 1.7.0 | 1.8.0 |
|---|---|---|---|
beta.messages.create(betas=["a","b"]) | a,b | a,b | a,b |
messages.create(extra_headers={"anthropic-beta": "x, y"}) | x, y | x, y | x, y |
| 上の 2 つを同時に指定 | x, y | x, y | x, y |
BetaRefusalFallbackMiddleware + betas=["a","b"] | a,b, fallback-credit-2026-07-01 | a,b,fallback-credit-2026-07-01 | a,b,fallback-credit-2026-07-01 |
betas= は 1.6.0 から "," 結合だった。resources/beta/messages/messages.py に ",".join(str(e) for e in betas) と書いてあって、ここは commit で触られていない。1.7.0 の src 側の diff は 2 行しかない。
--- 1.6.0/lib/middleware/_fallbacks.py
+++ 1.7.0/lib/middleware/_fallbacks.py
- beta_header = {"anthropic-beta": ", ".join(filter(None, [current, *additions]))} if current or additions else {}
+ beta_header = {"anthropic-beta": ",".join(filter(None, [current, *additions]))} if current or additions else {}
--- 1.6.0/lib/credentials/_auth.py
+++ 1.7.0/lib/credentials/_auth.py
- request.headers["anthropic-beta"] = ", ".join(existing_flags)
+ request.headers["anthropic-beta"] = ",".join(existing_flags)
つまり変わるのは、refusal fallback の middleware が自分の beta を足すときと、OAuth の credentials が oauth-2025-04-20 を足すときだけ。表の 4 行目がそれで、1.6.0 だけ a,b, fallback と区切りが混在している。middleware も OAuth も使っていなければ、このリリースで届くヘッダは変わらない。
表の 3 行目は別の話として気になった。betas= と extra_headers を同時に渡すと、betas の a,b は消えて extra_headers の値だけが届く。マージされない。3 版で同じなので今回の差分ではないが、extra_headers で beta を足しているコードは betas= を上書きしている。
add_tools() は 1.7.0 に無い。1.8.0 では tools ではなく system ロールの tool_addition で届く
1.8.0 の Bug Fixes に "add_tools() takes effect straight away" とあるので、1.7.0 と 1.8.0 で tool runner の 2 回目のリクエストを比べるつもりだった。
$ grep -c "def add_tools" 1.7.0/_beta_runner.py 1.8.0/_beta_runner.py
1.7.0/lib/tools/_beta_runner.py:0
1.8.0/lib/tools/_beta_runner.py:1
$ /tmp/anth-1.7.0/bin/python toolrun.py
AttributeError: 'BetaToolRunner' object has no attribute 'add_tools'
1.7.0 には add_tools() 自体が無い。GitHub API で追うと、add_tools / remove_tools は 09-20 の b5cc700(inline tool definitions の feature、_beta_runner.py に +78 行)で入り、翌 09-21 の 06d0a6b が直した。両方とも 1.8.0 に入っている。Bug Fixes に載っているが、リリース済みの版に対する修正ではない。
そこで 1.8.0 だけで、echo サーバが 1 回目に tool_use を返し、応答を受けた後に add_tools(get_time) を呼んで、2 回目のリクエストを見た。
-- toolrun-1.8.0-1.json bytes=319
tools: ['get_weather']
messages roles: ['user']
-- toolrun-1.8.0-2.json bytes=842
tools: ['get_weather']
messages roles: ['user', 'assistant', 'user', 'system']
system-role msg: [{"type": "tool_addition", "tool": {"type": "tool_definition", "definition": {"name": "get_time", "description": "Get time.", "input_schema": {...}}}}]
tools 配列は get_weather のまま増えない。追加した定義は role: "system" のメッセージに tool_addition ブロックとして乗り、messages の末尾に付く。docstring に "without changing the tools param, which would miss the prompt cache" とある通りで、tools を触らないのは prompt cache を外さないためだ。2 回目のリクエストの tools 配列に新しい tool が入るかを見るつもりだったが、設計上そこには入らない。
同じ docstring に "Requires the inline-tools-2026-09-15 beta" とある。SDK がこのヘッダを自動で足すのかを見たら、足していない。
-- toolrun-1.8.0-2.json anthropic-beta=["structured-outputs-2025-12-15"]
$ grep -rn "inline-tools" 1.8.0/anthropic/ --include=*.py | grep -v /types/
lib/tools/_beta_runner.py:288: function tool of the same name. Requires the `inline-tools-2026-09-15` beta.
lib/tools/_beta_runner.py:310: Requires the `inline-tools-2026-09-15` beta.
betas= に自分で書かないと、tool_addition ブロック入りのリクエストが beta 無しで飛ぶ。本物の API がそれをどう扱うかは、キーが無いので確認していない。
06d0a6b が直したのは「モデルがもう呼んでしまった tool を、その場で add_tools() したら実行されるか」だ。patch を読むと、修正前は追加した function を _pending_tool_additions に溜めて次のリクエスト送信時に登録していたのを、add_tools() の中で即 _tools_by_name に入れるようにしている。試すには、tools=[get_time] で始めて、サーバに未宣言の get_weather を呼ばせ、応答を受けた直後に add_tools(get_weather) を呼べばいい。
turn 1: stop_reason=tool_use content=['tool_use']
-> add_tools(get_weather) after the model already called it
turn 2: stop_reason=end_turn content=['text']
-- toolrun-fix-2.json bytes=842 anthropic-beta=["inline-tools-2026-09-15"]
tool_result: [{"type": "tool_result", "tool_use_id": "toolu_01", "content": "sunny in Tokyo"}]
tool_result に sunny in Tokyo が入っている。宣言前に呼ばれた tool が、追加した直後の同じターンで実行された。修正前の状態は pip に無いので、"not found" のエラーになる側は再現していない。
消えた _transform.py の代わりに、transform が扱っていた値を送った
1.8.0 で _utils/_transform.py(458 行)が消え、_utils/_prepare.py(210 行)が増えた。pip の show -f で 1.7.0 と 1.8.0 のファイル一覧を diff すると 106 行の差があるが、types/beta/ 以外はこの 2 ファイルだけだ。1.6.0 と 1.7.0 の差は 6 ファイルで、全部 rate limit の group 型。
transform 層は、TypedDict のフィールド注釈を見て datetime を ISO 8601 にしたり、pydantic model を dump したり、Base64FileInput を base64 にしたりしていた。それが無くなったなら、そういう値を送ったときに出力が変わりそうだと考えて、まとめて投げた。
messages=[{"role": "user", "content": [
{"type": "image", "source": {"type": "base64", "media_type": "image/png", "data": png}},
{"type": "text", "text": "hi", "cache_control": {"type": "ephemeral", "ttl": anthropic.NOT_GIVEN}},
]}],
extra_body={"x_dt": datetime.datetime(2026, 9, 24, 9, 0, tzinfo=datetime.timezone.utc),
"x_model": Meta(user_id="u", when=datetime.datetime(2026, 1, 1)), # pydantic.BaseModel
"x_omit": anthropic.NOT_GIVEN, "x_none": None},
png を bytes にした 1 回目は、3 版とも TypeError: Object of type bytes is not JSON serializable だった。transform があった 1.7.0 でも bytes は通らない。data を文字列にした 2 回目は 3 版とも 343 バイトで同一。
{"max_tokens":8,"messages":[{"role":"user","content":[{"type":"image","source":{"type":"base64","media_type":"image/png","data":"iVBORw0KGgo="}},{"type":"text","text":"hi","cache_control":{"type":"ephemeral"}}]}],"model":"claude-sonnet-5","x_dt":"2026-09-24T09:00:00+00:00","x_model":{"user_id":"u","when":"2026-01-01T00:00:00"},"x_none":null}
datetime は ISO 8601、pydantic model は dump、入れ子の NOT_GIVEN は落ち、None は null で残る。data に pathlib.Path を渡すと、3 版ともファイルを読んで base64 にした 202 バイトのボディが届いた。1.8.0 の _constants.py には FILE_INPUT_MARKERS = (("type", "base64", "data"),) があり、この形のマッピングの下にある path だけを読む。transform が注釈で判定していたものを、キーの形で判定するように変えている。結果は同じだった。
変わったのは private な名前だ。
$ python -c "from anthropic._utils import transform, maybe_transform"
1.7.0 import OK
1.8.0 ImportError: cannot import name 'transform' from 'anthropic._utils'
transform / maybe_transform / async_transform / is_typeddict が _utils から消え、prepare_request_data / serialize_data / strip_omit が増えた。このリポジトリを grep したら anthropic SDK の import は 0 件で、Gemfile にも package.json にも無かった。私の側に直すものは無い。
私の条件では、1.8.0 に上げる理由も止める理由も無かった
3 版で違いが出た場所を並べる。
| 見た場所 | 1.6.0 → 1.7.0 | 1.7.0 → 1.8.0 |
|---|---|---|
messages.create のボディ(319 バイト) | 同一 | 同一 |
| ヘッダ 19 本 | バージョン番号の 2 本 | 同左 |
betas= の結合 | a,b のまま | 同左 |
| middleware / OAuth が足す beta の区切り | ", " → "," | 変化なし |
| datetime / pydantic / Path / NOT_GIVEN の直列化 | 同一 | 同一 |
from anthropic._utils import transform | 通る | ImportError |
runner.add_tools() | 無い | 追加(inline-tools-2026-09-15 は自分で付ける) |
| 依存 | pydantic の下限 1.9 → 1.10 | 変化なし |
普通に messages.create を呼んでいるコードは、1.6.0 から 1.8.0 まで送るバイト列が変わらない。上げても何も起きない。止める理由も無い。差が出るのは _utils の private 名を import しているか、fallback middleware か OAuth credentials を使っているか、tool runner で add_tools() を使い始めるかの 3 つで、最後のは新機能なので「上げたら壊れる」ではなく「上げないと使えない」側だ。
release note の "remove the request param transform in favour of the JSON encoder" は、リポジトリの規模で言えばこの 1 週間で最大の変更で、届くバイト列で言えばゼロだった。逆に、Bug Fixes の 1 行に見えた add_tools() は、そもそも 1.7.0 に無い機能の初出だった。行数と影響は別に見た方がいい。
試していないことを書いておく。async client と streaming は投げていない。OAuth の credentials 経路は token cache が要るので、_auth.py の diff を読んだだけで動かしていない。temperature がいつ消えたかも追っていない。本物の API に inline-tools-2026-09-15 無しで tool_addition を送るとどうなるかは、キーが無いので分からない。
Tags
参考文献
- anthropic-sdk-python v1.8.0 release notes(2026-09-22)
- anthropic-sdk-python v1.7.0 release notes(2026-09-18)
- anthropic-sdk-python v1.6.0 release notes
- commit 539422c: refactor(client): remove the request param transform in favour of the JSON encoder
- commit 1680324: fix(client): join multiple anthropic-beta values with a comma and no space (#781)
- commit b5cc700: feat(api): inline tool definitions and MCP tool-list pinning (beta)
- commit 06d0a6b: fix(tools): add_tools() takes effect straight away (#874)