fix(guardrails): scan every chunk when rerouting a streaming guardrail off its OpenAI-only hook

Sampling forwarded unscanned Anthropic deltas before the first scan, so short streams still leaked blocked content.

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
Devin AI 2026-07-30 19:50:25 +00:00
parent 8baaf6e71b
commit 2c4690b3c1
3 changed files with 9 additions and 7 deletions

View file

@ -8,7 +8,7 @@ Unified Guardrail, leveraging LiteLLM's /applyGuardrail endpoint
import copy
import json
from typing import TYPE_CHECKING, Any, AsyncGenerator, List, Union
from typing import TYPE_CHECKING, Any, AsyncGenerator, List, Mapping, Optional, Union
from fastapi import HTTPException
@ -804,6 +804,7 @@ class UnifiedLLMGuardrails(CustomLogger):
user_api_key_dict: UserAPIKeyAuth,
response: Any,
request_data: dict,
streaming_flag_defaults: Optional[Mapping[str, Any]] = None,
) -> AsyncGenerator[Any, None]:
"""
Passes the entire stream to the guardrail
@ -827,10 +828,10 @@ class UnifiedLLMGuardrails(CustomLogger):
guardrail_to_apply: CustomGuardrail = request_data.pop("guardrail_to_apply", None)
# Get streaming configuration. Resolution order (later wins): default
# < guardrail attribute < guardrail_config dict < this callback's
# optional_params.
# < caller-supplied default < guardrail attribute < guardrail_config
# dict < this callback's optional_params.
def _streaming_flag(name: str, default: Any) -> Any:
value = default
value = default if streaming_flag_defaults is None else streaming_flag_defaults.get(name, default)
if guardrail_to_apply is not None:
value = getattr(guardrail_to_apply, name, value)
config = getattr(guardrail_to_apply, "guardrail_config", {})

View file

@ -2705,6 +2705,10 @@ class ProxyLogging:
user_api_key_dict=user_api_key_dict,
request_data=request_data,
response=current_response,
# A guardrail that ships its own iterator hook scans
# every chunk there, so keep that cadence when we
# reroute it: sampling would forward unscanned content.
streaming_flag_defaults=({"streaming_sampling_rate": 1} if effective_kind != kind else None),
),
)

View file

@ -485,9 +485,6 @@ def _content_filter_guardrail():
blocked_words=[{"keyword": "zebra", "action": "BLOCK"}],
default_on=True,
)
# scan every chunk so the block decision lands before the offending delta
# is forwarded, matching the /chat/completions withholding behavior
guardrail.streaming_sampling_rate = 1
return guardrail