mirror of
https://github.com/BerriAI/litellm.git
synced 2026-11-07 07:58:09 +00:00
fix(anthropic): filter Converse body betas, keep Foundry auto betas, read the bundled beta config for row checks
This commit is contained in:
parent
39b9b8d787
commit
aedc352aaa
6 changed files with 87 additions and 7 deletions
|
|
@ -242,7 +242,10 @@ def get_provider_name(provider: str) -> str:
|
|||
|
||||
|
||||
def has_beta_headers_config(provider: str) -> bool:
|
||||
return get_provider_name(provider) in _load_beta_headers_config()
|
||||
config: Final = _bundled_beta_headers_config()
|
||||
aliases: Final = config.get("provider_aliases", {})
|
||||
canonical_provider: Final = (aliases.get(provider) if isinstance(aliases, Mapping) else None) or provider
|
||||
return canonical_provider in config
|
||||
|
||||
|
||||
def filter_and_transform_beta_headers(
|
||||
|
|
|
|||
|
|
@ -110,7 +110,8 @@ class AzureAnthropicConfig(AnthropicConfig):
|
|||
thinking=optional_params.get("thinking"), # pyright: ignore[reportUnknownMemberType, reportUnknownArgumentType] # optional_params is the base config's bare dict
|
||||
)
|
||||
# Merge headers - Azure auth (api-key or Authorization) takes precedence
|
||||
headers = {**anthropic_headers, **headers}
|
||||
merged_beta_header: Final = {k: v for k, v in anthropic_headers.items() if k == "anthropic-beta"}
|
||||
headers = {**anthropic_headers, **headers, **merged_beta_header}
|
||||
|
||||
# Ensure anthropic-version header is set
|
||||
if "anthropic-version" not in headers:
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ from typing_extensions import ReadOnly, TypedDict
|
|||
import litellm
|
||||
from litellm._logging import verbose_logger
|
||||
from litellm.anthropic_beta_gated_fields import active_beta_values, log_beta_gated_strips, strip_beta_gated_thinking
|
||||
from litellm.anthropic_beta_headers_manager import filter_and_transform_beta_headers
|
||||
from litellm.constants import (
|
||||
BEDROCK_MIN_THINKING_BUDGET_TOKENS,
|
||||
RESPONSE_FORMAT_TOOL_NAME,
|
||||
|
|
@ -1830,11 +1831,14 @@ class AmazonConverseConfig(BaseConfig):
|
|||
|
||||
base_model: Final = BedrockModelInfo.get_base_model(model)
|
||||
header_beta_list: Final = get_anthropic_beta_from_headers(headers or {})
|
||||
client_beta_list: Final = header_beta_list or sorted(
|
||||
active_beta_values(
|
||||
{},
|
||||
additional_request_params.get("anthropic_beta"), # pyright: ignore[reportUnknownMemberType, reportUnknownArgumentType] # additional_request_params is a bare dict
|
||||
)
|
||||
client_beta_list: Final = header_beta_list or filter_and_transform_beta_headers(
|
||||
sorted(
|
||||
active_beta_values(
|
||||
{},
|
||||
additional_request_params.get("anthropic_beta"), # pyright: ignore[reportUnknownMemberType, reportUnknownArgumentType] # additional_request_params is a bare dict
|
||||
)
|
||||
),
|
||||
provider="bedrock_converse",
|
||||
)
|
||||
eager_beta: Final = (
|
||||
(ANTHROPIC_FINE_GRAINED_TOOL_STREAMING_BETA_HEADER,)
|
||||
|
|
|
|||
|
|
@ -7,9 +7,11 @@ from unittest.mock import patch
|
|||
|
||||
import httpx
|
||||
import pytest
|
||||
import respx
|
||||
from pydantic import JsonValue
|
||||
|
||||
import litellm
|
||||
from litellm import anthropic_beta_headers_manager
|
||||
from litellm.litellm_core_utils.litellm_logging import Logging
|
||||
from litellm.llms.anthropic.messages.native.transformation import (
|
||||
AnthropicMessagesConfig,
|
||||
|
|
@ -521,3 +523,29 @@ def test_a_provider_without_a_beta_headers_row_forwards_beta_gated_fields_under_
|
|||
)
|
||||
assert request["thinking"] == thinking
|
||||
assert request["messages"][-1]["output_config"] == {"effort": "low"}
|
||||
|
||||
|
||||
def test_a_first_party_messages_request_never_fetches_the_remote_beta_headers_config(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
monkeypatch.delenv("LITELLM_LOCAL_ANTHROPIC_BETA_HEADERS", raising=False)
|
||||
monkeypatch.setattr(anthropic_beta_headers_manager, "_BETA_HEADERS_CONFIG", None)
|
||||
|
||||
with respx.mock(assert_all_called=False) as router:
|
||||
remote_config: Final = router.get(litellm.anthropic_beta_headers_url).mock(
|
||||
return_value=httpx.Response(200, json={})
|
||||
)
|
||||
request: Final = AnthropicMessagesConfig().transform_anthropic_messages_request(
|
||||
model="claude-opus-4-8",
|
||||
messages=[
|
||||
{"role": "user", "content": "hi"},
|
||||
{"role": "system", "content": "be terse", "clear_at": "end_turn"},
|
||||
{"role": "user", "content": "go"},
|
||||
],
|
||||
anthropic_messages_optional_request_params={"max_tokens": 64},
|
||||
litellm_params=GenericLiteLLMParams(drop_params=True),
|
||||
headers={},
|
||||
)
|
||||
|
||||
assert request["messages"][1] == {"role": "system", "content": "be terse"}
|
||||
assert not remote_config.called
|
||||
|
|
|
|||
|
|
@ -310,3 +310,34 @@ def test_completion_adds_the_binding_beta_so_block_binding_is_kept(
|
|||
sent: Final = json.loads(route.calls.last.request.content)
|
||||
assert sent["thinking"] == {"type": "enabled", "budget_tokens": 1024, "block_binding": block_binding}
|
||||
assert "thinking-binding-controls-2026-08-01" in route.calls.last.request.headers["anthropic-beta"].split(",")
|
||||
|
||||
|
||||
@respx.mock
|
||||
def test_completion_sends_the_binding_beta_next_to_the_callers_own_betas(local_beta_headers_config: None) -> None:
|
||||
route: Final = respx.post("https://example-resource.services.ai.azure.com/anthropic/v1/messages").mock(
|
||||
return_value=Response(
|
||||
200,
|
||||
json={
|
||||
"id": "msg_bound",
|
||||
"type": "message",
|
||||
"role": "assistant",
|
||||
"model": "claude-sonnet-4-6",
|
||||
"content": [{"type": "text", "text": "Hello"}],
|
||||
"stop_reason": "end_turn",
|
||||
"stop_sequence": None,
|
||||
"usage": {"input_tokens": 1, "output_tokens": 1},
|
||||
},
|
||||
)
|
||||
)
|
||||
|
||||
litellm.completion(
|
||||
model="azure_ai/claude-sonnet-4-6",
|
||||
messages=[{"role": "user", "content": "hi"}],
|
||||
api_base="https://example-resource.services.ai.azure.com/anthropic",
|
||||
api_key="azure-test-key",
|
||||
thinking={"type": "enabled", "budget_tokens": 1024, "block_binding": {"prefix_mismatch_behavior": "error"}},
|
||||
extra_headers={"anthropic-beta": "context-1m-2025-08-07"},
|
||||
)
|
||||
|
||||
sent_betas: Final = set(route.calls.last.request.headers["anthropic-beta"].split(","))
|
||||
assert {"context-1m-2025-08-07", "thinking-binding-controls-2026-08-01"} <= sent_betas
|
||||
|
|
|
|||
|
|
@ -10884,6 +10884,19 @@ def test_converse_keeps_beta_gated_thinking_fields_the_body_anthropic_beta_carri
|
|||
}
|
||||
|
||||
|
||||
def test_converse_drops_body_betas_the_bedrock_converse_row_rejects_when_adding_thinking_betas(
|
||||
local_beta_headers_config: None,
|
||||
) -> None:
|
||||
fields: Final = _converse_bound_thinking_request(
|
||||
None, body_betas=("per-turn-control-2026-07-01", "context-1m-2025-08-07")
|
||||
)
|
||||
assert sorted(fields["anthropic_beta"]) == [
|
||||
"context-1m-2025-08-07",
|
||||
"thinking-binding-controls-2026-08-01",
|
||||
"thinking-display-updates-2026-08-18",
|
||||
]
|
||||
|
||||
|
||||
def test_converse_sends_only_the_header_betas_when_header_and_body_both_carry_betas(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue