From a62337a9eaf83b3059077d876f9121e76dee663b Mon Sep 17 00:00:00 2001 From: Conduct AI Date: Thu, 10 Sep 2026 21:31:43 -0500 Subject: [PATCH] fix: real stub class in the missing-package fallback MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Runtime regression in the previous simplification — the guardrail registry iterates every registered class at load time and calls get_supported_event_hooks(). Fallback of ConductGuardrail = None crashed the whole registry with AttributeError, which cascaded into unrelated guardrails' tests (noma_v2, repelloai, hide_secrets, provider_specific_params, etc.). Fallback now defines ConductGuardrail as a real subclass of CustomGuardrail with the required class attrs (SUPPORTED_EVENT_HOOKS + get_supported_event_hooks). Matches the pattern the guardrails_ai integration already uses in the same repo. raise_if_missing_package still fires before instantiation so users see the friendly pip install error. All four budget gates re-verified locally against upstream/litellm_internal_staging: ruff_strict_gate OK test_quality_gate OK type_discipline_gate OK type_check_gate OK --- .../guardrail_hooks/conduct/conduct.py | 49 +++++++++++++------ 1 file changed, 35 insertions(+), 14 deletions(-) diff --git a/litellm/proxy/guardrails/guardrail_hooks/conduct/conduct.py b/litellm/proxy/guardrails/guardrail_hooks/conduct/conduct.py index b4747c14e3c..9826e27ffaf 100644 --- a/litellm/proxy/guardrails/guardrail_hooks/conduct/conduct.py +++ b/litellm/proxy/guardrails/guardrail_hooks/conduct/conduct.py @@ -1,11 +1,18 @@ """Conduct Guard as a LiteLLM guardrail. -Pure alias for the ``conduct-litellm-guard`` PyPI package. The +Thin alias for the ``conduct-litellm-guard`` PyPI package. The ``ConductGuard`` class ships with ``SUPPORTED_EVENT_HOOKS`` + ``get_supported_event_hooks`` since plugin 0.2.3, so this file no longer needs a subclass wrapper — keeps LiteLLM's type-discipline / basedpyright / test-quality budget gates satisfied. +When the standalone package isn't installed we still register a real +stub class so LiteLLM's guardrail registry can scan +``get_supported_event_hooks`` at load time without crashing (see +BerriAI/litellm#38143 CI regression: registry iteration expects every +registered class to expose the hooks classmethod). Instantiation of +the stub raises ``ImportError`` via ``raise_if_missing_package``. + Install: ``pip install "conduct-litellm-guard>=0.2.3"`` Source: https://github.com/sseshachala/conductai/tree/main/packages/conduct-litellm-guard Docs: https://conductai.ai/guard @@ -13,21 +20,16 @@ Docs: https://conductai.ai/guard from __future__ import annotations +from typing import ClassVar + +from litellm.integrations.custom_guardrail import CustomGuardrail + _import_error_message = ( "conduct-litellm-guard is required for the Conduct guardrail. " 'Install it with: pip install "conduct-litellm-guard>=0.2.3"' ) -# ── Base plugin import ───────────────────────────────────────────────── -# 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 surface the friendly error at -# config-load time from :func:`raise_if_missing_package` — called by -# ``initialize_guardrail`` before construction. -# (cursor[bot] finding on BerriAI/litellm#38143.) - try: from conduct_litellm_guard import ConductGuard as ConductGuardrail from conduct_litellm_guard.guardrail import ( @@ -36,18 +38,37 @@ try: from conduct_litellm_guard.guardrail import GuardDecision _import_error: ImportError | None = None -except ImportError as _import_err: - ConductGuardrail = None +except ImportError as _err: + + class ConductGuardrail(CustomGuardrail): + """Stub used when ``conduct-litellm-guard`` isn't installed. + + Exposes the class-level surface LiteLLM's guardrail registry + scans at load time — ``SUPPORTED_EVENT_HOOKS`` + + ``get_supported_event_hooks`` — so the registry doesn't crash + when this hook is discovered without the runtime dependency. + ``initialize_guardrail`` calls ``raise_if_missing_package`` + before ever constructing this class, so users see a friendly + ``pip install`` error rather than the stub silently activating. + """ + + SUPPORTED_EVENT_HOOKS: ClassVar[tuple[str, ...]] = ("pre_call",) + + @classmethod + def get_supported_event_hooks(cls) -> list[str]: + return list(cls.SUPPORTED_EVENT_HOOKS) + ConductGuardrailBlocked = None GuardDecision = None - _import_error = _import_err + _import_error = _err 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. + (config load) rather than silently dropping the hook or letting the + stub run. """ if _import_error is not None: raise ImportError(_import_error_message) from _import_error