fix(anthropic): drop the clear_thinking edit when the reasoning budget cannot fit

When reasoning_effort maps to a budget that max_tokens cannot hold, thinking is dropped. The clear_thinking edit now goes with it on native /v1/messages and in the Rust path, unless the caller sent thinking themselves.
This commit is contained in:
mateo-berri 2026-10-29 01:41:31 -07:00
parent de22512809
commit 7bb8650140
3 changed files with 35 additions and 1 deletions

View file

@ -250,7 +250,13 @@ fn translate_reasoning_effort(
});
}
let Some(budget) = fit_budget_to_max_tokens(budget, request.params.max_tokens) else {
return Ok(request);
if request.params.thinking.is_some() {
return Ok(request);
}
return Ok(MessagesRequest {
params: without_thinking(request.params, context),
..request
});
};
let enabled = ThinkingConfig::enabled(budget);
Ok(MessagesRequest {
@ -687,6 +693,20 @@ mod tests {
serde_json::json!({"max_tokens": 8192, "reasoning_effort": "high"}),
true
)]
#[case::reasoning_effort_budget_cannot_fit(
haiku_4_5(),
serde_json::json!({"max_tokens": 1024, "reasoning_effort": "high"}),
false
)]
#[case::caller_thinking_kept_when_budget_cannot_fit(
haiku_4_5(),
serde_json::json!({
"max_tokens": 1024,
"reasoning_effort": "high",
"thinking": {"type": "enabled", "budget_tokens": 1000}
}),
true
)]
#[case::effort_model_drops_adaptive(opus_4_5(), claude_code_payload("high", 8192), false)]
#[case::adaptive_model_keeps_adaptive(sonnet_4_6(), claude_code_payload("high", 8192), true)]
#[case::non_reasoning_model(haiku_3_5(), claude_code_payload("high", 8192), false)]

View file

@ -459,6 +459,8 @@ class AnthropicMessagesConfig(BaseAnthropicMessagesConfig):
fitted_thinking: Final = AnthropicConfig.cap_thinking_budget_to_max_tokens(mapped_thinking, max_tokens)
if fitted_thinking is None:
verbose_logger.warning(DROP_UNFITTING_REASONING_EFFORT_WARNING, reasoning_effort, model, max_tokens)
if "thinking" not in optional_params:
self._drop_thinking(optional_params, model, custom_llm_provider)
return
optional_params.setdefault("thinking", fitted_thinking)

View file

@ -347,6 +347,18 @@ CLEAR_TOOL_USES_EDIT: Final = {"type": "clear_tool_uses_20250919"}
[
pytest.param("claude-sonnet-4-5", {"reasoning_effort": "none"}, False, id="reasoning-effort-none"),
pytest.param("claude-haiku-4-5", {"reasoning_effort": "high"}, True, id="reasoning-effort-high"),
pytest.param(
"claude-haiku-4-5",
{"max_tokens": 1024, "reasoning_effort": "high"},
False,
id="reasoning-effort-budget-cannot-fit",
),
pytest.param(
"claude-haiku-4-5",
{"max_tokens": 1024, "reasoning_effort": "high", "thinking": {"type": "enabled", "budget_tokens": 1000}},
True,
id="caller-thinking-kept-when-budget-cannot-fit",
),
pytest.param("claude-opus-4-5", _claude_code_payload(effort="high"), False, id="effort-model-drops-adaptive"),
pytest.param("claude-sonnet-4-6", _claude_code_payload(effort="high"), True, id="adaptive-model"),
pytest.param("claude-3-5-haiku-latest", _claude_code_payload(), False, id="non-reasoning-model"),