mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-08-01 19:18:35 -04:00
fix(llm): omit temperature for major-only Opus ids (claude-opus-5) (#5761)
The version pattern in _anthropic_rejects_temperature() required a minor
component, so major-only ids like `claude-opus-5` never matched and the
guard reported that the model accepts `temperature`. Anthropic rejects the
field outright on Opus 4.7+, so every such call returned HTTP 400 and the
stream aborted with zero tokens ("the model returned an empty response").
Make the minor optional and read a missing minor as `.0`. The major is also
capped at 1-2 digits with a no-trailing-digit lookahead, mirroring the
minor: once the minor is optional, a greedy major would swallow the date in
`claude-3-opus-20240229` and read it as version 20240229, dropping
temperature from a model that accepts it.
Fixes #5753
Co-authored-by: Alexandre Teixeira <111787685+alteixeira20@users.noreply.github.com>
This commit is contained in:
+19
-7
@@ -1237,15 +1237,27 @@ def _anthropic_rejects_temperature(model: str) -> bool:
|
|||||||
return False
|
return False
|
||||||
# `(?<![a-z])` anchors "opus" to a word boundary so a substring match like
|
# `(?<![a-z])` anchors "opus" to a word boundary so a substring match like
|
||||||
# `oct-opus`/`octopus-4-8` can't be read as Opus (it would otherwise strip
|
# `oct-opus`/`octopus-4-8` can't be read as Opus (it would otherwise strip
|
||||||
# temperature). Cap the minor at 1-2 digits and forbid a trailing digit so a
|
# temperature). Both version components are capped at 1-2 digits and forbid a
|
||||||
# dated id like `claude-opus-4-20250514` (Opus 4.0) parses as major-only (no
|
# trailing digit, so an 8-digit date can never be read as a version number:
|
||||||
# minor match, kept) instead of reading the date `20250514` as a giant minor
|
# `claude-opus-4-20250514` (Opus 4.0) parses as major-only rather than reading
|
||||||
# that would falsely test >= 4.7. Dated 4.7+ snapshots (`claude-opus-4-7-
|
# `20250514` as a giant minor, and `claude-3-opus-20240229` (legacy Claude 3
|
||||||
# 20260201`) keep their explicit minor and are still matched.
|
# Opus, date directly after "opus-") fails to match at all rather than reading
|
||||||
match = re.search(r"(?<![a-z])opus[-_]?(\d+)[-_.](\d{1,2})(?!\d)", model.lower())
|
# the date as a giant major. Dated 4.7+ snapshots (`claude-opus-4-7-20260201`)
|
||||||
|
# keep their explicit minor and are still matched.
|
||||||
|
#
|
||||||
|
# The minor is optional and a missing minor reads as `.0`, so major-only ids
|
||||||
|
# like `claude-opus-5` are correctly treated as >= 4.7 (issue #5753). Without
|
||||||
|
# this, every Opus 5 call kept `temperature` and failed with HTTP 400 — visible
|
||||||
|
# only on paths that pass a temperature, e.g. scheduled tasks inheriting
|
||||||
|
# `stream_agent_loop`'s 0.3 default, which returned empty responses.
|
||||||
|
match = re.search(
|
||||||
|
r"(?<![a-z])opus[-_]?(\d{1,2})(?!\d)(?:[-_.](\d{1,2})(?!\d))?", model.lower()
|
||||||
|
)
|
||||||
if not match:
|
if not match:
|
||||||
return False
|
return False
|
||||||
return (int(match.group(1)), int(match.group(2))) >= (4, 7)
|
major = int(match.group(1))
|
||||||
|
minor = int(match.group(2)) if match.group(2) else 0
|
||||||
|
return (major, minor) >= (4, 7)
|
||||||
|
|
||||||
# Reasoning effort level sent to Mistral thinking-capable models. Mistral's
|
# Reasoning effort level sent to Mistral thinking-capable models. Mistral's
|
||||||
# API accepts "high", "medium", "low", "none" — see
|
# API accepts "high", "medium", "low", "none" — see
|
||||||
|
|||||||
@@ -29,6 +29,13 @@ from src.llm_core import _anthropic_rejects_temperature, _build_anthropic_payloa
|
|||||||
"anthropic/claude-opus-4-7", # tolerate a provider-prefixed id
|
"anthropic/claude-opus-4-7", # tolerate a provider-prefixed id
|
||||||
"claude-opus-4-10", # future minor still >= 4.7
|
"claude-opus-4-10", # future minor still >= 4.7
|
||||||
"claude-opus-5-0", # future major
|
"claude-opus-5-0", # future major
|
||||||
|
# Major-only ids: a missing minor reads as `.0`, so these are >= 4.7 too
|
||||||
|
# (issue #5753). Before the fix the version pattern required a minor, so
|
||||||
|
# these fell through to "accepts temperature" and every call 400'd.
|
||||||
|
"claude-opus-5",
|
||||||
|
"claude-opus-5-20260101", # major-only + dated snapshot
|
||||||
|
"anthropic/claude-opus-5", # major-only behind a provider prefix
|
||||||
|
"claude-opus-6", # future major-only
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_opus_47_plus_rejects_temperature(model):
|
def test_opus_47_plus_rejects_temperature(model):
|
||||||
@@ -48,7 +55,10 @@ def test_opus_47_plus_rejects_temperature(model):
|
|||||||
"claude-opus-4-6-20251201", # dated 4.6 snapshot — older, still keeps temperature
|
"claude-opus-4-6-20251201", # dated 4.6 snapshot — older, still keeps temperature
|
||||||
"claude-sonnet-4-6",
|
"claude-sonnet-4-6",
|
||||||
"claude-3-5-sonnet",
|
"claude-3-5-sonnet",
|
||||||
"claude-3-opus-20240229", # legacy Claude 3 Opus — no opus-N-M pattern, kept
|
"claude-3-opus-20240229", # legacy Claude 3 Opus — date directly after
|
||||||
|
# "opus-", so the major must not swallow it as version 20240229 (that is
|
||||||
|
# what makes capping the major at 1-2 digits necessary once the minor
|
||||||
|
# became optional in #5753).
|
||||||
"claude-haiku-4-5",
|
"claude-haiku-4-5",
|
||||||
"claude-x",
|
"claude-x",
|
||||||
"octopus-4-8", # "opus" only as a substring of another word — must not match
|
"octopus-4-8", # "opus" only as a substring of another word — must not match
|
||||||
@@ -87,6 +97,20 @@ def test_payload_keeps_temperature_for_older_models():
|
|||||||
assert _payload("claude-3-5-sonnet", 1.2)["temperature"] == 1.0
|
assert _payload("claude-3-5-sonnet", 1.2)["temperature"] == 1.0
|
||||||
|
|
||||||
|
|
||||||
|
def test_payload_omits_temperature_for_major_only_opus_5():
|
||||||
|
# Issue #5753: the scheduled-task path calls stream_agent_loop() without a
|
||||||
|
# temperature and inherits its 0.3 default, so `claude-opus-5` 400'd on every
|
||||||
|
# run and surfaced as "the model returned an empty response". Interactive chat
|
||||||
|
# leaves temperature None and never hit it.
|
||||||
|
assert "temperature" not in _payload("claude-opus-5", 0.3)
|
||||||
|
|
||||||
|
|
||||||
|
def test_payload_keeps_temperature_for_legacy_claude_3_opus():
|
||||||
|
# Guards the major-digit cap: `opus-20240229` must not parse as version
|
||||||
|
# 20240229, or Claude 3 Opus would silently lose the caller's temperature.
|
||||||
|
assert _payload("claude-3-opus-20240229", 0.5)["temperature"] == 0.5
|
||||||
|
|
||||||
|
|
||||||
def test_payload_keeps_temperature_for_dated_opus_4_0():
|
def test_payload_keeps_temperature_for_dated_opus_4_0():
|
||||||
# Anthropic's dated id for Opus 4.0 (claude-opus-4-20250514) is in this repo's
|
# Anthropic's dated id for Opus 4.0 (claude-opus-4-20250514) is in this repo's
|
||||||
# ANTHROPIC_MODELS list. The date must not be misread as a >= 4.7 minor, or the
|
# ANTHROPIC_MODELS list. The date must not be misread as a >= 4.7 minor, or the
|
||||||
|
|||||||
Reference in New Issue
Block a user