Skip to content

fix: omit temperature for Opus 4.7+ on native Anthropic path (#3065)#3117

Merged
RaresKeY merged 1 commit into
odysseus-dev:devfrom
DKANomad:fix/opus-4-7-temperature-400
Jun 11, 2026
Merged

fix: omit temperature for Opus 4.7+ on native Anthropic path (#3065)#3117
RaresKeY merged 1 commit into
odysseus-dev:devfrom
DKANomad:fix/opus-4-7-temperature-400

Conversation

@DKANomad

@DKANomad DKANomad commented Jun 6, 2026

Copy link
Copy Markdown
Contributor

Summary

On the native Anthropic path, _build_anthropic_payload (src/llm_core.py) always set temperature in the request. Anthropic removed the sampling parameters (temperature, top_p, top_k) starting with Claude Opus 4.7, so sending temperature at all — even 0.0 — returns HTTP 400. This broke every native-Anthropic call to Opus 4.7/4.8: the research endpoint probe (ResearchHandler._probe_endpoint, which sends temperature=0) failed first and aborted the run, and every DeepResearcher._llm call 400'd. The existing _restricts_temperature guard 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-gates opus N.M >= (4, 7), and omits temperature in the Anthropic builder for those models. Older Claude models (Opus 4.6 and below, all Sonnet/Haiku) keep temperature and 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_k are 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-8 isn't 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, which is in this repo's ANTHROPIC_MODELS list) keeps temperature while dated 4.7+ snapshots still match; and a non-string guard so a non-string model can't raise AttributeError (the previous builder never called .lower() on it).

Target branch

  • This PR targets dev, not main.

Linked Issue

Fixes #3065

Type of Change

  • Bug fix (non-breaking — fixes a confirmed issue)
  • New feature (non-breaking — adds new behaviour)
  • Breaking change (changes or removes existing behaviour)
  • Refactor / cleanup (behaviour unchanged)
  • Documentation only
  • CI / tooling / configuration

Checklist

  • I searched open issues and open PRs — this is not a duplicate.
  • This PR targets dev
  • My changes are limited to the scope described above — no unrelated refactors or whitespace changes mixed in.
  • I actually ran the app (docker compose up or uvicorn app:app) and verified the change works end-to-end. Type-checks and unit tests are not enough.

Note: verified via the unit suite below (36 passing). I have not run the full app end-to-end against a live Opus 4.7/4.8 Anthropic endpoint, so the end-to-end box above is left unchecked.

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 passed

Coverage: 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, legacy claude-3-opus, and Sonnet/Haiku keep it; the octopus-4-8 substring 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):

  1. Point a session at the native Anthropic API (https://api.anthropic.com/...) with claude-opus-4-8 (or claude-opus-4-7).
  2. Start a Deep Research run (or any chat turn routed to the Anthropic provider). Before the fix the endpoint probe (temperature=0) returns HTTP 400 and aborts; after the fix the probe and run succeed because temperature is omitted for Opus 4.7+.
  3. Switch to an older model (e.g. claude-opus-4-6 or a Sonnet) and confirm it still sends temperature (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, or static/ changes.


cc @NicholaiVogel — thanks for the go-ahead on the issue.

🤖 Generated with Claude Code

@github-actions github-actions Bot added the needs work PR description incomplete — please update before review label Jun 6, 2026
@DKANomad

DKANomad commented Jun 6, 2026

Copy link
Copy Markdown
Contributor Author

@NicholaiVogel tagging you as requested on #3065 — PR is up with the version-gated temperature omission for Opus 4.7+ on the native Anthropic path, plus regression tests (36 passing). Couldn't add you as a formal reviewer (cross-fork permissions), so flagging here. Would appreciate your review when you have a moment.

@github-actions github-actions Bot added ready for review Description complete — ready for maintainer review and removed needs work PR description incomplete — please update before review labels Jun 6, 2026
@RaresKeY

RaresKeY commented Jun 6, 2026

Copy link
Copy Markdown
Collaborator

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.

@DKANomad

DKANomad commented Jun 7, 2026

Copy link
Copy Markdown
Contributor Author

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.

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?

@RaresKeY

RaresKeY commented Jun 7, 2026

Copy link
Copy Markdown
Collaborator

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 my_opus-4-8 as Opus 4.8. That’s a nit rather than a blocker because the gate is scoped to the native Anthropic payload builder, covers the immediate Opus 4.7+ request-shape break, and has targeted regression coverage for the obvious false positives like octopus-4-8.

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.

@DKANomad

Copy link
Copy Markdown
Contributor Author

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>
@RaresKeY
RaresKeY force-pushed the fix/opus-4-7-temperature-400 branch from 06dc7f9 to 77c7e75 Compare June 11, 2026 13:20
@RaresKeY

Copy link
Copy Markdown
Collaborator

Merging this as a narrow, specific fix for #3065: the native Anthropic path should omit temperature for Opus 4.7+ while the broader provider capability/schema cleanup stays separate.

@RaresKeY
RaresKeY merged commit 4f48cfa into odysseus-dev:dev Jun 11, 2026
6 checks passed
kootenayalex pushed a commit to kootenayalex/odysseus-mlx that referenced this pull request Jun 16, 2026
…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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready for review Description complete — ready for maintainer review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

temperature sent to Opus 4.7/4.8 on native Anthropic path returns HTTP 400 (breaks research probe + DeepResearcher)

2 participants