mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-07 08:26:10 +00:00
fix(anthropic): translate reasoning_effort on /v1/messages route
Closes the remaining QA-sweep gap on PR #27074: Bedrock Invoke /v1/messages was silently ignoring ``reasoning_effort`` because the shared param filter only kept native Anthropic keys, so every effort tier collapsed to the same behavior on the wire (27/231 cells failing across opus-4-5 / opus-4-6 / sonnet-4-6). Map ``reasoning_effort`` to native Anthropic ``thinking`` / ``output_config.effort`` at the ``AnthropicMessagesConfig`` layer so all four /v1/messages routes (direct Anthropic, Azure AI, Vertex AI, Bedrock Invoke) inherit the same translation: - Add ``reasoning_effort`` to ``AnthropicMessagesRequestOptionalParams`` so the param filter in ``AnthropicMessagesRequestUtils.get_requested_anthropic_messages_optional_param`` no longer drops it before the transformation runs. - Add ``_translate_reasoning_effort_to_anthropic`` and call it from ``transform_anthropic_messages_request``. Mirrors ``AnthropicConfig.map_openai_params`` on the chat completion path (re-uses ``_map_reasoning_effort`` and ``REASONING_EFFORT_TO_OUTPUT_CONFIG_EFFORT``) so the two routes cannot drift. Pops ``reasoning_effort`` so it never reaches the wire. - Caller-supplied native ``thinking`` / ``output_config.effort`` always win — same precedence as ``_translate_legacy_thinking_for_adaptive_model``. - Garbage values (``""``, ``"disabled"``, ``"invalid"``) raise ``AnthropicError(status_code=400)`` instead of falling through and surfacing as 500s from the provider. - ``"none"`` clears thinking + output_config so callers can opt out per request. Also restores the non-adaptive-model test coverage on Bedrock Invoke /v1/messages that the previous commit lost when ``test_bedrock_messages_strips_output_config`` was renamed to the ``forwards`` variant on Opus 4.7. Adds a new test file ``test_reasoning_effort_translation.py`` covering the translation at the shared config level (adaptive + non-adaptive models, none, garbage, caller precedence) so all four /v1/messages routes are exercised by a single suite. Adds parametrized + behavioral tests on the Bedrock Invoke /v1/messages suite covering: minimal/low/medium/high/xhigh/max mapping for adaptive models, thinking-budget mapping for non-adaptive Opus 4.5, ``none`` clears both, garbage raises 400, explicit ``output_config`` wins. Refs: https://github.com/BerriAI/litellm/pull/27074
This commit is contained in:
parent
ec022fd1bb
commit
e400de4654
5 changed files with 477 additions and 1 deletions
|
|
@ -1580,7 +1580,10 @@ class AnthropicConfig(AnthropicModelInfo, BaseConfig):
|
|||
)
|
||||
# ``max`` is for Opus 4.6+ output effort (not Sonnet 4.6, not Opus 4.5).
|
||||
# Accept known Opus 4.6/4.7 id patterns and/or ``supports_max_reasoning_effort``
|
||||
# in the model map (same pattern as ``xhigh`` below).
|
||||
# in the model map (same pattern as ``xhigh`` below). The hardcoded
|
||||
# patterns cover OpenRouter/GitHub Copilot/Vercel variants that don't
|
||||
# carry the model-map flag yet — keep both checks until those provider
|
||||
# entries are fully populated.
|
||||
if effort == "max" and not (
|
||||
self._is_opus_4_6_model(model)
|
||||
or self._is_opus_4_7_model(model)
|
||||
|
|
|
|||
|
|
@ -47,6 +47,10 @@ class AnthropicMessagesConfig(BaseAnthropicMessagesConfig):
|
|||
"inference_geo",
|
||||
"speed",
|
||||
"output_config",
|
||||
# OpenAI-style tier knob — translated to native ``thinking`` +
|
||||
# ``output_config`` in ``transform_anthropic_messages_request``
|
||||
# and popped before the request is forwarded.
|
||||
"reasoning_effort",
|
||||
# TODO: Add Anthropic `metadata` support
|
||||
# "metadata",
|
||||
]
|
||||
|
|
@ -166,6 +170,74 @@ class AnthropicMessagesConfig(BaseAnthropicMessagesConfig):
|
|||
|
||||
return headers, api_base
|
||||
|
||||
@staticmethod
|
||||
def _translate_reasoning_effort_to_anthropic(
|
||||
model: str, optional_params: Dict
|
||||
) -> None:
|
||||
"""Map OpenAI-style ``reasoning_effort`` to native Anthropic params.
|
||||
|
||||
The /v1/messages spec doesn't include ``reasoning_effort`` — without
|
||||
this translation it gets silently dropped, leaving every adaptive
|
||||
tier collapsed to the same behavior on Bedrock Invoke /v1/messages
|
||||
(and on Anthropic / Azure AI / Vertex AI when callers pass it on
|
||||
the messages route). Mirrors ``AnthropicConfig.map_openai_params``
|
||||
on the chat completion path so the two routes can't drift.
|
||||
|
||||
- Pops ``reasoning_effort`` from ``optional_params`` so it never
|
||||
reaches the wire.
|
||||
- Caller-supplied ``thinking`` / ``output_config`` always win — we
|
||||
don't override an explicit native value.
|
||||
- Effort=``none`` clears thinking + output_config so callers can
|
||||
opt out per request.
|
||||
- Invalid efforts raise ``BadRequestError`` (clean 400) instead of
|
||||
surfacing as 500s downstream.
|
||||
"""
|
||||
from litellm.llms.anthropic.chat.transformation import AnthropicConfig
|
||||
|
||||
reasoning_effort = optional_params.pop("reasoning_effort", None)
|
||||
if not isinstance(reasoning_effort, str):
|
||||
return
|
||||
|
||||
try:
|
||||
mapped_thinking = AnthropicConfig._map_reasoning_effort(
|
||||
reasoning_effort=reasoning_effort, model=model
|
||||
)
|
||||
except ValueError as e:
|
||||
raise AnthropicError(message=str(e), status_code=400)
|
||||
|
||||
if mapped_thinking is None:
|
||||
optional_params.pop("thinking", None)
|
||||
optional_params.pop("output_config", None)
|
||||
return
|
||||
|
||||
optional_params.setdefault("thinking", mapped_thinking)
|
||||
if AnthropicModelInfo._is_adaptive_thinking_model(model):
|
||||
mapped_effort = (
|
||||
AnthropicConfig.REASONING_EFFORT_TO_OUTPUT_CONFIG_EFFORT.get(
|
||||
reasoning_effort
|
||||
)
|
||||
)
|
||||
# ``_map_reasoning_effort`` returns ``type=adaptive`` for any
|
||||
# string on adaptive models without checking the value. The
|
||||
# chat completion path validates the resolved effort downstream
|
||||
# via ``_apply_output_config``; /v1/messages has no equivalent
|
||||
# downstream check, so reject unmapped values here so callers
|
||||
# see a clean 400 instead of a 500 from the provider.
|
||||
if mapped_effort is None:
|
||||
raise AnthropicError(
|
||||
message=(
|
||||
f"Invalid reasoning_effort: {reasoning_effort!r}. "
|
||||
f"Must be one of: 'minimal', 'low', 'medium', 'high', "
|
||||
f"'xhigh', 'max', 'none'"
|
||||
),
|
||||
status_code=400,
|
||||
)
|
||||
existing_output_config = optional_params.get("output_config")
|
||||
if not isinstance(existing_output_config, dict):
|
||||
existing_output_config = {}
|
||||
existing_output_config.setdefault("effort", mapped_effort)
|
||||
optional_params["output_config"] = existing_output_config
|
||||
|
||||
@staticmethod
|
||||
def _translate_legacy_thinking_for_adaptive_model(
|
||||
model: str, optional_params: Dict
|
||||
|
|
@ -217,6 +289,11 @@ class AnthropicMessagesConfig(BaseAnthropicMessagesConfig):
|
|||
status_code=400,
|
||||
)
|
||||
|
||||
self._translate_reasoning_effort_to_anthropic(
|
||||
model=model,
|
||||
optional_params=anthropic_messages_optional_request_params,
|
||||
)
|
||||
|
||||
self._translate_legacy_thinking_for_adaptive_model(
|
||||
model=model,
|
||||
optional_params=anthropic_messages_optional_request_params,
|
||||
|
|
|
|||
|
|
@ -393,6 +393,13 @@ class AnthropicMessagesRequestOptionalParams(TypedDict, total=False):
|
|||
AnthropicOutputConfig
|
||||
] # Configuration for Claude's output behavior
|
||||
cache_control: Optional[Dict[str, Any]] # Automatic prompt caching
|
||||
# OpenAI-style ``reasoning_effort`` is accepted on /v1/messages so callers
|
||||
# can drive adaptive/extended thinking with a single tier-name knob (the
|
||||
# same vocabulary as the chat completion path). The transformation layer
|
||||
# maps it to native Anthropic ``thinking`` + ``output_config`` and pops
|
||||
# this key before the request is forwarded — no provider receives
|
||||
# ``reasoning_effort`` on the wire.
|
||||
reasoning_effort: Optional[str]
|
||||
|
||||
|
||||
class AnthropicMessagesRequest(AnthropicMessagesRequestOptionalParams, total=False):
|
||||
|
|
|
|||
|
|
@ -0,0 +1,184 @@
|
|||
"""
|
||||
Tests for OpenAI-style ``reasoning_effort`` translation on the Anthropic
|
||||
/v1/messages route.
|
||||
|
||||
The /v1/messages spec doesn't include ``reasoning_effort`` — without
|
||||
translation it gets silently dropped at the filter step, leaving every
|
||||
adaptive tier collapsed to the same behavior on Bedrock Invoke /v1/messages
|
||||
(and on Anthropic / Azure AI / Vertex AI when callers pass it on the
|
||||
messages route).
|
||||
|
||||
These tests pin the translation and validation behavior at the shared
|
||||
``AnthropicMessagesConfig`` level so all four /v1/messages routes
|
||||
(direct Anthropic, Azure AI, Vertex AI, Bedrock Invoke) inherit the
|
||||
same mapping.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
|
||||
from litellm.llms.anthropic.common_utils import AnthropicError
|
||||
from litellm.llms.anthropic.experimental_pass_through.messages.transformation import (
|
||||
AnthropicMessagesConfig,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"reasoning_effort,expected_effort",
|
||||
[
|
||||
("minimal", "low"),
|
||||
("low", "low"),
|
||||
("medium", "medium"),
|
||||
("high", "high"),
|
||||
("xhigh", "xhigh"),
|
||||
("max", "max"),
|
||||
],
|
||||
)
|
||||
def test_reasoning_effort_maps_to_output_config_for_adaptive_model(
|
||||
reasoning_effort, expected_effort
|
||||
):
|
||||
"""
|
||||
For Claude 4.6 / 4.7, ``reasoning_effort`` is mapped to
|
||||
``thinking={"type": "adaptive"}`` plus ``output_config.effort=<tier>``,
|
||||
using the same mapping table as the chat completion path so the two
|
||||
routes can't drift.
|
||||
"""
|
||||
config = AnthropicMessagesConfig()
|
||||
optional_params = {"max_tokens": 1024, "reasoning_effort": reasoning_effort}
|
||||
|
||||
result = config.transform_anthropic_messages_request(
|
||||
model="claude-opus-4-7",
|
||||
messages=[{"role": "user", "content": "Hello"}],
|
||||
anthropic_messages_optional_request_params=optional_params,
|
||||
litellm_params={},
|
||||
headers={},
|
||||
)
|
||||
|
||||
assert "reasoning_effort" not in result
|
||||
assert result.get("thinking") == {"type": "adaptive"}
|
||||
assert result.get("output_config") == {"effort": expected_effort}
|
||||
|
||||
|
||||
def test_reasoning_effort_none_clears_thinking_and_output_config():
|
||||
"""``reasoning_effort='none'`` opts out of extended thinking entirely."""
|
||||
config = AnthropicMessagesConfig()
|
||||
optional_params = {
|
||||
"max_tokens": 1024,
|
||||
"reasoning_effort": "none",
|
||||
"thinking": {"type": "adaptive"},
|
||||
"output_config": {"effort": "high"},
|
||||
}
|
||||
|
||||
result = config.transform_anthropic_messages_request(
|
||||
model="claude-opus-4-7",
|
||||
messages=[{"role": "user", "content": "Hello"}],
|
||||
anthropic_messages_optional_request_params=optional_params,
|
||||
litellm_params={},
|
||||
headers={},
|
||||
)
|
||||
|
||||
assert "reasoning_effort" not in result
|
||||
assert "thinking" not in result
|
||||
assert "output_config" not in result
|
||||
|
||||
|
||||
def test_reasoning_effort_on_non_adaptive_model_uses_thinking_budget():
|
||||
"""
|
||||
Non-adaptive models (Opus 4.5 / earlier) take ``thinking.budget_tokens``
|
||||
rather than ``output_config.effort``. The translation falls back to
|
||||
the budget mapping in that case.
|
||||
"""
|
||||
config = AnthropicMessagesConfig()
|
||||
optional_params = {"max_tokens": 1024, "reasoning_effort": "high"}
|
||||
|
||||
result = config.transform_anthropic_messages_request(
|
||||
model="claude-opus-4-5",
|
||||
messages=[{"role": "user", "content": "Hello"}],
|
||||
anthropic_messages_optional_request_params=optional_params,
|
||||
litellm_params={},
|
||||
headers={},
|
||||
)
|
||||
|
||||
assert "reasoning_effort" not in result
|
||||
assert "output_config" not in result
|
||||
thinking = result.get("thinking")
|
||||
assert isinstance(thinking, dict)
|
||||
assert thinking.get("type") == "enabled"
|
||||
assert isinstance(thinking.get("budget_tokens"), int)
|
||||
assert thinking["budget_tokens"] >= 1024
|
||||
|
||||
|
||||
@pytest.mark.parametrize("bad_effort", ["invalid", "disabled", ""])
|
||||
def test_invalid_reasoning_effort_raises_400(bad_effort):
|
||||
"""
|
||||
Garbage ``reasoning_effort`` values surface as a clean 400 instead of
|
||||
silently passing through to the provider as an unknown
|
||||
``output_config.effort`` (which would 500).
|
||||
"""
|
||||
config = AnthropicMessagesConfig()
|
||||
optional_params = {"max_tokens": 1024, "reasoning_effort": bad_effort}
|
||||
|
||||
with pytest.raises(AnthropicError) as exc_info:
|
||||
config.transform_anthropic_messages_request(
|
||||
model="claude-opus-4-7",
|
||||
messages=[{"role": "user", "content": "Hello"}],
|
||||
anthropic_messages_optional_request_params=optional_params,
|
||||
litellm_params={},
|
||||
headers={},
|
||||
)
|
||||
|
||||
assert exc_info.value.status_code == 400
|
||||
|
||||
|
||||
def test_explicit_output_config_wins_over_reasoning_effort():
|
||||
"""
|
||||
Explicit native ``output_config.effort`` is never overridden by the
|
||||
OpenAI alias. Same precedence as
|
||||
``_translate_legacy_thinking_for_adaptive_model``.
|
||||
"""
|
||||
config = AnthropicMessagesConfig()
|
||||
optional_params = {
|
||||
"max_tokens": 1024,
|
||||
"reasoning_effort": "low",
|
||||
"output_config": {"effort": "max"},
|
||||
}
|
||||
|
||||
result = config.transform_anthropic_messages_request(
|
||||
model="claude-opus-4-7",
|
||||
messages=[{"role": "user", "content": "Hello"}],
|
||||
anthropic_messages_optional_request_params=optional_params,
|
||||
litellm_params={},
|
||||
headers={},
|
||||
)
|
||||
|
||||
assert "reasoning_effort" not in result
|
||||
assert result.get("output_config") == {"effort": "max"}
|
||||
|
||||
|
||||
def test_explicit_thinking_wins_over_reasoning_effort():
|
||||
"""Explicit native ``thinking`` is never overridden by the alias."""
|
||||
config = AnthropicMessagesConfig()
|
||||
optional_params = {
|
||||
"max_tokens": 1024,
|
||||
"reasoning_effort": "low",
|
||||
"thinking": {"type": "enabled", "budget_tokens": 8000},
|
||||
}
|
||||
|
||||
result = config.transform_anthropic_messages_request(
|
||||
model="claude-opus-4-5",
|
||||
messages=[{"role": "user", "content": "Hello"}],
|
||||
anthropic_messages_optional_request_params=optional_params,
|
||||
litellm_params={},
|
||||
headers={},
|
||||
)
|
||||
|
||||
assert "reasoning_effort" not in result
|
||||
assert result.get("thinking") == {"type": "enabled", "budget_tokens": 8000}
|
||||
|
||||
|
||||
def test_reasoning_effort_in_supported_params():
|
||||
"""``reasoning_effort`` is advertised as a supported messages param so
|
||||
callers and validation paths can introspect the schema."""
|
||||
config = AnthropicMessagesConfig()
|
||||
assert "reasoning_effort" in config.get_supported_anthropic_messages_params(
|
||||
"claude-opus-4-7"
|
||||
)
|
||||
|
|
@ -662,6 +662,211 @@ def test_bedrock_messages_forwards_output_config_with_output_format():
|
|||
assert "output_format" not in result
|
||||
|
||||
|
||||
def test_bedrock_messages_forwards_output_config_for_non_adaptive_model():
|
||||
"""
|
||||
``output_config`` is forwarded for non-adaptive models too (e.g. haiku).
|
||||
Bedrock will reject the unsupported key for those models — surfacing the
|
||||
provider error is the correct behavior, since silently swallowing the
|
||||
knob would hide caller bugs.
|
||||
|
||||
Restores coverage previously asserted by
|
||||
``test_bedrock_messages_strips_output_config`` (renamed to the
|
||||
``forwards`` variant on Opus 4.7); the strip path no longer exists,
|
||||
but the non-adaptive pass-through path needs its own explicit test
|
||||
so a future regression that silently re-adds the strip can't sneak
|
||||
through.
|
||||
"""
|
||||
from litellm.types.router import GenericLiteLLMParams
|
||||
|
||||
cfg = AmazonAnthropicClaudeMessagesConfig()
|
||||
messages = [{"role": "user", "content": [{"type": "text", "text": "Hello"}]}]
|
||||
optional_params = {
|
||||
"max_tokens": 4096,
|
||||
"output_config": {"effort": "high"},
|
||||
}
|
||||
|
||||
result = cfg.transform_anthropic_messages_request(
|
||||
model="anthropic.claude-3-haiku-20240307-v1:0",
|
||||
messages=messages,
|
||||
anthropic_messages_optional_request_params=optional_params,
|
||||
litellm_params=GenericLiteLLMParams(),
|
||||
headers={},
|
||||
)
|
||||
|
||||
assert result.get("output_config") == {"effort": "high"}
|
||||
assert result.get("max_tokens") == 4096
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"reasoning_effort,expected_effort",
|
||||
[
|
||||
("minimal", "low"),
|
||||
("low", "low"),
|
||||
("medium", "medium"),
|
||||
("high", "high"),
|
||||
("xhigh", "xhigh"),
|
||||
("max", "max"),
|
||||
],
|
||||
)
|
||||
def test_bedrock_messages_maps_reasoning_effort_for_adaptive_model(
|
||||
reasoning_effort, expected_effort
|
||||
):
|
||||
"""
|
||||
OpenAI-style ``reasoning_effort`` is mapped to native Anthropic
|
||||
``thinking`` + ``output_config.effort`` on the /v1/messages route so
|
||||
callers can drive adaptive thinking with the same tier vocabulary as
|
||||
the chat completion path. ``reasoning_effort`` itself is popped — the
|
||||
/v1/messages spec doesn't define it and Bedrock rejects unknown
|
||||
top-level fields.
|
||||
|
||||
Closes the QA-sweep gap on PR #27074 where Bedrock Invoke /v1/messages
|
||||
silently dropped ``reasoning_effort`` and every effort tier collapsed
|
||||
to the same behavior.
|
||||
"""
|
||||
from litellm.types.router import GenericLiteLLMParams
|
||||
|
||||
cfg = AmazonAnthropicClaudeMessagesConfig()
|
||||
messages = [{"role": "user", "content": [{"type": "text", "text": "Hello"}]}]
|
||||
optional_params = {
|
||||
"max_tokens": 4096,
|
||||
"reasoning_effort": reasoning_effort,
|
||||
}
|
||||
|
||||
result = cfg.transform_anthropic_messages_request(
|
||||
model="anthropic.claude-opus-4-7",
|
||||
messages=messages,
|
||||
anthropic_messages_optional_request_params=optional_params,
|
||||
litellm_params=GenericLiteLLMParams(),
|
||||
headers={},
|
||||
)
|
||||
|
||||
assert "reasoning_effort" not in result
|
||||
assert result.get("thinking") == {"type": "adaptive"}
|
||||
assert result.get("output_config") == {"effort": expected_effort}
|
||||
|
||||
|
||||
def test_bedrock_messages_reasoning_effort_on_non_adaptive_uses_thinking_budget():
|
||||
"""
|
||||
For non-adaptive thinking models (e.g. Opus 4.5), ``reasoning_effort``
|
||||
is mapped to ``thinking.type=enabled`` with a budget_tokens value
|
||||
instead of ``output_config.effort``. ``output_config`` is not set on
|
||||
these models because they don't accept it.
|
||||
|
||||
Mirrors ``AnthropicConfig._map_reasoning_effort`` behavior for the
|
||||
non-adaptive branch on Opus 4.5 / earlier Claude 4 models, applied to
|
||||
the /v1/messages route.
|
||||
"""
|
||||
from litellm.types.router import GenericLiteLLMParams
|
||||
|
||||
cfg = AmazonAnthropicClaudeMessagesConfig()
|
||||
messages = [{"role": "user", "content": [{"type": "text", "text": "Hello"}]}]
|
||||
optional_params = {
|
||||
"max_tokens": 4096,
|
||||
"reasoning_effort": "medium",
|
||||
}
|
||||
|
||||
result = cfg.transform_anthropic_messages_request(
|
||||
model="anthropic.claude-opus-4-5-20251101-v1:0",
|
||||
messages=messages,
|
||||
anthropic_messages_optional_request_params=optional_params,
|
||||
litellm_params=GenericLiteLLMParams(),
|
||||
headers={},
|
||||
)
|
||||
|
||||
assert "reasoning_effort" not in result
|
||||
assert "output_config" not in result
|
||||
thinking = result.get("thinking")
|
||||
assert isinstance(thinking, dict)
|
||||
assert thinking.get("type") == "enabled"
|
||||
assert isinstance(thinking.get("budget_tokens"), int)
|
||||
assert thinking["budget_tokens"] >= 1024
|
||||
|
||||
|
||||
def test_bedrock_messages_reasoning_effort_none_clears_thinking():
|
||||
"""
|
||||
``reasoning_effort='none'`` opts out — both ``thinking`` and
|
||||
``output_config`` are cleared so the request goes out without
|
||||
extended thinking. Mirrors the chat completion path's behavior.
|
||||
"""
|
||||
from litellm.types.router import GenericLiteLLMParams
|
||||
|
||||
cfg = AmazonAnthropicClaudeMessagesConfig()
|
||||
messages = [{"role": "user", "content": [{"type": "text", "text": "Hello"}]}]
|
||||
optional_params = {
|
||||
"max_tokens": 4096,
|
||||
"reasoning_effort": "none",
|
||||
"output_config": {"effort": "high"},
|
||||
"thinking": {"type": "adaptive"},
|
||||
}
|
||||
|
||||
result = cfg.transform_anthropic_messages_request(
|
||||
model="anthropic.claude-opus-4-7",
|
||||
messages=messages,
|
||||
anthropic_messages_optional_request_params=optional_params,
|
||||
litellm_params=GenericLiteLLMParams(),
|
||||
headers={},
|
||||
)
|
||||
|
||||
assert "reasoning_effort" not in result
|
||||
assert "thinking" not in result
|
||||
assert "output_config" not in result
|
||||
|
||||
|
||||
def test_bedrock_messages_invalid_reasoning_effort_raises_400():
|
||||
"""
|
||||
Garbage ``reasoning_effort`` values (``invalid`` / ``disabled`` / ``""``)
|
||||
surface as a clean 400 ``AnthropicError`` instead of silently passing
|
||||
an invalid string through to Bedrock as ``output_config.effort``.
|
||||
"""
|
||||
from litellm.llms.anthropic.common_utils import AnthropicError
|
||||
from litellm.types.router import GenericLiteLLMParams
|
||||
|
||||
cfg = AmazonAnthropicClaudeMessagesConfig()
|
||||
messages = [{"role": "user", "content": [{"type": "text", "text": "Hello"}]}]
|
||||
|
||||
for bad_effort in ("invalid", "disabled", ""):
|
||||
with pytest.raises(AnthropicError):
|
||||
cfg.transform_anthropic_messages_request(
|
||||
model="anthropic.claude-opus-4-7",
|
||||
messages=messages,
|
||||
anthropic_messages_optional_request_params={
|
||||
"max_tokens": 4096,
|
||||
"reasoning_effort": bad_effort,
|
||||
},
|
||||
litellm_params=GenericLiteLLMParams(),
|
||||
headers={},
|
||||
)
|
||||
|
||||
|
||||
def test_bedrock_messages_explicit_output_config_wins_over_reasoning_effort():
|
||||
"""
|
||||
Caller-supplied native ``output_config.effort`` wins over the OpenAI
|
||||
``reasoning_effort`` knob. Same precedence as
|
||||
``_translate_legacy_thinking_for_adaptive_model``: explicit native
|
||||
Anthropic params are never overridden by the alias.
|
||||
"""
|
||||
from litellm.types.router import GenericLiteLLMParams
|
||||
|
||||
cfg = AmazonAnthropicClaudeMessagesConfig()
|
||||
messages = [{"role": "user", "content": [{"type": "text", "text": "Hello"}]}]
|
||||
optional_params = {
|
||||
"max_tokens": 4096,
|
||||
"reasoning_effort": "low",
|
||||
"output_config": {"effort": "max"},
|
||||
}
|
||||
|
||||
result = cfg.transform_anthropic_messages_request(
|
||||
model="anthropic.claude-opus-4-7",
|
||||
messages=messages,
|
||||
anthropic_messages_optional_request_params=optional_params,
|
||||
litellm_params=GenericLiteLLMParams(),
|
||||
headers={},
|
||||
)
|
||||
|
||||
assert "reasoning_effort" not in result
|
||||
assert result.get("output_config") == {"effort": "max"}
|
||||
|
||||
|
||||
def test_bedrock_messages_strips_context_management():
|
||||
"""
|
||||
Ensure context_management is stripped from the request before sending to
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue