From 47cf3170a2b8ee937240777916d0f2139cb6ac76 Mon Sep 17 00:00:00 2001 From: Ninad Phalak Date: Sun, 13 Sep 2026 20:09:12 -0500 Subject: [PATCH] fix(generic_guardrail_api): annotate the returned tool calls exactly, never rebind The type alias that named this field read the request's field through a TypedDict subscript, which ruff reads as a forward reference, and it failed the basedpyright budget. The annotations spell the union out again, which is exact and checks against GenericGuardrailAPIInputs without an alias and without a suppression comment. The unusable-reply path no longer rebinds a Final local, so it needs no suppression comment either: the calls that will be applied are picked in one expression. --- .../generic_guardrail_api.py | 20 +++++++++---------- .../guardrail_hooks/generic_guardrail_api.py | 10 ++++------ 2 files changed, 13 insertions(+), 17 deletions(-) diff --git a/litellm/proxy/guardrails/guardrail_hooks/generic_guardrail_api/generic_guardrail_api.py b/litellm/proxy/guardrails/guardrail_hooks/generic_guardrail_api/generic_guardrail_api.py index 153b6275652..9912cfcf736 100644 --- a/litellm/proxy/guardrails/guardrail_hooks/generic_guardrail_api/generic_guardrail_api.py +++ b/litellm/proxy/guardrails/guardrail_hooks/generic_guardrail_api/generic_guardrail_api.py @@ -24,15 +24,14 @@ from litellm.llms.custom_httpx.http_handler import ( httpxSpecialProvider, ) from litellm.types.guardrails import GuardrailEventHooks -from litellm.types.llms.openai import ChatCompletionToolParam +from litellm.types.llms.openai import ChatCompletionToolCallChunk, ChatCompletionToolParam from litellm.types.proxy.guardrails.guardrail_hooks.generic_guardrail_api import ( GenericGuardrailAPIMetadata, GenericGuardrailAPIRequest, GenericGuardrailAPIResponse, - GenericGuardrailAPIToolCalls, GuardrailToolParam, ) -from litellm.types.utils import GenericGuardrailAPIInputs +from litellm.types.utils import ChatCompletionMessageToolCall, GenericGuardrailAPIInputs if TYPE_CHECKING: from litellm.litellm_core_utils.litellm_logging import Logging as LiteLLMLoggingObj @@ -334,7 +333,7 @@ class GenericGuardrailAPI(CustomGuardrail): images: list[str] | None, tools: list[ChatCompletionToolParam] | None, guardrail_response: GenericGuardrailAPIResponse, - tool_calls: GenericGuardrailAPIToolCalls | None = None, + tool_calls: list[ChatCompletionToolCallChunk] | list[ChatCompletionMessageToolCall] | None = None, ) -> GenericGuardrailAPIInputs: # Action is NONE or no modifications needed return_inputs: Final = GenericGuardrailAPIInputs(texts=texts) @@ -348,16 +347,15 @@ class GenericGuardrailAPI(CustomGuardrail): return_inputs["tools"] = guardrail_response.tools elif tools: return_inputs["tools"] = tools - returned_tool_calls: Final = guardrail_response.tool_calls - if returned_tool_calls and not all(_returned_tool_call_is_usable(call) for call in returned_tool_calls): + returned: Final = guardrail_response.tool_calls + usable: Final = returned is not None and all(_returned_tool_call_is_usable(call) for call in returned) + if returned and not usable: verbose_proxy_logger.warning( "Generic Guardrail API returned tool calls missing a function name or arguments; keeping the ones already sent", ) - returned_tool_calls = None # rebind-ok: an unusable reply is discarded - if returned_tool_calls: - return_inputs["tool_calls"] = returned_tool_calls - elif tool_calls: - return_inputs["tool_calls"] = tool_calls + selected: Final = returned if usable else tool_calls + if selected: + return_inputs["tool_calls"] = selected if guardrail_response.stream_holdback_chars is not None: return_inputs["stream_holdback_chars"] = guardrail_response.stream_holdback_chars return return_inputs diff --git a/litellm/types/proxy/guardrails/guardrail_hooks/generic_guardrail_api.py b/litellm/types/proxy/guardrails/guardrail_hooks/generic_guardrail_api.py index f86d655989e..fe49c279960 100644 --- a/litellm/types/proxy/guardrails/guardrail_hooks/generic_guardrail_api.py +++ b/litellm/types/proxy/guardrails/guardrail_hooks/generic_guardrail_api.py @@ -1,4 +1,4 @@ -from typing import Any, Final, Literal, TypeAlias +from typing import Any, Final, Literal from pydantic import BaseModel, ConfigDict, Field from typing_extensions import TypedDict @@ -8,9 +8,7 @@ from litellm.types.llms.openai import ( ChatCompletionToolCallChunk, ) from litellm.types.proxy.guardrails.guardrail_hooks.base import GuardrailConfigModel -from litellm.types.utils import ChatCompletionMessageToolCall, GenericGuardrailAPIInputs - -GenericGuardrailAPIToolCalls: TypeAlias = GenericGuardrailAPIInputs["tool_calls"] # noqa: F821 # TypedDict field reference, not a name +from litellm.types.utils import ChatCompletionMessageToolCall class GuardrailToolParam(BaseModel): @@ -169,7 +167,7 @@ class GenericGuardrailAPIResponse: action: str blocked_reason: str | None stream_holdback_chars: list[int] | None - tool_calls: GenericGuardrailAPIToolCalls | None + tool_calls: list[ChatCompletionToolCallChunk] | list[ChatCompletionMessageToolCall] | None def __init__( self, @@ -179,7 +177,7 @@ class GenericGuardrailAPIResponse: images: list[str] | None = None, tools: list[GuardrailToolParam] | None = None, stream_holdback_chars: list[int] | None = None, - tool_calls: GenericGuardrailAPIToolCalls | None = None, + tool_calls: list[ChatCompletionToolCallChunk] | list[ChatCompletionMessageToolCall] | None = None, ) -> None: self.action = action self.blocked_reason = blocked_reason