From a5e41d91acdf4080ae8c268b0e64c3edb3481b5f Mon Sep 17 00:00:00 2001 From: mateo-berri <277851410+mateo-berri@users.noreply.github.com> Date: Wed, 28 Oct 2026 12:27:16 -0700 Subject: [PATCH] fix(guardrails): say the decisions warning covers only skipped question instructions --- litellm/proxy/utils.py | 11 +++++-- .../test_proxy_logging_hook_detection.py | 31 +++++++++++++------ 2 files changed, 30 insertions(+), 12 deletions(-) diff --git a/litellm/proxy/utils.py b/litellm/proxy/utils.py index ede7dcd9243..c0b50c552ec 100644 --- a/litellm/proxy/utils.py +++ b/litellm/proxy/utils.py @@ -345,6 +345,7 @@ _VIEW_SETUP_GATE_TABLE: Final = '"LiteLLM_SpendLogs"' _VIEW_SETUP_GATE_PROBE_ROWS: Final = TypeAdapter(tuple[Mapping[str, bool], ...]) _DELEGATED_ATTRIBUTE: Final = TypeAdapter(object) _REQUESTED_GUARDRAILS: Final = TypeAdapter(tuple[object, ...]) +_DECISIONS_CALL_TYPES: Final = frozenset((CallTypes.decisions.value, CallTypes.adecisions.value)) _ViewSetupOutcome: TypeAlias = Literal["ready", "timed_out"] _ViewSetupAttempt: TypeAlias = Literal["ready", "table_missing"] | Exception @@ -741,12 +742,18 @@ def _warn_on_guardrails_that_cannot_scan(requested: tuple[object, ...], call_typ ) if unscannable: verbose_proxy_logger.warning( - "Guardrails %s do not implement apply_guardrail, so they cannot scan the text of %s requests", + "Guardrails %s do not implement apply_guardrail, so they %s", ", ".join(unscannable), - call_type, + _unscanned_request_text(call_type), ) +def _unscanned_request_text(call_type: str) -> str: + if call_type in _DECISIONS_CALL_TYPES: + return f"skip the question instructions of {call_type} requests and see the input only where their own hook reads it" + return f"cannot scan the text of {call_type} requests" + + def _merge_pipeline_metadata_bucket(data: dict, bucket_key: str, modified_bucket_value: object) -> None: if not isinstance(modified_bucket_value, dict): return diff --git a/tests/unit/proxy/test_proxy_logging_hook_detection.py b/tests/unit/proxy/test_proxy_logging_hook_detection.py index a6671aba7a6..d3d00aa0794 100644 --- a/tests/unit/proxy/test_proxy_logging_hook_detection.py +++ b/tests/unit/proxy/test_proxy_logging_hook_detection.py @@ -1205,9 +1205,24 @@ class _ReadsNoDecisionText(CustomGuardrail): @pytest.mark.asyncio -@pytest.mark.parametrize(("call_type", "listed"), (("aextraction", False), ("acompletion", True))) +@pytest.mark.parametrize( + ("call_type", "listed", "warning"), + ( + ( + "aextraction", + False, + "Guardrails requested-native, default-native do not implement apply_guardrail, so they cannot scan the text of aextraction requests", + ), + ( + "adecisions", + False, + "Guardrails requested-native, default-native do not implement apply_guardrail, so they skip the question instructions of adecisions requests and see the input only where their own hook reads it", + ), + ("acompletion", True, None), + ), +) async def test_requested_guardrail_without_apply_guardrail_is_listed_only_where_it_reads_the_text( - monkeypatch, caplog, call_type, listed + monkeypatch, caplog, call_type, listed, warning ): requested = _ReadsNoDecisionText("requested-native") default_on = _ReadsNoDecisionText("default-native", default_on=True) @@ -1228,14 +1243,10 @@ async def test_requested_guardrail_without_apply_guardrail_is_listed_only_where_ assert requested.native_hooks_ran == [call_type] assert ("requested-native" in _applied_guardrails(result)) is listed - warnings = [record.getMessage() for record in caplog.records if "cannot scan" in record.getMessage()] - assert warnings == ( - [] - if listed - else [ - "Guardrails requested-native, default-native do not implement apply_guardrail, so they cannot scan the text of aextraction requests" - ] - ) + warnings = [ + record.getMessage() for record in caplog.records if "do not implement apply_guardrail" in record.getMessage() + ] + assert warnings == ([] if warning is None else [warning]) class _ReroutesToPrivateModel(CustomGuardrail):