Skip to content

fix(anthropic): make the SDK compatible with the anthropic 1.x client (temperature crash + strict-endpoint nulls) - #312

Open
philipph-askui wants to merge 8 commits into
mainfrom
fix/anthropic-temperature-kwarg
Open

fix(anthropic): make the SDK compatible with the anthropic 1.x client (temperature crash + strict-endpoint nulls)#312
philipph-askui wants to merge 8 commits into
mainfrom
fix/anthropic-temperature-kwarg

Conversation

@philipph-askui

@philipph-askui philipph-askui commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

Summary

Makes the SDK compatible with the newer anthropic client (1.x). Fixes to the Anthropic messages path (src/askui/models/anthropic/messages_api.py), which both AnthropicVlmProvider and AskUIVlmProvider route Claude through.

The old problem

Our dependency was unbounded (anthropic>=0.86.0), so a fresh install pulls the latest anthropic (1.2.0). But anthropic 1.x removed the temperature parameter from beta.messages.create, and create_message forwarded temperature= to it unconditionally — even the omit sentinel when the user set none. Result: the agent crashes on the first model call, no matter the config:

TypeError: Messages.create() got an unexpected keyword argument 'temperature'

This is why it fires for everyone on a fresh install (e.g. it took down a downstream project that never sets temperature at all).

Relationship to #313: #313 is a one-line stopgap that caps the dependency to anthropic<1, keeping installs on the old (working) 0.x client. This PR is the durable fix — it makes the code work with anthropic 1.x and moves the constraint the other way (anthropic>=1,<2). The two therefore change the same dependency line in opposite directions: this PR supersedes #313, so once it lands #313 can be closed (or, if #313 shipped first as a hotfix, this PR flips the cap forward onto 1.x).

1. temperature crash → gate by model

Root cause (confirmed against the real API): the adaptive-thinking Claude generation deprecated sampling params — from Sonnet 5 / Opus 4.7 onward the API rejects temperature outright (400 "temperature is deprecated for this model."), and even on Sonnet 4.6 / Opus 4.6 a non-default temperature is rejected while adaptive thinking is active. Newer clients also removed the typed temperature param entirely.

Fix: decide per request whether to send temperature, using the SDK's per-model capability check accepts_sampling_params(model_id) plus a request-level adaptive-thinking check:

  • Sampling-capable + not adaptive thinking (Sonnet 4/4.5/4.6, Opus 4.1/4.5/4.6, Haiku 4.5, …) → forward temperature in the request body via extra_body (works regardless of client version).
  • Sampling-deprecated models (Sonnet 5, Opus 4.7/4.8, Fable 5) or adaptive thinking on → drop it and warn once per model.

This preserves backward compatibility (incl. the Android agent's deterministic temperature=0.0 via make_non_thinking_settings) and avoids per-call log spam, while never crashing.

2. Omit null fields when serializing content blocks

from_content_block used model_dump(), serializing unset optionals as explicit null (cache_control: null, citations: null). Real Anthropic and AskUI tolerate this, but stricter Anthropic-compatible endpoints (e.g. OpenRouter's) reject it with cache_control: expected object, received null. Fixed with exclude_none=True (verified it does not strip Nones inside tool input dicts).

Dependency / CI

  • Require anthropic>=1,<2 (plus the bedrock/vertex extras): the declared constraint now matches the 1.x client the code targets, and <2 prevents the next major from silently breaking users the way 1.x did. Lock resolves 1.2.0.
  • Bumped anthropic in pdm.lock to 1.2.0 so CI runs against what users actually get on a fresh install (the lock previously pinned 0.116.0, masking the crash).
  • Real-signature guard test: binds the SDK's actual create() kwargs (with/without temperature, sampling & non-sampling models) against the real installed client signature.

Testing

  • Unit tests: temperature via extra_body for sampling-capable models, dropped (deprecated model or adaptive thinking) + warn-once for others, never a typed kwarg; serialization emits no nulls; real-signature guard.
  • pdm run qa:fix clean; full unit suite green on anthropic 1.2.0.
  • Verified end-to-end via a manual playground: act() across providers (anthropic/askui/openrouter) × models (Sonnet 4.5/4.6/5) × temperatures — 36/36, with per-cell confirmation of whether temperature was actually sent or safely dropped.

🤖 Generated with Claude Code

philipph-askui and others added 4 commits August 28, 2026 10:48
AnthropicMessagesApi.create_message always passed `temperature=` to
`client.beta.messages.create`, even as the `omit` sentinel. `anthropic` client
versions that dropped `temperature` from `beta.messages.create` (and accept no
`**kwargs`) then raise `TypeError: ... unexpected keyword argument 'temperature'`
at argument binding, making AnthropicVlmProvider unusable out of the box.

Forward `temperature` only when a value was actually requested (build it into
the create() kwargs conditionally). The other options remain passed as `omit`
since they are still part of the client signature.

Also fix a related pre-existing bug: `temperature or omit` dropped an explicit
`temperature=0.0` (a valid deterministic value) because 0.0 is falsy; use
`is None` instead.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The `temperature` crash only reproduces on anthropic versions that removed the
parameter (1.2.0), which a fresh `pip install` already resolves (the constraint
is `anthropic>=0.86.0` with no upper bound); CI missed it only because the lock
pinned 0.116.0, which still has `temperature`.

- Bump the lockfile to anthropic 1.2.0 so CI runs against the version users
  actually get. Full unit suite + typecheck pass on 1.2.0.
- Add an integration guard that binds the SDK's actual create() kwargs against
  the real installed client signature, so forwarding any unsupported parameter
  fails in CI (on 1.2.0 this fails if the temperature fix is reverted).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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>
…ested

Live testing against the real Anthropic API showed two things:
- The previous `extra_body=... or omit` passed the `omit` sentinel, crashing the
  default (unset) path with `TypeError: 'Omit' object is not a mapping`.
- Anthropic has DEPRECATED `temperature` for its models: newer clients removed it
  from `beta.messages.create`, and the API rejects a non-default value with
  `400 "temperature is deprecated for this model."` (only the default is
  accepted). So there is no way to make a non-default temperature work.

Stop forwarding `temperature` to the Anthropic Messages API entirely (this also
fixes the AskUI provider, which routes Claude through the same API), and log a
warning when a caller explicitly requests one so it is not silently ignored.
`act()` now works for any temperature value.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@philipph-askui philipph-askui changed the title fix(anthropic): only forward temperature to the client when set fix(anthropic): stop forwarding deprecated temperature (fixes act() crash) Aug 28, 2026
`from_content_block` used `model_dump()`, which serialized unset optional fields
as explicit `null` (e.g. `cache_control: null`, `citations: null`) on every text/
image/tool_result block. The real Anthropic API and the AskUI proxy tolerate
these nulls, but stricter Anthropic-compatible endpoints (e.g. OpenRouter's)
reject them with `cache_control: expected object, received null`, failing every
request.

Serialize with `exclude_none=True` so optional fields are omitted rather than
sent as null. This is more schema-correct and unblocks strict endpoints while
remaining valid for Anthropic/AskUI.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@philipph-askui philipph-askui changed the title fix(anthropic): stop forwarding deprecated temperature (fixes act() crash) fix(anthropic): direct-API compatibility (deprecated temperature + null content fields) Aug 28, 2026
philipph-askui and others added 2 commits August 28, 2026 15:40
Replaces the blanket temperature drop with the SDK's existing per-model
capability check (`accepts_sampling_params`). Forward `temperature` only to
models that accept sampling params, routed via `extra_body` (the typed param was
removed from newer clients); drop it for the adaptive-thinking generation and
warn once per model.

This preserves backward compatibility for legacy budget-thinking models (Sonnet
4/4.5, Opus 4.1/4.5, Haiku 4.5, ...) - including the Android agent's deterministic
`temperature=0.0` via `make_non_thinking_settings` - and avoids per-call log spam.

Also reconcile the taxonomy: the 4.6 adaptive generation (Sonnet 4.6, Opus 4.6)
was marked sampling-capable, but the API rejects a non-default temperature with
`400 "temperature is deprecated for this model."` (confirmed against both direct
Anthropic and Vertex). `accepts_sampling_params` now returns False for the whole
adaptive generation.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… model)

A direct API probe corrected the earlier assumption: Sonnet 4.6 / Opus 4.6 DO
accept `temperature` (only Sonnet 5 / Opus 4.7+ deprecated it). The Sonnet 4.6
"temperature is deprecated" 400 seen earlier was the *adaptive-thinking +
temperature* incompatibility (ComputerAgent enables adaptive thinking; the probe
did not), not model-level deprecation - which is why a budget-thinking model like
Sonnet 4.5 replayed fine with temperature.

- Revert the taxonomy change: `accepts_sampling_params` returns True again for the
  4.6 generation (Sonnet 4.6 / Opus 4.6).
- Forward `temperature` only when the model accepts sampling params AND adaptive
  thinking is not enabled (fixed budget_tokens thinking is compatible); otherwise
  drop it and warn once per model with the specific reason. Route it via
  `extra_body` when sent.

This keeps temperature working for the 4.5 and 4.6 generations without thinking
(and with budget thinking), drops it under adaptive thinking and for Sonnet 5+,
and never crashes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@philipph-askui philipph-askui changed the title fix(anthropic): direct-API compatibility (deprecated temperature + null content fields) fix(anthropic): make the SDK compatible with the anthropic 1.x client (temperature crash + strict-endpoint nulls) Aug 28, 2026
This PR makes the messages API work with the 1.x anthropic client
(routes `temperature` through `extra_body` instead of the typed kwarg
that 1.x removed), and the lock already resolves 1.2.0. Bump the
dependency floor to `>=1` so the declared constraint matches what the
code targets, and cap at `<2` so the next major can't silently break us
the way 1.x did.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant