Clamp Anthropic temperature to [0.0, 1.0] in _build_anthropic_payload (#1737)

Anthropic's Messages API rejects temperature > 1.0 with HTTP 400, but
_build_anthropic_payload forwarded it verbatim. The shipped "Nietzsche" preset
uses temperature 1.2 and the UI slider allows up to 2.0, so every Claude request
under such a preset hard-broke. Clamp into [0.0, 1.0] in the Anthropic builder
only (OpenAI keeps its wider 0.0-2.0 range). Covers all three Anthropic call
paths, which build through this one function. None is passed through unchanged.

Fixes #1615

Co-authored-by: Ethan <23321960+0xLeathery@users.noreply.github.com>
This commit is contained in:
Ethan
2026-06-03 14:29:36 +10:00
committed by GitHub
parent 96a874c604
commit b9c382006e
2 changed files with 46 additions and 0 deletions

View File

@@ -512,6 +512,12 @@ def _build_anthropic_payload(model, messages, temperature, max_tokens, stream=Fa
# Convert multimodal content (image_url → image) for Anthropic
content = _convert_openai_content_to_anthropic(m["content"])
chat_messages.append({"role": m["role"], "content": content})
# Anthropic only accepts temperature in [0.0, 1.0] and 400s on anything above
# 1.0. Clamp here (in the Anthropic builder only) so presets/sliders that use
# the wider OpenAI 0.0-2.0 range — e.g. the shipped "Nietzsche" preset at 1.2
# — don't hard-break every Claude request. OpenAI's own path is left untouched.
if temperature is not None:
temperature = max(0.0, min(temperature, 1.0))
payload = {
"model": model,
"messages": chat_messages,

View File

@@ -0,0 +1,40 @@
"""Regression guard for #1615 — Anthropic temperature must be clamped to [0.0, 1.0].
Anthropic's Messages API rejects temperature > 1.0 with HTTP 400. The shipped
"Nietzsche" preset uses temperature 1.2 (static/js/presets.js) and the UI slider
allows up to 2.0 (static/index.html), so _build_anthropic_payload must clamp into
[0.0, 1.0]. The clamp lives only in the Anthropic builder — OpenAI keeps its
wider 0.0-2.0 range.
"""
import os
os.environ.setdefault("DATABASE_URL", "sqlite:///:memory:")
from src.llm_core import _build_anthropic_payload
def _temp(t):
payload = _build_anthropic_payload(
"claude-x", [{"role": "user", "content": "hi"}], t, 100
)
return payload["temperature"]
def test_above_range_is_clamped_to_one():
assert _temp(1.2) == 1.0 # the shipped "Nietzsche" preset — previously 400'd
assert _temp(2.0) == 1.0 # UI slider max
def test_in_range_is_unchanged():
assert _temp(0.0) == 0.0
assert _temp(0.7) == 0.7
assert _temp(1.0) == 1.0
def test_below_range_is_clamped_to_zero():
assert _temp(-0.5) == 0.0
def test_none_is_passed_through_unchanged():
# Callers may pass None; behavior is unchanged (no clamp, no crash).
assert _temp(None) is None