From 79424f504e23d8729ed543b95fc729964a221119 Mon Sep 17 00:00:00 2001 From: Milan Date: Thu, 11 Jun 2026 00:52:03 +0300 Subject: [PATCH] fix(anthropic_passthrough): honor base_model in passthrough cost calculation The Anthropic passthrough logging handler called completion_cost without the deployment's model_info.base_model, so unmapped model aliases (e.g. proxy-to-proxy setups) logged "This model isn't mapped yet" errors on every streaming /v1/messages request, even though the standard cost path resolves the cost via base_model correctly. Co-authored-by: Cursor --- .../anthropic_passthrough_logging_handler.py | 6 + ...t_anthropic_passthrough_logging_handler.py | 123 ++++++++++++++++++ 2 files changed, 129 insertions(+) diff --git a/litellm/proxy/pass_through_endpoints/llm_provider_handlers/anthropic_passthrough_logging_handler.py b/litellm/proxy/pass_through_endpoints/llm_provider_handlers/anthropic_passthrough_logging_handler.py index a94672f9487..2b8fabc1c20 100644 --- a/litellm/proxy/pass_through_endpoints/llm_provider_handlers/anthropic_passthrough_logging_handler.py +++ b/litellm/proxy/pass_through_endpoints/llm_provider_handlers/anthropic_passthrough_logging_handler.py @@ -18,6 +18,7 @@ from litellm.types.passthrough_endpoints.pass_through_endpoints import ( PassthroughStandardLoggingPayload, ) from litellm.types.utils import LiteLLMBatch, ModelResponse, TextCompletionResponse +from litellm.utils import _get_base_model_from_metadata if TYPE_CHECKING: from litellm.types.passthrough_endpoints.pass_through_endpoints import EndpointType @@ -141,12 +142,17 @@ class AnthropicPassthroughLoggingHandler: ) ) + base_model = _get_base_model_from_metadata( + model_call_details=logging_obj.model_call_details + ) + response_cost = litellm.completion_cost( completion_response=litellm_model_response, model=model_for_cost, custom_llm_provider=custom_llm_provider, custom_pricing=custom_pricing, router_model_id=router_model_id, + base_model=base_model, ) kwargs["response_cost"] = response_cost diff --git a/tests/test_litellm/proxy/pass_through_endpoints/llm_provider_handlers/test_anthropic_passthrough_logging_handler.py b/tests/test_litellm/proxy/pass_through_endpoints/llm_provider_handlers/test_anthropic_passthrough_logging_handler.py index 1114b3df0c2..e599b165236 100644 --- a/tests/test_litellm/proxy/pass_through_endpoints/llm_provider_handlers/test_anthropic_passthrough_logging_handler.py +++ b/tests/test_litellm/proxy/pass_through_endpoints/llm_provider_handlers/test_anthropic_passthrough_logging_handler.py @@ -360,6 +360,129 @@ class TestAzureAnthropicCostCalculation: assert kwargs["response_cost"] > 0 +class TestBaseModelCostCalculation: + """Test that the deployment's model_info.base_model is used for cost calculation.""" + + def _create_mock_logging_obj( + self, + model: str, + base_model: str = None, + custom_llm_provider: str = None, + ) -> LiteLLMLoggingObj: + mock_logging_obj = MagicMock() + model_call_details = {"model": model} + if custom_llm_provider: + model_call_details["custom_llm_provider"] = custom_llm_provider + if base_model: + model_call_details["litellm_params"] = { + "metadata": {"model_info": {"base_model": base_model}} + } + mock_logging_obj.model_call_details = model_call_details + mock_logging_obj.litellm_call_id = "test-call-id" + mock_logging_obj.get_router_model_id.return_value = None + mock_logging_obj.litellm_params = {} + return mock_logging_obj + + @patch("litellm.completion_cost") + def test_base_model_passed_to_completion_cost(self, mock_completion_cost): + """base_model from litellm_params metadata must be forwarded to completion_cost""" + from litellm.types.utils import ModelResponse + + mock_completion_cost.return_value = 0.001 + + logging_obj = self._create_mock_logging_obj( + model="us/aws/anthropic/eccn-claude-sonnet-4-6", + base_model="claude-sonnet-4-6", + custom_llm_provider="anthropic", + ) + + mock_response = MagicMock(spec=ModelResponse) + mock_response.id = "test-id" + mock_response.model = "us/aws/anthropic/eccn-claude-sonnet-4-6" + + AnthropicPassthroughLoggingHandler._create_anthropic_response_logging_payload( + litellm_model_response=mock_response, + model="us/aws/anthropic/eccn-claude-sonnet-4-6", + kwargs={}, + start_time=datetime.now(), + end_time=datetime.now(), + logging_obj=logging_obj, + ) + + mock_completion_cost.assert_called_once() + call_kwargs = mock_completion_cost.call_args[1] + assert call_kwargs["base_model"] == "claude-sonnet-4-6" + + @patch("litellm.completion_cost") + def test_base_model_none_when_not_configured(self, mock_completion_cost): + """base_model should be None when the deployment doesn't set it""" + from litellm.types.utils import ModelResponse + + mock_completion_cost.return_value = 0.001 + + logging_obj = self._create_mock_logging_obj(model="claude-3-sonnet-20240229") + + mock_response = MagicMock(spec=ModelResponse) + mock_response.id = "test-id" + mock_response.model = "claude-3-sonnet-20240229" + + AnthropicPassthroughLoggingHandler._create_anthropic_response_logging_payload( + litellm_model_response=mock_response, + model="claude-3-sonnet-20240229", + kwargs={}, + start_time=datetime.now(), + end_time=datetime.now(), + logging_obj=logging_obj, + ) + + mock_completion_cost.assert_called_once() + call_kwargs = mock_completion_cost.call_args[1] + assert call_kwargs["base_model"] is None + + def test_unmapped_alias_with_base_model_computes_cost(self): + """ + Unmapped model alias (e.g. proxy-to-proxy setup) with base_model set must + compute a real cost instead of failing with 'This model isn't mapped yet'. + """ + from litellm.types.utils import Choices, Message, ModelResponse + + logging_obj = self._create_mock_logging_obj( + model="us/aws/anthropic/eccn-claude-sonnet-4-6", + base_model="claude-sonnet-4-6", + custom_llm_provider="anthropic", + ) + + response = ModelResponse( + id="test-id", + choices=[ + Choices( + finish_reason="stop", + index=0, + message=Message(content="test", role="assistant"), + ) + ], + created=1234567890, + model="us/aws/anthropic/eccn-claude-sonnet-4-6", + usage={ + "prompt_tokens": 25, + "completion_tokens": 10, + "total_tokens": 35, + }, + ) + + kwargs = AnthropicPassthroughLoggingHandler._create_anthropic_response_logging_payload( + litellm_model_response=response, + model="us/aws/anthropic/eccn-claude-sonnet-4-6", + kwargs={}, + start_time=datetime.now(), + end_time=datetime.now(), + logging_obj=logging_obj, + ) + + assert "response_cost" in kwargs + assert kwargs["response_cost"] > 0 + + class TestAnthropicBatchPassthroughCostTracking: """Test cases for Anthropic batch passthrough cost tracking functionality"""