diff --git a/litellm/proxy/litellm_pre_call_utils.py b/litellm/proxy/litellm_pre_call_utils.py index c09cce6476..ac32d18aa8 100644 --- a/litellm/proxy/litellm_pre_call_utils.py +++ b/litellm/proxy/litellm_pre_call_utils.py @@ -240,16 +240,31 @@ def _strip_client_pricing_overrides(data: Dict[str, Any]) -> None: """Drop pricing overrides from the request body and any metadata variant. Skipped only when the calling key/team carries - ``allow_client_pricing_override: True`` in its metadata. + ``allow_client_pricing_override: True`` in its metadata. Emits a + ``debug``-level log line naming the dropped fields so operators can + trace why a client-supplied pricing override stopped being applied + (otherwise the strip is invisible from the caller's perspective). """ + stripped: List[str] = [] for field in _CLIENT_PRICING_CONTROL_FIELDS: - data.pop(field, None) + if field in data: + stripped.append(field) + data.pop(field, None) for metadata_key in ("metadata", "litellm_metadata"): metadata = data.get(metadata_key) if not isinstance(metadata, dict): continue for field in _CLIENT_PRICING_METADATA_FIELDS: - metadata.pop(field, None) + if field in metadata: + stripped.append(f"{metadata_key}.{field}") + metadata.pop(field, None) + if stripped: + verbose_proxy_logger.debug( + "Stripped client-supplied pricing fields from request body: %s. " + "Set `allow_client_pricing_override: true` on the key or team " + "metadata to keep these values.", + ", ".join(stripped), + ) def _get_metadata_variable_name(request: Request) -> str: diff --git a/tests/test_litellm/proxy/test_pricing_field_strip.py b/tests/test_litellm/proxy/test_pricing_field_strip.py index 63dde7a56b..881a44e93c 100644 --- a/tests/test_litellm/proxy/test_pricing_field_strip.py +++ b/tests/test_litellm/proxy/test_pricing_field_strip.py @@ -130,6 +130,41 @@ class TestStripClientPricingOverrides: def test_metadata_field_set_contains_model_info(self): assert "model_info" in _CLIENT_PRICING_METADATA_FIELDS + def test_strip_emits_debug_log_listing_dropped_fields(self, caplog): + # Operators need a paper trail so they can diagnose why a previously + # working override stopped applying after the strip landed. + import logging + + from litellm._logging import verbose_proxy_logger + + verbose_proxy_logger.setLevel(logging.DEBUG) + with caplog.at_level(logging.DEBUG, logger=verbose_proxy_logger.name): + _strip_client_pricing_overrides( + { + "model": "gpt-4", + "input_cost_per_token": 0.0, + "metadata": {"model_info": {"output_cost_per_token": 0.0}}, + } + ) + log_text = " ".join(record.getMessage() for record in caplog.records) + assert "input_cost_per_token" in log_text + assert "metadata.model_info" in log_text + assert "allow_client_pricing_override" in log_text + + def test_strip_does_not_log_when_no_fields_present(self, caplog): + # No-op strips must stay silent so the log isn't filled with noise on + # every legitimate request. + import logging + + from litellm._logging import verbose_proxy_logger + + verbose_proxy_logger.setLevel(logging.DEBUG) + with caplog.at_level(logging.DEBUG, logger=verbose_proxy_logger.name): + _strip_client_pricing_overrides({"model": "gpt-4", "temperature": 0.7}) + assert not any( + "pricing" in record.getMessage().lower() for record in caplog.records + ) + @pytest.mark.asyncio async def test_add_litellm_data_to_request_strips_root_pricing_fields():