This commit is contained in:
Sami Jawhar 2026-10-30 21:05:28 +00:00 • committed by GitHub
commit 30d23a967d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
24 changed files with 827 additions and 45 deletions

View file

@ -122,6 +122,7 @@ mod tests {
"supports_reasoning": true,
"supports_adaptive_thinking": true,
"thinking_always_on": false,
"thinking_on_by_default": true,
"supports_legacy_thinking": false,
"supports_output_config": true,
"supports_sampling_params": false,
@ -144,6 +145,7 @@ mod tests {
supports_reasoning: true,
supports_adaptive_thinking: true,
thinking_always_on: false,
thinking_on_by_default: true,
supports_legacy_thinking: false,
supports_output_config: true,
supports_sampling_params: false,

View file

@ -1882,6 +1882,7 @@ mod tests {
supports_reasoning: false,
supports_adaptive_thinking: false,
thinking_always_on: false,
thinking_on_by_default: false,
supports_legacy_thinking: false,
supports_output_config: false,
supports_sampling_params: true,

View file

@ -2,8 +2,8 @@ use litellm_llms_types::{
formats::{
chat_completions::ReasoningEffort,
messages::{
EffortLevel, MessagesOptionalParams, MessagesRequest, OutputConfig, ThinkingConfig,
ThinkingDisplay,
ContextEdit, ContextManagement, EffortLevel, MessagesOptionalParams, MessagesRequest,
OutputConfig, ThinkingConfig, ThinkingDisplay,
},
},
recognized::Recognized,
@ -144,6 +144,60 @@ fn legacy_reasoning_effort(
}
}
fn is_clear_thinking_edit(edit: &Recognized<ContextEdit>) -> bool {
match edit {
Recognized::Known(ContextEdit::ClearThinking { .. }) => true,
Recognized::Known(_) => false,
Recognized::Unrecognized(value) => value
.get("type")
.and_then(Value::as_str)
.is_some_and(|edit_type| edit_type.starts_with("clear_thinking_")),
}
}
/// Anthropic rejects `clear_thinking_*` edits without thinking. `context_management` goes
/// only when such an edit was removed and nothing else is left in it.
fn without_clear_thinking_edits(
context_management: Option<Recognized<ContextManagement>>,
) -> Option<Recognized<ContextManagement>> {
match context_management {
Some(Recognized::Known(ContextManagement {
edits: Some(edits),
extra,
})) if edits.iter().any(is_clear_thinking_edit) => {
let kept: Vec<_> = edits
.into_iter()
.filter(|edit| !is_clear_thinking_edit(edit))
.collect();
(!kept.is_empty() || !extra.is_empty()).then(|| {
Recognized::Known(ContextManagement {
edits: Some(kept),
extra,
})
})
}
other => other,
}
}
fn without_thinking(
params: MessagesOptionalParams,
context: &ThinkingContext,
) -> MessagesOptionalParams {
let thinks_without_thinking_param =
context.capabilities.thinking_always_on || context.capabilities.thinking_on_by_default;
let context_management = if thinks_without_thinking_param {
params.context_management
} else {
without_clear_thinking_edits(params.context_management)
};
MessagesOptionalParams {
thinking: None,
context_management,
..params
}
}
fn translate_reasoning_effort(
request: MessagesRequest,
context: &ThinkingContext,
@ -171,9 +225,8 @@ fn translate_reasoning_effort(
) else {
return Ok(MessagesRequest {
params: MessagesOptionalParams {
thinking: None,
output_config: None,
..request.params
..without_thinking(request.params, context)
},
..request
});
@ -199,7 +252,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 {
@ -277,15 +336,11 @@ fn translate_adaptive_effort_for_non_adaptive_model(
_ => true,
};
if supports_effort_param(capabilities) && (!adaptive_thinking || level_accepted) {
if !adaptive_thinking {
return Ok(request);
}
return Ok(MessagesRequest {
params: MessagesOptionalParams {
thinking: if adaptive_thinking {
None
} else {
request.params.thinking
},
..request.params
},
params: without_thinking(request.params, context),
..request
});
}
@ -294,13 +349,17 @@ fn translate_adaptive_effort_for_non_adaptive_model(
} else {
None
};
let thinking = budget
.and_then(|budget| fit_budget_to_max_tokens(budget, request.params.max_tokens))
.map(|budget| Recognized::Known(ThinkingConfig::enabled(budget)));
let params = MessagesOptionalParams {
output_config: without_effort(request.params.output_config),
..request.params
};
Ok(MessagesRequest {
params: MessagesOptionalParams {
thinking: budget
.and_then(|budget| fit_budget_to_max_tokens(budget, request.params.max_tokens))
.map(|budget| Recognized::Known(ThinkingConfig::enabled(budget))),
output_config: without_effort(request.params.output_config),
..request.params
params: match thinking {
Some(_) => MessagesOptionalParams { thinking, ..params },
None => without_thinking(params, context),
},
..request
})
@ -458,6 +517,14 @@ mod tests {
}
}
#[fixture]
fn sonnet_5() -> MessagesModelCapabilities {
MessagesModelCapabilities {
thinking_on_by_default: true,
..opus_4_7()
}
}
#[fixture]
fn newfamily_6() -> MessagesModelCapabilities {
MessagesModelCapabilities {
@ -625,6 +692,120 @@ mod tests {
assert_eq!(translate(capabilities, input), Ok(request(expected)));
}
#[rstest]
#[case::reasoning_effort_none(
haiku_4_5(),
serde_json::json!({"max_tokens": 8192, "reasoning_effort": "none"}),
false
)]
#[case::reasoning_effort_high(
haiku_4_5(),
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)]
#[case::legacy_thinking_model(haiku_4_5(), claude_code_payload("high", 8192), true)]
#[case::always_on_model_keeps_edit(
fable_5_1(),
serde_json::json!({"max_tokens": 8192, "reasoning_effort": "none"}),
true
)]
#[case::default_thinking_model_keeps_edit(
sonnet_5(),
serde_json::json!({"max_tokens": 8192, "reasoning_effort": "none"}),
true
)]
#[case::adaptive_model_without_default_thinking_drops_edit(
opus_4_7(),
serde_json::json!({"max_tokens": 8192, "reasoning_effort": "none"}),
false
)]
fn clear_thinking_edit_is_dropped_with_thinking_except_on_models_that_think_by_default(
#[case] capabilities: MessagesModelCapabilities,
#[case] fields: Value,
#[case] keeps_edit: bool,
) {
let mut fields = fields;
fields.as_object_mut().unwrap().insert(
"context_management".into(),
serde_json::json!({"edits": [{"type": "clear_thinking_20251015", "keep": "all"}, {"type": "clear_tool_uses_20250919"}]}),
);
let thinks_without_thinking_param =
capabilities.thinking_always_on || capabilities.thinking_on_by_default;
let translated = serde_json::to_value(translate(capabilities, fields).unwrap()).unwrap();
let edit_types: Vec<&str> = translated["context_management"]["edits"]
.as_array()
.unwrap()
.iter()
.filter_map(|edit| edit["type"].as_str())
.collect();
let expected: &[&str] = if keeps_edit {
&["clear_thinking_20251015", "clear_tool_uses_20250919"]
} else {
&["clear_tool_uses_20250919"]
};
assert_eq!(edit_types, expected);
assert_eq!(
translated.get("thinking").is_some(),
keeps_edit && !thinks_without_thinking_param
);
}
#[rstest]
#[case::only_clear_thinking_is_removed_whole(
serde_json::json!({"edits": [{"type": "clear_thinking_20251015"}]}),
None
)]
#[case::any_clear_thinking_version(
serde_json::json!({"edits": [{"type": "clear_thinking_20990101"}, {"type": "compact_20260112"}]}),
Some(serde_json::json!({"edits": [{"type": "compact_20260112"}]}))
)]
#[case::other_keys_kept(
serde_json::json!({"edits": [{"type": "clear_thinking_20251015"}], "extra": 1}),
Some(serde_json::json!({"edits": [], "extra": 1}))
)]
#[case::no_clear_thinking_untouched(
serde_json::json!({"edits": []}),
Some(serde_json::json!({"edits": []}))
)]
#[case::openai_list_untouched(
serde_json::json!([{"type": "compaction"}]),
Some(serde_json::json!([{"type": "compaction"}]))
)]
fn clear_thinking_edits_are_removed(
#[case] context_management: Value,
#[case] expected: Option<Value>,
) {
let translated = translate(
haiku_4_5(),
serde_json::json!({"max_tokens": 8192, "reasoning_effort": "none", "context_management": context_management}),
)
.unwrap();
assert_eq!(
serde_json::to_value(translated)
.unwrap()
.get("context_management")
.cloned(),
expected
);
}
#[rstest]
#[case::minimal_floors_at_min_budget("minimal", 1024)]
#[case::low("low", 1024)]

View file

