mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-13 23:11:40 +00:00
feat(guardrails): support structured_messages in generic guardrail API response
Guardrails that modify full message structure (e.g. prompt compression) can now return structured_messages in GUARDRAIL_INTERVENED responses. LiteLLM passes it back through GenericGuardrailAPIInputs so the compressed message array reaches the LLM provider instead of only flat text replacements.
This commit is contained in:
parent
97008bad29
commit
70a893ca53
3 changed files with 34 additions and 1 deletions
|
|
@ -329,8 +329,9 @@ class GenericGuardrailAPI(CustomGuardrail):
|
|||
tools: Any,
|
||||
guardrail_response: GenericGuardrailAPIResponse,
|
||||
) -> GenericGuardrailAPIInputs:
|
||||
# Action is NONE or no modifications needed
|
||||
return_inputs = GenericGuardrailAPIInputs(texts=texts)
|
||||
if guardrail_response.structured_messages:
|
||||
return_inputs["structured_messages"] = guardrail_response.structured_messages
|
||||
if guardrail_response.texts:
|
||||
return_inputs["texts"] = guardrail_response.texts
|
||||
if guardrail_response.images:
|
||||
|
|
|
|||
|
|
@ -89,6 +89,7 @@ class GenericGuardrailAPIResponse:
|
|||
texts: Optional[List[str]]
|
||||
images: Optional[List[str]]
|
||||
tools: Optional[List[ChatCompletionToolParam]]
|
||||
structured_messages: Optional[List[AllMessageValues]]
|
||||
action: str
|
||||
blocked_reason: Optional[str]
|
||||
|
||||
|
|
@ -99,12 +100,14 @@ class GenericGuardrailAPIResponse:
|
|||
blocked_reason: Optional[str] = None,
|
||||
images: Optional[List[str]] = None,
|
||||
tools: Optional[List[ChatCompletionToolParam]] = None,
|
||||
structured_messages: Optional[List[AllMessageValues]] = None,
|
||||
):
|
||||
self.action = action
|
||||
self.blocked_reason = blocked_reason
|
||||
self.texts = texts
|
||||
self.images = images
|
||||
self.tools = tools
|
||||
self.structured_messages = structured_messages
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data: dict) -> "GenericGuardrailAPIResponse":
|
||||
|
|
@ -114,4 +117,5 @@ class GenericGuardrailAPIResponse:
|
|||
texts=data.get("texts"),
|
||||
images=data.get("images"),
|
||||
tools=data.get("tools"),
|
||||
structured_messages=data.get("structured_messages"),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -581,6 +581,34 @@ class TestGuardrailActions:
|
|||
assert result_texts == ["[REDACTED]"]
|
||||
assert result_images is None
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_action_intervened_with_structured_messages(
|
||||
self, generic_guardrail, mock_request_data_input
|
||||
):
|
||||
compressed_messages = [
|
||||
{"role": "user", "content": "Analyze this."},
|
||||
{"role": "user", "content": [{"type": "tool_result", "tool_use_id": "t1", "content": "compressed json"}]},
|
||||
]
|
||||
mock_response = MagicMock()
|
||||
mock_response.json.return_value = {
|
||||
"action": "GUARDRAIL_INTERVENED",
|
||||
"structured_messages": compressed_messages,
|
||||
}
|
||||
mock_response.raise_for_status = MagicMock()
|
||||
|
||||
with patch.object(
|
||||
generic_guardrail.async_handler, "post", return_value=mock_response
|
||||
):
|
||||
guardrailed_inputs = await generic_guardrail.apply_guardrail(
|
||||
inputs={"texts": ["Analyze this."], "structured_messages": [
|
||||
{"role": "user", "content": "Analyze this."},
|
||||
{"role": "user", "content": [{"type": "tool_result", "tool_use_id": "t1", "content": "very long uncompressed json " * 100}]},
|
||||
]},
|
||||
request_data=mock_request_data_input,
|
||||
input_type="request",
|
||||
)
|
||||
assert guardrailed_inputs.get("structured_messages") == compressed_messages
|
||||
|
||||
|
||||
class TestImageSupport:
|
||||
"""Test image handling in guardrail requests"""
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue