mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-07 02:59:05 +00:00
fix(realtime): forward sanitized function_call_output on guardrail block
Providers that pair every toolCall with a toolResponse (e.g. Gemini and Vertex Live) stay in the awaiting-tool-call state until a toolResponse arrives. Dropping a blocked function_call_output outright left those providers stalled — the subsequent guardrail clientContent and response.create were ignored because the prior toolCall had no matching toolResponse. When the client-supplied tool output fails the realtime guardrail check, forward a sanitized placeholder function_call_output (same call_id, generic policy marker as output) instead of dropping the message entirely. The placeholder carries no blocked content, so the model never sees it, while still completing the provider's tool-call cycle so the session can recover and the violation message reaches the user.
This commit is contained in:
parent
d3490859a4
commit
70e1169989
2 changed files with 33 additions and 2 deletions
|
|
@ -899,6 +899,30 @@ class RealTimeStreaming:
|
|||
output_text
|
||||
)
|
||||
if blocked:
|
||||
# Forward a sanitized function_call_output so
|
||||
# providers that pair every toolCall with a
|
||||
# toolResponse (e.g. Gemini/Vertex Live) exit
|
||||
# their pending-tool-call state. Dropping the
|
||||
# blocked item outright would leave such
|
||||
# providers waiting indefinitely while the
|
||||
# subsequent guardrail clientContent is
|
||||
# ignored. The sanitized payload carries no
|
||||
# blocked content — only a generic policy
|
||||
# marker.
|
||||
sanitized_msg = json.dumps(
|
||||
{
|
||||
**msg_obj,
|
||||
"item": {
|
||||
**item,
|
||||
"output": json.dumps(
|
||||
{
|
||||
"error": "Tool output blocked by content policy",
|
||||
}
|
||||
),
|
||||
},
|
||||
}
|
||||
)
|
||||
await self._send_to_backend(sanitized_msg)
|
||||
self._pending_guardrail_message = output_text
|
||||
continue
|
||||
|
||||
|
|
|
|||
|
|
@ -951,10 +951,17 @@ async def test_realtime_function_call_output_guardrail_blocks_and_returns_error(
|
|||
and json.loads(m).get("type") == "conversation.item.create"
|
||||
and json.loads(m).get("item", {}).get("type") == "function_call_output"
|
||||
]
|
||||
assert len(forwarded_tool_outputs) == 0, (
|
||||
f"Blocked function_call_output should not be forwarded, got: "
|
||||
# A sanitized placeholder must reach the backend so providers that pair
|
||||
# every toolCall with a toolResponse (Gemini/Vertex Live) exit their
|
||||
# pending-tool-call state instead of stalling. The placeholder must NOT
|
||||
# contain any of the blocked content.
|
||||
assert len(forwarded_tool_outputs) == 1, (
|
||||
f"Sanitized function_call_output should be forwarded, got: "
|
||||
f"{forwarded_tool_outputs}"
|
||||
)
|
||||
sanitized_item = forwarded_tool_outputs[0]["item"]
|
||||
assert sanitized_item["call_id"] == "call_123"
|
||||
assert "test@example.com" not in sanitized_item["output"]
|
||||
|
||||
litellm.callbacks = [] # cleanup
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue