diff --git a/litellm/llms/anthropic/common_utils.py b/litellm/llms/anthropic/common_utils.py
index 36372e7b4e5..3770dc66f0e 100644
--- a/litellm/llms/anthropic/common_utils.py
+++ b/litellm/llms/anthropic/common_utils.py
@@ -20,7 +20,11 @@ from litellm.litellm_core_utils.prompt_templates.common_utils import (
from litellm.litellm_core_utils.prompt_templates.factory import (
THOUGHT_SIGNATURE_SEPARATOR,
)
-from litellm.llms.anthropic.wif import aget_anthropic_wif_token, get_anthropic_wif_token
+from litellm.llms.anthropic.wif import (
+ aget_anthropic_wif_token,
+ anthropic_base_without_chat_suffix,
+ get_anthropic_wif_token,
+)
from litellm.llms.base_llm.base_utils import BaseLLMModelInfo, BaseTokenCounter
from litellm.llms.base_llm.chat.transformation import BaseLLMException
from litellm.proxy._types import SpecialHeaders
@@ -62,6 +66,16 @@ _SERVER_OWNED_AUTH_HEADERS: Final = SpecialHeaders.litellm_credential_header_nam
_WIF_ELIGIBILITY_ATTR: Final = "_workload_identity_eligible"
+def without_caller_credential_headers(headers: Mapping[str, str]) -> dict[str, str]:
+ """``headers`` minus every header that authenticates the caller to litellm.
+
+ The deployment's own credential is applied on top of the result, so a caller-supplied
+ credential must not survive into the upstream request: without this a minted federation
+ Bearer travels beside the caller's own ``x-api-key``, and Anthropic sees two credentials.
+ """
+ return {name: value for name, value in headers.items() if name.lower() not in _SERVER_OWNED_AUTH_HEADERS}
+
+
def config_allows_workload_identity(config: object) -> bool:
"""A federation token is an Anthropic-org credential and its exchange POSTs the workload's OIDC
assertion to the deployment's own host, so eligibility is declared per class and read from that
@@ -887,11 +901,7 @@ class AnthropicModelInfo(BaseLLMModelInfo):
wif_minted=wif_minted,
)
- caller_headers: Final = (
- {name: value for name, value in headers.items() if name.lower() not in _SERVER_OWNED_AUTH_HEADERS}
- if wif_minted
- else headers
- )
+ caller_headers: Final = without_caller_credential_headers(headers) if wif_minted else headers
return {**caller_headers, **anthropic_headers}
@@ -1032,8 +1042,13 @@ class AnthropicModelInfo(BaseLLMModelInfo):
"Anthropic's `/models` endpoint."
)
headers: Final = MappingProxyType({"anthropic-version": "2023-06-01", **auth_header})
+ # /v1/models is appended below, so a base the operator already wrote as .../v1 or
+ # .../v1/messages would otherwise be asked for /v1/v1/models.
model_ids: Final = _fetch_anthropic_model_ids(
- resolved_api_base, headers, after_id=None, pages_left=_MODEL_LIST_PAGE_CAP
+ anthropic_base_without_chat_suffix(resolved_api_base),
+ headers,
+ after_id=None,
+ pages_left=_MODEL_LIST_PAGE_CAP,
)
return [ # mutable-ok: matches get_models' list[str] contract shared by every provider override
"anthropic/" + model_id for model_id in model_ids
diff --git a/litellm/llms/anthropic/files/transformation.py b/litellm/llms/anthropic/files/transformation.py
index f8ebccb7215..f8ce572bfc8 100644
--- a/litellm/llms/anthropic/files/transformation.py
+++ b/litellm/llms/anthropic/files/transformation.py
@@ -36,7 +36,12 @@ from litellm.types.llms.openai import (
)
from litellm.types.utils import LlmProviders
-from ..common_utils import AnthropicError, AnthropicModelInfo, merge_anthropic_beta_headers
+from ..common_utils import (
+ AnthropicError,
+ AnthropicModelInfo,
+ merge_anthropic_beta_headers,
+ without_caller_credential_headers,
+)
ANTHROPIC_FILES_API_BASE: Final = "https://api.anthropic.com"
ANTHROPIC_FILES_BETA_HEADER: Final = "files-api-2025-04-14"
@@ -138,14 +143,12 @@ class AnthropicFilesConfig(BaseFilesConfig):
merge_anthropic_beta_headers(headers.get("anthropic-beta"), auth_header.get("anthropic-beta")),
ANTHROPIC_FILES_BETA_HEADER,
)
- headers.update(
- {
- **auth_header,
- "anthropic-version": "2023-06-01",
- "anthropic-beta": merged_beta,
- }
- )
- return headers
+ return {
+ **without_caller_credential_headers(headers),
+ **auth_header,
+ "anthropic-version": "2023-06-01",
+ "anthropic-beta": merged_beta,
+ }
def get_supported_openai_params(self, model: str) -> list[OpenAICreateFileRequestOptionalParams]:
return ["purpose"]
diff --git a/litellm/llms/anthropic/wif.py b/litellm/llms/anthropic/wif.py
index 0342926a598..6b88bfd7f63 100644
--- a/litellm/llms/anthropic/wif.py
+++ b/litellm/llms/anthropic/wif.py
@@ -271,7 +271,7 @@ def _token_exchange_base(api_base: str | None) -> str:
"""Exchange base for any caller-supplied form of the deployment base: trailing
slashes and chat-appended ``/v1/messages`` suffixes stripped, so every tier
derives the same token URL (and cache key) for the same deployment."""
- return _strip_chat_suffix(api_base if api_base is not None else _resolve_default_api_base())
+ return anthropic_base_without_chat_suffix(api_base if api_base is not None else _resolve_default_api_base())
def _trusted_exchange_hosts() -> frozenset[str]:
@@ -315,7 +315,9 @@ def _resolve_default_api_base() -> str:
return AnthropicModelInfo.get_api_base(None) or _DEFAULT_API_BASE
-def _strip_chat_suffix(base: str) -> str:
+def anthropic_base_without_chat_suffix(base: str) -> str:
+ """A deployment base with its chat-surface suffix removed, so the token URL and model
+ discovery both derive from the same value whatever form the operator configured."""
parts: Final = urlsplit(base)
if not parts.scheme or not parts.netloc:
return base.rstrip("/")
diff --git a/tests/test_litellm/llms/anthropic/batches/test_handler.py b/tests/test_litellm/llms/anthropic/batches/test_handler.py
index 398e0aeea0f..398c94a6798 100644
--- a/tests/test_litellm/llms/anthropic/batches/test_handler.py
+++ b/tests/test_litellm/llms/anthropic/batches/test_handler.py
@@ -37,9 +37,7 @@ def _ok_batch_response():
"ended_at": "2024-09-24T11:00:00Z",
"request_counts": {"succeeded": 2, "errored": 0},
},
- request=httpx.Request(
- "GET", "https://api.anthropic.com/v1/messages/batches/msgbatch_abc"
- ),
+ request=httpx.Request("GET", "https://api.anthropic.com/v1/messages/batches/msgbatch_abc"),
)
@@ -61,9 +59,7 @@ def patched_client():
@pytest.mark.asyncio
-async def test_aretrieve_batch_fires_get_with_correct_url_and_headers(
- handler, patched_client
-):
+async def test_aretrieve_batch_fires_get_with_correct_url_and_headers(handler, patched_client):
fake_client, factory = patched_client
batch = await handler.aretrieve_batch(
@@ -78,9 +74,7 @@ async def test_aretrieve_batch_fires_get_with_correct_url_and_headers(
fake_client.get.assert_awaited_once()
_, call_kwargs = fake_client.get.call_args
# Exact URL built by get_retrieve_batch_url.
- assert call_kwargs["url"] == (
- "https://api.anthropic.com/v1/messages/batches/msgbatch_abc"
- )
+ assert call_kwargs["url"] == ("https://api.anthropic.com/v1/messages/batches/msgbatch_abc")
# Auth + version + beta headers built by validate_environment.
headers = call_kwargs["headers"]
assert headers["x-api-key"] == "sk-ant-test"
@@ -95,9 +89,7 @@ async def test_aretrieve_batch_fires_get_with_correct_url_and_headers(
@pytest.mark.asyncio
-async def test_aretrieve_batch_uses_anthropic_provider_for_client(
- handler, patched_client
-):
+async def test_aretrieve_batch_uses_anthropic_provider_for_client(handler, patched_client):
from litellm.types.utils import LlmProviders
_, factory = patched_client
@@ -113,14 +105,10 @@ async def test_aretrieve_batch_uses_anthropic_provider_for_client(
@pytest.mark.asyncio
-async def test_aretrieve_batch_resolves_api_key_from_model_info(
- handler, patched_client
-):
+async def test_aretrieve_batch_resolves_api_key_from_model_info(handler, patched_client):
fake_client, _ = patched_client
# api_key=None -> handler falls back to AnthropicModelInfo.get_api_key().
- with patch.object(
- handler.anthropic_model_info, "get_api_key", return_value="sk-from-env"
- ):
+ with patch.object(handler.anthropic_model_info, "get_api_key", return_value="sk-from-env"):
await handler.aretrieve_batch(
batch_id="msgbatch_abc",
api_base="https://api.anthropic.com",
@@ -136,9 +124,7 @@ async def test_aretrieve_batch_resolves_api_key_from_model_info(
async def test_aretrieve_batch_missing_api_key_raises(handler, patched_client):
fake_client, _ = patched_client
# No api_key and resolver yields None -> hard error before any network call.
- with patch.object(
- handler.anthropic_model_info, "get_api_key", return_value=None
- ):
+ with patch.object(handler.anthropic_model_info, "get_api_key", return_value=None):
with pytest.raises(ValueError, match="Missing Anthropic API Key"):
await handler.aretrieve_batch(
batch_id="msgbatch_abc",
@@ -167,9 +153,7 @@ async def test_aretrieve_batch_resolves_default_api_base(handler, patched_client
max_retries=0,
)
_, call_kwargs = fake_client.get.call_args
- assert call_kwargs["url"] == (
- "https://api.anthropic.com/v1/messages/batches/msgbatch_abc"
- )
+ assert call_kwargs["url"] == ("https://api.anthropic.com/v1/messages/batches/msgbatch_abc")
@pytest.mark.asyncio
@@ -178,9 +162,7 @@ async def test_aretrieve_batch_raises_for_status(handler):
error_response = httpx.Response(
status_code=404,
json={"error": "not found"},
- request=httpx.Request(
- "GET", "https://api.anthropic.com/v1/messages/batches/missing"
- ),
+ request=httpx.Request("GET", "https://api.anthropic.com/v1/messages/batches/missing"),
)
fake_client = MagicMock()
fake_client.get = AsyncMock(return_value=error_response)
@@ -215,21 +197,15 @@ async def test_aretrieve_batch_invokes_pre_call_logging(handler, patched_client)
assert pre_kwargs["input"] == "msgbatch_abc"
assert pre_kwargs["api_key"] == "sk-ant-test"
# The logged api_base is the full retrieve URL, not the bare base.
- assert pre_kwargs["additional_args"]["api_base"] == (
- "https://api.anthropic.com/v1/messages/batches/msgbatch_abc"
- )
+ assert pre_kwargs["additional_args"]["api_base"] == ("https://api.anthropic.com/v1/messages/batches/msgbatch_abc")
@pytest.mark.asyncio
-async def test_aretrieve_batch_builds_default_logging_obj_when_absent(
- handler, patched_client
-):
+async def test_aretrieve_batch_builds_default_logging_obj_when_absent(handler, patched_client):
# logging_obj=None -> handler constructs a real Logging object; the call
# must still complete (no AttributeError on a missing logger).
_, _ = patched_client
- with patch(
- "litellm.litellm_core_utils.litellm_logging.Logging"
- ) as logging_cls:
+ with patch("litellm.litellm_core_utils.litellm_logging.Logging") as logging_cls:
logging_cls.return_value = MagicMock()
batch = await handler.aretrieve_batch(
batch_id="msgbatch_abc",
@@ -318,9 +294,7 @@ class _BlockingPoster:
@pytest.mark.asyncio
-async def test_aretrieve_batch_wif_exchange_does_not_block_event_loop(
- handler, patched_client, monkeypatch
-):
+async def test_aretrieve_batch_wif_exchange_does_not_block_event_loop(handler, patched_client, monkeypatch):
"""Regression: aretrieve_batch called the synchronous validate_environment
directly, so a cold WIF mint ran inline on the event loop and froze every
other concurrent coroutine until the exchange finished."""
@@ -345,9 +319,7 @@ async def test_aretrieve_batch_wif_exchange_does_not_block_event_loop(
def routed_through_injected_engine(litellm_params, api_base, model):
return get_anthropic_wif_token(litellm_params, api_base, model, engine)
- monkeypatch.setattr(
- anthropic_common_utils, "get_anthropic_wif_token", routed_through_injected_engine
- )
+ monkeypatch.setattr(anthropic_common_utils, "get_anthropic_wif_token", routed_through_injected_engine)
ticks = []
diff --git a/tests/test_litellm/llms/anthropic/batches/test_transformation.py b/tests/test_litellm/llms/anthropic/batches/test_transformation.py
index 52b620edd3a..dbdb77db093 100644
--- a/tests/test_litellm/llms/anthropic/batches/test_transformation.py
+++ b/tests/test_litellm/llms/anthropic/batches/test_transformation.py
@@ -103,9 +103,7 @@ def test_validate_environment_oauth_key_uses_bearer(config):
def test_validate_environment_missing_key_raises(config):
# No api_key passed and no env credentials -> get_auth_header returns None.
- with patch.object(
- config.anthropic_model_info, "get_auth_header", return_value=None
- ):
+ with patch.object(config.anthropic_model_info, "get_auth_header", return_value=None):
with pytest.raises(ValueError, match="Missing Anthropic API Key"):
config.validate_environment(
headers={},
@@ -244,12 +242,7 @@ def test_get_retrieve_batch_url_uses_default_api_base(config):
def test_transform_retrieve_batch_request_returns_empty_dict(config):
- assert (
- config.transform_retrieve_batch_request(
- batch_id="msgbatch_123", optional_params={}, litellm_params={}
- )
- == {}
- )
+ assert config.transform_retrieve_batch_request(batch_id="msgbatch_123", optional_params={}, litellm_params={}) == {}
# =========================================================================== #
@@ -458,9 +451,7 @@ def test_transform_retrieve_response_unparseable_json_raises(config):
def test_get_error_class_with_dict_headers(config):
- err = config.get_error_class(
- error_message="rate limited", status_code=429, headers={"x-ratelimit": "0"}
- )
+ err = config.get_error_class(error_message="rate limited", status_code=429, headers={"x-ratelimit": "0"})
from litellm.llms.anthropic.common_utils import AnthropicError
assert isinstance(err, AnthropicError)
@@ -470,9 +461,7 @@ def test_get_error_class_with_dict_headers(config):
def test_get_error_class_with_httpx_headers(config):
hdrs = httpx.Headers({"retry-after": "5"})
- err = config.get_error_class(
- error_message="server error", status_code=500, headers=hdrs
- )
+ err = config.get_error_class(error_message="server error", status_code=500, headers=hdrs)
assert err.status_code == 500
assert err.message == "server error"
@@ -546,9 +535,7 @@ def test_transform_response_skips_malformed_lines(config):
def fake_transform_parsed(*, completion_response, raw_response, model_response):
mr = ModelResponse()
- setattr(
- mr, "usage", Usage(prompt_tokens=7, completion_tokens=3, total_tokens=10)
- )
+ setattr(mr, "usage", Usage(prompt_tokens=7, completion_tokens=3, total_tokens=10))
return mr
with patch.object(
@@ -591,13 +578,16 @@ def test_transform_response_reraises_unexpected_error(config):
# A non-JSONDecodeError raised during usage aggregation must propagate
# (the outer `except Exception: raise e`), not be swallowed.
- with patch.object(
- config.anthropic_chat_config,
- "transform_parsed_response",
- side_effect=fake_transform_parsed,
- ), patch(
- "litellm.cost_calculator.BaseTokenUsageProcessor.combine_usage_objects",
- side_effect=RuntimeError("boom"),
+ with (
+ patch.object(
+ config.anthropic_chat_config,
+ "transform_parsed_response",
+ side_effect=fake_transform_parsed,
+ ),
+ patch(
+ "litellm.cost_calculator.BaseTokenUsageProcessor.combine_usage_objects",
+ side_effect=RuntimeError("boom"),
+ ),
):
with pytest.raises(RuntimeError, match="boom"):
config.transform_response(
diff --git a/tests/test_litellm/llms/anthropic/chat/guardrail_translation/test_anthropic_guardrail_handler.py b/tests/test_litellm/llms/anthropic/chat/guardrail_translation/test_anthropic_guardrail_handler.py
index 2b392456763..7c508c5b00d 100644
--- a/tests/test_litellm/llms/anthropic/chat/guardrail_translation/test_anthropic_guardrail_handler.py
+++ b/tests/test_litellm/llms/anthropic/chat/guardrail_translation/test_anthropic_guardrail_handler.py
@@ -47,9 +47,7 @@ class MockDynamicGuardrail(CustomGuardrail):
input_type: Literal["request", "response"],
logging_obj: Optional[Any] = None,
) -> GenericGuardrailAPIInputs:
- self.dynamic_params = self.get_guardrail_dynamic_request_body_params(
- request_data
- )
+ self.dynamic_params = self.get_guardrail_dynamic_request_body_params(request_data)
return inputs
@@ -196,9 +194,7 @@ class TestAnthropicMessagesHandlerStreamingRequestData:
assert guardrail.request_data is not None
assert guardrail.request_data["response"] is mock_response
- assert (
- guardrail.request_data["litellm_metadata"]["user_api_key_user_id"] == "u-1"
- )
+ assert guardrail.request_data["litellm_metadata"]["user_api_key_user_id"] == "u-1"
@pytest.mark.asyncio
async def test_mid_stream_chunk_passes_responses_so_far_and_metadata(self):
@@ -210,9 +206,7 @@ class TestAnthropicMessagesHandlerStreamingRequestData:
with (
patch.object(handler, "_check_streaming_has_ended", return_value=False),
- patch.object(
- handler, "get_streaming_string_so_far", return_value="partial text"
- ),
+ patch.object(handler, "get_streaming_string_so_far", return_value="partial text"),
):
await handler.process_output_streaming_response(
responses_so_far=responses_so_far,
@@ -224,9 +218,7 @@ class TestAnthropicMessagesHandlerStreamingRequestData:
assert guardrail.request_data is not None
assert guardrail.request_data["responses"] is responses_so_far
- assert (
- guardrail.request_data["litellm_metadata"]["user_api_key_user_id"] == "u-1"
- )
+ assert guardrail.request_data["litellm_metadata"]["user_api_key_user_id"] == "u-1"
class TestAnthropicMessagesHandlerStreamingOutputProcessing:
@@ -275,17 +267,11 @@ class TestAnthropicMessagesHandlerInputProcessing:
data = {
"model": "claude-3-5-sonnet-20241022",
"messages": [{"role": "user", "content": "hello"}],
- "litellm_metadata": {
- "guardrails": [
- {"cygnal-monitor": {"extra_body": {"policy_id": "policy-123"}}}
- ]
- },
+ "litellm_metadata": {"guardrails": [{"cygnal-monitor": {"extra_body": {"policy_id": "policy-123"}}}]},
}
with patch("litellm.proxy.proxy_server.premium_user", True):
- await handler.process_input_messages(
- data=data, guardrail_to_apply=guardrail
- )
+ await handler.process_input_messages(data=data, guardrail_to_apply=guardrail)
assert data.get("litellm_metadata", {}).get("guardrails")
assert guardrail.dynamic_params == {"policy_id": "policy-123"}
@@ -1164,9 +1150,7 @@ class TestAnthropicMessagesHandlerInputProcessing:
# Mock _check_streaming_has_ended to return False (stream not ended)
with (
patch.object(handler, "_check_streaming_has_ended", return_value=False),
- patch.object(
- handler, "get_streaming_string_so_far", return_value="partial text"
- ),
+ patch.object(handler, "get_streaming_string_so_far", return_value="partial text"),
):
responses_so_far = [b"data: some chunk"]
@@ -1197,9 +1181,7 @@ class TestAnthropicMessagesHandlerInputProcessing:
data = {
"model": "claude-opus-4-6",
- "messages": [
- {"role": "user", "content": "What is the weather in San Francisco?"}
- ],
+ "messages": [{"role": "user", "content": "What is the weather in San Francisco?"}],
"tools": [
{
"type": "tool_search_tool_regex_20251119",
@@ -1362,17 +1344,11 @@ class TestAnthropicMessagesIncrementalScan:
]
with patch.object(guardrail, "make_bedrock_api_request", new_callable=AsyncMock) as mock_api:
mock_api.return_value = {"action": "NONE", "output": [], "outputs": []}
- await handler.process_input_messages(
- data=self._data(turn1, sid), guardrail_to_apply=guardrail
- )
+ await handler.process_input_messages(data=self._data(turn1, sid), guardrail_to_apply=guardrail)
assert mock_api.call_count == 1
- assert [m["content"] for m in mock_api.call_args.kwargs["messages"]] == [
- "What is the capital of France?"
- ]
+ assert [m["content"] for m in mock_api.call_args.kwargs["messages"]] == ["What is the capital of France?"]
mock_api.reset_mock()
- await handler.process_input_messages(
- data=self._data(turn2, sid), guardrail_to_apply=guardrail
- )
+ await handler.process_input_messages(data=self._data(turn2, sid), guardrail_to_apply=guardrail)
assert mock_api.call_count == 1
assert [m["content"] for m in mock_api.call_args.kwargs["messages"]] == [
"Paris.",
@@ -1725,9 +1701,7 @@ class TestAnthropicMessagesScanOnlyToolResults:
await handler.process_input_messages(data=data, guardrail_to_apply=guardrail)
- assert guardrail.seen_texts == ["fetched POISON page"], (
- "only the tool_result payload may reach the guardrail"
- )
+ assert guardrail.seen_texts == ["fetched POISON page"], "only the tool_result payload may reach the guardrail"
assert guardrail.captured_inputs is not None
assert guardrail.captured_inputs.get("tools") is None
assert [m["role"] for m in guardrail.captured_inputs["structured_messages"]] == ["tool"]
diff --git a/tests/test_litellm/llms/anthropic/chat/test_anthropic_chat_handler.py b/tests/test_litellm/llms/anthropic/chat/test_anthropic_chat_handler.py
index f6cd6ac6734..5506d8e4979 100644
--- a/tests/test_litellm/llms/anthropic/chat/test_anthropic_chat_handler.py
+++ b/tests/test_litellm/llms/anthropic/chat/test_anthropic_chat_handler.py
@@ -21,9 +21,7 @@ async def test_make_call_passes_logging_obj_to_client_post():
mock_client = AsyncMock()
mock_response = MagicMock()
mock_response.aiter_lines = MagicMock(
- return_value=iter(
- [b'data: {"type":"message_start"}\n', b'data: {"type":"message_delta"}\n']
- )
+ return_value=iter([b'data: {"type":"message_start"}\n', b'data: {"type":"message_delta"}\n'])
)
mock_client.post.return_value = mock_response
@@ -55,9 +53,7 @@ def test_redacted_thinking_content_block_delta():
"data": "EuoBCoYBGAIiQJ/SxkPAgqxhKok29YrpJHRUJ0OT8ahCHKAwyhmRuUhtdmDX9+mn4gDzKNv3fVpQdB01zEPMzNY3QuTCd+1bdtEqQK6JuKHqdndbwpr81oVWb4wxd1GqF/7Jkw74IlQa27oobX+KuRkopr9Dllt/RDe7Se0sI1IkU7tJIAQCoP46OAwSDF51P09q67xhHlQ3ihoM2aOVlkghq/X0w8NlIjBMNvXYNbjhyrOcIg6kPFn2ed/KK7Cm5prYAtXCwkb4Wr5tUSoSHu9T5hKdJRbr6WsqEc7Lle7FULqMLZGkhqXyc3BA",
},
}
- model_response_iterator = ModelResponseIterator(
- streaming_response=MagicMock(), sync_stream=False, json_mode=False
- )
+ model_response_iterator = ModelResponseIterator(streaming_response=MagicMock(), sync_stream=False, json_mode=False)
model_response = model_response_iterator.chunk_parser(chunk=chunk)
print(f"\n\nmodel_response: {model_response}\n\n")
assert model_response.choices[0].delta.thinking_blocks is not None
@@ -65,19 +61,14 @@ def test_redacted_thinking_content_block_delta():
print(
f"\n\nmodel_response.choices[0].delta.thinking_blocks[0]: {model_response.choices[0].delta.thinking_blocks[0]}\n\n"
)
- assert (
- model_response.choices[0].delta.thinking_blocks[0]["type"]
- == "redacted_thinking"
- )
+ assert model_response.choices[0].delta.thinking_blocks[0]["type"] == "redacted_thinking"
assert model_response.choices[0].delta.provider_specific_fields is not None
assert "thinking_blocks" in model_response.choices[0].delta.provider_specific_fields
def test_streaming_thinking_blocks_are_replayable_after_signature_delta():
- model_response_iterator = ModelResponseIterator(
- streaming_response=MagicMock(), sync_stream=True, json_mode=False
- )
+ model_response_iterator = ModelResponseIterator(streaming_response=MagicMock(), sync_stream=True, json_mode=False)
chunks = [
{
"type": "content_block_start",
@@ -101,17 +92,12 @@ def test_streaming_thinking_blocks_are_replayable_after_signature_delta():
},
]
- parsed_chunks = [
- model_response_iterator.chunk_parser(chunk=chunk) for chunk in chunks
- ]
+ parsed_chunks = [model_response_iterator.chunk_parser(chunk=chunk) for chunk in chunks]
reasoning_content = "".join(
- getattr(chunk.choices[0].delta, "reasoning_content", None) or ""
- for chunk in parsed_chunks
+ getattr(chunk.choices[0].delta, "reasoning_content", None) or "" for chunk in parsed_chunks
)
thinking_blocks = tuple(
- block
- for chunk in parsed_chunks
- for block in (getattr(chunk.choices[0].delta, "thinking_blocks", None) or [])
+ block for chunk in parsed_chunks for block in (getattr(chunk.choices[0].delta, "thinking_blocks", None) or [])
)
expected_delta_blocks = (
{"type": "thinking", "thinking": "Step 1. "},
@@ -125,18 +111,12 @@ def test_streaming_thinking_blocks_are_replayable_after_signature_delta():
assert reasoning_content == "Step 1. Step 2."
assert thinking_blocks == (*expected_delta_blocks, expected_thinking_block)
- assert parsed_chunks[1].choices[0].delta.provider_specific_fields == {
- "thinking_blocks": [expected_delta_blocks[0]]
- }
- assert parsed_chunks[-1].choices[0].delta.provider_specific_fields == {
- "thinking_blocks": [expected_thinking_block]
- }
+ assert parsed_chunks[1].choices[0].delta.provider_specific_fields == {"thinking_blocks": [expected_delta_blocks[0]]}
+ assert parsed_chunks[-1].choices[0].delta.provider_specific_fields == {"thinking_blocks": [expected_thinking_block]}
def test_streaming_unsigned_thinking_deltas_keep_reasoning_content():
- model_response_iterator = ModelResponseIterator(
- streaming_response=MagicMock(), sync_stream=True, json_mode=False
- )
+ model_response_iterator = ModelResponseIterator(streaming_response=MagicMock(), sync_stream=True, json_mode=False)
chunks = [
{
"type": "content_block_start",
@@ -156,17 +136,12 @@ def test_streaming_unsigned_thinking_deltas_keep_reasoning_content():
{"type": "content_block_stop", "index": 0},
]
- parsed_chunks = [
- model_response_iterator.chunk_parser(chunk=chunk) for chunk in chunks
- ]
+ parsed_chunks = [model_response_iterator.chunk_parser(chunk=chunk) for chunk in chunks]
reasoning_content = "".join(
- getattr(chunk.choices[0].delta, "reasoning_content", None) or ""
- for chunk in parsed_chunks
+ getattr(chunk.choices[0].delta, "reasoning_content", None) or "" for chunk in parsed_chunks
)
thinking_blocks = tuple(
- block
- for chunk in parsed_chunks
- for block in (getattr(chunk.choices[0].delta, "thinking_blocks", None) or [])
+ block for chunk in parsed_chunks for block in (getattr(chunk.choices[0].delta, "thinking_blocks", None) or [])
)
assert reasoning_content == "Step 1. Step 2."
@@ -177,9 +152,7 @@ def test_streaming_unsigned_thinking_deltas_keep_reasoning_content():
def test_streaming_truncated_thinking_deltas_keep_reasoning_content():
- model_response_iterator = ModelResponseIterator(
- streaming_response=MagicMock(), sync_stream=True, json_mode=False
- )
+ model_response_iterator = ModelResponseIterator(streaming_response=MagicMock(), sync_stream=True, json_mode=False)
chunks = [
{
"type": "content_block_start",
@@ -198,17 +171,12 @@ def test_streaming_truncated_thinking_deltas_keep_reasoning_content():
},
]
- parsed_chunks = [
- model_response_iterator.chunk_parser(chunk=chunk) for chunk in chunks
- ]
+ parsed_chunks = [model_response_iterator.chunk_parser(chunk=chunk) for chunk in chunks]
reasoning_content = "".join(
- getattr(chunk.choices[0].delta, "reasoning_content", None) or ""
- for chunk in parsed_chunks
+ getattr(chunk.choices[0].delta, "reasoning_content", None) or "" for chunk in parsed_chunks
)
thinking_blocks = tuple(
- block
- for chunk in parsed_chunks
- for block in (getattr(chunk.choices[0].delta, "thinking_blocks", None) or [])
+ block for chunk in parsed_chunks for block in (getattr(chunk.choices[0].delta, "thinking_blocks", None) or [])
)
assert reasoning_content == "Step 1. Step 2."
@@ -219,9 +187,7 @@ def test_streaming_truncated_thinking_deltas_keep_reasoning_content():
def test_handle_json_mode_chunk_response_format_tool():
- model_response_iterator = ModelResponseIterator(
- streaming_response=MagicMock(), sync_stream=True, json_mode=True
- )
+ model_response_iterator = ModelResponseIterator(streaming_response=MagicMock(), sync_stream=True, json_mode=True)
response_format_tool = ChatCompletionToolCallChunk(
id="tool_123",
type="function",
@@ -232,9 +198,7 @@ def test_handle_json_mode_chunk_response_format_tool():
index=0,
)
- text, tool_use = model_response_iterator._handle_json_mode_chunk(
- "", response_format_tool
- )
+ text, tool_use = model_response_iterator._handle_json_mode_chunk("", response_format_tool)
print(f"\n\nresponse_format_tool text: {text}\n\n")
print(f"\n\nresponse_format_tool tool_use: {tool_use}\n\n")
@@ -243,15 +207,11 @@ def test_handle_json_mode_chunk_response_format_tool():
def test_handle_json_mode_chunk_regular_tool():
- model_response_iterator = ModelResponseIterator(
- streaming_response=MagicMock(), sync_stream=True, json_mode=True
- )
+ model_response_iterator = ModelResponseIterator(streaming_response=MagicMock(), sync_stream=True, json_mode=True)
regular_tool = ChatCompletionToolCallChunk(
id="tool_456",
type="function",
- function=ChatCompletionToolCallFunctionChunk(
- name="get_weather", arguments='{"location": "San Francisco, CA"}'
- ),
+ function=ChatCompletionToolCallFunctionChunk(name="get_weather", arguments='{"location": "San Francisco, CA"}'),
index=0,
)
@@ -265,17 +225,13 @@ def test_handle_json_mode_chunk_regular_tool():
def test_handle_json_mode_chunk_streaming_response_format_tool():
- model_response_iterator = ModelResponseIterator(
- streaming_response=MagicMock(), sync_stream=True, json_mode=True
- )
+ model_response_iterator = ModelResponseIterator(streaming_response=MagicMock(), sync_stream=True, json_mode=True)
# First chunk: response_format tool with id and name, but no arguments
first_chunk = ChatCompletionToolCallChunk(
id="tool_123",
type="function",
- function=ChatCompletionToolCallFunctionChunk(
- name=RESPONSE_FORMAT_TOOL_NAME, arguments=""
- ),
+ function=ChatCompletionToolCallFunctionChunk(name=RESPONSE_FORMAT_TOOL_NAME, arguments=""),
index=0,
)
@@ -283,9 +239,7 @@ def test_handle_json_mode_chunk_streaming_response_format_tool():
second_chunk = ChatCompletionToolCallChunk(
id=None,
type="function",
- function=ChatCompletionToolCallFunctionChunk(
- name=None, arguments='{"question": "What is the weather?"'
- ),
+ function=ChatCompletionToolCallFunctionChunk(name=None, arguments='{"question": "What is the weather?"'),
index=0,
)
@@ -293,9 +247,7 @@ def test_handle_json_mode_chunk_streaming_response_format_tool():
third_chunk = ChatCompletionToolCallChunk(
id=None,
type="function",
- function=ChatCompletionToolCallFunctionChunk(
- name=None, arguments=', "answer": "It is sunny"}'
- ),
+ function=ChatCompletionToolCallFunctionChunk(name=None, arguments=', "answer": "It is sunny"}'),
index=0,
)
@@ -326,9 +278,7 @@ def test_handle_json_mode_chunk_streaming_response_format_tool():
def test_handle_json_mode_chunk_streaming_regular_tool():
- model_response_iterator = ModelResponseIterator(
- streaming_response=MagicMock(), sync_stream=True, json_mode=True
- )
+ model_response_iterator = ModelResponseIterator(streaming_response=MagicMock(), sync_stream=True, json_mode=True)
# First chunk: regular tool with id and name, but no arguments
first_chunk = ChatCompletionToolCallChunk(
@@ -342,9 +292,7 @@ def test_handle_json_mode_chunk_streaming_regular_tool():
second_chunk = ChatCompletionToolCallChunk(
id=None,
type="function",
- function=ChatCompletionToolCallFunctionChunk(
- name=None, arguments='{"location": "San Francisco, CA"}'
- ),
+ function=ChatCompletionToolCallFunctionChunk(name=None, arguments='{"location": "San Francisco, CA"}'),
index=0,
)
@@ -369,27 +317,19 @@ def test_handle_json_mode_chunk_streaming_regular_tool():
def test_response_format_tool_finish_reason():
- model_response_iterator = ModelResponseIterator(
- streaming_response=MagicMock(), sync_stream=True, json_mode=True
- )
+ model_response_iterator = ModelResponseIterator(streaming_response=MagicMock(), sync_stream=True, json_mode=True)
# First chunk: response_format tool
response_format_tool = ChatCompletionToolCallChunk(
id="tool_123",
type="function",
- function=ChatCompletionToolCallFunctionChunk(
- name=RESPONSE_FORMAT_TOOL_NAME, arguments='{"answer": "test"}'
- ),
+ function=ChatCompletionToolCallFunctionChunk(name=RESPONSE_FORMAT_TOOL_NAME, arguments='{"answer": "test"}'),
index=0,
)
# Process the tool call (should set converted_response_format_tool flag)
- text, tool_use = model_response_iterator._handle_json_mode_chunk(
- "", response_format_tool
- )
- print(
- f"\n\nconverted_response_format_tool flag: {model_response_iterator.converted_response_format_tool}\n\n"
- )
+ text, tool_use = model_response_iterator._handle_json_mode_chunk("", response_format_tool)
+ print(f"\n\nconverted_response_format_tool flag: {model_response_iterator.converted_response_format_tool}\n\n")
# Simulate message_delta chunk with tool_use stop_reason
message_delta_chunk = {
@@ -408,25 +348,19 @@ def test_response_format_tool_finish_reason():
def test_regular_tool_finish_reason():
- model_response_iterator = ModelResponseIterator(
- streaming_response=MagicMock(), sync_stream=True, json_mode=True
- )
+ model_response_iterator = ModelResponseIterator(streaming_response=MagicMock(), sync_stream=True, json_mode=True)
# First chunk: regular tool (not response_format)
regular_tool = ChatCompletionToolCallChunk(
id="tool_456",
type="function",
- function=ChatCompletionToolCallFunctionChunk(
- name="get_weather", arguments='{"location": "San Francisco, CA"}'
- ),
+ function=ChatCompletionToolCallFunctionChunk(name="get_weather", arguments='{"location": "San Francisco, CA"}'),
index=0,
)
# Process the tool call (should NOT set converted_response_format_tool flag)
text, tool_use = model_response_iterator._handle_json_mode_chunk("", regular_tool)
- print(
- f"\n\nconverted_response_format_tool flag: {model_response_iterator.converted_response_format_tool}\n\n"
- )
+ print(f"\n\nconverted_response_format_tool flag: {model_response_iterator.converted_response_format_tool}\n\n")
# Simulate message_delta chunk with tool_use stop_reason
message_delta_chunk = {
@@ -486,9 +420,7 @@ def test_text_only_streaming_has_index_zero():
for chunk in chunks:
parsed = iterator.chunk_parser(chunk)
if parsed.choices:
- assert (
- parsed.choices[0].index == 0
- ), f"Expected index=0, got {parsed.choices[0].index}"
+ assert parsed.choices[0].index == 0, f"Expected index=0, got {parsed.choices[0].index}"
def test_streaming_thinking_deltas_count_reasoning_tokens_in_usage():
@@ -665,9 +597,7 @@ def test_anthropic_completion_streaming_usage_matches_non_streaming_with_thinkin
]
self._write_response(
content_type="text/event-stream",
- body="".join(
- f"data: {json.dumps(event)}\n\n" for event in events
- ).encode("utf-8"),
+ body="".join(f"data: {json.dumps(event)}\n\n" for event in events).encode("utf-8"),
)
return
@@ -748,13 +678,9 @@ def test_anthropic_completion_streaming_usage_matches_non_streaming_with_thinkin
assert content_chunks == [answer_text]
assert stream_usage is not None
stream_completion_details = stream_usage["completion_tokens_details"]
- assert (
- stream_completion_details["reasoning_tokens"]
- == non_stream_details.reasoning_tokens
- )
+ assert stream_completion_details["reasoning_tokens"] == non_stream_details.reasoning_tokens
assert stream_completion_details["text_tokens"] == (
- stream_usage["completion_tokens"]
- - stream_completion_details["reasoning_tokens"]
+ stream_usage["completion_tokens"] - stream_completion_details["reasoning_tokens"]
)
assert requests_seen == [
{
@@ -846,9 +772,9 @@ def test_text_and_tool_streaming_has_index_zero():
for chunk in chunks:
parsed = iterator.chunk_parser(chunk)
if parsed.choices:
- assert (
- parsed.choices[0].index == 0
- ), f"Expected index=0 for chunk type {chunk.get('type')}, got {parsed.choices[0].index}"
+ assert parsed.choices[0].index == 0, (
+ f"Expected index=0 for chunk type {chunk.get('type')}, got {parsed.choices[0].index}"
+ )
def test_multiple_tools_streaming_has_index_zero():
@@ -901,15 +827,11 @@ def test_multiple_tools_streaming_has_index_zero():
for chunk in chunks:
parsed = iterator.chunk_parser(chunk)
if parsed.choices:
- assert (
- parsed.choices[0].index == 0
- ), f"Expected index=0, got {parsed.choices[0].index}"
+ assert parsed.choices[0].index == 0, f"Expected index=0, got {parsed.choices[0].index}"
def test_streaming_chunks_have_stable_ids():
- iterator = ModelResponseIterator(
- streaming_response=MagicMock(), sync_stream=False, json_mode=False
- )
+ iterator = ModelResponseIterator(streaming_response=MagicMock(), sync_stream=False, json_mode=False)
first_chunk = {
"type": "content_block_delta",
"index": 0,
@@ -934,9 +856,7 @@ def test_partial_json_chunk_accumulation():
This tests the fix for https://github.com/BerriAI/litellm/issues/17473
where network fragmentation can cause SSE data to arrive in partial chunks.
"""
- iterator = ModelResponseIterator(
- streaming_response=MagicMock(), sync_stream=True, json_mode=False
- )
+ iterator = ModelResponseIterator(streaming_response=MagicMock(), sync_stream=True, json_mode=False)
partial_chunk_1 = '{"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"Hel'
partial_chunk_2 = 'lo"}}'
@@ -944,31 +864,21 @@ def test_partial_json_chunk_accumulation():
# First partial chunk should return None (still accumulating)
result1 = iterator._parse_sse_data(f"data:{partial_chunk_1}")
assert result1 is None, "First partial chunk should return None while accumulating"
- assert (
- iterator.chunk_type == "accumulated_json"
- ), "Should switch to accumulated_json mode"
- assert (
- iterator.accumulated_json == partial_chunk_1
- ), "Should have accumulated first part"
+ assert iterator.chunk_type == "accumulated_json", "Should switch to accumulated_json mode"
+ assert iterator.accumulated_json == partial_chunk_1, "Should have accumulated first part"
# Second partial chunk should complete the JSON and return a parsed result
result2 = iterator._parse_sse_data(f"data:{partial_chunk_2}")
assert result2 is not None, "Second chunk should return parsed result"
- assert (
- iterator.accumulated_json == ""
- ), "Buffer should be cleared after successful parse"
- assert (
- result2.choices[0].delta.content == "Hello"
- ), f"Expected 'Hello', got '{result2.choices[0].delta.content}'"
+ assert iterator.accumulated_json == "", "Buffer should be cleared after successful parse"
+ assert result2.choices[0].delta.content == "Hello", f"Expected 'Hello', got '{result2.choices[0].delta.content}'"
def test_complete_json_chunk_no_accumulation():
"""
Test that complete JSON chunks are parsed immediately without accumulation.
"""
- iterator = ModelResponseIterator(
- streaming_response=MagicMock(), sync_stream=True, json_mode=False
- )
+ iterator = ModelResponseIterator(streaming_response=MagicMock(), sync_stream=True, json_mode=False)
complete_chunk = '{"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"Hello"}}'
@@ -976,18 +886,14 @@ def test_complete_json_chunk_no_accumulation():
assert result is not None, "Complete chunk should return parsed result immediately"
assert iterator.chunk_type == "valid_json", "Should remain in valid_json mode"
assert iterator.accumulated_json == "", "Buffer should remain empty"
- assert (
- result.choices[0].delta.content == "Hello"
- ), f"Expected 'Hello', got '{result.choices[0].delta.content}'"
+ assert result.choices[0].delta.content == "Hello", f"Expected 'Hello', got '{result.choices[0].delta.content}'"
def test_multiple_partial_chunks_accumulation():
"""
Test that multiple partial chunks can be accumulated across several iterations.
"""
- iterator = ModelResponseIterator(
- streaming_response=MagicMock(), sync_stream=True, json_mode=False
- )
+ iterator = ModelResponseIterator(streaming_response=MagicMock(), sync_stream=True, json_mode=False)
# Split a JSON chunk into three parts
part1 = '{"type":"content_block_del'
@@ -1018,9 +924,7 @@ def test_web_search_tool_result_no_extra_tool_calls():
The issue was that web_search_tool_result blocks have input_json_delta events with {}
that were incorrectly being converted to tool calls.
"""
- iterator = ModelResponseIterator(
- streaming_response=MagicMock(), sync_stream=True, json_mode=False
- )
+ iterator = ModelResponseIterator(streaming_response=MagicMock(), sync_stream=True, json_mode=False)
# Simulate the streaming sequence:
# 1. server_tool_use block starts (web_search)
@@ -1095,9 +999,7 @@ def test_web_search_tool_result_no_extra_tool_calls():
# Should have exactly 2 tool calls:
# 1. From content_block_start (server_tool_use) with id and name
# 2. From content_block_delta with the actual query
- assert (
- len(tool_calls_emitted) == 2
- ), f"Expected 2 tool calls, got {len(tool_calls_emitted)}"
+ assert len(tool_calls_emitted) == 2, f"Expected 2 tool calls, got {len(tool_calls_emitted)}"
# First tool call should have the id and name
assert tool_calls_emitted[0]["id"] == "srvtoolu_01ABC123"
@@ -1113,9 +1015,7 @@ def test_current_content_block_type_tracking():
"""
Test that current_content_block_type is properly tracked and reset.
"""
- iterator = ModelResponseIterator(
- streaming_response=MagicMock(), sync_stream=True, json_mode=False
- )
+ iterator = ModelResponseIterator(streaming_response=MagicMock(), sync_stream=True, json_mode=False)
# Initially should be None
assert iterator.current_content_block_type is None
@@ -1168,9 +1068,7 @@ def test_web_search_tool_result_captured_in_provider_specific_fields():
The web_search_tool_result content comes ALL AT ONCE in content_block_start,
not in deltas, so we need to capture it there.
"""
- iterator = ModelResponseIterator(
- streaming_response=MagicMock(), sync_stream=True, json_mode=False
- )
+ iterator = ModelResponseIterator(streaming_response=MagicMock(), sync_stream=True, json_mode=False)
# Simulate the streaming sequence with web_search_tool_result
chunks = [
@@ -1241,23 +1139,15 @@ def test_web_search_tool_result_captured_in_provider_specific_fields():
and parsed.choices[0].delta.provider_specific_fields
and "web_search_results" in parsed.choices[0].delta.provider_specific_fields
):
- web_search_results = parsed.choices[0].delta.provider_specific_fields[
- "web_search_results"
- ]
+ web_search_results = parsed.choices[0].delta.provider_specific_fields["web_search_results"]
# Verify web_search_results was captured
assert web_search_results is not None, "web_search_results should be captured"
assert len(web_search_results) == 1, "Should have 1 web_search_tool_result block"
- assert (
- web_search_results[0]["type"] == "web_search_tool_result"
- ), "Block type should be web_search_tool_result"
- assert (
- web_search_results[0]["tool_use_id"] == "srvtoolu_01ABC123"
- ), "tool_use_id should match"
+ assert web_search_results[0]["type"] == "web_search_tool_result", "Block type should be web_search_tool_result"
+ assert web_search_results[0]["tool_use_id"] == "srvtoolu_01ABC123", "tool_use_id should match"
assert len(web_search_results[0]["content"]) == 2, "Should have 2 search results"
- assert (
- web_search_results[0]["content"][0]["title"] == "Fun Otter Facts"
- ), "First result title should match"
+ assert web_search_results[0]["content"][0]["title"] == "Fun Otter Facts", "First result title should match"
def test_web_fetch_tool_result_captured_in_provider_specific_fields():
@@ -1271,9 +1161,7 @@ def test_web_fetch_tool_result_captured_in_provider_specific_fields():
The web_fetch_tool_result content comes ALL AT ONCE in content_block_start,
not in deltas, so we need to capture it there.
"""
- iterator = ModelResponseIterator(
- streaming_response=MagicMock(), sync_stream=True, json_mode=False
- )
+ iterator = ModelResponseIterator(streaming_response=MagicMock(), sync_stream=True, json_mode=False)
# Simulate the streaming sequence with web_fetch_tool_result
chunks = [
@@ -1344,25 +1232,15 @@ def test_web_fetch_tool_result_captured_in_provider_specific_fields():
and parsed.choices[0].delta.provider_specific_fields
and "web_search_results" in parsed.choices[0].delta.provider_specific_fields
):
- web_search_results = parsed.choices[0].delta.provider_specific_fields[
- "web_search_results"
- ]
+ web_search_results = parsed.choices[0].delta.provider_specific_fields["web_search_results"]
# Verify web_fetch_tool_result was captured (stored in web_search_results list)
assert web_search_results is not None, "web_search_results should be captured"
assert len(web_search_results) == 1, "Should have 1 web_fetch_tool_result block"
- assert (
- web_search_results[0]["type"] == "web_fetch_tool_result"
- ), "Block type should be web_fetch_tool_result"
- assert (
- web_search_results[0]["tool_use_id"] == "srvtoolu_01ABC123"
- ), "tool_use_id should match"
- assert (
- web_search_results[0]["content"]["url"] == "https://example.com"
- ), "URL should match"
- assert (
- web_search_results[0]["content"]["content"]["title"] == "Example Page"
- ), "Title should match"
+ assert web_search_results[0]["type"] == "web_fetch_tool_result", "Block type should be web_fetch_tool_result"
+ assert web_search_results[0]["tool_use_id"] == "srvtoolu_01ABC123", "tool_use_id should match"
+ assert web_search_results[0]["content"]["url"] == "https://example.com", "URL should match"
+ assert web_search_results[0]["content"]["content"]["title"] == "Example Page", "Title should match"
def test_web_fetch_tool_result_no_extra_tool_calls():
@@ -1375,9 +1253,7 @@ def test_web_fetch_tool_result_no_extra_tool_calls():
The issue was that web_fetch_tool_result blocks have input_json_delta events with {}
that were incorrectly being converted to tool calls.
"""
- iterator = ModelResponseIterator(
- streaming_response=MagicMock(), sync_stream=True, json_mode=False
- )
+ iterator = ModelResponseIterator(streaming_response=MagicMock(), sync_stream=True, json_mode=False)
# to verify it doesn't emit tool calls
chunks = [
@@ -1421,9 +1297,9 @@ def test_web_fetch_tool_result_no_extra_tool_calls():
tool_call_count += 1
# Should have 0 tool calls - web_fetch_tool_result should not emit tool calls
- assert (
- tool_call_count == 0
- ), f"Expected 0 tool calls, got {tool_call_count}. web_fetch_tool_result should not emit tool calls"
+ assert tool_call_count == 0, (
+ f"Expected 0 tool calls, got {tool_call_count}. web_fetch_tool_result should not emit tool calls"
+ )
def test_container_in_provider_specific_fields_streaming():
@@ -1433,9 +1309,7 @@ def test_container_in_provider_specific_fields_streaming():
When container with skills is used, the container field should be present in
the provider_specific_fields of the message_delta chunk.
"""
- iterator = ModelResponseIterator(
- streaming_response=MagicMock(), sync_stream=True, json_mode=False
- )
+ iterator = ModelResponseIterator(streaming_response=MagicMock(), sync_stream=True, json_mode=False)
# Simulate streaming chunks
chunks = [
@@ -1503,20 +1377,12 @@ def test_container_in_provider_specific_fields_streaming():
and parsed.choices[0].delta.provider_specific_fields
and "container" in parsed.choices[0].delta.provider_specific_fields
):
- container_field = parsed.choices[0].delta.provider_specific_fields[
- "container"
- ]
+ container_field = parsed.choices[0].delta.provider_specific_fields["container"]
# Verify container was captured
- assert (
- container_field is not None
- ), "container should be captured in provider_specific_fields"
- assert (
- container_field["id"] == "container_011CW9hA9zpZ8xD3bjjShy4p"
- ), "container id should match"
- assert (
- container_field["expires_at"] == "2025-12-16T04:57:16.913181Z"
- ), "expires_at should match"
+ assert container_field is not None, "container should be captured in provider_specific_fields"
+ assert container_field["id"] == "container_011CW9hA9zpZ8xD3bjjShy4p", "container id should match"
+ assert container_field["expires_at"] == "2025-12-16T04:57:16.913181Z", "expires_at should match"
assert len(container_field["skills"]) == 1, "Should have 1 skill"
assert container_field["skills"][0]["skill_id"] == "pptx", "skill_id should be pptx"
assert container_field["skills"][0]["version"] == "20251013", "version should match"
@@ -1529,9 +1395,7 @@ def test_container_in_provider_specific_fields_non_streaming():
When container with skills is used in non-streaming, the container field should be
present in the provider_specific_fields of the response.
"""
- iterator = ModelResponseIterator(
- streaming_response=MagicMock(), sync_stream=False, json_mode=False
- )
+ iterator = ModelResponseIterator(streaming_response=MagicMock(), sync_stream=False, json_mode=False)
# Simulate a message_delta chunk with container (as it would appear in non-streaming)
message_delta_chunk = {
@@ -1567,21 +1431,13 @@ def test_container_in_provider_specific_fields_non_streaming():
# Verify container is in provider_specific_fields
assert model_response.choices[0].delta.provider_specific_fields is not None
assert "container" in model_response.choices[0].delta.provider_specific_fields
- container_field = model_response.choices[0].delta.provider_specific_fields[
- "container"
- ]
+ container_field = model_response.choices[0].delta.provider_specific_fields["container"]
assert container_field["id"] == "container_abc123xyz", "container id should match"
- assert (
- container_field["expires_at"] == "2025-12-20T10:30:00.000000Z"
- ), "expires_at should match"
+ assert container_field["expires_at"] == "2025-12-20T10:30:00.000000Z", "expires_at should match"
assert len(container_field["skills"]) == 2, "Should have 2 skills"
- assert (
- container_field["skills"][0]["skill_id"] == "code_execution"
- ), "First skill_id should be code_execution"
- assert (
- container_field["skills"][1]["skill_id"] == "pptx"
- ), "Second skill_id should be pptx"
+ assert container_field["skills"][0]["skill_id"] == "code_execution", "First skill_id should be code_execution"
+ assert container_field["skills"][1]["skill_id"] == "pptx", "Second skill_id should be pptx"
def test_container_absent_when_not_provided():
@@ -1590,9 +1446,7 @@ def test_container_absent_when_not_provided():
This ensures we don't add empty or None container fields.
"""
- iterator = ModelResponseIterator(
- streaming_response=MagicMock(), sync_stream=False, json_mode=False
- )
+ iterator = ModelResponseIterator(streaming_response=MagicMock(), sync_stream=False, json_mode=False)
# message_delta without container
message_delta_chunk = {
@@ -1611,9 +1465,9 @@ def test_container_absent_when_not_provided():
# Verify container is NOT in provider_specific_fields when not provided
if model_response.choices[0].delta.provider_specific_fields:
- assert (
- "container" not in model_response.choices[0].delta.provider_specific_fields
- ), "container should not be present when not provided in delta"
+ assert "container" not in model_response.choices[0].delta.provider_specific_fields, (
+ "container should not be present when not provided in delta"
+ )
def test_streaming_code_execution_produces_code_interpreter_results():
@@ -1809,8 +1663,7 @@ def test_streaming_multiple_code_executions_no_duplicates():
# Second (final) emission: cumulative list with BOTH results
# This is what stream_chunk_builder will pick as "last value wins"
assert len(emissions[1]) == 2, (
- f"Expected final emission to have 2 results, got {len(emissions[1])}. "
- f"IDs: {[r.id for r in emissions[1]]}"
+ f"Expected final emission to have 2 results, got {len(emissions[1])}. IDs: {[r.id for r in emissions[1]]}"
)
assert emissions[1][0].id == "srvtoolu_01AAA"
assert emissions[1][0].code == "echo first"
@@ -1974,9 +1827,7 @@ def test_empty_output_produces_null_outputs():
assert code_results is not None, "No code_interpreter_results emitted"
assert len(code_results) == 1
assert code_results[0].id == "srvtoolu_01AAA"
- assert (
- code_results[0].outputs is None
- ), f"Expected outputs=None for empty execution, got {code_results[0].outputs}"
+ assert code_results[0].outputs is None, f"Expected outputs=None for empty execution, got {code_results[0].outputs}"
def test_non_bash_tool_result_skipped():
@@ -2039,12 +1890,10 @@ def test_non_bash_tool_result_skipped():
code_results = psf["code_interpreter_results"]
# code_interpreter_results should be emitted but empty (no bash results)
- assert (
- code_results is not None
- ), "Expected code_interpreter_results key to be emitted"
- assert (
- len(code_results) == 0
- ), f"Expected 0 code_interpreter_results for text_editor result, got {len(code_results)}"
+ assert code_results is not None, "Expected code_interpreter_results key to be emitted"
+ assert len(code_results) == 0, (
+ f"Expected 0 code_interpreter_results for text_editor result, got {len(code_results)}"
+ )
class TestRustChatCompletionsHook:
@@ -2081,13 +1930,9 @@ class TestRustChatCompletionsHook:
from litellm.rust_bridge import chat_completions as bridge
monkeypatch.delenv("LITELLM_RUST", raising=False)
- bridge.set_rust_chat_completions(
- chat_completions=None, achat_completions=None, decline=None
- )
+ bridge.set_rust_chat_completions(chat_completions=None, achat_completions=None, decline=None)
yield
- bridge.set_rust_chat_completions(
- chat_completions=None, achat_completions=None, decline=None
- )
+ bridge.set_rust_chat_completions(chat_completions=None, achat_completions=None, decline=None)
@staticmethod
def _completion_kwargs(**overrides):
@@ -2185,9 +2030,7 @@ class TestRustChatCompletionsHook:
from litellm.llms.anthropic.chat.handler import AnthropicChatCompletion
seen = self._inject()
- AnthropicChatCompletion().completion(
- **self._completion_kwargs(optional_params={"max_tokens": 7})
- )
+ AnthropicChatCompletion().completion(**self._completion_kwargs(optional_params={"max_tokens": 7}))
assert seen["call"][0]["optional_params"]["max_tokens"] == 7
def test_without_the_opt_in_the_core_is_never_consulted(self):
@@ -2195,15 +2038,14 @@ class TestRustChatCompletionsHook:
from litellm.llms.anthropic.chat.transformation import AnthropicConfig
seen = self._inject()
- with patch.object(
- AnthropicConfig, "transform_request", return_value={"model": "m", "messages": []}
- ) as transform, patch.object(
- AnthropicChatCompletion, "acompletion_function"
+ with (
+ patch.object(
+ AnthropicConfig, "transform_request", return_value={"model": "m", "messages": []}
+ ) as transform,
+ patch.object(AnthropicChatCompletion, "acompletion_function"),
):
try:
- AnthropicChatCompletion().completion(
- **self._completion_kwargs(litellm_params={})
- )
+ AnthropicChatCompletion().completion(**self._completion_kwargs(litellm_params={}))
except Exception:
# The Python path goes on to make an HTTP call; reaching it is
# the assertion, so the network failure below is expected.
@@ -2217,9 +2059,7 @@ class TestRustChatCompletionsHook:
from litellm.llms.anthropic.chat.transformation import AnthropicConfig
seen = self._inject(decline_reason="unrecognized request parameter")
- with patch.object(
- AnthropicConfig, "transform_request", return_value={"model": "m", "messages": []}
- ):
+ with patch.object(AnthropicConfig, "transform_request", return_value={"model": "m", "messages": []}):
try:
AnthropicChatCompletion().completion(**self._completion_kwargs())
except Exception:
@@ -2232,9 +2072,7 @@ class TestRustChatCompletionsHook:
from litellm.llms.anthropic.chat.transformation import AnthropicConfig
seen = self._inject()
- with patch.object(
- AnthropicConfig, "transform_request", return_value={"model": "m", "messages": []}
- ):
+ with patch.object(AnthropicConfig, "transform_request", return_value={"model": "m", "messages": []}):
try:
AnthropicChatCompletion().completion(
**self._completion_kwargs(optional_params={"max_tokens": 16, "stream": True})
@@ -2248,9 +2086,7 @@ class TestRustChatCompletionsHook:
seen = self._inject()
logging_obj = MagicMock()
- AnthropicChatCompletion().completion(
- **self._completion_kwargs(logging_obj=logging_obj)
- )
+ AnthropicChatCompletion().completion(**self._completion_kwargs(logging_obj=logging_obj))
assert logging_obj.pre_call.call_count == 1
assert len(seen["call"]) == 1
@@ -2264,9 +2100,7 @@ class TestRustChatCompletionsHook:
self._inject()
logging_obj = MagicMock()
- AnthropicChatCompletion().completion(
- **self._completion_kwargs(logging_obj=logging_obj)
- )
+ AnthropicChatCompletion().completion(**self._completion_kwargs(logging_obj=logging_obj))
assert logging_obj.post_call.call_count == 1
logged = logging_obj.post_call.call_args.kwargs["original_response"]
@@ -2291,18 +2125,12 @@ class TestRustChatCompletionsHook:
raise _Declined("blank message text")
monkeypatch.setattr(bridge, "get_native_bridge", lambda: _FakeNative())
- bridge.set_rust_chat_completions(
- decline=lambda **_kwargs: None, chat_completions=declining_native
- )
+ bridge.set_rust_chat_completions(decline=lambda **_kwargs: None, chat_completions=declining_native)
logging_obj, calls = self._recording_logging_obj()
- with patch.object(
- AnthropicConfig, "transform_request", return_value={"model": "m", "messages": []}
- ):
+ with patch.object(AnthropicConfig, "transform_request", return_value={"model": "m", "messages": []}):
try:
- AnthropicChatCompletion().completion(
- **self._completion_kwargs(logging_obj=logging_obj)
- )
+ AnthropicChatCompletion().completion(**self._completion_kwargs(logging_obj=logging_obj))
except Exception:
# The Python path goes on to make an HTTP call; the log count is
# the assertion, so a failure past this point is expected.
@@ -2327,21 +2155,15 @@ class TestRustChatCompletionsHook:
async def declining_native(**_kwargs):
raise _Declined("blank message text")
- bridge.set_rust_chat_completions(
- decline=lambda **_kwargs: None, achat_completions=declining_native
- )
+ bridge.set_rust_chat_completions(decline=lambda **_kwargs: None, achat_completions=declining_native)
sentinel = object()
async def python_path(**_kwargs):
return sentinel
- with patch.object(
- AnthropicChatCompletion, "acompletion_function", side_effect=python_path
- ) as python_call:
- result = await AnthropicChatCompletion().completion(
- **self._completion_kwargs(acompletion=True)
- )
+ with patch.object(AnthropicChatCompletion, "acompletion_function", side_effect=python_path) as python_call:
+ result = await AnthropicChatCompletion().completion(**self._completion_kwargs(acompletion=True))
assert result is sentinel
assert python_call.called, "a failing rust call must re-enter the python path"
@@ -2354,20 +2176,15 @@ class TestRustChatCompletionsHook:
async def native(**_kwargs):
return dict(self.RUST_RESPONSE)
- bridge.set_rust_chat_completions(
- decline=lambda **_kwargs: None, achat_completions=native
- )
+ bridge.set_rust_chat_completions(decline=lambda **_kwargs: None, achat_completions=native)
with patch.object(AnthropicChatCompletion, "acompletion_function") as python_call:
- result = await AnthropicChatCompletion().completion(
- **self._completion_kwargs(acompletion=True)
- )
+ result = await AnthropicChatCompletion().completion(**self._completion_kwargs(acompletion=True))
assert result.choices[0].message.content == "hello from rust"
assert result._hidden_params["additional_headers"] == {"x-litellm-rust": "true"}
assert not python_call.called
-
def test_pre_call_logging_fires_once_when_the_sync_rust_call_declines(self, monkeypatch):
"""One request, one pre_call, on the synchronous path too. Without the
suppression the Python path logs a second time for the same attempt."""
@@ -2387,27 +2204,19 @@ class TestRustChatCompletionsHook:
def declining_native(**_kwargs):
raise _Declined("blank message text")
- bridge.set_rust_chat_completions(
- decline=lambda **_kwargs: None, chat_completions=declining_native
- )
+ bridge.set_rust_chat_completions(decline=lambda **_kwargs: None, chat_completions=declining_native)
logging_obj, calls = self._recording_logging_obj()
- with patch.object(
- AnthropicConfig, "transform_request", return_value={"model": "m", "messages": []}
- ):
+ with patch.object(AnthropicConfig, "transform_request", return_value={"model": "m", "messages": []}):
try:
- AnthropicChatCompletion().completion(
- **self._completion_kwargs(logging_obj=logging_obj)
- )
+ AnthropicChatCompletion().completion(**self._completion_kwargs(logging_obj=logging_obj))
except Exception:
# The Python path goes on to make an HTTP call; the log count is
# the assertion, so a failure past this point is expected.
pass
assert len(calls["pre_call"]) == 1
- assert calls["pre_call"][0]["additional_args"]["complete_input_dict"]["model"] == (
- "claude-sonnet-4-5"
- )
+ assert calls["pre_call"][0]["additional_args"]["complete_input_dict"]["model"] == ("claude-sonnet-4-5")
def test_pre_call_logging_still_fires_when_rust_is_not_involved(self, monkeypatch):
"""The suppression must not swallow the log on the ordinary path."""
@@ -2416,9 +2225,7 @@ class TestRustChatCompletionsHook:
self._inject()
logging_obj, calls = self._recording_logging_obj()
- with patch.object(
- AnthropicConfig, "transform_request", return_value={"model": "m", "messages": []}
- ):
+ with patch.object(AnthropicConfig, "transform_request", return_value={"model": "m", "messages": []}):
try:
AnthropicChatCompletion().completion(
**self._completion_kwargs(litellm_params={}, logging_obj=logging_obj)
diff --git a/tests/test_litellm/llms/anthropic/chat/test_anthropic_chat_transformation.py b/tests/test_litellm/llms/anthropic/chat/test_anthropic_chat_transformation.py
index 4f340ee0f3f..1f250bd3ae5 100644
--- a/tests/test_litellm/llms/anthropic/chat/test_anthropic_chat_transformation.py
+++ b/tests/test_litellm/llms/anthropic/chat/test_anthropic_chat_transformation.py
@@ -1,4 +1,3 @@
-
import pytest
from unittest.mock import MagicMock, patch
@@ -33,13 +32,9 @@ def test_response_format_transformation_unit_test():
"additionalProperties": False,
}
- result = config._create_json_tool_call_for_response_format(
- json_schema=response_format_json_schema
- )
+ result = config._create_json_tool_call_for_response_format(json_schema=response_format_json_schema)
- assert result["input_schema"]["properties"] == {
- "agent_doing": {"title": "Agent Doing", "type": "string"}
- }
+ assert result["input_schema"]["properties"] == {"agent_doing": {"title": "Agent Doing", "type": "string"}}
print(result)
@@ -508,9 +503,7 @@ def test_extract_response_content_with_citations():
},
}
- _, citations, _, _, _, _, _, _ = config.extract_response_content(
- completion_response
- )
+ _, citations, _, _, _, _, _, _ = config.extract_response_content(completion_response)
assert citations == [
[
{
@@ -583,12 +576,8 @@ def test_web_search_tool_transformation():
assert anthropic_web_search_tool["user_location"]["city"] == "San Francisco"
-@pytest.mark.parametrize(
- "search_context_size, expected_max_uses", [("low", 1), ("medium", 5), ("high", 10)]
-)
-def test_web_search_tool_transformation_with_search_context_size(
- search_context_size, expected_max_uses
-):
+@pytest.mark.parametrize("search_context_size, expected_max_uses", [("low", 1), ("medium", 5), ("high", 10)])
+def test_web_search_tool_transformation_with_search_context_size(search_context_size, expected_max_uses):
from litellm.types.llms.openai import OpenAIWebSearchOptions
config = AnthropicConfig()
@@ -763,10 +752,7 @@ def test_web_search_tool_result_in_provider_specific_fields():
assert "web_search_results" in provider_fields
assert len(provider_fields["web_search_results"]) == 1
assert provider_fields["web_search_results"][0]["type"] == "web_search_tool_result"
- assert (
- provider_fields["web_search_results"][0]["tool_use_id"]
- == "srvtoolu_provider_test"
- )
+ assert provider_fields["web_search_results"][0]["tool_use_id"] == "srvtoolu_provider_test"
def test_multiple_web_search_tool_results():
@@ -990,10 +976,7 @@ def test_transform_response_with_prefix_prompt():
)
assert result is not None
- assert (
- result.choices[0].message.content
- == "You are a helpful assistant. The grass is green."
- )
+ assert result.choices[0].message.content == "You are a helpful assistant. The grass is green."
def test_get_supported_params_thinking():
@@ -1107,18 +1090,12 @@ def test_anthropic_beta_header_merging_with_output_format():
}
}
- result_headers = config.update_headers_with_optional_anthropic_beta(
- headers, optional_params
- )
+ result_headers = config.update_headers_with_optional_anthropic_beta(headers, optional_params)
# Both beta headers should be present
beta_value = result_headers["anthropic-beta"]
- assert (
- "context-1m-2025-08-07" in beta_value
- ), f"User's context-1m beta header missing from: {beta_value}"
- assert (
- "structured-outputs-2025-11-13" in beta_value
- ), f"Structured output beta header missing from: {beta_value}"
+ assert "context-1m-2025-08-07" in beta_value, f"User's context-1m beta header missing from: {beta_value}"
+ assert "structured-outputs-2025-11-13" in beta_value, f"Structured output beta header missing from: {beta_value}"
def test_anthropic_beta_header_merging_with_multiple_features():
@@ -1140,9 +1117,7 @@ def test_anthropic_beta_header_merging_with_multiple_features():
"tools": [{"type": "web_fetch_20250910", "name": "web_fetch"}],
}
- result_headers = config.update_headers_with_optional_anthropic_beta(
- headers, optional_params
- )
+ result_headers = config.update_headers_with_optional_anthropic_beta(headers, optional_params)
beta_value = result_headers["anthropic-beta"]
@@ -1185,9 +1160,7 @@ def test_anthropic_structured_output_beta_header():
"strict": True,
"schema": {
"description": 'Progress report for the thinking process\n\nThis model represents a snapshot of the agent\'s current progress during\nthe thinking process, providing a brief description of the current activity.\n\nAttributes:\n agent_doing: Brief description of what the agent is currently doing.\n Should be kept under 10 words. Example: "Learning about home automation"',
- "properties": {
- "agent_doing": {"title": "Agent Doing", "type": "string"}
- },
+ "properties": {"agent_doing": {"title": "Agent Doing", "type": "string"}},
"required": ["agent_doing"],
"title": "ThinkingStep",
"type": "object",
@@ -1201,10 +1174,7 @@ def test_anthropic_structured_output_beta_header():
assert response is not None
print(f"response: {response}")
print(f"raw_request_headers: {response['raw_request_headers']}")
- assert (
- "structured-outputs-2025-11-13"
- in response["raw_request_headers"]["anthropic-beta"]
- )
+ assert "structured-outputs-2025-11-13" in response["raw_request_headers"]["anthropic-beta"]
@pytest.mark.parametrize(
@@ -1340,9 +1310,7 @@ def test_tool_search_regex_detection():
config = AnthropicModelInfo()
# Test with tool search regex tool
- tools = [
- {"type": "tool_search_tool_regex_20251119", "name": "tool_search_tool_regex"}
- ]
+ tools = [{"type": "tool_search_tool_regex_20251119", "name": "tool_search_tool_regex"}]
assert config.is_tool_search_used(tools) is True
# Test without tool search
@@ -1357,9 +1325,7 @@ def test_tool_search_bm25_detection():
config = AnthropicModelInfo()
# Test with tool search BM25 tool
- tools = [
- {"type": "tool_search_tool_bm25_20251119", "name": "tool_search_tool_bm25"}
- ]
+ tools = [{"type": "tool_search_tool_bm25_20251119", "name": "tool_search_tool_bm25"}]
assert config.is_tool_search_used(tools) is True
@@ -1551,9 +1517,7 @@ def test_tool_search_complete_response_parsing():
"tool_use_id": "srvtoolu_015i6aVA2niwzv4RG4DtnxDJ",
"content": {
"type": "tool_search_tool_search_result",
- "tool_references": [
- {"type": "tool_reference", "tool_name": "get_weather"}
- ],
+ "tool_references": [{"type": "tool_reference", "tool_name": "get_weather"}],
},
},
{"type": "text", "text": "Great! I found a weather tool."},
@@ -1604,9 +1568,7 @@ def test_tool_search_complete_response_parsing():
assert usage.server_tool_use is not None
assert usage.server_tool_use.web_search_requests == 0
- assert (
- usage.server_tool_use.tool_search_requests == 1
- ) # Counted from server_tool_use blocks
+ assert usage.server_tool_use.tool_search_requests == 1 # Counted from server_tool_use blocks
def test_allowed_callers_field_preservation():
@@ -1658,9 +1620,7 @@ def test_programmatic_tool_calling_beta_header():
assert is_programmatic is True
# Test header generation
- headers = model_info.get_anthropic_headers(
- api_key="test-key", programmatic_tool_calling_used=True
- )
+ headers = model_info.get_anthropic_headers(api_key="test-key", programmatic_tool_calling_used=True)
assert "anthropic-beta" in headers
assert "advanced-tool-use-2025-11-20" in headers["anthropic-beta"]
@@ -1804,9 +1764,7 @@ def test_input_examples_beta_header():
assert is_examples_used is True
# Test header generation
- headers = model_info.get_anthropic_headers(
- api_key="test-key", input_examples_used=True
- )
+ headers = model_info.get_anthropic_headers(api_key="test-key", input_examples_used=True)
assert "anthropic-beta" in headers
assert "advanced-tool-use-2025-11-20" in headers["anthropic-beta"]
@@ -1892,10 +1850,7 @@ def test_input_examples_empty_list_not_added():
transformed_tool, _ = config._map_tool_helper(tool)
assert transformed_tool is not None
# Empty list should not be added
- assert (
- "input_examples" not in transformed_tool
- or len(transformed_tool.get("input_examples", [])) == 0
- )
+ assert "input_examples" not in transformed_tool or len(transformed_tool.get("input_examples", [])) == 0
# ============ Effort Parameter Tests ============
@@ -1955,9 +1910,7 @@ def test_effort_beta_header_injection():
effort_used = model_info.is_effort_used(optional_params=optional_params, custom_llm_provider="anthropic")
assert effort_used is True
- headers = model_info.get_anthropic_headers(
- api_key="test-key", effort_used=effort_used
- )
+ headers = model_info.get_anthropic_headers(api_key="test-key", effort_used=effort_used)
assert "anthropic-beta" in headers
assert "effort-2025-11-24" in headers["anthropic-beta"]
@@ -1983,9 +1936,7 @@ def test_effort_validation():
optional_params = {"output_config": {"effort": "invalid"}}
- with pytest.raises(
- litellm.exceptions.BadRequestError, match="Invalid effort value"
- ):
+ with pytest.raises(litellm.exceptions.BadRequestError, match="Invalid effort value"):
config.transform_request(
model="claude-opus-4-5-20251101",
messages=messages,
@@ -2221,16 +2172,8 @@ def test_anthropic_model_supports_speed_param_rejects_non_anthropic_providers(
):
"""Fast mode is direct-Anthropic-only. Vertex/Azure/Bedrock strip their prefix
before the shared transform runs, so the bare Opus id must still be rejected."""
- assert (
- AnthropicConfig._model_supports_speed_param(
- "claude-opus-4-8", custom_llm_provider
- )
- is False
- )
- assert (
- AnthropicConfig._model_supports_speed_param("claude-opus-4-8", "anthropic")
- is True
- )
+ assert AnthropicConfig._model_supports_speed_param("claude-opus-4-8", custom_llm_provider) is False
+ assert AnthropicConfig._model_supports_speed_param("claude-opus-4-8", "anthropic") is True
def test_vertex_anthropic_drops_speed_for_opus_with_drop_params(monkeypatch):
@@ -2529,9 +2472,7 @@ def test_supports_effort_level_handles_provider_prefixes(model, level, expected)
("claude-opus-4-5-20251101", None, False),
],
)
-def test_validate_effort_for_model_centralises_per_model_gating(
- model, effort, expect_error
-):
+def test_validate_effort_for_model_centralises_per_model_gating(model, effort, expect_error):
err = AnthropicConfig._validate_effort_for_model(model, effort, "anthropic")
if expect_error:
assert err is not None
@@ -2580,11 +2521,7 @@ def test_transform_request_injects_dummy_tool_without_tools_param():
litellm.modify_params = prev_modify_params
assert "tools" in result
- names = [
- t.get("name")
- for t in result["tools"]
- if isinstance(t, dict) and t.get("name") is not None
- ]
+ names = [t.get("name") for t in result["tools"] if isinstance(t, dict) and t.get("name") is not None]
assert "dummy_tool" in names
@@ -2673,13 +2610,9 @@ def test_calculate_usage_completion_tokens_details_with_reasoning():
"output_tokens": 500,
}
# Simulating reasoning content that would count as ~50 tokens
- reasoning_content = (
- "Let me think about this step by step. " * 10
- ) # Roughly 50 tokens
+ reasoning_content = "Let me think about this step by step. " * 10 # Roughly 50 tokens
- usage = config.calculate_usage(
- usage_object=usage_object, reasoning_content=reasoning_content
- )
+ usage = config.calculate_usage(usage_object=usage_object, reasoning_content=reasoning_content)
# completion_tokens_details should be populated with both reasoning and text tokens
assert usage.completion_tokens_details is not None
@@ -2730,9 +2663,7 @@ def test_reasoning_effort_maps_to_adaptive_thinking_for_claude_4_6_models():
# reasoning_effort should not be in the result (it's transformed to thinking)
assert "reasoning_effort" not in result
# Should set output_config with the mapped effort value
- assert (
- "output_config" in result
- ), f"output_config missing for {model} with effort={effort}"
+ assert "output_config" in result, f"output_config missing for {model} with effort={effort}"
assert result["output_config"]["effort"] == effort_map[effort]
@@ -2808,7 +2739,6 @@ def test_raw_adaptive_thinking_untouched_for_46_plus_model():
assert result["thinking"] == {"type": "adaptive"}
-
@pytest.mark.parametrize(
"model, expected",
[
@@ -2834,9 +2764,7 @@ def test_raw_adaptive_thinking_untouched_for_46_plus_model():
("gpt-4o", False),
],
)
-def test_is_adaptive_thinking_model_is_sourced_from_cost_map(
- local_model_cost_map, model, expected
-):
+def test_is_adaptive_thinking_model_is_sourced_from_cost_map(local_model_cost_map, model, expected):
"""Adaptive thinking resolves from the cost map first (an explicit
supports_adaptive_thinking entry, or the anthropic-claude fallback rule for unmapped
future Claudes), then from a date-safe opus/sonnet/haiku >= 4.6 name version as a
@@ -2952,9 +2880,7 @@ def test_reasoning_effort_sets_output_config_for_46_models():
drop_params=False,
)
- assert (
- "output_config" in result
- ), f"output_config missing for {model} with effort={effort}"
+ assert "output_config" in result, f"output_config missing for {model} with effort={effort}"
assert result["output_config"]["effort"] == effort
@@ -2993,9 +2919,7 @@ def test_reasoning_effort_does_not_set_output_config_for_older_models():
drop_params=False,
)
- assert (
- "output_config" not in result
- ), f"output_config should not be set for {model}"
+ assert "output_config" not in result, f"output_config should not be set for {model}"
@pytest.mark.parametrize(
@@ -3035,14 +2959,10 @@ def test_reasoning_effort_accepts_dict_shape_for_adaptive_model(reasoning_effort
)
# thinking must be set (adaptive for 4.6+)
- assert (
- "thinking" in result
- ), f"thinking missing for reasoning_effort={reasoning_effort_value!r}"
+ assert "thinking" in result, f"thinking missing for reasoning_effort={reasoning_effort_value!r}"
assert result["thinking"]["type"] == "adaptive"
# output_config must carry the mapped effort
- assert (
- "output_config" in result
- ), f"output_config missing for reasoning_effort={reasoning_effort_value!r}"
+ assert "output_config" in result, f"output_config missing for reasoning_effort={reasoning_effort_value!r}"
assert result["output_config"]["effort"] == "low"
@@ -3071,16 +2991,13 @@ def test_reasoning_effort_accepts_dict_shape_for_non_adaptive_model(
drop_params=False,
)
- assert (
- "thinking" in result
- ), f"thinking missing for reasoning_effort={reasoning_effort_value!r}"
+ assert "thinking" in result, f"thinking missing for reasoning_effort={reasoning_effort_value!r}"
assert result["thinking"]["type"] == "enabled"
assert "budget_tokens" in result["thinking"]
assert result["thinking"]["budget_tokens"] > 0
# Older models must not get adaptive-thinking output_config
assert "output_config" not in result, (
- f"output_config should not be set for non-adaptive model "
- f"(reasoning_effort={reasoning_effort_value!r})"
+ f"output_config should not be set for non-adaptive model (reasoning_effort={reasoning_effort_value!r})"
)
@@ -3106,12 +3023,8 @@ def test_reasoning_effort_unparseable_dict_is_dropped(bad_value):
model="claude-sonnet-4-6-20260219",
drop_params=False,
)
- assert (
- "thinking" not in result
- ), f"thinking should not be set for bad value {bad_value!r}"
- assert (
- "output_config" not in result
- ), f"output_config should not be set for bad value {bad_value!r}"
+ assert "thinking" not in result, f"thinking should not be set for bad value {bad_value!r}"
+ assert "output_config" not in result, f"output_config should not be set for bad value {bad_value!r}"
@pytest.mark.parametrize(
@@ -3242,9 +3155,7 @@ def test_reasoning_effort_garbage_raises_bad_request(effort):
("max", DEFAULT_REASONING_EFFORT_MAX_THINKING_BUDGET),
],
)
-def test_reasoning_effort_xhigh_max_maps_to_budget_on_budget_model(
- effort, expected_budget
-):
+def test_reasoning_effort_xhigh_max_maps_to_budget_on_budget_model(effort, expected_budget):
"""``xhigh`` / ``max`` extend the budget_tokens progression on budget-mode models."""
config = AnthropicConfig()
@@ -3391,17 +3302,11 @@ def test_code_execution_tool_results_extraction():
# Verify first tool call
assert transformed_response.choices[0].message.tool_calls[0].id == "srvtoolu_01ABC"
- assert (
- transformed_response.choices[0].message.tool_calls[0].function.name
- == "bash_code_execution"
- )
+ assert transformed_response.choices[0].message.tool_calls[0].function.name == "bash_code_execution"
# Verify second tool call
assert transformed_response.choices[0].message.tool_calls[1].id == "srvtoolu_01DEF"
- assert (
- transformed_response.choices[0].message.tool_calls[1].function.name
- == "text_editor_code_execution"
- )
+ assert transformed_response.choices[0].message.tool_calls[1].function.name == "text_editor_code_execution"
# Verify tool results are in provider_specific_fields
provider_fields = transformed_response.choices[0].message.provider_specific_fields
@@ -3424,10 +3329,7 @@ def test_code_execution_tool_results_extraction():
assert editor_result["content"]["is_file_update"] is False
# Verify text content is properly concatenated
- assert (
- "I'll calculate that for you."
- in transformed_response.choices[0].message.content
- )
+ assert "I'll calculate that for you." in transformed_response.choices[0].message.content
assert "Done!" in transformed_response.choices[0].message.content
@@ -3495,10 +3397,7 @@ def test_code_execution_tool_results_in_hidden_params():
assert "provider_specific_fields" in hidden
assert "tool_results" in hidden["provider_specific_fields"]
assert len(hidden["provider_specific_fields"]["tool_results"]) == 1
- assert (
- hidden["provider_specific_fields"]["tool_results"][0]["content"]["stdout"]
- == "hello\n"
- )
+ assert hidden["provider_specific_fields"]["tool_results"][0]["content"]["stdout"] == "hello\n"
def test_tool_search_tool_result_not_in_tool_results():
@@ -3694,10 +3593,7 @@ def test_compaction_block_in_provider_specific_fields():
assert "compaction_blocks" in provider_fields
assert len(provider_fields["compaction_blocks"]) == 1
assert provider_fields["compaction_blocks"][0]["type"] == "compaction"
- assert (
- "Summary of the conversation"
- in provider_fields["compaction_blocks"][0]["content"]
- )
+ assert "Summary of the conversation" in provider_fields["compaction_blocks"][0]["content"]
def test_multiple_compaction_blocks():
@@ -3745,9 +3641,7 @@ def test_compaction_block_request_transformation():
{"role": "user", "content": "What is the weather in San Francisco?"},
{
"role": "assistant",
- "content": [
- {"type": "text", "text": "I don't have access to real-time data."}
- ],
+ "content": [{"type": "text", "text": "I don't have access to real-time data."}],
"provider_specific_fields": {
"compaction_blocks": [
{
@@ -3760,9 +3654,7 @@ def test_compaction_block_request_transformation():
{"role": "user", "content": "What about New York?"},
]
- result = anthropic_messages_pt(
- messages=messages, model="claude-opus-4-6", llm_provider="anthropic"
- )
+ result = anthropic_messages_pt(messages=messages, model="claude-opus-4-6", llm_provider="anthropic")
# Find the assistant message
assistant_message = None
@@ -3876,9 +3768,7 @@ def test_map_openai_context_management_to_anthropic():
"instructions": "Focus on preserving code snippets",
}
]
- result = config.map_openai_context_management_to_anthropic(
- openai_format_with_instructions
- )
+ result = config.map_openai_context_management_to_anthropic(openai_format_with_instructions)
assert result is not None
assert result["edits"][0]["trigger"]["value"] == 150000
@@ -3905,9 +3795,7 @@ def test_map_openai_params_with_context_management():
config = AnthropicConfig()
# Test with OpenAI list format
- non_default_params = {
- "context_management": [{"type": "compaction", "compact_threshold": 200000}]
- }
+ non_default_params = {"context_management": [{"type": "compaction", "compact_threshold": 200000}]}
optional_params = {}
result = config.map_openai_params(
@@ -3944,10 +3832,7 @@ def test_map_openai_params_with_context_management():
)
assert "context_management" in result
- assert (
- result["context_management"]
- == non_default_params_anthropic["context_management"]
- )
+ assert result["context_management"] == non_default_params_anthropic["context_management"]
def test_cache_control_in_supported_params():
@@ -4058,10 +3943,7 @@ def test_compaction_block_empty_list_not_added():
# Verify compaction_blocks is not in provider_specific_fields when there are none
provider_fields = result.choices[0].message.provider_specific_fields
if provider_fields:
- assert (
- "compaction_blocks" not in provider_fields
- or provider_fields.get("compaction_blocks") is None
- )
+ assert "compaction_blocks" not in provider_fields or provider_fields.get("compaction_blocks") is None
def test_fast_mode_beta_header():
@@ -4110,9 +3992,7 @@ def test_fast_mode_usage_calculation():
"output_tokens": 500,
}
- usage = config.calculate_usage(
- usage_object=usage_object, reasoning_content=None, speed="fast"
- )
+ usage = config.calculate_usage(usage_object=usage_object, reasoning_content=None, speed="fast")
assert usage.prompt_tokens == 1000
assert usage.completion_tokens == 500
@@ -4133,9 +4013,7 @@ def test_fast_mode_cost_calculation():
base_completion = 0.025
with (
- patch(
- "litellm.llms.anthropic.cost_calculation.generic_cost_per_token"
- ) as mock_cost,
+ patch("litellm.llms.anthropic.cost_calculation.generic_cost_per_token") as mock_cost,
patch("litellm.get_model_info") as mock_info,
):
mock_cost.return_value = (base_prompt, base_completion)
@@ -4175,9 +4053,7 @@ def test_fast_mode_with_inference_geo():
base_completion = 0.025
with (
- patch(
- "litellm.llms.anthropic.cost_calculation.generic_cost_per_token"
- ) as mock_cost,
+ patch("litellm.llms.anthropic.cost_calculation.generic_cost_per_token") as mock_cost,
patch("litellm.get_model_info") as mock_info,
):
mock_cost.return_value = (base_prompt, base_completion)
@@ -4368,9 +4244,7 @@ def test_map_tool_helper_enforces_object_type_when_missing():
"name": "search_code",
"description": "Search for code patterns",
"parameters": {
- "properties": {
- "query": {"type": "string", "description": "Search query"}
- },
+ "properties": {"query": {"type": "string", "description": "Search query"}},
"required": ["query"],
},
},
@@ -4383,9 +4257,9 @@ def test_map_tool_helper_enforces_object_type_when_missing():
assert "properties" in result["input_schema"]
assert "query" in result["input_schema"]["properties"]
# Original parameters dict must not be modified in place
- assert (
- tool["function"]["parameters"] == original_params
- ), "parameters dict was mutated; _map_tool_helper should not modify caller data"
+ assert tool["function"]["parameters"] == original_params, (
+ "parameters dict was mutated; _map_tool_helper should not modify caller data"
+ )
def test_map_tool_helper_enforces_object_type_when_wrong_type():
@@ -4411,13 +4285,13 @@ def test_map_tool_helper_enforces_object_type_when_wrong_type():
result, _ = config._map_tool_helper(tool)
assert result is not None
assert result["input_schema"]["type"] == "object"
- assert (
- result["input_schema"].get("properties") == {}
- ), "properties should be injected as {} when schema has non-object type and no properties key"
+ assert result["input_schema"].get("properties") == {}, (
+ "properties should be injected as {} when schema has non-object type and no properties key"
+ )
# Original parameters dict must not be modified in place
- assert (
- tool["function"]["parameters"] == original_params
- ), "parameters dict was mutated; _map_tool_helper should not modify caller data"
+ assert tool["function"]["parameters"] == original_params, (
+ "parameters dict was mutated; _map_tool_helper should not modify caller data"
+ )
def test_map_tool_helper_preserves_valid_object_schema():
@@ -4484,12 +4358,8 @@ def test_extract_response_content_thinking_block_null_thinking():
{"type": "text", "text": "Hello"},
]
}
- text, _, thinking_blocks, _, _, _, _, _ = config.extract_response_content(
- completion_response_null
- )
- assert (
- thinking_blocks is not None
- ), "thinking blocks should not be None when thinking=null"
+ text, _, thinking_blocks, _, _, _, _, _ = config.extract_response_content(completion_response_null)
+ assert thinking_blocks is not None, "thinking blocks should not be None when thinking=null"
assert len(thinking_blocks) == 1
assert "Hello" in text
@@ -4500,12 +4370,8 @@ def test_extract_response_content_thinking_block_null_thinking():
{"type": "text", "text": "World"},
]
}
- text, _, thinking_blocks, _, _, _, _, _ = config.extract_response_content(
- completion_response_missing
- )
- assert (
- thinking_blocks is not None
- ), "thinking blocks should not be None when thinking key is absent"
+ text, _, thinking_blocks, _, _, _, _, _ = config.extract_response_content(completion_response_missing)
+ assert thinking_blocks is not None, "thinking blocks should not be None when thinking key is absent"
assert len(thinking_blocks) == 1
assert "World" in text
@@ -4516,9 +4382,7 @@ def test_extract_response_content_thinking_block_null_thinking():
{"type": "text", "text": "Done"},
]
}
- text, _, thinking_blocks, _, _, _, _, _ = config.extract_response_content(
- completion_response_text
- )
+ text, _, thinking_blocks, _, _, _, _, _ = config.extract_response_content(completion_response_text)
assert thinking_blocks is not None
assert len(thinking_blocks) == 1
assert thinking_blocks[0]["thinking"] == "Let me think..."
@@ -4577,12 +4441,8 @@ def test_advisor_beta_header_injected():
}
]
}
- result = config.update_headers_with_optional_anthropic_beta(
- headers, optional_params
- )
- assert ANTHROPIC_BETA_HEADER_VALUES.ADVISOR_TOOL_2026_03_01.value in result.get(
- "anthropic-beta", ""
- )
+ result = config.update_headers_with_optional_anthropic_beta(headers, optional_params)
+ assert ANTHROPIC_BETA_HEADER_VALUES.ADVISOR_TOOL_2026_03_01.value in result.get("anthropic-beta", "")
def test_advisor_beta_header_not_injected_without_tool():
@@ -4590,9 +4450,7 @@ def test_advisor_beta_header_not_injected_without_tool():
config = AnthropicConfig()
headers: dict = {}
optional_params: dict = {"tools": []}
- result = config.update_headers_with_optional_anthropic_beta(
- headers, optional_params
- )
+ result = config.update_headers_with_optional_anthropic_beta(headers, optional_params)
assert "advisor-tool-2026-03-01" not in result.get("anthropic-beta", "")
@@ -4619,9 +4477,7 @@ def test_advisor_tool_result_preserved_in_response():
{"type": "text", "text": "Here is the implementation."},
]
}
- text, _, _, _, tool_calls, _, tool_results, _ = config.extract_response_content(
- completion_response
- )
+ text, _, _, _, tool_calls, _, tool_results, _ = config.extract_response_content(completion_response)
assert "Consulting advisor." in text
assert "Here is the implementation." in text
# server_tool_use (advisor) should be a tool_call
@@ -4736,9 +4592,7 @@ def test_basic_sanitize_anthropic_tool_name_replaces_invalid_chars():
)
assert (
- _basic_sanitize_anthropic_tool_name(
- "github_openapi_mcp-actions/download-job-logs-for-workflow-run"
- )
+ _basic_sanitize_anthropic_tool_name("github_openapi_mcp-actions/download-job-logs-for-workflow-run")
== "github_openapi_mcp-actions_download-job-logs-for-workflow-run"
)
# other punctuation
@@ -4767,9 +4621,7 @@ def test_build_anthropic_tool_name_maps_no_collisions():
]
)
assert forward == {
- "actions/download-job-logs-for-workflow-run": (
- "actions_download-job-logs-for-workflow-run"
- ),
+ "actions/download-job-logs-for-workflow-run": ("actions_download-job-logs-for-workflow-run"),
"pulls/list-files": "pulls_list-files",
}
assert reverse == {v: k for k, v in forward.items()}
@@ -4820,9 +4672,7 @@ def test_build_anthropic_tool_name_maps_three_way_collision():
_build_anthropic_tool_name_maps,
)
- forward, reverse = _build_anthropic_tool_name_maps(
- ["foo_bar", "foo/bar", "foo.bar"]
- )
+ forward, reverse = _build_anthropic_tool_name_maps(["foo_bar", "foo/bar", "foo.bar"])
assert "foo_bar" not in forward # untouched
assert forward["foo/bar"] == "foo_bar_2"
assert forward["foo.bar"] == "foo_bar_3"
@@ -4895,16 +4745,13 @@ def test_map_openai_params_does_not_pollute_optional_params_with_internal_keys()
)
# No internal keys may appear in optional_params for ANY input.
for key in optional_params:
- assert not key.startswith(
- "_anthropic_tool_name"
- ), f"optional_params leaked internal key {key!r}: {optional_params}"
+ assert not key.startswith("_anthropic_tool_name"), (
+ f"optional_params leaked internal key {key!r}: {optional_params}"
+ )
# And no key starting with `_` either; optional_params should only
# contain documented Anthropic Messages API parameters.
for key in optional_params:
- assert not key.startswith("_"), (
- f"optional_params leaked underscore-prefixed key {key!r}: "
- f"{optional_params}"
- )
+ assert not key.startswith("_"), f"optional_params leaked underscore-prefixed key {key!r}: {optional_params}"
def test_map_openai_params_no_maps_when_all_names_already_valid():
@@ -4933,11 +4780,7 @@ def test_map_openai_params_no_maps_when_all_names_already_valid():
def test_rewrite_tool_names_in_messages_uses_forward_map():
config = AnthropicConfig()
- forward_map = {
- "actions/download-job-logs-for-workflow-run": (
- "actions_download-job-logs-for-workflow-run"
- )
- }
+ forward_map = {"actions/download-job-logs-for-workflow-run": ("actions_download-job-logs-for-workflow-run")}
messages = [
{"role": "user", "content": "go"},
{
@@ -4960,15 +4803,9 @@ def test_rewrite_tool_names_in_messages_uses_forward_map():
out = config._rewrite_tool_names_in_messages(messages, forward_map)
# input list must not be mutated
- assert (
- messages[1]["tool_calls"][0]["function"]["name"]
- == "actions/download-job-logs-for-workflow-run"
- )
+ assert messages[1]["tool_calls"][0]["function"]["name"] == "actions/download-job-logs-for-workflow-run"
# output rewritten according to forward map
- assert (
- out[1]["tool_calls"][0]["function"]["name"]
- == "actions_download-job-logs-for-workflow-run"
- )
+ assert out[1]["tool_calls"][0]["function"]["name"] == "actions_download-job-logs-for-workflow-run"
# non-tool-call messages pass through unchanged (same object)
assert out[0] is messages[0]
assert out[2] is messages[2]
@@ -5044,9 +4881,7 @@ def test_sanitize_tool_names_in_request_does_not_mutate_caller_tool_dicts():
caller_tools = [caller_tool]
optional_params: dict = {"tools": caller_tools}
- forward, reverse = config._sanitize_tool_names_in_request(
- optional_params=optional_params
- )
+ forward, reverse = config._sanitize_tool_names_in_request(optional_params=optional_params)
assert forward.get(original_name)
sanitized = forward[original_name]
@@ -5195,10 +5030,7 @@ def test_streaming_iterator_reverse_maps_tool_use_name():
parsed = iterator.chunk_parser(chunk=chunk)
tool_calls = parsed.choices[0].delta.tool_calls
assert tool_calls is not None and len(tool_calls) == 1
- assert (
- tool_calls[0]["function"]["name"]
- == "actions/download-job-logs-for-workflow-run"
- )
+ assert tool_calls[0]["function"]["name"] == "actions/download-job-logs-for-workflow-run"
def test_streaming_iterator_passthrough_when_name_not_in_map():
@@ -5294,9 +5126,9 @@ def test_transform_request_does_not_leak_internal_keys_into_body():
for tool in data.get("tools", []):
name = tool.get("name")
assert isinstance(name, str)
- assert _re.fullmatch(
- r"[a-zA-Z0-9_-]{1,128}", name
- ), f"sanitized tool name {name!r} still violates Anthropic regex"
+ assert _re.fullmatch(r"[a-zA-Z0-9_-]{1,128}", name), (
+ f"sanitized tool name {name!r} still violates Anthropic regex"
+ )
# Sent name for the bad tool is the disambiguated form, valid name passes through.
sent_names = {t["name"] for t in data["tools"]}
@@ -5432,9 +5264,7 @@ def test_transform_request_rewrites_tool_names_in_history():
for block in content:
if isinstance(block, dict) and block.get("type") == "tool_use":
tool_use_names.append(block.get("name"))
- assert (
- tool_use_names
- ), "expected at least one tool_use block in transformed messages"
+ assert tool_use_names, "expected at least one tool_use block in transformed messages"
for name in tool_use_names:
assert name == "actions_download-job-logs-for-workflow-run", (
f"history tool_use.name {name!r} not rewritten -- Anthropic will "
@@ -5458,19 +5288,12 @@ def test_sanitize_tool_names_in_request_skips_hosted_tools():
}
forward, reverse = AnthropicConfig._sanitize_tool_names_in_request(optional_params)
# Only the custom tool was rewritten.
- assert forward == {
- "actions/download-job-logs-for-workflow-run": "actions_download-job-logs-for-workflow-run"
- }
- assert reverse == {
- "actions_download-job-logs-for-workflow-run": "actions/download-job-logs-for-workflow-run"
- }
+ assert forward == {"actions/download-job-logs-for-workflow-run": "actions_download-job-logs-for-workflow-run"}
+ assert reverse == {"actions_download-job-logs-for-workflow-run": "actions/download-job-logs-for-workflow-run"}
# Hosted tool's name unchanged.
assert optional_params["tools"][0]["name"] == "web_search"
# Custom tool's name updated in place.
- assert (
- optional_params["tools"][1]["name"]
- == "actions_download-job-logs-for-workflow-run"
- )
+ assert optional_params["tools"][1]["name"] == "actions_download-job-logs-for-workflow-run"
def test_sanitize_tool_names_in_request_no_tools_is_noop():
@@ -5704,9 +5527,7 @@ def test_translate_system_message_keeps_billing_header_for_first_party_anthropic
assert config.should_strip_billing_metadata() is False
result = config.translate_system_message(
- messages=_system_with_billing_header(
- "You are Claude Code, Anthropic's official CLI for Claude."
- )
+ messages=_system_with_billing_header("You are Claude Code, Anthropic's official CLI for Claude.")
)
texts = [block["text"] for block in result]
@@ -5722,9 +5543,7 @@ def test_translate_system_message_strips_billing_header_for_bedrock():
config = BedrockClaudePlatformConfig()
assert config.should_strip_billing_metadata() is True
- result = config.translate_system_message(
- messages=_system_with_billing_header("real system prompt")
- )
+ result = config.translate_system_message(messages=_system_with_billing_header("real system prompt"))
texts = [block["text"] for block in result]
assert all(not t.startswith("x-anthropic-billing-header:") for t in texts)
@@ -5790,9 +5609,7 @@ def test_translate_system_message_strips_billing_header_for_bedrock_invoke():
config = AmazonAnthropicClaudeConfig()
assert config.should_strip_billing_metadata() is True
- result = config.translate_system_message(
- messages=_system_with_billing_header("real system prompt")
- )
+ result = config.translate_system_message(messages=_system_with_billing_header("real system prompt"))
texts = [block["text"] for block in result]
assert all(not t.startswith("x-anthropic-billing-header:") for t in texts)
@@ -5846,9 +5663,7 @@ def test_translate_system_message_strips_billing_header_for_bedrock_invoke():
),
],
)
-def test_should_strip_billing_metadata_by_provider(
- module_path, class_name, expected_strip
-):
+def test_should_strip_billing_metadata_by_provider(module_path, class_name, expected_strip):
import importlib
config_cls = getattr(importlib.import_module(module_path), class_name)
@@ -6020,12 +5835,8 @@ def test_sampling_param_gating_driven_by_model_map_flag(monkeypatch):
"""The drop/raise decision must come from ``supports_sampling_params`` in
the model map, not just name matching: a flagged entry gates a model whose
name says nothing, and an explicit ``true`` overrides the name fallback."""
- monkeypatch.setitem(
- litellm.model_cost, "claude-zeta-9", {"supports_sampling_params": False}
- )
- monkeypatch.setitem(
- litellm.model_cost, "claude-fable-5-test", {"supports_sampling_params": True}
- )
+ monkeypatch.setitem(litellm.model_cost, "claude-zeta-9", {"supports_sampling_params": False})
+ monkeypatch.setitem(litellm.model_cost, "claude-fable-5-test", {"supports_sampling_params": True})
config = AnthropicConfig()
flagged_off = config.map_openai_params(
@@ -6144,9 +5955,7 @@ def test_is_anthropic_usage_object_rejects_responses_api_usage():
("claude-sonnet-4-5-20250929", False),
],
)
-def test_disabled_thinking_omitted_only_for_always_on_models(
- local_model_cost_map, model, expected_dropped
-):
+def test_disabled_thinking_omitted_only_for_always_on_models(local_model_cost_map, model, expected_dropped):
"""``thinking={"type": "disabled"}`` is omitted for always-on-thinking models
(Fable/Mythos, which 400 on it: the API remedy is to omit the param) and is
forwarded verbatim for every model that accepts it."""
diff --git a/tests/test_litellm/llms/anthropic/chat/test_code_interpreter_results_extraction.py b/tests/test_litellm/llms/anthropic/chat/test_code_interpreter_results_extraction.py
index 60e45c9b8ce..99266f92c56 100644
--- a/tests/test_litellm/llms/anthropic/chat/test_code_interpreter_results_extraction.py
+++ b/tests/test_litellm/llms/anthropic/chat/test_code_interpreter_results_extraction.py
@@ -136,22 +136,14 @@ def test_in_place_substitution_preserves_ordering():
responses_output = [msg_item, fc_exec1, fc_regular, fc_exec2]
# Apply the same logic as _transform_chat_completion_choices_to_responses_output
- tool_result_items = (
- LiteLLMCompletionResponsesConfig._extract_tool_result_output_items(resp)
- )
+ tool_result_items = LiteLLMCompletionResponsesConfig._extract_tool_result_output_items(resp)
if tool_result_items:
- result_by_id = {
- (item.get("id") if isinstance(item, dict) else item.id): item
- for item in tool_result_items
- }
+ result_by_id = {(item.get("id") if isinstance(item, dict) else item.id): item for item in tool_result_items}
replaced_ids = set(result_by_id.keys())
responses_output = [
(
result_by_id[getattr(item, "call_id", None)]
- if (
- getattr(item, "type", None) == "function_call"
- and getattr(item, "call_id", None) in replaced_ids
- )
+ if (getattr(item, "type", None) == "function_call" and getattr(item, "call_id", None) in replaced_ids)
else item
)
for item in responses_output
@@ -255,9 +247,7 @@ def test_end_to_end_streaming_chunks_to_code_interpreter_output():
assert code_results[0]["code"] == "echo e2e_test"
# Step 3: Extract via _extract_tool_result_output_items (Responses API layer)
- tool_result_items = (
- LiteLLMCompletionResponsesConfig._extract_tool_result_output_items(assembled)
- )
+ tool_result_items = LiteLLMCompletionResponsesConfig._extract_tool_result_output_items(assembled)
assert len(tool_result_items) == 1
item = tool_result_items[0]
# Items are reconstructed as Pydantic OutputCodeInterpreterCall objects
diff --git a/tests/test_litellm/llms/anthropic/experimental_pass_through/adapters/test_anthropic_experimental_pass_through_adapters_transformation.py b/tests/test_litellm/llms/anthropic/experimental_pass_through/adapters/test_anthropic_experimental_pass_through_adapters_transformation.py
index e4dacc308dc..287794712ea 100644
--- a/tests/test_litellm/llms/anthropic/experimental_pass_through/adapters/test_anthropic_experimental_pass_through_adapters_transformation.py
+++ b/tests/test_litellm/llms/anthropic/experimental_pass_through/adapters/test_anthropic_experimental_pass_through_adapters_transformation.py
@@ -5,7 +5,6 @@ import pytest
import litellm
-
from litellm.litellm_core_utils.prompt_templates.common_utils import (
TOOL_RESULT_IMAGE_PLACEHOLDER,
)
@@ -50,9 +49,7 @@ def test_translate_streaming_openai_chunk_to_anthropic_content_block():
tool_calls=[
ChatCompletionDeltaToolCall(
id="call_d581d130-e234-4315-94e8-27e7ff7c4e55",
- function=Function(
- arguments='{"location": "Boston"}', name="get_weather"
- ),
+ function=Function(arguments='{"location": "Boston"}', name="get_weather"),
type="function",
index=0,
)
@@ -66,9 +63,7 @@ def test_translate_streaming_openai_chunk_to_anthropic_content_block():
(
block_type,
content_block_start,
- ) = LiteLLMAnthropicMessagesAdapter()._translate_streaming_openai_chunk_to_anthropic_content_block(
- choices=choices
- )
+ ) = LiteLLMAnthropicMessagesAdapter()._translate_streaming_openai_chunk_to_anthropic_content_block(choices=choices)
print(content_block_start)
@@ -98,9 +93,7 @@ def test_translate_streaming_openai_chunk_strips_gemini_thought_from_tool_call_i
tool_calls=[
ChatCompletionDeltaToolCall(
id=combined,
- function=Function(
- arguments='{"a": 17, "b": 25}', name="add_numbers"
- ),
+ function=Function(arguments='{"a": 17, "b": 25}', name="add_numbers"),
type="function",
index=0,
)
@@ -114,9 +107,7 @@ def test_translate_streaming_openai_chunk_strips_gemini_thought_from_tool_call_i
(
block_type,
content_block_start,
- ) = LiteLLMAnthropicMessagesAdapter()._translate_streaming_openai_chunk_to_anthropic_content_block(
- choices=choices
- )
+ ) = LiteLLMAnthropicMessagesAdapter()._translate_streaming_openai_chunk_to_anthropic_content_block(choices=choices)
assert block_type == "tool_use"
assert content_block_start["id"] == base
@@ -161,9 +152,7 @@ def test_translate_streaming_openai_chunk_to_anthropic_thinking_content_block():
(
block_type,
content_block_start,
- ) = LiteLLMAnthropicMessagesAdapter()._translate_streaming_openai_chunk_to_anthropic_content_block(
- choices=choices
- )
+ ) = LiteLLMAnthropicMessagesAdapter()._translate_streaming_openai_chunk_to_anthropic_content_block(choices=choices)
assert block_type == "thinking"
assert content_block_start == {
@@ -199,9 +188,7 @@ def test_translate_streaming_openai_chunk_to_anthropic_reasoning_content_only_co
(
block_type,
content_block_start,
- ) = LiteLLMAnthropicMessagesAdapter()._translate_streaming_openai_chunk_to_anthropic_content_block(
- choices=choices
- )
+ ) = LiteLLMAnthropicMessagesAdapter()._translate_streaming_openai_chunk_to_anthropic_content_block(choices=choices)
assert block_type == "thinking"
assert content_block_start == {
@@ -247,9 +234,7 @@ def test_translate_streaming_openai_chunk_to_anthropic_thinking_signature_block(
(
block_type,
content_block_start,
- ) = LiteLLMAnthropicMessagesAdapter()._translate_streaming_openai_chunk_to_anthropic_content_block(
- choices=choices
- )
+ ) = LiteLLMAnthropicMessagesAdapter()._translate_streaming_openai_chunk_to_anthropic_content_block(choices=choices)
assert block_type == "thinking"
assert content_block_start == {
@@ -302,9 +287,7 @@ def test_translate_streaming_openai_chunk_to_anthropic_content_block_thinking_an
(
block_type,
content_block_start,
- ) = LiteLLMAnthropicMessagesAdapter()._translate_streaming_openai_chunk_to_anthropic_content_block(
- choices=choices
- )
+ ) = LiteLLMAnthropicMessagesAdapter()._translate_streaming_openai_chunk_to_anthropic_content_block(choices=choices)
assert block_type == "thinking"
@@ -347,10 +330,7 @@ def test_translate_anthropic_messages_to_openai_thinking_blocks():
assert "thinking_blocks" in result[1]
assert len(result[1]["thinking_blocks"]) == 2
assert result[1]["thinking_blocks"][0]["type"] == "thinking"
- assert (
- result[1]["thinking_blocks"][0]["thinking"]
- == "I will call the get_weather tool."
- )
+ assert result[1]["thinking_blocks"][0]["thinking"] == "I will call the get_weather tool."
assert result[1]["thinking_blocks"][0]["signature"] == "sigsig"
assert result[1]["thinking_blocks"][1]["type"] == "redacted_thinking"
assert result[1]["thinking_blocks"][1]["data"] == "REDACTED"
@@ -411,9 +391,7 @@ def test_translate_anthropic_messages_to_openai_tool_message_placement():
assert tool_message_idx is not None, "Tool message not found"
assert user_message_idx is not None, "User message not found"
- assert (
- tool_message_idx < user_message_idx
- ), "Tool message should be placed before user message"
+ assert tool_message_idx < user_message_idx, "Tool message should be placed before user message"
@pytest.mark.parametrize(
@@ -634,9 +612,7 @@ def test_translate_anthropic_to_openai_orders_top_level_and_midturn_system():
]
-def _translate_with_metadata(
- model: str, metadata: dict[str, Any], custom_llm_provider: str | None
-) -> dict[str, Any]:
+def _translate_with_metadata(model: str, metadata: dict[str, Any], custom_llm_provider: str | None) -> dict[str, Any]:
openai_request, _ = LiteLLMAnthropicMessagesAdapter().translate_anthropic_to_openai(
anthropic_message_request={
"model": model,
@@ -688,9 +664,7 @@ def test_translate_anthropic_to_openai_skips_prompt_cache_key_when_provider_lack
def test_translate_anthropic_to_openai_skips_prompt_cache_key_for_chained_litellm_proxy():
- assert "prompt_cache_key" in litellm.get_supported_openai_params(
- model="xai", custom_llm_provider="litellm_proxy"
- )
+ assert "prompt_cache_key" in litellm.get_supported_openai_params(model="xai", custom_llm_provider="litellm_proxy")
openai_request = _translate_with_metadata("litellm_proxy/xai", {"user_id": "session-abc"}, "litellm_proxy")
assert openai_request["user"] == "session-abc"
assert "prompt_cache_key" not in openai_request
@@ -735,7 +709,8 @@ def test_translate_openai_content_to_anthropic_empty_function_arguments():
id="call_empty_args",
type="function",
function=Function(
- name="test_function", arguments="" # empty arguments string
+ name="test_function",
+ arguments="", # empty arguments string
),
)
],
@@ -750,9 +725,7 @@ def test_translate_openai_content_to_anthropic_empty_function_arguments():
assert result[0]["type"] == "tool_use"
assert result[0]["id"] == "call_empty_args"
assert result[0]["name"] == "test_function"
- assert (
- result[0]["input"] == {}
- ), "Empty function arguments should result in empty dict"
+ assert result[0]["input"] == {}, "Empty function arguments should result in empty dict"
def test_translate_openai_content_to_anthropic_text_and_tool_calls():
@@ -872,9 +845,7 @@ def test_translate_openai_response_to_anthropic_text_and_tool_calls():
ChatCompletionAssistantToolCall(
id="call_tool_combo",
type="function",
- function=Function(
- name="get_weather", arguments='{"location": "Paris"}'
- ),
+ function=Function(name="get_weather", arguments='{"location": "Paris"}'),
)
],
),
@@ -884,9 +855,7 @@ def test_translate_openai_response_to_anthropic_text_and_tool_calls():
)
adapter = LiteLLMAnthropicMessagesAdapter()
- anthropic_response = adapter.translate_openai_response_to_anthropic(
- response=openai_response
- )
+ anthropic_response = adapter.translate_openai_response_to_anthropic(response=openai_response)
anthropic_content = anthropic_response.get("content")
assert anthropic_content is not None
@@ -927,9 +896,7 @@ def test_translate_streaming_openai_chunk_to_anthropic_with_partial_json():
(
type_of_content,
content_block_delta,
- ) = LiteLLMAnthropicMessagesAdapter()._translate_streaming_openai_chunk_to_anthropic(
- choices=choices
- )
+ ) = LiteLLMAnthropicMessagesAdapter()._translate_streaming_openai_chunk_to_anthropic(choices=choices)
print("Type of content:", type_of_content)
print("Content block delta:", content_block_delta)
@@ -1004,9 +971,7 @@ def test_translate_streaming_openai_chunk_to_anthropic_thinking_delta():
(
type_of_content,
content_block_delta,
- ) = LiteLLMAnthropicMessagesAdapter()._translate_streaming_openai_chunk_to_anthropic(
- choices=choices
- )
+ ) = LiteLLMAnthropicMessagesAdapter()._translate_streaming_openai_chunk_to_anthropic(choices=choices)
assert type_of_content == "thinking_delta"
assert content_block_delta["type"] == "thinking_delta"
@@ -1049,9 +1014,7 @@ def test_translate_streaming_openai_chunk_to_anthropic_with_thinking():
(
type_of_content,
content_block_delta,
- ) = LiteLLMAnthropicMessagesAdapter()._translate_streaming_openai_chunk_to_anthropic(
- choices=choices
- )
+ ) = LiteLLMAnthropicMessagesAdapter()._translate_streaming_openai_chunk_to_anthropic(choices=choices)
assert type_of_content == "signature_delta"
assert content_block_delta["type"] == "signature_delta"
@@ -1115,9 +1078,7 @@ def test_translate_streaming_openai_chunk_to_anthropic_emits_signature_when_thin
(
block_type,
content_block_start,
- ) = adapter._translate_streaming_openai_chunk_to_anthropic_content_block(
- choices=choices
- )
+ ) = adapter._translate_streaming_openai_chunk_to_anthropic_content_block(choices=choices)
assert block_type == "thinking"
@@ -1157,9 +1118,7 @@ def test_translate_anthropic_messages_to_openai_user_message_with_base64_image()
# Check image content
assert result[0]["content"][1]["type"] == "image_url"
assert "image_url" in result[0]["content"][1]
- assert result[0]["content"][1]["image_url"]["url"].startswith(
- "data:image/png;base64,"
- )
+ assert result[0]["content"][1]["image_url"]["url"].startswith("data:image/png;base64,")
assert (
"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg=="
in result[0]["content"][1]["image_url"]["url"]
@@ -1197,18 +1156,14 @@ def test_translate_anthropic_messages_to_openai_user_message_with_url_image():
# Check image content
assert result[0]["content"][1]["type"] == "image_url"
assert "image_url" in result[0]["content"][1]
- assert (
- result[0]["content"][1]["image_url"]["url"] == "https://example.com/forest.jpg"
- )
+ assert result[0]["content"][1]["image_url"]["url"] == "https://example.com/forest.jpg"
def test_translate_anthropic_messages_to_openai_tool_result_with_base64_image():
"""Test that base64 images in tool results are correctly translated to OpenAI format."""
anthropic_messages = [
- AnthropicMessagesUserMessageParam(
- role="user", content=[{"type": "text", "text": "Take a screenshot"}]
- ),
+ AnthropicMessagesUserMessageParam(role="user", content=[{"type": "text", "text": "Take a screenshot"}]),
AnthopicMessagesAssistantMessageParam(
role="assistant",
content=[
@@ -1360,9 +1315,7 @@ def test_translate_anthropic_messages_to_openai_mixed_content_with_image():
# Check first image (base64)
assert result[0]["content"][1]["type"] == "image_url"
- assert result[0]["content"][1]["image_url"]["url"].startswith(
- "data:image/png;base64,"
- )
+ assert result[0]["content"][1]["image_url"]["url"].startswith("data:image/png;base64,")
# Check middle text
assert result[0]["content"][2]["type"] == "text"
@@ -1370,9 +1323,7 @@ def test_translate_anthropic_messages_to_openai_mixed_content_with_image():
# Check second image (URL)
assert result[0]["content"][3]["type"] == "image_url"
- assert (
- result[0]["content"][3]["image_url"]["url"] == "https://example.com/image2.jpg"
- )
+ assert result[0]["content"][3]["image_url"]["url"] == "https://example.com/image2.jpg"
# Check final text
assert result[0]["content"][4]["type"] == "text"
@@ -1418,10 +1369,7 @@ def test_translate_anthropic_messages_to_openai_tool_use_with_signature():
assert tool_call["id"] == "call_386f67af31f9415781bc35071405"
assert "function" in tool_call
assert "provider_specific_fields" in tool_call["function"]
- assert (
- tool_call["function"]["provider_specific_fields"]["thought_signature"]
- == test_signature
- )
+ assert tool_call["function"]["provider_specific_fields"]["thought_signature"] == test_signature
def test_translate_anthropic_messages_to_openai_tool_result_with_multiple_content_items():
@@ -1479,9 +1427,7 @@ def test_translate_anthropic_messages_to_openai_tool_result_with_multiple_conten
result = adapter.translate_anthropic_messages_to_openai(messages=anthropic_messages)
# Count how many tool messages have the same tool_call_id
- tool_messages = [
- msg for msg in result if isinstance(msg, dict) and msg.get("role") == "tool"
- ]
+ tool_messages = [msg for msg in result if isinstance(msg, dict) and msg.get("role") == "tool"]
tool_call_ids = [msg.get("tool_call_id") for msg in tool_messages]
# The critical assertion: each tool_call_id should appear only ONCE
@@ -1497,12 +1443,8 @@ def test_translate_anthropic_messages_to_openai_tool_result_with_multiple_conten
# The content should be a list with all items combined
tool_message = tool_messages[0]
assert tool_message["tool_call_id"] == "toolu_016hYHBkTf4JDF3p22UoYk5C"
- assert isinstance(
- tool_message["content"], list
- ), "Multiple content items should be combined into a list"
- assert (
- len(tool_message["content"]) == 3
- ), f"Expected 3 content items, got {len(tool_message['content'])}"
+ assert isinstance(tool_message["content"], list), "Multiple content items should be combined into a list"
+ assert len(tool_message["content"]) == 3, f"Expected 3 content items, got {len(tool_message['content'])}"
# Verify content types
assert tool_message["content"][0]["type"] == "text"
@@ -1551,17 +1493,14 @@ def test_translate_anthropic_messages_to_openai_tool_result_single_item_backward
adapter = LiteLLMAnthropicMessagesAdapter()
result = adapter.translate_anthropic_messages_to_openai(messages=anthropic_messages)
- tool_messages = [
- msg for msg in result if isinstance(msg, dict) and msg.get("role") == "tool"
- ]
+ tool_messages = [msg for msg in result if isinstance(msg, dict) and msg.get("role") == "tool"]
assert len(tool_messages) == 1
tool_message = tool_messages[0]
# Single item should be a string for backward compatibility
assert isinstance(tool_message["content"], str), (
- f"Single content item should be a string for backward compatibility, "
- f"got {type(tool_message['content'])}"
+ f"Single content item should be a string for backward compatibility, got {type(tool_message['content'])}"
)
assert tool_message["content"] == "72°F and sunny"
@@ -1610,9 +1549,7 @@ def test_streaming_chunk_with_both_text_and_tool_calls_issue_18238():
(
block_type,
content_block_start,
- ) = adapter._translate_streaming_openai_chunk_to_anthropic_content_block(
- choices=choices
- )
+ ) = adapter._translate_streaming_openai_chunk_to_anthropic_content_block(choices=choices)
assert block_type == "tool_use"
assert content_block_start["name"] == "Bash"
@@ -1656,9 +1593,7 @@ def test_streaming_chunk_with_text_and_empty_tool_calls_returns_text_delta():
(
block_type,
content_block_start,
- ) = adapter._translate_streaming_openai_chunk_to_anthropic_content_block(
- choices=choices
- )
+ ) = adapter._translate_streaming_openai_chunk_to_anthropic_content_block(choices=choices)
assert block_type == "text"
assert content_block_start == {"type": "text", "text": ""}
@@ -1669,15 +1604,12 @@ def test_streaming_chunk_with_text_and_empty_tool_calls_returns_text_delta():
# ============================================================================
# Model constant for cache control tests
-CACHE_CONTROL_BEDROCK_CONVERSE_MODEL = (
- "bedrock/converse/global.anthropic.claude-opus-4-5-20251101-v1:0"
-)
+CACHE_CONTROL_BEDROCK_CONVERSE_MODEL = "bedrock/converse/global.anthropic.claude-opus-4-5-20251101-v1:0"
CACHE_CONTROL_NON_ANTHROPIC_MODEL = "gpt-4"
# Bedrock Application Inference Profile ARN: the string contains neither
# "anthropic" nor "claude", so the model can only be recognized via its ARN shape
CACHE_CONTROL_BEDROCK_ARN_MODEL = (
- "bedrock/converse/arn:aws:bedrock:us-east-1:123456789012:"
- "application-inference-profile/abcdef123456"
+ "bedrock/converse/arn:aws:bedrock:us-east-1:123456789012:application-inference-profile/abcdef123456"
)
@@ -1693,9 +1625,7 @@ def test_should_add_cache_control_for_anthropic_model():
"vertex_ai/claude-3-sonnet@20240229",
]:
target = {}
- adapter._add_cache_control_if_applicable(
- {"cache_control": cache_control}, target, model
- )
+ adapter._add_cache_control_if_applicable({"cache_control": cache_control}, target, model)
assert "cache_control" in target
assert target["cache_control"] == cache_control
@@ -1711,9 +1641,7 @@ def test_should_not_add_cache_control_for_non_anthropic_model():
"gemini-pro",
]:
target = {}
- adapter._add_cache_control_if_applicable(
- {"cache_control": cache_control}, target, model
- )
+ adapter._add_cache_control_if_applicable({"cache_control": cache_control}, target, model)
assert "cache_control" not in target
@@ -1728,9 +1656,7 @@ def test_should_not_add_cache_control_when_none():
{},
]:
target = {}
- adapter._add_cache_control_if_applicable(
- source, target, CACHE_CONTROL_BEDROCK_CONVERSE_MODEL
- )
+ adapter._add_cache_control_if_applicable(source, target, CACHE_CONTROL_BEDROCK_CONVERSE_MODEL)
assert "cache_control" not in target
@@ -1741,9 +1667,7 @@ def test_should_not_add_cache_control_when_model_none():
for model in [None, ""]:
target = {}
- adapter._add_cache_control_if_applicable(
- {"cache_control": cache_control}, target, model
- )
+ adapter._add_cache_control_if_applicable({"cache_control": cache_control}, target, model)
assert "cache_control" not in target
@@ -1849,12 +1773,7 @@ def test_cache_control_fix_does_not_broaden_claude_detection():
make is_anthropic_claude_model treat ARN profiles as Claude, which would route
thinking params through unmodified and break non-Claude Bedrock profiles.
"""
- assert (
- LiteLLMAnthropicMessagesAdapter.is_anthropic_claude_model(
- CACHE_CONTROL_BEDROCK_ARN_MODEL
- )
- is False
- )
+ assert LiteLLMAnthropicMessagesAdapter.is_anthropic_claude_model(CACHE_CONTROL_BEDROCK_ARN_MODEL) is False
def test_thinking_preserved_for_bedrock_arn_inference_profile():
@@ -2322,9 +2241,7 @@ def test_translate_streaming_openai_chunk_to_anthropic_reasoning_content_without
(
type_of_content,
content_block_delta,
- ) = LiteLLMAnthropicMessagesAdapter()._translate_streaming_openai_chunk_to_anthropic(
- choices=choices
- )
+ ) = LiteLLMAnthropicMessagesAdapter()._translate_streaming_openai_chunk_to_anthropic(choices=choices)
assert type_of_content == "thinking_delta"
assert content_block_delta["type"] == "thinking_delta"
@@ -2356,9 +2273,7 @@ def test_translate_openai_response_to_anthropic_with_reasoning_content_only():
)
adapter = LiteLLMAnthropicMessagesAdapter()
- anthropic_response = adapter.translate_openai_response_to_anthropic(
- response=openai_response
- )
+ anthropic_response = adapter.translate_openai_response_to_anthropic(response=openai_response)
anthropic_content = anthropic_response.get("content")
assert anthropic_content is not None
@@ -2371,9 +2286,7 @@ def test_translate_openai_response_to_anthropic_with_reasoning_content_only():
# Second block should be text
assert anthropic_content[1]["type"] == "text"
- assert (
- anthropic_content[1]["text"] == 'There are **3** "r"s in the word strawberry.'
- )
+ assert anthropic_content[1]["text"] == 'There are **3** "r"s in the word strawberry.'
assert anthropic_response.get("stop_reason") == "end_turn"
@@ -2425,9 +2338,7 @@ def test_truncate_tool_name_deterministic():
def test_truncate_tool_name_avoids_collisions():
"""Similar long names should produce different truncated names."""
name1 = "process_user_data_with_validation_and_error_handling_for_production_environment"
- name2 = (
- "process_user_data_with_validation_and_error_handling_for_staging_environment"
- )
+ name2 = "process_user_data_with_validation_and_error_handling_for_staging_environment"
result1 = truncate_tool_name(name1)
result2 = truncate_tool_name(name2)
@@ -2447,9 +2358,7 @@ def test_create_tool_name_mapping_no_long_names():
def test_create_tool_name_mapping_with_long_names():
"""Mapping should contain entries for truncated names."""
- long_name = (
- "a_very_long_tool_name_that_exceeds_the_64_character_limit_imposed_by_openai"
- )
+ long_name = "a_very_long_tool_name_that_exceeds_the_64_character_limit_imposed_by_openai"
tools = [
{"name": "short_name"},
{"name": long_name},
@@ -2474,9 +2383,7 @@ def test_translate_anthropic_tools_with_long_names():
]
adapter = LiteLLMAnthropicMessagesAdapter()
- result, tool_name_mapping = adapter.translate_anthropic_tools_to_openai(
- tools=tools, model="gpt-4"
- )
+ result, tool_name_mapping = adapter.translate_anthropic_tools_to_openai(tools=tools, model="gpt-4")
assert len(result) == 1
# The tool name should be truncated
@@ -2498,9 +2405,7 @@ def test_translate_anthropic_tools_mixed_names():
]
adapter = LiteLLMAnthropicMessagesAdapter()
- result, tool_name_mapping = adapter.translate_anthropic_tools_to_openai(
- tools=tools, model="gpt-4"
- )
+ result, tool_name_mapping = adapter.translate_anthropic_tools_to_openai(tools=tools, model="gpt-4")
assert len(result) == 2
# Short name unchanged
@@ -2514,9 +2419,7 @@ def test_translate_anthropic_tools_mixed_names():
def test_translate_openai_response_restores_tool_names():
"""Tool names in responses should be restored to original."""
- original_name = (
- "a_very_long_tool_name_that_needs_truncation_for_openai_api_compatibility"
- )
+ original_name = "a_very_long_tool_name_that_needs_truncation_for_openai_api_compatibility"
truncated_name = truncate_tool_name(original_name)
tool_name_mapping = {truncated_name: original_name}
@@ -2548,9 +2451,7 @@ def test_translate_openai_response_restores_tool_names():
)
adapter = LiteLLMAnthropicMessagesAdapter()
- result = adapter.translate_openai_response_to_anthropic(
- response=response, tool_name_mapping=tool_name_mapping
- )
+ result = adapter.translate_openai_response_to_anthropic(response=response, tool_name_mapping=tool_name_mapping)
# Find the tool_use block in the response
tool_use_blocks = [c for c in result["content"] if c.get("type") == "tool_use"]
@@ -2716,9 +2617,7 @@ def test_translate_openai_usage_to_anthropic_cache_tokens_from_dict_details_with
"cache_write_tokens": 20.0,
}
- anthropic_usage = LiteLLMAnthropicMessagesAdapter._translate_openai_usage_to_anthropic_usage_delta(
- usage
- )
+ anthropic_usage = LiteLLMAnthropicMessagesAdapter._translate_openai_usage_to_anthropic_usage_delta(usage)
assert anthropic_usage["input_tokens"] == 70
assert anthropic_usage["output_tokens"] == 50
@@ -2737,9 +2636,7 @@ def test_translate_openai_usage_to_anthropic_ignores_fractional_cache_tokens():
"cache_creation_tokens": 20.25,
}
- anthropic_usage = LiteLLMAnthropicMessagesAdapter._translate_openai_usage_to_anthropic_usage_delta(
- usage
- )
+ anthropic_usage = LiteLLMAnthropicMessagesAdapter._translate_openai_usage_to_anthropic_usage_delta(usage)
assert anthropic_usage["input_tokens"] == 120
assert anthropic_usage["output_tokens"] == 50
@@ -2756,9 +2653,7 @@ def test_translate_openai_usage_to_anthropic_ignores_bool_cache_tokens():
usage.cache_read_input_tokens = True
usage.cache_creation_input_tokens = True
- anthropic_usage = LiteLLMAnthropicMessagesAdapter._translate_openai_usage_to_anthropic_usage_delta(
- usage
- )
+ anthropic_usage = LiteLLMAnthropicMessagesAdapter._translate_openai_usage_to_anthropic_usage_delta(usage)
assert anthropic_usage["input_tokens"] == 120
assert anthropic_usage["output_tokens"] == 50
@@ -2977,9 +2872,7 @@ def test_translate_streaming_openai_response_to_anthropic_cache_tokens_with_appl
assert message_delta["usage"]["output_tokens"] == 50
assert message_delta["usage"]["cache_read_input_tokens"] == 30
assert message_delta["usage"]["cache_creation_input_tokens"] == 20
- assert message_delta["context_management"]["applied_edits"][0]["type"] == (
- "compact_20260112"
- )
+ assert message_delta["context_management"]["applied_edits"][0]["type"] == ("compact_20260112")
# =====================================================================
@@ -3154,15 +3047,8 @@ class TestTranslateAnthropicOutputFormatToOpenAI:
assert schema["required"] == ["user"]
assert schema["properties"]["user"]["additionalProperties"] is False
assert schema["properties"]["user"]["required"] == ["name", "address"]
- assert (
- schema["properties"]["user"]["properties"]["address"][
- "additionalProperties"
- ]
- is False
- )
- assert schema["properties"]["user"]["properties"]["address"]["required"] == [
- "city"
- ]
+ assert schema["properties"]["user"]["properties"]["address"]["additionalProperties"] is False
+ assert schema["properties"]["user"]["properties"]["address"]["required"] == ["city"]
def test_array_items_object_adds_additional_properties_false(self):
output_format = {
@@ -3237,19 +3123,9 @@ class TestTranslateAnthropicOutputFormatToOpenAI:
assert sorted(schema["required"]) == ["age", "email", "name"]
def test_invalid_output_format_returns_none(self):
- assert (
- self.adapter.translate_anthropic_output_format_to_openai("invalid") is None
- )
- assert (
- self.adapter.translate_anthropic_output_format_to_openai({"type": "text"})
- is None
- )
- assert (
- self.adapter.translate_anthropic_output_format_to_openai(
- {"type": "json_schema"}
- )
- is None
- )
+ assert self.adapter.translate_anthropic_output_format_to_openai("invalid") is None
+ assert self.adapter.translate_anthropic_output_format_to_openai({"type": "text"}) is None
+ assert self.adapter.translate_anthropic_output_format_to_openai({"type": "json_schema"}) is None
class TestAnthropicStreamWrapperToolArgs:
@@ -3453,9 +3329,7 @@ def test_translate_openai_response_to_anthropic_with_polyfill_compaction_block()
)
response = _make_simple_openai_response(text="Hello after compaction.")
adapter = LiteLLMAnthropicMessagesAdapter()
- result = adapter.translate_openai_response_to_anthropic(
- response=response, polyfill_result=polyfill
- )
+ result = adapter.translate_openai_response_to_anthropic(response=response, polyfill_result=polyfill)
content = result.get("content")
assert content is not None
@@ -3487,9 +3361,7 @@ def test_translate_openai_response_to_anthropic_with_polyfill_iterations_usage()
)
response = _make_simple_openai_response(prompt_tokens=100, completion_tokens=30)
adapter = LiteLLMAnthropicMessagesAdapter()
- result = adapter.translate_openai_response_to_anthropic(
- response=response, polyfill_result=polyfill
- )
+ result = adapter.translate_openai_response_to_anthropic(response=response, polyfill_result=polyfill)
usage = result.get("usage")
assert usage is not None
@@ -3544,13 +3416,9 @@ def test_translate_openai_response_to_anthropic_with_polyfill_both_compaction_an
{"type": "compaction", "input_tokens": 300, "output_tokens": 75},
],
)
- response = _make_simple_openai_response(
- text="After compaction.", prompt_tokens=120, completion_tokens=40
- )
+ response = _make_simple_openai_response(text="After compaction.", prompt_tokens=120, completion_tokens=40)
adapter = LiteLLMAnthropicMessagesAdapter()
- result = adapter.translate_openai_response_to_anthropic(
- response=response, polyfill_result=polyfill
- )
+ result = adapter.translate_openai_response_to_anthropic(response=response, polyfill_result=polyfill)
# compaction block must come first
content = result.get("content")
@@ -3652,7 +3520,9 @@ def test_translate_anthropic_tools_to_openai_omits_unset_strict():
assert function["parameters"]["required"] == ["query"]
-TOOL_RESULT_IMAGE_B64 = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg=="
+TOOL_RESULT_IMAGE_B64 = (
+ "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg=="
+)
TOOL_RESULT_IMAGE_URL = "https://example.com/screenshot.png"
@@ -3660,8 +3530,7 @@ def _anthropic_tool_use_turn(*tool_use_ids):
return AnthopicMessagesAssistantMessageParam(
role="assistant",
content=[
- {"type": "tool_use", "id": tid, "name": "read_file", "input": {"path": "img.png"}}
- for tid in tool_use_ids
+ {"type": "tool_use", "id": tid, "name": "read_file", "input": {"path": "img.png"}} for tid in tool_use_ids
],
)
@@ -3781,9 +3650,7 @@ def test_tool_result_parallel_tool_calls_keep_tool_message_adjacency():
result = _run_chat_completions_pipeline(
[
_anthropic_tool_use_turn("toolu_01", "toolu_02"),
- _anthropic_tool_result_turn(
- {"toolu_01": [_base64_image_block()], "toolu_02": [_url_image_block()]}
- ),
+ _anthropic_tool_result_turn({"toolu_01": [_base64_image_block()], "toolu_02": [_url_image_block()]}),
]
)
@@ -3878,7 +3745,9 @@ def test_translate_anthropic_to_openai_without_prompt_cache_breakpoint_adds_noth
def test_translate_anthropic_messages_to_openai_carries_midturn_system_prompt_cache_breakpoint():
explicit = {"mode": "explicit"}
result = LiteLLMAnthropicMessagesAdapter().translate_anthropic_messages_to_openai(
- messages=[{"role": "system", "content": [{"type": "text", "text": "fix", "prompt_cache_breakpoint": explicit}]}],
+ messages=[
+ {"role": "system", "content": [{"type": "text", "text": "fix", "prompt_cache_breakpoint": explicit}]}
+ ],
model="gpt-5.6",
)
assert result == [
diff --git a/tests/test_litellm/llms/anthropic/experimental_pass_through/adapters/test_handler_output_config_passthrough.py b/tests/test_litellm/llms/anthropic/experimental_pass_through/adapters/test_handler_output_config_passthrough.py
index a944afc6152..71d7974c2d4 100644
--- a/tests/test_litellm/llms/anthropic/experimental_pass_through/adapters/test_handler_output_config_passthrough.py
+++ b/tests/test_litellm/llms/anthropic/experimental_pass_through/adapters/test_handler_output_config_passthrough.py
@@ -34,9 +34,7 @@ import pytest
# Anchor sys.path to this file's location — not the working-directory-relative
# pattern Greptile flagged on PR #23706. Resolves correctly regardless of
# where pytest is invoked from.
-sys.path.insert(
- 0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../../../../../.."))
-)
+sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../../../../../..")))
from litellm.llms.anthropic.experimental_pass_through.adapters.handler import (
ANTHROPIC_ONLY_REQUEST_KEYS,
@@ -174,9 +172,7 @@ class TestOutputConfigStrippedFromCompletionKwargs:
result = _call_prepare(
extra_kwargs={
"custom_llm_provider": "azure",
- "output_config": {
- "format": {"type": "json_schema", "schema": losing_schema}
- },
+ "output_config": {"format": {"type": "json_schema", "schema": losing_schema}},
},
output_format={"type": "json_schema", "schema": winning_schema},
)
diff --git a/tests/test_litellm/llms/anthropic/experimental_pass_through/adapters/test_streaming_iterator_combined_chunk.py b/tests/test_litellm/llms/anthropic/experimental_pass_through/adapters/test_streaming_iterator_combined_chunk.py
index 6973340101e..0a4d9c693d6 100644
--- a/tests/test_litellm/llms/anthropic/experimental_pass_through/adapters/test_streaming_iterator_combined_chunk.py
+++ b/tests/test_litellm/llms/anthropic/experimental_pass_through/adapters/test_streaming_iterator_combined_chunk.py
@@ -31,9 +31,7 @@ from litellm.types.utils import (
)
-def _build_fake_stream(
- content: str, finish_reason: str = "stop"
-) -> MockResponseIterator:
+def _build_fake_stream(content: str, finish_reason: str = "stop") -> MockResponseIterator:
"""Mimic a Vertex Gemma `:predict` fake stream: one collapsed chunk."""
model_response = ModelResponse()
model_response.choices = [
@@ -133,9 +131,7 @@ def test_delayed_usage_chunk_preserves_cache_tokens():
wrapper = AnthropicStreamWrapper(completion_stream=iter(chunks), model="gpt-4o")
events = list(wrapper)
- message_delta = next(
- event for event in events if event.get("type") == "message_delta"
- )
+ message_delta = next(event for event in events if event.get("type") == "message_delta")
assert message_delta["usage"]["input_tokens"] == 70
assert message_delta["usage"]["output_tokens"] == 5
@@ -145,13 +141,7 @@ def test_delayed_usage_chunk_preserves_cache_tokens():
def test_splitter_passes_through_non_combined_chunks():
"""A chunk with content but no finish_reason is not split."""
- chunk = ModelResponseStream(
- choices=[
- StreamingChoices(
- index=0, delta=Delta(content="partial"), finish_reason=None
- )
- ]
- )
+ chunk = ModelResponseStream(choices=[StreamingChoices(index=0, delta=Delta(content="partial"), finish_reason=None)])
chunks = list(_CombinedChunkSplitter(iter([chunk])))
assert len(chunks) == 1
assert chunks[0].choices[0].delta.content == "partial"
@@ -159,11 +149,7 @@ def test_splitter_passes_through_non_combined_chunks():
def test_splitter_splits_combined_chunk_into_content_then_finish():
"""A chunk with both content and finish_reason becomes two chunks."""
- chunk = ModelResponseStream(
- choices=[
- StreamingChoices(index=0, delta=Delta(content="done"), finish_reason="stop")
- ]
- )
+ chunk = ModelResponseStream(choices=[StreamingChoices(index=0, delta=Delta(content="done"), finish_reason="stop")])
content_chunk, finish_chunk = list(_CombinedChunkSplitter(iter([chunk])))
assert content_chunk.choices[0].delta.content == "done"
@@ -193,9 +179,7 @@ def test_split_clears_reasoning_and_thinking_on_finish_chunk():
reasoning_content="some reasoning",
thinking_blocks=[{"type": "thinking"}],
)
- chunk = SimpleNamespace(
- choices=[SimpleNamespace(finish_reason="stop", delta=delta)]
- )
+ chunk = SimpleNamespace(choices=[SimpleNamespace(finish_reason="stop", delta=delta)])
content_chunk, finish_chunk = _CombinedChunkSplitter._split(chunk)
diff --git a/tests/test_litellm/llms/anthropic/experimental_pass_through/adapters/test_streaming_iterator_compaction.py b/tests/test_litellm/llms/anthropic/experimental_pass_through/adapters/test_streaming_iterator_compaction.py
index 5c53a8fc317..646d63e08bd 100644
--- a/tests/test_litellm/llms/anthropic/experimental_pass_through/adapters/test_streaming_iterator_compaction.py
+++ b/tests/test_litellm/llms/anthropic/experimental_pass_through/adapters/test_streaming_iterator_compaction.py
@@ -22,9 +22,7 @@ def _make_text_chunk(
StreamingChoices(
finish_reason=finish_reason,
index=0,
- delta=Delta(
- content=text, role="assistant" if text else None, tool_calls=None
- ),
+ delta=Delta(content=text, role="assistant" if text else None, tool_calls=None),
logprobs=None,
)
]
@@ -73,34 +71,23 @@ async def test_stream_emits_compaction_block_before_text():
compaction_start = next(
e
for e in events
- if e.get("type") == "content_block_start"
- and e.get("content_block", {}).get("type") == "compaction"
+ if e.get("type") == "content_block_start" and e.get("content_block", {}).get("type") == "compaction"
)
assert compaction_start["index"] == 0
compaction_delta = next(
e
for e in events
- if e.get("type") == "content_block_delta"
- and e.get("delta", {}).get("type") == "compaction_delta"
+ if e.get("type") == "content_block_delta" and e.get("delta", {}).get("type") == "compaction_delta"
)
assert compaction_delta["index"] == 0
- assert (
- compaction_delta["delta"]["content"] == "Summary of prior conversation turns."
- )
+ assert compaction_delta["delta"]["content"] == "Summary of prior conversation turns."
- compaction_stop = next(
- e
- for e in events
- if e.get("type") == "content_block_stop" and e.get("index") == 0
- )
+ compaction_stop = next(e for e in events if e.get("type") == "content_block_stop" and e.get("index") == 0)
assert compaction_stop is not None
text_start = next(
- e
- for e in events
- if e.get("type") == "content_block_start"
- and e.get("content_block", {}).get("type") == "text"
+ e for e in events if e.get("type") == "content_block_start" and e.get("content_block", {}).get("type") == "text"
)
assert text_start["index"] == 1
@@ -177,14 +164,9 @@ async def test_stream_without_compaction_block_unchanged():
events = await _collect_events_async(wrapper)
assert not any(
- e.get("content_block", {}).get("type") == "compaction"
- for e in events
- if e.get("type") == "content_block_start"
+ e.get("content_block", {}).get("type") == "compaction" for e in events if e.get("type") == "content_block_start"
)
text_start = next(
- e
- for e in events
- if e.get("type") == "content_block_start"
- and e.get("content_block", {}).get("type") == "text"
+ e for e in events if e.get("type") == "content_block_start" and e.get("content_block", {}).get("type") == "text"
)
assert text_start["index"] == 0
diff --git a/tests/test_litellm/llms/anthropic/experimental_pass_through/adapters/test_streaming_iterator_empty_choices.py b/tests/test_litellm/llms/anthropic/experimental_pass_through/adapters/test_streaming_iterator_empty_choices.py
index 3e85872f1e5..ce6d6c0656e 100644
--- a/tests/test_litellm/llms/anthropic/experimental_pass_through/adapters/test_streaming_iterator_empty_choices.py
+++ b/tests/test_litellm/llms/anthropic/experimental_pass_through/adapters/test_streaming_iterator_empty_choices.py
@@ -19,9 +19,7 @@ from litellm.types.utils import Delta, ModelResponseStream, StreamingChoices, Us
def _text_chunk(text: str) -> ModelResponseStream:
- return ModelResponseStream(
- choices=[StreamingChoices(index=0, delta=Delta(content=text), finish_reason=None)]
- )
+ return ModelResponseStream(choices=[StreamingChoices(index=0, delta=Delta(content=text), finish_reason=None)])
def _finish_chunk() -> ModelResponseStream:
@@ -61,9 +59,7 @@ def test_leading_metadata_chunk_without_choices_does_not_kill_stream():
wrapper = AnthropicStreamWrapper(completion_stream=iter(chunks), model="mock-model")
events = list(wrapper)
- text = "".join(
- event["delta"]["text"] for event in events if event.get("type") == "content_block_delta"
- )
+ text = "".join(event["delta"]["text"] for event in events if event.get("type") == "content_block_delta")
assert text == "Hello there"
assert events[-1]["type"] == "message_stop"
diff --git a/tests/test_litellm/llms/anthropic/experimental_pass_through/adapters/test_streaming_iterator_first_delta.py b/tests/test_litellm/llms/anthropic/experimental_pass_through/adapters/test_streaming_iterator_first_delta.py
index f64ffb6d233..e34223d72d3 100644
--- a/tests/test_litellm/llms/anthropic/experimental_pass_through/adapters/test_streaming_iterator_first_delta.py
+++ b/tests/test_litellm/llms/anthropic/experimental_pass_through/adapters/test_streaming_iterator_first_delta.py
@@ -507,11 +507,7 @@ def _thinking_first_chunks() -> List[MagicMock]:
def _assert_thinking_first_block_opens_at_index_zero(events: List[dict]) -> None:
- starts = [
- (e["index"], e["content_block"]["type"])
- for e in events
- if e.get("type") == "content_block_start"
- ]
+ starts = [(e["index"], e["content_block"]["type"]) for e in events if e.get("type") == "content_block_start"]
assert starts == [(0, "thinking"), (1, "text")], starts
assert "" not in _text_deltas(events)
assert _thinking_deltas(events) == ["Let me think", "about it."]
@@ -980,9 +976,7 @@ def test_tool_block_start_emitted_without_awaiting_the_next_chunk_sync():
"name": "Write",
"input": {},
}
- assert stream.pulled == 1, (
- f"content_block_start was withheld until {stream.pulled} upstream chunks had arrived"
- )
+ assert stream.pulled == 1, f"content_block_start was withheld until {stream.pulled} upstream chunks had arrived"
@pytest.mark.asyncio
@@ -997,9 +991,7 @@ async def test_tool_block_start_emitted_without_awaiting_the_next_chunk_async():
start = await wrapper.__anext__()
assert start["type"] == "content_block_start"
assert start["content_block"]["name"] == "Write"
- assert stream.pulled == 1, (
- f"content_block_start was withheld until {stream.pulled} upstream chunks had arrived"
- )
+ assert stream.pulled == 1, f"content_block_start was withheld until {stream.pulled} upstream chunks had arrived"
@pytest.mark.parametrize("is_async", [False, True])
diff --git a/tests/test_litellm/llms/anthropic/experimental_pass_through/adapters/test_streaming_iterator_tool_args.py b/tests/test_litellm/llms/anthropic/experimental_pass_through/adapters/test_streaming_iterator_tool_args.py
index 29e9279731d..cf0df4d7ea3 100644
--- a/tests/test_litellm/llms/anthropic/experimental_pass_through/adapters/test_streaming_iterator_tool_args.py
+++ b/tests/test_litellm/llms/anthropic/experimental_pass_through/adapters/test_streaming_iterator_tool_args.py
@@ -127,21 +127,17 @@ async def test_async_stream_emits_input_json_delta_for_bundled_tool_args():
):
input_json_delta_idx = i
- assert (
- tool_start_idx is not None
- ), f"Expected content_block_start with type=tool_use; events: {event_types}"
- assert (
- input_json_delta_idx is not None
- ), f"Expected content_block_delta with input_json_delta; events: {event_types}"
- assert (
- input_json_delta_idx == tool_start_idx + 1
- ), "input_json_delta should immediately follow the tool_use content_block_start"
+ assert tool_start_idx is not None, f"Expected content_block_start with type=tool_use; events: {event_types}"
+ assert input_json_delta_idx is not None, (
+ f"Expected content_block_delta with input_json_delta; events: {event_types}"
+ )
+ assert input_json_delta_idx == tool_start_idx + 1, (
+ "input_json_delta should immediately follow the tool_use content_block_start"
+ )
# Verify the delta carries the tool arguments
delta_event = events[input_json_delta_idx]
- assert delta_event["delta"][
- "partial_json"
- ], "input_json_delta should have non-empty partial_json"
+ assert delta_event["delta"]["partial_json"], "input_json_delta should have non-empty partial_json"
@pytest.mark.asyncio
@@ -230,8 +226,7 @@ async def test_async_stream_no_extra_delta_when_tool_args_empty():
and e["delta"].get("type") == "input_json_delta"
]
assert len(input_json_deltas) == 1, (
- f"Expected exactly 1 input_json_delta (from the follow-up chunk), "
- f"got {len(input_json_deltas)}"
+ f"Expected exactly 1 input_json_delta (from the follow-up chunk), got {len(input_json_deltas)}"
)
assert input_json_deltas[0]["delta"]["partial_json"] == '{"location": "NYC"}'
@@ -291,15 +286,13 @@ def test_sync_stream_emits_input_json_delta_for_bundled_tool_args():
):
input_json_delta_idx = i
- assert (
- tool_start_idx is not None
- ), f"Expected content_block_start with type=tool_use; events: {event_types}"
- assert (
- input_json_delta_idx is not None
- ), f"Expected content_block_delta with input_json_delta; events: {event_types}"
- assert (
- input_json_delta_idx == tool_start_idx + 1
- ), "input_json_delta should immediately follow the tool_use content_block_start"
+ assert tool_start_idx is not None, f"Expected content_block_start with type=tool_use; events: {event_types}"
+ assert input_json_delta_idx is not None, (
+ f"Expected content_block_delta with input_json_delta; events: {event_types}"
+ )
+ assert input_json_delta_idx == tool_start_idx + 1, (
+ "input_json_delta should immediately follow the tool_use content_block_start"
+ )
assert events[input_json_delta_idx]["delta"]["partial_json"]
@@ -343,9 +336,7 @@ def test_sync_stream_no_extra_delta_when_tool_args_empty():
)
wrapper = AnthropicStreamWrapper(
- completion_stream=iter(
- [text_chunk, tool_name_chunk, tool_args_chunk, finish_chunk]
- ),
+ completion_stream=iter([text_chunk, tool_name_chunk, tool_args_chunk, finish_chunk]),
model="test-model",
)
@@ -374,7 +365,6 @@ def test_sync_stream_no_extra_delta_when_tool_args_empty():
and e["delta"].get("type") == "input_json_delta"
]
assert len(input_json_deltas) == 1, (
- f"Expected exactly 1 input_json_delta (from the follow-up chunk), "
- f"got {len(input_json_deltas)}"
+ f"Expected exactly 1 input_json_delta (from the follow-up chunk), got {len(input_json_deltas)}"
)
assert input_json_deltas[0]["delta"]["partial_json"] == '{"location": "NYC"}'
diff --git a/tests/test_litellm/llms/anthropic/experimental_pass_through/context_management/test_clear_tool_uses.py b/tests/test_litellm/llms/anthropic/experimental_pass_through/context_management/test_clear_tool_uses.py
index 09ac95ab16e..cc4852f25de 100644
--- a/tests/test_litellm/llms/anthropic/experimental_pass_through/context_management/test_clear_tool_uses.py
+++ b/tests/test_litellm/llms/anthropic/experimental_pass_through/context_management/test_clear_tool_uses.py
@@ -258,9 +258,7 @@ def test_tool_result_list_content_shape_preserved():
{"role": "user", "content": "Hi"},
{
"role": "assistant",
- "content": [
- {"type": "tool_use", "id": "toolu_a", "name": "f", "input": {}}
- ],
+ "content": [{"type": "tool_use", "id": "toolu_a", "name": "f", "input": {}}],
},
{
"role": "user",
@@ -274,9 +272,7 @@ def test_tool_result_list_content_shape_preserved():
},
{
"role": "assistant",
- "content": [
- {"type": "tool_use", "id": "toolu_b", "name": "f", "input": {}}
- ],
+ "content": [{"type": "tool_use", "id": "toolu_b", "name": "f", "input": {}}],
},
{
"role": "user",
diff --git a/tests/test_litellm/llms/anthropic/experimental_pass_through/context_management/test_compact.py b/tests/test_litellm/llms/anthropic/experimental_pass_through/context_management/test_compact.py
index 28c82fdf528..da03f3c00ab 100644
--- a/tests/test_litellm/llms/anthropic/experimental_pass_through/context_management/test_compact.py
+++ b/tests/test_litellm/llms/anthropic/experimental_pass_through/context_management/test_compact.py
@@ -193,10 +193,7 @@ def test_select_last_user_question_strips_tool_result_from_mixed_turn():
content = selected[0]["content"]
assert isinstance(content, list)
assert all(b.get("type") != "tool_result" for b in content)
- assert any(
- b.get("type") == "text" and b.get("text") == "follow-up question"
- for b in content
- )
+ assert any(b.get("type") == "text" and b.get("text") == "follow-up question" for b in content)
def test_select_last_user_question_skips_pure_tool_result_turn():
@@ -425,9 +422,7 @@ def test_client_compaction_block_history_without_context_management():
def test_client_compaction_block_history_no_compaction_returns_none():
- result = apply_client_compaction_block_history(
- messages=_simple_messages(), system="base"
- )
+ result = apply_client_compaction_block_history(messages=_simple_messages(), system="base")
assert result is None
@@ -512,9 +507,7 @@ async def test_slice_only_no_compaction_block_under_threshold():
async def test_full_summary_path():
"""Over threshold: summary call fires, compaction_block and iterations_usage returned."""
messages = _simple_messages()
- mock_response = _make_mock_response(
- "Condensed history", prompt_tokens=200, completion_tokens=50
- )
+ mock_response = _make_mock_response("Condensed history", prompt_tokens=200, completion_tokens=50)
with (
patch(
@@ -1068,13 +1061,11 @@ async def test_summary_call_does_not_emit_consecutive_user_turns():
)
summary_messages = captured_calls[0]["summary_messages"]
- user_indices = [
- idx for idx, msg in enumerate(summary_messages) if msg.get("role") == "user"
- ]
+ user_indices = [idx for idx, msg in enumerate(summary_messages) if msg.get("role") == "user"]
# No two adjacent indices.
- assert all(
- b - a > 1 for a, b in zip(user_indices, user_indices[1:])
- ), f"two consecutive user turns produced: {summary_messages}"
+ assert all(b - a > 1 for a, b in zip(user_indices, user_indices[1:])), (
+ f"two consecutive user turns produced: {summary_messages}"
+ )
async def test_summary_call_sends_default_max_tokens():
@@ -1157,9 +1148,9 @@ def test_summary_max_tokens_setting_falls_back_for_invalid_values():
"litellm.proxy.proxy_server.general_settings",
{"context_management_summary_max_tokens": bad},
):
- assert (
- _read_summary_max_tokens_setting() == COMPACT_SUMMARY_MAX_TOKENS
- ), f"expected default for invalid override {bad!r}"
+ assert _read_summary_max_tokens_setting() == COMPACT_SUMMARY_MAX_TOKENS, (
+ f"expected default for invalid override {bad!r}"
+ )
async def test_summary_call_sends_default_timeout():
@@ -1282,9 +1273,7 @@ async def test_summary_model_denied_when_team_not_in_allowlist():
tools=None,
system=None,
edit_spec=_EDIT_SPEC_DEFAULT,
- user_api_key_auth=_fake_user_api_key_auth(
- key_models=["all-proxy-models"], team_models=["gpt-4o"]
- ),
+ user_api_key_auth=_fake_user_api_key_auth(key_models=["all-proxy-models"], team_models=["gpt-4o"]),
)
mock_call.assert_not_awaited()
@@ -1313,9 +1302,7 @@ async def test_summary_model_allowed_when_in_key_allowlist():
tools=None,
system=None,
edit_spec=_EDIT_SPEC_DEFAULT,
- user_api_key_auth=_fake_user_api_key_auth(
- key_models=["claude-haiku-4-5", "gpt-4o"]
- ),
+ user_api_key_auth=_fake_user_api_key_auth(key_models=["claude-haiku-4-5", "gpt-4o"]),
)
mock_call.assert_awaited_once()
@@ -1521,9 +1508,7 @@ async def test_summary_model_denied_when_key_over_model_budget():
limiter = MagicMock()
limiter.is_key_within_model_budget = AsyncMock(
- side_effect=litellm.BudgetExceededError(
- message="over budget", current_cost=10, max_budget=5
- )
+ side_effect=litellm.BudgetExceededError(message="over budget", current_cost=10, max_budget=5)
)
with (
@@ -1574,9 +1559,7 @@ async def test_summary_model_denied_when_user_over_model_budget():
limiter = MagicMock()
limiter.is_user_within_model_budget = AsyncMock(
- side_effect=litellm.BudgetExceededError(
- message="over budget", current_cost=10, max_budget=5
- )
+ side_effect=litellm.BudgetExceededError(message="over budget", current_cost=10, max_budget=5)
)
with (
@@ -1617,9 +1600,7 @@ async def test_summary_model_denied_when_user_over_model_budget():
_PROXY_VirtualKeyModelMaxBudgetLimiter,
)
- real_params = inspect.signature(
- _PROXY_VirtualKeyModelMaxBudgetLimiter.is_user_within_model_budget
- ).parameters
+ real_params = inspect.signature(_PROXY_VirtualKeyModelMaxBudgetLimiter.is_user_within_model_budget).parameters
for kwarg in ("user_id", "user_model_max_budget", "model"):
assert kwarg in real_params, f"compact.py passes {kwarg}=, which the limiter no longer accepts"
@@ -1641,9 +1622,7 @@ async def test_summary_model_denied_when_end_user_over_model_budget():
limiter = MagicMock()
limiter.is_key_within_model_budget = AsyncMock(return_value=True)
limiter.is_end_user_within_model_budget = AsyncMock(
- side_effect=litellm.BudgetExceededError(
- message="over budget", current_cost=10, max_budget=5
- )
+ side_effect=litellm.BudgetExceededError(message="over budget", current_cost=10, max_budget=5)
)
with (
@@ -1956,9 +1935,7 @@ async def test_model_budget_metadata_propagated_to_summary_call():
parent_litellm_metadata = {
"user_api_key": "sk-test",
"user_api_key_model_max_budget": {"claude-haiku-4-5": {"budget_limit": 5}},
- "user_api_key_end_user_model_max_budget": {
- "claude-haiku-4-5": {"budget_limit": 2}
- },
+ "user_api_key_end_user_model_max_budget": {"claude-haiku-4-5": {"budget_limit": 2}},
}
with (
@@ -1983,12 +1960,8 @@ async def test_model_budget_metadata_propagated_to_summary_call():
)
propagated = mock_call.call_args.kwargs["metadata"]
- assert propagated["user_api_key_model_max_budget"] == {
- "claude-haiku-4-5": {"budget_limit": 5}
- }
- assert propagated["user_api_key_end_user_model_max_budget"] == {
- "claude-haiku-4-5": {"budget_limit": 2}
- }
+ assert propagated["user_api_key_model_max_budget"] == {"claude-haiku-4-5": {"budget_limit": 5}}
+ assert propagated["user_api_key_end_user_model_max_budget"] == {"claude-haiku-4-5": {"budget_limit": 2}}
async def test_summary_call_propagates_allowed_model_region():
@@ -2460,9 +2433,7 @@ def test_endpoint_returns_anthropic_400_on_context_management_error():
mock_proxy_server.version = "test"
with patch.dict(sys.modules, {"litellm.proxy.proxy_server": mock_proxy_server}):
- with patch(
- "litellm.proxy.anthropic_endpoints.endpoints.ProxyBaseLLMRequestProcessing"
- ) as mock_cls:
+ with patch("litellm.proxy.anthropic_endpoints.endpoints.ProxyBaseLLMRequestProcessing") as mock_cls:
mock_instance = MagicMock()
mock_instance.base_process_llm_request = AsyncMock(
side_effect=AnthropicContextManagementError(
@@ -2521,9 +2492,7 @@ def test_endpoint_runs_failure_hook_on_500_context_management_error():
mock_proxy_server.version = "test"
with patch.dict(sys.modules, {"litellm.proxy.proxy_server": mock_proxy_server}):
- with patch(
- "litellm.proxy.anthropic_endpoints.endpoints.ProxyBaseLLMRequestProcessing"
- ) as mock_cls:
+ with patch("litellm.proxy.anthropic_endpoints.endpoints.ProxyBaseLLMRequestProcessing") as mock_cls:
mock_instance = MagicMock()
mock_instance.base_process_llm_request = AsyncMock(
side_effect=AnthropicContextManagementError(
diff --git a/tests/test_litellm/llms/anthropic/experimental_pass_through/context_management/test_dispatcher.py b/tests/test_litellm/llms/anthropic/experimental_pass_through/context_management/test_dispatcher.py
index 50c72cfe8d0..56b1ce16ebc 100644
--- a/tests/test_litellm/llms/anthropic/experimental_pass_through/context_management/test_dispatcher.py
+++ b/tests/test_litellm/llms/anthropic/experimental_pass_through/context_management/test_dispatcher.py
@@ -50,9 +50,7 @@ async def test_unknown_edit_type_is_noop():
messages=messages,
tools=None,
system=None,
- context_management_spec={
- "edits": [{"type": "totally_not_a_real_edit_20999999"}]
- },
+ context_management_spec={"edits": [{"type": "totally_not_a_real_edit_20999999"}]},
)
assert result.applied_edits == []
assert result.messages == messages
diff --git a/tests/test_litellm/llms/anthropic/experimental_pass_through/messages/test_advisor_integration.py b/tests/test_litellm/llms/anthropic/experimental_pass_through/messages/test_advisor_integration.py
index 414ba8f0f5c..b6df9ecf3bd 100644
--- a/tests/test_litellm/llms/anthropic/experimental_pass_through/messages/test_advisor_integration.py
+++ b/tests/test_litellm/llms/anthropic/experimental_pass_through/messages/test_advisor_integration.py
@@ -39,9 +39,7 @@ def _text_resp(text: str, model: str = "gpt-4o-mini") -> Dict:
}
-def _advisor_call_resp(
- question: str = "How do I approach this?", tool_id: str = "tid_01"
-) -> Dict:
+def _advisor_call_resp(question: str = "How do I approach this?", tool_id: str = "tid_01") -> Dict:
return {
"id": "msg_int_test",
"type": "message",
@@ -106,14 +104,10 @@ async def test_full_dispatch_interceptor_fires_and_loop_completes():
assert isinstance(result, dict)
content = result.get("content", [])
text_blocks = [b for b in content if b.get("type") == "text"]
- advisor_uses = [
- b for b in content if b.get("type") == "tool_use" and b.get("name") == "advisor"
- ]
+ advisor_uses = [b for b in content if b.get("type") == "tool_use" and b.get("name") == "advisor"]
assert len(text_blocks) >= 1, "Final response must have text"
- assert (
- len(advisor_uses) == 0
- ), "No advisor tool_use blocks must appear in final output"
+ assert len(advisor_uses) == 0, "No advisor tool_use blocks must appear in final output"
# ---------------------------------------------------------------------------
@@ -221,9 +215,7 @@ async def test_named_params_forwarded_into_advisor_executor_subcall():
captured_executor_kwargs: Dict = {}
- async def mock_handler(
- model, messages, tools, stream, max_tokens, custom_llm_provider, **kwargs
- ):
+ async def mock_handler(model, messages, tools, stream, max_tokens, custom_llm_provider, **kwargs):
# First call is the executor sub-call (returns advisor tool_use).
# Capture its kwargs so we can assert the forwarded params.
if not captured_executor_kwargs:
@@ -267,8 +259,7 @@ async def test_named_params_forwarded_into_advisor_executor_subcall():
)
assert captured_executor_kwargs["thinking"] == {"type": "adaptive"}, (
- "thinking must be forwarded into executor sub-call — see "
- "anthropic_messages.handler interceptor invocation."
+ "thinking must be forwarded into executor sub-call — see anthropic_messages.handler interceptor invocation."
)
# The advisor enriches metadata with `advisor_sub_call` / `parent_request_id`,
# but the original caller fields must survive into the executor sub-call.
@@ -304,9 +295,7 @@ async def test_pre_request_hook_override_does_not_collide_with_explicit_kwargs()
captured: Dict = {}
- async def mock_handler(
- model, messages, tools, stream, max_tokens, custom_llm_provider, **kwargs
- ):
+ async def mock_handler(model, messages, tools, stream, max_tokens, custom_llm_provider, **kwargs):
if not captured:
captured.update(
{
@@ -320,9 +309,7 @@ async def test_pre_request_hook_override_does_not_collide_with_explicit_kwargs()
return _text_resp("Some advice.", model="claude-opus-4-6")
return _text_resp("Final answer.")
- async def fake_pre_request_hooks(
- model, messages, tools, stream, custom_llm_provider, **hook_kwargs
- ):
+ async def fake_pre_request_hooks(model, messages, tools, stream, custom_llm_provider, **hook_kwargs):
# Simulate a CustomLogger.async_pre_request_hook that overrides several
# named params on its way through. Without the request_kwargs.pop()
# extraction in handler.py, these would collide with the explicit
diff --git a/tests/test_litellm/llms/anthropic/experimental_pass_through/messages/test_agentic_streaming_iterator.py b/tests/test_litellm/llms/anthropic/experimental_pass_through/messages/test_agentic_streaming_iterator.py
index db8aae6702f..4a8a38a8acc 100644
--- a/tests/test_litellm/llms/anthropic/experimental_pass_through/messages/test_agentic_streaming_iterator.py
+++ b/tests/test_litellm/llms/anthropic/experimental_pass_through/messages/test_agentic_streaming_iterator.py
@@ -70,9 +70,7 @@ def _build_simple_text_stream() -> List[bytes]:
},
)
)
- chunks.append(
- _sse_event("content_block_stop", {"type": "content_block_stop", "index": 0})
- )
+ chunks.append(_sse_event("content_block_stop", {"type": "content_block_stop", "index": 0}))
chunks.append(
_sse_event(
"message_delta",
@@ -145,9 +143,7 @@ def _build_tool_use_stream() -> List[bytes]:
},
)
)
- chunks.append(
- _sse_event("content_block_stop", {"type": "content_block_stop", "index": 0})
- )
+ chunks.append(_sse_event("content_block_stop", {"type": "content_block_stop", "index": 0}))
# tool_use block
chunks.append(
_sse_event(
@@ -187,9 +183,7 @@ def _build_tool_use_stream() -> List[bytes]:
},
)
)
- chunks.append(
- _sse_event("content_block_stop", {"type": "content_block_stop", "index": 1})
- )
+ chunks.append(_sse_event("content_block_stop", {"type": "content_block_stop", "index": 1}))
chunks.append(
_sse_event(
"message_delta",
@@ -234,9 +228,7 @@ class MockAsyncStream:
class TestParseSSEEvents:
def test_should_parse_single_event(self):
- raw = _sse_event(
- "message_start", {"type": "message_start", "message": {"id": "1"}}
- )
+ raw = _sse_event("message_start", {"type": "message_start", "message": {"id": "1"}})
events = _parse_sse_events(raw)
assert len(events) == 1
assert events[0][0] == "message_start"
@@ -407,9 +399,7 @@ class TestHandleMessageDelta:
class TestRebuildAnthropicResponse:
def test_should_rebuild_simple_text_response(self):
raw_bytes = _build_simple_text_stream()
- result = AgenticAnthropicStreamingIterator._rebuild_anthropic_response_from_sse(
- raw_bytes
- )
+ result = AgenticAnthropicStreamingIterator._rebuild_anthropic_response_from_sse(raw_bytes)
assert result is not None
assert result["id"] == "msg_123"
assert result["model"] == "claude-sonnet-4-20250514"
@@ -422,9 +412,7 @@ class TestRebuildAnthropicResponse:
def test_should_rebuild_tool_use_response(self):
raw_bytes = _build_tool_use_stream()
- result = AgenticAnthropicStreamingIterator._rebuild_anthropic_response_from_sse(
- raw_bytes
- )
+ result = AgenticAnthropicStreamingIterator._rebuild_anthropic_response_from_sse(raw_bytes)
assert result is not None
assert result["id"] == "msg_tool_456"
assert result["stop_reason"] == "tool_use"
@@ -452,23 +440,17 @@ class TestRebuildAnthropicResponse:
},
)
]
- result = AgenticAnthropicStreamingIterator._rebuild_anthropic_response_from_sse(
- raw_bytes
- )
+ result = AgenticAnthropicStreamingIterator._rebuild_anthropic_response_from_sse(raw_bytes)
assert result is None
def test_should_handle_empty_bytes(self):
- result = AgenticAnthropicStreamingIterator._rebuild_anthropic_response_from_sse(
- []
- )
+ result = AgenticAnthropicStreamingIterator._rebuild_anthropic_response_from_sse([])
assert result is None
def test_should_handle_multi_event_chunks(self):
"""When multiple SSE events arrive in a single bytes chunk."""
combined = b"".join(_build_simple_text_stream())
- result = AgenticAnthropicStreamingIterator._rebuild_anthropic_response_from_sse(
- [combined]
- )
+ result = AgenticAnthropicStreamingIterator._rebuild_anthropic_response_from_sse([combined])
assert result is not None
assert result["content"][0]["text"] == "Hello, world!"
@@ -500,9 +482,7 @@ class TestRebuildAnthropicResponse:
),
_sse_event("message_stop", {"type": "message_stop"}),
]
- result = AgenticAnthropicStreamingIterator._rebuild_anthropic_response_from_sse(
- raw_bytes
- )
+ result = AgenticAnthropicStreamingIterator._rebuild_anthropic_response_from_sse(raw_bytes)
assert result is not None
assert result["usage"]["cache_creation_input_tokens"] == 50
assert result["usage"]["cache_read_input_tokens"] == 30
@@ -543,9 +523,7 @@ class TestRebuildAnthropicResponse:
),
_sse_event("message_stop", {"type": "message_stop"}),
]
- result = AgenticAnthropicStreamingIterator._rebuild_anthropic_response_from_sse(
- raw_bytes
- )
+ result = AgenticAnthropicStreamingIterator._rebuild_anthropic_response_from_sse(raw_bytes)
assert result is not None
assert result["content"][0]["type"] == "redacted_thinking"
@@ -672,9 +650,7 @@ class TestAgenticStreamingIteratorPhase2:
}
mock_handler = MagicMock()
- mock_handler._call_agentic_completion_hooks = AsyncMock(
- return_value=fake_response
- )
+ mock_handler._call_agentic_completion_hooks = AsyncMock(return_value=fake_response)
iterator = AgenticAnthropicStreamingIterator(
completion_stream=mock_stream,
@@ -707,9 +683,7 @@ class TestAgenticStreamingIteratorErrorHandling:
mock_stream = MockAsyncStream(chunks)
mock_handler = MagicMock()
- mock_handler._call_agentic_completion_hooks = AsyncMock(
- side_effect=RuntimeError("hook exploded")
- )
+ mock_handler._call_agentic_completion_hooks = AsyncMock(side_effect=RuntimeError("hook exploded"))
mock_logging = MagicMock()
mock_logging.litellm_call_id = "test_call_123"
diff --git a/tests/test_litellm/llms/anthropic/experimental_pass_through/messages/test_anthropic_experimental_pass_through_messages_handler.py b/tests/test_litellm/llms/anthropic/experimental_pass_through/messages/test_anthropic_experimental_pass_through_messages_handler.py
index 9e58ded81bd..3d7582135a2 100644
--- a/tests/test_litellm/llms/anthropic/experimental_pass_through/messages/test_anthropic_experimental_pass_through_messages_handler.py
+++ b/tests/test_litellm/llms/anthropic/experimental_pass_through/messages/test_anthropic_experimental_pass_through_messages_handler.py
@@ -290,21 +290,15 @@ async def test_bedrock_converse_budget_tokens_preserved():
mock_acompletion.assert_called_once()
call_kwargs = mock_acompletion.call_args.kwargs
- print(
- "acompletion call kwargs: ", json.dumps(call_kwargs, indent=4, default=str)
- )
+ print("acompletion call kwargs: ", json.dumps(call_kwargs, indent=4, default=str))
# Verify thinking parameter is passed through with budget_tokens preserved
thinking_param = call_kwargs.get("thinking")
- assert (
- thinking_param is not None
- ), "thinking parameter should be passed to acompletion"
- assert (
- thinking_param.get("type") == "enabled"
- ), "thinking.type should be 'enabled'"
- assert (
- thinking_param.get("budget_tokens") == 1024
- ), f"thinking.budget_tokens should be 1024, but got {thinking_param.get('budget_tokens')}"
+ assert thinking_param is not None, "thinking parameter should be passed to acompletion"
+ assert thinking_param.get("type") == "enabled", "thinking.type should be 'enabled'"
+ assert thinking_param.get("budget_tokens") == 1024, (
+ f"thinking.budget_tokens should be 1024, but got {thinking_param.get('budget_tokens')}"
+ )
def test_openai_model_with_thinking_converts_to_reasoning():
@@ -336,23 +330,18 @@ def test_openai_model_with_thinking_converts_to_reasoning():
call_kwargs = mock_responses.call_args.kwargs
# Verify reasoning is set (converted from thinking)
- assert (
- "reasoning" in call_kwargs
- ), "reasoning should be passed to litellm.responses"
+ assert "reasoning" in call_kwargs, "reasoning should be passed to litellm.responses"
# budget_tokens=1024 -> effort="low" (at the LOW budget threshold)
# reasoning_auto_summary is False by default, so no summary key
expected_reasoning = {"effort": "low"}
assert call_kwargs["reasoning"] == expected_reasoning, (
- f"reasoning should be {expected_reasoning} for budget_tokens=1024, "
- f"got {call_kwargs.get('reasoning')}"
+ f"reasoning should be {expected_reasoning} for budget_tokens=1024, got {call_kwargs.get('reasoning')}"
)
assert "summary" not in call_kwargs["reasoning"]
# Verify thinking is NOT passed directly to the Responses API
- assert (
- "thinking" not in call_kwargs
- ), "thinking should NOT be passed directly to litellm.responses"
+ assert "thinking" not in call_kwargs, "thinking should NOT be passed directly to litellm.responses"
class TestThinkingParameterTransformation:
@@ -405,9 +394,7 @@ class TestThinkingParameterTransformation:
thinking=thinking,
model="openai/gpt-5.2",
)
- assert result == {
- "reasoning_effort": {"effort": "high", "summary": "detailed"}
- }
+ assert result == {"reasoning_effort": {"effort": "high", "summary": "detailed"}}
finally:
litellm.reasoning_auto_summary = original
@@ -605,9 +592,9 @@ class TestThinkingSummaryPreservation:
mock_responses.assert_called_once()
call_kwargs = mock_responses.call_args.kwargs
reasoning = call_kwargs["reasoning"]
- assert (
- reasoning["summary"] == "concise"
- ), f"Expected summary='concise', got summary='{reasoning.get('summary')}'"
+ assert reasoning["summary"] == "concise", (
+ f"Expected summary='concise', got summary='{reasoning.get('summary')}'"
+ )
def test_responses_adapter_preserves_summary(self):
"""translate_thinking_to_reasoning should include summary when user provides it."""
@@ -616,9 +603,7 @@ class TestThinkingSummaryPreservation:
)
thinking = {"type": "enabled", "budget_tokens": 5000, "summary": "concise"}
- result = LiteLLMAnthropicToResponsesAPIAdapter.translate_thinking_to_reasoning(
- thinking
- )
+ result = LiteLLMAnthropicToResponsesAPIAdapter.translate_thinking_to_reasoning(thinking)
assert result == {"effort": "high", "summary": "concise"}
def test_responses_adapter_no_summary_by_default(self):
@@ -632,11 +617,7 @@ class TestThinkingSummaryPreservation:
try:
litellm.reasoning_auto_summary = False
thinking = {"type": "enabled", "budget_tokens": 5000}
- result = (
- LiteLLMAnthropicToResponsesAPIAdapter.translate_thinking_to_reasoning(
- thinking
- )
- )
+ result = LiteLLMAnthropicToResponsesAPIAdapter.translate_thinking_to_reasoning(thinking)
assert result == {"effort": "high"}
assert result is not None and "summary" not in result
finally:
@@ -653,9 +634,7 @@ class TestThinkingSummaryPreservation:
thinking=thinking,
model="openai/gpt-5.2",
)
- assert result == {
- "reasoning_effort": {"effort": "high", "summary": "concise"}
- }
+ assert result == {"reasoning_effort": {"effort": "high", "summary": "concise"}}
def test_translate_thinking_for_model_disabled_stays_plain_string_when_auto_summary_enabled(self):
"""Disabled thinking must stay a plain string even when reasoning_auto_summary is on."""
@@ -801,9 +780,7 @@ def test_presanitized_flag_not_leaked_to_provider_params():
def fake_base_handler(*args, **kwargs):
captured.update(kwargs)
- captured["optional"] = kwargs.get(
- "anthropic_messages_optional_request_params", {}
- )
+ captured["optional"] = kwargs.get("anthropic_messages_optional_request_params", {})
return "stub"
with patch.object(
@@ -981,9 +958,7 @@ def test_first_party_claude_4_8_plus_cost_map_entries_carry_mid_conversation_sys
import litellm
- cost_map_path = os.path.join(
- os.path.dirname(litellm.__file__), "model_prices_and_context_window_backup.json"
- )
+ cost_map_path = os.path.join(os.path.dirname(litellm.__file__), "model_prices_and_context_window_backup.json")
with open(cost_map_path) as f:
cost_map = json.load(f)
rules = cost_map["fallback_generalizations"]["rules"]
@@ -1022,9 +997,7 @@ def test_first_party_claude_4_8_plus_cost_map_entries_carry_mid_conversation_sys
("perplexity/sonar", "sonar", "https://api.perplexity.ai/chat/completions"),
],
)
-async def test_messages_strips_provider_prefix_exactly_once(
- requested_model, expected_wire_model, expected_url
-):
+async def test_messages_strips_provider_prefix_exactly_once(requested_model, expected_wire_model, expected_url):
"""
BerriAI/litellm#37716: only the leading provider segment may be stripped on the way upstream.
diff --git a/tests/test_litellm/llms/anthropic/experimental_pass_through/messages/test_anthropic_messages_speed.py b/tests/test_litellm/llms/anthropic/experimental_pass_through/messages/test_anthropic_messages_speed.py
index 6900f1062bf..87d2a6fed0f 100644
--- a/tests/test_litellm/llms/anthropic/experimental_pass_through/messages/test_anthropic_messages_speed.py
+++ b/tests/test_litellm/llms/anthropic/experimental_pass_through/messages/test_anthropic_messages_speed.py
@@ -91,13 +91,11 @@ def test_messages_drops_speed_for_vertex_opus_with_drop_params(monkeypatch):
"""Regression: a vertex_ai Opus passthrough must drop ``speed`` even though the
prefix-stripped model id maps to a fast-mode-capable direct-Anthropic entry."""
monkeypatch.setattr(litellm, "drop_params", True)
- optional_params = (
- AnthropicMessagesRequestUtils.get_requested_anthropic_messages_optional_param(
- params={"max_tokens": 1024, "speed": "fast"},
- model="claude-opus-4-8",
- drop_params=False,
- custom_llm_provider="vertex_ai",
- )
+ optional_params = AnthropicMessagesRequestUtils.get_requested_anthropic_messages_optional_param(
+ params={"max_tokens": 1024, "speed": "fast"},
+ model="claude-opus-4-8",
+ drop_params=False,
+ custom_llm_provider="vertex_ai",
)
assert "speed" not in optional_params
diff --git a/tests/test_litellm/llms/anthropic/experimental_pass_through/messages/test_anthropic_messages_structured_outputs.py b/tests/test_litellm/llms/anthropic/experimental_pass_through/messages/test_anthropic_messages_structured_outputs.py
index e6d5c6f4ee1..8a0d195107b 100644
--- a/tests/test_litellm/llms/anthropic/experimental_pass_through/messages/test_anthropic_messages_structured_outputs.py
+++ b/tests/test_litellm/llms/anthropic/experimental_pass_through/messages/test_anthropic_messages_structured_outputs.py
@@ -13,9 +13,7 @@ def test_output_format_supported_and_transforms_correctly():
config = AnthropicMessagesConfig()
# 1. Verify it's in supported parameters
- supported_params = config.get_supported_anthropic_messages_params(
- "claude-sonnet-4-5"
- )
+ supported_params = config.get_supported_anthropic_messages_params("claude-sonnet-4-5")
assert "output_format" in supported_params
# 2. Verify transformation preserves output_format and adds beta header
diff --git a/tests/test_litellm/llms/anthropic/experimental_pass_through/messages/test_content_after_stop_reason.py b/tests/test_litellm/llms/anthropic/experimental_pass_through/messages/test_content_after_stop_reason.py
index a0d1f9de6ec..5736de66670 100644
--- a/tests/test_litellm/llms/anthropic/experimental_pass_through/messages/test_content_after_stop_reason.py
+++ b/tests/test_litellm/llms/anthropic/experimental_pass_through/messages/test_content_after_stop_reason.py
@@ -30,26 +30,14 @@ class MockCompletionStreamWithContentAfterStopReason:
self.responses = [
# Initial text content
ModelResponseStream(
- choices=[
- StreamingChoices(
- delta=Delta(content="Hello"), index=0, finish_reason=None
- )
- ],
+ choices=[StreamingChoices(delta=Delta(content="Hello"), index=0, finish_reason=None)],
),
ModelResponseStream(
- choices=[
- StreamingChoices(
- delta=Delta(content=" world"), index=0, finish_reason=None
- )
- ],
+ choices=[StreamingChoices(delta=Delta(content=" world"), index=0, finish_reason=None)],
),
# Message delta with stop_reason AND usage (this is how it actually comes from the API)
ModelResponseStream(
- choices=[
- StreamingChoices(
- delta=Delta(content=""), index=0, finish_reason="stop"
- )
- ],
+ choices=[StreamingChoices(delta=Delta(content=""), index=0, finish_reason="stop")],
usage=Usage(prompt_tokens=230, completion_tokens=65, total_tokens=295),
),
# Additional content after the stop_reason - this simulates the scenario
@@ -118,9 +106,9 @@ def test_anthropic_stream_wrapper_content_after_stop_reason():
print(f"Expected chunk types: {expected_types}")
# Verify we have the expected number of chunks
- assert len(chunk_types) >= len(
- expected_types
- ), f"Expected at least {len(expected_types)} chunks, got {len(chunk_types)}"
+ assert len(chunk_types) >= len(expected_types), (
+ f"Expected at least {len(expected_types)} chunks, got {len(chunk_types)}"
+ )
# Verify key chunk types are present
assert "message_start" in chunk_types
@@ -143,15 +131,9 @@ def test_anthropic_stream_wrapper_content_after_stop_reason():
delta = message_delta_chunk.get("delta", {})
usage = message_delta_chunk.get("usage", {})
- assert (
- delta.get("stop_reason") == "end_turn"
- ), f"Expected stop_reason 'end_turn', got {delta.get('stop_reason')}"
- assert (
- usage.get("input_tokens") == 230
- ), f"Expected input_tokens 230, got {usage.get('input_tokens')}"
- assert (
- usage.get("output_tokens") == 65
- ), f"Expected output_tokens 65, got {usage.get('output_tokens')}"
+ assert delta.get("stop_reason") == "end_turn", f"Expected stop_reason 'end_turn', got {delta.get('stop_reason')}"
+ assert usage.get("input_tokens") == 230, f"Expected input_tokens 230, got {usage.get('input_tokens')}"
+ assert usage.get("output_tokens") == 65, f"Expected output_tokens 65, got {usage.get('output_tokens')}"
# Verify content_block_stop comes before message_delta
content_block_stop_index = None
@@ -165,9 +147,7 @@ def test_anthropic_stream_wrapper_content_after_stop_reason():
assert content_block_stop_index is not None, "content_block_stop not found"
assert message_delta_index is not None, "message_delta not found"
- assert (
- content_block_stop_index < message_delta_index
- ), "content_block_stop should come before message_delta"
+ assert content_block_stop_index < message_delta_index, "content_block_stop should come before message_delta"
@pytest.mark.asyncio
@@ -210,15 +190,9 @@ async def test_async_anthropic_stream_wrapper_content_after_stop_reason():
delta = message_delta_chunk.get("delta", {})
usage = message_delta_chunk.get("usage", {})
- assert (
- delta.get("stop_reason") == "end_turn"
- ), f"Expected stop_reason 'end_turn', got {delta.get('stop_reason')}"
- assert (
- usage.get("input_tokens") == 230
- ), f"Expected input_tokens 230, got {usage.get('input_tokens')}"
- assert (
- usage.get("output_tokens") == 65
- ), f"Expected output_tokens 65, got {usage.get('output_tokens')}"
+ assert delta.get("stop_reason") == "end_turn", f"Expected stop_reason 'end_turn', got {delta.get('stop_reason')}"
+ assert usage.get("input_tokens") == 230, f"Expected input_tokens 230, got {usage.get('input_tokens')}"
+ assert usage.get("output_tokens") == 65, f"Expected output_tokens 65, got {usage.get('output_tokens')}"
def test_usage_merging_behavior():
@@ -234,18 +208,10 @@ def test_usage_merging_behavior():
for chunk in wrapper:
chunks.append(chunk)
# If this is a message_delta with stop_reason, verify it has usage
- if (
- chunk.get("type") == "message_delta"
- and chunk.get("delta", {}).get("stop_reason") is not None
- ):
-
+ if chunk.get("type") == "message_delta" and chunk.get("delta", {}).get("stop_reason") is not None:
usage = chunk.get("usage", {})
- assert (
- usage.get("input_tokens") is not None
- ), "Usage should be merged with stop_reason chunk"
- assert (
- usage.get("output_tokens") is not None
- ), "Usage should be merged with stop_reason chunk"
+ assert usage.get("input_tokens") is not None, "Usage should be merged with stop_reason chunk"
+ assert usage.get("output_tokens") is not None, "Usage should be merged with stop_reason chunk"
break
@@ -273,12 +239,8 @@ def test_sse_wrapper_with_content_after_stop_reason():
lines = chunk_str.split("\n")
# Should have event and data lines
- assert any(
- line.startswith("event: ") for line in lines
- ), f"Missing event line in: {chunk_str}"
- assert any(
- line.startswith("data: ") for line in lines
- ), f"Missing data line in: {chunk_str}"
+ assert any(line.startswith("event: ") for line in lines), f"Missing event line in: {chunk_str}"
+ assert any(line.startswith("data: ") for line in lines), f"Missing data line in: {chunk_str}"
@pytest.mark.asyncio
@@ -306,12 +268,8 @@ async def test_async_sse_wrapper_with_content_after_stop_reason():
lines = chunk_str.split("\n")
# Should have event and data lines
- assert any(
- line.startswith("event: ") for line in lines
- ), f"Missing event line in: {chunk_str}"
- assert any(
- line.startswith("data: ") for line in lines
- ), f"Missing data line in: {chunk_str}"
+ assert any(line.startswith("event: ") for line in lines), f"Missing event line in: {chunk_str}"
+ assert any(line.startswith("data: ") for line in lines), f"Missing data line in: {chunk_str}"
if __name__ == "__main__":
diff --git a/tests/test_litellm/llms/anthropic/experimental_pass_through/messages/test_mcp_handler.py b/tests/test_litellm/llms/anthropic/experimental_pass_through/messages/test_mcp_handler.py
index b6914809263..c2daf11a1af 100644
--- a/tests/test_litellm/llms/anthropic/experimental_pass_through/messages/test_mcp_handler.py
+++ b/tests/test_litellm/llms/anthropic/experimental_pass_through/messages/test_mcp_handler.py
@@ -58,7 +58,7 @@ def test_anthropic_messages_handler_skips_the_gateway_on_recursion():
"litellm.llms.anthropic.experimental_pass_through.messages.mcp_handler.anthropic_messages_with_mcp",
new=AsyncMock(return_value={"routed": True}),
) as routed:
- with pytest.raises(ValueError, match='anthropic_messages_handler is not implemented for sync calls'):
+ with pytest.raises(ValueError, match="anthropic_messages_handler is not implemented for sync calls"):
anthropic_messages_handler(
max_tokens=100,
messages=[{"role": "user", "content": "hi"}],
@@ -77,7 +77,7 @@ def test_anthropic_messages_handler_leaves_native_tools_alone():
"litellm.llms.anthropic.experimental_pass_through.messages.mcp_handler.anthropic_messages_with_mcp",
new=AsyncMock(return_value={"routed": True}),
) as routed:
- with pytest.raises(ValueError, match='anthropic_messages_handler is not implemented for sync calls'):
+ with pytest.raises(ValueError, match="anthropic_messages_handler is not implemented for sync calls"):
anthropic_messages_handler(
max_tokens=100,
messages=[{"role": "user", "content": "hi"}],
@@ -114,9 +114,7 @@ def test_build_tool_result_message_uses_anthropic_tool_result_blocks():
message = _build_tool_result_message([{"tool_call_id": "toolu_1", "result": "9 sections", "name": "read_wiki"}])
assert message["role"] == "user"
- assert list(message["content"]) == [
- {"type": "tool_result", "tool_use_id": "toolu_1", "content": "9 sections"}
- ]
+ assert list(message["content"]) == [{"type": "tool_result", "tool_use_id": "toolu_1", "content": "9 sections"}]
@pytest.mark.asyncio
@@ -155,19 +153,22 @@ async def test_anthropic_messages_with_mcp_forwards_the_callers_mcp_credentials(
{"stop_reason": "end_turn", "content": [{"type": "text", "text": "done"}]},
]
- with patch.object(MCPRequestContext, "resolve", return_value=context), patch.object(
- mcp_handler.LiteLLM_Proxy_MCP_Handler
- if hasattr(mcp_handler, "LiteLLM_Proxy_MCP_Handler")
- else __import__(
- "litellm.responses.mcp.litellm_proxy_mcp_handler", fromlist=["LiteLLM_Proxy_MCP_Handler"]
- ).LiteLLM_Proxy_MCP_Handler,
- "_process_mcp_tools_without_openai_transform",
- new=process,
- ), patch(
- "litellm.responses.mcp.litellm_proxy_mcp_handler.LiteLLM_Proxy_MCP_Handler._execute_tool_calls",
- new=execute,
- ), patch(
- "litellm.anthropic_messages", new=AsyncMock(side_effect=responses)
+ with (
+ patch.object(MCPRequestContext, "resolve", return_value=context),
+ patch.object(
+ mcp_handler.LiteLLM_Proxy_MCP_Handler
+ if hasattr(mcp_handler, "LiteLLM_Proxy_MCP_Handler")
+ else __import__(
+ "litellm.responses.mcp.litellm_proxy_mcp_handler", fromlist=["LiteLLM_Proxy_MCP_Handler"]
+ ).LiteLLM_Proxy_MCP_Handler,
+ "_process_mcp_tools_without_openai_transform",
+ new=process,
+ ),
+ patch(
+ "litellm.responses.mcp.litellm_proxy_mcp_handler.LiteLLM_Proxy_MCP_Handler._execute_tool_calls",
+ new=execute,
+ ),
+ patch("litellm.anthropic_messages", new=AsyncMock(side_effect=responses)),
):
await mcp_handler.anthropic_messages_with_mcp(
max_tokens=100,
@@ -216,16 +217,17 @@ async def test_anthropic_messages_with_mcp_stops_when_every_tool_call_is_skipped
}
anthropic_messages_mock = AsyncMock(return_value=tool_use_response)
- with patch.object(
- MCPRequestContext, "resolve", return_value=MCPRequestContext(user_api_key_auth="auth")
- ), patch(
- "litellm.responses.mcp.litellm_proxy_mcp_handler.LiteLLM_Proxy_MCP_Handler._process_mcp_tools_without_openai_transform",
- new=AsyncMock(return_value=([], {})),
- ), patch(
- "litellm.responses.mcp.litellm_proxy_mcp_handler.LiteLLM_Proxy_MCP_Handler._execute_tool_calls",
- new=AsyncMock(return_value=[]),
- ), patch(
- "litellm.anthropic_messages", new=anthropic_messages_mock
+ with (
+ patch.object(MCPRequestContext, "resolve", return_value=MCPRequestContext(user_api_key_auth="auth")),
+ patch(
+ "litellm.responses.mcp.litellm_proxy_mcp_handler.LiteLLM_Proxy_MCP_Handler._process_mcp_tools_without_openai_transform",
+ new=AsyncMock(return_value=([], {})),
+ ),
+ patch(
+ "litellm.responses.mcp.litellm_proxy_mcp_handler.LiteLLM_Proxy_MCP_Handler._execute_tool_calls",
+ new=AsyncMock(return_value=[]),
+ ),
+ patch("litellm.anthropic_messages", new=anthropic_messages_mock),
):
result = await mcp_handler.anthropic_messages_with_mcp(
max_tokens=100,
diff --git a/tests/test_litellm/llms/anthropic/experimental_pass_through/messages/test_parallel_tool_calls.py b/tests/test_litellm/llms/anthropic/experimental_pass_through/messages/test_parallel_tool_calls.py
index 137286a18c4..b13dc850f61 100644
--- a/tests/test_litellm/llms/anthropic/experimental_pass_through/messages/test_parallel_tool_calls.py
+++ b/tests/test_litellm/llms/anthropic/experimental_pass_through/messages/test_parallel_tool_calls.py
@@ -1,7 +1,6 @@
from typing import List
-
from litellm.llms.anthropic.experimental_pass_through.adapters.streaming_iterator import (
AnthropicStreamWrapper,
)
@@ -53,9 +52,7 @@ def construct_text_chunk(text: str) -> ModelResponseStream:
)
-def construct_split_tool_call(
- id: str, function_name: str, function_arg_parts: List[str]
-) -> List[ModelResponseStream]:
+def construct_split_tool_call(id: str, function_name: str, function_arg_parts: List[str]) -> List[ModelResponseStream]:
return [
# https://platform.openai.com/docs/guides/function-calling#streaming
ModelResponseStream(
@@ -144,10 +141,7 @@ def test_anthropic_stream_wrapper_single_tool_call():
get_weather_calls = 0
for chunk in chunks:
- if (
- chunk.get("type") == "content_block_start"
- and chunk["content_block"]["type"] == "tool_use"
- ):
+ if chunk.get("type") == "content_block_start" and chunk["content_block"]["type"] == "tool_use":
if chunk["content_block"]["name"] == "get_weather":
get_weather_calls += 1
@@ -203,10 +197,7 @@ def test_anthropic_stream_wrapper_back_to_back_tool_calls():
get_weather_calls = 0
for chunk in chunks:
- if (
- chunk.get("type") == "content_block_start"
- and chunk["content_block"]["type"] == "tool_use"
- ):
+ if chunk.get("type") == "content_block_start" and chunk["content_block"]["type"] == "tool_use":
if chunk["content_block"]["name"] == "get_weather":
get_weather_calls += 1
@@ -218,9 +209,7 @@ def test_anthropic_stream_wrapper_interleaved_tool_calls_and_text():
*construct_split_tool_call("tooluse_foo", "get_weather", ['{"city":', '"NY"}']),
construct_text_chunk("The weather is nice today."),
*construct_split_tool_call("tooluse_bar", "get_weather", ['{"city":', '"SF"}']),
- *construct_split_tool_call(
- "tooluse_bar", "get_weather", ['{"city":', '"CHI"}']
- ),
+ *construct_split_tool_call("tooluse_bar", "get_weather", ['{"city":', '"CHI"}']),
construct_text_chunk("The weather is not so nice today."),
ModelResponseStream(
choices=[
@@ -280,8 +269,7 @@ def test_anthropic_stream_wrapper_interleaved_tool_calls_and_text():
text_deltas = [
chunk["delta"]["text"]
for chunk in chunks
- if chunk.get("type") == "content_block_delta"
- and chunk["delta"].get("type") == "text_delta"
+ if chunk.get("type") == "content_block_delta" and chunk["delta"].get("type") == "text_delta"
]
assert text_deltas == [
"The weather is nice today.",
@@ -291,10 +279,7 @@ def test_anthropic_stream_wrapper_interleaved_tool_calls_and_text():
get_weather_calls = 0
for chunk in chunks:
- if (
- chunk.get("type") == "content_block_start"
- and chunk["content_block"]["type"] == "tool_use"
- ):
+ if chunk.get("type") == "content_block_start" and chunk["content_block"]["type"] == "tool_use":
if chunk["content_block"]["name"] == "get_weather":
get_weather_calls += 1
diff --git a/tests/test_litellm/llms/anthropic/experimental_pass_through/messages/test_reasoning_auto_summary_messages.py b/tests/test_litellm/llms/anthropic/experimental_pass_through/messages/test_reasoning_auto_summary_messages.py
index f478bbb9b50..dacb59c9fc0 100644
--- a/tests/test_litellm/llms/anthropic/experimental_pass_through/messages/test_reasoning_auto_summary_messages.py
+++ b/tests/test_litellm/llms/anthropic/experimental_pass_through/messages/test_reasoning_auto_summary_messages.py
@@ -29,13 +29,12 @@ def _call_handler_and_capture_optional_params(thinking=None, **extra_kwargs):
"""
captured = {}
- with patch(
- "litellm.llms.anthropic.experimental_pass_through.messages.handler."
- "base_llm_http_handler"
- ) as mock_handler, patch(
- "litellm.llms.anthropic.experimental_pass_through.messages.handler."
- "ProviderConfigManager"
- ) as mock_pcm:
+ with (
+ patch(
+ "litellm.llms.anthropic.experimental_pass_through.messages.handler.base_llm_http_handler"
+ ) as mock_handler,
+ patch("litellm.llms.anthropic.experimental_pass_through.messages.handler.ProviderConfigManager") as mock_pcm,
+ ):
# Make get_provider_anthropic_messages_config return a non-None config
# so the handler takes the native Anthropic path
mock_pcm.get_provider_anthropic_messages_config.return_value = MagicMock()
@@ -71,9 +70,7 @@ class TestReasoningAutoSummaryMessages:
def test_adaptive_thinking_gets_display_summarized(self):
"""reasoning_auto_summary=True + thinking.type='adaptive' -> display='summarized'."""
with patch.object(litellm, "reasoning_auto_summary", True):
- params = _call_handler_and_capture_optional_params(
- thinking={"type": "adaptive", "budget_tokens": 5000}
- )
+ params = _call_handler_and_capture_optional_params(thinking={"type": "adaptive", "budget_tokens": 5000})
thinking = params.get("thinking", {})
assert thinking.get("display") == "summarized"
assert thinking.get("type") == "adaptive"
@@ -82,9 +79,7 @@ class TestReasoningAutoSummaryMessages:
def test_enabled_thinking_gets_display_summarized(self):
"""reasoning_auto_summary=True + thinking.type='enabled' -> display='summarized'."""
with patch.object(litellm, "reasoning_auto_summary", True):
- params = _call_handler_and_capture_optional_params(
- thinking={"type": "enabled", "budget_tokens": 10000}
- )
+ params = _call_handler_and_capture_optional_params(thinking={"type": "enabled", "budget_tokens": 10000})
thinking = params.get("thinking", {})
assert thinking.get("display") == "summarized"
assert thinking.get("type") == "enabled"
@@ -92,18 +87,14 @@ class TestReasoningAutoSummaryMessages:
def test_disabled_thinking_no_display(self):
"""reasoning_auto_summary=True + thinking.type='disabled' -> display NOT set."""
with patch.object(litellm, "reasoning_auto_summary", True):
- params = _call_handler_and_capture_optional_params(
- thinking={"type": "disabled"}
- )
+ params = _call_handler_and_capture_optional_params(thinking={"type": "disabled"})
thinking = params.get("thinking", {})
assert "display" not in thinking
def test_no_injection_when_flag_false(self):
"""reasoning_auto_summary=False + active thinking -> display NOT set."""
with patch.object(litellm, "reasoning_auto_summary", False):
- params = _call_handler_and_capture_optional_params(
- thinking={"type": "enabled", "budget_tokens": 10000}
- )
+ params = _call_handler_and_capture_optional_params(thinking={"type": "enabled", "budget_tokens": 10000})
thinking = params.get("thinking", {})
assert "display" not in thinking
@@ -117,12 +108,11 @@ class TestReasoningAutoSummaryMessages:
def test_env_var_enables_auto_summary(self):
"""LITELLM_REASONING_AUTO_SUMMARY=true env var enables the feature."""
- with patch.object(litellm, "reasoning_auto_summary", False), patch.dict(
- os.environ, {"LITELLM_REASONING_AUTO_SUMMARY": "true"}
+ with (
+ patch.object(litellm, "reasoning_auto_summary", False),
+ patch.dict(os.environ, {"LITELLM_REASONING_AUTO_SUMMARY": "true"}),
):
- params = _call_handler_and_capture_optional_params(
- thinking={"type": "adaptive", "budget_tokens": 5000}
- )
+ params = _call_handler_and_capture_optional_params(thinking={"type": "adaptive", "budget_tokens": 5000})
thinking = params.get("thinking", {})
assert thinking.get("display") == "summarized"
diff --git a/tests/test_litellm/llms/anthropic/experimental_pass_through/messages/test_reasoning_effort_translation.py b/tests/test_litellm/llms/anthropic/experimental_pass_through/messages/test_reasoning_effort_translation.py
index f393a7b50b1..b7b628f94a4 100644
--- a/tests/test_litellm/llms/anthropic/experimental_pass_through/messages/test_reasoning_effort_translation.py
+++ b/tests/test_litellm/llms/anthropic/experimental_pass_through/messages/test_reasoning_effort_translation.py
@@ -17,7 +17,6 @@ from litellm.llms.bedrock.messages.invoke_transformations.anthropic_claude3_tran
)
-
@pytest.mark.parametrize(
"reasoning_effort,expected_effort",
[
@@ -29,9 +28,7 @@ from litellm.llms.bedrock.messages.invoke_transformations.anthropic_claude3_tran
("max", "max"),
],
)
-def test_reasoning_effort_maps_to_output_config_for_adaptive_model(
- reasoning_effort, expected_effort
-):
+def test_reasoning_effort_maps_to_output_config_for_adaptive_model(reasoning_effort, expected_effort):
config = AnthropicMessagesConfig()
optional_params = {"max_tokens": 1024, "reasoning_effort": reasoning_effort}
@@ -141,9 +138,7 @@ def test_reasoning_effort_unsupported_tier_raises_400_messages(model, bad_effort
("invoke/us.anthropic.claude-opus-4-7", "xhigh", "xhigh"),
],
)
-def test_bedrock_invoke_messages_clamps_effort_to_ceiling(
- local_model_cost_map, model, effort, expected_effort
-):
+def test_bedrock_invoke_messages_clamps_effort_to_ceiling(local_model_cost_map, model, effort, expected_effort):
"""Bedrock Invoke /v1/messages degrades effort to the model's ceiling.
Claude Code "goal mode" sends ``xhigh``; Opus 4.6 must clamp to ``max``
@@ -189,9 +184,7 @@ def test_bedrock_invoke_messages_rejects_xhigh_without_ceiling(local_model_cost_
"bedrock/invoke/us.anthropic.claude-sonnet-4-6",
],
)
-def test_reasoning_effort_max_accepted_on_sonnet_46_messages(
- local_model_cost_map, model
-):
+def test_reasoning_effort_max_accepted_on_sonnet_46_messages(local_model_cost_map, model):
config = AnthropicMessagesConfig()
optional_params = {"max_tokens": 1024, "reasoning_effort": "max"}
@@ -249,9 +242,7 @@ def test_explicit_thinking_wins_over_reasoning_effort():
def test_reasoning_effort_in_supported_params():
config = AnthropicMessagesConfig()
- assert "reasoning_effort" in config.get_supported_anthropic_messages_params(
- "claude-opus-4-7"
- )
+ assert "reasoning_effort" in config.get_supported_anthropic_messages_params("claude-opus-4-7")
@pytest.mark.parametrize(
@@ -265,9 +256,7 @@ def test_reasoning_effort_in_supported_params():
"vertex_ai/claude-opus-4-6",
],
)
-def test_legacy_thinking_high_budget_clamps_to_high_when_xhigh_unsupported(
- local_model_cost_map, model
-):
+def test_legacy_thinking_high_budget_clamps_to_high_when_xhigh_unsupported(local_model_cost_map, model):
"""Claude Code sends ``thinking.budget_tokens=31999``; Sonnet 4.6 and Opus 4.6
have no ``xhigh`` tier, so the translator must emit ``high`` rather than the
provider-invalid ``xhigh`` (regression for issue #29282)."""
@@ -317,9 +306,7 @@ def test_legacy_thinking_high_budget_keeps_xhigh_when_supported():
"bedrock/invoke/us.anthropic.claude-opus-4-8",
],
)
-def test_legacy_thinking_translates_to_adaptive_for_opus_48(
- model, local_model_cost_map
-):
+def test_legacy_thinking_translates_to_adaptive_for_opus_48(model, local_model_cost_map):
"""Regression for issue #29188: Opus 4.8 requires adaptive thinking, but the
legacy ``thinking.type='enabled'`` shape was passed through unchanged for
Bedrock 4.8 (its cost-map entry lacked ``supports_adaptive_thinking`` and the
@@ -419,9 +406,7 @@ def test_legacy_thinking_left_untouched_on_non_adaptive_model():
("claude-sonnet-4-5", False),
],
)
-def test_disabled_thinking_omitted_for_always_on_models_messages(
- local_model_cost_map, model, expected_dropped
-):
+def test_disabled_thinking_omitted_for_always_on_models_messages(local_model_cost_map, model, expected_dropped):
"""/v1/messages: ``thinking={"type": "disabled"}`` is omitted for always-on-thinking
models and forwarded verbatim for models that accept it."""
config = AnthropicMessagesConfig()
diff --git a/tests/test_litellm/llms/anthropic/experimental_pass_through/messages/test_request_optional_param_utils.py b/tests/test_litellm/llms/anthropic/experimental_pass_through/messages/test_request_optional_param_utils.py
index dc2e107928f..95696a536f1 100644
--- a/tests/test_litellm/llms/anthropic/experimental_pass_through/messages/test_request_optional_param_utils.py
+++ b/tests/test_litellm/llms/anthropic/experimental_pass_through/messages/test_request_optional_param_utils.py
@@ -23,11 +23,7 @@ def test_optional_param_filtering_unchanged():
"not_a_real_param": "drop me", # invalid key dropped
"stream": True,
}
- result = (
- AnthropicMessagesRequestUtils.get_requested_anthropic_messages_optional_param(
- params
- )
- )
+ result = AnthropicMessagesRequestUtils.get_requested_anthropic_messages_optional_param(params)
assert result == {"temperature": 0.5, "tools": [{"name": "x"}], "stream": True}
assert "top_p" not in result
assert "not_a_real_param" not in result
@@ -37,9 +33,7 @@ def test_valid_keys_are_memoized():
_anthropic_messages_optional_param_keys.cache_clear()
first = _anthropic_messages_optional_param_keys()
for _ in range(50):
- AnthropicMessagesRequestUtils.get_requested_anthropic_messages_optional_param(
- {"temperature": 0.1}
- )
+ AnthropicMessagesRequestUtils.get_requested_anthropic_messages_optional_param({"temperature": 0.1})
info = _anthropic_messages_optional_param_keys.cache_info()
# Resolved exactly once despite many calls.
assert info.misses == 1
@@ -51,23 +45,16 @@ def test_valid_keys_are_memoized():
def test_empty_params():
- assert (
- AnthropicMessagesRequestUtils.get_requested_anthropic_messages_optional_param(
- {}
- )
- == {}
- )
+ assert AnthropicMessagesRequestUtils.get_requested_anthropic_messages_optional_param({}) == {}
def test_drop_params_strips_speed_for_unsupported_model():
original = litellm.drop_params
litellm.drop_params = True
try:
- result = (
- AnthropicMessagesRequestUtils.get_requested_anthropic_messages_optional_param(
- params={"speed": "fast", "temperature": 0.5},
- model="claude-sonnet-4-6",
- )
+ result = AnthropicMessagesRequestUtils.get_requested_anthropic_messages_optional_param(
+ params={"speed": "fast", "temperature": 0.5},
+ model="claude-sonnet-4-6",
)
finally:
litellm.drop_params = original
@@ -80,11 +67,9 @@ def test_drop_params_keeps_speed_for_supporting_model():
original = litellm.drop_params
litellm.drop_params = True
try:
- result = (
- AnthropicMessagesRequestUtils.get_requested_anthropic_messages_optional_param(
- params={"speed": "fast"},
- model="claude-opus-4-6",
- )
+ result = AnthropicMessagesRequestUtils.get_requested_anthropic_messages_optional_param(
+ params={"speed": "fast"},
+ model="claude-opus-4-6",
)
finally:
litellm.drop_params = original
diff --git a/tests/test_litellm/llms/anthropic/experimental_pass_through/messages/test_sse_wrapper.py b/tests/test_litellm/llms/anthropic/experimental_pass_through/messages/test_sse_wrapper.py
index bebdbe9f512..cd5f3f3f327 100644
--- a/tests/test_litellm/llms/anthropic/experimental_pass_through/messages/test_sse_wrapper.py
+++ b/tests/test_litellm/llms/anthropic/experimental_pass_through/messages/test_sse_wrapper.py
@@ -1,4 +1,3 @@
-
import pytest
from fastapi.testclient import TestClient
@@ -14,25 +13,13 @@ class MockCompletionStream:
def __init__(self):
self.responses = [
ModelResponseStream(
- choices=[
- StreamingChoices(
- delta=Delta(content="Hello"), index=0, finish_reason=None
- )
- ],
+ choices=[StreamingChoices(delta=Delta(content="Hello"), index=0, finish_reason=None)],
),
ModelResponseStream(
- choices=[
- StreamingChoices(
- delta=Delta(content=" World"), index=0, finish_reason=None
- )
- ],
+ choices=[StreamingChoices(delta=Delta(content=" World"), index=0, finish_reason=None)],
),
ModelResponseStream(
- choices=[
- StreamingChoices(
- delta=Delta(content=""), index=0, finish_reason="stop"
- )
- ],
+ choices=[StreamingChoices(delta=Delta(content=""), index=0, finish_reason="stop")],
),
]
self.index = 0
@@ -50,9 +37,7 @@ class MockCompletionStream:
def test_anthropic_sse_wrapper_format():
"""Test that the SSE wrapper produces proper event and data formatting"""
- wrapper = AnthropicStreamWrapper(
- completion_stream=MockCompletionStream(), model="claude-3"
- )
+ wrapper = AnthropicStreamWrapper(completion_stream=MockCompletionStream(), model="claude-3")
# Get the first chunk from the SSE wrapper
first_chunk = next(wrapper.anthropic_sse_wrapper())
@@ -73,9 +58,7 @@ def test_anthropic_sse_wrapper_format():
def test_anthropic_sse_wrapper_event_types():
"""Test that different chunk types produce correct event types"""
- wrapper = AnthropicStreamWrapper(
- completion_stream=MockCompletionStream(), model="claude-3"
- )
+ wrapper = AnthropicStreamWrapper(completion_stream=MockCompletionStream(), model="claude-3")
chunks = []
for chunk in wrapper.anthropic_sse_wrapper():
@@ -104,18 +87,10 @@ async def test_async_anthropic_sse_wrapper():
def __init__(self):
self.responses = [
ModelResponseStream(
- choices=[
- StreamingChoices(
- delta=Delta(content="Hello"), index=0, finish_reason=None
- )
- ],
+ choices=[StreamingChoices(delta=Delta(content="Hello"), index=0, finish_reason=None)],
),
ModelResponseStream(
- choices=[
- StreamingChoices(
- delta=Delta(content=" World"), index=0, finish_reason=None
- )
- ],
+ choices=[StreamingChoices(delta=Delta(content=" World"), index=0, finish_reason=None)],
),
]
self.index = 0
@@ -130,9 +105,7 @@ async def test_async_anthropic_sse_wrapper():
self.index += 1
return response
- wrapper = AnthropicStreamWrapper(
- completion_stream=AsyncMockCompletionStream(), model="claude-3"
- )
+ wrapper = AnthropicStreamWrapper(completion_stream=AsyncMockCompletionStream(), model="claude-3")
# Get the first chunk from the async SSE wrapper
first_chunk = None
diff --git a/tests/test_litellm/llms/anthropic/experimental_pass_through/messages/test_streaming_iterator.py b/tests/test_litellm/llms/anthropic/experimental_pass_through/messages/test_streaming_iterator.py
index f33bb3dda8b..40218e8b0f2 100644
--- a/tests/test_litellm/llms/anthropic/experimental_pass_through/messages/test_streaming_iterator.py
+++ b/tests/test_litellm/llms/anthropic/experimental_pass_through/messages/test_streaming_iterator.py
@@ -138,7 +138,7 @@ async def test_async_sse_wrapper_treats_message_stop_bytes_as_complete():
def test_is_message_stop_chunk():
assert _is_message_stop_chunk({"type": "message_stop"}) is True
assert _is_message_stop_chunk({"type": "message_delta"}) is False
- assert _is_message_stop_chunk(b'event: message_stop\ndata: {}\n\n') is True
+ assert _is_message_stop_chunk(b"event: message_stop\ndata: {}\n\n") is True
assert _is_message_stop_chunk(b"raw-bytes") is False
assert _is_message_stop_chunk("message_stop") is False
@@ -150,7 +150,7 @@ def test_is_message_stop_chunk_ignores_substring_in_payload():
not be treated as a terminal stop event.
"""
delta_frame_with_substring = (
- b'event: content_block_delta\n'
+ b"event: content_block_delta\n"
b'data: {"type": "content_block_delta", "delta": '
b'{"type": "input_json_delta", "partial_json": "\\"message_stop\\""}}\n\n'
)
@@ -164,10 +164,11 @@ async def test_async_sse_wrapper_emits_error_when_bytes_stream_only_mentions_mes
payload text contains `message_stop` (but never emits the actual
`event: message_stop` frame) must still be flagged as incomplete.
"""
+
async def _byte_stream():
yield b'event: message_start\ndata: {"type": "message_start"}\n\n'
yield (
- b'event: content_block_delta\n'
+ b"event: content_block_delta\n"
b'data: {"type": "content_block_delta", "delta": '
b'{"type": "input_json_delta", "partial_json": "\\"message_stop\\""}}\n\n'
)
diff --git a/tests/test_litellm/llms/anthropic/experimental_pass_through/responses_adapters/test_responses_adapters_transformation.py b/tests/test_litellm/llms/anthropic/experimental_pass_through/responses_adapters/test_responses_adapters_transformation.py
index 964f4b9f68b..80de6665ac9 100644
--- a/tests/test_litellm/llms/anthropic/experimental_pass_through/responses_adapters/test_responses_adapters_transformation.py
+++ b/tests/test_litellm/llms/anthropic/experimental_pass_through/responses_adapters/test_responses_adapters_transformation.py
@@ -1308,9 +1308,7 @@ class TestToolResultImages:
},
{
"role": "user",
- "content": [
- {"type": "tool_result", "tool_use_id": "toolu_01", "content": tool_result_content}
- ],
+ "content": [{"type": "tool_result", "tool_use_id": "toolu_01", "content": tool_result_content}],
},
]
@@ -1473,7 +1471,9 @@ class TestPromptCacheBreakpointToResponses:
]
def test_system_without_breakpoint_still_becomes_instructions(self):
- request = _make_request(system=[{"type": "text", "text": "Be concise."}, {"type": "text", "text": "Be helpful."}])
+ request = _make_request(
+ system=[{"type": "text", "text": "Be concise."}, {"type": "text", "text": "Be helpful."}]
+ )
kwargs = _ADAPTER.translate_request(request)
assert kwargs["instructions"] == "Be concise.\nBe helpful."
assert kwargs["input"] == [
diff --git a/tests/test_litellm/llms/anthropic/experimental_pass_through/test_reasoning_effort_fields.py b/tests/test_litellm/llms/anthropic/experimental_pass_through/test_reasoning_effort_fields.py
index 08fef8c6a24..9c67aa96247 100644
--- a/tests/test_litellm/llms/anthropic/experimental_pass_through/test_reasoning_effort_fields.py
+++ b/tests/test_litellm/llms/anthropic/experimental_pass_through/test_reasoning_effort_fields.py
@@ -188,9 +188,7 @@ class TestNormalizeReasoningEffortValue:
"litellm.utils.get_model_info",
return_value=_mock_model_info(supports_minimal_reasoning_effort=True),
):
- assert (
- normalize_reasoning_effort_value("minimal", model="test") == "minimal"
- )
+ assert normalize_reasoning_effort_value("minimal", model="test") == "minimal"
def test_minimal_degrades_to_low(self):
with patch(
@@ -239,9 +237,7 @@ class TestAdapterAdaptiveThinking:
)
adapter = LiteLLMAnthropicMessagesAdapter()
- result = adapter.translate_anthropic_thinking_to_reasoning_effort(
- {"type": "adaptive"}
- )
+ result = adapter.translate_anthropic_thinking_to_reasoning_effort({"type": "adaptive"})
assert result == "medium"
def test_messages_adapter_adaptive_overridden_by_output_config(self):
diff --git a/tests/test_litellm/llms/anthropic/files/test_anthropic_files_transformation.py b/tests/test_litellm/llms/anthropic/files/test_anthropic_files_transformation.py
index 01aa4799c8d..f385c2f2211 100644
--- a/tests/test_litellm/llms/anthropic/files/test_anthropic_files_transformation.py
+++ b/tests/test_litellm/llms/anthropic/files/test_anthropic_files_transformation.py
@@ -221,10 +221,7 @@ class TestAnthropicFilesConfig:
litellm_params={},
)
- assert (
- url
- == f"{ANTHROPIC_FILES_API_BASE}/v1/files/..%2F..%2Fv1%2Fmessages%2Fbatches%3Flimit%3D1%23frag"
- )
+ assert url == f"{ANTHROPIC_FILES_API_BASE}/v1/files/..%2F..%2Fv1%2Fmessages%2Fbatches%3Flimit%3D1%23frag"
assert params == {}
def test_transform_retrieve_file_response(self):
diff --git a/tests/test_litellm/llms/anthropic/messages/test_advisor_orchestration.py b/tests/test_litellm/llms/anthropic/messages/test_advisor_orchestration.py
index da5b5ac3867..ab14d914640 100644
--- a/tests/test_litellm/llms/anthropic/messages/test_advisor_orchestration.py
+++ b/tests/test_litellm/llms/anthropic/messages/test_advisor_orchestration.py
@@ -101,9 +101,7 @@ async def test_anthropic_native_interceptor_skipped():
)
h = AdvisorOrchestrationHandler()
- assert not h.can_handle(
- [ADVISOR_TOOL], "anthropic"
- ), "Interceptor must NOT trigger for anthropic provider"
+ assert not h.can_handle([ADVISOR_TOOL], "anthropic"), "Interceptor must NOT trigger for anthropic provider"
# ---------------------------------------------------------------------------
@@ -204,9 +202,7 @@ async def test_loop_one_advisor_call():
assert "is_prime" in texts[0]["text"]
# No advisor tool_use blocks in final response
- advisor_uses = [
- b for b in content if b.get("type") == "tool_use" and b.get("name") == "advisor"
- ]
+ advisor_uses = [b for b in content if b.get("type") == "tool_use" and b.get("name") == "advisor"]
assert len(advisor_uses) == 0
@@ -366,9 +362,7 @@ async def test_prior_advisor_blocks_replaced_in_history():
# Text block with advisor feedback must be present
text_blocks = [b for b in content if b.get("type") == "text"]
- feedback_blocks = [
- b for b in text_blocks if "advisor_feedback" in b.get("text", "")
- ]
+ feedback_blocks = [b for b in text_blocks if "advisor_feedback" in b.get("text", "")]
assert len(feedback_blocks) >= 1
assert "trial division" in feedback_blocks[0]["text"]
@@ -707,11 +701,7 @@ async def test_advisor_ignores_tool_credentials_when_clientside_disabled():
with patch.dict(
sys.modules,
- {
- "litellm.proxy.proxy_server": _fake_proxy_server(
- {"allow_client_side_credentials": False}
- )
- },
+ {"litellm.proxy.proxy_server": _fake_proxy_server({"allow_client_side_credentials": False})},
):
captured = await _run_advisor_and_capture_subcall_kwargs()
assert captured["api_key"] is None
@@ -726,11 +716,7 @@ async def test_advisor_uses_tool_credentials_when_clientside_enabled():
with patch.dict(
sys.modules,
- {
- "litellm.proxy.proxy_server": _fake_proxy_server(
- {"allow_client_side_credentials": True}
- )
- },
+ {"litellm.proxy.proxy_server": _fake_proxy_server({"allow_client_side_credentials": True})},
):
captured = await _run_advisor_and_capture_subcall_kwargs()
assert captured["api_key"] == "sk-other"
diff --git a/tests/test_litellm/llms/anthropic/test_anthropic_common_utils.py b/tests/test_litellm/llms/anthropic/test_anthropic_common_utils.py
index 52713f32374..dd6f7e0ff8d 100644
--- a/tests/test_litellm/llms/anthropic/test_anthropic_common_utils.py
+++ b/tests/test_litellm/llms/anthropic/test_anthropic_common_utils.py
@@ -2316,6 +2316,31 @@ class TestWifServerOwnedAuthHeaderStrip:
assert all(caller_key not in value for value in headers.values())
assert headers["user-agent"] == "caller/1.0"
+ @pytest.mark.parametrize("header_name", PROXY_CREDENTIAL_HEADER_NAMES)
+ def test_files_surface_strips_caller_credentials_too(self, monkeypatch, wif_engine, header_name):
+ """The files surface builds its own headers, so it needs the same strip the chat surface
+ has: without it a minted federation Bearer travels beside the caller's own credential."""
+ from litellm.llms.anthropic.files.transformation import AnthropicFilesConfig
+
+ for name, value in WIF_ENV.items():
+ monkeypatch.setenv(name, value)
+ caller_key = "sk-litellm-CALLER-VIRTUAL-KEY"
+
+ headers = AnthropicFilesConfig().validate_environment(
+ headers={header_name.title(): caller_key, "user-agent": "caller/1.0"},
+ model="claude-sonnet-4-5",
+ messages=[],
+ optional_params={},
+ litellm_params={},
+ api_key=None,
+ api_base=None,
+ )
+
+ assert headers["authorization"] == f"Bearer {FAKE_MINTED_TOKEN}"
+ assert header_name == "authorization" or header_name not in {name.lower() for name in headers}
+ assert all(caller_key not in value for value in headers.values())
+ assert headers["user-agent"] == "caller/1.0"
+
def test_no_mint_preserves_caller_supplied_authorization(self, monkeypatch, clean_anthropic_env):
"""No-regression: LiteLLM deliberately lets a caller-forwarded credential
header ride alongside a statically configured ANTHROPIC_API_KEY, because the
@@ -3019,6 +3044,23 @@ class TestModelDiscovery:
WIF source configured in litellm_params, rather than the environment, could not
discover)."""
+ @pytest.mark.parametrize(
+ "configured_base", ["https://api.anthropic.com/v1", "https://api.anthropic.com/v1/messages"]
+ )
+ def test_discovery_does_not_double_the_version_segment(self, monkeypatch, clean_anthropic_env, configured_base):
+ """Regression: /v1/models is appended here, so a base an operator already wrote as
+ .../v1 (or the chat URL they copied) would be asked for /v1/v1/models and 404."""
+ from litellm.llms.anthropic.common_utils import AnthropicModelInfo
+
+ monkeypatch.setenv("ANTHROPIC_API_KEY", FAKE_REGULAR_KEY)
+ client = RecordingModelsClient([{"data": [{"id": "claude-a"}], "has_more": False, "last_id": "claude-a"}])
+ monkeypatch.setattr("litellm.module_level_client", client)
+
+ models = AnthropicModelInfo().get_models(api_base=configured_base)
+
+ assert models == ["anthropic/claude-a"]
+ assert client.calls[0].url == "https://api.anthropic.com/v1/models"
+
def test_get_models_paginates_via_has_more_and_last_id(self, monkeypatch, clean_anthropic_env):
from litellm.llms.anthropic.common_utils import AnthropicModelInfo
diff --git a/tests/test_litellm/llms/anthropic/test_anthropic_count_tokens_transformation.py b/tests/test_litellm/llms/anthropic/test_anthropic_count_tokens_transformation.py
index ddac561f337..a436744f648 100644
--- a/tests/test_litellm/llms/anthropic/test_anthropic_count_tokens_transformation.py
+++ b/tests/test_litellm/llms/anthropic/test_anthropic_count_tokens_transformation.py
@@ -1,4 +1,3 @@
-
from litellm.llms.anthropic.count_tokens.transformation import (
AnthropicCountTokensConfig,
)
diff --git a/tests/test_litellm/llms/anthropic/test_azure_ai_cache_pricing.py b/tests/test_litellm/llms/anthropic/test_azure_ai_cache_pricing.py
index 69738118d7a..8b22acc4a23 100644
--- a/tests/test_litellm/llms/anthropic/test_azure_ai_cache_pricing.py
+++ b/tests/test_litellm/llms/anthropic/test_azure_ai_cache_pricing.py
@@ -3,8 +3,6 @@ Test that Azure AI Anthropic models have cache pricing configured.
Verifies the fix for issue #19532.
"""
-
-
import litellm
from litellm import get_model_info
from litellm.litellm_core_utils.get_model_cost_map import get_model_cost_map
@@ -27,16 +25,11 @@ def reload_model_costs():
("claude-sonnet-4-5", 3.75e-06, 3e-07),
],
)
-def test_azure_ai_claude_cache_pricing(
- model, expected_cache_creation_cost, expected_cache_read_cost
-):
+def test_azure_ai_claude_cache_pricing(model, expected_cache_creation_cost, expected_cache_read_cost):
"""Test that Azure AI Claude models have correct cache pricing."""
model_info = get_model_info(model=model, custom_llm_provider="azure_ai")
assert model_info.get("cache_creation_input_token_cost") is not None
assert model_info.get("cache_read_input_token_cost") is not None
- assert (
- model_info.get("cache_creation_input_token_cost")
- == expected_cache_creation_cost
- )
+ assert model_info.get("cache_creation_input_token_cost") == expected_cache_creation_cost
assert model_info.get("cache_read_input_token_cost") == expected_cache_read_cost
diff --git a/tests/test_litellm/llms/anthropic/test_cost_calculation_dict_safety.py b/tests/test_litellm/llms/anthropic/test_cost_calculation_dict_safety.py
index 5c88ae17679..5a262153272 100644
--- a/tests/test_litellm/llms/anthropic/test_cost_calculation_dict_safety.py
+++ b/tests/test_litellm/llms/anthropic/test_cost_calculation_dict_safety.py
@@ -5,7 +5,6 @@ being either a ``dict`` or a ``ServerToolUse`` pydantic instance.
See https://github.com/BerriAI/litellm/issues/26153.
"""
-
import pytest
@@ -57,7 +56,8 @@ def test_get_cost_for_anthropic_web_search_with_dict_server_tool_use():
info = _make_model_info(cost_per_query=0.01)
cost = get_cost_for_anthropic_web_search(
- model_info=info, usage=usage # type: ignore[arg-type]
+ model_info=info,
+ usage=usage, # type: ignore[arg-type]
)
assert cost == pytest.approx(0.03)
@@ -68,7 +68,8 @@ def test_get_cost_for_anthropic_web_search_with_pydantic_server_tool_use():
info = _make_model_info(cost_per_query=0.01)
cost = get_cost_for_anthropic_web_search(
- model_info=info, usage=usage # type: ignore[arg-type]
+ model_info=info,
+ usage=usage, # type: ignore[arg-type]
)
assert cost == pytest.approx(0.03)
@@ -79,7 +80,8 @@ def test_get_cost_for_anthropic_web_search_with_none_server_tool_use():
info = _make_model_info(cost_per_query=0.01)
cost = get_cost_for_anthropic_web_search(
- model_info=info, usage=usage # type: ignore[arg-type]
+ model_info=info,
+ usage=usage, # type: ignore[arg-type]
)
assert cost == 0.0
diff --git a/tests/test_litellm/llms/anthropic/test_count_tokens_oauth.py b/tests/test_litellm/llms/anthropic/test_count_tokens_oauth.py
index bcfc56577eb..1551292b246 100644
--- a/tests/test_litellm/llms/anthropic/test_count_tokens_oauth.py
+++ b/tests/test_litellm/llms/anthropic/test_count_tokens_oauth.py
@@ -10,9 +10,7 @@ Regression test for https://github.com/BerriAI/litellm/issues/22040
import os
import sys
-sys.path.insert(
- 0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../../../../.."))
-)
+sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../../../../..")))
from litellm.llms.anthropic.count_tokens.transformation import (
AnthropicCountTokensConfig,
@@ -78,9 +76,5 @@ class TestCountTokensOAuthHeaders:
headers = config.get_required_headers(FAKE_OAUTH_TOKEN)
beta_value = headers.get("anthropic-beta", "")
- assert (
- "token-counting" in beta_value
- ), f"token-counting beta missing from OAuth headers: {beta_value}"
- assert (
- "oauth-2025-04-20" in beta_value
- ), f"oauth beta missing from OAuth headers: {beta_value}"
+ assert "token-counting" in beta_value, f"token-counting beta missing from OAuth headers: {beta_value}"
+ assert "oauth-2025-04-20" in beta_value, f"oauth beta missing from OAuth headers: {beta_value}"
diff --git a/tests/test_litellm/llms/anthropic/test_message_sanitization.py b/tests/test_litellm/llms/anthropic/test_message_sanitization.py
index 79ed321d0ee..7afa60baf7e 100644
--- a/tests/test_litellm/llms/anthropic/test_message_sanitization.py
+++ b/tests/test_litellm/llms/anthropic/test_message_sanitization.py
@@ -12,9 +12,7 @@ import sys
import os
# Add the parent directory to the path so we can import litellm
-sys.path.insert(
- 0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../../../.."))
-)
+sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../../../..")))
import litellm
from litellm.litellm_core_utils.prompt_templates.factory import (
@@ -68,10 +66,7 @@ class TestMessageSanitization:
assert sanitized[1]["role"] == "assistant"
assert sanitized[2]["role"] == "tool"
assert sanitized[2]["tool_call_id"] == "toolu_01Kus2cC3ydjBW7UK4GJqBP4"
- assert (
- "skipped" in sanitized[2]["content"].lower()
- or "interrupted" in sanitized[2]["content"].lower()
- )
+ assert "skipped" in sanitized[2]["content"].lower() or "interrupted" in sanitized[2]["content"].lower()
assert "get_weather" in sanitized[2]["content"]
def test_case_a_orphaned_tool_call_multiple(self):
@@ -115,12 +110,8 @@ class TestMessageSanitization:
assert len(sanitized) == 4
assert sanitized[0]["role"] == "user"
assert sanitized[1]["role"] == "assistant"
- assert (
- sanitized[2]["tool_call_id"] == "call_1"
- ) # Original tool result (first in tool_calls)
- assert (
- sanitized[3]["tool_call_id"] == "call_2"
- ) # Dummy added for missing call_2
+ assert sanitized[2]["tool_call_id"] == "call_1" # Original tool result (first in tool_calls)
+ assert sanitized[3]["tool_call_id"] == "call_2" # Dummy added for missing call_2
def test_case_b_orphaned_tool_result(self):
"""
@@ -188,10 +179,7 @@ class TestMessageSanitization:
assert len(sanitized) == 2
assert sanitized[0]["role"] == "user"
- assert (
- sanitized[0]["content"]
- == "[System: Empty message content sanitised to satisfy protocol]"
- )
+ assert sanitized[0]["content"] == "[System: Empty message content sanitised to satisfy protocol]"
def test_case_c_whitespace_only_content(self):
"""
@@ -206,14 +194,8 @@ class TestMessageSanitization:
sanitized = sanitize_messages_for_tool_calling(messages)
assert len(sanitized) == 2
- assert (
- sanitized[0]["content"]
- == "[System: Empty message content sanitised to satisfy protocol]"
- )
- assert (
- sanitized[1]["content"]
- == "[System: Empty message content sanitised to satisfy protocol]"
- )
+ assert sanitized[0]["content"] == "[System: Empty message content sanitised to satisfy protocol]"
+ assert sanitized[1]["content"] == "[System: Empty message content sanitised to satisfy protocol]"
def test_case_c_valid_content_preserved(self):
"""
@@ -270,10 +252,7 @@ class TestMessageSanitization:
assert sanitized[2]["role"] == "tool"
assert sanitized[2]["tool_call_id"] == "call_1" # Dummy added
assert sanitized[3]["role"] == "user"
- assert (
- sanitized[3]["content"]
- == "[System: Empty message content sanitised to satisfy protocol]"
- )
+ assert sanitized[3]["content"] == "[System: Empty message content sanitised to satisfy protocol]"
assert sanitized[4]["role"] == "assistant"
def test_modify_params_false_no_sanitization(self):
@@ -329,9 +308,7 @@ class TestMessageSanitization:
]
# This should not raise an error and should add dummy tool result
- result = anthropic_messages_pt(
- messages=messages, model="claude-sonnet-4-5", llm_provider="anthropic"
- )
+ result = anthropic_messages_pt(messages=messages, model="claude-sonnet-4-5", llm_provider="anthropic")
# Should have at least 2 messages (user and assistant)
# The tool result will be merged into user content
@@ -355,23 +332,17 @@ class TestMessageSanitization:
{"role": "user", "content": ""},
]
- result = anthropic_messages_pt(
- messages=messages, model="claude-sonnet-4-5", llm_provider="anthropic"
- )
+ result = anthropic_messages_pt(messages=messages, model="claude-sonnet-4-5", llm_provider="anthropic")
# All three user messages get merged into one user turn for Anthropic.
assert len(result) == 1
assert result[0]["role"] == "user"
- text_blocks = [
- b for b in result[0]["content"] if isinstance(b, dict) and b.get("type") == "text"
- ]
+ text_blocks = [b for b in result[0]["content"] if isinstance(b, dict) and b.get("type") == "text"]
assert len(text_blocks) == 3
# No text block may be empty — that's the contract Anthropic enforces.
for block in text_blocks:
assert block["text"].strip() != ""
- assert text_blocks[2]["text"] == (
- "[System: Empty message content sanitised to satisfy protocol]"
- )
+ assert text_blocks[2]["text"] == ("[System: Empty message content sanitised to satisfy protocol]")
def test_empty_text_block_in_list_content_sanitized(self):
"""
@@ -392,14 +363,10 @@ class TestMessageSanitization:
},
]
- result = anthropic_messages_pt(
- messages=messages, model="claude-sonnet-4-5", llm_provider="anthropic"
- )
+ result = anthropic_messages_pt(messages=messages, model="claude-sonnet-4-5", llm_provider="anthropic")
assert len(result) == 1
- text_blocks = [
- b for b in result[0]["content"] if isinstance(b, dict) and b.get("type") == "text"
- ]
+ text_blocks = [b for b in result[0]["content"] if isinstance(b, dict) and b.get("type") == "text"]
assert len(text_blocks) == 3
assert text_blocks[0]["text"] == "real content"
for block in text_blocks[1:]:
@@ -418,9 +385,7 @@ class TestMessageSanitization:
{"role": "user", "content": "How are you?"},
]
- result = anthropic_messages_pt(
- messages=messages, model="claude-sonnet-4-5", llm_provider="anthropic"
- )
+ result = anthropic_messages_pt(messages=messages, model="claude-sonnet-4-5", llm_provider="anthropic")
# Two user turns + one assistant turn (alternation preserved).
assert len(result) == 3