mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-06 02:48:13 +00:00
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 <cursoragent@cursor.com>
This commit is contained in:
parent
20e453f698
commit
79424f504e
2 changed files with 129 additions and 0 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"""
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue