mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-14 23:21:35 +00:00
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>
This commit is contained in:
parent
7ffde11054
commit
48c2fe1879
8 changed files with 106 additions and 1 deletions
|
|
@ -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,
|
||||
|
|
|
|||
42
litellm/types/proxy/guardrails/guardrail_hooks/conduct.py
Normal file
42
litellm/types/proxy/guardrails/guardrail_hooks/conduct.py
Normal file
|
|
@ -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 <api_base>/mcp.",
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def ui_friendly_name() -> str:
|
||||
return "Conduct Guard"
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
BIN
ui/litellm-dashboard/public/assets/logos/conduct.png
Normal file
BIN
ui/litellm-dashboard/public/assets/logos/conduct.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
|
|
@ -318,4 +318,10 @@ export const GUARDRAIL_PRESETS: Record<string, GuardrailPreset> = {
|
|||
mode: "pre_call",
|
||||
defaultOn: false,
|
||||
},
|
||||
conduct: {
|
||||
provider: "Conduct",
|
||||
guardrailNameSuggestion: "Conduct Guard",
|
||||
mode: "pre_call",
|
||||
defaultOn: false,
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ const EXPECTED_PARTNER_LOGO_FILES: Record<string, string> = {
|
|||
repelloai: "repelloai.png",
|
||||
straiker: "straiker.svg",
|
||||
alice: "alice.svg",
|
||||
conduct: "conduct.png",
|
||||
};
|
||||
|
||||
describe("guardrail_garden_data logos", () => {
|
||||
|
|
|
|||
|
|
@ -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];
|
||||
|
|
|
|||
|
|
@ -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<string, string> = {
|
|||
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<string, string>;
|
||||
|
||||
export const getGuardrailLogo = (displayName: string): string | undefined =>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue