diff --git a/litellm/litellm_core_utils/litellm_logging.py b/litellm/litellm_core_utils/litellm_logging.py index 3a1dafa331c..b065d4dd91a 100644 --- a/litellm/litellm_core_utils/litellm_logging.py +++ b/litellm/litellm_core_utils/litellm_logging.py @@ -6471,6 +6471,7 @@ class StandardLoggingPayloadSetup: custom_llm_provider: str | None, init_response_obj: object, api_base: str | None = None, + router_model_id: str | None = None, ) -> StandardLoggingModelInformation: model_cost_name: Final = select_model_name_for_cost_calc( model=base_model if custom_pricing else None, @@ -6478,12 +6479,23 @@ class StandardLoggingPayloadSetup: base_model=base_model, custom_pricing=custom_pricing, ) + pricing_model_name: Final = ( + select_model_name_for_cost_calc( + model=base_model if custom_pricing else None, + completion_response=init_response_obj, + base_model=base_model, + custom_pricing=custom_pricing, + router_model_id=router_model_id, + ) + if router_model_id is not None + else model_cost_name + ) if model_cost_name is None: model_cost_information = StandardLoggingModelInformation(model_map_key="", model_map_value=None) else: try: _model_cost_information: Final = litellm.get_model_info( - model=model_cost_name, + model=pricing_model_name or model_cost_name, custom_llm_provider=custom_llm_provider, api_base=api_base, ) @@ -7193,6 +7205,7 @@ def get_standard_logging_object_payload( custom_llm_provider=kwargs.get("custom_llm_provider"), init_response_obj=init_response_obj, api_base=litellm_params.get("api_base"), + router_model_id=logging_obj.get_router_model_id(), ) error_information, error_str = StandardLoggingPayloadSetup.get_error_information_for_logging_payload( diff --git a/tests/unit/litellm_core_utils/test_litellm_logging.py b/tests/unit/litellm_core_utils/test_litellm_logging.py index 37a985264b7..1d3bb2d0f34 100644 --- a/tests/unit/litellm_core_utils/test_litellm_logging.py +++ b/tests/unit/litellm_core_utils/test_litellm_logging.py @@ -10718,6 +10718,89 @@ def test_uncached_price_leaves_no_pricing_state_on_the_call(): litellm.model_cost.pop(deployment_id, None) +@pytest.mark.parametrize( + ("input_cost_per_token", "output_cost_per_token"), + [(1e-06, 2e-06), (0.0, 0.0)], + ids=["custom_price", "explicit_zero_price"], +) +def test_standard_logging_payload_reports_the_deployment_entry_the_request_was_billed_from( + input_cost_per_token: float, output_cost_per_token: float +): + """A deployment with its own price keeps the catalog model as model_map_key but is logged at its own rates.""" + deployment_id: Final = "lit9660-priced-deployment" + deployment_model_info: Final = { + "id": deployment_id, + "input_cost_per_token": input_cost_per_token, + "output_cost_per_token": output_cost_per_token, + } + litellm.register_model( + model_cost={ + deployment_id: { + "litellm_provider": "openai", + "mode": "chat", + "input_cost_per_token": input_cost_per_token, + "output_cost_per_token": output_cost_per_token, + } + }, + persist_across_reloads=False, + ) + try: + logging_obj: Final = LitellmLogging( + model="gpt-5.4-nano", + messages=[{"role": "user", "content": "Hi"}], + stream=False, + call_type="completion", + start_time=time.time(), + litellm_call_id="lit9660", + function_id="fn", + ) + litellm_params: Final = { + "metadata": { + "model_group": "lit9660-group", + "deployment": "openai/gpt-5.4-nano", + "model_info": deployment_model_info, + } + } + logging_obj.update_environment_variables( + model="gpt-5.4-nano", user="", optional_params={}, litellm_params=litellm_params, custom_llm_provider="openai" + ) + response: Final = ModelResponse( + model="gpt-5.4-nano", + choices=[litellm.Choices(message=litellm.Message(role="assistant", content="hi"))], + usage=litellm.Usage(prompt_tokens=10, completion_tokens=6, total_tokens=16), + ) + + response_cost: Final = logging_obj.response_cost_calculator(result=response) + payload: Final = get_standard_logging_object_payload( + kwargs={ + "model": "gpt-5.4-nano", + "custom_llm_provider": "openai", + "litellm_params": litellm_params, + "response_cost": response_cost, + }, + init_response_obj=response, + start_time=datetime.datetime.now(), + end_time=datetime.datetime.now(), + logging_obj=logging_obj, + status="success", + ) + + assert response_cost == 10 * input_cost_per_token + 6 * output_cost_per_token + assert payload is not None + assert payload["model_map_information"] == { + "model_map_key": "openai/gpt-5.4-nano", + "model_map_value": litellm.get_model_info(model=f"openai/{deployment_id}", custom_llm_provider="openai"), + } + model_map_value: Final = payload["model_map_information"]["model_map_value"] + assert model_map_value is not None + assert (model_map_value["input_cost_per_token"], model_map_value["output_cost_per_token"]) == ( + input_cost_per_token, + output_cost_per_token, + ) + finally: + litellm.model_cost.pop(deployment_id, None) + + def _assert_zeroed_copy(hit_breakdown: CostBreakdown | None, fresh_breakdown: CostBreakdown) -> None: basis_and_rates: Final = ("service_tier", "data_residency", "vertex_location", "discount_percent", "margin_percent") assert hit_breakdown is not None and hit_breakdown.keys() == fresh_breakdown.keys()