fix: omit temperature for Opus 4.7+ on native Anthropic path (#3065)#3117
Conversation
|
@NicholaiVogel tagging you as requested on #3065 — PR is up with the version-gated |
|
This also relates to #2739. The Opus 4.7+ temperature rejection is a concrete Anthropic request-shape/capability case that the model capability schema should eventually represent, rather than leaving only model-name gates in payload construction. I would keep this PR focused on the direct fix, but I’d be up for collaborating on the Anthropic shape/reader follow-up in #2739 if you’re up for it. P.S. This is also a useful supporting example for the shape-reader direction in #2739: model ID alone is not a stable capability key. The same nominal model can be served through native Anthropic, OpenRouter, or a local OpenAI-compatible proxy, and each path may advertise or require different request fields, reasoning/thought controls, and response shapes. That is why I think we should move toward a clean normalization path: provider-native shape -> general/OpenAI-compatible + legacy compatibility readers -> canonical capability/control shape, with deterministic controls coming from provider evidence rather than name-derived capability guesses. |
Good to know, thanks heaps for the context. Yeah I'd be down to help out on that initiative. Just to confirm are you happy for this to merge as is? Or would you prefer we take this work and pull it into your existing PR? |
|
Yep, LGTM. It’s a good focused current fix for #3065: the model-name/version gate is inherently a bit brittle; for example, the regex would also classify a contrived ID like I’d keep the broader provider capability/request-shape normalization in #2739 as the follow-up rather than making this PR wait on it. This is a useful concrete case for that work, but the direct bug fix should land independently. |
|
Just pinging @RaresKeY and @NicholaiVogel to follow up on getting this merged. I don't have the permissions. |
Anthropic removed the sampling parameters (temperature, top_p, top_k) starting with Claude Opus 4.7 — sending temperature at all, even 0.0, returns HTTP 400. _build_anthropic_payload sent it unconditionally, so every native-Anthropic request to Opus 4.7/4.8 failed: the research probe (ResearchHandler._probe_endpoint, temperature=0) aborted runs before they started, and all DeepResearcher._llm calls 400'd. Add _anthropic_rejects_temperature (version-gates opus-N-M >= (4,7)) and omit temperature in the Anthropic builder for those models. Older Claude models (Opus 4.6 and below, Sonnet/Haiku) keep temperature and the existing [0,1] clamp. The version gate is hardened against real-world model id shapes: - a word-boundary anchor so a substring like `octopus-4-8` is not read as Opus and stripped of temperature; - a 1-2 digit minor cap so a dated id such as `claude-opus-4-20250514` (Opus 4.0, listed in ANTHROPIC_MODELS) parses as major-only and keeps temperature, while dated 4.7+ snapshots still match; - a non-string guard so a non-string model can't raise AttributeError (the previous builder never called .lower() on it). Adds regression tests covering 4.7/4.8 omission, older/dated/legacy retention, the substring overmatch, and non-string inputs. Fixes odysseus-dev#3065 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
06dc7f9 to
77c7e75
Compare
|
Merging this as a narrow, specific fix for #3065: the native Anthropic path should omit |
…s-dev#3117) Anthropic removed the sampling parameters (temperature, top_p, top_k) starting with Claude Opus 4.7 — sending temperature at all, even 0.0, returns HTTP 400. _build_anthropic_payload sent it unconditionally, so every native-Anthropic request to Opus 4.7/4.8 failed: the research probe (ResearchHandler._probe_endpoint, temperature=0) aborted runs before they started, and all DeepResearcher._llm calls 400'd. Add _anthropic_rejects_temperature (version-gates opus-N-M >= (4,7)) and omit temperature in the Anthropic builder for those models. Older Claude models (Opus 4.6 and below, Sonnet/Haiku) keep temperature and the existing [0,1] clamp. The version gate is hardened against real-world model id shapes: - a word-boundary anchor so a substring like `octopus-4-8` is not read as Opus and stripped of temperature; - a 1-2 digit minor cap so a dated id such as `claude-opus-4-20250514` (Opus 4.0, listed in ANTHROPIC_MODELS) parses as major-only and keeps temperature, while dated 4.7+ snapshots still match; - a non-string guard so a non-string model can't raise AttributeError (the previous builder never called .lower() on it). Adds regression tests covering 4.7/4.8 omission, older/dated/legacy retention, the substring overmatch, and non-string inputs. Fixes odysseus-dev#3065
Summary
On the native Anthropic path,
_build_anthropic_payload(src/llm_core.py) always settemperaturein the request. Anthropic removed the sampling parameters (temperature,top_p,top_k) starting with Claude Opus 4.7, so sendingtemperatureat all — even0.0— returns HTTP 400. This broke every native-Anthropic call to Opus 4.7/4.8: the research endpoint probe (ResearchHandler._probe_endpoint, which sendstemperature=0) failed first and aborted the run, and everyDeepResearcher._llmcall 400'd. The existing_restricts_temperatureguard only covers OpenAI reasoning models and is only applied on the OpenAI-compatible path — never inside the Anthropic builder.This adds
_anthropic_rejects_temperature(model), which version-gatesopus N.M >= (4, 7), and omitstemperaturein the Anthropic builder for those models. Older Claude models (Opus 4.6 and below, all Sonnet/Haiku) keeptemperatureand the existing clamp-to-[0, 1]behavior — unchanged. The builder is the single chokepoint for all three native-Anthropic call paths (llm_call, the sync variant,stream_llm) and the probe, so the gate covers them all;top_p/top_kare never sent by the builder, so no other field needs gating.The version gate is hardened against real-world model id shapes: a word-boundary anchor so a substring like
octopus-4-8isn't read as Opus and stripped of temperature; a 1–2 digit minor cap so a dated id such asclaude-opus-4-20250514(Opus 4.0, which is in this repo'sANTHROPIC_MODELSlist) keeps temperature while dated 4.7+ snapshots still match; and a non-string guard so a non-string model can't raiseAttributeError(the previous builder never called.lower()on it).Target branch
dev, notmain.Linked Issue
Fixes #3065
Type of Change
Checklist
devdocker compose uporuvicorn app:app) and verified the change works end-to-end. Type-checks and unit tests are not enough.How to Test
Automated (what I ran):
python -m pytest -q tests/test_llm_core_anthropic_temp_omit.py tests/test_llm_core_anthropic_temp_clamp.py # 36 passedCoverage: Opus 4.7/4.8 (incl. dated and provider-prefixed forms) and future minors/majors omit
temperature; Opus 4.6/4.5/4.1/4.0, bare major, dated Opus 4.0/4.1/4.6, legacyclaude-3-opus, and Sonnet/Haiku keep it; theoctopus-4-8substring overmatch and non-string inputs produce no false match and no crash; plus payload-level omit/keep assertions. The pre-existing clamp test (test_llm_core_anthropic_temp_clamp.py) is unchanged and still passes.Manual end-to-end (for a reviewer):
https://api.anthropic.com/...) withclaude-opus-4-8(orclaude-opus-4-7).temperature=0) returns HTTP 400 and aborts; after the fix the probe and run succeed becausetemperatureis omitted for Opus 4.7+.claude-opus-4-6or a Sonnet) and confirm it still sendstemperature(clamped to[0, 1]) and works as before.Visual / UI changes — REQUIRED if you touched anything that renders
N/A — this PR only changes
src/llm_core.py(payload construction) and adds a test file. No UI, rendering, CSS/HTML/SVG, orstatic/changes.cc @NicholaiVogel — thanks for the go-ahead on the issue.
🤖 Generated with Claude Code