mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-22 00:31:44 +00:00
style: satisfy type-discipline budget (LIT008, LIT009)
BerriAI/litellm CI's type-discipline budget check flagged the subclass __init__ shim. Fixes: - Drop the __init__ override entirely — the subclass now inherits __init__ from _BaseConductGuard (when the standalone package is installed) or from CustomGuardrail (fallback). Removes both the banned **kwargs (LIT008) and all four inert # type: ignore markers (LIT009 x 4). - Move the missing-package check into a dedicated raise_if_missing_package() helper called by initialize_guardrail before construction. Preserves the cursor[bot] fix (silent-drop-on-import-failure) without needing a custom __init__. - Fallback branch aliases _BaseConductGuard = CustomGuardrail directly, no type-ignore comment needed. - Test updated to exercise the helper instead of the removed __init__ path; new companion test asserts the helper is a no-op when the package IS installed. Local: ruff --select ANN,TID passes clean. ruff format applied. Same behavioral surface — user-visible error message unchanged.
This commit is contained in:
parent
2e1f320192
commit
ad779be901
3 changed files with 63 additions and 48 deletions
|
|
@ -2,7 +2,7 @@ from typing import TYPE_CHECKING, Final
|
|||
|
||||
from litellm.types.guardrails import SupportedGuardrailIntegrations
|
||||
|
||||
from .conduct import ConductGuardrail
|
||||
from .conduct import ConductGuardrail, raise_if_missing_package
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from litellm.types.guardrails import Guardrail, LitellmParams
|
||||
|
|
@ -37,6 +37,10 @@ def initialize_guardrail(litellm_params: "LitellmParams", guardrail: "Guardrail"
|
|||
"""
|
||||
import litellm
|
||||
|
||||
# Surface the missing-package error at config load, not silently at
|
||||
# module import (cursor[bot] finding — see conduct.py header comment).
|
||||
raise_if_missing_package()
|
||||
|
||||
# ``getattr(..., default)`` only fires when the attribute is missing;
|
||||
# ``LitellmParams`` always defines ``timeout`` and defaults it to
|
||||
# ``None``, so the default was never applied. Use ``or`` so an
|
||||
|
|
|
|||
|
|
@ -24,14 +24,15 @@ _IMPORT_ERROR_MESSAGE = (
|
|||
)
|
||||
|
||||
|
||||
# ── Base plugin import — deferred to init-time ─────────────────────────
|
||||
# Reason: the guardrail-hook auto-discovery loop treats a module-level
|
||||
# ``raise ImportError`` as "hook unavailable" and silently drops the
|
||||
# registration. A user who installed LiteLLM but forgot the
|
||||
# ``conduct-litellm-guard`` dependency would see their config load with
|
||||
# no guardrail active and no error message (cursor[bot] finding on
|
||||
# BerriAI/litellm#38143). Import here without raising; surface the
|
||||
# missing dep at ``__init__`` time when it is actionable.
|
||||
# ── Base plugin import — deferred to init-time via raise_if_missing_package ──
|
||||
# Raising ImportError at module load caused the guardrail-hook auto-loader to
|
||||
# treat a missing ``conduct-litellm-guard`` as "hook unavailable" and silently
|
||||
# drop the registration. Users saw configs load with no guardrail active and
|
||||
# no error message. Instead we fall back to ``CustomGuardrail`` at module load
|
||||
# so the class hierarchy stays intact; ``initialize_guardrail`` (in
|
||||
# ``__init__.py``) calls :func:`raise_if_missing_package` before construction
|
||||
# so the friendly error surfaces when actionable.
|
||||
# (cursor[bot] finding on BerriAI/litellm#38143.)
|
||||
|
||||
try:
|
||||
from conduct_litellm_guard import ConductGuard as _BaseConductGuard
|
||||
|
|
@ -41,45 +42,36 @@ try:
|
|||
from conduct_litellm_guard.guardrail import GuardDecision
|
||||
|
||||
_IMPORT_ERROR: ImportError | None = None
|
||||
except ImportError as _e:
|
||||
_BaseConductGuard = None # type: ignore[assignment,misc]
|
||||
ConductGuardrailBlocked = None # type: ignore[assignment,misc]
|
||||
GuardDecision = None # type: ignore[assignment,misc]
|
||||
_IMPORT_ERROR = _e
|
||||
except ImportError as _import_err:
|
||||
_BaseConductGuard = CustomGuardrail
|
||||
ConductGuardrailBlocked = None
|
||||
GuardDecision = None
|
||||
_IMPORT_ERROR = _import_err
|
||||
|
||||
|
||||
# When the base package isn't installed we still need a real class so
|
||||
# LiteLLM's registry lookup succeeds; the friendly error surfaces on
|
||||
# construction.
|
||||
_ParentClass = _BaseConductGuard if _BaseConductGuard is not None else CustomGuardrail
|
||||
|
||||
|
||||
class ConductGuardrail(_ParentClass): # type: ignore[valid-type,misc]
|
||||
class ConductGuardrail(_BaseConductGuard):
|
||||
"""LiteLLM adapter over ``conduct_litellm_guard.ConductGuard``.
|
||||
|
||||
Subclass exists so we can:
|
||||
- Advertise supported event hooks honestly to LiteLLM (see
|
||||
``get_supported_event_hooks``).
|
||||
- Raise a friendly error at construction time when the base package
|
||||
isn't installed (rather than at module import — see comment
|
||||
above).
|
||||
Inherits its ``__init__`` from the base runtime when the standalone
|
||||
package is installed; otherwise inherits from ``CustomGuardrail``
|
||||
and ``initialize_guardrail`` short-circuits with a friendly error
|
||||
before this class is ever constructed.
|
||||
|
||||
Only two additions on this side:
|
||||
- ``SUPPORTED_EVENT_HOOKS`` / ``get_supported_event_hooks`` so
|
||||
LiteLLM validates configs against modes we actually implement.
|
||||
"""
|
||||
|
||||
# Advertised event hooks. The plugin currently runs at pre_call
|
||||
# (input rail) — a policy block short-circuits before the model
|
||||
# sees the prompt, which is the semantic LiteLLM users expect for
|
||||
# a "guardrail". ``during_call`` / ``post_call`` support lands with
|
||||
# 0.3.x once the underlying Conduct response gate is wired through
|
||||
# ``guard_check_response`` (tracked in the plugin repo). Advertising
|
||||
# only pre_call today prevents silent bypass of ``during_call``
|
||||
# a "guardrail". ``during_call`` / ``post_call`` support lands
|
||||
# with plugin 0.3.x once the underlying Conduct response gate is
|
||||
# wired through ``guard_check_response``. Advertising only
|
||||
# pre_call today prevents silent bypass of ``during_call``
|
||||
# configurations — see veria-ai finding on BerriAI/litellm#38143.
|
||||
SUPPORTED_EVENT_HOOKS: ClassVar[tuple[GuardrailEventHooks, ...]] = (GuardrailEventHooks.pre_call,)
|
||||
|
||||
def __init__(self, *args: object, **kwargs: object) -> None:
|
||||
if _IMPORT_ERROR is not None or _BaseConductGuard is None:
|
||||
raise ImportError(_IMPORT_ERROR_MESSAGE) from _IMPORT_ERROR
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
@classmethod
|
||||
def get_supported_event_hooks(cls) -> list[GuardrailEventHooks]:
|
||||
"""LiteLLM calls this during config validation to reject
|
||||
|
|
@ -88,4 +80,19 @@ class ConductGuardrail(_ParentClass): # type: ignore[valid-type,misc]
|
|||
return list(cls.SUPPORTED_EVENT_HOOKS)
|
||||
|
||||
|
||||
__all__ = ["ConductGuardrail", "ConductGuardrailBlocked", "GuardDecision"]
|
||||
def raise_if_missing_package() -> None:
|
||||
"""Called by ``initialize_guardrail`` before constructing the class.
|
||||
|
||||
Surfaces the friendly ``pip install`` error at the actionable moment
|
||||
(config load) rather than silently dropping the hook at module load.
|
||||
"""
|
||||
if _IMPORT_ERROR is not None:
|
||||
raise ImportError(_IMPORT_ERROR_MESSAGE) from _IMPORT_ERROR
|
||||
|
||||
|
||||
__all__ = [
|
||||
"ConductGuardrail",
|
||||
"ConductGuardrailBlocked",
|
||||
"GuardDecision",
|
||||
"raise_if_missing_package",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -182,24 +182,28 @@ def test_initialize_applies_timeout_default_when_field_is_none(
|
|||
assert captured["timeout"] == 8.0
|
||||
|
||||
|
||||
def test_missing_standalone_package_raises_at_construction() -> None:
|
||||
def test_missing_standalone_package_raises_at_initialize() -> None:
|
||||
"""Regression for cursor[bot] finding — the previous shim raised
|
||||
``ImportError`` at module load, which the guardrail-hook auto-loader
|
||||
treats as "hook unavailable" and silently drops. The subclass now
|
||||
imports lazily and raises at ``__init__`` time when actionable."""
|
||||
treats as "hook unavailable" and silently drops. The check is now
|
||||
deferred to :func:`raise_if_missing_package` which
|
||||
``initialize_guardrail`` calls at config-load time when actionable."""
|
||||
from litellm.proxy.guardrails.guardrail_hooks.conduct import conduct as _mod
|
||||
|
||||
original_base = _mod._BaseConductGuard
|
||||
original_error = _mod._IMPORT_ERROR
|
||||
try:
|
||||
_mod._BaseConductGuard = None # type: ignore[assignment]
|
||||
_mod._IMPORT_ERROR = ImportError("simulated missing package")
|
||||
|
||||
# Class is still importable — no module-load side effect.
|
||||
from litellm.proxy.guardrails.guardrail_hooks.conduct import ConductGuardrail
|
||||
|
||||
with pytest.raises(ImportError, match="pip install"):
|
||||
ConductGuardrail()
|
||||
_mod.raise_if_missing_package()
|
||||
finally:
|
||||
_mod._BaseConductGuard = original_base # type: ignore[assignment]
|
||||
_mod._IMPORT_ERROR = original_error
|
||||
|
||||
|
||||
def test_raise_if_missing_package_is_noop_when_present() -> None:
|
||||
"""The check should be silent when ``conduct-litellm-guard`` is
|
||||
importable (the normal case for anyone who ``pip install``ed it)."""
|
||||
from litellm.proxy.guardrails.guardrail_hooks.conduct.conduct import (
|
||||
raise_if_missing_package,
|
||||
)
|
||||
|
||||
raise_if_missing_package() # must not raise
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue