fix: anthropic_messages_handler only treated litellm_params.mock_respons

Addresses #31976.
This commit is contained in:
Amitesh Gupta 2026-07-03 18:32:23 +05:30
parent 4e53ae98a7
commit 262e596b9b
2 changed files with 44 additions and 3 deletions

View file

@ -362,6 +362,18 @@ async def anthropic_messages(
return response
def _get_mock_response_content(
mock_response: Optional[Union[str, litellm.ModelResponse, Exception, Any]],
) -> Optional[str]:
if isinstance(mock_response, str):
return mock_response
if isinstance(mock_response, litellm.ModelResponse):
first_choice = mock_response.choices[0]
if isinstance(first_choice, litellm.Choices):
return first_choice.message.content
return None
def validate_anthropic_api_metadata(metadata: Optional[Dict] = None) -> Optional[Dict]:
"""
Validate Anthropic API metadata - This is done to ensure only allowed `metadata` fields are passed to Anthropic API
@ -466,12 +478,13 @@ def anthropic_messages_handler(
if kwargs.get("_websearch_interception_converted_stream", False):
litellm_logging_obj.model_call_details["websearch_interception_converted_stream"] = True
if litellm_params.mock_response and isinstance(litellm_params.mock_response, str):
mock_response_content = _get_mock_response_content(litellm_params.mock_response)
if mock_response_content is not None:
return mock_response(
model=model,
messages=messages,
max_tokens=max_tokens,
mock_response=litellm_params.mock_response,
mock_response=mock_response_content,
)
anthropic_messages_provider_config: Optional[BaseAnthropicMessagesConfig] = None

View file

@ -11,7 +11,7 @@ from unittest.mock import AsyncMock, MagicMock, patch
from litellm.anthropic_interface import messages
from litellm.llms.custom_httpx.http_handler import AsyncHTTPHandler
from litellm.types.utils import Delta, ModelResponse, StreamingChoices
from litellm.types.utils import Choices, Delta, Message, ModelResponse, StreamingChoices
def test_anthropic_experimental_pass_through_messages_handler():
@ -648,6 +648,34 @@ def test_handler_skips_strip_when_presanitized():
assert result is not None
def test_handler_short_circuits_on_model_response_mock_response():
"""Regression test for #31976. BedrockGuardrail with
disable_exception_on_block=True sets a ModelResponse (not a str) as
litellm_params.mock_response to block a request without raising. The
handler must honor it instead of silently calling the real model."""
from litellm.llms.anthropic.experimental_pass_through.messages import handler
blocked_response = ModelResponse(
choices=[Choices(message=Message(content="Blocked by guardrail"))],
model="bedrock-guardrail",
)
with patch.object(
handler.base_llm_http_handler,
"anthropic_messages_handler",
) as mock_base_handler:
result = handler.anthropic_messages_handler(
max_tokens=10,
messages=[{"role": "user", "content": "some blocked content"}],
model="anthropic/claude-3-5-sonnet-20241022",
custom_llm_provider="anthropic",
mock_response=blocked_response,
)
mock_base_handler.assert_not_called()
assert result["content"][0]["text"] == "Blocked by guardrail"
def test_presanitized_flag_not_leaked_to_provider_params():
"""The private sentinel must be popped, never forwarded as a request param."""
from litellm.llms.anthropic.experimental_pass_through.messages import handler