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.
This commit is contained in:
Ninad Phalak 2026-09-13 20:09:12 -05:00
parent f0f06aec07
commit 47cf3170a2
No known key found for this signature in database
GPG key ID: 59119ED515433744
2 changed files with 13 additions and 17 deletions

View file

@ -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

View file

@ -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