diff --git a/litellm/llms/custom_httpx/llm_http_handler.py b/litellm/llms/custom_httpx/llm_http_handler.py index c9ab3c648acd..7939e2626f16 100644 --- a/litellm/llms/custom_httpx/llm_http_handler.py +++ b/litellm/llms/custom_httpx/llm_http_handler.py @@ -2026,6 +2026,7 @@ async def async_anthropic_messages_handler( litellm_params={ "preset_cache_key": None, "stream_response": {}, + "model_info": kwargs.get("model_info"), **anthropic_messages_optional_request_params, }, custom_llm_provider=custom_llm_provider, diff --git a/tests/test_litellm/llms/custom_httpx/test_llm_http_handler.py b/tests/test_litellm/llms/custom_httpx/test_llm_http_handler.py index 07a61c9c1049..083f4a97ab62 100644 --- a/tests/test_litellm/llms/custom_httpx/test_llm_http_handler.py +++ b/tests/test_litellm/llms/custom_httpx/test_llm_http_handler.py @@ -334,6 +334,79 @@ async def test_async_anthropic_messages_handler_passes_litellm_metadata(): assert kwargs_arg["litellm_metadata"]["model_info"] == custom_model_info +@pytest.mark.asyncio +async def test_async_anthropic_messages_handler_forwards_router_model_info(): + """Ensure router deployment model_info is forwarded into litellm_params. + + The Router stamps kwargs['model_info'] on every deployment dispatch via + _update_kwargs_with_deployment. Downstream cooldown / success callbacks + (router.deployment_callback_on_failure, deployment_callback_on_success) + look up the deployment id via kwargs['litellm_params']['model_info']['id']. + If async_anthropic_messages_handler builds its own litellm_params dict + without forwarding model_info, the id is missing and cooldown is silently + skipped for /v1/messages requests under the Router. + """ + handler = BaseLLMHTTPHandler() + + mock_config = Mock() + mock_config.validate_anthropic_messages_environment = Mock( + return_value=({"x-api-key": "test-key"}, "https://api.anthropic.com") + ) + mock_config.transform_anthropic_messages_request = Mock( + return_value={"model": "claude-sonnet-4-20250514", "messages": []} + ) + + mock_client = AsyncMock() + mock_response = Mock() + mock_response.status_code = 200 + mock_response.json.return_value = { + "id": "msg_123", + "type": "message", + "role": "assistant", + "content": [{"type": "text", "text": "Hello!"}], + "model": "claude-sonnet-4-20250514", + "stop_reason": "end_turn", + } + mock_client.post = AsyncMock(return_value=mock_response) + + mock_logging_obj = Mock() + mock_logging_obj.update_from_kwargs = Mock() + mock_logging_obj.model_call_details = {} + mock_logging_obj.stream = False + + deployment_model_info = { + "id": "deployment-123", + "db_model": False, + } + + try: + await handler.async_anthropic_messages_handler( + model="claude-sonnet-4-20250514", + messages=[{"role": "user", "content": "Hello"}], + anthropic_messages_provider_config=mock_config, + anthropic_messages_optional_request_params={}, + custom_llm_provider="anthropic", + litellm_params=GenericLiteLLMParams(), + logging_obj=mock_logging_obj, + client=mock_client, + kwargs={"model_info": deployment_model_info}, + ) + except Exception: + pass + + mock_logging_obj.update_from_kwargs.assert_called_once() + call_kwargs = mock_logging_obj.update_from_kwargs.call_args + litellm_params_arg = ( + call_kwargs.kwargs.get( + "litellm_params", call_kwargs[1].get("litellm_params", {}) + ) + if call_kwargs.kwargs + else call_kwargs[1].get("litellm_params", {}) + ) + + assert litellm_params_arg.get("model_info") == deployment_model_info + + @pytest.mark.asyncio async def test_async_anthropic_messages_handler_header_priority(): """