@ -32,6 +32,8 @@ pub struct MessagesModelCapabilities {
#[serde(default)]
pub thinking_always_on: bool,
#[serde(default)]
pub thinking_on_by_default: bool,
#[serde(default)]
pub supports_legacy_thinking: bool,
#[serde(default)]
pub supports_output_config: bool,
@ -55,6 +57,7 @@ impl Default for MessagesModelCapabilities {
supports_reasoning: false,
supports_adaptive_thinking: false,
thinking_always_on: false,
thinking_on_by_default: false,
supports_legacy_thinking: false,
supports_output_config: false,
supports_sampling_params: true,

View file

@ -155,6 +155,7 @@ pub struct ModelInfoFields {
pub supports_adaptive_thinking: Option<bool>,
pub supports_legacy_thinking: Option<bool>,
pub thinking_always_on: Option<bool>,
pub thinking_on_by_default: Option<bool>,
pub supports_tool_search: Option<bool>,
pub supports_mid_conversation_system: Option<bool>,
pub supports_anthropic_thinking_payload: Option<bool>,

View file

@ -104,6 +104,7 @@ attributes! {
/// Cloud regions the model is available in ('global' or region ids).
supported_regions: Vec<String>,
thinking_always_on: bool,
thinking_on_by_default: bool,
token_count_framing: u64 as Count,
token_count_model: TokenCountModel,
token_count_tokenizer: TokenCountTokenizer,

View file

@ -2,7 +2,7 @@ import copy
import json
import re
import time
from collections.abc import Callable, Mapping, Sequence
from collections.abc import Callable, Mapping, MutableMapping, Sequence
from types import MappingProxyType
from typing import TYPE_CHECKING, Any, ClassVar, Final, NoReturn, cast
@ -48,7 +48,7 @@ from litellm.litellm_core_utils.prompt_templates.mid_conversation_system import
place_mid_conversation_system,
split_leading_system_run,
)
from litellm.litellm_core_utils.reasoning_effort_utils import OPAQUE_VALUE, warn_non_string_reasoning_effort
from litellm.litellm_core_utils.reasoning_effort_utils import warn_non_string_reasoning_effort
from litellm.llms.base_llm.base_utils import (
ResponseFormatSchema,
as_json_object,
@ -123,6 +123,7 @@ from ..common_utils import (
REFUSAL_STOP_FIELDS,
AnthropicError,
AnthropicModelInfo,
drop_clear_thinking_edits_for_model,
eager_input_streaming_flag,
process_anthropic_headers,
refusal_provider_specific_fields,
@ -183,6 +184,16 @@ def _tool_type(tool: object) -> str | None:
return None
def _requested_effort(reasoning_effort: object) -> str | None:
match reasoning_effort:
case str():
return reasoning_effort
case {"effort": str(effort)}:
return effort
case _:
return None
def _is_openai_tool_search_tool(tool: object) -> bool:
return _tool_type(tool) == "tool_search"
@ -1835,6 +1846,10 @@ class AnthropicConfig(AnthropicModelInfo, BaseConfig):
# via ``data = {**optional_params}`` and would 400 with
# ``Extra inputs are not permitted``).
requested_params: Final = cast( # cast-ok: non_default_params is the untyped dict of the shared signature
Mapping[str, object], non_default_params
)
for param, value in non_default_params.items():
if param == "max_tokens" or param == "max_completion_tokens":
optional_params["max_tokens"] = value if isinstance(value, int) else max(1, int(round(value)))
@ -1948,11 +1963,9 @@ class AnthropicConfig(AnthropicModelInfo, BaseConfig):
# send. Coerce to the effort string before mapping — same
# shape-tolerance the GPT-5 path already implements in
# `_normalize_reasoning_effort_for_chat_completion`.
effort_value = value
if isinstance(effort_value, dict):
effort_value = effort_value.get("effort")
if not isinstance(effort_value, str):
warn_non_string_reasoning_effort(OPAQUE_VALUE.validate_python(value), model)
effort_value = _requested_effort(requested_params[param])
if effort_value is None:
warn_non_string_reasoning_effort(requested_params[param], model)
continue
mapped_thinking = AnthropicConfig._map_reasoning_effort(
reasoning_effort=effort_value,
@ -2004,6 +2017,14 @@ class AnthropicConfig(AnthropicModelInfo, BaseConfig):
):
optional_params["service_tier"] = anthropic_tier # rebind-ok: in-place fill is this method's contract
mapped_params: Final = cast( # cast-ok: optional_params is the untyped dict of the shared signature
MutableMapping[str, object], optional_params
)
if "thinking" not in mapped_params and (
"thinking" in requested_params or _requested_effort(requested_params.get("reasoning_effort")) is not None
):
drop_clear_thinking_edits_for_model(mapped_params, model, self._resolved_provider)
## handle thinking tokens
self.update_optional_params_with_thinking_tokens(
non_default_params=non_default_params, optional_params=optional_params
@ -2290,7 +2311,11 @@ class AnthropicConfig(AnthropicModelInfo, BaseConfig):
and not any_assistant_message_has_thinking_blocks(messages)
):
if litellm.modify_params:
optional_params.pop("thinking", None)
mapped_params: Final = cast( # cast-ok: optional_params is the untyped dict of the shared signature
MutableMapping[str, object], optional_params
)
mapped_params.pop("thinking", None)
drop_clear_thinking_edits_for_model(mapped_params, model, self._resolved_provider)
litellm.verbose_logger.warning(
"Dropping 'thinking' param because the last assistant message with tool_calls "
"has no thinking_blocks. The model won't use extended thinking for this turn."

View file

@ -1975,17 +1975,67 @@ def strip_encrypted_reasoning_blocks_from_anthropic_messages(
return [m for m in stripped if m is not None]
def _is_clear_thinking_edit(edit: object) -> bool:
match edit:
case {"type": str(edit_type)}:
return edit_type.startswith("clear_thinking_")
case _:
return False
def drop_clear_thinking_edits(params: MutableMapping[str, object]) -> None: # mutable-ok: edits in place
"""
Remove the ``clear_thinking_*`` context-management edits, which Anthropic
rejects without ``thinking``. ``context_management`` itself goes only when
such an edit was removed and nothing else is left in it.
"""
raw_context_management: Final = params.get("context_management")
if not isinstance(raw_context_management, dict):
return
context_management: Final = OBJECT_MAPPING_ADAPTER.validate_python(raw_context_management)
raw_edits: Final = context_management.get("edits")
if not isinstance(raw_edits, list):
return
edits: Final = OBJECT_LIST_ADAPTER.validate_python(raw_edits)
kept_edits: Final = [edit for edit in edits if not _is_clear_thinking_edit(edit)]
if len(kept_edits) == len(edits):
return
if not kept_edits and len(context_management) == 1:
params.pop("context_management")
return
params["context_management"] = { # rebind-ok: callers mutate their request params in place
**context_management,
"edits": kept_edits,
}
def drop_clear_thinking_edits_for_model(
params: MutableMapping[str, object], # mutable-ok: edits in place
model: str,
custom_llm_provider: str,
) -> None:
"""Remove the clear_thinking edits unless the model still thinks without a ``thinking`` param."""
thinks_without_thinking_param: Final = any(
AnthropicModelInfo.supports_model_capability(model, capability, custom_llm_provider)
for capability in ("thinking_always_on", "thinking_on_by_default")
)
if not thinks_without_thinking_param:
drop_clear_thinking_edits(params)
def strip_thinking_blocks_from_anthropic_messages_request_dict(
data: dict[str, object],
) -> None:
"""
Mutate an Anthropic Messages-style request dict: strip thinking blocks from
``messages`` and remove the top-level ``thinking`` extended-thinking param.
``messages`` and remove ``thinking`` with its dependent context-management
edits (see :func:`drop_clear_thinking_edits`).
"""
msgs: Final = data.get("messages")
if isinstance(msgs, list):
data["messages"] = strip_thinking_blocks_from_anthropic_messages(msgs)
data.pop("thinking", None)
drop_clear_thinking_edits(data)
_DROP_ANTHROPIC_MESSAGE: Final = object()

View file

@ -1,4 +1,4 @@
from collections.abc import AsyncIterator, Mapping, Sequence
from collections.abc import AsyncIterator, Mapping, MutableMapping, Sequence
from typing import ClassVar, Final
import httpx
@ -46,6 +46,7 @@ from ...common_utils import (
AnthropicError,
AnthropicModelInfo,
context_management_betas,
drop_clear_thinking_edits_for_model,
is_claude_model_target,
merge_anthropic_beta_headers,
optionally_handle_anthropic_oauth,
@ -115,6 +116,16 @@ class AnthropicMessagesConfig(BaseAnthropicMessagesConfig):
def _resolved_provider(self) -> str:
return self.custom_llm_provider or "anthropic"
def _drop_thinking(
self,
optional_params: MutableMapping[str, object], # mutable-ok: edits optional params in place
model: str,
custom_llm_provider: str,
max_tokens: int | None,
) -> None:
optional_params.pop("thinking", None)
drop_clear_thinking_edits_for_model(optional_params, model, custom_llm_provider)
def get_supported_anthropic_messages_params(self, model: str) -> list:
return [
"messages",
@ -464,9 +475,8 @@ class AnthropicMessagesConfig(BaseAnthropicMessagesConfig):
messages=messages,
)
@staticmethod
def _translate_reasoning_effort_to_anthropic(
model: str, optional_params: dict, max_tokens: int | None, custom_llm_provider: str
self, model: str, optional_params: dict[str, object], max_tokens: int | None, custom_llm_provider: str
) -> None:
"""Map OpenAI-style ``reasoning_effort`` to native Anthropic params.
@ -495,13 +505,15 @@ class AnthropicMessagesConfig(BaseAnthropicMessagesConfig):
raise AnthropicError(message=str(e.message), status_code=400)
if mapped_thinking is None:
optional_params.pop("thinking", None)
self._drop_thinking(optional_params, model, custom_llm_provider, max_tokens)
optional_params.pop("output_config", None)
return
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, max_tokens)
return
optional_params.setdefault("thinking", fitted_thinking)
@ -525,9 +537,8 @@ class AnthropicMessagesConfig(BaseAnthropicMessagesConfig):
existing_output_config.setdefault("effort", mapped_effort)
optional_params["output_config"] = existing_output_config
@staticmethod
def _translate_adaptive_effort_for_non_adaptive_model(
model: str, optional_params: dict, max_tokens: int | None, custom_llm_provider: str
self, model: str, optional_params: dict[str, object], max_tokens: int | None, custom_llm_provider: str
) -> None:
"""Translate the 4.6+ adaptive-thinking interface (``thinking.type=adaptive``
and/or ``output_config.effort``) down to what an older Anthropic model
@ -590,7 +601,7 @@ class AnthropicMessagesConfig(BaseAnthropicMessagesConfig):
or AnthropicConfig.validate_effort_for_model(model, effort, custom_llm_provider) is None
):
if adaptive_thinking:
optional_params.pop("thinking", None)
self._drop_thinking(optional_params, model, custom_llm_provider, max_tokens)
return
supports_thinking: Final = AnthropicModelInfo.supports_model_capability(
@ -618,7 +629,7 @@ class AnthropicMessagesConfig(BaseAnthropicMessagesConfig):
optional_params["thinking"] = capped_thinking
else:
verbose_logger.warning(DROP_UNSUPPORTED_ADAPTIVE_EFFORT_WARNING, model)
optional_params.pop("thinking", None)
self._drop_thinking(optional_params, model, custom_llm_provider, max_tokens)
if isinstance(output_config, dict) and "effort" in output_config:
residual: Final = {k: v for k, v in output_config.items() if k != "effort"}
@ -703,7 +714,7 @@ class AnthropicMessagesConfig(BaseAnthropicMessagesConfig):
)
self._translate_reasoning_effort_to_anthropic(
model=model,
optional_params=anthropic_messages_optional_request_params,
optional_params=anthropic_messages_optional_request_params, # pyright: ignore[reportUnknownArgumentType] # the request params arrive as a bare dict
max_tokens=max_tokens,
custom_llm_provider=self._resolved_provider,
)
@ -727,7 +738,7 @@ class AnthropicMessagesConfig(BaseAnthropicMessagesConfig):
self._translate_adaptive_effort_for_non_adaptive_model(
model=model,
optional_params=anthropic_messages_optional_request_params,
optional_params=anthropic_messages_optional_request_params, # pyright: ignore[reportUnknownArgumentType] # the request params arrive as a bare dict
max_tokens=max_tokens,
custom_llm_provider=self._resolved_provider,
)

View file

@ -1,4 +1,4 @@
from collections.abc import AsyncIterator, Mapping, Sequence
from collections.abc import AsyncIterator, Mapping, MutableMapping, Sequence
from types import MappingProxyType
from typing import TYPE_CHECKING, Any, Final, cast
@ -100,6 +100,18 @@ class AmazonAnthropicClaudeMessagesConfig(
def beta_headers_provider(self) -> str:
return self.custom_llm_provider or "bedrock"
def _drop_thinking(
self,
optional_params: MutableMapping[str, object], # mutable-ok: edits optional params in place
model: str,
custom_llm_provider: str,
max_tokens: int | None,
) -> None:
"""Keep the clear_thinking edit: it makes
``_ensure_thinking_for_clear_thinking_context_management`` add thinking back,
and ``_filter_context_management_for_bedrock_invoke`` strips it before sending."""
optional_params.pop("thinking", None)
def should_strip_billing_metadata(self) -> bool:
return True

View file

@ -1,8 +1,9 @@
from collections.abc import Mapping
from collections.abc import Mapping, MutableMapping
from types import MappingProxyType
from typing import Final
from litellm.litellm_core_utils.url_utils import join_api_base
from litellm.llms.anthropic.common_utils import drop_clear_thinking_edits_for_model
from litellm.llms.bedrock.base_aws_llm import BaseAWSLLM
from litellm.llms.bedrock.common_utils import MANTLE_MESSAGES_PATH
from litellm.llms.bedrock.messages.mantle_transformation import AmazonMantleMessagesConfig
@ -45,6 +46,26 @@ class BedrockMantleAnthropicMessagesConfig(BedrockMantleAuthMixin, AmazonMantleM
}
)
def _drop_thinking(
self,
optional_params: MutableMapping[str, object], # mutable-ok: edits optional params in place
model: str,
custom_llm_provider: str,
max_tokens: int | None,
) -> None:
super()._drop_thinking(optional_params, model, custom_llm_provider, max_tokens)
if not self._adds_thinking_back_for_clear_thinking(optional_params, model, max_tokens):
drop_clear_thinking_edits_for_model(optional_params, model, custom_llm_provider)
def _adds_thinking_back_for_clear_thinking(
self, optional_params: Mapping[str, object], model: str, max_tokens: int | None
) -> bool:
trial_request: Final = {
"context_management": optional_params.get("context_management"),
"max_tokens": max_tokens,
}
return self._ensure_thinking_for_clear_thinking_context_management(trial_request, model, drop_params=False)
def __init__(self, aws_signer: BaseAWSLLM | None = None) -> None:
AmazonMantleMessagesConfig.__init__(self)
self._aws_signer = aws_signer or self

View file

@ -154,15 +154,13 @@ class BigModelMessagesConfig(OpenAILikeAnthropicMessagesConfig):
model, normalized, request_body, litellm_logging_obj
)
@staticmethod
def _translate_reasoning_effort_to_anthropic(
model: str, optional_params: Mapping[str, object], max_tokens: int | None, custom_llm_provider: str
self, model: str, optional_params: Mapping[str, object], max_tokens: int | None, custom_llm_provider: str
) -> None:
return None
@staticmethod
def _translate_adaptive_effort_for_non_adaptive_model(
model: str, optional_params: Mapping[str, object], max_tokens: int | None, custom_llm_provider: str
self, model: str, optional_params: Mapping[str, object], max_tokens: int | None, custom_llm_provider: str
) -> None:
return None

View file

@ -1939,6 +1939,7 @@
"anthropic.claude-opus-5": {
"bedrock_converse_supports_strict_tools": false,
"supports_adaptive_thinking": true,
"thinking_on_by_default": true,
"supports_mid_conversation_system": true,
"cache_creation_input_token_cost": 6.25e-06,
"cache_creation_input_token_cost_above_1hr": 1e-05,
@ -2025,6 +2026,7 @@
"global.anthropic.claude-opus-5": {
"bedrock_converse_supports_strict_tools": false,
"supports_adaptive_thinking": true,
"thinking_on_by_default": true,
"supports_mid_conversation_system": true,
"cache_creation_input_token_cost": 6.25e-06,
"cache_creation_input_token_cost_above_1hr": 1e-05,
@ -2111,6 +2113,7 @@
"us.anthropic.claude-opus-5": {
"bedrock_converse_supports_strict_tools": false,
"supports_adaptive_thinking": true,
"thinking_on_by_default": true,
"supports_mid_conversation_system": true,
"cache_creation_input_token_cost": 6.875e-06,
"cache_creation_input_token_cost_above_1hr": 1.1e-05,
@ -2197,6 +2200,7 @@
"eu.anthropic.claude-opus-5": {
"bedrock_converse_supports_strict_tools": false,
"supports_adaptive_thinking": true,
"thinking_on_by_default": true,
"supports_mid_conversation_system": true,
"cache_creation_input_token_cost": 6.875e-06,
"cache_creation_input_token_cost_above_1hr": 1.1e-05,
@ -2278,6 +2282,7 @@
"au.anthropic.claude-opus-5": {
"bedrock_converse_supports_strict_tools": false,
"supports_adaptive_thinking": true,
"thinking_on_by_default": true,
"supports_mid_conversation_system": true,
"cache_creation_input_token_cost": 6.875e-06,
"cache_creation_input_token_cost_above_1hr": 1.1e-05,
@ -2364,6 +2369,7 @@
"jp.anthropic.claude-opus-5": {
"bedrock_converse_supports_strict_tools": false,
"supports_adaptive_thinking": true,
"thinking_on_by_default": true,
"supports_mid_conversation_system": true,
"cache_creation_input_token_cost": 6.875e-06,
"cache_creation_input_token_cost_above_1hr": 1.1e-05,
@ -2760,6 +2766,7 @@
"search_context_size_medium": 0.01
},
"supports_adaptive_thinking": true,
"thinking_on_by_default": true,
"supports_mid_conversation_system": true,
"supports_assistant_prefill": false,
"supports_computer_use": true,
@ -2804,6 +2811,7 @@
"search_context_size_medium": 0.01
},
"supports_adaptive_thinking": true,
"thinking_on_by_default": true,
"supports_mid_conversation_system": true,
"supports_assistant_prefill": false,
"supports_computer_use": true,
@ -2848,6 +2856,7 @@
"search_context_size_medium": 0.01
},
"supports_adaptive_thinking": true,
"thinking_on_by_default": true,
"supports_mid_conversation_system": true,
"supports_assistant_prefill": false,
"supports_computer_use": true,
@ -2892,6 +2901,7 @@
"search_context_size_medium": 0.01
},
"supports_adaptive_thinking": true,
"thinking_on_by_default": true,
"supports_mid_conversation_system": true,
"supports_assistant_prefill": false,
"supports_computer_use": true,
@ -2936,6 +2946,7 @@
"search_context_size_medium": 0.01
},
"supports_adaptive_thinking": true,
"thinking_on_by_default": true,
"supports_mid_conversation_system": true,
"supports_assistant_prefill": false,
"supports_computer_use": true,
@ -2980,6 +2991,7 @@
"search_context_size_medium": 0.01
},
"supports_adaptive_thinking": true,
"thinking_on_by_default": true,
"supports_mid_conversation_system": true,
"supports_assistant_prefill": false,
"supports_computer_use": true,
@ -3827,6 +3839,7 @@
"deprecation_date": "2027-07-08",
"supports_mid_conversation_system": true,
"supports_adaptive_thinking": true,
"thinking_on_by_default": true,
"input_cost_per_token": 5e-06,
"output_cost_per_token": 2.5e-05,
"litellm_provider": "azure_ai",
@ -3964,6 +3977,7 @@
"search_context_size_medium": 0.01
},
"supports_adaptive_thinking": true,
"thinking_on_by_default": true,
"supports_assistant_prefill": false,
"supports_computer_use": true,
"supports_function_calling": true,
@ -16169,6 +16183,7 @@
"search_context_size_medium": 0.01
},
"supports_adaptive_thinking": true,
"thinking_on_by_default": true,
"supports_mid_conversation_system": true,
"supports_assistant_prefill": false,
"supports_computer_use": true,
@ -16702,6 +16717,7 @@
"search_context_size_medium": 0.01
},
"supports_adaptive_thinking": true,
"thinking_on_by_default": true,
"supports_mid_conversation_system": true,
"supports_assistant_prefill": false,
"supports_computer_use": true,
@ -21562,6 +21578,7 @@
"prompt_cache_min_tokens": 512,
"source": "https://www.databricks.com/product/pricing/proprietary-foundation-model-serving",
"supports_adaptive_thinking": true,
"thinking_on_by_default": true,
"supports_assistant_prefill": false,
"supports_function_calling": true,
"supports_mid_conversation_system": true,
@ -21719,6 +21736,7 @@
"prompt_cache_min_tokens": 1024,
"source": "https://www.databricks.com/product/pricing/proprietary-foundation-model-serving",
"supports_adaptive_thinking": true,
"thinking_on_by_default": true,
"supports_assistant_prefill": false,
"supports_function_calling": true,
"supports_mid_conversation_system": true,
@ -33155,6 +33173,7 @@
"/v1/messages"
],
"supports_adaptive_thinking": true,
"thinking_on_by_default": true,
"supports_function_calling": true,
"supports_parallel_function_calling": true,
"supports_reasoning": true,
@ -33222,6 +33241,7 @@
"/v1/messages"
],
"supports_adaptive_thinking": true,
"thinking_on_by_default": true,
"supports_function_calling": true,
"supports_parallel_function_calling": true,
"supports_reasoning": true,
@ -33265,6 +33285,7 @@
"/v1/messages"
],
"supports_adaptive_thinking": true,
"thinking_on_by_default": true,
"supports_function_calling": true,
"supports_parallel_function_calling": true,
"supports_reasoning": true,
@ -49386,6 +49407,7 @@
"prompt_cache_min_tokens": 1024,
"source": "https://aws.amazon.com/bedrock/pricing/",
"supports_adaptive_thinking": true,
"thinking_on_by_default": true,
"supports_assistant_prefill": false,
"supports_computer_use": true,
"supports_function_calling": true,
@ -49463,6 +49485,7 @@
"prompt_cache_min_tokens": 512,
"source": "https://aws.amazon.com/bedrock/pricing/",
"supports_adaptive_thinking": true,
"thinking_on_by_default": true,
"supports_assistant_prefill": false,
"supports_computer_use": true,
"supports_function_calling": true,
@ -51863,6 +51886,7 @@
"regional_endpoint_uplift_multiplier": 1.1,
"supports_mid_conversation_system": true,
"supports_adaptive_thinking": true,
"thinking_on_by_default": true,
"cache_creation_input_token_cost": 6.25e-06,
"cache_creation_input_token_cost_above_1hr": 1e-05,
"cache_creation_input_token_cost_batches": 3.125e-06,
@ -51901,6 +51925,7 @@
"regional_endpoint_uplift_multiplier": 1.1,
"supports_mid_conversation_system": true,
"supports_adaptive_thinking": true,
"thinking_on_by_default": true,
"cache_creation_input_token_cost": 6.25e-06,
"cache_creation_input_token_cost_above_1hr": 1e-05,
"cache_creation_input_token_cost_batches": 3.125e-06,
@ -52144,6 +52169,7 @@
"search_context_size_medium": 0.01
},
"supports_adaptive_thinking": true,
"thinking_on_by_default": true,
"supports_assistant_prefill": false,
"supports_computer_use": true,
"supports_function_calling": true,
@ -59497,6 +59523,7 @@
"search_context_size_medium": 0.01
},
"supports_adaptive_thinking": true,
"thinking_on_by_default": true,
"supports_assistant_prefill": false,
"supports_computer_use": true,
"supports_function_calling": true,
@ -63607,6 +63634,14 @@
"thinking_always_on": true
}
},
{
"name": "claude-thinking-on-by-default",
"pattern": "claude-[a-z]+-[5-9](?!\\d)(?:[-._]\\d{1,2}(?!\\d))?",
"description": "Claude at major version 5 or higher, in any id shape that contains claude-<family>-<major>, so new minors such as claude-sonnet-5-1 and provider-namespaced ids match. Every Claude 5 model thinks when a request carries no thinking param, so a clear_thinking context-management edit stays valid when LiteLLM drops thinking. Two-digit majors are deliberately not matched so ids like claude-opus-41 (4.1) are not read as major 41.",
"model_info": {
"thinking_on_by_default": true
}
},
{
"name": "claude-mid-conversation-system",
"pattern": "claude-[a-z]+-(?:4[-._](?:[89]|[1-9]\\d)(?!\\d)|[5-9](?!\\d)(?:[-._]\\d{1,2}(?!\\d))?)",
@ -65707,6 +65742,7 @@
"prompt_cache_min_tokens": 512,
"source": "https://deepinfra.com/pricing",
"supports_adaptive_thinking": true,
"thinking_on_by_default": true,
"supports_function_calling": true,
"supports_reasoning": true,
"supports_response_schema": true,
@ -66022,6 +66058,7 @@
"prompt_cache_min_tokens": 1024,
"source": "https://deepinfra.com/pricing",
"supports_adaptive_thinking": true,
"thinking_on_by_default": true,
"supports_function_calling": true,
"supports_reasoning": true,
"supports_response_schema": true,
@ -68035,6 +68072,7 @@
"output_cost_per_token": 1.2e-05,
"prompt_cache_min_tokens": 1024,
"supports_adaptive_thinking": true,
"thinking_on_by_default": true,
"supports_assistant_prefill": false,
"supports_computer_use": true,
"supports_function_calling": true,
@ -68110,6 +68148,7 @@
"output_cost_per_token": 3e-05,
"prompt_cache_min_tokens": 512,
"supports_adaptive_thinking": true,
"thinking_on_by_default": true,
"supports_assistant_prefill": false,
"supports_computer_use": true,
"supports_function_calling": true,
@ -68323,6 +68362,7 @@
"output_cost_per_token": 1.2e-05,
"prompt_cache_min_tokens": 1024,
"supports_adaptive_thinking": true,
"thinking_on_by_default": true,
"supports_assistant_prefill": false,
"supports_computer_use": true,
"supports_function_calling": true,
@ -68388,6 +68428,7 @@
"output_cost_per_token": 3e-05,
"prompt_cache_min_tokens": 512,
"supports_adaptive_thinking": true,
"thinking_on_by_default": true,
"supports_assistant_prefill": false,
"supports_computer_use": true,
"supports_function_calling": true,
@ -69225,6 +69266,7 @@
"litellm_provider": "perplexity",
"mode": "responses",
"supports_adaptive_thinking": true,
"thinking_on_by_default": true,
"prompt_cache_min_tokens": 512,
"supports_web_search": true,
"supports_function_calling": true,
@ -69248,6 +69290,7 @@
"litellm_provider": "perplexity",
"mode": "responses",
"supports_adaptive_thinking": true,
"thinking_on_by_default": true,
"supports_web_search": true,
"supports_function_calling": true,
"input_cost_per_token": 2e-06,
@ -71324,6 +71367,7 @@
"supports_vision": true,
"supports_web_search": true,
"supports_adaptive_thinking": true,
"thinking_on_by_default": true,
"prompt_cache_min_tokens": 512,
"supports_sampling_params": false
},
@ -71344,6 +71388,7 @@
"supports_vision": true,
"prompt_cache_min_tokens": 1024,
"supports_adaptive_thinking": true,
"thinking_on_by_default": true,
"supports_sampling_params": false
},
"aihubmix/coding-glm-5.3": {
@ -74024,6 +74069,7 @@
"apac.anthropic.claude-opus-5": {
"bedrock_converse_supports_strict_tools": false,
"supports_adaptive_thinking": true,
"thinking_on_by_default": true,
"supports_mid_conversation_system": true,
"litellm_provider": "bedrock_converse",
"supports_tool_search": true,
@ -74138,6 +74184,7 @@
"max_output_tokens": 128000,
"mode": "chat",
"supports_adaptive_thinking": true,
"thinking_on_by_default": true,
"supports_mid_conversation_system": true,
"supports_assistant_prefill": false,
"supports_computer_use": true,
@ -75628,6 +75675,7 @@
"search_context_size_medium": 0.01
},
"supports_adaptive_thinking": true,
"thinking_on_by_default": true,
"supports_mid_conversation_system": true,
"supports_assistant_prefill": false,
"supports_computer_use": true,
@ -75680,6 +75728,7 @@
"search_context_size_medium": 0.01
},
"supports_adaptive_thinking": true,
"thinking_on_by_default": true,
"supports_mid_conversation_system": true,
"supports_assistant_prefill": false,
"supports_computer_use": true,
@ -75724,6 +75773,7 @@
"thinking_always_on": false,
"prompt_cache_min_tokens": 512,
"supports_adaptive_thinking": true,
"thinking_on_by_default": true,
"supports_assistant_prefill": false,
"supports_computer_use": true,
"supports_function_calling": true,
@ -75771,6 +75821,7 @@
"search_context_size_medium": 0.01
},
"supports_adaptive_thinking": true,
"thinking_on_by_default": true,
"supports_mid_conversation_system": true,
"supports_assistant_prefill": false,
"supports_computer_use": true,
@ -75807,6 +75858,7 @@
"max_output_tokens": 128000,
"mode": "chat",
"supports_adaptive_thinking": true,
"thinking_on_by_default": true,
"supports_mid_conversation_system": true,
"supports_assistant_prefill": false,
"supports_computer_use": true,
@ -75863,6 +75915,7 @@
"search_context_size_medium": 0.01
},
"supports_adaptive_thinking": true,
"thinking_on_by_default": true,
"supports_mid_conversation_system": true,
"supports_assistant_prefill": false,
"supports_computer_use": true,
@ -75909,6 +75962,7 @@
"search_context_size_medium": 0.01
},
"supports_adaptive_thinking": true,
"thinking_on_by_default": true,
"supports_assistant_prefill": false,
"supports_computer_use": true,
"supports_function_calling": true,
@ -75949,6 +76003,7 @@
"prompt_cache_min_tokens": 512,
"source": "https://pricing.us-east-1.amazonaws.com/offers/v1.0/aws/AmazonBedrockFoundationModels/current/index.json",
"supports_adaptive_thinking": true,
"thinking_on_by_default": true,
"supports_assistant_prefill": false,
"supports_computer_use": true,
"supports_function_calling": true,
@ -75990,6 +76045,7 @@
"prompt_cache_min_tokens": 512,
"source": "https://pricing.us-east-1.amazonaws.com/offers/v1.0/aws/AmazonBedrockFoundationModels/current/index.json",
"supports_adaptive_thinking": true,
"thinking_on_by_default": true,
"supports_assistant_prefill": false,
"supports_computer_use": true,
"supports_function_calling": true,
@ -76036,6 +76092,7 @@
"search_context_size_medium": 0.01
},
"supports_adaptive_thinking": true,
"thinking_on_by_default": true,
"supports_mid_conversation_system": true,
"supports_assistant_prefill": false,
"supports_computer_use": true,
@ -76087,6 +76144,7 @@
"search_context_size_medium": 0.01
},
"supports_adaptive_thinking": true,
"thinking_on_by_default": true,
"supports_mid_conversation_system": true,
"supports_assistant_prefill": false,
"supports_computer_use": true,
@ -76133,6 +76191,7 @@
"search_context_size_medium": 0.01
},
"supports_adaptive_thinking": true,
"thinking_on_by_default": true,
"supports_mid_conversation_system": true,
"supports_assistant_prefill": false,
"supports_computer_use": true,
@ -76160,6 +76219,7 @@
"litellm_provider": "perplexity",
"mode": "responses",
"supports_adaptive_thinking": true,
"thinking_on_by_default": true,
"supports_web_search": true,
"supports_function_calling": true,
"input_cost_per_token": 1e-07,
@ -76190,6 +76250,7 @@
"prompt_cache_min_tokens": 512,
"source": "https://pricing.us-east-1.amazonaws.com/offers/v1.0/aws/AmazonBedrockFoundationModels/current/index.json",
"supports_adaptive_thinking": true,
"thinking_on_by_default": true,
"supports_assistant_prefill": false,
"supports_computer_use": true,
"supports_function_calling": true,
@ -76241,6 +76302,7 @@
"search_context_size_medium": 0.01
},
"supports_adaptive_thinking": true,
"thinking_on_by_default": true,
"supports_mid_conversation_system": true,
"supports_assistant_prefill": false,
"supports_computer_use": true,
@ -76291,6 +76353,7 @@
"search_context_size_medium": 0.01
},
"supports_adaptive_thinking": true,
"thinking_on_by_default": true,
"supports_assistant_prefill": false,
"supports_computer_use": true,
"supports_function_calling": true,
@ -76335,6 +76398,7 @@
"search_context_size_medium": 0.01
},
"supports_adaptive_thinking": true,
"thinking_on_by_default": true,
"supports_assistant_prefill": false,
"supports_computer_use": true,
"supports_function_calling": true,
@ -78094,6 +78158,7 @@
"search_context_size_medium": 0.01
},
"supports_adaptive_thinking": true,
"thinking_on_by_default": true,
"supports_mid_conversation_system": true,
"supports_assistant_prefill": false,
"supports_computer_use": true,
@ -78308,6 +78373,7 @@
"search_context_size_medium": 0.01
},
"supports_adaptive_thinking": true,
"thinking_on_by_default": true,
"supports_mid_conversation_system": true,
"supports_assistant_prefill": false,
"supports_computer_use": true,
@ -78472,6 +78538,7 @@
"search_context_size_medium": 0.01
},
"supports_adaptive_thinking": true,
"thinking_on_by_default": true,
"supports_mid_conversation_system": true,
"supports_assistant_prefill": false,
"supports_computer_use": true,
@ -80454,6 +80521,7 @@
"prompt_cache_min_tokens": 512.0,
"source": "https://www.databricks.com/product/pricing/proprietary-foundation-model-serving",
"supports_adaptive_thinking": true,
"thinking_on_by_default": true,
"supports_anthropic_thinking_payload": true,
"supports_assistant_prefill": false,
"supports_function_calling": true,

