From a4d9c441914e22504535c069ba42c8d2beffcea5 Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Sat, 28 Feb 2026 15:41:08 -0800 Subject: [PATCH] feat(ui/): guardrails - team submissions via UI --- .../migration.sql | 6 + .../migration.sql | 10 + .../litellm_proxy_extras/schema.prisma | 8 + .../out/{404.html => 404/index.html} | 0 .../index.html} | 0 .../index.html} | 0 .../index.html} | 0 .../{budgets.html => budgets/index.html} | 0 .../{caching.html => caching/index.html} | 0 .../index.html} | 0 .../{old-usage.html => old-usage/index.html} | 0 .../{prompts.html => prompts/index.html} | 0 .../index.html} | 0 .../index.html} | 0 .../out/{login.html => login/index.html} | 0 .../out/{logs.html => logs/index.html} | 0 .../{callback.html => callback/index.html} | 0 .../{model-hub.html => model-hub/index.html} | 0 .../{model_hub.html => model_hub/index.html} | 0 .../index.html} | 0 .../index.html} | 0 .../index.html} | 0 .../index.html} | 0 .../index.html} | 0 .../{policies.html => policies/index.html} | 0 .../index.html} | 0 .../index.html} | 0 .../index.html} | 0 .../{ui-theme.html => ui-theme/index.html} | 0 .../out/{teams.html => teams/index.html} | 0 .../{test-key.html => test-key/index.html} | 0 .../index.html} | 0 .../index.html} | 0 .../out/{usage.html => usage/index.html} | 0 .../out/{users.html => users/index.html} | 0 .../index.html} | 0 .../proxy/guardrails/guardrail_endpoints.py | 76 +- .../proxy/guardrails/guardrail_registry.py | 36 +- litellm/proxy/schema.prisma | 8 + schema.prisma | 8 + ui/litellm-dashboard/package-lock.json | 15 + .../src/components/guardrails.tsx | 242 +----- .../guardrails/GuardrailGardenTab.tsx | 177 ++++ .../guardrails/GuardrailsListTab.tsx | 81 ++ .../components/guardrails/GuardrailsPage.tsx | 62 ++ .../components/guardrails/PlaygroundTab.tsx | 63 ++ .../guardrails/TeamGuardrailsTab.tsx | 798 ++++++++++++++++++ ui/litellm-dashboard/tsconfig.json | 2 +- 48 files changed, 1294 insertions(+), 298 deletions(-) create mode 100644 litellm-proxy-extras/litellm_proxy_extras/migrations/20260228153253_baseline_diff/migration.sql create mode 100644 litellm-proxy-extras/litellm_proxy_extras/migrations/20260228160000_add_guardrail_submission_status/migration.sql rename litellm/proxy/_experimental/out/{404.html => 404/index.html} (100%) rename litellm/proxy/_experimental/out/{_not-found.html => _not-found/index.html} (100%) rename litellm/proxy/_experimental/out/{api-reference.html => api-reference/index.html} (100%) rename litellm/proxy/_experimental/out/experimental/{api-playground.html => api-playground/index.html} (100%) rename litellm/proxy/_experimental/out/experimental/{budgets.html => budgets/index.html} (100%) rename litellm/proxy/_experimental/out/experimental/{caching.html => caching/index.html} (100%) rename litellm/proxy/_experimental/out/experimental/{claude-code-plugins.html => claude-code-plugins/index.html} (100%) rename litellm/proxy/_experimental/out/experimental/{old-usage.html => old-usage/index.html} (100%) rename litellm/proxy/_experimental/out/experimental/{prompts.html => prompts/index.html} (100%) rename litellm/proxy/_experimental/out/experimental/{tag-management.html => tag-management/index.html} (100%) rename litellm/proxy/_experimental/out/{guardrails.html => guardrails/index.html} (100%) rename litellm/proxy/_experimental/out/{login.html => login/index.html} (100%) rename litellm/proxy/_experimental/out/{logs.html => logs/index.html} (100%) rename litellm/proxy/_experimental/out/mcp/oauth/{callback.html => callback/index.html} (100%) rename litellm/proxy/_experimental/out/{model-hub.html => model-hub/index.html} (100%) rename litellm/proxy/_experimental/out/{model_hub.html => model_hub/index.html} (100%) rename litellm/proxy/_experimental/out/{model_hub_table.html => model_hub_table/index.html} (100%) rename litellm/proxy/_experimental/out/{models-and-endpoints.html => models-and-endpoints/index.html} (100%) rename litellm/proxy/_experimental/out/{onboarding.html => onboarding/index.html} (100%) rename litellm/proxy/_experimental/out/{organizations.html => organizations/index.html} (100%) rename litellm/proxy/_experimental/out/{playground.html => playground/index.html} (100%) rename litellm/proxy/_experimental/out/{policies.html => policies/index.html} (100%) rename litellm/proxy/_experimental/out/settings/{admin-settings.html => admin-settings/index.html} (100%) rename litellm/proxy/_experimental/out/settings/{logging-and-alerts.html => logging-and-alerts/index.html} (100%) rename litellm/proxy/_experimental/out/settings/{router-settings.html => router-settings/index.html} (100%) rename litellm/proxy/_experimental/out/settings/{ui-theme.html => ui-theme/index.html} (100%) rename litellm/proxy/_experimental/out/{teams.html => teams/index.html} (100%) rename litellm/proxy/_experimental/out/{test-key.html => test-key/index.html} (100%) rename litellm/proxy/_experimental/out/tools/{mcp-servers.html => mcp-servers/index.html} (100%) rename litellm/proxy/_experimental/out/tools/{vector-stores.html => vector-stores/index.html} (100%) rename litellm/proxy/_experimental/out/{usage.html => usage/index.html} (100%) rename litellm/proxy/_experimental/out/{users.html => users/index.html} (100%) rename litellm/proxy/_experimental/out/{virtual-keys.html => virtual-keys/index.html} (100%) create mode 100644 ui/litellm-dashboard/src/components/guardrails/GuardrailGardenTab.tsx create mode 100644 ui/litellm-dashboard/src/components/guardrails/GuardrailsListTab.tsx create mode 100644 ui/litellm-dashboard/src/components/guardrails/GuardrailsPage.tsx create mode 100644 ui/litellm-dashboard/src/components/guardrails/PlaygroundTab.tsx create mode 100644 ui/litellm-dashboard/src/components/guardrails/TeamGuardrailsTab.tsx diff --git a/litellm-proxy-extras/litellm_proxy_extras/migrations/20260228153253_baseline_diff/migration.sql b/litellm-proxy-extras/litellm_proxy_extras/migrations/20260228153253_baseline_diff/migration.sql new file mode 100644 index 00000000000..0a72f2182f4 --- /dev/null +++ b/litellm-proxy-extras/litellm_proxy_extras/migrations/20260228153253_baseline_diff/migration.sql @@ -0,0 +1,6 @@ +-- DropIndex +DROP INDEX "LiteLLM_GuardrailsTable_guardrail_name_team_id_key"; + +-- CreateIndex +CREATE UNIQUE INDEX "LiteLLM_GuardrailsTable_guardrail_name_key" ON "LiteLLM_GuardrailsTable"("guardrail_name"); + diff --git a/litellm-proxy-extras/litellm_proxy_extras/migrations/20260228160000_add_guardrail_submission_status/migration.sql b/litellm-proxy-extras/litellm_proxy_extras/migrations/20260228160000_add_guardrail_submission_status/migration.sql new file mode 100644 index 00000000000..2ef4901b274 --- /dev/null +++ b/litellm-proxy-extras/litellm_proxy_extras/migrations/20260228160000_add_guardrail_submission_status/migration.sql @@ -0,0 +1,10 @@ +-- AlterTable: add submission lifecycle columns to LiteLLM_GuardrailsTable +-- status: pending_review (team-registered), active (approved), rejected. Default active for existing rows. +ALTER TABLE "LiteLLM_GuardrailsTable" ADD COLUMN "status" TEXT NOT NULL DEFAULT 'active'; +ALTER TABLE "LiteLLM_GuardrailsTable" ADD COLUMN "submitted_by_user_id" TEXT; +ALTER TABLE "LiteLLM_GuardrailsTable" ADD COLUMN "submitted_by_email" TEXT; +ALTER TABLE "LiteLLM_GuardrailsTable" ADD COLUMN "submitted_at" TIMESTAMP(3); +ALTER TABLE "LiteLLM_GuardrailsTable" ADD COLUMN "reviewed_at" TIMESTAMP(3); + +-- CreateIndex +CREATE INDEX "LiteLLM_GuardrailsTable_status_idx" ON "LiteLLM_GuardrailsTable"("status"); diff --git a/litellm-proxy-extras/litellm_proxy_extras/schema.prisma b/litellm-proxy-extras/litellm_proxy_extras/schema.prisma index f18556ac329..6dd9b29f2f7 100644 --- a/litellm-proxy-extras/litellm_proxy_extras/schema.prisma +++ b/litellm-proxy-extras/litellm_proxy_extras/schema.prisma @@ -871,6 +871,14 @@ model LiteLLM_GuardrailsTable { team_id String? created_at DateTime @default(now()) updated_at DateTime @updatedAt + // Submission lifecycle: pending_review (team-registered), active (approved), rejected + status String @default("active") + submitted_by_user_id String? + submitted_by_email String? + submitted_at DateTime? + reviewed_at DateTime? + + @@index([status]) } // Daily guardrail metrics for usage dashboard (one row per guardrail per day) diff --git a/litellm/proxy/_experimental/out/404.html b/litellm/proxy/_experimental/out/404/index.html similarity index 100% rename from litellm/proxy/_experimental/out/404.html rename to litellm/proxy/_experimental/out/404/index.html diff --git a/litellm/proxy/_experimental/out/_not-found.html b/litellm/proxy/_experimental/out/_not-found/index.html similarity index 100% rename from litellm/proxy/_experimental/out/_not-found.html rename to litellm/proxy/_experimental/out/_not-found/index.html diff --git a/litellm/proxy/_experimental/out/api-reference.html b/litellm/proxy/_experimental/out/api-reference/index.html similarity index 100% rename from litellm/proxy/_experimental/out/api-reference.html rename to litellm/proxy/_experimental/out/api-reference/index.html diff --git a/litellm/proxy/_experimental/out/experimental/api-playground.html b/litellm/proxy/_experimental/out/experimental/api-playground/index.html similarity index 100% rename from litellm/proxy/_experimental/out/experimental/api-playground.html rename to litellm/proxy/_experimental/out/experimental/api-playground/index.html diff --git a/litellm/proxy/_experimental/out/experimental/budgets.html b/litellm/proxy/_experimental/out/experimental/budgets/index.html similarity index 100% rename from litellm/proxy/_experimental/out/experimental/budgets.html rename to litellm/proxy/_experimental/out/experimental/budgets/index.html diff --git a/litellm/proxy/_experimental/out/experimental/caching.html b/litellm/proxy/_experimental/out/experimental/caching/index.html similarity index 100% rename from litellm/proxy/_experimental/out/experimental/caching.html rename to litellm/proxy/_experimental/out/experimental/caching/index.html diff --git a/litellm/proxy/_experimental/out/experimental/claude-code-plugins.html b/litellm/proxy/_experimental/out/experimental/claude-code-plugins/index.html similarity index 100% rename from litellm/proxy/_experimental/out/experimental/claude-code-plugins.html rename to litellm/proxy/_experimental/out/experimental/claude-code-plugins/index.html diff --git a/litellm/proxy/_experimental/out/experimental/old-usage.html b/litellm/proxy/_experimental/out/experimental/old-usage/index.html similarity index 100% rename from litellm/proxy/_experimental/out/experimental/old-usage.html rename to litellm/proxy/_experimental/out/experimental/old-usage/index.html diff --git a/litellm/proxy/_experimental/out/experimental/prompts.html b/litellm/proxy/_experimental/out/experimental/prompts/index.html similarity index 100% rename from litellm/proxy/_experimental/out/experimental/prompts.html rename to litellm/proxy/_experimental/out/experimental/prompts/index.html diff --git a/litellm/proxy/_experimental/out/experimental/tag-management.html b/litellm/proxy/_experimental/out/experimental/tag-management/index.html similarity index 100% rename from litellm/proxy/_experimental/out/experimental/tag-management.html rename to litellm/proxy/_experimental/out/experimental/tag-management/index.html diff --git a/litellm/proxy/_experimental/out/guardrails.html b/litellm/proxy/_experimental/out/guardrails/index.html similarity index 100% rename from litellm/proxy/_experimental/out/guardrails.html rename to litellm/proxy/_experimental/out/guardrails/index.html diff --git a/litellm/proxy/_experimental/out/login.html b/litellm/proxy/_experimental/out/login/index.html similarity index 100% rename from litellm/proxy/_experimental/out/login.html rename to litellm/proxy/_experimental/out/login/index.html diff --git a/litellm/proxy/_experimental/out/logs.html b/litellm/proxy/_experimental/out/logs/index.html similarity index 100% rename from litellm/proxy/_experimental/out/logs.html rename to litellm/proxy/_experimental/out/logs/index.html diff --git a/litellm/proxy/_experimental/out/mcp/oauth/callback.html b/litellm/proxy/_experimental/out/mcp/oauth/callback/index.html similarity index 100% rename from litellm/proxy/_experimental/out/mcp/oauth/callback.html rename to litellm/proxy/_experimental/out/mcp/oauth/callback/index.html diff --git a/litellm/proxy/_experimental/out/model-hub.html b/litellm/proxy/_experimental/out/model-hub/index.html similarity index 100% rename from litellm/proxy/_experimental/out/model-hub.html rename to litellm/proxy/_experimental/out/model-hub/index.html diff --git a/litellm/proxy/_experimental/out/model_hub.html b/litellm/proxy/_experimental/out/model_hub/index.html similarity index 100% rename from litellm/proxy/_experimental/out/model_hub.html rename to litellm/proxy/_experimental/out/model_hub/index.html diff --git a/litellm/proxy/_experimental/out/model_hub_table.html b/litellm/proxy/_experimental/out/model_hub_table/index.html similarity index 100% rename from litellm/proxy/_experimental/out/model_hub_table.html rename to litellm/proxy/_experimental/out/model_hub_table/index.html diff --git a/litellm/proxy/_experimental/out/models-and-endpoints.html b/litellm/proxy/_experimental/out/models-and-endpoints/index.html similarity index 100% rename from litellm/proxy/_experimental/out/models-and-endpoints.html rename to litellm/proxy/_experimental/out/models-and-endpoints/index.html diff --git a/litellm/proxy/_experimental/out/onboarding.html b/litellm/proxy/_experimental/out/onboarding/index.html similarity index 100% rename from litellm/proxy/_experimental/out/onboarding.html rename to litellm/proxy/_experimental/out/onboarding/index.html diff --git a/litellm/proxy/_experimental/out/organizations.html b/litellm/proxy/_experimental/out/organizations/index.html similarity index 100% rename from litellm/proxy/_experimental/out/organizations.html rename to litellm/proxy/_experimental/out/organizations/index.html diff --git a/litellm/proxy/_experimental/out/playground.html b/litellm/proxy/_experimental/out/playground/index.html similarity index 100% rename from litellm/proxy/_experimental/out/playground.html rename to litellm/proxy/_experimental/out/playground/index.html diff --git a/litellm/proxy/_experimental/out/policies.html b/litellm/proxy/_experimental/out/policies/index.html similarity index 100% rename from litellm/proxy/_experimental/out/policies.html rename to litellm/proxy/_experimental/out/policies/index.html diff --git a/litellm/proxy/_experimental/out/settings/admin-settings.html b/litellm/proxy/_experimental/out/settings/admin-settings/index.html similarity index 100% rename from litellm/proxy/_experimental/out/settings/admin-settings.html rename to litellm/proxy/_experimental/out/settings/admin-settings/index.html diff --git a/litellm/proxy/_experimental/out/settings/logging-and-alerts.html b/litellm/proxy/_experimental/out/settings/logging-and-alerts/index.html similarity index 100% rename from litellm/proxy/_experimental/out/settings/logging-and-alerts.html rename to litellm/proxy/_experimental/out/settings/logging-and-alerts/index.html diff --git a/litellm/proxy/_experimental/out/settings/router-settings.html b/litellm/proxy/_experimental/out/settings/router-settings/index.html similarity index 100% rename from litellm/proxy/_experimental/out/settings/router-settings.html rename to litellm/proxy/_experimental/out/settings/router-settings/index.html diff --git a/litellm/proxy/_experimental/out/settings/ui-theme.html b/litellm/proxy/_experimental/out/settings/ui-theme/index.html similarity index 100% rename from litellm/proxy/_experimental/out/settings/ui-theme.html rename to litellm/proxy/_experimental/out/settings/ui-theme/index.html diff --git a/litellm/proxy/_experimental/out/teams.html b/litellm/proxy/_experimental/out/teams/index.html similarity index 100% rename from litellm/proxy/_experimental/out/teams.html rename to litellm/proxy/_experimental/out/teams/index.html diff --git a/litellm/proxy/_experimental/out/test-key.html b/litellm/proxy/_experimental/out/test-key/index.html similarity index 100% rename from litellm/proxy/_experimental/out/test-key.html rename to litellm/proxy/_experimental/out/test-key/index.html diff --git a/litellm/proxy/_experimental/out/tools/mcp-servers.html b/litellm/proxy/_experimental/out/tools/mcp-servers/index.html similarity index 100% rename from litellm/proxy/_experimental/out/tools/mcp-servers.html rename to litellm/proxy/_experimental/out/tools/mcp-servers/index.html diff --git a/litellm/proxy/_experimental/out/tools/vector-stores.html b/litellm/proxy/_experimental/out/tools/vector-stores/index.html similarity index 100% rename from litellm/proxy/_experimental/out/tools/vector-stores.html rename to litellm/proxy/_experimental/out/tools/vector-stores/index.html diff --git a/litellm/proxy/_experimental/out/usage.html b/litellm/proxy/_experimental/out/usage/index.html similarity index 100% rename from litellm/proxy/_experimental/out/usage.html rename to litellm/proxy/_experimental/out/usage/index.html diff --git a/litellm/proxy/_experimental/out/users.html b/litellm/proxy/_experimental/out/users/index.html similarity index 100% rename from litellm/proxy/_experimental/out/users.html rename to litellm/proxy/_experimental/out/users/index.html diff --git a/litellm/proxy/_experimental/out/virtual-keys.html b/litellm/proxy/_experimental/out/virtual-keys/index.html similarity index 100% rename from litellm/proxy/_experimental/out/virtual-keys.html rename to litellm/proxy/_experimental/out/virtual-keys/index.html diff --git a/litellm/proxy/guardrails/guardrail_endpoints.py b/litellm/proxy/guardrails/guardrail_endpoints.py index 5215fca0293..6568864e87f 100644 --- a/litellm/proxy/guardrails/guardrail_endpoints.py +++ b/litellm/proxy/guardrails/guardrail_endpoints.py @@ -4,6 +4,7 @@ CRUD ENDPOINTS FOR GUARDRAILS import concurrent.futures import inspect +from datetime import datetime, timezone from typing import Any, Dict, List, Optional, Type, TypeVar, Union, cast from fastapi import APIRouter, Depends, HTTPException @@ -12,37 +13,31 @@ from pydantic import BaseModel from litellm._logging import verbose_proxy_logger from litellm.constants import DEFAULT_MAX_RECURSE_DEPTH from litellm.integrations.custom_guardrail import CustomGuardrail +from litellm.litellm_core_utils.safe_json_dumps import safe_dumps from litellm.proxy._types import LitellmUserRoles, UserAPIKeyAuth from litellm.proxy.auth.user_api_key_auth import user_api_key_auth from litellm.proxy.guardrails.guardrail_hooks.custom_code.code_validator import ( - CustomCodeValidationError, - validate_custom_code, -) -from litellm.proxy.guardrails.guardrail_hooks.custom_code.primitives import ( - get_custom_code_primitives, -) + CustomCodeValidationError, validate_custom_code) +from litellm.proxy.guardrails.guardrail_hooks.custom_code.primitives import \ + get_custom_code_primitives from litellm.proxy.guardrails.guardrail_registry import GuardrailRegistry -from litellm.proxy.guardrails.usage_endpoints import router as guardrails_usage_router -from litellm.types.guardrails import ( - PII_ENTITY_CATEGORIES_MAP, - ApplyGuardrailRequest, - ApplyGuardrailResponse, - BaseLitellmParams, - BedrockGuardrailConfigModel, - Guardrail, - GuardrailEventHooks, - GuardrailInfoResponse, - GuardrailUIAddGuardrailSettings, - LakeraV2GuardrailConfigModel, - ListGuardrailsResponse, - LitellmParams, - PatchGuardrailRequest, - PiiAction, - PiiEntityType, - PresidioPresidioConfigModelUserInterface, - SupportedGuardrailIntegrations, - ToolPermissionGuardrailConfigModel, -) +from litellm.proxy.guardrails.usage_endpoints import \ + router as guardrails_usage_router +from litellm.types.guardrails import (PII_ENTITY_CATEGORIES_MAP, + ApplyGuardrailRequest, + ApplyGuardrailResponse, + BaseLitellmParams, + BedrockGuardrailConfigModel, Guardrail, + GuardrailEventHooks, + GuardrailInfoResponse, + GuardrailUIAddGuardrailSettings, + LakeraV2GuardrailConfigModel, + ListGuardrailsResponse, LitellmParams, + PatchGuardrailRequest, PiiAction, + PiiEntityType, + PresidioPresidioConfigModelUserInterface, + SupportedGuardrailIntegrations, + ToolPermissionGuardrailConfigModel) #### GUARDRAILS ENDPOINTS #### @@ -161,7 +156,8 @@ async def list_guardrails_v2(): ``` """ from litellm.litellm_core_utils.litellm_logging import _get_masked_values - from litellm.proxy.guardrails.guardrail_registry import IN_MEMORY_GUARDRAIL_HANDLER + from litellm.proxy.guardrails.guardrail_registry import \ + IN_MEMORY_GUARDRAIL_HANDLER from litellm.proxy.proxy_server import prisma_client if prisma_client is None: @@ -303,7 +299,8 @@ async def create_guardrail( } ``` """ - from litellm.proxy.guardrails.guardrail_registry import IN_MEMORY_GUARDRAIL_HANDLER + from litellm.proxy.guardrails.guardrail_registry import \ + IN_MEMORY_GUARDRAIL_HANDLER from litellm.proxy.proxy_server import prisma_client if user_api_key_dict.user_role != LitellmUserRoles.PROXY_ADMIN: @@ -401,7 +398,8 @@ async def update_guardrail( } ``` """ - from litellm.proxy.guardrails.guardrail_registry import IN_MEMORY_GUARDRAIL_HANDLER + from litellm.proxy.guardrails.guardrail_registry import \ + IN_MEMORY_GUARDRAIL_HANDLER from litellm.proxy.proxy_server import prisma_client if user_api_key_dict.user_role != LitellmUserRoles.PROXY_ADMIN: @@ -477,7 +475,8 @@ async def delete_guardrail( } ``` """ - from litellm.proxy.guardrails.guardrail_registry import IN_MEMORY_GUARDRAIL_HANDLER + from litellm.proxy.guardrails.guardrail_registry import \ + IN_MEMORY_GUARDRAIL_HANDLER from litellm.proxy.proxy_server import prisma_client if user_api_key_dict.user_role != LitellmUserRoles.PROXY_ADMIN: @@ -579,7 +578,8 @@ async def patch_guardrail( } ``` """ - from litellm.proxy.guardrails.guardrail_registry import IN_MEMORY_GUARDRAIL_HANDLER + from litellm.proxy.guardrails.guardrail_registry import \ + IN_MEMORY_GUARDRAIL_HANDLER from litellm.proxy.proxy_server import prisma_client if user_api_key_dict.user_role != LitellmUserRoles.PROXY_ADMIN: @@ -707,7 +707,8 @@ async def get_guardrail_info(guardrail_id: str): """ from litellm.litellm_core_utils.litellm_logging import _get_masked_values - from litellm.proxy.guardrails.guardrail_registry import IN_MEMORY_GUARDRAIL_HANDLER + from litellm.proxy.guardrails.guardrail_registry import \ + IN_MEMORY_GUARDRAIL_HANDLER from litellm.proxy.proxy_server import prisma_client from litellm.types.guardrails import GUARDRAIL_DEFINITION_LOCATION @@ -782,10 +783,8 @@ async def get_guardrail_ui_settings(): - Content filter settings (patterns and categories) """ from litellm.proxy.guardrails.guardrail_hooks.litellm_content_filter.patterns import ( - PATTERN_CATEGORIES, - get_available_content_categories, - get_pattern_metadata, - ) + PATTERN_CATEGORIES, get_available_content_categories, + get_pattern_metadata) # Convert the PII_ENTITY_CATEGORIES_MAP to the format expected by the UI category_maps = [] @@ -1369,7 +1368,8 @@ async def get_provider_specific_params(): } ### get the config model for the guardrail - go through the registry and get the config model for the guardrail - from litellm.proxy.guardrails.guardrail_registry import guardrail_class_registry + from litellm.proxy.guardrails.guardrail_registry import \ + guardrail_class_registry for guardrail_name, guardrail_class in guardrail_class_registry.items(): guardrail_config_model = guardrail_class.get_config_model() diff --git a/litellm/proxy/guardrails/guardrail_registry.py b/litellm/proxy/guardrails/guardrail_registry.py index c0903a35b6d..09fff66c2a5 100644 --- a/litellm/proxy/guardrails/guardrail_registry.py +++ b/litellm/proxy/guardrails/guardrail_registry.py @@ -11,29 +11,21 @@ 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.utils import PrismaClient +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.types_utils.utils import get_instance_fn +from litellm.proxy.utils import PrismaClient from litellm.secret_managers.main import get_secret -from litellm.types.guardrails import ( - Guardrail, - GuardrailEventHooks, - LakeraCategoryThresholds, - LitellmParams, - SupportedGuardrailIntegrations, -) -from litellm.proxy.guardrails.guardrail_hooks.grayswan import ( - GraySwanGuardrail, - initialize_guardrail as initialize_grayswan, -) +from litellm.types.guardrails import (Guardrail, GuardrailEventHooks, + LakeraCategoryThresholds, LitellmParams, + SupportedGuardrailIntegrations) -from .guardrail_initializers import ( - initialize_bedrock, - initialize_hide_secrets, - initialize_lakera, - initialize_lakera_v2, - initialize_presidio, - initialize_tool_permission, -) +from .guardrail_initializers import (initialize_bedrock, + initialize_hide_secrets, + initialize_lakera, initialize_lakera_v2, + initialize_presidio, + initialize_tool_permission) guardrail_initializer_registry = { SupportedGuardrailIntegrations.BEDROCK.value: initialize_bedrock, @@ -327,11 +319,13 @@ class GuardrailRegistry: prisma_client: PrismaClient, ) -> List[Guardrail]: """ - Get all guardrails from the database + Get all active guardrails from the database. + Only rows with status == "active" are returned (pending_review and rejected are excluded). """ try: guardrails_from_db = ( await prisma_client.db.litellm_guardrailstable.find_many( + where={"status": "active"}, order={"created_at": "desc"}, ) ) diff --git a/litellm/proxy/schema.prisma b/litellm/proxy/schema.prisma index f18556ac329..6dd9b29f2f7 100644 --- a/litellm/proxy/schema.prisma +++ b/litellm/proxy/schema.prisma @@ -871,6 +871,14 @@ model LiteLLM_GuardrailsTable { team_id String? created_at DateTime @default(now()) updated_at DateTime @updatedAt + // Submission lifecycle: pending_review (team-registered), active (approved), rejected + status String @default("active") + submitted_by_user_id String? + submitted_by_email String? + submitted_at DateTime? + reviewed_at DateTime? + + @@index([status]) } // Daily guardrail metrics for usage dashboard (one row per guardrail per day) diff --git a/schema.prisma b/schema.prisma index f18556ac329..6dd9b29f2f7 100644 --- a/schema.prisma +++ b/schema.prisma @@ -871,6 +871,14 @@ model LiteLLM_GuardrailsTable { team_id String? created_at DateTime @default(now()) updated_at DateTime @updatedAt + // Submission lifecycle: pending_review (team-registered), active (approved), rejected + status String @default("active") + submitted_by_user_id String? + submitted_by_email String? + submitted_at DateTime? + reviewed_at DateTime? + + @@index([status]) } // Daily guardrail metrics for usage dashboard (one row per guardrail per day) diff --git a/ui/litellm-dashboard/package-lock.json b/ui/litellm-dashboard/package-lock.json index 200182cf551..697ec68e0bb 100644 --- a/ui/litellm-dashboard/package-lock.json +++ b/ui/litellm-dashboard/package-lock.json @@ -12984,6 +12984,21 @@ "type": "github", "url": "https://github.com/sponsors/wooorm" } + }, + "node_modules/@next/swc-win32-ia32-msvc": { + "version": "14.2.33", + "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.33.tgz", + "integrity": "sha512-pc9LpGNKhJ0dXQhZ5QMmYxtARwwmWLpeocFmVG5Z0DzWq5Uf0izcI8tLc+qOpqxO1PWqZ5A7J1blrUIKrIFc7Q==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } } } } diff --git a/ui/litellm-dashboard/src/components/guardrails.tsx b/ui/litellm-dashboard/src/components/guardrails.tsx index a8de7dd2f4f..9c2475c7aea 100644 --- a/ui/litellm-dashboard/src/components/guardrails.tsx +++ b/ui/litellm-dashboard/src/components/guardrails.tsx @@ -1,249 +1,15 @@ -import React, { useState, useEffect } from "react"; -import { Button, TabGroup, TabList, Tab, TabPanels, TabPanel } from "@tremor/react"; -import { Dropdown } from "antd"; -import { DownOutlined, PlusOutlined, CodeOutlined } from "@ant-design/icons"; -import { getGuardrailsList, deleteGuardrailCall } from "./networking"; -import AddGuardrailForm from "./guardrails/add_guardrail_form"; -import GuardrailTable from "./guardrails/guardrail_table"; -import { isAdminRole } from "@/utils/roles"; -import GuardrailInfoView from "./guardrails/guardrail_info"; -import GuardrailTestPlayground from "./guardrails/GuardrailTestPlayground"; -import NotificationsManager from "./molecules/notifications_manager"; -import { Guardrail, GuardrailDefinitionLocation } from "./guardrails/types"; -import DeleteResourceModal from "./common_components/DeleteResourceModal"; -import { getGuardrailLogoAndName } from "./guardrails/guardrail_info_helpers"; -import { CustomCodeModal } from "./guardrails/custom_code"; -import GuardrailGarden from "./guardrails/guardrail_garden"; +import React from "react"; +import { GuardrailsPage } from "./guardrails/GuardrailsPage"; interface GuardrailsPanelProps { accessToken: string | null; userRole?: string; } -interface GuardrailItem { - guardrail_id?: string; - guardrail_name: string | null; - litellm_params: { - guardrail: string; - mode: string; - default_on: boolean; - }; - guardrail_info: Record | null; - created_at?: string; - updated_at?: string; - guardrail_definition_location: GuardrailDefinitionLocation; -} - -interface GuardrailsResponse { - guardrails: Guardrail[]; -} - -const GuardrailsPanel: React.FC = ({ accessToken, userRole }) => { - const [guardrailsList, setGuardrailsList] = useState([]); - const [isAddModalVisible, setIsAddModalVisible] = useState(false); - const [isCustomCodeModalVisible, setIsCustomCodeModalVisible] = useState(false); - const [isLoading, setIsLoading] = useState(false); - const [isDeleting, setIsDeleting] = useState(false); - const [guardrailToDelete, setGuardrailToDelete] = useState(null); - const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false); - const [selectedGuardrailId, setSelectedGuardrailId] = useState(null); - const [activeTab, setActiveTab] = useState(0); - - const isAdmin = userRole ? isAdminRole(userRole) : false; - - const fetchGuardrails = async () => { - if (!accessToken) { - return; - } - - setIsLoading(true); - try { - const response: GuardrailsResponse = await getGuardrailsList(accessToken); - console.log(`guardrails: ${JSON.stringify(response)}`); - setGuardrailsList(response.guardrails); - } catch (error) { - console.error("Error fetching guardrails:", error); - } finally { - setIsLoading(false); - } - }; - - useEffect(() => { - fetchGuardrails(); - }, [accessToken]); - - const handleAddGuardrail = () => { - if (selectedGuardrailId) { - setSelectedGuardrailId(null); - } - setIsAddModalVisible(true); - }; - - const handleAddCustomCodeGuardrail = () => { - if (selectedGuardrailId) { - setSelectedGuardrailId(null); - } - setIsCustomCodeModalVisible(true); - }; - - const handleCloseModal = () => { - setIsAddModalVisible(false); - }; - - const handleCloseCustomCodeModal = () => { - setIsCustomCodeModalVisible(false); - }; - - const handleSuccess = () => { - fetchGuardrails(); - }; - - const handleDeleteClick = (guardrailId: string, guardrailName: string) => { - const guardrail = guardrailsList.find((g) => g.guardrail_id === guardrailId) || null; - setGuardrailToDelete(guardrail); - setIsDeleteModalOpen(true); - }; - - const handleDeleteConfirm = async () => { - if (!guardrailToDelete || !accessToken) return; - - setIsDeleting(true); - try { - await deleteGuardrailCall(accessToken, guardrailToDelete.guardrail_id); - NotificationsManager.success(`Guardrail "${guardrailToDelete.guardrail_name}" deleted successfully`); - await fetchGuardrails(); - } catch (error) { - console.error("Error deleting guardrail:", error); - NotificationsManager.fromBackend("Failed to delete guardrail"); - } finally { - setIsDeleting(false); - setIsDeleteModalOpen(false); - setGuardrailToDelete(null); - } - }; - - const handleDeleteCancel = () => { - setIsDeleteModalOpen(false); - setGuardrailToDelete(null); - }; - - const providerDisplayName = - guardrailToDelete && guardrailToDelete.litellm_params - ? getGuardrailLogoAndName(guardrailToDelete.litellm_params.guardrail).displayName - : undefined; - +const GuardrailsPanel: React.FC = ({ accessToken }) => { return (
- - - Guardrail Garden - Guardrails - Test Playground - - - - {/* Guardrail Garden Tab */} - - - - - {/* Existing Guardrails Tab */} - -
- , - label: "Add Provider Guardrail", - onClick: handleAddGuardrail, - }, - { - key: "custom_code", - icon: , - label: "Create Custom Code Guardrail", - onClick: handleAddCustomCodeGuardrail, - }, - ], - }} - trigger={["click"]} - disabled={!accessToken} - > - - -
- - {selectedGuardrailId ? ( - setSelectedGuardrailId(null)} - accessToken={accessToken} - isAdmin={isAdmin} - /> - ) : ( - setSelectedGuardrailId(id)} - /> - )} - - - - - - -
- - {/* Test Playground Tab */} - - setActiveTab(0)} - /> - -
-
+
); }; diff --git a/ui/litellm-dashboard/src/components/guardrails/GuardrailGardenTab.tsx b/ui/litellm-dashboard/src/components/guardrails/GuardrailGardenTab.tsx new file mode 100644 index 00000000000..58879db6ece --- /dev/null +++ b/ui/litellm-dashboard/src/components/guardrails/GuardrailGardenTab.tsx @@ -0,0 +1,177 @@ +"use client"; + +import React, { useState } from "react"; +import { + SearchIcon, + ArrowRightIcon, + CheckCircleIcon, + ShieldIcon, +} from "lucide-react"; + +const MOCK_GARDEN_CARDS = [ + { + title: "Denied Financial Advice", + description: + "Detects requests for personalized financial advice, investment recommendations, or financial...", + f1Score: "100%", + testCases: 207, + }, + { + title: "Insults & Personal Attacks", + description: + "Detects insults, name-calling, and personal attacks directed at the chatbot, staff, or other people.", + f1Score: "100%", + testCases: 299, + }, + { + title: "Denied Legal Advice", + description: + "Detects requests for unauthorized legal advice, case analysis, or legal recommendations.", + }, + { + title: "Denied Medical Advice", + description: + "Detects requests for medical diagnosis, treatment recommendations, or health advice.", + }, + { + title: "Harmful Violence", + description: + "Detects content related to violence, criminal planning, attacks, and violent threats.", + }, + { + title: "Harmful Self-Harm", + description: + "Detects content related to self-harm, suicide, and dangerous self-destructive behavior.", + }, + { + title: "Harmful Child Safety", + description: + "Detects content that could endanger child safety or exploit minors.", + }, + { + title: "Harmful Illegal Weapons", + description: + "Detects content related to illegal weapons manufacturing, distribution, or acquisition.", + }, + { + title: "Bias: Gender", + description: + "Detects gender-based discrimination, stereotypes, and biased language.", + }, + { + title: "Bias: Racial", + description: + "Detects racial discrimination, stereotypes, and racially biased content.", + }, +]; + +type GuardrailCardProps = { + title: string; + description: string; + f1Score?: string; + testCases?: number; +}; + +function GuardrailCard({ + title, + description, + f1Score, + testCases, +}: GuardrailCardProps) { + return ( +
+
+
+ +
+

+ {title} +

+
+

+ {description} +

+ {f1Score && testCases !== undefined && ( +
+ + + F1: {f1Score} ยท {testCases} test cases + +
+ )} +
+ ); +} + +export function GuardrailGardenTab() { + const [search, setSearch] = useState(""); + + const filteredCards = MOCK_GARDEN_CARDS.filter( + (card) => + !search || + card.title.toLowerCase().includes(search.toLowerCase()) || + card.description.toLowerCase().includes(search.toLowerCase()) + ); + + return ( +
+ {/* Search */} +
+ + setSearch(e.target.value)} + className="w-full pl-9 pr-4 py-2 border border-gray-200 rounded-md text-sm text-gray-700 placeholder-gray-400 focus:outline-none focus:ring-1 focus:ring-blue-500 focus:border-blue-500" + /> +
+ + {/* LiteLLM Content Filter Section */} +
+
+

+ LiteLLM Content Filter +

+ +
+

+ Built-in guardrails powered by LiteLLM. Zero latency, no external + dependencies, no additional cost. +

+ + {/* Row 1 */} +
+ {filteredCards.slice(0, 6).map((card) => ( + + ))} +
+ + {/* Row 2 */} +
+ {filteredCards.slice(6, 10).map((card) => ( + + ))} +
+
+
+ ); +} diff --git a/ui/litellm-dashboard/src/components/guardrails/GuardrailsListTab.tsx b/ui/litellm-dashboard/src/components/guardrails/GuardrailsListTab.tsx new file mode 100644 index 00000000000..571ffde3f3d --- /dev/null +++ b/ui/litellm-dashboard/src/components/guardrails/GuardrailsListTab.tsx @@ -0,0 +1,81 @@ +"use client"; + +import React from "react"; + +const MOCK_GUARDRAILS = [ + { + name: "Denied Financial Advice", + type: "LiteLLM Built-in", + status: "Active" as const, + appliedTo: "All routes", + }, + { + name: "Insults & Personal Attacks", + type: "LiteLLM Built-in", + status: "Active" as const, + appliedTo: "Customer-facing", + }, + { + name: "Prompt Injection Detector", + type: "Team Custom", + status: "Active" as const, + appliedTo: "ML Platform team", + }, +]; + +export function GuardrailsListTab() { + return ( +
+
+

Guardrails

+

+ Configure and manage active guardrails for your AI gateway. +

+
+
+ + + + + + + + + + + {MOCK_GUARDRAILS.map((row, i) => ( + + + + + + + ))} + +
+ Name + + Type + + Status + + Applied To +
+ {row.name} + {row.type} + + + {row.status} + + {row.appliedTo}
+
+
+ ); +} diff --git a/ui/litellm-dashboard/src/components/guardrails/GuardrailsPage.tsx b/ui/litellm-dashboard/src/components/guardrails/GuardrailsPage.tsx new file mode 100644 index 00000000000..c912ce4f9aa --- /dev/null +++ b/ui/litellm-dashboard/src/components/guardrails/GuardrailsPage.tsx @@ -0,0 +1,62 @@ +"use client"; + +import React, { useState } from "react"; +import { GuardrailGardenTab } from "./GuardrailGardenTab"; +import { GuardrailsListTab } from "./GuardrailsListTab"; +import { PlaygroundTab } from "./PlaygroundTab"; +import { TeamGuardrailsTab } from "./TeamGuardrailsTab"; + +type Tab = "garden" | "guardrails" | "playground" | "team"; + +const TABS: { id: Tab; label: string }[] = [ + { id: "garden", label: "Guardrail Garden" }, + { id: "guardrails", label: "Guardrails" }, + { id: "playground", label: "Test Playground" }, + { id: "team", label: "Team Guardrails" }, +]; + +interface GuardrailsPageProps { + accessToken?: string | null; +} + +export function GuardrailsPage({ accessToken }: GuardrailsPageProps) { + const [activeTab, setActiveTab] = useState("garden"); + + return ( +
+ {/* Tab bar */} +
+
+ {TABS.map((tab) => ( + + ))} +
+
+ + {/* Tab content */} +
+ {activeTab === "garden" && } + {activeTab === "guardrails" && } + {activeTab === "playground" && } + {activeTab === "team" && } +
+
+ ); +} diff --git a/ui/litellm-dashboard/src/components/guardrails/PlaygroundTab.tsx b/ui/litellm-dashboard/src/components/guardrails/PlaygroundTab.tsx new file mode 100644 index 00000000000..384a20983fd --- /dev/null +++ b/ui/litellm-dashboard/src/components/guardrails/PlaygroundTab.tsx @@ -0,0 +1,63 @@ +"use client"; + +import React, { useState } from "react"; + +export function PlaygroundTab() { + const [prompt, setPrompt] = useState(""); + const [result, setResult] = useState(null); + + function handleTest() { + setResult( + prompt.toLowerCase().includes("financial") + ? "๐Ÿšซ Blocked by: Denied Financial Advice guardrail (confidence: 97%)" + : "โœ… Passed all guardrails. Safe to proceed." + ); + } + + return ( +
+
+

+ Test Playground +

+

+ Test your guardrails against sample prompts to verify they work as + expected. +

+
+
+
+ +