Skip to content

Commit 243f144

Browse files
fix(anthropic): send temperature via extra_body so it works when set
The previous fix stopped forwarding `temperature` when unset, but forwarding it when a value *was* set still crashed on anthropic clients that removed the typed parameter (e.g. 1.2.0) - which affects BOTH AnthropicVlmProvider and AskUIVlmProvider, since both route Claude through this AnthropicMessagesApi. The Messages API still accepts `temperature` in the request body, so send it via `extra_body` (only when set) instead of as a typed keyword. Verified against the real anthropic 1.2.0 client: `extra_body={"temperature": x}` binds cleanly while `temperature=x` raises TypeError. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent a0529ae commit 243f144

2 files changed

Lines changed: 47 additions & 32 deletions

File tree

‎src/askui/models/anthropic/messages_api.py‎

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -269,18 +269,17 @@ def create_message(
269269
temperature,
270270
)
271271

272-
# Only forward `temperature` when a value was actually requested. It is
273-
# an optional sampling parameter that some `anthropic` client versions do
274-
# not expose on `beta.messages.create` (and the client does not accept
275-
# `**kwargs`), so passing it unconditionally - even as the `omit`
276-
# sentinel - raises `TypeError` at argument binding on those clients.
277-
# The other options remain passed as `omit`; they are still part of the
278-
# client signature.
279-
temperature_kwarg: dict[str, float] = {}
272+
# `temperature` was removed from the typed `beta.messages.create`
273+
# signature in newer `anthropic` clients (e.g. 1.2.0), which accept no
274+
# `**kwargs` - so forwarding it as a normal keyword raises `TypeError` at
275+
# argument binding. The Messages API itself still accepts `temperature`
276+
# in the request body, so send it via `extra_body` (only when a value was
277+
# actually requested). This works regardless of client version.
278+
extra_body: dict[str, Any] = {}
280279
if not isinstance(_temperature, Omit):
281-
temperature_kwarg["temperature"] = _temperature
280+
extra_body["temperature"] = _temperature
282281

283-
response = self._client.beta.messages.create( # type: ignore[misc, call-overload]
282+
response = self._client.beta.messages.create( # type: ignore[misc]
284283
messages=_messages,
285284
max_tokens=max_tokens or 8192,
286285
cache_control=_cache_control,
@@ -292,6 +291,6 @@ def create_message(
292291
output_config=_output_config,
293292
tool_choice=_tool_choice,
294293
timeout=300.0,
295-
**temperature_kwarg,
294+
extra_body=extra_body or omit,
296295
)
297296
return MessageParam.model_validate(response.model_dump())

‎tests/unit/models/anthropic/test_messages_api.py‎

Lines changed: 37 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ def test_no_output_config_omits_it(self) -> None:
8585
assert kwargs["output_config"] is omit
8686
assert kwargs["thinking"] == {"type": "enabled", "budget_tokens": 2048}
8787

88-
def test_temperature_not_forwarded_when_unset(self) -> None:
88+
def test_temperature_not_in_body_when_unset(self) -> None:
8989
api, client = self._make_api()
9090

9191
api.create_message(
@@ -94,11 +94,11 @@ def test_temperature_not_forwarded_when_unset(self) -> None:
9494
)
9595

9696
kwargs = client.beta.messages.create.call_args.kwargs
97-
# Not passed at all (not even as `omit`) so clients that dropped the
98-
# parameter do not raise TypeError.
97+
# Never sent as a typed keyword, and no temperature in the body.
9998
assert "temperature" not in kwargs
99+
assert kwargs["extra_body"] is omit
100100

101-
def test_temperature_forwarded_when_set(self) -> None:
101+
def test_temperature_sent_via_extra_body_when_set(self) -> None:
102102
api, client = self._make_api()
103103

104104
api.create_message(
@@ -108,13 +108,27 @@ def test_temperature_forwarded_when_set(self) -> None:
108108
)
109109

110110
kwargs = client.beta.messages.create.call_args.kwargs
111-
assert kwargs["temperature"] == 0.3
111+
# Routed through the request body (not the typed `temperature=` kwarg,
112+
# which newer clients removed).
113+
assert "temperature" not in kwargs
114+
assert kwargs["extra_body"] == {"temperature": 0.3}
115+
116+
def test_temperature_zero_sent_via_extra_body(self) -> None:
117+
api, client = self._make_api()
118+
119+
api.create_message(
120+
messages=[MessageParam(role="user", content="hi")],
121+
model_id="claude-sonnet-5",
122+
temperature=0.0,
123+
)
112124

113-
def test_succeeds_on_client_that_rejects_temperature(self) -> None:
114-
"""Regression: mirrors an anthropic client whose create() has no
115-
`temperature` parameter (and no **kwargs). Passing `temperature` at all -
116-
even as the `omit` sentinel - would raise TypeError, so the SDK must not
117-
forward it when it is unset."""
125+
kwargs = client.beta.messages.create.call_args.kwargs
126+
assert kwargs["extra_body"] == {"temperature": 0.0}
127+
128+
def test_succeeds_on_client_that_rejects_temperature_kwarg(self) -> None:
129+
"""Regression: a client whose create() has no `temperature` parameter
130+
(and no **kwargs) must not receive it as a keyword - even when a
131+
temperature is requested. It goes into the request body instead."""
118132

119133
def create(**kwargs: object) -> MagicMock:
120134
if "temperature" in kwargs:
@@ -131,16 +145,16 @@ def create(**kwargs: object) -> MagicMock:
131145
result = api.create_message(
132146
messages=[MessageParam(role="user", content="hi")],
133147
model_id="claude-sonnet-5",
148+
temperature=0.5, # even when set, must not become a kwarg
134149
)
135150
assert isinstance(result, MessageParam)
136151

137152
def test_kwargs_accepted_by_real_client_signature(self) -> None:
138-
"""Integration guard: every kwarg the SDK sends must be accepted by the
139-
REAL installed `anthropic` client's `beta.messages.create` signature.
140-
141-
This binds against the real signature (no network), so it fails if the
142-
SDK forwards a parameter the installed client version does not support -
143-
catching this class of breakage on whatever anthropic CI resolves."""
153+
"""Integration guard: every kwarg the SDK sends - with and without a
154+
temperature - must be accepted by the REAL installed `anthropic` client's
155+
`beta.messages.create` signature (bound with no network). This catches
156+
the SDK forwarding a parameter the installed client version does not
157+
support, on whatever anthropic CI resolves."""
144158
real_client = anthropic.Anthropic(api_key="dummy")
145159
real_signature = inspect.signature(real_client.beta.messages.create)
146160

@@ -154,8 +168,10 @@ def spy(**kwargs: Any) -> MagicMock:
154168
real_client.beta.messages.create = spy # type: ignore[method-assign]
155169
api = AnthropicMessagesApi(client=real_client)
156170

157-
result = api.create_message(
158-
messages=[MessageParam(role="user", content="hi")],
159-
model_id="claude-sonnet-5",
160-
)
161-
assert isinstance(result, MessageParam)
171+
for temperature in (None, 0.0, 0.7):
172+
result = api.create_message(
173+
messages=[MessageParam(role="user", content="hi")],
174+
model_id="claude-sonnet-5",
175+
temperature=temperature,
176+
)
177+
assert isinstance(result, MessageParam)

0 commit comments

Comments
 (0)