From 48c2fe18791ec6f4e1f439f3db10c470c4ec8890 Mon Sep 17 00:00:00 2001 From: yucheng Date: Sat, 12 Sep 2026 00:15:16 +0000 Subject: [PATCH] feat(guardrails): add Conduct config model and Admin UI garden entry Expose ConductGuardrailConfigModel through get_config_model() so /guardrails/ui/provider_specific_params returns the api_key, api_base, workspace_id, tool_name, timeout and unreachable_fallback fields, and add the Conduct Guard partner card, preset and logo to the guardrail garden so the integration can be created from the Admin UI Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- .../guardrail_hooks/conduct/conduct.py | 9 ++++ .../guardrails/guardrail_hooks/conduct.py | 42 ++++++++++++++++++ .../guardrail_hooks/test_conduct.py | 36 ++++++++++++++- .../public/assets/logos/conduct.png | Bin 0 -> 12730 bytes .../_components/guardrail_garden_configs.ts | 6 +++ .../_components/guardrail_garden_data.test.ts | 1 + .../_components/guardrail_garden_data.ts | 10 +++++ .../_components/guardrail_info_helpers.tsx | 3 ++ 8 files changed, 106 insertions(+), 1 deletion(-) create mode 100644 litellm/types/proxy/guardrails/guardrail_hooks/conduct.py create mode 100644 ui/litellm-dashboard/public/assets/logos/conduct.png diff --git a/litellm/proxy/guardrails/guardrail_hooks/conduct/conduct.py b/litellm/proxy/guardrails/guardrail_hooks/conduct/conduct.py index 3d7c69ba443..54a6a9b4003 100644 --- a/litellm/proxy/guardrails/guardrail_hooks/conduct/conduct.py +++ b/litellm/proxy/guardrails/guardrail_hooks/conduct/conduct.py @@ -15,6 +15,7 @@ from pydantic import BaseModel, ConfigDict from litellm.integrations.custom_guardrail import CustomGuardrail, log_guardrail_information from litellm.types.llms.openai import ChatCompletionUserMessage +from litellm.types.proxy.guardrails.guardrail_hooks.conduct import ConductGuardrailConfigModel if TYPE_CHECKING: from litellm.litellm_core_utils.litellm_logging import Logging as LiteLLMLoggingObj @@ -104,9 +105,17 @@ except ImportError as import_error: def __init__(self, **kwargs: object) -> None: # kwargs-ok: mirrors the plugin constructor, only raises raise ImportError(MISSING_PACKAGE_MESSAGE) from _import_error + @staticmethod + def get_config_model() -> type[ConductGuardrailConfigModel]: + return ConductGuardrailConfigModel + else: class ConductGuardrail(ConductGuard): # pyright: ignore[reportUntypedBaseClass] # optional dep, absent at type-check + @staticmethod + def get_config_model() -> type[ConductGuardrailConfigModel]: + return ConductGuardrailConfigModel + @log_guardrail_information async def apply_guardrail( self, diff --git a/litellm/types/proxy/guardrails/guardrail_hooks/conduct.py b/litellm/types/proxy/guardrails/guardrail_hooks/conduct.py new file mode 100644 index 00000000000..fbff4363351 --- /dev/null +++ b/litellm/types/proxy/guardrails/guardrail_hooks/conduct.py @@ -0,0 +1,42 @@ +from __future__ import annotations + +from typing import Literal + +from pydantic import BaseModel, Field + +from .base import GuardrailConfigModel + + +class ConductGuardrailConfigModelOptionalParams(BaseModel): + workspace_id: str | None = Field( + default=None, + description="Conduct workspace id, sent as the X-Workspace-Id header. Env: CONDUCT_WORKSPACE_ID.", + ) + tool_name: str | None = Field( + default="llm_call", + description="Conduct tool name the prompt is evaluated under. Match the tool your rules target.", + ) + timeout: float | None = Field( + default=8.0, + gt=0.0, + description="Timeout in seconds for the Conduct check.", + ) + unreachable_fallback: Literal["fail_open", "fail_closed"] | None = Field( + default="fail_closed", + description="Behavior when Conduct is unreachable, times out, or rejects the token.", + ) + + +class ConductGuardrailConfigModel(GuardrailConfigModel[ConductGuardrailConfigModelOptionalParams]): + api_key: str = Field( + min_length=1, + description="Conduct agent token. Env: CONDUCT_AGENT_TOKEN.", + ) + api_base: str | None = Field( + default="https://api.conductai.ai", + description="Conduct API base URL. The MCP endpoint is derived as /mcp.", + ) + + @staticmethod + def ui_friendly_name() -> str: + return "Conduct Guard" diff --git a/tests/test_litellm/proxy/guardrails/guardrail_hooks/test_conduct.py b/tests/test_litellm/proxy/guardrails/guardrail_hooks/test_conduct.py index f2f52bef3bb..730de7a3083 100644 --- a/tests/test_litellm/proxy/guardrails/guardrail_hooks/test_conduct.py +++ b/tests/test_litellm/proxy/guardrails/guardrail_hooks/test_conduct.py @@ -13,6 +13,7 @@ from fastapi import HTTPException import litellm from litellm.integrations.custom_guardrail import CustomGuardrail +from litellm.proxy.guardrails.guardrail_endpoints import get_guardrail_ui_settings, get_provider_specific_params from litellm.proxy.guardrails.guardrail_hooks.conduct import ( DEFAULT_TIMEOUT_SECONDS, ConductGuardrail, @@ -23,10 +24,13 @@ from litellm.proxy.guardrails.guardrail_hooks.conduct.conduct import ( record_decision, request_payload, ) -from litellm.proxy.guardrails.guardrail_endpoints import get_guardrail_ui_settings from litellm.proxy.guardrails.guardrail_registry import InMemoryGuardrailHandler from litellm.types.guardrails import Guardrail, GuardrailEventHooks, LitellmParams from litellm.types.llms.openai import ChatCompletionAssistantMessage +from litellm.types.proxy.guardrails.guardrail_hooks.conduct import ( + ConductGuardrailConfigModel, + ConductGuardrailConfigModelOptionalParams, +) from litellm.types.utils import GenericGuardrailAPIInputs PACKAGE_INSTALLED: Final = importlib.util.find_spec("conduct_litellm_guard") is not None @@ -157,6 +161,36 @@ def test_defaults_when_optional_config_is_omitted() -> None: assert callback.tool_name == "llm_call" +def test_ui_form_defaults_match_what_the_initializer_forwards() -> None: + optional: Final = ConductGuardrailConfigModelOptionalParams() + model: Final = ConductGuardrailConfigModel(api_key="cond_agt_test") + callback: Final = _init( + _params(**{**model.model_dump(exclude={"api_key", "optional_params"}), **optional.model_dump()}) + ) + + assert callback.api_url == model.api_base + assert callback.fail_mode == optional.unreachable_fallback + assert callback.timeout == optional.timeout + assert callback.workspace_id == optional.workspace_id + assert callback.tool_name == optional.tool_name + + +@pytest.mark.asyncio +async def test_ui_offers_conduct_fields_without_the_package() -> None: + assert ConductGuardrail.get_config_model() is ConductGuardrailConfigModel + + fields: Final = (await get_provider_specific_params())["conduct"] + + assert fields["ui_friendly_name"] == "Conduct Guard" + assert fields["api_key"]["required"] is True + assert fields["api_base"]["default_value"] == "https://api.conductai.ai" + optional: Final = fields["optional_params"]["fields"] + assert set(optional) == {"workspace_id", "tool_name", "timeout", "unreachable_fallback"} + assert optional["unreachable_fallback"]["type"] == "select" + assert optional["unreachable_fallback"]["options"] == ["fail_open", "fail_closed"] + assert optional["timeout"]["default_value"] == DEFAULT_TIMEOUT_SECONDS + + @pytest.mark.parametrize("mode", ["during_call", "post_call", "logging_only"]) def test_rejects_modes_the_plugin_does_not_implement(mode: str, monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.delenv("LITELLM_STRICT_GUARDRAIL_MODES", raising=False) diff --git a/ui/litellm-dashboard/public/assets/logos/conduct.png b/ui/litellm-dashboard/public/assets/logos/conduct.png new file mode 100644 index 0000000000000000000000000000000000000000..e68b32df916f7957627f1b34fc319eaf24952225 GIT binary patch literal 12730 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 = { mode: "pre_call", defaultOn: false, }, + conduct: { + provider: "Conduct", + guardrailNameSuggestion: "Conduct Guard", + mode: "pre_call", + defaultOn: false, + }, }; diff --git a/ui/litellm-dashboard/src/app/(dashboard)/guardrails/_components/guardrail_garden_data.test.ts b/ui/litellm-dashboard/src/app/(dashboard)/guardrails/_components/guardrail_garden_data.test.ts index 1e486639840..9a9ab3a61d7 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/guardrails/_components/guardrail_garden_data.test.ts +++ b/ui/litellm-dashboard/src/app/(dashboard)/guardrails/_components/guardrail_garden_data.test.ts @@ -28,6 +28,7 @@ const EXPECTED_PARTNER_LOGO_FILES: Record = { repelloai: "repelloai.png", straiker: "straiker.svg", alice: "alice.svg", + conduct: "conduct.png", }; describe("guardrail_garden_data logos", () => { diff --git a/ui/litellm-dashboard/src/app/(dashboard)/guardrails/_components/guardrail_garden_data.ts b/ui/litellm-dashboard/src/app/(dashboard)/guardrails/_components/guardrail_garden_data.ts index 931b3a111d8..165bd8f9967 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/guardrails/_components/guardrail_garden_data.ts +++ b/ui/litellm-dashboard/src/app/(dashboard)/guardrails/_components/guardrail_garden_data.ts @@ -474,6 +474,16 @@ export const PARTNER_GUARDRAIL_CARDS: GuardrailCardInfo[] = [ tags: ["Content Moderation", "Prompt Injection", "PII", "Policy"], providerKey: "Alice", }, + { + id: "conduct", + name: "Conduct Guard", + description: + "Conduct Guard evaluates prompts against workspace rules before the model call: prompt injection, PII, and custom policies, with block, warning, and approval verdicts.", + category: "partner", + logo: guardrailLogoMap["Conduct Guard"], + tags: ["Security", "Prompt Injection", "PII", "Policy"], + providerKey: "Conduct", + }, ]; export const ALL_CARDS = [...LITELLM_CONTENT_FILTER_CARDS, ...PARTNER_GUARDRAIL_CARDS]; diff --git a/ui/litellm-dashboard/src/app/(dashboard)/guardrails/_components/guardrail_info_helpers.tsx b/ui/litellm-dashboard/src/app/(dashboard)/guardrails/_components/guardrail_info_helpers.tsx index f686ff5644a..fb3cf8f309a 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/guardrails/_components/guardrail_info_helpers.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/guardrails/_components/guardrail_info_helpers.tsx @@ -1,6 +1,7 @@ import aimSecurityLogo from "../../../../../public/assets/logos/aim_security.jpeg"; import aktoLogo from "../../../../../public/assets/logos/akto.svg"; import aliceLogo from "../../../../../public/assets/logos/alice.svg"; +import conductLogo from "../../../../../public/assets/logos/conduct.png"; import aporiaLogo from "../../../../../public/assets/logos/aporia.png"; import bedrockLogo from "../../../../../public/assets/logos/bedrock.svg"; import catoNetworksLogo from "../../../../../public/assets/logos/cato_networks.svg"; @@ -85,6 +86,7 @@ export const guardrail_provider_map: Record = { QostodianNexus: "qostodian_nexus", Repelloai: "repelloai", Alice: "alice", + Conduct: "conduct", }; // Function to populate provider map from API response - updates the original map @@ -208,6 +210,7 @@ export const guardrailLogoMap = { "RepelloAI Argus": repelloAiLogo.src, Straiker: straikerLogo.src, Alice: aliceLogo.src, + "Conduct Guard": conductLogo.src, } satisfies Record; export const getGuardrailLogo = (displayName: string): string | undefined =>