mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-10 03:28:53 +00:00
fix(guardrails): filter Add-Guardrail mode dropdown per provider (#32712)
* fix(guardrails): filter Add-Guardrail mode dropdown per provider The GET /guardrails/ui/add_guardrail_settings endpoint returned every GuardrailEventHooks value in one flat supported_modes list, so the Admin UI rendered pre_mcp_call as a selectable Mode for every guardrail. Saving Content Filter or Tool Permission with pre_mcp_call then failed with a 400 because those guardrails' server-side supported_event_hooks list excludes it. Expose each guardrail's supported hooks as a get_supported_event_hooks classmethod on CustomGuardrail (mirrors the existing get_config_model pattern) and have the endpoint iterate guardrail_class_registry to build a supported_modes_by_provider map. The UI Mode dropdown filters by that map when the selected provider is known and falls back to the global list otherwise. __init__ now sources its own supported_event_hooks list from the classmethod so the two sides can't drift. Also register BedrockGuardrail, ToolPermissionGuardrail, lakera, lakera_v2, and presidio in guardrail_class_registry so they participate in the map (they were previously only in guardrail_initializer_registry and had no class-registry entry). Behavior change: guardrails that previously had no supported_event_hooks declared (aim, javelin, azure/text_moderation, cato_networks, crowdstrike_aidr, headroom, hiddenlayer, lasso, noma, onyx, prompt_security, qualifire, repelloai, zscaler_ai_guard, aporia_ai, lakera_ai, lakera_ai_v2, mcp_jwt_signer, model_armor, presidio) now validate the configured mode at instantiation. Existing configs where the mode was silently a no-op will fail at proxy startup with a clear validation error rather than running as a broken guardrail. Resolves LIT-4226 * fix(guardrails): add LITELLM_STRICT_GUARDRAIL_MODES escape hatch, preserve current mode in edit form Address Greptile P1 (startup break) and P2 (edit form UX): LITELLM_STRICT_GUARDRAIL_MODES defaults to true (raise on unsupported event_hook, unchanged behavior for the guardrails validated pre-PR). Setting it to false logs a warning and continues, giving deployments an opt-out while they fix configs that now surface as errors instead of silently no-op'ing. Regression test covers both modes. Edit form now surfaces the currently-saved mode even when it is not in the filtered per-provider list, so a legacy row (e.g. content_filter saved with pre_mcp_call before this fix) no longer disappears from the dropdown; the option renders with a 'not supported by <provider>' note so the user knows to pick another. * fix(guardrails): correct audited hook lists, prune stale modes on provider switch, clean form lint Audited every get_supported_event_hooks classmethod against the hooks each guardrail's own tests exercise and its handler methods. Five were too narrow and their tests caught it in CI: rubrik gains pre_call, presidio gains during_call and pre_mcp_call, prompt_security, onyx and qualifire gain during_call. The remaining classes match either their original __init__ declarations or their exercised modes exactly. Cursor review fixes: the Add form now drops selected modes the new provider does not support when the user switches providers, so a pre_mcp_call selection cannot ride along into a provider that rejects it at save; the edit form handles list-shaped stored modes instead of treating mode as always a string. Extracted shared toModeArray and getSupportedModesForProvider helpers into guardrail_info_helpers so both forms use one implementation, typed the remaining any usages in both forms, removed nested ternaries, and committed the ratcheted-down eslint metrics and pruned suppressions
This commit is contained in:
parent
f9ed4f8aea
commit
69c5839cc0
59 changed files with 1222 additions and 684 deletions
|
|
@ -1,3 +1,4 @@
|
|||
import os
|
||||
import secrets
|
||||
from datetime import datetime
|
||||
from typing import (
|
||||
|
|
@ -17,6 +18,7 @@ from litellm._logging import verbose_logger
|
|||
from litellm.litellm_core_utils.core_helpers import redact_nested_match_and_regex_keys
|
||||
from litellm.caching import DualCache
|
||||
from litellm.integrations.custom_logger import CustomLogger
|
||||
from litellm.secret_managers.main import str_to_bool
|
||||
from litellm.types.guardrails import (
|
||||
DynamicGuardrailParams,
|
||||
GuardrailEventHooks,
|
||||
|
|
@ -59,6 +61,20 @@ from litellm.exceptions import (
|
|||
_PRE_CALL_EXECUTED_TOKEN = secrets.token_hex(16)
|
||||
|
||||
|
||||
def _strict_guardrail_modes_enabled() -> bool:
|
||||
"""Whether guardrail-mode validation raises (default) or logs a warning.
|
||||
|
||||
Set `LITELLM_STRICT_GUARDRAIL_MODES=false` to keep the pre-LIT-4226 behavior
|
||||
for guardrails whose supported_event_hooks list newly includes their
|
||||
configured mode: log the mismatch and continue instead of raising at boot.
|
||||
"""
|
||||
raw = os.environ.get("LITELLM_STRICT_GUARDRAIL_MODES")
|
||||
if raw is None:
|
||||
return True
|
||||
parsed = str_to_bool(raw)
|
||||
return True if parsed is None else parsed
|
||||
|
||||
|
||||
def get_session_id_from_request_data(request_data: Dict[str, Any]) -> Optional[str]:
|
||||
"""Extract session_id from request data (litellm_session_id or metadata)."""
|
||||
session_id = request_data.get("litellm_session_id")
|
||||
|
|
@ -132,7 +148,17 @@ class CustomGuardrail(CustomLogger):
|
|||
|
||||
if supported_event_hooks:
|
||||
## validate event_hook is in supported_event_hooks
|
||||
self._validate_event_hook(event_hook, supported_event_hooks)
|
||||
try:
|
||||
self._validate_event_hook(event_hook, supported_event_hooks)
|
||||
except ValueError as validation_error:
|
||||
if _strict_guardrail_modes_enabled():
|
||||
raise
|
||||
verbose_logger.warning(
|
||||
"%s. LITELLM_STRICT_GUARDRAIL_MODES=false; continuing "
|
||||
"with unsupported event_hook. Set the env var to true "
|
||||
"(default) to enforce validation and fail at startup.",
|
||||
validation_error,
|
||||
)
|
||||
super().__init__(**kwargs)
|
||||
|
||||
def render_violation_message(self, default: str, context: Optional[Dict[str, Any]] = None) -> str:
|
||||
|
|
@ -303,6 +329,18 @@ class CustomGuardrail(CustomLogger):
|
|||
"""
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
def get_supported_event_hooks(cls) -> Optional[List[GuardrailEventHooks]]:
|
||||
"""
|
||||
Returns the event hooks this guardrail supports, for the UI to render.
|
||||
|
||||
Subclasses should override to return their supported hooks list. When a
|
||||
subclass returns None, the endpoint omits it from the per-provider map
|
||||
and the UI is expected to fall back to the global `supported_modes`
|
||||
list client-side.
|
||||
"""
|
||||
return None
|
||||
|
||||
def _validate_event_hook(
|
||||
self,
|
||||
event_hook: Optional[Union[GuardrailEventHooks, List[GuardrailEventHooks], Mode]],
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import time
|
|||
import urllib.parse
|
||||
import uuid
|
||||
from collections import Counter
|
||||
from typing import TYPE_CHECKING, Any, Literal, Optional
|
||||
from typing import TYPE_CHECKING, Any, List, Literal, Optional
|
||||
|
||||
import httpx
|
||||
from litellm._logging import verbose_logger
|
||||
|
|
@ -52,6 +52,10 @@ class _MalformedToolBlockingResponseError(Exception):
|
|||
|
||||
|
||||
class RubrikLogger(CustomGuardrail, CustomBatchLogger):
|
||||
@classmethod
|
||||
def get_supported_event_hooks(cls) -> List[GuardrailEventHooks]:
|
||||
return [GuardrailEventHooks.pre_call, GuardrailEventHooks.post_call]
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
api_key: str | None = None,
|
||||
|
|
@ -69,6 +73,7 @@ class RubrikLogger(CustomGuardrail, CustomBatchLogger):
|
|||
kwargs["event_hook"] = kwargs.get("event_hook") or GuardrailEventHooks.post_call
|
||||
if kwargs.get("default_on") is None:
|
||||
kwargs["default_on"] = True
|
||||
kwargs.setdefault("supported_event_hooks", list(self.get_supported_event_hooks()))
|
||||
super().__init__(
|
||||
flush_lock=self.flush_lock,
|
||||
**kwargs,
|
||||
|
|
|
|||
|
|
@ -1296,21 +1296,27 @@ async def get_guardrail_ui_settings():
|
|||
get_available_content_categories,
|
||||
get_pattern_metadata,
|
||||
)
|
||||
from litellm.proxy.guardrails.guardrail_registry import guardrail_class_registry
|
||||
|
||||
# Convert the PII_ENTITY_CATEGORIES_MAP to the format expected by the UI
|
||||
category_maps = []
|
||||
for category, entities in PII_ENTITY_CATEGORIES_MAP.items():
|
||||
category_maps.append(
|
||||
{
|
||||
"category": category.value,
|
||||
"entities": [entity.value for entity in entities],
|
||||
}
|
||||
)
|
||||
category_maps = [
|
||||
{
|
||||
"category": category.value,
|
||||
"entities": [entity.value for entity in entities],
|
||||
}
|
||||
for category, entities in PII_ENTITY_CATEGORIES_MAP.items()
|
||||
]
|
||||
|
||||
supported_modes_by_provider = {
|
||||
provider: [hook.value for hook in hooks]
|
||||
for provider, guardrail_class in guardrail_class_registry.items()
|
||||
if (hooks := guardrail_class.get_supported_event_hooks()) is not None
|
||||
}
|
||||
|
||||
return GuardrailUIAddGuardrailSettings(
|
||||
supported_entities=[entity.value for entity in PiiEntityType],
|
||||
supported_actions=[action.value for action in PiiAction],
|
||||
supported_modes=[mode.value for mode in GuardrailEventHooks],
|
||||
supported_modes_by_provider=supported_modes_by_provider,
|
||||
pii_entity_categories=category_maps,
|
||||
content_filter_settings={
|
||||
"prebuilt_patterns": get_pattern_metadata(),
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
import asyncio
|
||||
import json
|
||||
import os
|
||||
from typing import TYPE_CHECKING, Any, AsyncGenerator, Optional, Type, Union
|
||||
from typing import TYPE_CHECKING, Any, AsyncGenerator, List, Optional, Type, Union
|
||||
|
||||
from pydantic import BaseModel
|
||||
from websockets.asyncio.client import ClientConnection, connect
|
||||
|
|
@ -26,6 +26,7 @@ from litellm.proxy.guardrails._content_utils import (
|
|||
build_inspection_messages,
|
||||
has_non_string_content,
|
||||
)
|
||||
from litellm.types.guardrails import GuardrailEventHooks
|
||||
from litellm.types.utils import (
|
||||
CallTypesLiteral,
|
||||
Choices,
|
||||
|
|
@ -44,7 +45,16 @@ class AimGuardrailMissingSecrets(Exception):
|
|||
|
||||
|
||||
class AimGuardrail(CustomGuardrail):
|
||||
@classmethod
|
||||
def get_supported_event_hooks(cls) -> List[GuardrailEventHooks]:
|
||||
return [
|
||||
GuardrailEventHooks.pre_call,
|
||||
GuardrailEventHooks.during_call,
|
||||
GuardrailEventHooks.post_call,
|
||||
]
|
||||
|
||||
def __init__(self, api_key: Optional[str] = None, api_base: Optional[str] = None, **kwargs):
|
||||
kwargs.setdefault("supported_event_hooks", list(self.get_supported_event_hooks()))
|
||||
ssl_verify = kwargs.pop("ssl_verify", None)
|
||||
self.async_handler = get_async_httpx_client(
|
||||
llm_provider=httpxSpecialProvider.GuardrailCallback,
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import asyncio
|
|||
import json
|
||||
import os
|
||||
from datetime import datetime
|
||||
from typing import TYPE_CHECKING, Any, Dict, Literal, Optional, Tuple, Type
|
||||
from typing import TYPE_CHECKING, Any, Dict, List, Literal, Optional, Tuple, Type
|
||||
|
||||
from fastapi import HTTPException
|
||||
|
||||
|
|
@ -52,6 +52,13 @@ class AktoGuardrail(CustomGuardrail):
|
|||
|
||||
return AktoConfigModel
|
||||
|
||||
@classmethod
|
||||
def get_supported_event_hooks(cls) -> List[GuardrailEventHooks]:
|
||||
return [
|
||||
GuardrailEventHooks.pre_call,
|
||||
GuardrailEventHooks.post_call,
|
||||
]
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
akto_base_url: Optional[str] = None,
|
||||
|
|
@ -90,10 +97,7 @@ class AktoGuardrail(CustomGuardrail):
|
|||
self.akto_account_id = akto_account_id or os.environ.get("AKTO_ACCOUNT_ID", "1000000")
|
||||
self.akto_vxlan_id = akto_vxlan_id or os.environ.get("AKTO_VXLAN_ID", "0")
|
||||
|
||||
kwargs["supported_event_hooks"] = [
|
||||
GuardrailEventHooks.pre_call,
|
||||
GuardrailEventHooks.post_call,
|
||||
]
|
||||
kwargs["supported_event_hooks"] = list(self.get_supported_event_hooks())
|
||||
super().__init__(**kwargs)
|
||||
|
||||
verbose_proxy_logger.debug(
|
||||
|
|
|
|||
|
|
@ -37,7 +37,15 @@ if TYPE_CHECKING:
|
|||
|
||||
|
||||
class AporiaGuardrail(CustomGuardrail):
|
||||
@classmethod
|
||||
def get_supported_event_hooks(cls) -> List[GuardrailEventHooks]:
|
||||
return [
|
||||
GuardrailEventHooks.during_call,
|
||||
GuardrailEventHooks.post_call,
|
||||
]
|
||||
|
||||
def __init__(self, api_key: Optional[str] = None, api_base: Optional[str] = None, **kwargs):
|
||||
kwargs.setdefault("supported_event_hooks", list(self.get_supported_event_hooks()))
|
||||
self.async_handler = get_async_httpx_client(llm_provider=httpxSpecialProvider.GuardrailCallback)
|
||||
self.aporia_api_key = api_key or os.environ["APORIO_API_KEY"]
|
||||
self.aporia_api_base = api_base or os.environ["APORIO_API_BASE"]
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ from litellm.integrations.custom_guardrail import (
|
|||
CustomGuardrail,
|
||||
log_guardrail_information,
|
||||
)
|
||||
from litellm.types.guardrails import GuardrailEventHooks
|
||||
from litellm.types.utils import CallTypesLiteral
|
||||
|
||||
from .base import AzureGuardrailBase
|
||||
|
|
@ -47,19 +48,13 @@ class AzureContentSafetyPromptShieldGuardrail(AzureGuardrailBase, CustomGuardrai
|
|||
**kwargs,
|
||||
):
|
||||
"""Initialize Azure Prompt Shield guardrail handler."""
|
||||
from litellm.types.guardrails import GuardrailEventHooks
|
||||
|
||||
supported_event_hooks = [
|
||||
GuardrailEventHooks.pre_call,
|
||||
GuardrailEventHooks.during_call,
|
||||
]
|
||||
# AzureGuardrailBase.__init__ stores api_key, api_base, api_version,
|
||||
# async_handler and forwards the rest to CustomGuardrail.
|
||||
super().__init__(
|
||||
api_key=api_key,
|
||||
api_base=api_base,
|
||||
guardrail_name=guardrail_name,
|
||||
supported_event_hooks=supported_event_hooks,
|
||||
supported_event_hooks=list(self.get_supported_event_hooks()),
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
|
|
@ -149,3 +144,10 @@ class AzureContentSafetyPromptShieldGuardrail(AzureGuardrailBase, CustomGuardrai
|
|||
)
|
||||
|
||||
return AzurePromptShieldGuardrailConfigModel
|
||||
|
||||
@classmethod
|
||||
def get_supported_event_hooks(cls) -> List[GuardrailEventHooks]:
|
||||
return [
|
||||
GuardrailEventHooks.pre_call,
|
||||
GuardrailEventHooks.during_call,
|
||||
]
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ from litellm.integrations.custom_guardrail import (
|
|||
log_guardrail_information,
|
||||
)
|
||||
from litellm.proxy._types import UserAPIKeyAuth
|
||||
from litellm.types.guardrails import GuardrailEventHooks
|
||||
from litellm.types.utils import CallTypesLiteral
|
||||
|
||||
from .base import AzureGuardrailBase
|
||||
|
|
@ -42,6 +43,13 @@ class AzureContentSafetyTextModerationGuardrail(AzureGuardrailBase, CustomGuardr
|
|||
|
||||
default_severity_threshold: int = 2
|
||||
|
||||
@classmethod
|
||||
def get_supported_event_hooks(cls) -> List[GuardrailEventHooks]:
|
||||
return [
|
||||
GuardrailEventHooks.pre_call,
|
||||
GuardrailEventHooks.post_call,
|
||||
]
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
guardrail_name: str,
|
||||
|
|
@ -56,6 +64,7 @@ class AzureContentSafetyTextModerationGuardrail(AzureGuardrailBase, CustomGuardr
|
|||
AzureTextModerationRequestBodyOptionalParams,
|
||||
)
|
||||
|
||||
kwargs.setdefault("supported_event_hooks", list(self.get_supported_event_hooks()))
|
||||
# AzureGuardrailBase.__init__ stores api_key, api_base, api_version,
|
||||
# async_handler and forwards the rest to CustomGuardrail.
|
||||
super().__init__(
|
||||
|
|
|
|||
|
|
@ -166,14 +166,7 @@ class BedrockGuardrail(CustomGuardrail, BaseAWSLLM):
|
|||
"""
|
||||
|
||||
# Set supported event hooks to include MCP hooks
|
||||
if "supported_event_hooks" not in kwargs:
|
||||
kwargs["supported_event_hooks"] = [
|
||||
GuardrailEventHooks.pre_call,
|
||||
GuardrailEventHooks.post_call,
|
||||
GuardrailEventHooks.during_call,
|
||||
GuardrailEventHooks.pre_mcp_call,
|
||||
GuardrailEventHooks.during_mcp_call,
|
||||
]
|
||||
kwargs.setdefault("supported_event_hooks", list(self.get_supported_event_hooks()))
|
||||
|
||||
super().__init__(**kwargs)
|
||||
BaseAWSLLM.__init__(self)
|
||||
|
|
@ -184,6 +177,16 @@ class BedrockGuardrail(CustomGuardrail, BaseAWSLLM):
|
|||
self.guardrailVersion,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def get_supported_event_hooks(cls) -> List[GuardrailEventHooks]:
|
||||
return [
|
||||
GuardrailEventHooks.pre_call,
|
||||
GuardrailEventHooks.post_call,
|
||||
GuardrailEventHooks.during_call,
|
||||
GuardrailEventHooks.pre_mcp_call,
|
||||
GuardrailEventHooks.during_mcp_call,
|
||||
]
|
||||
|
||||
def _create_bedrock_input_content_request(self, messages: Optional[List[AllMessageValues]]) -> BedrockRequest:
|
||||
"""
|
||||
Create a bedrock request for the input content - the LLM request.
|
||||
|
|
|
|||
|
|
@ -351,11 +351,7 @@ class BlockCodeExecutionGuardrail(CustomGuardrail):
|
|||
_event_hook = GuardrailEventHooks(event_hook)
|
||||
super().__init__(
|
||||
guardrail_name=guardrail_name or "block_code_execution",
|
||||
supported_event_hooks=[
|
||||
GuardrailEventHooks.pre_call,
|
||||
GuardrailEventHooks.post_call,
|
||||
GuardrailEventHooks.during_call,
|
||||
],
|
||||
supported_event_hooks=list(self.get_supported_event_hooks()),
|
||||
event_hook=_event_hook
|
||||
or [
|
||||
GuardrailEventHooks.pre_call,
|
||||
|
|
@ -378,6 +374,14 @@ class BlockCodeExecutionGuardrail(CustomGuardrail):
|
|||
|
||||
return BlockCodeExecutionGuardrailConfigModel
|
||||
|
||||
@classmethod
|
||||
def get_supported_event_hooks(cls) -> List[GuardrailEventHooks]:
|
||||
return [
|
||||
GuardrailEventHooks.pre_call,
|
||||
GuardrailEventHooks.post_call,
|
||||
GuardrailEventHooks.during_call,
|
||||
]
|
||||
|
||||
def _find_blocks(self, text: str) -> List[Tuple[int, int, str, str, float, CodeBlockActionTaken]]:
|
||||
"""
|
||||
Find all fenced code blocks in text. Returns list of
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import contextlib
|
|||
import json
|
||||
import os
|
||||
import ssl
|
||||
from typing import TYPE_CHECKING, Any, AsyncGenerator, Optional, Type, Union
|
||||
from typing import TYPE_CHECKING, Any, AsyncGenerator, List, Optional, Type, Union
|
||||
|
||||
from fastapi import HTTPException
|
||||
from pydantic import BaseModel
|
||||
|
|
@ -30,6 +30,7 @@ from litellm.proxy.guardrails._content_utils import (
|
|||
apply_redacted_messages_back,
|
||||
build_inspection_messages,
|
||||
)
|
||||
from litellm.types.guardrails import GuardrailEventHooks
|
||||
from litellm.types.utils import (
|
||||
CallTypesLiteral,
|
||||
Choices,
|
||||
|
|
@ -49,7 +50,16 @@ class CatoNetworksGuardrailMissingSecrets(Exception):
|
|||
|
||||
|
||||
class CatoNetworksGuardrail(CustomGuardrail):
|
||||
@classmethod
|
||||
def get_supported_event_hooks(cls) -> List[GuardrailEventHooks]:
|
||||
return [
|
||||
GuardrailEventHooks.pre_call,
|
||||
GuardrailEventHooks.during_call,
|
||||
GuardrailEventHooks.post_call,
|
||||
]
|
||||
|
||||
def __init__(self, api_key: Optional[str] = None, api_base: Optional[str] = None, **kwargs):
|
||||
kwargs.setdefault("supported_event_hooks", list(self.get_supported_event_hooks()))
|
||||
ssl_verify = kwargs.pop("ssl_verify", None)
|
||||
self.async_handler = get_async_httpx_client(
|
||||
llm_provider=httpxSpecialProvider.GuardrailCallback,
|
||||
|
|
|
|||
|
|
@ -224,18 +224,9 @@ class CiscoAIDefenseGuardrail(_CiscoAIDefenseMcpMixin, CustomGuardrail):
|
|||
self.async_handler = get_async_httpx_client(llm_provider=httpxSpecialProvider.GuardrailCallback)
|
||||
|
||||
# Register broadly; runtime filtering happens in ``_surface_matches``.
|
||||
supported_event_hooks = [
|
||||
GuardrailEventHooks.pre_call,
|
||||
GuardrailEventHooks.during_call,
|
||||
GuardrailEventHooks.post_call,
|
||||
GuardrailEventHooks.logging_only,
|
||||
GuardrailEventHooks.pre_mcp_call,
|
||||
GuardrailEventHooks.during_mcp_call,
|
||||
]
|
||||
|
||||
super().__init__(
|
||||
guardrail_name=guardrail_name,
|
||||
supported_event_hooks=supported_event_hooks,
|
||||
supported_event_hooks=list(self.get_supported_event_hooks()),
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
|
|
@ -2133,3 +2124,14 @@ class CiscoAIDefenseGuardrail(_CiscoAIDefenseMcpMixin, CustomGuardrail):
|
|||
)
|
||||
|
||||
return CiscoAIDefenseGuardrailConfigModel
|
||||
|
||||
@classmethod
|
||||
def get_supported_event_hooks(cls) -> List[GuardrailEventHooks]:
|
||||
return [
|
||||
GuardrailEventHooks.pre_call,
|
||||
GuardrailEventHooks.during_call,
|
||||
GuardrailEventHooks.post_call,
|
||||
GuardrailEventHooks.logging_only,
|
||||
GuardrailEventHooks.pre_mcp_call,
|
||||
GuardrailEventHooks.during_mcp_call,
|
||||
]
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ from collections.abc import Mapping, Sequence
|
|||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Annotated,
|
||||
List,
|
||||
Literal,
|
||||
NamedTuple,
|
||||
Optional,
|
||||
|
|
@ -31,6 +32,7 @@ from litellm.llms.custom_httpx.http_handler import (
|
|||
from litellm.proxy.common_utils.callback_utils import (
|
||||
add_guardrail_to_applied_guardrails_header,
|
||||
)
|
||||
from litellm.types.guardrails import GuardrailEventHooks
|
||||
from litellm.types.llms.openai import AllMessageValues, OpenAIChatCompletionToolParam
|
||||
from litellm.types.utils import GenericGuardrailAPIInputs
|
||||
|
||||
|
|
@ -236,6 +238,13 @@ class CrowdStrikeAIDRHandler(CustomGuardrail):
|
|||
AI Guard service.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def get_supported_event_hooks(cls) -> List[GuardrailEventHooks]:
|
||||
return [
|
||||
GuardrailEventHooks.pre_call,
|
||||
GuardrailEventHooks.post_call,
|
||||
]
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
guardrail_name: str,
|
||||
|
|
@ -266,6 +275,7 @@ class CrowdStrikeAIDRHandler(CustomGuardrail):
|
|||
"CrowdStrike AIDR API base URL is required. Set CS_AIDR_BASE_URL environment variable or pass it in litellm_params."
|
||||
)
|
||||
|
||||
kwargs.setdefault("supported_event_hooks", list(self.get_supported_event_hooks()))
|
||||
# Pass relevant kwargs to the parent class
|
||||
super().__init__(guardrail_name=guardrail_name, **kwargs)
|
||||
verbose_proxy_logger.debug(
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ Example: block when response rejects the user (input_type response only):
|
|||
|
||||
import asyncio
|
||||
import threading
|
||||
from typing import TYPE_CHECKING, Any, Dict, Literal, Optional, Type, cast
|
||||
from typing import TYPE_CHECKING, Any, Dict, List, Literal, Optional, Type, cast
|
||||
|
||||
from fastapi import HTTPException
|
||||
|
||||
|
|
@ -121,18 +121,9 @@ class CustomCodeGuardrail(CustomGuardrail):
|
|||
self._compile_lock = threading.Lock()
|
||||
self._compile_error: Optional[str] = None
|
||||
|
||||
supported_event_hooks = [
|
||||
GuardrailEventHooks.pre_call,
|
||||
GuardrailEventHooks.during_call,
|
||||
GuardrailEventHooks.post_call,
|
||||
GuardrailEventHooks.pre_mcp_call,
|
||||
GuardrailEventHooks.during_mcp_call,
|
||||
GuardrailEventHooks.logging_only,
|
||||
]
|
||||
|
||||
super().__init__(
|
||||
guardrail_name=guardrail_name,
|
||||
supported_event_hooks=supported_event_hooks,
|
||||
supported_event_hooks=list(self.get_supported_event_hooks()),
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
|
|
@ -144,6 +135,17 @@ class CustomCodeGuardrail(CustomGuardrail):
|
|||
"""Returns the config model for the UI."""
|
||||
return CustomCodeGuardrailConfigModel
|
||||
|
||||
@classmethod
|
||||
def get_supported_event_hooks(cls) -> List[GuardrailEventHooks]:
|
||||
return [
|
||||
GuardrailEventHooks.pre_call,
|
||||
GuardrailEventHooks.during_call,
|
||||
GuardrailEventHooks.post_call,
|
||||
GuardrailEventHooks.pre_mcp_call,
|
||||
GuardrailEventHooks.during_mcp_call,
|
||||
GuardrailEventHooks.logging_only,
|
||||
]
|
||||
|
||||
def _do_compile(self) -> None:
|
||||
"""Internal compilation method without lock. Expected to run inside _compile_lock."""
|
||||
exec_globals = build_sandbox_globals()
|
||||
|
|
|
|||
|
|
@ -73,12 +73,7 @@ class DynamoAIGuardrails(CustomGuardrail):
|
|||
self.optional_params = kwargs
|
||||
|
||||
# Set supported event hooks
|
||||
if "supported_event_hooks" not in kwargs:
|
||||
kwargs["supported_event_hooks"] = [
|
||||
GuardrailEventHooks.pre_call,
|
||||
GuardrailEventHooks.post_call,
|
||||
GuardrailEventHooks.during_call,
|
||||
]
|
||||
kwargs.setdefault("supported_event_hooks", list(self.get_supported_event_hooks()))
|
||||
|
||||
super().__init__(guardrail_name=guardrail_name, **kwargs)
|
||||
|
||||
|
|
@ -470,3 +465,11 @@ class DynamoAIGuardrails(CustomGuardrail):
|
|||
)
|
||||
|
||||
return DynamoAIGuardrailConfigModel
|
||||
|
||||
@classmethod
|
||||
def get_supported_event_hooks(cls) -> List[GuardrailEventHooks]:
|
||||
return [
|
||||
GuardrailEventHooks.pre_call,
|
||||
GuardrailEventHooks.post_call,
|
||||
GuardrailEventHooks.during_call,
|
||||
]
|
||||
|
|
|
|||
|
|
@ -80,12 +80,7 @@ class EnkryptAIGuardrails(CustomGuardrail):
|
|||
self.optional_params = kwargs
|
||||
|
||||
# Set supported event hooks
|
||||
if "supported_event_hooks" not in kwargs:
|
||||
kwargs["supported_event_hooks"] = [
|
||||
GuardrailEventHooks.pre_call,
|
||||
GuardrailEventHooks.post_call,
|
||||
GuardrailEventHooks.during_call,
|
||||
]
|
||||
kwargs.setdefault("supported_event_hooks", list(self.get_supported_event_hooks()))
|
||||
|
||||
super().__init__(guardrail_name=guardrail_name, **kwargs)
|
||||
|
||||
|
|
@ -500,3 +495,11 @@ class EnkryptAIGuardrails(CustomGuardrail):
|
|||
)
|
||||
|
||||
return EnkryptAIGuardrailConfigModel
|
||||
|
||||
@classmethod
|
||||
def get_supported_event_hooks(cls) -> List[GuardrailEventHooks]:
|
||||
return [
|
||||
GuardrailEventHooks.pre_call,
|
||||
GuardrailEventHooks.post_call,
|
||||
GuardrailEventHooks.during_call,
|
||||
]
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
import fnmatch
|
||||
import os
|
||||
from typing import TYPE_CHECKING, Any, Dict, Literal, Optional, Set
|
||||
from typing import TYPE_CHECKING, Any, Dict, List, Literal, Optional, Set
|
||||
|
||||
import httpx
|
||||
|
||||
|
|
@ -222,12 +222,7 @@ class GenericGuardrailAPI(CustomGuardrail):
|
|||
self.streaming_sampling_rate: int = 5 if streaming_sampling_rate is None else streaming_sampling_rate
|
||||
|
||||
# Set supported event hooks
|
||||
if "supported_event_hooks" not in kwargs:
|
||||
kwargs["supported_event_hooks"] = [
|
||||
GuardrailEventHooks.pre_call,
|
||||
GuardrailEventHooks.post_call,
|
||||
GuardrailEventHooks.during_call,
|
||||
]
|
||||
kwargs.setdefault("supported_event_hooks", list(self.get_supported_event_hooks()))
|
||||
|
||||
super().__init__(**kwargs)
|
||||
|
||||
|
|
@ -490,3 +485,11 @@ class GenericGuardrailAPI(CustomGuardrail):
|
|||
)
|
||||
|
||||
return GenericGuardrailAPIConfigModel
|
||||
|
||||
@classmethod
|
||||
def get_supported_event_hooks(cls) -> List[GuardrailEventHooks]:
|
||||
return [
|
||||
GuardrailEventHooks.pre_call,
|
||||
GuardrailEventHooks.post_call,
|
||||
GuardrailEventHooks.during_call,
|
||||
]
|
||||
|
|
|
|||
|
|
@ -119,18 +119,20 @@ class GraySwanGuardrail(CustomGuardrail):
|
|||
streaming_sampling_rate,
|
||||
)
|
||||
|
||||
supported_event_hooks = [
|
||||
super().__init__(
|
||||
guardrail_name=guardrail_name,
|
||||
supported_event_hooks=list(self.get_supported_event_hooks()),
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def get_supported_event_hooks(cls) -> List[GuardrailEventHooks]:
|
||||
return [
|
||||
GuardrailEventHooks.pre_call,
|
||||
GuardrailEventHooks.during_call,
|
||||
GuardrailEventHooks.post_call,
|
||||
]
|
||||
|
||||
super().__init__(
|
||||
guardrail_name=guardrail_name,
|
||||
supported_event_hooks=supported_event_hooks,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Debug override to trace post_call issues
|
||||
# ------------------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -74,12 +74,7 @@ class GuardrailsAI(CustomGuardrail):
|
|||
self.guardrails_ai_guard_name = guard_name
|
||||
self.optional_params = kwargs
|
||||
self.guardrails_ai_api_input_format = guardrails_ai_api_input_format
|
||||
supported_event_hooks = [
|
||||
GuardrailEventHooks.post_call,
|
||||
GuardrailEventHooks.pre_call,
|
||||
GuardrailEventHooks.logging_only,
|
||||
]
|
||||
super().__init__(supported_event_hooks=supported_event_hooks, **kwargs)
|
||||
super().__init__(supported_event_hooks=list(self.get_supported_event_hooks()), **kwargs)
|
||||
|
||||
async def make_guardrails_ai_api_request(self, llm_output: str, request_data: dict) -> GuardrailsAIResponse:
|
||||
from httpx import URL
|
||||
|
|
@ -240,3 +235,11 @@ class GuardrailsAI(CustomGuardrail):
|
|||
)
|
||||
|
||||
return GuardrailsAIGuardrailConfigModel
|
||||
|
||||
@classmethod
|
||||
def get_supported_event_hooks(cls) -> List[GuardrailEventHooks]:
|
||||
return [
|
||||
GuardrailEventHooks.post_call,
|
||||
GuardrailEventHooks.pre_call,
|
||||
GuardrailEventHooks.logging_only,
|
||||
]
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import json
|
|||
import re
|
||||
import time
|
||||
import uuid
|
||||
from typing import TYPE_CHECKING, Any, Literal, Optional
|
||||
from typing import TYPE_CHECKING, Any, List, Literal, Optional
|
||||
|
||||
import httpx
|
||||
from fastapi import HTTPException
|
||||
|
|
@ -208,6 +208,13 @@ def _build_responses_followup_items(
|
|||
|
||||
|
||||
class HeadroomGuardrail(CustomGuardrail):
|
||||
@classmethod
|
||||
def get_supported_event_hooks(cls) -> List[GuardrailEventHooks]:
|
||||
return [
|
||||
GuardrailEventHooks.pre_call,
|
||||
GuardrailEventHooks.post_call,
|
||||
]
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
api_base: str | None = None,
|
||||
|
|
@ -237,6 +244,7 @@ class HeadroomGuardrail(CustomGuardrail):
|
|||
guardrail_name=guardrail_name,
|
||||
event_hook=event_hook,
|
||||
default_on=default_on,
|
||||
supported_event_hooks=list(self.get_supported_event_hooks()),
|
||||
)
|
||||
|
||||
def _should_bypass(self, request_data: dict) -> bool:
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ from uuid import uuid4
|
|||
import httpx
|
||||
|
||||
import os
|
||||
from typing import TYPE_CHECKING, Any, Literal, Optional, Type
|
||||
from typing import TYPE_CHECKING, Any, List, Literal, Optional, Type
|
||||
from urllib.parse import urlparse
|
||||
|
||||
import requests
|
||||
|
|
@ -21,6 +21,7 @@ from litellm.llms.custom_httpx.http_handler import (
|
|||
get_async_httpx_client,
|
||||
httpxSpecialProvider,
|
||||
)
|
||||
from litellm.types.guardrails import GuardrailEventHooks
|
||||
from litellm.types.proxy.guardrails.guardrail_hooks.hiddenlayer import (
|
||||
HiddenlayerAction,
|
||||
HiddenlayerMessages,
|
||||
|
|
@ -63,6 +64,13 @@ def _get_jwt(auth_url, api_id, api_key):
|
|||
class HiddenlayerGuardrail(CustomGuardrail):
|
||||
"""Custom guardrail wrapper for HiddenLayer's safety checks."""
|
||||
|
||||
@classmethod
|
||||
def get_supported_event_hooks(cls) -> List[GuardrailEventHooks]:
|
||||
return [
|
||||
GuardrailEventHooks.pre_call,
|
||||
GuardrailEventHooks.post_call,
|
||||
]
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
api_id: Optional[str] = None,
|
||||
|
|
@ -71,6 +79,7 @@ class HiddenlayerGuardrail(CustomGuardrail):
|
|||
auth_url: Optional[str] = None,
|
||||
**kwargs: Any,
|
||||
) -> None:
|
||||
kwargs.setdefault("supported_event_hooks", list(self.get_supported_event_hooks()))
|
||||
self.hiddenlayer_client_id = api_id or os.getenv("HIDDENLAYER_CLIENT_ID")
|
||||
self.hiddenlayer_client_secret = api_key or os.getenv("HIDDENLAYER_CLIENT_SECRET")
|
||||
self.api_base = api_base or os.getenv("HIDDENLAYER_API_BASE") or "https://api.hiddenlayer.ai"
|
||||
|
|
|
|||
|
|
@ -86,12 +86,7 @@ class IBMGuardrailDetector(CustomGuardrail):
|
|||
self.optional_params = kwargs
|
||||
|
||||
# Set supported event hooks
|
||||
if "supported_event_hooks" not in kwargs:
|
||||
kwargs["supported_event_hooks"] = [
|
||||
GuardrailEventHooks.pre_call,
|
||||
GuardrailEventHooks.post_call,
|
||||
GuardrailEventHooks.during_call,
|
||||
]
|
||||
kwargs.setdefault("supported_event_hooks", list(self.get_supported_event_hooks()))
|
||||
|
||||
super().__init__(guardrail_name=guardrail_name, **kwargs)
|
||||
|
||||
|
|
@ -669,3 +664,11 @@ class IBMGuardrailDetector(CustomGuardrail):
|
|||
)
|
||||
|
||||
return IBMDetectorGuardrailConfigModel
|
||||
|
||||
@classmethod
|
||||
def get_supported_event_hooks(cls) -> List[GuardrailEventHooks]:
|
||||
return [
|
||||
GuardrailEventHooks.pre_call,
|
||||
GuardrailEventHooks.post_call,
|
||||
GuardrailEventHooks.during_call,
|
||||
]
|
||||
|
|
|
|||
|
|
@ -25,6 +25,12 @@ if TYPE_CHECKING:
|
|||
|
||||
|
||||
class JavelinGuardrail(CustomGuardrail):
|
||||
@classmethod
|
||||
def get_supported_event_hooks(cls) -> List[GuardrailEventHooks]:
|
||||
return [
|
||||
GuardrailEventHooks.pre_call,
|
||||
]
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
api_key: Optional[str] = None,
|
||||
|
|
@ -72,6 +78,7 @@ class JavelinGuardrail(CustomGuardrail):
|
|||
self.api_version,
|
||||
)
|
||||
|
||||
kwargs.setdefault("supported_event_hooks", list(self.get_supported_event_hooks()))
|
||||
super().__init__(guardrail_name=guardrail_name, default_on=default_on, **kwargs)
|
||||
|
||||
async def call_javelin_guard(
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ from litellm.proxy._types import UserAPIKeyAuth
|
|||
from litellm.proxy.guardrails.guardrail_helpers import should_proceed_based_on_metadata
|
||||
from litellm.secret_managers.main import get_secret
|
||||
from litellm.types.guardrails import (
|
||||
GuardrailEventHooks,
|
||||
GuardrailItem,
|
||||
LakeraCategoryThresholds,
|
||||
Role,
|
||||
|
|
@ -46,6 +47,13 @@ INPUT_POSITIONING_MAP = {
|
|||
|
||||
|
||||
class lakeraAI_Moderation(CustomGuardrail):
|
||||
@classmethod
|
||||
def get_supported_event_hooks(cls) -> List[GuardrailEventHooks]:
|
||||
return [
|
||||
GuardrailEventHooks.pre_call,
|
||||
GuardrailEventHooks.during_call,
|
||||
]
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
moderation_check: Literal["pre_call", "in_parallel"] = "in_parallel",
|
||||
|
|
@ -54,6 +62,7 @@ class lakeraAI_Moderation(CustomGuardrail):
|
|||
api_key: Optional[str] = None,
|
||||
**kwargs,
|
||||
):
|
||||
kwargs.setdefault("supported_event_hooks", list(self.get_supported_event_hooks()))
|
||||
self.async_handler = get_async_httpx_client(llm_provider=httpxSpecialProvider.GuardrailCallback)
|
||||
self.lakera_api_key = api_key or os.environ.get("LAKERA_API_KEY") or ""
|
||||
self.moderation_check = moderation_check
|
||||
|
|
|
|||
|
|
@ -29,6 +29,14 @@ from litellm.types.utils import CallTypesLiteral, GuardrailStatus, ModelResponse
|
|||
|
||||
|
||||
class LakeraAIGuardrail(CustomGuardrail):
|
||||
@classmethod
|
||||
def get_supported_event_hooks(cls) -> List[GuardrailEventHooks]:
|
||||
return [
|
||||
GuardrailEventHooks.pre_call,
|
||||
GuardrailEventHooks.during_call,
|
||||
GuardrailEventHooks.post_call,
|
||||
]
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
api_key: Optional[str] = None,
|
||||
|
|
@ -68,6 +76,7 @@ class LakeraAIGuardrail(CustomGuardrail):
|
|||
self.metadata: Optional[Dict] = metadata
|
||||
self.dev_info: Optional[bool] = dev_info
|
||||
self.on_flagged = on_flagged or "block"
|
||||
kwargs.setdefault("supported_event_hooks", list(self.get_supported_event_hooks()))
|
||||
super().__init__(**kwargs)
|
||||
|
||||
async def call_v2_guard(
|
||||
|
|
|
|||
|
|
@ -93,6 +93,14 @@ class LassoGuardrail(CustomGuardrail):
|
|||
through the Lasso Security API.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def get_supported_event_hooks(cls) -> List[GuardrailEventHooks]:
|
||||
return [
|
||||
GuardrailEventHooks.pre_call,
|
||||
GuardrailEventHooks.during_call,
|
||||
GuardrailEventHooks.post_call,
|
||||
]
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
lasso_api_key: Optional[str] = None,
|
||||
|
|
@ -103,6 +111,7 @@ class LassoGuardrail(CustomGuardrail):
|
|||
mask: Optional[bool] = False,
|
||||
**kwargs,
|
||||
):
|
||||
kwargs.setdefault("supported_event_hooks", list(self.get_supported_event_hooks()))
|
||||
self.async_handler = get_async_httpx_client(llm_provider=httpxSpecialProvider.GuardrailCallback)
|
||||
self.lasso_api_key = lasso_api_key or api_key or os.environ.get("LASSO_API_KEY")
|
||||
self.user_id = user_id or os.environ.get("LASSO_USER_ID")
|
||||
|
|
|
|||
|
|
@ -179,12 +179,7 @@ class ContentFilterGuardrail(CustomGuardrail):
|
|||
|
||||
super().__init__(
|
||||
guardrail_name=guardrail_name,
|
||||
supported_event_hooks=[
|
||||
GuardrailEventHooks.pre_call,
|
||||
GuardrailEventHooks.post_call,
|
||||
GuardrailEventHooks.during_call,
|
||||
GuardrailEventHooks.realtime_input_transcription,
|
||||
],
|
||||
supported_event_hooks=list(self.get_supported_event_hooks()),
|
||||
event_hook=event_hook or GuardrailEventHooks.pre_call,
|
||||
default_on=default_on,
|
||||
**kwargs,
|
||||
|
|
@ -1900,3 +1895,12 @@ class ContentFilterGuardrail(CustomGuardrail):
|
|||
)
|
||||
|
||||
return LitellmContentFilterGuardrailConfigModel
|
||||
|
||||
@classmethod
|
||||
def get_supported_event_hooks(cls) -> List[GuardrailEventHooks]:
|
||||
return [
|
||||
GuardrailEventHooks.pre_call,
|
||||
GuardrailEventHooks.post_call,
|
||||
GuardrailEventHooks.during_call,
|
||||
GuardrailEventHooks.realtime_input_transcription,
|
||||
]
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ from fastapi import HTTPException
|
|||
|
||||
from litellm._logging import verbose_logger
|
||||
from litellm.integrations.custom_guardrail import CustomGuardrail
|
||||
from litellm.types.guardrails import GuardrailEventHooks
|
||||
from litellm.types.guardrails import GuardrailEventHooks, SupportedGuardrailIntegrations
|
||||
from litellm.types.utils import GenericGuardrailAPIInputs, GuardrailStatus
|
||||
|
||||
if TYPE_CHECKING:
|
||||
|
|
@ -105,7 +105,7 @@ class LLMAsAJudgeGuardrail(CustomGuardrail):
|
|||
|
||||
super().__init__(
|
||||
guardrail_name=guardrail_name,
|
||||
supported_event_hooks=[GuardrailEventHooks.post_call],
|
||||
supported_event_hooks=list(self.get_supported_event_hooks()),
|
||||
event_hook=_event_hook or GuardrailEventHooks.post_call,
|
||||
default_on=default_on,
|
||||
**kwargs,
|
||||
|
|
@ -115,6 +115,10 @@ class LLMAsAJudgeGuardrail(CustomGuardrail):
|
|||
self.overall_threshold = overall_threshold
|
||||
self.on_failure = on_failure
|
||||
|
||||
@classmethod
|
||||
def get_supported_event_hooks(cls) -> List[GuardrailEventHooks]:
|
||||
return [GuardrailEventHooks.post_call]
|
||||
|
||||
async def _run_judge(
|
||||
self,
|
||||
messages: List[Dict[str, Any]],
|
||||
|
|
@ -267,7 +271,13 @@ def initialize_guardrail(
|
|||
return instance
|
||||
|
||||
|
||||
guardrail_class_registry = {
|
||||
SupportedGuardrailIntegrations.LLM_AS_A_JUDGE.value: LLMAsAJudgeGuardrail,
|
||||
}
|
||||
|
||||
|
||||
__all__ = [
|
||||
"LLMAsAJudgeGuardrail",
|
||||
"guardrail_class_registry",
|
||||
"initialize_guardrail",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -40,10 +40,7 @@ class MCPEndUserPermissionGuardrail(CustomGuardrail):
|
|||
"""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
if "supported_event_hooks" not in kwargs:
|
||||
kwargs["supported_event_hooks"] = [
|
||||
GuardrailEventHooks.pre_call,
|
||||
]
|
||||
kwargs.setdefault("supported_event_hooks", list(self.get_supported_event_hooks()))
|
||||
super().__init__(**kwargs)
|
||||
verbose_proxy_logger.debug("MCP End User Permission Guardrail initialized")
|
||||
|
||||
|
|
@ -210,6 +207,12 @@ class MCPEndUserPermissionGuardrail(CustomGuardrail):
|
|||
|
||||
return MCPEndUserPermissionGuardrailConfigModel
|
||||
|
||||
@classmethod
|
||||
def get_supported_event_hooks(cls) -> List[GuardrailEventHooks]:
|
||||
return [
|
||||
GuardrailEventHooks.pre_call,
|
||||
]
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Private — tool name extraction
|
||||
# ------------------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -87,6 +87,7 @@ from litellm.integrations.custom_guardrail import (
|
|||
log_guardrail_information,
|
||||
)
|
||||
from litellm.proxy._types import UserAPIKeyAuth
|
||||
from litellm.types.guardrails import GuardrailEventHooks
|
||||
from litellm.types.utils import CallTypesLiteral
|
||||
|
||||
# Module-level singleton for the JWKS discovery endpoint to access.
|
||||
|
|
@ -211,6 +212,10 @@ class MCPJWTSigner(CustomGuardrail):
|
|||
DEFAULT_AUDIENCE = "mcp"
|
||||
SIGNING_KEY_ENV = "MCP_JWT_SIGNING_KEY"
|
||||
|
||||
@classmethod
|
||||
def get_supported_event_hooks(cls) -> List[GuardrailEventHooks]:
|
||||
return [GuardrailEventHooks.pre_mcp_call]
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
# Core signing config
|
||||
|
|
@ -240,6 +245,7 @@ class MCPJWTSigner(CustomGuardrail):
|
|||
allowed_scopes: Optional[List[str]] = None,
|
||||
**kwargs: Any,
|
||||
) -> None:
|
||||
kwargs.setdefault("supported_event_hooks", list(self.get_supported_event_hooks()))
|
||||
super().__init__(**kwargs)
|
||||
|
||||
# --- Signing key setup ---
|
||||
|
|
|
|||
|
|
@ -27,11 +27,14 @@ class MCPSecurityGuardrail(CustomGuardrail):
|
|||
on_violation: Literal["block", "alert"] = "block",
|
||||
**kwargs,
|
||||
):
|
||||
if "supported_event_hooks" not in kwargs:
|
||||
kwargs["supported_event_hooks"] = [GuardrailEventHooks.pre_call]
|
||||
kwargs.setdefault("supported_event_hooks", list(self.get_supported_event_hooks()))
|
||||
super().__init__(**kwargs)
|
||||
self.on_violation = on_violation
|
||||
|
||||
@classmethod
|
||||
def get_supported_event_hooks(cls) -> List[GuardrailEventHooks]:
|
||||
return [GuardrailEventHooks.pre_call]
|
||||
|
||||
@log_guardrail_information
|
||||
async def async_pre_call_hook(
|
||||
self,
|
||||
|
|
|
|||
|
|
@ -75,12 +75,6 @@ class MicrosoftPurviewDLPGuardrail(PurviewGuardrailBase, CustomGuardrail):
|
|||
user_id_field: str = "user_id",
|
||||
**kwargs: Any,
|
||||
):
|
||||
supported_event_hooks = [
|
||||
GuardrailEventHooks.pre_call,
|
||||
GuardrailEventHooks.post_call,
|
||||
GuardrailEventHooks.logging_only,
|
||||
]
|
||||
|
||||
super().__init__(
|
||||
tenant_id=tenant_id,
|
||||
client_id=client_id,
|
||||
|
|
@ -88,7 +82,7 @@ class MicrosoftPurviewDLPGuardrail(PurviewGuardrailBase, CustomGuardrail):
|
|||
purview_app_name=purview_app_name,
|
||||
user_id_field=user_id_field,
|
||||
guardrail_name=guardrail_name,
|
||||
supported_event_hooks=supported_event_hooks,
|
||||
supported_event_hooks=list(self.get_supported_event_hooks()),
|
||||
**kwargs,
|
||||
)
|
||||
self.guardrail_provider = "microsoft_purview"
|
||||
|
|
@ -101,6 +95,14 @@ class MicrosoftPurviewDLPGuardrail(PurviewGuardrailBase, CustomGuardrail):
|
|||
def get_config_model() -> Optional[Type["GuardrailConfigModel"]]:
|
||||
return None # Config model can be added later for UI support
|
||||
|
||||
@classmethod
|
||||
def get_supported_event_hooks(cls) -> List[GuardrailEventHooks]:
|
||||
return [
|
||||
GuardrailEventHooks.pre_call,
|
||||
GuardrailEventHooks.post_call,
|
||||
GuardrailEventHooks.logging_only,
|
||||
]
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Core DLP check
|
||||
# ------------------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -60,6 +60,16 @@ class ModelArmorGuardrail(CustomGuardrail, VertexBase):
|
|||
- Post-call sanitization (sanitizeModelResponse)
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def get_supported_event_hooks(cls) -> List[GuardrailEventHooks]:
|
||||
return [
|
||||
GuardrailEventHooks.pre_call,
|
||||
GuardrailEventHooks.during_call,
|
||||
GuardrailEventHooks.post_call,
|
||||
GuardrailEventHooks.pre_mcp_call,
|
||||
GuardrailEventHooks.during_mcp_call,
|
||||
]
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
template_id: Optional[str] = None,
|
||||
|
|
@ -76,6 +86,7 @@ class ModelArmorGuardrail(CustomGuardrail, VertexBase):
|
|||
GuardrailEventHooks.during_call,
|
||||
GuardrailEventHooks.post_call,
|
||||
]
|
||||
kwargs.setdefault("supported_event_hooks", list(self.get_supported_event_hooks()))
|
||||
|
||||
# Initialize parent classes first
|
||||
super().__init__(**kwargs)
|
||||
|
|
|
|||
|
|
@ -104,6 +104,15 @@ class NomaGuardrail(CustomGuardrail):
|
|||
_DEFAULT_API_BASE = "https://api.noma.security/"
|
||||
_AIDR_ENDPOINT = "/ai-dr/v2/prompt/scan"
|
||||
|
||||
@classmethod
|
||||
def get_supported_event_hooks(cls) -> List[GuardrailEventHooks]:
|
||||
return [
|
||||
GuardrailEventHooks.pre_call,
|
||||
GuardrailEventHooks.during_call,
|
||||
GuardrailEventHooks.post_call,
|
||||
GuardrailEventHooks.pre_mcp_call,
|
||||
]
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
api_key: Optional[str] = None,
|
||||
|
|
@ -147,6 +156,7 @@ class NomaGuardrail(CustomGuardrail):
|
|||
else:
|
||||
self.anonymize_input = anonymize_input
|
||||
|
||||
kwargs.setdefault("supported_event_hooks", list(self.get_supported_event_hooks()))
|
||||
super().__init__(**kwargs)
|
||||
|
||||
def _create_background_noma_check(
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import enum
|
|||
import json
|
||||
import os
|
||||
from datetime import datetime
|
||||
from typing import TYPE_CHECKING, Any, Literal, Optional, Type, cast
|
||||
from typing import TYPE_CHECKING, Any, List, Literal, Optional, Type, cast
|
||||
from urllib.parse import urlparse
|
||||
|
||||
from litellm._logging import verbose_proxy_logger
|
||||
|
|
@ -71,14 +71,7 @@ class NomaV2Guardrail(CustomGuardrail):
|
|||
if self._requires_api_key(api_base=self.api_base) and not self.api_key:
|
||||
raise ValueError("Noma v2 guardrail requires api_key when using Noma SaaS endpoint")
|
||||
|
||||
if "supported_event_hooks" not in kwargs:
|
||||
kwargs["supported_event_hooks"] = [
|
||||
GuardrailEventHooks.pre_call,
|
||||
GuardrailEventHooks.during_call,
|
||||
GuardrailEventHooks.post_call,
|
||||
GuardrailEventHooks.pre_mcp_call,
|
||||
GuardrailEventHooks.during_mcp_call,
|
||||
]
|
||||
kwargs.setdefault("supported_event_hooks", list(self.get_supported_event_hooks()))
|
||||
|
||||
super().__init__(**kwargs)
|
||||
|
||||
|
|
@ -90,6 +83,16 @@ class NomaV2Guardrail(CustomGuardrail):
|
|||
|
||||
return NomaV2GuardrailConfigModel
|
||||
|
||||
@classmethod
|
||||
def get_supported_event_hooks(cls) -> List[GuardrailEventHooks]:
|
||||
return [
|
||||
GuardrailEventHooks.pre_call,
|
||||
GuardrailEventHooks.during_call,
|
||||
GuardrailEventHooks.post_call,
|
||||
GuardrailEventHooks.pre_mcp_call,
|
||||
GuardrailEventHooks.during_mcp_call,
|
||||
]
|
||||
|
||||
def _get_authorization_header(self) -> str:
|
||||
if not self.api_key:
|
||||
return ""
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
# +-------------------------------------------------------------+
|
||||
import os
|
||||
import uuid
|
||||
from typing import TYPE_CHECKING, Any, Literal, Optional, Type
|
||||
from typing import TYPE_CHECKING, Any, List, Literal, Optional, Type
|
||||
|
||||
import httpx
|
||||
from fastapi import HTTPException
|
||||
|
|
@ -21,6 +21,7 @@ from litellm.llms.custom_httpx.http_handler import (
|
|||
get_async_httpx_client,
|
||||
httpxSpecialProvider,
|
||||
)
|
||||
from litellm.types.guardrails import GuardrailEventHooks
|
||||
from litellm.types.utils import GenericGuardrailAPIInputs, ModelResponse
|
||||
|
||||
if TYPE_CHECKING:
|
||||
|
|
@ -28,6 +29,14 @@ if TYPE_CHECKING:
|
|||
|
||||
|
||||
class OnyxGuardrail(CustomGuardrail):
|
||||
@classmethod
|
||||
def get_supported_event_hooks(cls) -> List[GuardrailEventHooks]:
|
||||
return [
|
||||
GuardrailEventHooks.pre_call,
|
||||
GuardrailEventHooks.during_call,
|
||||
GuardrailEventHooks.post_call,
|
||||
]
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
api_base: Optional[str] = None,
|
||||
|
|
@ -35,6 +44,7 @@ class OnyxGuardrail(CustomGuardrail):
|
|||
timeout: Optional[float] = 10.0,
|
||||
**kwargs,
|
||||
):
|
||||
kwargs.setdefault("supported_event_hooks", list(self.get_supported_event_hooks()))
|
||||
timeout = timeout or int(os.getenv("ONYX_TIMEOUT", 10.0))
|
||||
self.async_handler = get_async_httpx_client(
|
||||
llm_provider=httpxSpecialProvider.GuardrailCallback,
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ OpenAI Moderation Guardrail Integration for LiteLLM
|
|||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Dict,
|
||||
List,
|
||||
Literal,
|
||||
Optional,
|
||||
Type,
|
||||
|
|
@ -64,17 +65,9 @@ class OpenAIModerationGuardrail(OpenAIGuardrailBase, CustomGuardrail):
|
|||
**kwargs,
|
||||
):
|
||||
"""Initialize OpenAI Moderation guardrail handler."""
|
||||
from litellm.types.guardrails import GuardrailEventHooks
|
||||
|
||||
# Initialize parent CustomGuardrail
|
||||
supported_event_hooks = [
|
||||
GuardrailEventHooks.pre_call,
|
||||
GuardrailEventHooks.during_call,
|
||||
GuardrailEventHooks.post_call,
|
||||
]
|
||||
super().__init__(
|
||||
guardrail_name=guardrail_name,
|
||||
supported_event_hooks=supported_event_hooks,
|
||||
supported_event_hooks=list(self.get_supported_event_hooks()),
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
|
|
@ -353,3 +346,11 @@ class OpenAIModerationGuardrail(OpenAIGuardrailBase, CustomGuardrail):
|
|||
)
|
||||
|
||||
return OpenAIModerationGuardrailConfigModel
|
||||
|
||||
@classmethod
|
||||
def get_supported_event_hooks(cls) -> List[GuardrailEventHooks]:
|
||||
return [
|
||||
GuardrailEventHooks.pre_call,
|
||||
GuardrailEventHooks.during_call,
|
||||
GuardrailEventHooks.post_call,
|
||||
]
|
||||
|
|
|
|||
|
|
@ -66,6 +66,13 @@ class OvalixGuardrail(CustomGuardrail):
|
|||
Monolith backend.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def get_supported_event_hooks(cls) -> List[GuardrailEventHooks]:
|
||||
return [
|
||||
GuardrailEventHooks.pre_call,
|
||||
GuardrailEventHooks.post_call,
|
||||
]
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
tracker_api_base: Optional[str] = None,
|
||||
|
|
|
|||
|
|
@ -89,15 +89,10 @@ class PangeaHandler(CustomGuardrail):
|
|||
self.pangea_input_recipe = pangea_input_recipe
|
||||
self.pangea_output_recipe = pangea_output_recipe
|
||||
|
||||
supported_event_hooks = [
|
||||
GuardrailEventHooks.pre_call,
|
||||
GuardrailEventHooks.post_call,
|
||||
]
|
||||
|
||||
# Pass relevant kwargs to the parent class
|
||||
super().__init__(
|
||||
guardrail_name=guardrail_name,
|
||||
supported_event_hooks=supported_event_hooks,
|
||||
supported_event_hooks=list(self.get_supported_event_hooks()),
|
||||
**kwargs,
|
||||
)
|
||||
verbose_proxy_logger.debug(
|
||||
|
|
@ -317,3 +312,10 @@ class PangeaHandler(CustomGuardrail):
|
|||
)
|
||||
|
||||
return PangeaGuardrailConfigModel
|
||||
|
||||
@classmethod
|
||||
def get_supported_event_hooks(cls) -> List[GuardrailEventHooks]:
|
||||
return [
|
||||
GuardrailEventHooks.pre_call,
|
||||
GuardrailEventHooks.post_call,
|
||||
]
|
||||
|
|
|
|||
|
|
@ -94,14 +94,7 @@ class PanwPrismaAirsHandler(CustomGuardrail):
|
|||
super().__init__(
|
||||
guardrail_name=guardrail_name,
|
||||
default_on=default_on,
|
||||
supported_event_hooks=[
|
||||
GuardrailEventHooks.pre_call,
|
||||
GuardrailEventHooks.during_call,
|
||||
GuardrailEventHooks.post_call,
|
||||
GuardrailEventHooks.logging_only,
|
||||
GuardrailEventHooks.pre_mcp_call,
|
||||
GuardrailEventHooks.during_mcp_call,
|
||||
],
|
||||
supported_event_hooks=list(self.get_supported_event_hooks()),
|
||||
mask_request_content=_mask_request_content,
|
||||
mask_response_content=_mask_response_content,
|
||||
violation_message_template=violation_message_template,
|
||||
|
|
@ -1854,3 +1847,14 @@ class PanwPrismaAirsHandler(CustomGuardrail):
|
|||
)
|
||||
|
||||
return PanwPrismaAirsGuardrailConfigModel
|
||||
|
||||
@classmethod
|
||||
def get_supported_event_hooks(cls) -> List[GuardrailEventHooks]:
|
||||
return [
|
||||
GuardrailEventHooks.pre_call,
|
||||
GuardrailEventHooks.during_call,
|
||||
GuardrailEventHooks.post_call,
|
||||
GuardrailEventHooks.logging_only,
|
||||
GuardrailEventHooks.pre_mcp_call,
|
||||
GuardrailEventHooks.during_mcp_call,
|
||||
]
|
||||
|
|
|
|||
|
|
@ -269,18 +269,9 @@ class PillarGuardrail(CustomGuardrail):
|
|||
)
|
||||
self.timeout = self.DEFAULT_TIMEOUT
|
||||
|
||||
# Define supported event hooks
|
||||
supported_event_hooks = [
|
||||
GuardrailEventHooks.pre_call,
|
||||
GuardrailEventHooks.during_call,
|
||||
GuardrailEventHooks.post_call,
|
||||
GuardrailEventHooks.pre_mcp_call,
|
||||
GuardrailEventHooks.during_mcp_call,
|
||||
]
|
||||
|
||||
super().__init__(
|
||||
guardrail_name=guardrail_name,
|
||||
supported_event_hooks=supported_event_hooks,
|
||||
supported_event_hooks=list(self.get_supported_event_hooks()),
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
|
|
@ -830,3 +821,13 @@ class PillarGuardrail(CustomGuardrail):
|
|||
)
|
||||
|
||||
return PillarGuardrailConfigModel
|
||||
|
||||
@classmethod
|
||||
def get_supported_event_hooks(cls) -> List[GuardrailEventHooks]:
|
||||
return [
|
||||
GuardrailEventHooks.pre_call,
|
||||
GuardrailEventHooks.during_call,
|
||||
GuardrailEventHooks.post_call,
|
||||
GuardrailEventHooks.pre_mcp_call,
|
||||
GuardrailEventHooks.during_mcp_call,
|
||||
]
|
||||
|
|
|
|||
|
|
@ -67,6 +67,16 @@ class _OPTIONAL_PresidioPIIMasking(CustomGuardrail):
|
|||
user_api_key_cache = None
|
||||
ad_hoc_recognizers = None
|
||||
|
||||
@classmethod
|
||||
def get_supported_event_hooks(cls) -> List[GuardrailEventHooks]:
|
||||
return [
|
||||
GuardrailEventHooks.pre_call,
|
||||
GuardrailEventHooks.during_call,
|
||||
GuardrailEventHooks.post_call,
|
||||
GuardrailEventHooks.logging_only,
|
||||
GuardrailEventHooks.pre_mcp_call,
|
||||
]
|
||||
|
||||
# Class variables or attributes
|
||||
def __init__(
|
||||
self,
|
||||
|
|
@ -87,6 +97,7 @@ class _OPTIONAL_PresidioPIIMasking(CustomGuardrail):
|
|||
if logging_only is True:
|
||||
self.logging_only = True
|
||||
kwargs["event_hook"] = GuardrailEventHooks.logging_only
|
||||
kwargs.setdefault("supported_event_hooks", list(self.get_supported_event_hooks()))
|
||||
super().__init__(**kwargs)
|
||||
self.guardrail_provider = "presidio"
|
||||
self.pii_tokens: dict = {} # mapping of PII token to original text - only used with Presidio `replace` operation
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ from litellm.llms.custom_httpx.http_handler import (
|
|||
get_async_httpx_client,
|
||||
httpxSpecialProvider,
|
||||
)
|
||||
from litellm.types.guardrails import GuardrailEventHooks
|
||||
from litellm.types.utils import GenericGuardrailAPIInputs
|
||||
|
||||
if TYPE_CHECKING:
|
||||
|
|
@ -26,6 +27,14 @@ class PromptSecurityGuardrailMissingSecrets(Exception):
|
|||
|
||||
|
||||
class PromptSecurityGuardrail(CustomGuardrail):
|
||||
@classmethod
|
||||
def get_supported_event_hooks(cls) -> List[GuardrailEventHooks]:
|
||||
return [
|
||||
GuardrailEventHooks.pre_call,
|
||||
GuardrailEventHooks.during_call,
|
||||
GuardrailEventHooks.post_call,
|
||||
]
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
api_key: Optional[str] = None,
|
||||
|
|
@ -35,6 +44,7 @@ class PromptSecurityGuardrail(CustomGuardrail):
|
|||
check_tool_results: Optional[bool] = None,
|
||||
**kwargs,
|
||||
):
|
||||
kwargs.setdefault("supported_event_hooks", list(self.get_supported_event_hooks()))
|
||||
self.async_handler = get_async_httpx_client(llm_provider=httpxSpecialProvider.GuardrailCallback)
|
||||
self.api_key = api_key or os.environ.get("PROMPT_SECURITY_API_KEY")
|
||||
self.api_base = api_base or os.environ.get("PROMPT_SECURITY_API_BASE")
|
||||
|
|
|
|||
|
|
@ -81,11 +81,7 @@ class PromptGuardGuardrail(CustomGuardrail):
|
|||
llm_provider=httpxSpecialProvider.GuardrailCallback,
|
||||
)
|
||||
|
||||
if "supported_event_hooks" not in kwargs:
|
||||
kwargs["supported_event_hooks"] = [
|
||||
GuardrailEventHooks.pre_call,
|
||||
GuardrailEventHooks.post_call,
|
||||
]
|
||||
kwargs.setdefault("supported_event_hooks", list(self.get_supported_event_hooks()))
|
||||
|
||||
super().__init__(**kwargs)
|
||||
|
||||
|
|
@ -97,6 +93,13 @@ class PromptGuardGuardrail(CustomGuardrail):
|
|||
|
||||
return PromptGuardConfigModel
|
||||
|
||||
@classmethod
|
||||
def get_supported_event_hooks(cls) -> List[GuardrailEventHooks]:
|
||||
return [
|
||||
GuardrailEventHooks.pre_call,
|
||||
GuardrailEventHooks.post_call,
|
||||
]
|
||||
|
||||
@log_guardrail_information
|
||||
async def apply_guardrail(
|
||||
self,
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ from litellm.llms.custom_httpx.http_handler import (
|
|||
httpxSpecialProvider,
|
||||
)
|
||||
from litellm.secret_managers.main import get_secret_str
|
||||
from litellm.types.guardrails import GuardrailEventHooks
|
||||
from litellm.types.llms.openai import AllMessageValues
|
||||
from litellm.types.proxy.guardrails.guardrail_hooks.base import GuardrailConfigModel
|
||||
from litellm.types.utils import GenericGuardrailAPIInputs
|
||||
|
|
@ -31,6 +32,14 @@ DEFAULT_QUALIFIRE_API_BASE = "https://proxy.qualifire.ai"
|
|||
|
||||
|
||||
class QualifireGuardrail(CustomGuardrail):
|
||||
@classmethod
|
||||
def get_supported_event_hooks(cls) -> List[GuardrailEventHooks]:
|
||||
return [
|
||||
GuardrailEventHooks.pre_call,
|
||||
GuardrailEventHooks.during_call,
|
||||
GuardrailEventHooks.post_call,
|
||||
]
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
api_key: Optional[str] = None,
|
||||
|
|
@ -86,6 +95,7 @@ class QualifireGuardrail(CustomGuardrail):
|
|||
# Initialize async HTTP client for direct API calls
|
||||
self.async_handler = get_async_httpx_client(llm_provider=httpxSpecialProvider.GuardrailCallback)
|
||||
|
||||
kwargs.setdefault("supported_event_hooks", list(self.get_supported_event_hooks()))
|
||||
super().__init__(**kwargs)
|
||||
|
||||
def _has_any_check_enabled(self) -> bool:
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime
|
||||
from typing import AsyncGenerator, Literal
|
||||
from typing import AsyncGenerator, List, Literal
|
||||
|
||||
from pydantic import TypeAdapter, ValidationError
|
||||
from pydantic import BaseModel
|
||||
|
|
@ -64,6 +64,13 @@ def _is_object_list(value: object) -> TypeGuard[list[object]]: # guard-ok: isin
|
|||
|
||||
|
||||
class RepelloAIGuardrail(CustomGuardrail):
|
||||
@classmethod
|
||||
def get_supported_event_hooks(cls) -> List[GuardrailEventHooks]:
|
||||
return [
|
||||
GuardrailEventHooks.pre_call,
|
||||
GuardrailEventHooks.post_call,
|
||||
]
|
||||
|
||||
@staticmethod
|
||||
def _get_field(obj: object, key: str) -> object:
|
||||
if _is_object_dict(obj):
|
||||
|
|
@ -169,6 +176,7 @@ class RepelloAIGuardrail(CustomGuardrail):
|
|||
guardrail_name=guardrail_name,
|
||||
event_hook=event_hook,
|
||||
default_on=default_on,
|
||||
supported_event_hooks=list(self.get_supported_event_hooks()),
|
||||
)
|
||||
|
||||
async def _call_analyze(
|
||||
|
|
|
|||
|
|
@ -58,10 +58,7 @@ class SemanticGuardrail(CustomGuardrail):
|
|||
):
|
||||
super().__init__(
|
||||
guardrail_name=guardrail_name,
|
||||
supported_event_hooks=[
|
||||
GuardrailEventHooks.pre_call,
|
||||
GuardrailEventHooks.post_call,
|
||||
],
|
||||
supported_event_hooks=list(self.get_supported_event_hooks()),
|
||||
event_hook=event_hook or GuardrailEventHooks.pre_call,
|
||||
default_on=default_on,
|
||||
**kwargs,
|
||||
|
|
@ -96,6 +93,13 @@ class SemanticGuardrail(CustomGuardrail):
|
|||
f"embedding_model={embedding_model}, threshold={similarity_threshold}"
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def get_supported_event_hooks(cls) -> List[GuardrailEventHooks]:
|
||||
return [
|
||||
GuardrailEventHooks.pre_call,
|
||||
GuardrailEventHooks.post_call,
|
||||
]
|
||||
|
||||
@log_guardrail_information
|
||||
async def async_pre_call_hook(
|
||||
self,
|
||||
|
|
|
|||
|
|
@ -52,11 +52,7 @@ class ToolPermissionGuardrail(CustomGuardrail):
|
|||
**kwargs: Additional arguments passed to CustomGuardrail
|
||||
"""
|
||||
# Set supported event hooks - this guardrail only works on post_call
|
||||
if "supported_event_hooks" not in kwargs:
|
||||
kwargs["supported_event_hooks"] = [
|
||||
GuardrailEventHooks.pre_call,
|
||||
GuardrailEventHooks.post_call,
|
||||
]
|
||||
kwargs.setdefault("supported_event_hooks", list(self.get_supported_event_hooks()))
|
||||
|
||||
super().__init__(**kwargs)
|
||||
|
||||
|
|
@ -179,6 +175,13 @@ class ToolPermissionGuardrail(CustomGuardrail):
|
|||
|
||||
return ToolPermissionGuardrailConfigModel
|
||||
|
||||
@classmethod
|
||||
def get_supported_event_hooks(cls) -> List[GuardrailEventHooks]:
|
||||
return [
|
||||
GuardrailEventHooks.pre_call,
|
||||
GuardrailEventHooks.post_call,
|
||||
]
|
||||
|
||||
def _matches_regex(self, pattern: Optional[re.Pattern], value: Optional[str]) -> bool:
|
||||
if pattern is None:
|
||||
return True
|
||||
|
|
|
|||
|
|
@ -116,11 +116,7 @@ class VigilGuardGuardrail(CustomGuardrail):
|
|||
llm_provider=httpxSpecialProvider.GuardrailCallback,
|
||||
)
|
||||
|
||||
if "supported_event_hooks" not in kwargs:
|
||||
kwargs["supported_event_hooks"] = [
|
||||
GuardrailEventHooks.pre_call,
|
||||
GuardrailEventHooks.post_call,
|
||||
]
|
||||
kwargs.setdefault("supported_event_hooks", list(self.get_supported_event_hooks()))
|
||||
|
||||
super().__init__(**kwargs)
|
||||
|
||||
|
|
@ -132,6 +128,13 @@ class VigilGuardGuardrail(CustomGuardrail):
|
|||
|
||||
return VigilGuardGuardrailConfigModel
|
||||
|
||||
@classmethod
|
||||
def get_supported_event_hooks(cls) -> List[GuardrailEventHooks]:
|
||||
return [
|
||||
GuardrailEventHooks.pre_call,
|
||||
GuardrailEventHooks.post_call,
|
||||
]
|
||||
|
||||
@log_guardrail_information
|
||||
async def apply_guardrail(
|
||||
self,
|
||||
|
|
|
|||
|
|
@ -119,13 +119,7 @@ class XecGuardGuardrail(CustomGuardrail):
|
|||
llm_provider=httpxSpecialProvider.GuardrailCallback,
|
||||
)
|
||||
|
||||
if "supported_event_hooks" not in kwargs:
|
||||
kwargs["supported_event_hooks"] = [
|
||||
GuardrailEventHooks.pre_call,
|
||||
GuardrailEventHooks.during_call,
|
||||
GuardrailEventHooks.post_call,
|
||||
GuardrailEventHooks.logging_only,
|
||||
]
|
||||
kwargs.setdefault("supported_event_hooks", list(self.get_supported_event_hooks()))
|
||||
|
||||
super().__init__(**kwargs)
|
||||
|
||||
|
|
@ -137,6 +131,15 @@ class XecGuardGuardrail(CustomGuardrail):
|
|||
|
||||
return XecGuardConfigModel
|
||||
|
||||
@classmethod
|
||||
def get_supported_event_hooks(cls) -> List[GuardrailEventHooks]:
|
||||
return [
|
||||
GuardrailEventHooks.pre_call,
|
||||
GuardrailEventHooks.during_call,
|
||||
GuardrailEventHooks.post_call,
|
||||
GuardrailEventHooks.logging_only,
|
||||
]
|
||||
|
||||
@log_guardrail_information
|
||||
async def apply_guardrail(
|
||||
self,
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
#
|
||||
# +-------------------------------------------------------------+
|
||||
import os
|
||||
from typing import TYPE_CHECKING, Literal, Optional
|
||||
from typing import TYPE_CHECKING, List, Literal, Optional
|
||||
|
||||
from fastapi import HTTPException
|
||||
|
||||
|
|
@ -17,6 +17,7 @@ from litellm.llms.custom_httpx.http_handler import (
|
|||
get_async_httpx_client,
|
||||
httpxSpecialProvider,
|
||||
)
|
||||
from litellm.types.guardrails import GuardrailEventHooks
|
||||
from litellm.types.utils import GenericGuardrailAPIInputs
|
||||
|
||||
if TYPE_CHECKING:
|
||||
|
|
@ -27,6 +28,13 @@ GUARDRAIL_TIMEOUT = 5
|
|||
|
||||
|
||||
class ZscalerAIGuard(CustomGuardrail):
|
||||
@classmethod
|
||||
def get_supported_event_hooks(cls) -> List[GuardrailEventHooks]:
|
||||
return [
|
||||
GuardrailEventHooks.pre_call,
|
||||
GuardrailEventHooks.post_call,
|
||||
]
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
api_key: Optional[str] = None,
|
||||
|
|
@ -37,6 +45,7 @@ class ZscalerAIGuard(CustomGuardrail):
|
|||
send_user_api_key_team_id: Optional[bool] = None,
|
||||
**kwargs,
|
||||
):
|
||||
kwargs.setdefault("supported_event_hooks", list(self.get_supported_event_hooks()))
|
||||
self.optional_params = kwargs
|
||||
self.zscaler_ai_guard_url = api_base or os.getenv(
|
||||
"ZSCALER_AI_GUARD_URL",
|
||||
|
|
|
|||
|
|
@ -13,12 +13,23 @@ from litellm._logging import verbose_proxy_logger
|
|||
from litellm._uuid import uuid
|
||||
from litellm.integrations.custom_guardrail import CustomGuardrail
|
||||
from litellm.litellm_core_utils.safe_json_dumps import safe_dumps
|
||||
from litellm.proxy.guardrails.guardrail_hooks.bedrock_guardrails import (
|
||||
BedrockGuardrail,
|
||||
)
|
||||
from litellm.proxy.guardrails.guardrail_hooks.grayswan import (
|
||||
GraySwanGuardrail,
|
||||
)
|
||||
from litellm.proxy.guardrails.guardrail_hooks.grayswan import (
|
||||
initialize_guardrail as initialize_grayswan,
|
||||
)
|
||||
from litellm.proxy.guardrails.guardrail_hooks.lakera_ai import lakeraAI_Moderation
|
||||
from litellm.proxy.guardrails.guardrail_hooks.lakera_ai_v2 import LakeraAIGuardrail
|
||||
from litellm.proxy.guardrails.guardrail_hooks.presidio import (
|
||||
_OPTIONAL_PresidioPIIMasking,
|
||||
)
|
||||
from litellm.proxy.guardrails.guardrail_hooks.tool_permission import (
|
||||
ToolPermissionGuardrail,
|
||||
)
|
||||
from litellm.proxy.types_utils.utils import get_instance_fn
|
||||
from litellm.proxy.utils import PrismaClient
|
||||
from litellm.repositories.table_repositories import GuardrailsRepository
|
||||
|
|
@ -55,7 +66,12 @@ guardrail_initializer_registry = {
|
|||
}
|
||||
|
||||
guardrail_class_registry: Dict[str, Type[CustomGuardrail]] = {
|
||||
SupportedGuardrailIntegrations.GRAYSWAN.value: GraySwanGuardrail
|
||||
SupportedGuardrailIntegrations.BEDROCK.value: BedrockGuardrail,
|
||||
SupportedGuardrailIntegrations.GRAYSWAN.value: GraySwanGuardrail,
|
||||
SupportedGuardrailIntegrations.LAKERA.value: lakeraAI_Moderation,
|
||||
SupportedGuardrailIntegrations.LAKERA_V2.value: LakeraAIGuardrail,
|
||||
SupportedGuardrailIntegrations.PRESIDIO.value: _OPTIONAL_PresidioPIIMasking,
|
||||
SupportedGuardrailIntegrations.TOOL_PERMISSION.value: ToolPermissionGuardrail,
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -904,6 +904,7 @@ class GuardrailUIAddGuardrailSettings(BaseModel):
|
|||
supported_entities: List[str]
|
||||
supported_actions: List[str]
|
||||
supported_modes: List[str]
|
||||
supported_modes_by_provider: Dict[str, List[str]]
|
||||
pii_entity_categories: List[PiiEntityCategoryMap]
|
||||
content_filter_settings: Optional[Dict[str, Any]] = None
|
||||
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ from dataclasses import dataclass
|
|||
from typing import Literal
|
||||
|
||||
import pytest
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
from pydantic import BaseModel, ConfigDict, Field, JsonValue, TypeAdapter, ValidationError
|
||||
|
||||
from e2e_config import POLL_INTERVAL, POLL_TIMEOUT
|
||||
from e2e_gateway import Gateway, build_gateway
|
||||
|
|
@ -200,15 +200,16 @@ def costs_agree(expected: float, actual: float, *, rel_tol: float = 0.05) -> boo
|
|||
return abs(expected - actual) <= max(1e-9, abs(expected) * rel_tol)
|
||||
|
||||
|
||||
_COMPLETION_BODY_ADAPTER: TypeAdapter[dict[str, JsonValue]] = TypeAdapter(dict[str, JsonValue])
|
||||
|
||||
|
||||
def completion_response_id(body: str) -> str | None:
|
||||
"""SpendLogs.request_id is the chat completion body id, not x-litellm-call-id."""
|
||||
if not body or body == "<streamed>":
|
||||
return None
|
||||
try:
|
||||
parsed = json.loads(body)
|
||||
except json.JSONDecodeError:
|
||||
return None
|
||||
if not isinstance(parsed, dict):
|
||||
parsed = _COMPLETION_BODY_ADAPTER.validate_json(body)
|
||||
except ValidationError:
|
||||
return None
|
||||
raw = parsed.get("id")
|
||||
return raw if isinstance(raw, str) and raw else None
|
||||
|
|
@ -499,9 +500,9 @@ class LoggingClient:
|
|||
headers=creds.auth_headers,
|
||||
params=LangfuseListParams(
|
||||
limit=100,
|
||||
trace_id=trace_id,
|
||||
traceId=trace_id,
|
||||
name=name,
|
||||
from_start_time=from_start_time,
|
||||
fromStartTime=from_start_time,
|
||||
),
|
||||
response_type=LangfuseObservationList,
|
||||
timeout=30.0,
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ from litellm.proxy.guardrails.guardrail_endpoints import (
|
|||
delete_guardrail,
|
||||
get_guardrail_info,
|
||||
get_guardrail_submission,
|
||||
get_guardrail_ui_settings,
|
||||
list_guardrail_submissions,
|
||||
list_guardrails_v2,
|
||||
patch_guardrail,
|
||||
|
|
@ -2079,3 +2080,131 @@ async def test_list_submissions_summary_counts_unaffected_by_filters(mocker):
|
|||
assert result.summary.total == 2 # unfiltered
|
||||
assert result.summary.pending_review == 1
|
||||
assert result.summary.active == 1
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_guardrail_ui_settings_returns_per_provider_supported_modes():
|
||||
"""
|
||||
Regression test for LIT-4226. The Admin UI used to render `pre_mcp_call` as a
|
||||
selectable mode for every guardrail because the settings endpoint returned a
|
||||
single global `supported_modes` list. The proxy then rejected the save because
|
||||
Content Filter and Tool Permission do not accept `pre_mcp_call`. The endpoint
|
||||
must now return per-provider modes so the UI can filter its dropdown.
|
||||
"""
|
||||
result = await get_guardrail_ui_settings()
|
||||
|
||||
modes_by_provider = result.supported_modes_by_provider
|
||||
|
||||
# Guardrails from the bug report: neither accepts pre_mcp_call, and the
|
||||
# settings endpoint must reflect that so the UI can hide it.
|
||||
assert "pre_mcp_call" not in modes_by_provider["litellm_content_filter"]
|
||||
assert modes_by_provider["tool_permission"] == ["pre_call", "post_call"]
|
||||
|
||||
# MCP-capable guardrails must still advertise the MCP hooks so users who
|
||||
# picked one of them can actually configure pre_mcp_call / during_mcp_call.
|
||||
for provider in ("bedrock", "panw_prisma_airs", "cisco_ai_defense", "custom_code", "pillar"):
|
||||
assert "pre_mcp_call" in modes_by_provider[provider], provider
|
||||
assert "during_mcp_call" in modes_by_provider[provider], provider
|
||||
|
||||
# The union list stays exhaustive for legacy clients that ignore the
|
||||
# per-provider map; it must cover every declared GuardrailEventHooks value.
|
||||
from litellm.types.guardrails import GuardrailEventHooks
|
||||
|
||||
assert set(result.supported_modes) == {m.value for m in GuardrailEventHooks}
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_ui_settings_map_matches_runtime_supported_event_hooks():
|
||||
"""
|
||||
Regression guard against the two-copy-of-the-list drift risk. The map the
|
||||
UI reads must agree with what CustomGuardrail._validate_event_hook accepts
|
||||
at save time, otherwise the bug in LIT-4226 comes back one classname at a
|
||||
time as future guardrails drift.
|
||||
"""
|
||||
from litellm.proxy.guardrails.guardrail_registry import guardrail_class_registry
|
||||
|
||||
result = await get_guardrail_ui_settings()
|
||||
|
||||
for provider, guardrail_class in guardrail_class_registry.items():
|
||||
declared = guardrail_class.get_supported_event_hooks()
|
||||
if declared is None:
|
||||
assert (
|
||||
provider not in result.supported_modes_by_provider
|
||||
), f"{provider} returned None from classmethod but appears in map"
|
||||
continue
|
||||
|
||||
assert provider in result.supported_modes_by_provider, provider
|
||||
assert result.supported_modes_by_provider[provider] == [
|
||||
hook.value for hook in declared
|
||||
], provider
|
||||
|
||||
|
||||
def test_content_filter_runtime_rejects_pre_mcp_call():
|
||||
"""
|
||||
Locks the runtime side of the LIT-4226 contract: the ContentFilterGuardrail
|
||||
validator must reject pre_mcp_call at construction. If someone widens the
|
||||
UI classmethod but forgets to widen the runtime supported_event_hooks (or
|
||||
vice versa), the two-lists-must-agree test above catches the drift and this
|
||||
test catches the specific bug the ticket reported.
|
||||
"""
|
||||
from litellm.proxy.guardrails.guardrail_hooks.litellm_content_filter.content_filter import (
|
||||
ContentFilterGuardrail,
|
||||
)
|
||||
from litellm.types.guardrails import GuardrailEventHooks
|
||||
|
||||
with pytest.raises(ValueError, match="not in the supported event hooks"):
|
||||
ContentFilterGuardrail(
|
||||
guardrail_name="lit4226-runtime-check",
|
||||
event_hook=GuardrailEventHooks.pre_mcp_call,
|
||||
)
|
||||
|
||||
|
||||
def test_model_armor_runtime_supported_event_hooks_match_classmethod():
|
||||
"""
|
||||
Regression for the drift Round 2 caught: the ModelArmorGuardrail classmethod
|
||||
declared its supported hooks for the UI, but __init__ did not seed the
|
||||
runtime instance's `supported_event_hooks` from that classmethod, so the
|
||||
runtime validator accepted any hook (including nonsense like logging_only)
|
||||
while the UI hid them. Ensures the two sides agree at instantiation time.
|
||||
"""
|
||||
from litellm.proxy.guardrails.guardrail_hooks.model_armor.model_armor import (
|
||||
ModelArmorGuardrail,
|
||||
)
|
||||
|
||||
instance = ModelArmorGuardrail(
|
||||
guardrail_name="lit4226-model-armor-drift",
|
||||
template_id="t",
|
||||
project_id="p",
|
||||
)
|
||||
assert instance.supported_event_hooks == ModelArmorGuardrail.get_supported_event_hooks()
|
||||
|
||||
|
||||
def test_strict_guardrail_modes_flag_controls_raise_vs_warn(monkeypatch, caplog):
|
||||
"""
|
||||
Escape hatch for the boot-time behavior change. Deployments upgrading from
|
||||
a build where a guardrail previously silently no-op'd on an unsupported
|
||||
mode should be able to set LITELLM_STRICT_GUARDRAIL_MODES=false and boot
|
||||
with a warning instead of a hard failure while they fix their config.
|
||||
"""
|
||||
import logging
|
||||
|
||||
from litellm.proxy.guardrails.guardrail_hooks.litellm_content_filter.content_filter import (
|
||||
ContentFilterGuardrail,
|
||||
)
|
||||
from litellm.types.guardrails import GuardrailEventHooks
|
||||
|
||||
monkeypatch.delenv("LITELLM_STRICT_GUARDRAIL_MODES", raising=False)
|
||||
with pytest.raises(ValueError, match="not in the supported event hooks"):
|
||||
ContentFilterGuardrail(
|
||||
guardrail_name="lit4226-strict-default",
|
||||
event_hook=GuardrailEventHooks.pre_mcp_call,
|
||||
)
|
||||
|
||||
monkeypatch.setenv("LITELLM_STRICT_GUARDRAIL_MODES", "false")
|
||||
with caplog.at_level(logging.WARNING):
|
||||
instance = ContentFilterGuardrail(
|
||||
guardrail_name="lit4226-strict-off",
|
||||
event_hook=GuardrailEventHooks.pre_mcp_call,
|
||||
)
|
||||
assert instance is not None
|
||||
assert any("not in the supported event hooks" in rec.message for rec in caplog.records)
|
||||
|
|
|
|||
|
|
@ -12,6 +12,67 @@
|
|||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/app/(dashboard)/agents/_components/AgentsPanel.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
},
|
||||
"react-hooks/set-state-in-effect": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/app/(dashboard)/agents/_components/add_agent_form.tsx": {
|
||||
"no-nested-ternary": {
|
||||
"count": 3
|
||||
},
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
},
|
||||
"react-hooks/set-state-in-effect": {
|
||||
"count": 2
|
||||
},
|
||||
"unused-imports/no-unused-imports": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/app/(dashboard)/agents/_components/agent_card_discovery.tsx": {
|
||||
"react-hooks/refs": {
|
||||
"count": 3
|
||||
},
|
||||
"react-hooks/set-state-in-effect": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/app/(dashboard)/agents/_components/agent_cost_view.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/app/(dashboard)/agents/_components/agent_form_fields.tsx": {
|
||||
"no-nested-ternary": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/app/(dashboard)/agents/_components/agent_info.tsx": {
|
||||
"no-nested-ternary": {
|
||||
"count": 1
|
||||
},
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
},
|
||||
"react-hooks/immutability": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/app/(dashboard)/agents/_components/agent_virtual_keys.tsx": {
|
||||
"no-nested-ternary": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/app/(dashboard)/agents/_components/dynamic_agent_form_fields.tsx": {
|
||||
"no-nested-ternary": {
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
"src/app/(dashboard)/api-reference/_components/APIReferenceView.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
|
|
@ -164,6 +225,136 @@
|
|||
"count": 8
|
||||
}
|
||||
},
|
||||
"src/app/(dashboard)/guardrails/_components/GuardrailTestPanel.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/app/(dashboard)/guardrails/_components/GuardrailTestPlayground.tsx": {
|
||||
"no-nested-ternary": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/app/(dashboard)/guardrails/_components/GuardrailTestResults.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/app/(dashboard)/guardrails/_components/GuardrailsPanel.tsx": {
|
||||
"react-hooks/set-state-in-effect": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/app/(dashboard)/guardrails/_components/TeamGuardrailsTab.tsx": {
|
||||
"no-nested-ternary": {
|
||||
"count": 2
|
||||
},
|
||||
"react-hooks/set-state-in-effect": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/app/(dashboard)/guardrails/_components/add_guardrail_form.tsx": {
|
||||
"react-hooks/set-state-in-effect": {
|
||||
"count": 1
|
||||
},
|
||||
"react/no-unescaped-entities": {
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
"src/app/(dashboard)/guardrails/_components/content_filter/CompetitorIntentConfiguration.tsx": {
|
||||
"no-nested-ternary": {
|
||||
"count": 1
|
||||
},
|
||||
"react-hooks/set-state-in-effect": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/app/(dashboard)/guardrails/_components/content_filter/ContentCategoryConfiguration.tsx": {
|
||||
"no-nested-ternary": {
|
||||
"count": 3
|
||||
},
|
||||
"react-hooks/set-state-in-effect": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/app/(dashboard)/guardrails/_components/content_filter/ContentFilterDisplay.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/app/(dashboard)/guardrails/_components/content_filter/ContentFilterManager.tsx": {
|
||||
"max-params": {
|
||||
"count": 2
|
||||
},
|
||||
"react-hooks/set-state-in-effect": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/app/(dashboard)/guardrails/_components/custom_code/CustomCodeModal.tsx": {
|
||||
"no-nested-ternary": {
|
||||
"count": 6
|
||||
},
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
},
|
||||
"react-hooks/set-state-in-effect": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/app/(dashboard)/guardrails/_components/edit_guardrail_form.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
},
|
||||
"no-restricted-syntax": {
|
||||
"count": 1
|
||||
},
|
||||
"react-hooks/set-state-in-effect": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/app/(dashboard)/guardrails/_components/guardrail_info.tsx": {
|
||||
"max-params": {
|
||||
"count": 1
|
||||
},
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
},
|
||||
"react-hooks/set-state-in-effect": {
|
||||
"count": 3
|
||||
}
|
||||
},
|
||||
"src/app/(dashboard)/guardrails/_components/guardrail_optional_params.tsx": {
|
||||
"no-nested-ternary": {
|
||||
"count": 5
|
||||
},
|
||||
"react-hooks/set-state-in-effect": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/app/(dashboard)/guardrails/_components/guardrail_provider_fields.tsx": {
|
||||
"no-nested-ternary": {
|
||||
"count": 5
|
||||
},
|
||||
"react-hooks/set-state-in-effect": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/app/(dashboard)/guardrails/_components/guardrail_table.tsx": {
|
||||
"no-nested-ternary": {
|
||||
"count": 1
|
||||
},
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/app/(dashboard)/guardrails/_components/tool_permission/ToolPermissionRulesEditor.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
},
|
||||
"react-hooks/purity": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/app/(dashboard)/hooks/accessGroups/useAccessGroupDetails.ts": {
|
||||
"no-restricted-syntax": {
|
||||
"count": 1
|
||||
|
|
@ -536,6 +727,134 @@
|
|||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/app/(dashboard)/policies/_components/add_attachment_form.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
},
|
||||
"react-hooks/immutability": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/app/(dashboard)/policies/_components/add_policy_form.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
},
|
||||
"react-hooks/immutability": {
|
||||
"count": 2
|
||||
},
|
||||
"react-hooks/set-state-in-effect": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/app/(dashboard)/policies/_components/ai_suggestion_modal.tsx": {
|
||||
"no-nested-ternary": {
|
||||
"count": 10
|
||||
},
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
},
|
||||
"react-hooks/immutability": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/app/(dashboard)/policies/_components/attachment_table.test.tsx": {
|
||||
"react/display-name": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/app/(dashboard)/policies/_components/attachment_table.tsx": {
|
||||
"no-nested-ternary": {
|
||||
"count": 1
|
||||
},
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/app/(dashboard)/policies/_components/guardrail_selection_modal.tsx": {
|
||||
"no-nested-ternary": {
|
||||
"count": 1
|
||||
},
|
||||
"react-hooks/set-state-in-effect": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/app/(dashboard)/policies/_components/impact_popover.test.tsx": {
|
||||
"react/display-name": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/app/(dashboard)/policies/_components/impact_popover.tsx": {
|
||||
"no-nested-ternary": {
|
||||
"count": 1
|
||||
},
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/app/(dashboard)/policies/_components/index.test.tsx": {
|
||||
"react/display-name": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/app/(dashboard)/policies/_components/index.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
},
|
||||
"react-hooks/set-state-in-effect": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/app/(dashboard)/policies/_components/pipeline_flow_builder.tsx": {
|
||||
"no-nested-ternary": {
|
||||
"count": 1
|
||||
},
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
},
|
||||
"react-hooks/set-state-in-effect": {
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
"src/app/(dashboard)/policies/_components/policy_info.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
},
|
||||
"react-hooks/set-state-in-effect": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/app/(dashboard)/policies/_components/policy_table.test.tsx": {
|
||||
"react/display-name": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/app/(dashboard)/policies/_components/policy_table.tsx": {
|
||||
"no-nested-ternary": {
|
||||
"count": 2
|
||||
},
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/app/(dashboard)/policies/_components/policy_test_panel.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
},
|
||||
"react-hooks/immutability": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/app/(dashboard)/policies/_components/template_parameter_modal.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
},
|
||||
"react-hooks/immutability": {
|
||||
"count": 1
|
||||
},
|
||||
"react-hooks/set-state-in-effect": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/app/(dashboard)/projects/_components/ProjectDetailsPage.tsx": {
|
||||
"no-nested-ternary": {
|
||||
"count": 3
|
||||
|
|
@ -697,6 +1016,53 @@
|
|||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/app/(dashboard)/skills/_components/ClaudeCodePluginsPanel.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
},
|
||||
"react-hooks/set-state-in-effect": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/app/(dashboard)/skills/_components/add_plugin_form.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/app/(dashboard)/skills/_components/plugin_table.tsx": {
|
||||
"no-nested-ternary": {
|
||||
"count": 1
|
||||
},
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/app/(dashboard)/tag-management/_components/TagTable.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/app/(dashboard)/tag-management/_components/components/CreateTagModal.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/app/(dashboard)/tag-management/_components/index.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
},
|
||||
"react-hooks/set-state-in-effect": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/app/(dashboard)/tag-management/_components/tag_info.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
},
|
||||
"react-hooks/set-state-in-effect": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/app/(dashboard)/transform-request/TransformRequestPanel.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
|
|
@ -774,6 +1140,43 @@
|
|||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/app/(dashboard)/vector-stores/_components/CreateVectorStore.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/app/(dashboard)/vector-stores/_components/VectorStoreForm.tsx": {
|
||||
"no-nested-ternary": {
|
||||
"count": 2
|
||||
},
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
},
|
||||
"react/no-unescaped-entities": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/app/(dashboard)/vector-stores/_components/VectorStoreTable.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/app/(dashboard)/vector-stores/_components/index.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
},
|
||||
"react-hooks/set-state-in-effect": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/app/(dashboard)/vector-stores/_components/vector_store_info.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
},
|
||||
"react-hooks/set-state-in-effect": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/app/(dashboard)/workflows/WorkflowRuns.tsx": {
|
||||
"no-nested-ternary": {
|
||||
"count": 1
|
||||
|
|
@ -1261,67 +1664,6 @@
|
|||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/app/(dashboard)/agents/_components/AgentsPanel.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
},
|
||||
"react-hooks/set-state-in-effect": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/app/(dashboard)/agents/_components/add_agent_form.tsx": {
|
||||
"no-nested-ternary": {
|
||||
"count": 3
|
||||
},
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
},
|
||||
"react-hooks/set-state-in-effect": {
|
||||
"count": 2
|
||||
},
|
||||
"unused-imports/no-unused-imports": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/app/(dashboard)/agents/_components/agent_card_discovery.tsx": {
|
||||
"react-hooks/refs": {
|
||||
"count": 3
|
||||
},
|
||||
"react-hooks/set-state-in-effect": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/app/(dashboard)/agents/_components/agent_cost_view.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/app/(dashboard)/agents/_components/agent_form_fields.tsx": {
|
||||
"no-nested-ternary": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/app/(dashboard)/agents/_components/agent_info.tsx": {
|
||||
"no-nested-ternary": {
|
||||
"count": 1
|
||||
},
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
},
|
||||
"react-hooks/immutability": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/app/(dashboard)/agents/_components/agent_virtual_keys.tsx": {
|
||||
"no-nested-ternary": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/app/(dashboard)/agents/_components/dynamic_agent_form_fields.tsx": {
|
||||
"no-nested-ternary": {
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
"src/components/alerting/dynamic_form.tsx": {
|
||||
"no-nested-ternary": {
|
||||
"count": 4
|
||||
|
|
@ -1363,14 +1705,6 @@
|
|||
"count": 2
|
||||
}
|
||||
},
|
||||
"src/app/(dashboard)/skills/_components/ClaudeCodePluginsPanel.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
},
|
||||
"react-hooks/set-state-in-effect": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/components/claude_code_plugins/MakeSkillPublicForm.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
|
|
@ -1379,19 +1713,6 @@
|
|||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/app/(dashboard)/skills/_components/add_plugin_form.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/app/(dashboard)/skills/_components/plugin_table.tsx": {
|
||||
"no-nested-ternary": {
|
||||
"count": 1
|
||||
},
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/components/cloudzero_export_modal.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
|
|
@ -1534,139 +1855,6 @@
|
|||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/app/(dashboard)/guardrails/_components/GuardrailsPanel.tsx": {
|
||||
"react-hooks/set-state-in-effect": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/app/(dashboard)/guardrails/_components/GuardrailTestPanel.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/app/(dashboard)/guardrails/_components/GuardrailTestPlayground.tsx": {
|
||||
"no-nested-ternary": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/app/(dashboard)/guardrails/_components/GuardrailTestResults.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/app/(dashboard)/guardrails/_components/TeamGuardrailsTab.tsx": {
|
||||
"no-nested-ternary": {
|
||||
"count": 2
|
||||
},
|
||||
"react-hooks/set-state-in-effect": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/app/(dashboard)/guardrails/_components/add_guardrail_form.tsx": {
|
||||
"no-nested-ternary": {
|
||||
"count": 4
|
||||
},
|
||||
"react-hooks/set-state-in-effect": {
|
||||
"count": 1
|
||||
},
|
||||
"react/no-unescaped-entities": {
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
"src/app/(dashboard)/guardrails/_components/content_filter/CompetitorIntentConfiguration.tsx": {
|
||||
"no-nested-ternary": {
|
||||
"count": 1
|
||||
},
|
||||
"react-hooks/set-state-in-effect": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/app/(dashboard)/guardrails/_components/content_filter/ContentCategoryConfiguration.tsx": {
|
||||
"no-nested-ternary": {
|
||||
"count": 3
|
||||
},
|
||||
"react-hooks/set-state-in-effect": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/app/(dashboard)/guardrails/_components/content_filter/ContentFilterDisplay.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/app/(dashboard)/guardrails/_components/content_filter/ContentFilterManager.tsx": {
|
||||
"max-params": {
|
||||
"count": 2
|
||||
},
|
||||
"react-hooks/set-state-in-effect": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/app/(dashboard)/guardrails/_components/custom_code/CustomCodeModal.tsx": {
|
||||
"no-nested-ternary": {
|
||||
"count": 6
|
||||
},
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
},
|
||||
"react-hooks/set-state-in-effect": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/app/(dashboard)/guardrails/_components/edit_guardrail_form.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
},
|
||||
"no-restricted-syntax": {
|
||||
"count": 1
|
||||
},
|
||||
"react-hooks/set-state-in-effect": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/app/(dashboard)/guardrails/_components/guardrail_info.tsx": {
|
||||
"max-params": {
|
||||
"count": 1
|
||||
},
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
},
|
||||
"react-hooks/set-state-in-effect": {
|
||||
"count": 3
|
||||
}
|
||||
},
|
||||
"src/app/(dashboard)/guardrails/_components/guardrail_optional_params.tsx": {
|
||||
"no-nested-ternary": {
|
||||
"count": 5
|
||||
},
|
||||
"react-hooks/set-state-in-effect": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/app/(dashboard)/guardrails/_components/guardrail_provider_fields.tsx": {
|
||||
"no-nested-ternary": {
|
||||
"count": 5
|
||||
},
|
||||
"react-hooks/set-state-in-effect": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/app/(dashboard)/guardrails/_components/guardrail_table.tsx": {
|
||||
"no-nested-ternary": {
|
||||
"count": 1
|
||||
},
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/app/(dashboard)/guardrails/_components/tool_permission/ToolPermissionRulesEditor.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
},
|
||||
"react-hooks/purity": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/components/key_team_helpers/key_list.tsx": {
|
||||
"react-hooks/set-state-in-effect": {
|
||||
"count": 1
|
||||
|
|
@ -2080,134 +2268,6 @@
|
|||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/app/(dashboard)/policies/_components/add_attachment_form.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
},
|
||||
"react-hooks/immutability": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/app/(dashboard)/policies/_components/add_policy_form.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
},
|
||||
"react-hooks/immutability": {
|
||||
"count": 2
|
||||
},
|
||||
"react-hooks/set-state-in-effect": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/app/(dashboard)/policies/_components/ai_suggestion_modal.tsx": {
|
||||
"no-nested-ternary": {
|
||||
"count": 10
|
||||
},
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
},
|
||||
"react-hooks/immutability": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/app/(dashboard)/policies/_components/attachment_table.test.tsx": {
|
||||
"react/display-name": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/app/(dashboard)/policies/_components/attachment_table.tsx": {
|
||||
"no-nested-ternary": {
|
||||
"count": 1
|
||||
},
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/app/(dashboard)/policies/_components/guardrail_selection_modal.tsx": {
|
||||
"no-nested-ternary": {
|
||||
"count": 1
|
||||
},
|
||||
"react-hooks/set-state-in-effect": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/app/(dashboard)/policies/_components/impact_popover.test.tsx": {
|
||||
"react/display-name": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/app/(dashboard)/policies/_components/impact_popover.tsx": {
|
||||
"no-nested-ternary": {
|
||||
"count": 1
|
||||
},
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/app/(dashboard)/policies/_components/index.test.tsx": {
|
||||
"react/display-name": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/app/(dashboard)/policies/_components/index.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
},
|
||||
"react-hooks/set-state-in-effect": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/app/(dashboard)/policies/_components/pipeline_flow_builder.tsx": {
|
||||
"no-nested-ternary": {
|
||||
"count": 1
|
||||
},
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
},
|
||||
"react-hooks/set-state-in-effect": {
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
"src/app/(dashboard)/policies/_components/policy_info.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
},
|
||||
"react-hooks/set-state-in-effect": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/app/(dashboard)/policies/_components/policy_table.test.tsx": {
|
||||
"react/display-name": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/app/(dashboard)/policies/_components/policy_table.tsx": {
|
||||
"no-nested-ternary": {
|
||||
"count": 2
|
||||
},
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/app/(dashboard)/policies/_components/policy_test_panel.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
},
|
||||
"react-hooks/immutability": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/app/(dashboard)/policies/_components/template_parameter_modal.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
},
|
||||
"react-hooks/immutability": {
|
||||
"count": 1
|
||||
},
|
||||
"react-hooks/set-state-in-effect": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/components/price_data_reload.tsx": {
|
||||
"react-hooks/immutability": {
|
||||
"count": 2
|
||||
|
|
@ -2272,32 +2332,6 @@
|
|||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/app/(dashboard)/tag-management/_components/TagTable.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/app/(dashboard)/tag-management/_components/components/CreateTagModal.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/app/(dashboard)/tag-management/_components/index.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
},
|
||||
"react-hooks/set-state-in-effect": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/app/(dashboard)/tag-management/_components/tag_info.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
},
|
||||
"react-hooks/set-state-in-effect": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/components/team/EditMembership.tsx": {
|
||||
"no-nested-ternary": {
|
||||
"count": 1
|
||||
|
|
@ -2388,43 +2422,6 @@
|
|||
"count": 2
|
||||
}
|
||||
},
|
||||
"src/app/(dashboard)/vector-stores/_components/CreateVectorStore.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/app/(dashboard)/vector-stores/_components/VectorStoreForm.tsx": {
|
||||
"no-nested-ternary": {
|
||||
"count": 2
|
||||
},
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
},
|
||||
"react/no-unescaped-entities": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/app/(dashboard)/vector-stores/_components/VectorStoreTable.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/app/(dashboard)/vector-stores/_components/index.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
},
|
||||
"react-hooks/set-state-in-effect": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/app/(dashboard)/vector-stores/_components/vector_store_info.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
},
|
||||
"react-hooks/set-state-in-effect": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/components/view_logs/EvalViewer/EvalViewer.tsx": {
|
||||
"no-nested-ternary": {
|
||||
"count": 1
|
||||
|
|
|
|||
|
|
@ -8,10 +8,12 @@ import {
|
|||
modelAvailableCall,
|
||||
} from "@/components/networking";
|
||||
import ContentFilterConfiguration from "./content_filter/ContentFilterConfiguration";
|
||||
import { type CompetitorIntentConfig } from "./content_filter/CompetitorIntentConfiguration";
|
||||
import {
|
||||
choiceToSkipSystemForCreate,
|
||||
choiceToSkipToolForCreate,
|
||||
getGuardrailProviders,
|
||||
getSupportedModesForProvider,
|
||||
guardrail_provider_map,
|
||||
guardrailLogoMap,
|
||||
populateGuardrailProviderMap,
|
||||
|
|
@ -19,6 +21,7 @@ import {
|
|||
shouldRenderContentFilterConfigSettings,
|
||||
shouldRenderLLMJudgeFields,
|
||||
shouldRenderPIIConfigSettings,
|
||||
toModeArray,
|
||||
} from "./guardrail_info_helpers";
|
||||
import { resolveLogoSrc } from "@/lib/assetPaths";
|
||||
import GuardrailOptionalParams from "./guardrail_optional_params";
|
||||
|
|
@ -60,6 +63,7 @@ interface GuardrailSettings {
|
|||
supported_entities: string[];
|
||||
supported_actions: string[];
|
||||
supported_modes: string[];
|
||||
supported_modes_by_provider?: Record<string, string[]>;
|
||||
pii_entity_categories: Array<{
|
||||
category: string;
|
||||
entities: string[];
|
||||
|
|
@ -82,13 +86,55 @@ interface GuardrailSettings {
|
|||
};
|
||||
}
|
||||
|
||||
interface LiteLLMParams {
|
||||
guardrail: string;
|
||||
mode: string;
|
||||
default_on: boolean;
|
||||
[key: string]: any; // Allow additional properties for specific guardrails
|
||||
interface ContentFilterPattern {
|
||||
id: string;
|
||||
type: "prebuilt" | "custom";
|
||||
name: string;
|
||||
display_name?: string;
|
||||
pattern?: string;
|
||||
action: "BLOCK" | "MASK";
|
||||
}
|
||||
|
||||
interface ContentFilterBlockedWord {
|
||||
id: string;
|
||||
keyword: string;
|
||||
action: "BLOCK" | "MASK";
|
||||
description?: string;
|
||||
}
|
||||
|
||||
interface SelectedContentCategory {
|
||||
id: string;
|
||||
category: string;
|
||||
display_name: string;
|
||||
action: "BLOCK" | "MASK";
|
||||
severity_threshold: "high" | "medium" | "low";
|
||||
}
|
||||
|
||||
interface JudgeCriterion {
|
||||
name: string;
|
||||
weight: number | string;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
const createEmptyToolPermissionConfig = (): ToolPermissionConfig => ({
|
||||
rules: [],
|
||||
default_action: "deny",
|
||||
on_disallowed_action: "block",
|
||||
violation_message_template: "",
|
||||
});
|
||||
|
||||
const getStepIndicatorStyle = (isDone: boolean, isCurrent: boolean): React.CSSProperties => {
|
||||
if (isDone) return { background: "#4f46e5", color: "#fff", border: "none" };
|
||||
if (isCurrent) return { background: "#fff", color: "#4f46e5", border: "2px solid #4f46e5" };
|
||||
return { background: "#f8fafc", color: "#94a3b8", border: "1px solid #e2e8f0" };
|
||||
};
|
||||
|
||||
const getStepTitleColor = (isDone: boolean, isCurrent: boolean): string => {
|
||||
if (isCurrent) return "#1e293b";
|
||||
if (isDone) return "#4f46e5";
|
||||
return "#94a3b8";
|
||||
};
|
||||
|
||||
// Mapping of provider -> list of param descriptors
|
||||
interface ProviderParam {
|
||||
param: string;
|
||||
|
|
@ -122,12 +168,12 @@ const AddGuardrailForm: React.FC<AddGuardrailFormProps> = ({ visible, onClose, a
|
|||
const [categorySpecificThresholds, setCategorySpecificThresholds] = useState<{ [key: string]: number }>({});
|
||||
|
||||
// Content Filter state
|
||||
const [selectedPatterns, setSelectedPatterns] = useState<any[]>([]);
|
||||
const [blockedWords, setBlockedWords] = useState<any[]>([]);
|
||||
const [selectedContentCategories, setSelectedContentCategories] = useState<any[]>([]);
|
||||
const [selectedPatterns, setSelectedPatterns] = useState<ContentFilterPattern[]>([]);
|
||||
const [blockedWords, setBlockedWords] = useState<ContentFilterBlockedWord[]>([]);
|
||||
const [selectedContentCategories, setSelectedContentCategories] = useState<SelectedContentCategory[]>([]);
|
||||
const [pendingCategorySelection, setPendingCategorySelection] = useState<string>("");
|
||||
const [competitorIntentEnabled, setCompetitorIntentEnabled] = useState(false);
|
||||
const [competitorIntentConfig, setCompetitorIntentConfig] = useState<any>(null);
|
||||
const [competitorIntentConfig, setCompetitorIntentConfig] = useState<CompetitorIntentConfig | null>(null);
|
||||
|
||||
// Endpoint Settings state (step 5)
|
||||
const [selectedEndpointType, setSelectedEndpointType] = useState<string>("");
|
||||
|
|
@ -137,12 +183,9 @@ const AddGuardrailForm: React.FC<AddGuardrailFormProps> = ({ visible, onClose, a
|
|||
const [endpointSettingsOpen, setEndpointSettingsOpen] = useState<boolean>(false);
|
||||
const [availableModels, setAvailableModels] = useState<string[]>([]);
|
||||
|
||||
const [toolPermissionConfig, setToolPermissionConfig] = useState<ToolPermissionConfig>({
|
||||
rules: [],
|
||||
default_action: "deny",
|
||||
on_disallowed_action: "block",
|
||||
violation_message_template: "",
|
||||
});
|
||||
const [toolPermissionConfig, setToolPermissionConfig] = useState<ToolPermissionConfig>(
|
||||
createEmptyToolPermissionConfig,
|
||||
);
|
||||
|
||||
const isToolPermissionProvider = useMemo(() => {
|
||||
if (!selectedProvider) {
|
||||
|
|
@ -168,7 +211,7 @@ const AddGuardrailForm: React.FC<AddGuardrailFormProps> = ({ visible, onClose, a
|
|||
setGuardrailSettings(uiSettings);
|
||||
setProviderParams(providerParamsResp);
|
||||
if (modelsResp?.data) {
|
||||
setAvailableModels(modelsResp.data.map((m: any) => m.id));
|
||||
setAvailableModels(modelsResp.data.map((m: { id: string }) => m.id));
|
||||
}
|
||||
|
||||
// Populate dynamic providers from API response
|
||||
|
|
@ -189,7 +232,7 @@ const AddGuardrailForm: React.FC<AddGuardrailFormProps> = ({ visible, onClose, a
|
|||
|
||||
// Set provider
|
||||
setSelectedProvider(preset.provider);
|
||||
const baseValues: Record<string, any> = {
|
||||
const baseValues: Record<string, unknown> = {
|
||||
provider: preset.provider,
|
||||
guardrail_name: preset.guardrailNameSuggestion,
|
||||
mode: preset.mode,
|
||||
|
|
@ -205,7 +248,7 @@ const AddGuardrailForm: React.FC<AddGuardrailFormProps> = ({ visible, onClose, a
|
|||
// Pre-select content category if specified
|
||||
if (preset.categoryName && guardrailSettings.content_filter_settings?.content_categories) {
|
||||
const category = guardrailSettings.content_filter_settings.content_categories.find(
|
||||
(c: any) => c.name === preset.categoryName,
|
||||
(c) => c.name === preset.categoryName,
|
||||
);
|
||||
if (category) {
|
||||
setSelectedContentCategories([
|
||||
|
|
@ -219,12 +262,12 @@ const AddGuardrailForm: React.FC<AddGuardrailFormProps> = ({ visible, onClose, a
|
|||
]);
|
||||
}
|
||||
}
|
||||
}, [preset, visible, guardrailSettings]);
|
||||
}, [preset, visible, guardrailSettings, form]);
|
||||
|
||||
const handleProviderChange = (value: string) => {
|
||||
setSelectedProvider(value);
|
||||
// Reset form fields that are provider-specific
|
||||
const resetValues: Record<string, any> = {
|
||||
const resetValues: Record<string, unknown> = {
|
||||
config: undefined,
|
||||
presidio_analyzer_api_base: undefined,
|
||||
presidio_anonymizer_api_base: undefined,
|
||||
|
|
@ -232,6 +275,21 @@ const AddGuardrailForm: React.FC<AddGuardrailFormProps> = ({ visible, onClose, a
|
|||
if (value === "BlockCodeExecution") {
|
||||
resetValues.confidence_threshold = 0.5;
|
||||
}
|
||||
|
||||
// Drop selected modes the new provider does not support
|
||||
const newProviderKey = guardrail_provider_map[value]?.toLowerCase();
|
||||
const newProviderModes =
|
||||
newProviderKey && guardrailSettings?.supported_modes_by_provider
|
||||
? guardrailSettings.supported_modes_by_provider[newProviderKey]
|
||||
: undefined;
|
||||
if (newProviderModes) {
|
||||
const selectedModes = toModeArray(form.getFieldValue("mode"));
|
||||
const keptModes = selectedModes.filter((m) => newProviderModes.includes(m));
|
||||
if (keptModes.length !== selectedModes.length) {
|
||||
resetValues.mode = keptModes.length > 0 ? keptModes : undefined;
|
||||
}
|
||||
}
|
||||
|
||||
form.setFieldsValue(resetValues);
|
||||
|
||||
// Reset PII selections when changing provider
|
||||
|
|
@ -251,12 +309,7 @@ const AddGuardrailForm: React.FC<AddGuardrailFormProps> = ({ visible, onClose, a
|
|||
setCompetitorIntentEnabled(false);
|
||||
setCompetitorIntentConfig(null);
|
||||
|
||||
setToolPermissionConfig({
|
||||
rules: [],
|
||||
default_action: "deny",
|
||||
on_disallowed_action: "block",
|
||||
violation_message_template: "",
|
||||
});
|
||||
setToolPermissionConfig(createEmptyToolPermissionConfig());
|
||||
|
||||
// Default LLM-as-a-Judge to post_call mode
|
||||
if (value === "LlmAsAJudge") {
|
||||
|
|
@ -385,12 +438,7 @@ const AddGuardrailForm: React.FC<AddGuardrailFormProps> = ({ visible, onClose, a
|
|||
setBlockedWords([]);
|
||||
setSelectedContentCategories([]);
|
||||
setPendingCategorySelection("");
|
||||
setToolPermissionConfig({
|
||||
rules: [],
|
||||
default_action: "deny",
|
||||
on_disallowed_action: "block",
|
||||
violation_message_template: "",
|
||||
});
|
||||
setToolPermissionConfig(createEmptyToolPermissionConfig());
|
||||
setSelectedEndpointType("");
|
||||
setEndSessionAfterNFails(undefined);
|
||||
setOnViolation("warn");
|
||||
|
|
@ -423,9 +471,9 @@ const AddGuardrailForm: React.FC<AddGuardrailFormProps> = ({ visible, onClose, a
|
|||
guardrail: string;
|
||||
mode: string;
|
||||
default_on: boolean;
|
||||
[key: string]: any; // Allow dynamic properties
|
||||
[key: string]: unknown; // Allow dynamic properties
|
||||
};
|
||||
guardrail_info: any;
|
||||
guardrail_info: Record<string, unknown>;
|
||||
} = {
|
||||
guardrail_name: values.guardrail_name,
|
||||
litellm_params: {
|
||||
|
|
@ -467,13 +515,10 @@ const AddGuardrailForm: React.FC<AddGuardrailFormProps> = ({ visible, onClose, a
|
|||
// For Content Filter, add patterns, blocked words, categories, and optionally competitor intent
|
||||
if (shouldRenderContentFilterConfigSettings(values.provider)) {
|
||||
// Validate that at least one content filter setting is configured
|
||||
const hasCompetitorIntent = competitorIntentEnabled && competitorIntentConfig?.brand_self?.length > 0;
|
||||
if (
|
||||
selectedPatterns.length === 0 &&
|
||||
blockedWords.length === 0 &&
|
||||
selectedContentCategories.length === 0 &&
|
||||
!hasCompetitorIntent
|
||||
) {
|
||||
const hasCompetitorIntent = competitorIntentEnabled && (competitorIntentConfig?.brand_self?.length ?? 0) > 0;
|
||||
const hasContentFilterSelections =
|
||||
selectedPatterns.length > 0 || blockedWords.length > 0 || selectedContentCategories.length > 0;
|
||||
if (!hasContentFilterSelections && !hasCompetitorIntent) {
|
||||
NotificationsManager.fromBackend(
|
||||
"Please configure at least one content filter setting (category, pattern, keyword, or competitor intent)",
|
||||
);
|
||||
|
|
@ -505,14 +550,15 @@ const AddGuardrailForm: React.FC<AddGuardrailFormProps> = ({ visible, onClose, a
|
|||
severity_threshold: c.severity_threshold || "medium",
|
||||
}));
|
||||
}
|
||||
if (competitorIntentEnabled && competitorIntentConfig?.brand_self?.length > 0) {
|
||||
if (hasCompetitorIntent && competitorIntentConfig) {
|
||||
guardrailData.litellm_params.competitor_intent_config = {
|
||||
competitor_intent_type: competitorIntentConfig.competitor_intent_type ?? "airline",
|
||||
brand_self: competitorIntentConfig.brand_self,
|
||||
locations: competitorIntentConfig.locations?.length > 0 ? competitorIntentConfig.locations : undefined,
|
||||
locations:
|
||||
(competitorIntentConfig.locations?.length ?? 0) > 0 ? competitorIntentConfig.locations : undefined,
|
||||
competitors:
|
||||
competitorIntentConfig.competitor_intent_type === "generic" &&
|
||||
competitorIntentConfig.competitors?.length > 0
|
||||
(competitorIntentConfig.competitors?.length ?? 0) > 0
|
||||
? competitorIntentConfig.competitors
|
||||
: undefined,
|
||||
policy: competitorIntentConfig.policy,
|
||||
|
|
@ -536,13 +582,13 @@ const AddGuardrailForm: React.FC<AddGuardrailFormProps> = ({ visible, onClose, a
|
|||
}
|
||||
|
||||
if (guardrailProvider === "llm_as_a_judge") {
|
||||
const criteria: any[] = values.criteria || [];
|
||||
const criteria: JudgeCriterion[] = values.criteria || [];
|
||||
if (criteria.length === 0) {
|
||||
NotificationsManager.fromBackend("Add at least one evaluation criterion");
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
const weightTotal = criteria.reduce((sum: number, c: any) => sum + (Number(c?.weight) || 0), 0);
|
||||
const weightTotal = criteria.reduce((sum, c) => sum + (Number(c?.weight) || 0), 0);
|
||||
if (weightTotal !== 100) {
|
||||
NotificationsManager.fromBackend(`Criterion weights must sum to 100% (currently ${weightTotal}%)`);
|
||||
setLoading(false);
|
||||
|
|
@ -551,7 +597,7 @@ const AddGuardrailForm: React.FC<AddGuardrailFormProps> = ({ visible, onClose, a
|
|||
guardrailData.litellm_params.judge_model = values.judge_model;
|
||||
guardrailData.litellm_params.overall_threshold = values.overall_threshold ?? 80;
|
||||
guardrailData.litellm_params.on_failure = values.on_failure ?? "block";
|
||||
guardrailData.litellm_params.criteria = criteria.map((c: any) => ({
|
||||
guardrailData.litellm_params.criteria = criteria.map((c) => ({
|
||||
name: c.name,
|
||||
weight: Number(c.weight),
|
||||
description: c.description || "",
|
||||
|
|
@ -652,6 +698,10 @@ const AddGuardrailForm: React.FC<AddGuardrailFormProps> = ({ visible, onClose, a
|
|||
};
|
||||
|
||||
const renderBasicInfo = () => {
|
||||
const showProviderFields =
|
||||
!isToolPermissionProvider &&
|
||||
!shouldRenderContentFilterConfigSettings(selectedProvider) &&
|
||||
!shouldRenderLLMJudgeFields(selectedProvider);
|
||||
return (
|
||||
<>
|
||||
<Form.Item
|
||||
|
|
@ -732,7 +782,7 @@ const AddGuardrailForm: React.FC<AddGuardrailFormProps> = ({ visible, onClose, a
|
|||
rules={[{ required: true, message: "Please select a mode" }]}
|
||||
>
|
||||
<Select optionLabelProp="label" mode="multiple">
|
||||
{guardrailSettings?.supported_modes?.map((mode) => (
|
||||
{getSupportedModesForProvider(guardrailSettings, selectedProvider)?.map((mode) => (
|
||||
<Option key={mode} value={mode} label={mode}>
|
||||
<div>
|
||||
<div>
|
||||
|
|
@ -823,15 +873,13 @@ const AddGuardrailForm: React.FC<AddGuardrailFormProps> = ({ visible, onClose, a
|
|||
</Form.Item>
|
||||
|
||||
{/* Use the GuardrailProviderFields component to render provider-specific fields */}
|
||||
{!isToolPermissionProvider &&
|
||||
!shouldRenderContentFilterConfigSettings(selectedProvider) &&
|
||||
!shouldRenderLLMJudgeFields(selectedProvider) && (
|
||||
<GuardrailProviderFields
|
||||
selectedProvider={selectedProvider}
|
||||
accessToken={accessToken}
|
||||
providerParams={providerParams}
|
||||
/>
|
||||
)}
|
||||
{showProviderFields && (
|
||||
<GuardrailProviderFields
|
||||
selectedProvider={selectedProvider}
|
||||
accessToken={accessToken}
|
||||
providerParams={providerParams}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
@ -1179,11 +1227,7 @@ const AddGuardrailForm: React.FC<AddGuardrailFormProps> = ({ visible, onClose, a
|
|||
<div className="flex flex-col items-center shrink-0" style={{ width: 24 }}>
|
||||
<div
|
||||
className="w-6 h-6 rounded-full flex items-center justify-center text-xs font-medium shrink-0"
|
||||
style={{
|
||||
background: isDone ? "#4f46e5" : isCurrent ? "#fff" : "#f8fafc",
|
||||
color: isDone ? "#fff" : isCurrent ? "#4f46e5" : "#94a3b8",
|
||||
border: isCurrent ? "2px solid #4f46e5" : isDone ? "none" : "1px solid #e2e8f0",
|
||||
}}
|
||||
style={getStepIndicatorStyle(isDone, isCurrent)}
|
||||
>
|
||||
{isDone ? "\u2713" : index + 1}
|
||||
</div>
|
||||
|
|
@ -1213,7 +1257,7 @@ const AddGuardrailForm: React.FC<AddGuardrailFormProps> = ({ visible, onClose, a
|
|||
className="text-sm"
|
||||
style={{
|
||||
fontWeight: isCurrent ? 600 : 500,
|
||||
color: isCurrent ? "#1e293b" : isDone ? "#4f46e5" : "#94a3b8",
|
||||
color: getStepTitleColor(isDone, isCurrent),
|
||||
}}
|
||||
>
|
||||
{step.title}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@ import {
|
|||
guardrail_provider_map,
|
||||
guardrailLogoMap,
|
||||
getGuardrailProviders,
|
||||
getSupportedModesForProvider,
|
||||
toModeArray,
|
||||
type SkipSystemMessageChoice,
|
||||
type SkipToolMessageChoice,
|
||||
} from "./guardrail_info_helpers";
|
||||
|
|
@ -23,7 +25,7 @@ interface EditGuardrailFormProps {
|
|||
onSuccess: () => void;
|
||||
guardrailId: string;
|
||||
/** Full stored params merged into PUT so optional fields (e.g. content filter) are preserved. */
|
||||
fullLitellmParams?: Record<string, any> | null;
|
||||
fullLitellmParams?: Record<string, unknown> | null;
|
||||
initialValues: {
|
||||
guardrail_name: string;
|
||||
provider: string;
|
||||
|
|
@ -32,7 +34,7 @@ interface EditGuardrailFormProps {
|
|||
pii_entities_config?: { [key: string]: string };
|
||||
skip_system_message_choice?: SkipSystemMessageChoice;
|
||||
skip_tool_message_choice?: SkipToolMessageChoice;
|
||||
[key: string]: any;
|
||||
[key: string]: unknown;
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -40,6 +42,7 @@ interface GuardrailSettings {
|
|||
supported_entities: string[];
|
||||
supported_actions: string[];
|
||||
supported_modes: string[];
|
||||
supported_modes_by_provider?: Record<string, string[]>;
|
||||
pii_entity_categories: Array<{
|
||||
category: string;
|
||||
entities: string[];
|
||||
|
|
@ -125,7 +128,7 @@ const EditGuardrailForm: React.FC<EditGuardrailFormProps> = ({
|
|||
// Get the guardrail provider value from the map
|
||||
const guardrailProvider = guardrail_provider_map[values.provider];
|
||||
|
||||
const litellm_params: Record<string, any> =
|
||||
const litellm_params: Record<string, unknown> =
|
||||
fullLitellmParams && typeof fullLitellmParams === "object" ? { ...fullLitellmParams } : {};
|
||||
|
||||
litellm_params.guardrail = guardrailProvider;
|
||||
|
|
@ -150,7 +153,7 @@ const EditGuardrailForm: React.FC<EditGuardrailFormProps> = ({
|
|||
delete litellm_params.skip_tool_message_in_guardrail;
|
||||
}
|
||||
|
||||
let guardrail_info: any = {};
|
||||
let guardrail_info: Record<string, unknown> = {};
|
||||
|
||||
// For Presidio PII, add the entity and action configurations
|
||||
if (values.provider === "PresidioPII" && selectedEntities.length > 0) {
|
||||
|
|
@ -189,8 +192,8 @@ const EditGuardrailForm: React.FC<EditGuardrailFormProps> = ({
|
|||
guardrail_id: string;
|
||||
guardrail: {
|
||||
guardrail_name: string;
|
||||
litellm_params: Record<string, any>;
|
||||
guardrail_info: any;
|
||||
litellm_params: Record<string, unknown>;
|
||||
guardrail_info: Record<string, unknown>;
|
||||
};
|
||||
} = {
|
||||
guardrail_id: guardrailId,
|
||||
|
|
@ -419,16 +422,21 @@ const EditGuardrailForm: React.FC<EditGuardrailFormProps> = ({
|
|||
rules={[{ required: true, message: "Please select a mode" }]}
|
||||
>
|
||||
<Select>
|
||||
{guardrailSettings?.supported_modes?.map((mode) => (
|
||||
<Option key={mode} value={mode}>
|
||||
{mode}
|
||||
</Option>
|
||||
)) || (
|
||||
<>
|
||||
<Option value="pre_call">pre_call</Option>
|
||||
<Option value="post_call">post_call</Option>
|
||||
</>
|
||||
)}
|
||||
{(() => {
|
||||
const modes = getSupportedModesForProvider(guardrailSettings, selectedProvider) ?? [
|
||||
"pre_call",
|
||||
"post_call",
|
||||
];
|
||||
const currentModes = toModeArray(initialValues?.mode);
|
||||
const unsupportedCurrent = currentModes.filter((m) => !modes.includes(m));
|
||||
return [...unsupportedCurrent, ...modes].map((mode) => (
|
||||
<Option key={mode} value={mode}>
|
||||
{unsupportedCurrent.includes(mode)
|
||||
? `${mode} (not supported by ${selectedProvider}, pick another)`
|
||||
: mode}
|
||||
</Option>
|
||||
));
|
||||
})()}
|
||||
</Select>
|
||||
</Form.Item>
|
||||
|
||||
|
|
|
|||
|
|
@ -76,6 +76,26 @@ export const populateGuardrailProviderMap = (providerParamsResponse: Record<stri
|
|||
});
|
||||
};
|
||||
|
||||
// Normalizes a form "mode" value (string, string[], or empty) into a string array
|
||||
export const toModeArray = (raw: unknown): string[] => {
|
||||
if (Array.isArray(raw)) return raw.filter((m): m is string => typeof m === "string");
|
||||
if (typeof raw === "string") return [raw];
|
||||
return [];
|
||||
};
|
||||
|
||||
// Resolves the supported modes for the selected provider, falling back to the global list
|
||||
export const getSupportedModesForProvider = (
|
||||
settings: { supported_modes?: string[]; supported_modes_by_provider?: Record<string, string[]> } | null,
|
||||
selectedProvider: string | null,
|
||||
): string[] | undefined => {
|
||||
const providerKey = selectedProvider ? guardrail_provider_map[selectedProvider]?.toLowerCase() : null;
|
||||
const perProvider =
|
||||
providerKey && settings?.supported_modes_by_provider
|
||||
? settings.supported_modes_by_provider[providerKey]
|
||||
: undefined;
|
||||
return perProvider ?? settings?.supported_modes;
|
||||
};
|
||||
|
||||
// Decides if we should render the PII config settings for a given provider
|
||||
// For now we only support PII config settings for Presidio PII
|
||||
export const shouldRenderPIIConfigSettings = (provider: string | null) => {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue