Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions litellm/anthropic_beta_headers_config.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"computer-use-2025-11-24": "computer-use-2025-11-24",
"context-1m-2025-08-07": "context-1m-2025-08-07",
"context-management-2025-06-27": "context-management-2025-06-27",
"dangerous-tool-use-2026-09-03": "dangerous-tool-use-2026-09-03",
"effort-2025-11-24": "effort-2025-11-24",
"fast-mode-2026-02-01": "fast-mode-2026-02-01",
"files-api-2025-04-14": "files-api-2025-04-14",
Expand Down Expand Up @@ -42,6 +43,7 @@
"computer-use-2025-11-24": "computer-use-2025-11-24",
"context-1m-2025-08-07": "context-1m-2025-08-07",
"context-management-2025-06-27": "context-management-2025-06-27",
"dangerous-tool-use-2026-09-03": null,
"effort-2025-11-24": "effort-2025-11-24",
"fast-mode-2026-02-01": null,
"files-api-2025-04-14": "files-api-2025-04-14",
Expand Down Expand Up @@ -72,6 +74,7 @@
"computer-use-2025-11-24": "computer-use-2025-11-24",
"context-1m-2025-08-07": "context-1m-2025-08-07",
"context-management-2025-06-27": null,
"dangerous-tool-use-2026-09-03": null,
"effort-2025-11-24": "effort-2025-11-24",
"fast-mode-2026-02-01": null,
"files-api-2025-04-14": null,
Expand Down Expand Up @@ -103,6 +106,7 @@
"computer-use-2025-11-24": "computer-use-2025-11-24",
"context-1m-2025-08-07": "context-1m-2025-08-07",
"context-management-2025-06-27": "context-management-2025-06-27",
"dangerous-tool-use-2026-09-03": "dangerous-tool-use-2026-09-03",
"effort-2025-11-24": "effort-2025-11-24",
"fast-mode-2026-02-01": null,
"files-api-2025-04-14": null,
Expand Down Expand Up @@ -134,6 +138,7 @@
"computer-use-2025-11-24": "computer-use-2025-11-24",
"context-1m-2025-08-07": "context-1m-2025-08-07",
"context-management-2025-06-27": "context-management-2025-06-27",
"dangerous-tool-use-2026-09-03": "dangerous-tool-use-2026-09-03",
"effort-2025-11-24": null,
"fast-mode-2026-02-01": null,
"files-api-2025-04-14": null,
Expand Down Expand Up @@ -165,6 +170,7 @@
"computer-use-2025-11-24": "computer-use-2025-11-24",
"context-1m-2025-08-07": "context-1m-2025-08-07",
"context-management-2025-06-27": "context-management-2025-06-27",
"dangerous-tool-use-2026-09-03": null,
"effort-2025-11-24": "effort-2025-11-24",
"fast-mode-2026-02-01": "fast-mode-2026-02-01",
"files-api-2025-04-14": "files-api-2025-04-14",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
from litellm.router import Router

# Anthropic-only keys already mapped by the translator; strip on extra_kwargs re-merge.
ANTHROPIC_ONLY_REQUEST_KEYS: Final[frozenset[str]] = frozenset({"output_config"})
ANTHROPIC_ONLY_REQUEST_KEYS: Final[frozenset[str]] = frozenset({"output_config", "safeguards"})

_AnthropicMessages: TypeAlias = "list[dict[str, object]]"
_AnthropicSystem: TypeAlias = "str | list[dict[str, object]] | None"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,10 +69,14 @@ def get_supported_anthropic_messages_params(self, model: str) -> list:
"speed",
"output_config",
"reasoning_effort",
"safeguards",
# TODO: Add Anthropic `metadata` support
# "metadata",
]

def should_filter_anthropic_beta_headers(self) -> bool:
return self._resolved_provider != "anthropic"

def _remove_scope_from_cache_control(self, anthropic_messages_request: dict) -> None:
"""
Remove `scope` field from cache_control blocks.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -552,6 +552,9 @@ def _get_bedrock_invoke_anthropic_beta_headers(
if injected_thinking_for_clear_thinking:
beta_set.add("interleaved-thinking-2025-05-14")

if anthropic_messages_optional_request_params.get("safeguards") is not None:
beta_set.add(ANTHROPIC_BETA_HEADER_VALUES.DANGEROUS_TOOL_USE_2026_09_03.value)

self._filter_context_management_for_bedrock_invoke(
anthropic_messages_request=anthropic_messages_request,
beta_set=beta_set,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,9 @@ def validate_anthropic_messages_environment(
if anthropic_model_info.is_tool_search_used(tools):
beta_values.add(get_tool_search_beta_header("vertex_ai"))

if optional_params.get("safeguards") is not None:
beta_values.add(ANTHROPIC_BETA_HEADER_VALUES.DANGEROUS_TOOL_USE_2026_09_03.value)

if beta_values:
headers["anthropic-beta"] = ",".join(beta_values)

Expand Down
6 changes: 5 additions & 1 deletion litellm/types/llms/anthropic.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from collections.abc import Iterable, Sequence
from collections.abc import Iterable, Mapping, Sequence
from enum import Enum
from typing import Any, Final, Literal, TypeAlias

Expand Down Expand Up @@ -410,6 +410,7 @@ class AnthropicMessagesRequestOptionalParams(TypedDict, total=False):
output_config: AnthropicOutputConfig | None # Configuration for Claude's output behavior
cache_control: dict[str, Any] | None # Automatic prompt caching
reasoning_effort: str | None
safeguards: ReadOnly[Sequence[Mapping[str, object]] | None]


class AnthropicMessagesRequest(AnthropicMessagesRequestOptionalParams, total=False):
Expand Down Expand Up @@ -522,6 +523,7 @@ class ContentBlockStartText(TypedDict):

class MessageDelta(TypedDict, total=False):
stop_reason: str | None
safeguard_results: ReadOnly[Sequence[Mapping[str, object]]]


class ServerToolUsage(TypedDict, total=False):
Expand Down Expand Up @@ -592,6 +594,7 @@ class MessageChunk(TypedDict, total=False):
stop_reason: str | None
stop_sequence: str | None
usage: UsageDelta
safeguard_results: ReadOnly[Sequence[Mapping[str, object]]]


class MessageStartBlock(TypedDict):
Expand Down Expand Up @@ -739,6 +742,7 @@ class ANTHROPIC_BETA_HEADER_VALUES(str, Enum):
ADVANCED_TOOL_USE_2025_11_20 = "advanced-tool-use-2025-11-20"
FAST_MODE_2026_02_01 = "fast-mode-2026-02-01"
ADVISOR_TOOL_2026_03_01 = "advisor-tool-2026-03-01"
DANGEROUS_TOOL_USE_2026_09_03 = "dangerous-tool-use-2026-09-03"


# Tool search beta header constant (for Anthropic direct API and Microsoft Foundry)
Expand Down
2 changes: 2 additions & 0 deletions litellm/types/llms/anthropic_messages/anthropic_response.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from collections.abc import Mapping, Sequence
from typing import Any, Literal, TypeAlias

from typing_extensions import NotRequired, ReadOnly, TypedDict
Expand Down Expand Up @@ -106,3 +107,4 @@ class AnthropicMessagesResponse(TypedDict, total=False):
type: Literal["message"] | None
usage: AnthropicUsage | None
context_management: NotRequired[ContextManagementResponse]
safeguard_results: NotRequired[ReadOnly[Sequence[Mapping[str, object]]]]
3 changes: 2 additions & 1 deletion litellm/types/llms/bedrock.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import json
from collections.abc import Sequence
from collections.abc import Mapping, Sequence
from enum import Enum
from typing import TYPE_CHECKING, Any, Final, Literal

Expand Down Expand Up @@ -1103,6 +1103,7 @@ class BedrockInvokeAnthropicMessagesRequest(TypedDict, total=False):
thinking: dict
metadata: dict
output_config: dict
safeguards: ReadOnly[Sequence[Mapping[str, object]]]

# `context_management` is allowed for Bedrock InvokeModel only when it
# carries `compact_20260112` edits paired with the `compact-2026-01-12`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,8 +101,8 @@ async def test_anthropic_messages_with_all_beta_headers(model_name, provider_nam
@pytest.mark.parametrize(
"model_name,provider_name",
[
("bedrock-claude-opus-4.5", "bedrock"),
("bedrock-converse-claude-sonnet-4.5", "bedrock_converse"),
("bedrock-claude-fable-5.1", "bedrock"),
("bedrock-converse-claude-fable-5.1", "bedrock_converse"),
],
)
async def test_bedrock_invoke_messages_with_all_beta_headers(model_name, provider_name):
Expand Down
10 changes: 10 additions & 0 deletions tests/proxy_e2e_anthropic_messages_tests/test_config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@ model_list:
model: "bedrock/us.anthropic.claude-opus-4-5-20251101-v1:0"
aws_region_name: "us-east-1"

- model_name: bedrock-claude-fable-5.1
litellm_params:
model: "bedrock/us.anthropic.claude-fable-5-1"
aws_region_name: "us-east-1"

- model_name: bedrock-nova-pro
litellm_params:
model: "bedrock/us.amazon.nova-pro-v1:0"
Expand All @@ -35,6 +40,11 @@ model_list:
litellm_params:
model: "bedrock/converse/us.anthropic.claude-sonnet-4-5-20250929-v1:0"
aws_region_name: "us-east-1"

- model_name: bedrock-converse-claude-fable-5.1
litellm_params:
model: "bedrock/converse/us.anthropic.claude-fable-5-1"
aws_region_name: "us-east-1"

# Azure AI models
- model_name: azure-ai-claude-opus-4.5
Expand Down
15 changes: 15 additions & 0 deletions tests/test_litellm/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,21 @@ def local_model_cost_map(monkeypatch):
litellm.get_model_info.cache_clear()


@pytest.fixture
def local_beta_headers_config(monkeypatch):
"""Pin the bundled ``anthropic_beta_headers_config.json`` so beta header assertions
do not depend on the network-fetched copy or on what earlier tests left cached."""
from litellm.anthropic_beta_headers_manager import reload_beta_headers_config

monkeypatch.setenv("LITELLM_LOCAL_ANTHROPIC_BETA_HEADERS", "True")
reload_beta_headers_config()
try:
yield
finally:
monkeypatch.delenv("LITELLM_LOCAL_ANTHROPIC_BETA_HEADERS", raising=False)
reload_beta_headers_config()
Comment on lines +216 to +218

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Cache reload uses wrong state

Teardown deletes the local override before reloading the global cache, triggering network access and leaving later tests with cached state that mismatches the environment



def _run_coroutine_if_needed(result):
if not asyncio.iscoroutine(result):
return
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,19 @@ def test_output_config_with_effort_is_stripped(self):
"reject it with 400 'Extra inputs are not permitted'"
)

def test_safeguards_is_stripped_for_non_anthropic_target(self):
extra_kwargs = {
"custom_llm_provider": "azure",
"safeguards": [{"type": "dangerous_tool_use", "classifier_context": {"v": 1}}],
}

result = _call_prepare(extra_kwargs=extra_kwargs)

completion_kwargs = result[0] if isinstance(result, tuple) else result
assert "safeguards" not in completion_kwargs, (
"safeguards is an Anthropic-only field; OpenAI-format backends reject it with 400"
)

def test_output_config_format_translated_to_response_format(self):
"""When ``output_config`` carries structured-output ``format``, the
translator now maps it to OpenAI's ``response_format`` so non-Anthropic
Expand Down
Loading
Loading