Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 13 additions & 7 deletions litellm/llms/a2a/chat/guardrail_translation/handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ async def process_output_response(
guardrail_to_apply: "CustomGuardrail",
litellm_logging_obj: Optional["LiteLLMLoggingObj"] = None,
user_api_key_dict: Optional["UserAPIKeyAuth"] = None,
request_data: Optional[dict] = None,
) -> Any:
"""
Process A2A output response by applying guardrails to text content.
Expand Down Expand Up @@ -166,13 +167,18 @@ async def process_output_response(
return response

# Step 2: Apply guardrail to all texts in batch
# Create a request_data dict with response info and user API key metadata
request_data: dict = {"response": response_dict}

# Add user API key metadata with prefixed keys
user_metadata = self.transform_user_api_key_dict_to_metadata(user_api_key_dict)
if user_metadata:
request_data["litellm_metadata"] = user_metadata
# Use the real request_data if provided (proxy path), otherwise
# create a throwaway dict (SDK / direct-call path).
if request_data is None:
request_data = {"response": response_dict}
user_metadata = self.transform_user_api_key_dict_to_metadata(
user_api_key_dict
)
if user_metadata:
request_data["litellm_metadata"] = user_metadata
else:
if "response" not in request_data:
request_data["response"] = response_dict

inputs = GenericGuardrailAPIInputs(texts=texts_to_check)

Expand Down
24 changes: 14 additions & 10 deletions litellm/llms/anthropic/chat/guardrail_translation/handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -246,12 +246,13 @@ async def _apply_guardrail_responses_to_input(
"text"
] = guardrail_response

async def process_output_response(
async def process_output_response( # noqa: PLR0915
self,
response: "AnthropicMessagesResponse",
guardrail_to_apply: "CustomGuardrail",
litellm_logging_obj: Optional[Any] = None,
user_api_key_dict: Optional[Any] = None,
request_data: Optional[dict] = None,
) -> Any:
"""
Process output response by applying guardrails to text content and tool calls.
Expand Down Expand Up @@ -323,15 +324,18 @@ async def process_output_response(

# Step 2: Apply guardrail to all texts in batch
if texts_to_check or tool_calls_to_check:
# Create a request_data dict with response info and user API key metadata
request_data: dict = {"response": response}

# Add user API key metadata with prefixed keys
user_metadata = self.transform_user_api_key_dict_to_metadata(
user_api_key_dict
)
if user_metadata:
request_data["litellm_metadata"] = user_metadata
# Use the real request_data if provided (proxy path), otherwise
# create a throwaway dict (SDK / direct-call path).
if request_data is None:
request_data = {"response": response}
user_metadata = self.transform_user_api_key_dict_to_metadata(
user_api_key_dict
)
if user_metadata:
request_data["litellm_metadata"] = user_metadata
else:
if "response" not in request_data:
request_data["response"] = response

inputs = GenericGuardrailAPIInputs(texts=texts_to_check)
if images_to_check:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ async def process_output_response(
guardrail_to_apply: "CustomGuardrail",
litellm_logging_obj: Optional["LiteLLMLoggingObj"] = None,
user_api_key_dict: Optional["UserAPIKeyAuth"] = None,
request_data: Optional[dict] = None,
) -> Any:
"""
Process output response with guardrails.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ async def process_output_response(
guardrail_to_apply: "CustomGuardrail",
litellm_logging_obj: Optional[Any] = None,
user_api_key_dict: Optional[Any] = None,
request_data: Optional[dict] = None,
) -> Any:
"""
Process output response - not applicable for rerank.
Expand Down
1 change: 1 addition & 0 deletions litellm/llms/mistral/ocr/guardrail_translation/handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ async def process_output_response(
guardrail_to_apply: "CustomGuardrail",
litellm_logging_obj: Optional[Any] = None,
user_api_key_dict: Optional[Any] = None,
request_data: Optional[dict] = None,
) -> Any:
"""
Process OCR output by applying guardrails to extracted page text.
Expand Down
22 changes: 13 additions & 9 deletions litellm/llms/openai/chat/guardrail_translation/handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,7 @@ async def process_output_response(
guardrail_to_apply: "CustomGuardrail",
litellm_logging_obj: Optional[Any] = None,
user_api_key_dict: Optional[Any] = None,
request_data: Optional[dict] = None,
) -> Any:
"""
Process output response by applying guardrails to text content.
Expand Down Expand Up @@ -308,15 +309,18 @@ async def process_output_response(

# Step 2: Apply guardrail to all texts and tool calls in batch
if texts_to_check or tool_calls_to_check:
# Create a request_data dict with response info and user API key metadata
request_data: dict = {"response": response}

# Add user API key metadata with prefixed keys
user_metadata = self.transform_user_api_key_dict_to_metadata(
user_api_key_dict
)
if user_metadata:
request_data["litellm_metadata"] = user_metadata
# Use the real request_data if provided (proxy path), otherwise
# create a throwaway dict (SDK / direct-call path).
if request_data is None:
request_data = {"response": response}
user_metadata = self.transform_user_api_key_dict_to_metadata(
user_api_key_dict
)
if user_metadata:
request_data["litellm_metadata"] = user_metadata
else:
if "response" not in request_data:
request_data["response"] = response

inputs = GenericGuardrailAPIInputs(texts=texts_to_check)
if images_to_check:
Comment on lines 309 to 326

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

request_data["response"] no longer populated when real request_data is passed

When request_data is provided (the fix for Bug 1), the handlers skip adding "response" to it. However, CrowdStrikeAIDRGuardrail._build_guard_input_for_response (at crowdstrike_aidr.py:163) explicitly reads request_data.get("response") to build the guard payload for post-call checks. With this change, that guardrail will always log a warning and return None for output responses, silently disabling its post-call inspection.

The same pattern applies to all six handler files in this PR (openai/chat, openai/completion, openai/responses, openai/transcriptions, anthropic/chat, a2a/chat).

To maintain backward compatibility, the real response object should be injected into the provided request_data if it isn't already present:

if request_data is None:
    request_data = {"response": response}
    user_metadata = self.transform_user_api_key_dict_to_metadata(user_api_key_dict)
    if user_metadata:
        request_data["litellm_metadata"] = user_metadata
else:
    # Inject the response so downstream guardrails can read request_data["response"]
    if "response" not in request_data:
        request_data["response"] = response
    user_metadata = self.transform_user_api_key_dict_to_metadata(user_api_key_dict)
    if user_metadata and "litellm_metadata" not in request_data:
        request_data["litellm_metadata"] = user_metadata

This same fix is needed across all six translation handlers.

Expand Down
22 changes: 13 additions & 9 deletions litellm/llms/openai/completion/guardrail_translation/handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ async def process_output_response(
guardrail_to_apply: "CustomGuardrail",
litellm_logging_obj: Optional[Any] = None,
user_api_key_dict: Optional[Any] = None,
request_data: Optional[dict] = None,
) -> Any:
"""
Process output response by applying guardrails to completion text.
Expand Down Expand Up @@ -155,15 +156,18 @@ async def process_output_response(

# Apply guardrails in batch
if texts_to_check:
# Create a request_data dict with response info and user API key metadata
request_data: dict = {"response": response}

# Add user API key metadata with prefixed keys
user_metadata = self.transform_user_api_key_dict_to_metadata(
user_api_key_dict
)
if user_metadata:
request_data["litellm_metadata"] = user_metadata
# Use the real request_data if provided (proxy path), otherwise
# create a throwaway dict (SDK / direct-call path).
if request_data is None:
request_data = {"response": response}
user_metadata = self.transform_user_api_key_dict_to_metadata(
user_api_key_dict
)
if user_metadata:
request_data["litellm_metadata"] = user_metadata
else:
if "response" not in request_data:
request_data["response"] = response

inputs = GenericGuardrailAPIInputs(texts=texts_to_check)
# Include model information from the response if available
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ async def process_output_response(
guardrail_to_apply: "CustomGuardrail",
litellm_logging_obj: Optional[Any] = None,
user_api_key_dict: Optional[Any] = None,
request_data: Optional[dict] = None,
) -> Any:
"""
Process output response - embeddings responses contain vectors, not text.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ async def process_output_response(
guardrail_to_apply: "CustomGuardrail",
litellm_logging_obj: Optional[Any] = None,
user_api_key_dict: Optional[Any] = None,
request_data: Optional[dict] = None,
) -> Any:
"""
Process output response - typically not needed for image generation.
Expand Down
22 changes: 13 additions & 9 deletions litellm/llms/openai/responses/guardrail_translation/handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,7 @@ async def process_output_response(
guardrail_to_apply: "CustomGuardrail",
litellm_logging_obj: Optional[Any] = None,
user_api_key_dict: Optional[Any] = None,
request_data: Optional[dict] = None,
) -> Any:
"""
Process output response by applying guardrails to text content and tool calls.
Expand Down Expand Up @@ -402,15 +403,18 @@ async def process_output_response(

# Step 2: Apply guardrail to all texts in batch
if texts_to_check or tool_calls_to_check:
# Create a request_data dict with response info and user API key metadata
request_data: dict = {"response": response}

# Add user API key metadata with prefixed keys
user_metadata = self.transform_user_api_key_dict_to_metadata(
user_api_key_dict
)
if user_metadata:
request_data["litellm_metadata"] = user_metadata
# Use the real request_data if provided (proxy path), otherwise
# create a throwaway dict (SDK / direct-call path).
if request_data is None:
request_data = {"response": response}
user_metadata = self.transform_user_api_key_dict_to_metadata(
user_api_key_dict
)
if user_metadata:
request_data["litellm_metadata"] = user_metadata
else:
if "response" not in request_data:
request_data["response"] = response

inputs = GenericGuardrailAPIInputs(texts=texts_to_check)
if images_to_check:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ async def process_output_response(
guardrail_to_apply: "CustomGuardrail",
litellm_logging_obj: Optional[Any] = None,
user_api_key_dict: Optional[Any] = None,
request_data: Optional[dict] = None,
) -> Any:
"""
Process output - not applicable for text-to-speech.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ async def process_output_response(
guardrail_to_apply: "CustomGuardrail",
litellm_logging_obj: Optional[Any] = None,
user_api_key_dict: Optional[Any] = None,
request_data: Optional[dict] = None,
) -> Any:
"""
Process output transcription by applying guardrails to transcribed text.
Expand All @@ -79,15 +80,18 @@ async def process_output_response(

if isinstance(response.text, str):
original_text = response.text
# Create a request_data dict with response info and user API key metadata
request_data: dict = {"response": response}

# Add user API key metadata with prefixed keys
user_metadata = self.transform_user_api_key_dict_to_metadata(
user_api_key_dict
)
if user_metadata:
request_data["litellm_metadata"] = user_metadata
# Use the real request_data if provided (proxy path), otherwise
# create a throwaway dict (SDK / direct-call path).
if request_data is None:
request_data = {"response": response}
user_metadata = self.transform_user_api_key_dict_to_metadata(
user_api_key_dict
)
if user_metadata:
request_data["litellm_metadata"] = user_metadata
else:
if "response" not in request_data:
request_data["response"] = response

inputs = GenericGuardrailAPIInputs(texts=[original_text])
# Include model information from the response if available
Expand Down
1 change: 1 addition & 0 deletions litellm/llms/pass_through/guardrail_translation/handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ async def process_output_response(
guardrail_to_apply: "CustomGuardrail",
litellm_logging_obj: Optional["LiteLLMLoggingObj"] = None,
user_api_key_dict: Optional[Any] = None,
request_data: Optional[dict] = None,
) -> Any:
"""
Process output response by applying guardrails to targeted fields.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ async def process_output_response(
guardrail_to_apply: "CustomGuardrail",
litellm_logging_obj: Optional[Any] = None,
user_api_key_dict: Optional[Any] = None,
request_data: Optional[dict] = None,
) -> Any:
verbose_proxy_logger.debug(
"MCP Guardrail: Output processing not implemented for MCP tools",
Expand Down
Loading
Loading