View file

@ -27,6 +27,7 @@ def anthropic_model_capabilities(model: str, custom_llm_provider: str | None) ->
"supports_reasoning": supports("supports_reasoning"),
"supports_adaptive_thinking": supports("supports_adaptive_thinking"),
"thinking_always_on": supports("thinking_always_on"),
"thinking_on_by_default": supports("thinking_on_by_default"),
"supports_legacy_thinking": supports("supports_legacy_thinking"),
"supports_output_config": supports("supports_output_config"),
"supports_sampling_params": AnthropicModelInfo._supports_sampling_params(resolved_model), # pyright: ignore[reportPrivateUsage] # same gate the handler applies

View file

@ -223,6 +223,7 @@ class ProviderSpecificModelInfo(TypedDict, total=False):
supports_adaptive_thinking: bool | None
supports_legacy_thinking: ReadOnly[bool | None]
thinking_always_on: ReadOnly[bool | None]
thinking_on_by_default: ReadOnly[bool | None]
supports_tool_search: bool | None
supports_mid_conversation_system: bool | None
supports_url_context: bool | None

View file

@ -7353,6 +7353,7 @@ def get_model_info_helper(
supports_adaptive_thinking=_model_info.get("supports_adaptive_thinking", None),
supports_legacy_thinking=_model_info.get("supports_legacy_thinking", None),
thinking_always_on=_model_info.get("thinking_always_on", None),
thinking_on_by_default=_model_info.get("thinking_on_by_default", None),
supports_tool_search=_model_info.get("supports_tool_search", None),
supports_mid_conversation_system=_model_info.get("supports_mid_conversation_system", None),
supports_anthropic_thinking_payload=_model_info.get("supports_anthropic_thinking_payload", None),

View file

@ -1939,6 +1939,7 @@
"anthropic.claude-opus-5": {
"bedrock_converse_supports_strict_tools": false,
"supports_adaptive_thinking": true,
"thinking_on_by_default": true,
"supports_mid_conversation_system": true,
"cache_creation_input_token_cost": 6.25e-06,
"cache_creation_input_token_cost_above_1hr": 1e-05,
@ -2025,6 +2026,7 @@
"global.anthropic.claude-opus-5": {
"bedrock_converse_supports_strict_tools": false,
"supports_adaptive_thinking": true,
"thinking_on_by_default": true,
"supports_mid_conversation_system": true,
"cache_creation_input_token_cost": 6.25e-06,
"cache_creation_input_token_cost_above_1hr": 1e-05,
@ -2111,6 +2113,7 @@
"us.anthropic.claude-opus-5": {
"bedrock_converse_supports_strict_tools": false,
"supports_adaptive_thinking": true,
"thinking_on_by_default": true,
"supports_mid_conversation_system": true,
"cache_creation_input_token_cost": 6.875e-06,
"cache_creation_input_token_cost_above_1hr": 1.1e-05,
@ -2197,6 +2200,7 @@
"eu.anthropic.claude-opus-5": {
"bedrock_converse_supports_strict_tools": false,
"supports_adaptive_thinking": true,
"thinking_on_by_default": true,
"supports_mid_conversation_system": true,
"cache_creation_input_token_cost": 6.875e-06,
"cache_creation_input_token_cost_above_1hr": 1.1e-05,
@ -2278,6 +2282,7 @@
"au.anthropic.claude-opus-5": {
"bedrock_converse_supports_strict_tools": false,
"supports_adaptive_thinking": true,
"thinking_on_by_default": true,
"supports_mid_conversation_system": true,
"cache_creation_input_token_cost": 6.875e-06,
"cache_creation_input_token_cost_above_1hr": 1.1e-05,
@ -2364,6 +2369,7 @@
"jp.anthropic.claude-opus-5": {
"bedrock_converse_supports_strict_tools": false,
"supports_adaptive_thinking": true,
"thinking_on_by_default": true,
"supports_mid_conversation_system": true,
"cache_creation_input_token_cost": 6.875e-06,
"cache_creation_input_token_cost_above_1hr": 1.1e-05,
@ -2760,6 +2766,7 @@
"search_context_size_medium": 0.01
},
"supports_adaptive_thinking": true,
"thinking_on_by_default": true,
"supports_mid_conversation_system": true,
"supports_assistant_prefill": false,
"supports_computer_use": true,
@ -2804,6 +2811,7 @@
"search_context_size_medium": 0.01
},
"supports_adaptive_thinking": true,
"thinking_on_by_default": true,
"supports_mid_conversation_system": true,
"supports_assistant_prefill": false,
"supports_computer_use": true,
@ -2848,6 +2856,7 @@
"search_context_size_medium": 0.01
},
"supports_adaptive_thinking": true,
"thinking_on_by_default": true,
"supports_mid_conversation_system": true,
"supports_assistant_prefill": false,
"supports_computer_use": true,
@ -2892,6 +2901,7 @@
"search_context_size_medium": 0.01
},
"supports_adaptive_thinking": true,
"thinking_on_by_default": true,
"supports_mid_conversation_system": true,
"supports_assistant_prefill": false,
"supports_computer_use": true,
@ -2936,6 +2946,7 @@
"search_context_size_medium": 0.01
},
"supports_adaptive_thinking": true,
"thinking_on_by_default": true,
"supports_mid_conversation_system": true,
"supports_assistant_prefill": false,
"supports_computer_use": true,
@ -2980,6 +2991,7 @@
"search_context_size_medium": 0.01
},
"supports_adaptive_thinking": true,
"thinking_on_by_default": true,
"supports_mid_conversation_system": true,
"supports_assistant_prefill": false,
"supports_computer_use": true,
@ -3827,6 +3839,7 @@
"deprecation_date": "2027-07-08",
"supports_mid_conversation_system": true,
"supports_adaptive_thinking": true,
"thinking_on_by_default": true,
"input_cost_per_token": 5e-06,
"output_cost_per_token": 2.5e-05,
"litellm_provider": "azure_ai",
@ -3964,6 +3977,7 @@
"search_context_size_medium": 0.01
},
"supports_adaptive_thinking": true,
"thinking_on_by_default": true,
"supports_assistant_prefill": false,
"supports_computer_use": true,
"supports_function_calling": true,
@ -16169,6 +16183,7 @@
"search_context_size_medium": 0.01
},
"supports_adaptive_thinking": true,
"thinking_on_by_default": true,
"supports_mid_conversation_system": true,
"supports_assistant_prefill": false,
"supports_computer_use": true,
@ -16702,6 +16717,7 @@
"search_context_size_medium": 0.01
},
"supports_adaptive_thinking": true,
"thinking_on_by_default": true,
"supports_mid_conversation_system": true,
"supports_assistant_prefill": false,
"supports_computer_use": true,
@ -21562,6 +21578,7 @@
"prompt_cache_min_tokens": 512,
"source": "https://www.databricks.com/product/pricing/proprietary-foundation-model-serving",
"supports_adaptive_thinking": true,
"thinking_on_by_default": true,
"supports_assistant_prefill": false,
"supports_function_calling": true,
"supports_mid_conversation_system": true,
@ -21719,6 +21736,7 @@
"prompt_cache_min_tokens": 1024,
"source": "https://www.databricks.com/product/pricing/proprietary-foundation-model-serving",
"supports_adaptive_thinking": true,
"thinking_on_by_default": true,
"supports_assistant_prefill": false,
"supports_function_calling": true,
"supports_mid_conversation_system": true,
@ -33155,6 +33173,7 @@
"/v1/messages"
],
"supports_adaptive_thinking": true,
"thinking_on_by_default": true,
"supports_function_calling": true,
"supports_parallel_function_calling": true,
"supports_reasoning": true,
@ -33222,6 +33241,7 @@
"/v1/messages"
],
"supports_adaptive_thinking": true,
"thinking_on_by_default": true,
"supports_function_calling": true,
"supports_parallel_function_calling": true,
"supports_reasoning": true,
@ -33265,6 +33285,7 @@
"/v1/messages"
],
"supports_adaptive_thinking": true,
"thinking_on_by_default": true,
"supports_function_calling": true,
"supports_parallel_function_calling": true,
"supports_reasoning": true,
@ -49386,6 +49407,7 @@
"prompt_cache_min_tokens": 1024,
"source": "https://aws.amazon.com/bedrock/pricing/",
"supports_adaptive_thinking": true,
"thinking_on_by_default": true,
"supports_assistant_prefill": false,
"supports_computer_use": true,
"supports_function_calling": true,
@ -49463,6 +49485,7 @@
"prompt_cache_min_tokens": 512,
"source": "https://aws.amazon.com/bedrock/pricing/",
"supports_adaptive_thinking": true,
"thinking_on_by_default": true,
"supports_assistant_prefill": false,
"supports_computer_use": true,
"supports_function_calling": true,
@ -51863,6 +51886,7 @@
"regional_endpoint_uplift_multiplier": 1.1,
"supports_mid_conversation_system": true,
"supports_adaptive_thinking": true,
"thinking_on_by_default": true,
"cache_creation_input_token_cost": 6.25e-06,
"cache_creation_input_token_cost_above_1hr": 1e-05,
"cache_creation_input_token_cost_batches": 3.125e-06,
@ -51901,6 +51925,7 @@
"regional_endpoint_uplift_multiplier": 1.1,
"supports_mid_conversation_system": true,
"supports_adaptive_thinking": true,
"thinking_on_by_default": true,
"cache_creation_input_token_cost": 6.25e-06,
"cache_creation_input_token_cost_above_1hr": 1e-05,
"cache_creation_input_token_cost_batches": 3.125e-06,
@ -52144,6 +52169,7 @@
"search_context_size_medium": 0.01
},
"supports_adaptive_thinking": true,
"thinking_on_by_default": true,
"supports_assistant_prefill": false,
"supports_computer_use": true,
"supports_function_calling": true,
@ -59497,6 +59523,7 @@
"search_context_size_medium": 0.01
},
"supports_adaptive_thinking": true,
"thinking_on_by_default": true,
"supports_assistant_prefill": false,
"supports_computer_use": true,
"supports_function_calling": true,
@ -63607,6 +63634,14 @@
"thinking_always_on": true
}
},
{
"name": "claude-thinking-on-by-default",
"pattern": "claude-[a-z]+-[5-9](?!\\d)(?:[-._]\\d{1,2}(?!\\d))?",
"description": "Claude at major version 5 or higher, in any id shape that contains claude-<family>-<major>, so new minors such as claude-sonnet-5-1 and provider-namespaced ids match. Every Claude 5 model thinks when a request carries no thinking param, so a clear_thinking context-management edit stays valid when LiteLLM drops thinking. Two-digit majors are deliberately not matched so ids like claude-opus-41 (4.1) are not read as major 41.",
"model_info": {
"thinking_on_by_default": true
}
},
{
"name": "claude-mid-conversation-system",
"pattern": "claude-[a-z]+-(?:4[-._](?:[89]|[1-9]\\d)(?!\\d)|[5-9](?!\\d)(?:[-._]\\d{1,2}(?!\\d))?)",
@ -65707,6 +65742,7 @@
"prompt_cache_min_tokens": 512,
"source": "https://deepinfra.com/pricing",
"supports_adaptive_thinking": true,
"thinking_on_by_default": true,
"supports_function_calling": true,
"supports_reasoning": true,
"supports_response_schema": true,
@ -66022,6 +66058,7 @@
"prompt_cache_min_tokens": 1024,
"source": "https://deepinfra.com/pricing",
"supports_adaptive_thinking": true,
"thinking_on_by_default": true,
"supports_function_calling": true,
"supports_reasoning": true,
"supports_response_schema": true,
@ -68035,6 +68072,7 @@
"output_cost_per_token": 1.2e-05,
"prompt_cache_min_tokens": 1024,
"supports_adaptive_thinking": true,
"thinking_on_by_default": true,
"supports_assistant_prefill": false,
"supports_computer_use": true,
"supports_function_calling": true,
@ -68110,6 +68148,7 @@
"output_cost_per_token": 3e-05,
"prompt_cache_min_tokens": 512,
"supports_adaptive_thinking": true,
"thinking_on_by_default": true,
"supports_assistant_prefill": false,
"supports_computer_use": true,
"supports_function_calling": true,
@ -68323,6 +68362,7 @@
"output_cost_per_token": 1.2e-05,
"prompt_cache_min_tokens": 1024,
"supports_adaptive_thinking": true,
"thinking_on_by_default": true,
"supports_assistant_prefill": false,
"supports_computer_use": true,
"supports_function_calling": true,
@ -68388,6 +68428,7 @@
"output_cost_per_token": 3e-05,
"prompt_cache_min_tokens": 512,
"supports_adaptive_thinking": true,
"thinking_on_by_default": true,
"supports_assistant_prefill": false,
"supports_computer_use": true,
"supports_function_calling": true,
@ -69225,6 +69266,7 @@
"litellm_provider": "perplexity",
"mode": "responses",
"supports_adaptive_thinking": true,
"thinking_on_by_default": true,
"prompt_cache_min_tokens": 512,
"supports_web_search": true,
"supports_function_calling": true,
@ -69248,6 +69290,7 @@
"litellm_provider": "perplexity",
"mode": "responses",
"supports_adaptive_thinking": true,
"thinking_on_by_default": true,
"supports_web_search": true,
"supports_function_calling": true,
"input_cost_per_token": 2e-06,
@ -71324,6 +71367,7 @@
"supports_vision": true,
"supports_web_search": true,
"supports_adaptive_thinking": true,
"thinking_on_by_default": true,
"prompt_cache_min_tokens": 512,
"supports_sampling_params": false
},
@ -71344,6 +71388,7 @@
"supports_vision": true,
"prompt_cache_min_tokens": 1024,
"supports_adaptive_thinking": true,
"thinking_on_by_default": true,
"supports_sampling_params": false
},
"aihubmix/coding-glm-5.3": {
@ -74024,6 +74069,7 @@
"apac.anthropic.claude-opus-5": {
"bedrock_converse_supports_strict_tools": false,
"supports_adaptive_thinking": true,
"thinking_on_by_default": true,
"supports_mid_conversation_system": true,
"litellm_provider": "bedrock_converse",
"supports_tool_search": true,
@ -74138,6 +74184,7 @@
"max_output_tokens": 128000,
"mode": "chat",
"supports_adaptive_thinking": true,
"thinking_on_by_default": true,
"supports_mid_conversation_system": true,
"supports_assistant_prefill": false,
"supports_computer_use": true,
@ -75628,6 +75675,7 @@
"search_context_size_medium": 0.01
},
"supports_adaptive_thinking": true,
"thinking_on_by_default": true,
"supports_mid_conversation_system": true,
"supports_assistant_prefill": false,
"supports_computer_use": true,
@ -75680,6 +75728,7 @@
"search_context_size_medium": 0.01
},
"supports_adaptive_thinking": true,
"thinking_on_by_default": true,
"supports_mid_conversation_system": true,
"supports_assistant_prefill": false,
"supports_computer_use": true,
@ -75724,6 +75773,7 @@
"thinking_always_on": false,
"prompt_cache_min_tokens": 512,
"supports_adaptive_thinking": true,
"thinking_on_by_default": true,
"supports_assistant_prefill": false,
"supports_computer_use": true,
"supports_function_calling": true,
@ -75771,6 +75821,7 @@
"search_context_size_medium": 0.01
},
"supports_adaptive_thinking": true,
"thinking_on_by_default": true,
"supports_mid_conversation_system": true,
"supports_assistant_prefill": false,
"supports_computer_use": true,
@ -75807,6 +75858,7 @@
"max_output_tokens": 128000,
"mode": "chat",
"supports_adaptive_thinking": true,
"thinking_on_by_default": true,
"supports_mid_conversation_system": true,
"supports_assistant_prefill": false,
"supports_computer_use": true,
@ -75863,6 +75915,7 @@
"search_context_size_medium": 0.01
},
"supports_adaptive_thinking": true,
"thinking_on_by_default": true,
"supports_mid_conversation_system": true,
"supports_assistant_prefill": false,
"supports_computer_use": true,
@ -75909,6 +75962,7 @@
"search_context_size_medium": 0.01
},
"supports_adaptive_thinking": true,
"thinking_on_by_default": true,
"supports_assistant_prefill": false,
"supports_computer_use": true,
"supports_function_calling": true,
@ -75949,6 +76003,7 @@
"prompt_cache_min_tokens": 512,
"source": "https://pricing.us-east-1.amazonaws.com/offers/v1.0/aws/AmazonBedrockFoundationModels/current/index.json",
"supports_adaptive_thinking": true,
"thinking_on_by_default": true,
"supports_assistant_prefill": false,
"supports_computer_use": true,
"supports_function_calling": true,
@ -75990,6 +76045,7 @@
"prompt_cache_min_tokens": 512,
"source": "https://pricing.us-east-1.amazonaws.com/offers/v1.0/aws/AmazonBedrockFoundationModels/current/index.json",
"supports_adaptive_thinking": true,
"thinking_on_by_default": true,
"supports_assistant_prefill": false,
"supports_computer_use": true,
"supports_function_calling": true,
@ -76036,6 +76092,7 @@
"search_context_size_medium": 0.01
},
"supports_adaptive_thinking": true,
"thinking_on_by_default": true,
"supports_mid_conversation_system": true,
"supports_assistant_prefill": false,
"supports_computer_use": true,
@ -76087,6 +76144,7 @@
"search_context_size_medium": 0.01
},
"supports_adaptive_thinking": true,
"thinking_on_by_default": true,
"supports_mid_conversation_system": true,
"supports_assistant_prefill": false,
"supports_computer_use": true,
@ -76133,6 +76191,7 @@
"search_context_size_medium": 0.01
},
"supports_adaptive_thinking": true,
"thinking_on_by_default": true,
"supports_mid_conversation_system": true,
"supports_assistant_prefill": false,
"supports_computer_use": true,
@ -76160,6 +76219,7 @@
"litellm_provider": "perplexity",
"mode": "responses",
"supports_adaptive_thinking": true,
"thinking_on_by_default": true,
"supports_web_search": true,
"supports_function_calling": true,
"input_cost_per_token": 1e-07,
@ -76190,6 +76250,7 @@
"prompt_cache_min_tokens": 512,
"source": "https://pricing.us-east-1.amazonaws.com/offers/v1.0/aws/AmazonBedrockFoundationModels/current/index.json",
"supports_adaptive_thinking": true,
"thinking_on_by_default": true,
"supports_assistant_prefill": false,
"supports_computer_use": true,
"supports_function_calling": true,
@ -76241,6 +76302,7 @@
"search_context_size_medium": 0.01
},
"supports_adaptive_thinking": true,
"thinking_on_by_default": true,
"supports_mid_conversation_system": true,
"supports_assistant_prefill": false,
"supports_computer_use": true,
@ -76291,6 +76353,7 @@
"search_context_size_medium": 0.01
},
"supports_adaptive_thinking": true,
"thinking_on_by_default": true,
"supports_assistant_prefill": false,
"supports_computer_use": true,
"supports_function_calling": true,
@ -76335,6 +76398,7 @@
"search_context_size_medium": 0.01
},
"supports_adaptive_thinking": true,
"thinking_on_by_default": true,
"supports_assistant_prefill": false,
"supports_computer_use": true,
"supports_function_calling": true,
@ -78094,6 +78158,7 @@
"search_context_size_medium": 0.01
},
"supports_adaptive_thinking": true,
"thinking_on_by_default": true,
"supports_mid_conversation_system": true,
"supports_assistant_prefill": false,
"supports_computer_use": true,
@ -78308,6 +78373,7 @@
"search_context_size_medium": 0.01
},
"supports_adaptive_thinking": true,
"thinking_on_by_default": true,
"supports_mid_conversation_system": true,
"supports_assistant_prefill": false,
"supports_computer_use": true,
@ -78472,6 +78538,7 @@
"search_context_size_medium": 0.01
},
"supports_adaptive_thinking": true,
"thinking_on_by_default": true,
"supports_mid_conversation_system": true,
"supports_assistant_prefill": false,
"supports_computer_use": true,
@ -80454,6 +80521,7 @@
"prompt_cache_min_tokens": 512.0,
"source": "https://www.databricks.com/product/pricing/proprietary-foundation-model-serving",
"supports_adaptive_thinking": true,
"thinking_on_by_default": true,
"supports_anthropic_thinking_payload": true,
"supports_assistant_prefill": false,
"supports_function_calling": true,

