diff --git a/litellm/cost_calculator.py b/litellm/cost_calculator.py index ff0c1275f9d..dbffceb16ea 100644 --- a/litellm/cost_calculator.py +++ b/litellm/cost_calculator.py @@ -316,7 +316,7 @@ def _is_positive_finite_number(value: Any) -> bool: ) -def _get_metadata_model_infos( +def _get_metadata_model_infos_for_cost_fallback( litellm_logging_obj: Optional[LitellmLoggingObject], ) -> List[Mapping[str, Any]]: litellm_params = getattr(litellm_logging_obj, "litellm_params", None) @@ -339,15 +339,6 @@ def _get_max_input_tokens_for_cost_fallback( custom_llm_provider: Optional[str], litellm_logging_obj: Optional[LitellmLoggingObject], ) -> Optional[Union[int, float]]: - metadata_model_infos = _get_metadata_model_infos( - litellm_logging_obj=litellm_logging_obj - ) - for model_info in metadata_model_infos: - for token_limit_key in ("max_input_tokens", "max_tokens"): - token_limit = model_info.get(token_limit_key) - if _is_positive_finite_number(token_limit): - return cast(Union[int, float], token_limit) - if model is None: return None @@ -369,6 +360,18 @@ def _get_max_input_tokens_for_cost_fallback( token_limit = model_info.get(token_limit_key) if _is_positive_finite_number(token_limit): return cast(Union[int, float], token_limit) + + # Request metadata can be caller-controlled on proxy paths. Prefer the + # registered model info above; use metadata only as a final compatibility + # fallback for router/deployment paths that have not registered the model. + metadata_model_infos = _get_metadata_model_infos_for_cost_fallback( + litellm_logging_obj=litellm_logging_obj + ) + for model_info in metadata_model_infos: + for token_limit_key in ("max_input_tokens", "max_tokens"): + token_limit = model_info.get(token_limit_key) + if _is_positive_finite_number(token_limit): + return cast(Union[int, float], token_limit) return None diff --git a/tests/local_testing/test_completion_cost.py b/tests/local_testing/test_completion_cost.py index cadafc34035..3ca4ad3461c 100644 --- a/tests/local_testing/test_completion_cost.py +++ b/tests/local_testing/test_completion_cost.py @@ -1031,6 +1031,68 @@ def test_completion_cost_uses_conservative_video_fallback_without_usage(): assert cost == pytest.approx(max_input_tokens * input_cost_per_token) +@pytest.mark.parametrize("metadata_key", ["metadata", "litellm_metadata"]) +def test_completion_cost_ignores_client_metadata_for_video_fallback_limit( + metadata_key, +): + model = "openai/test-video-untrusted-metadata-fallback" + input_cost_per_token = 0.25 + max_input_tokens = 32 + litellm.register_model( + model_cost={ + model: { + "input_cost_per_token": input_cost_per_token, + "output_cost_per_token": 0.0, + "max_tokens": max_input_tokens, + "max_input_tokens": max_input_tokens, + "max_output_tokens": 4, + "litellm_provider": "openai", + "mode": "chat", + } + } + ) + messages = [ + { + "role": "user", + "content": [ + { + "type": "video_url", + "video_url": { + "url": "https://example.com/video.mp4", + "video_metadata": { + "duration_seconds": 0, + "fps": 0, + "has_audio": False, + }, + }, + } + ], + } + ] + logging_obj = MagicMock() + logging_obj.litellm_params = { + metadata_key: { + "model_info": { + "max_input_tokens": 1, + "max_tokens": 1, + } + } + } + + try: + cost = completion_cost( + completion_response={"model": model, "usage": {}}, + model=model, + messages=messages, + custom_llm_provider="openai", + litellm_logging_obj=logging_obj, + ) + finally: + litellm.model_cost.pop(model, None) + + assert cost == pytest.approx(max_input_tokens * input_cost_per_token) + + def test_completion_cost_uses_provider_video_usage_when_present(): model = "openai/test-video-provider-usage" input_cost_per_token = 0.25