diff --git a/litellm/types/utils.py b/litellm/types/utils.py index c3ea605dd9e..407654e5387 100644 --- a/litellm/types/utils.py +++ b/litellm/types/utils.py @@ -2486,6 +2486,7 @@ class LoggedLiteLLMParams(TypedDict, total=False): litellm_call_id: Optional[str] model_alias_map: Optional[dict] metadata: Optional[dict] + litellm_metadata: Optional[dict] model_info: Optional[dict] proxy_server_request: Optional[dict] acompletion: Optional[bool] diff --git a/tests/test_litellm/proxy/test_proxy_utils.py b/tests/test_litellm/proxy/test_proxy_utils.py index f0015d9df0d..aace9405292 100644 --- a/tests/test_litellm/proxy/test_proxy_utils.py +++ b/tests/test_litellm/proxy/test_proxy_utils.py @@ -118,6 +118,63 @@ async def test_proxy_only_error_log_marks_no_upstream_llm_call(): assert captured.get("flag") is True +@pytest.mark.asyncio +async def test_proxy_only_error_log_keeps_litellm_metadata_in_litellm_params(): + """Responses API requests carry guardrail info under ``litellm_metadata`` + (not ``metadata``). It must land in litellm_params so + ``merge_litellm_metadata`` can surface ``guardrail_information`` in the + spend-log failure row, matching the chat completions path.""" + from litellm.proxy._types import UserAPIKeyAuth + + proxy_logging_obj = ProxyLogging(user_api_key_cache=DualCache()) + captured = {} + guardrail_info = [{"guardrail_name": "test-guard", "guardrail_status": "blocked"}] + + def fake_update_environment_variables(self, *args, **kwargs): + captured["litellm_params"] = kwargs.get("litellm_params") + captured["optional_params"] = kwargs.get("optional_params") + + from litellm.litellm_core_utils.litellm_logging import Logging + + orig_update_env = Logging.update_environment_variables + orig_pre_call = Logging.pre_call + orig_async_failure = Logging.async_failure_handler + + async def _noop_async_failure(self, *args, **kwargs): + return None + + Logging.update_environment_variables = fake_update_environment_variables + Logging.pre_call = lambda self, *args, **kwargs: None + Logging.async_failure_handler = _noop_async_failure + try: + await proxy_logging_obj._handle_logging_proxy_only_error( + request_data={ + "model": "gpt-4o", + "input": "blocked prompt", + "litellm_metadata": { + "standard_logging_guardrail_information": guardrail_info + }, + }, + user_api_key_dict=UserAPIKeyAuth( + api_key="sk-1234", request_route="/v1/responses" + ), + route="/v1/responses", + original_exception=HTTPException(status_code=400, detail="blocked"), + ) + finally: + Logging.update_environment_variables = orig_update_env + Logging.pre_call = orig_pre_call + Logging.async_failure_handler = orig_async_failure + + assert ( + captured["litellm_params"]["litellm_metadata"][ + "standard_logging_guardrail_information" + ] + == guardrail_info + ) + assert "litellm_metadata" not in captured["optional_params"] + + def test_get_model_group_info_order(): from litellm import Router from litellm.proxy.proxy_server import _get_model_group_info