fix(proxy): persist guardrail info in spend logs for /v1/responses

Pre-call guardrail blocks on /v1/responses wrote guardrail_information
as null in LiteLLM_SpendLogs because _handle_logging_proxy_only_error
splits request_data by LoggedLiteLLMParams keys and litellm_metadata,
where the Responses API stores request metadata including
standard_logging_guardrail_information, was not among them. It fell
into optional_params, so merge_litellm_metadata never saw it. Add
litellm_metadata to LoggedLiteLLMParams so it routes into
litellm_params the same way metadata does on the chat completions path

Fixes #28971.
This commit is contained in:
Filippo Mattia Menghi 2026-06-10 09:43:57 +02:00
parent e15b37a18e
commit d5aad8ced8
2 changed files with 58 additions and 0 deletions

View file

@ -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]

View file

@ -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