From e5fac49e7ba804e203a0d18d1d162da5f5e790ab Mon Sep 17 00:00:00 2001 From: yucheng Date: Tue, 29 Sep 2026 21:17:38 +0000 Subject: [PATCH] fix(grayswan): resolve post-call surface from request route before call_type Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- .../guardrail_hooks/grayswan/grayswan.py | 14 +++++++++-- .../guardrail_hooks/test_grayswan.py | 24 +++++++++++++++++++ 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/litellm/proxy/guardrails/guardrail_hooks/grayswan/grayswan.py b/litellm/proxy/guardrails/guardrail_hooks/grayswan/grayswan.py index 1ef564e5ea1..6841098730e 100644 --- a/litellm/proxy/guardrails/guardrail_hooks/grayswan/grayswan.py +++ b/litellm/proxy/guardrails/guardrail_hooks/grayswan/grayswan.py @@ -615,10 +615,20 @@ class GraySwanGuardrail(CustomGuardrail): Returns the scoped context messages plus the request's tool definitions, or ``((), None)`` when the request surface cannot be resolved. """ + from litellm.litellm_core_utils.api_route_to_call_types import get_call_types_for_route from litellm.llms import load_guardrail_translation_mappings - call_type: Final = (logging_obj.call_type if logging_obj is not None else None) or getattr( - request_data.get("litellm_logging_obj"), "call_type", None + litellm_metadata: Final = request_data.get("litellm_metadata") + request_route: Final = ( + litellm_metadata.get("user_api_key_request_route") if isinstance(litellm_metadata, Mapping) else None + ) + route_call_types: Final = ( + get_call_types_for_route(request_route) if isinstance(request_route, str) else None + ) + call_type: Final = ( + (route_call_types[0].value if route_call_types else None) + or (logging_obj.call_type if logging_obj is not None else None) + or getattr(request_data.get("litellm_logging_obj"), "call_type", None) ) if not isinstance(call_type, str): return (), None diff --git a/tests/test_litellm/proxy/guardrails/guardrail_hooks/test_grayswan.py b/tests/test_litellm/proxy/guardrails/guardrail_hooks/test_grayswan.py index 1a689ecbc02..bcfdab30be0 100644 --- a/tests/test_litellm/proxy/guardrails/guardrail_hooks/test_grayswan.py +++ b/tests/test_litellm/proxy/guardrails/guardrail_hooks/test_grayswan.py @@ -747,6 +747,30 @@ async def test_post_call_scan_only_tool_results_scopes_context_and_tools() -> No assert "tools" not in payload +@pytest.mark.asyncio +async def test_post_call_prefers_request_route_over_logging_call_type() -> None: + guardrail = _post_call_guardrail() + client = _CapturingClient() + guardrail.async_handler = client + + await guardrail.apply_guardrail( + inputs={"texts": ["response text"]}, + request_data={ + **_REQUEST_DATA, + "litellm_metadata": {"user_api_key_request_route": "/v1/chat/completions"}, + }, + input_type="response", + logging_obj=_LoggingObj("responses"), + ) + + payload = client.calls[0]["json"] + assert list(payload["messages"]) == [ + *_REQUEST_DATA["messages"], + {"role": "assistant", "content": "response text"}, + ] + assert list(payload["tools"]) == _REQUEST_DATA["tools"] + + @pytest.mark.asyncio async def test_post_call_unresolvable_call_type_sends_response_only() -> None: guardrail = _post_call_guardrail()