View file

@ -9849,3 +9849,108 @@ def test_transform_request_tool_config_point_without_tools_drops_the_key():
assert "cache_control_injection_points" not in data
assert "tools" not in data
assert litellm_params["metadata"] == {}
CLEAR_THINKING_EDIT: Final = {"type": "clear_thinking_20251015", "keep": "all"}
CLEAR_TOOL_USES_EDIT: Final = {"type": "clear_tool_uses_20250919"}
def _edits_for(keeps_thinking: bool) -> list[dict[str, str]]:
return [CLEAR_THINKING_EDIT, CLEAR_TOOL_USES_EDIT] if keeps_thinking else [CLEAR_TOOL_USES_EDIT]
@pytest.mark.parametrize(
("model", "params", "keeps_thinking"),
[
pytest.param(
"claude-haiku-4-5",
{"thinking": {"type": "adaptive"}, "max_tokens": 1000},
False,
id="adaptive-unfit-dropped",
),
pytest.param(
"claude-haiku-4-5", {"thinking": {"type": "adaptive"}, "max_tokens": 8192}, True, id="adaptive-translated"
),
pytest.param("claude-sonnet-4-5", {"reasoning_effort": "none"}, False, id="reasoning-effort-none"),
pytest.param(
"claude-sonnet-4-5", {"reasoning_effort": {"effort": "none"}}, False, id="reasoning-effort-dict-none"
),
pytest.param("claude-sonnet-4-5", {"reasoning_effort": "high"}, True, id="reasoning-effort-high"),
],
)
@pytest.mark.parametrize("context_management_first", [True, False])
def test_map_openai_params_drops_clear_thinking_edit_exactly_when_thinking_is(
model: str, params: dict[str, object], keeps_thinking: bool, context_management_first: bool
) -> None:
context_management: Final = {"context_management": {"edits": [CLEAR_THINKING_EDIT, CLEAR_TOOL_USES_EDIT]}}
rest: Final = {"max_tokens": 8192, **params}
result: Final = AnthropicConfig().map_openai_params(
non_default_params={**context_management, **rest}
if context_management_first
else {**rest, **context_management},
optional_params={},
model=model,
drop_params=False,
)
assert ("thinking" in result) is keeps_thinking
assert result["context_management"] == {"edits": _edits_for(keeps_thinking)}
@pytest.mark.parametrize(
("model", "reasoning_effort"),
[
pytest.param("claude-fable-5", "none", id="always-on-model"),
pytest.param("claude-sonnet-4-5", {"summary": "concise"}, id="effort-less-dict"),
],
)
def test_map_openai_params_keeps_clear_thinking_edit_when_thinking_was_not_dropped(
model: str, reasoning_effort: str | dict[str, str]
) -> None:
result: Final = AnthropicConfig().map_openai_params(
non_default_params={
"context_management": {"edits": [CLEAR_THINKING_EDIT]},
"max_tokens": 8192,
"reasoning_effort": reasoning_effort,
},
optional_params={},
model=model,
drop_params=False,
)
assert result["context_management"] == {"edits": [CLEAR_THINKING_EDIT]}
@pytest.mark.parametrize(
("model", "modify_params", "keeps_thinking", "keeps_edit"),
[
pytest.param("claude-sonnet-4-5", True, False, False, id="dropped"),
pytest.param("claude-sonnet-4-5", False, True, True, id="modify-params-off"),
pytest.param("claude-fable-5", True, False, True, id="always-on-keeps-edit"),
],
)
def test_transform_request_drops_clear_thinking_edit_with_thinking_for_tool_turn_without_thinking_blocks(
monkeypatch: pytest.MonkeyPatch, model: str, modify_params: bool, keeps_thinking: bool, keeps_edit: bool
) -> None:
monkeypatch.setattr(litellm, "modify_params", modify_params)
result: Final = AnthropicConfig().transform_request(
model=model,
messages=[
{"role": "user", "content": "Look it up"},
{
"role": "assistant",
"content": None,
"tool_calls": [{"id": "call_1", "type": "function", "function": {"name": "lookup", "arguments": "{}"}}],
},
{"role": "tool", "tool_call_id": "call_1", "content": "found"},
],
optional_params={
"thinking": {"type": "enabled", "budget_tokens": 1024},
"context_management": {"edits": [CLEAR_THINKING_EDIT, CLEAR_TOOL_USES_EDIT]},
},
litellm_params={},
headers={},
)
assert ("thinking" in result) is keeps_thinking
assert result["context_management"] == {"edits": _edits_for(keeps_edit)}

View file

@ -1,3 +1,5 @@
from typing import Final
import pytest
from litellm.constants import (
@ -334,3 +336,70 @@ def test_reasoning_effort_budget_capped_for_openai_like_messages_upstream():
assert result["thinking"] == {"type": "enabled", "budget_tokens": 3999}
assert result["max_tokens"] == 4000
CLEAR_THINKING_EDIT: Final = {"type": "clear_thinking_20251015", "keep": "all"}
CLEAR_TOOL_USES_EDIT: Final = {"type": "clear_tool_uses_20250919"}
@pytest.mark.parametrize(
("model", "params", "keeps_thinking"),
[
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"),
pytest.param("claude-haiku-4-5", _claude_code_payload(), True, id="legacy-thinking-model"),
pytest.param(
"claude-opus-4-8", {"reasoning_effort": "none"}, False, id="adaptive-model-without-default-thinking"
),
],
)
def test_clear_thinking_edit_dropped_exactly_when_thinking_is(
model: str, params: dict[str, object], keeps_thinking: bool
) -> None:
result: Final = _transform(
model,
{
"max_tokens": 8192,
**params,
"context_management": {"edits": [CLEAR_THINKING_EDIT, CLEAR_TOOL_USES_EDIT]},
},
)
assert ("thinking" in result) is keeps_thinking
expected_edits: Final = [CLEAR_THINKING_EDIT, CLEAR_TOOL_USES_EDIT] if keeps_thinking else [CLEAR_TOOL_USES_EDIT]
assert result["context_management"] == {"edits": expected_edits}
@pytest.mark.parametrize(
"model",
[
"claude-fable-5",
"claude-sonnet-5",
"claude-opus-5",
"claude-haiku-5-5",
pytest.param("claude-sonnet-5-1", id="unmapped-claude-5-minor"),
pytest.param("claude-opus-5-2-20270101", id="unmapped-dated-claude-5-minor"),
],
)
def test_clear_thinking_edit_kept_when_model_thinks_without_thinking_param(model: str) -> None:
result: Final = _transform(
model,
{"max_tokens": 8192, "reasoning_effort": "none", "context_management": {"edits": [CLEAR_THINKING_EDIT]}},
)
assert result["context_management"] == {"edits": [CLEAR_THINKING_EDIT]}

View file

@ -1612,6 +1612,52 @@ class TestAnthropicThinkingSignatureSelfHeal:
assert "thinking" not in data
assert data["messages"] == []
def test_strip_thinking_request_dict_drops_clear_thinking_edits(self) -> None:
from litellm.llms.anthropic.common_utils import (
strip_thinking_blocks_from_anthropic_messages_request_dict,
)
data: Final[dict[str, object]] = {
"messages": [{"role": "assistant", "content": [{"type": "thinking", "thinking": "unsigned"}]}],
"thinking": {"type": "adaptive"},
"context_management": {"edits": [{"type": "clear_thinking_20251015", "keep": "all"}]},
}
strip_thinking_blocks_from_anthropic_messages_request_dict(data)
assert "context_management" not in data
@pytest.mark.parametrize(
("context_management", "expected"),
[
pytest.param(
{"edits": [{"type": "clear_thinking_20990101"}, {"type": "clear_tool_uses_20250919"}]},
{"edits": [{"type": "clear_tool_uses_20250919"}]},
id="any-clear-thinking-version-dropped-others-kept",
),
pytest.param({"edits": [{"type": "clear_thinking_20251015"}]}, None, id="emptied-removed"),
pytest.param(
{"edits": [{"type": "clear_thinking_20251015"}], "extra": 1},
{"edits": [], "extra": 1},
id="other-keys-kept",
),
pytest.param({"edits": []}, {"edits": []}, id="empty-edits-untouched"),
pytest.param("clear_thinking_20251015", "clear_thinking_20251015", id="non-dict-untouched"),
pytest.param({"edits": "clear_thinking"}, {"edits": "clear_thinking"}, id="non-list-edits-untouched"),
pytest.param(
{"edits": ["clear_thinking_20251015", {"type": "clear_thinking_20251015"}]},
{"edits": ["clear_thinking_20251015"]},
id="non-dict-edit-kept",
),
],
)
def test_drop_clear_thinking_edits(self, context_management: object, expected: object) -> None:
from litellm.llms.anthropic.common_utils import (
drop_clear_thinking_edits,
)
params: Final[dict[str, object]] = {"context_management": context_management}
drop_clear_thinking_edits(params)
assert params == ({} if expected is None else {"context_management": expected})
def test_strip_empty_content_blocks_from_anthropic_messages(self):
"""Covers #22930. The core regression scenario: an assistant message
with an empty text block alongside ``tool_use`` loses the empty block

View file

@ -2195,6 +2195,36 @@ def test_bedrock_invoke_transform_emits_adaptive_thinking_for_opus_4_8():
assert result["output_config"]["effort"] == "low"
@pytest.mark.parametrize(
"thinking_params",
[
pytest.param({"thinking": {"type": "adaptive"}, "output_config": {"effort": "high"}}, id="adaptive-effort"),
pytest.param({"reasoning_effort": "none"}, id="reasoning-effort-none"),
],
)
def test_bedrock_invoke_keeps_thinking_for_clear_thinking_edit_when_base_drops_thinking(
thinking_params: dict[str, object],
) -> None:
from litellm.types.router import GenericLiteLLMParams
result: Final = AmazonAnthropicClaudeMessagesConfig().transform_anthropic_messages_request(
model="anthropic.claude-opus-4-5-20251101-v1:0",
messages=[{"role": "user", "content": "hi"}],
anthropic_messages_optional_request_params={
"max_tokens": 32000,
**thinking_params,
"context_management": {
"edits": [{"type": "clear_thinking_20251015", "keep": "all"}, {"type": "clear_tool_uses_20250919"}]
},
},
litellm_params=GenericLiteLLMParams(),
headers={},
)
assert result["thinking"] == {"type": "enabled", "budget_tokens": BEDROCK_MIN_THINKING_BUDGET_TOKENS}
assert result["context_management"] == {"edits": [{"type": "clear_tool_uses_20250919"}]}
def test_bedrock_invoke_transform_normalizes_system_role_message_into_system():
"""Bedrock Invoke rejects ``role: "system"`` entries in ``messages`` on some
Claude aliases; they must be moved into the top-level ``system`` field
@ -4488,7 +4518,10 @@ async def test_bedrock_messages_fallback_to_anthropic_sends_the_tool_names_bedro
"aws_region_name": "us-east-1",
},
},
{"model_name": "anthropic-claude", "litellm_params": {"model": "anthropic/claude-opus-4-8", "api_key": "test"}},
{
"model_name": "anthropic-claude",
"litellm_params": {"model": "anthropic/claude-opus-4-8", "api_key": "test"},
},
],
fallbacks=[{"bedrock-claude": ["anthropic-claude"]}],
num_retries=0,

View file

@ -19,6 +19,7 @@ import respx
import litellm
from litellm.caching.llm_caching_handler import LLMClientCache
from litellm.constants import BEDROCK_MIN_THINKING_BUDGET_TOKENS
from litellm.llms.bedrock.base_aws_llm import BaseAWSLLM
from litellm.llms.bedrock_mantle.messages.transformation import (
BedrockMantleAnthropicMessagesConfig,
@ -224,6 +225,86 @@ class TestRequestBody:
)
assert "stream" not in body
@pytest.mark.parametrize(
("model", "thinking_params", "expected_thinking", "expected_edit_types"),
[
pytest.param(
"anthropic.claude-3-5-haiku-20241022-v1:0",
{"thinking": {"type": "adaptive"}},
None,
["clear_tool_uses_20250919"],
id="no-thinking-support-drops-edit",
),
pytest.param(
"anthropic.claude-3-5-haiku-20241022-v1:0",
{},
None,
["clear_thinking_20251015", "clear_tool_uses_20250919"],
id="caller-edit-without-thinking-forwarded",
),
pytest.param(
"anthropic.claude-opus-4-5-20251101-v1:0",
{"thinking": {"type": "adaptive"}, "output_config": {"effort": "high"}},
{"type": "enabled", "budget_tokens": BEDROCK_MIN_THINKING_BUDGET_TOKENS},
["clear_thinking_20251015", "clear_tool_uses_20250919"],
id="thinking-added-back-keeps-edit",
),
pytest.param(
"anthropic.claude-opus-4-5-20251101-v1:0",
{"max_tokens": BEDROCK_MIN_THINKING_BUDGET_TOKENS, "reasoning_effort": "none"},
None,
["clear_tool_uses_20250919"],
id="max-tokens-too-small-to-add-thinking-back-drops-edit",
),
pytest.param(
"anthropic.claude-mythos-preview",
{},
None,
["clear_thinking_20251015", "clear_tool_uses_20250919"],
id="always-on-model-keeps-edit",
),
pytest.param(
"anthropic.claude-sonnet-4-6",
{"thinking": {"type": "adaptive"}},
{"type": "adaptive"},
["clear_thinking_20251015", "clear_tool_uses_20250919"],
id="adaptive-thinking-keeps-edit",
),
],
)
def test_clear_thinking_edit_dropped_only_with_dropped_thinking(
self,
model: str,
thinking_params: dict[str, object],
expected_thinking: dict[str, object] | None,
expected_edit_types: list[str],
) -> None:
body: Final = BedrockMantleAnthropicMessagesConfig().transform_anthropic_messages_request(
model=model,
messages=[{"role": "user", "content": "ping"}],
anthropic_messages_optional_request_params={
"max_tokens": 32000,
**thinking_params,
"context_management": {
"edits": [{"type": "clear_thinking_20251015", "keep": "all"}, {"type": "clear_tool_uses_20250919"}]
},
},
litellm_params=GenericLiteLLMParams(),
headers={},
)
assert body.get("thinking") == expected_thinking
assert [edit["type"] for edit in body["context_management"]["edits"]] == expected_edit_types
def test_disabled_thinking_without_edits_is_untouched(self) -> None:
body: Final = BedrockMantleAnthropicMessagesConfig().transform_anthropic_messages_request(
model="anthropic.claude-3-5-haiku-20241022-v1:0",
messages=[{"role": "user", "content": "ping"}],
anthropic_messages_optional_request_params={"max_tokens": 8, "thinking": {"type": "disabled"}},
litellm_params=GenericLiteLLMParams(),
headers={},
)
assert body["thinking"] == {"type": "disabled"}
def test_body_hoists_wrapped_defer_loading_and_drops_custom(self):
schema: Final = {"type": "object", "properties": {}}
body = BedrockMantleAnthropicMessagesConfig().transform_anthropic_messages_request(

View file

@ -60,6 +60,7 @@ class _CopilotThinkingCapabilities(BaseModel):
supports_adaptive_thinking: bool | None = None
supports_legacy_thinking: bool | None = None
thinking_always_on: bool | None = None
thinking_on_by_default: bool | None = None
def _copilot_endpoint_rows(path: Path) -> Mapping[str, _CopilotEndpointRow]:
@ -87,6 +88,7 @@ _THINKING_KEYS: Final[tuple[str, ...]] = (
"supports_adaptive_thinking",
"supports_legacy_thinking",
"thinking_always_on",
"thinking_on_by_default",
)

View file

@ -1211,6 +1211,7 @@ _COST_MAP_ROW_SCHEMA: Final = {
"supports_anthropic_thinking_payload": {"type": "boolean"},
"supports_legacy_thinking": {"type": "boolean"},
"thinking_always_on": {"type": "boolean"},
"thinking_on_by_default": {"type": "boolean"},
"supports_mid_conversation_system": {"type": "boolean"},
"supports_sampling_params": {"type": "boolean"},
"supports_output_config": {"type": "boolean"},