[Feat UI] Add Guardrails on LiteLLM UI (#10834)

* feat: add DB Add, Edit, Delete for Guardrails

* feat: endpoints for guardrail management

* add guardrail info helpers

* add presidio logo

* add createGuardrailCall

* basic add guardrails on UI

* fix cleanup form

* feat: working add guardrails

* ui view guardrails

* allow viweing list of guardrails

* fix: show created_at and updated_at for guardrails on UI

* cleanup guardrail view

* use 1 file for guardrail table

* docs adding guardrails

* fix: expose endpoint to show allowed modes, entities, actions

* fix allow adding entity configs for PII

* fixes guardrails

* fix adding guardrails on UI

* feat: add PII_ENTITY_CATEGORIES_MAP

* use categories for pii

* use step based form

* fixes pii configs

* fix pii config

* refactor pii config on ui

* clear form on submit

* add ability to create guardrails

* fix linting error
This commit is contained in:
Ishaan Jaff 2025-05-14 14:21:35 -07:00 • committed by GitHub
parent ee1557afcd
commit b9c3a3835c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 2121 additions and 180 deletions

View file

@ -4038,25 +4038,24 @@
"supports_prompt_caching": true
},
"groq/deepseek-r1-distill-llama-70b": {
"max_tokens": 131072,
"max_input_tokens": 131072,
"max_output_tokens": 131072,
"input_cost_per_token": 0.00000075,
"output_cost_per_token": 0.00000099,
"max_tokens": 128000,
"max_input_tokens": 128000,
"max_output_tokens": 128000,
"input_cost_per_token": 7.5e-07,
"output_cost_per_token": 9.9e-07,
"litellm_provider": "groq",
"mode": "chat",
"supports_system_messages": false,
"supports_function_calling": false,
"supports_function_calling": true,
"supports_response_schema": true,
"supports_reasoning": true,
"supports_response_schema": false,
"supports_tool_choice": true
},
"groq/llama-3.3-70b-versatile": {
"max_tokens": 8192,
"max_tokens": 32768,
"max_input_tokens": 128000,
"max_output_tokens": 8192,
"input_cost_per_token": 0.00000059,
"output_cost_per_token": 0.00000079,
"max_output_tokens": 32768,
"input_cost_per_token": 5.9e-07,
"output_cost_per_token": 7.9e-07,
"litellm_provider": "groq",
"mode": "chat",
"supports_function_calling": true,
@ -4067,11 +4066,21 @@
"max_tokens": 8192,
"max_input_tokens": 8192,
"max_output_tokens": 8192,
"input_cost_per_token": 0.00000059,
"output_cost_per_token": 0.00000099,
"input_cost_per_token": 5.9e-07,
"output_cost_per_token": 9.9e-07,
"litellm_provider": "groq",
"mode": "chat",
"supports_tool_choice": true
"supports_tool_choice": true,
"deprecation_date": "2025-04-14"
},
"groq/llama-guard-3-8b": {
"max_tokens": 8192,
"max_input_tokens": 8192,
"max_output_tokens": 8192,
"input_cost_per_token": 2e-07,
"output_cost_per_token": 2e-07,
"litellm_provider": "groq",
"mode": "chat"
},
"groq/llama2-70b-4096": {
"max_tokens": 4096,
@ -4089,106 +4098,106 @@
"max_tokens": 8192,
"max_input_tokens": 8192,
"max_output_tokens": 8192,
"input_cost_per_token": 0.00000005,
"output_cost_per_token": 0.00000008,
"input_cost_per_token": 5e-08,
"output_cost_per_token": 8e-08,
"litellm_provider": "groq",
"mode": "chat",
"supports_function_calling": true,
"supports_response_schema": true,
"supports_tool_choice": true
"mode": "chat"
},
"groq/llama-3.2-1b-preview": {
"max_tokens": 8192,
"max_input_tokens": 8192,
"max_output_tokens": 8192,
"input_cost_per_token": 0.00000004,
"output_cost_per_token": 0.00000004,
"input_cost_per_token": 4e-08,
"output_cost_per_token": 4e-08,
"litellm_provider": "groq",
"mode": "chat",
"supports_function_calling": true,
"supports_response_schema": true,
"supports_tool_choice": true
"supports_tool_choice": true,
"deprecation_date": "2025-04-14"
},
"groq/llama-3.2-3b-preview": {
"max_tokens": 8192,
"max_input_tokens": 8192,
"max_output_tokens": 8192,
"input_cost_per_token": 0.00000006,
"output_cost_per_token": 0.00000006,
"input_cost_per_token": 6e-08,
"output_cost_per_token": 6e-08,
"litellm_provider": "groq",
"mode": "chat",
"supports_function_calling": true,
"supports_response_schema": true,
"supports_tool_choice": true
"supports_tool_choice": true,
"deprecation_date": "2025-04-14"
},
"groq/llama-3.2-11b-text-preview": {
"max_tokens": 8192,
"max_input_tokens": 8192,
"max_output_tokens": 8192,
"input_cost_per_token": 0.00000018,
"output_cost_per_token": 0.00000018,
"input_cost_per_token": 1.8e-07,
"output_cost_per_token": 1.8e-07,
"litellm_provider": "groq",
"mode": "chat",
"supports_function_calling": true,
"supports_response_schema": true,
"supports_tool_choice": true
"supports_tool_choice": true,
"deprecation_date": "2024-10-28"
},
"groq/llama-3.2-11b-vision-preview": {
"max_tokens": 8192,
"max_input_tokens": 8192,
"max_output_tokens": 8192,
"input_cost_per_token": 0.00000018,
"output_cost_per_token": 0.00000018,
"input_cost_per_token": 1.8e-07,
"output_cost_per_token": 1.8e-07,
"litellm_provider": "groq",
"mode": "chat",
"supports_function_calling": true,
"supports_response_schema": true,
"supports_vision": true,
"supports_tool_choice": true
"supports_tool_choice": true,
"deprecation_date": "2025-04-14"
},
"groq/llama-3.2-90b-text-preview": {
"max_tokens": 8192,
"max_input_tokens": 8192,
"max_output_tokens": 8192,
"input_cost_per_token": 0.0000009,
"output_cost_per_token": 0.0000009,
"input_cost_per_token": 9e-07,
"output_cost_per_token": 9e-07,
"litellm_provider": "groq",
"mode": "chat",
"supports_function_calling": true,
"supports_response_schema": true,
"supports_tool_choice": true
"supports_tool_choice": true,
"deprecation_date": "2024-11-25"
},
"groq/llama-3.2-90b-vision-preview": {
"max_tokens": 8192,
"max_input_tokens": 8192,
"max_output_tokens": 8192,
"input_cost_per_token": 0.0000009,
"output_cost_per_token": 0.0000009,
"input_cost_per_token": 9e-07,
"output_cost_per_token": 9e-07,
"litellm_provider": "groq",
"mode": "chat",
"supports_function_calling": true,
"supports_response_schema": true,
"supports_vision": true,
"supports_tool_choice": true
"supports_tool_choice": true,
"deprecation_date": "2025-04-14"
},
"groq/llama3-70b-8192": {
"max_tokens": 8192,
"max_input_tokens": 8192,
"max_output_tokens": 8192,
"input_cost_per_token": 0.00000059,
"output_cost_per_token": 0.00000079,
"input_cost_per_token": 5.9e-07,
"output_cost_per_token": 7.9e-07,
"litellm_provider": "groq",
"mode": "chat",
"supports_function_calling": true,
"supports_response_schema": true,
"supports_tool_choice": true
"mode": "chat"
},
"groq/llama-3.1-8b-instant": {
"max_tokens": 8192,
"max_input_tokens": 8192,
"max_input_tokens": 128000,
"max_output_tokens": 8192,
"input_cost_per_token": 0.00000005,
"output_cost_per_token": 0.00000008,
"input_cost_per_token": 5e-08,
"output_cost_per_token": 8e-08,
"litellm_provider": "groq",
"mode": "chat",
"supports_function_calling": true,
@ -4199,13 +4208,14 @@
"max_tokens": 8192,
"max_input_tokens": 8192,
"max_output_tokens": 8192,
"input_cost_per_token": 0.00000059,
"output_cost_per_token": 0.00000079,
"input_cost_per_token": 5.9e-07,
"output_cost_per_token": 7.9e-07,
"litellm_provider": "groq",
"mode": "chat",
"supports_function_calling": true,
"supports_response_schema": true,
"supports_tool_choice": true
"supports_tool_choice": true,
"deprecation_date": "2025-01-24"
},
"groq/llama-3.1-405b-reasoning": {
"max_tokens": 8192,
@ -4219,83 +4229,141 @@
"supports_response_schema": true,
"supports_tool_choice": true
},
"groq/mixtral-8x7b-32768": {
"max_tokens": 32768,
"max_input_tokens": 32768,
"max_output_tokens": 32768,
"input_cost_per_token": 0.00000024,
"output_cost_per_token": 0.00000024,
"groq/meta-llama/llama-4-scout-17b-16e-instruct": {
"max_tokens": 8192,
"max_input_tokens": 131072,
"max_output_tokens": 8192,
"input_cost_per_token": 1.1e-07,
"output_cost_per_token": 3.4e-07,
"litellm_provider": "groq",
"mode": "chat",
"supports_function_calling": true,
"supports_response_schema": true,
"supports_tool_choice": true
},
"groq/meta-llama/llama-4-maverick-17b-128e-instruct": {
"max_tokens": 8192,
"max_input_tokens": 131072,
"max_output_tokens": 8192,
"input_cost_per_token": 2e-07,
"output_cost_per_token": 6e-07,
"litellm_provider": "groq",
"mode": "chat",
"supports_function_calling": true,
"supports_response_schema": true,
"supports_tool_choice": true
},
"groq/mistral-saba-24b": {
"max_tokens": 32000,
"max_input_tokens": 32000,
"max_output_tokens": 32000,
"input_cost_per_token": 7.9e-07,
"output_cost_per_token": 7.9e-07,
"litellm_provider": "groq",
"mode": "chat"
},
"groq/mixtral-8x7b-32768": {
"max_tokens": 32768,
"max_input_tokens": 32768,
"max_output_tokens": 32768,
"input_cost_per_token": 2.4e-07,
"output_cost_per_token": 2.4e-07,
"litellm_provider": "groq",
"mode": "chat",
"supports_function_calling": true,
"supports_response_schema": true,
"supports_tool_choice": true,
"deprecation_date": "2025-03-20"
},
"groq/gemma-7b-it": {
"max_tokens": 8192,
"max_input_tokens": 8192,
"max_output_tokens": 8192,
"input_cost_per_token": 0.00000007,
"output_cost_per_token": 0.00000007,
"input_cost_per_token": 7e-08,
"output_cost_per_token": 7e-08,
"litellm_provider": "groq",
"mode": "chat",
"supports_function_calling": true,
"supports_response_schema": true,
"supports_tool_choice": true
"supports_tool_choice": true,
"deprecation_date": "2024-12-18"
},
"groq/gemma2-9b-it": {
"max_tokens": 8192,
"max_input_tokens": 8192,
"max_output_tokens": 8192,
"input_cost_per_token": 0.00000020,
"output_cost_per_token": 0.00000020,
"input_cost_per_token": 2e-07,
"output_cost_per_token": 2e-07,
"litellm_provider": "groq",
"mode": "chat",
"supports_function_calling": true,
"supports_function_calling": false,
"supports_response_schema": true,
"supports_tool_choice": true
"supports_tool_choice": false
},
"groq/llama3-groq-70b-8192-tool-use-preview": {
"max_tokens": 8192,
"max_input_tokens": 8192,
"max_output_tokens": 8192,
"input_cost_per_token": 0.00000089,
"output_cost_per_token": 0.00000089,
"input_cost_per_token": 8.9e-07,
"output_cost_per_token": 8.9e-07,
"litellm_provider": "groq",
"mode": "chat",
"supports_function_calling": true,
"supports_response_schema": true,
"supports_tool_choice": true
"supports_tool_choice": true,
"deprecation_date": "2025-1-6"
},
"groq/llama3-groq-8b-8192-tool-use-preview": {
"max_tokens": 8192,
"max_input_tokens": 8192,
"max_output_tokens": 8192,
"input_cost_per_token": 0.00000019,
"output_cost_per_token": 0.00000019,
"input_cost_per_token": 1.9e-07,
"output_cost_per_token": 1.9e-07,
"litellm_provider": "groq",
"mode": "chat",
"supports_function_calling": true,
"supports_response_schema": true,
"supports_tool_choice": true,
"deprecation_date": "2025-1-6"
},
"groq/qwen-qwq-32b": {
"max_tokens": 128000,
"max_input_tokens": 128000,
"max_output_tokens": 128000,
"input_cost_per_token": 2.9e-07,
"output_cost_per_token": 3.9e-07,
"litellm_provider": "groq",
"mode": "chat",
"supports_function_calling": true,
"supports_response_schema": true,
"supports_reasoning": true,
"supports_tool_choice": true
},
"groq/playai-tts": {
"max_tokens": 10000,
"max_input_tokens": 10000,
"max_output_tokens": 10000,
"input_cost_per_character": 5e-05,
"litellm_provider": "groq",
"mode": "audio_speech"
},
"groq/whisper-large-v3": {
"mode": "audio_transcription",
"input_cost_per_second": 0.00003083,
"output_cost_per_second": 0,
"litellm_provider": "groq"
"input_cost_per_second": 3.083e-05,
"output_cost_per_second": 0.0,
"litellm_provider": "groq",
"mode": "audio_transcription"
},
"groq/whisper-large-v3-turbo": {
"mode": "audio_transcription",
"input_cost_per_second": 0.00001111,
"output_cost_per_second": 0,
"litellm_provider": "groq"
"input_cost_per_second": 1.111e-05,
"output_cost_per_second": 0.0,
"litellm_provider": "groq",
"mode": "audio_transcription"
},
"groq/distil-whisper-large-v3-en": {
"mode": "audio_transcription",
"input_cost_per_second": 0.00000556,
"output_cost_per_second": 0,
"litellm_provider": "groq"
"input_cost_per_second": 5.56e-06,
"output_cost_per_second": 0.0,
"litellm_provider": "groq",
"mode": "audio_transcription"
},
"cerebras/llama3.1-8b": {
"max_tokens": 128000,

View file

@ -7,12 +7,18 @@ from typing import Dict, List, Optional, cast
from fastapi import APIRouter, Depends, HTTPException
from pydantic import BaseModel
from litellm._logging import verbose_proxy_logger
from litellm.proxy.auth.user_api_key_auth import user_api_key_auth
from litellm.proxy.guardrails.guardrail_registry import GuardrailRegistry
from litellm.types.guardrails import (
PII_ENTITY_CATEGORIES_MAP,
Guardrail,
GuardrailEventHooks,
GuardrailInfoResponse,
GuardrailUIAddGuardrailSettings,
ListGuardrailsResponse,
PiiAction,
PiiEntityType,
)
#### GUARDRAILS ENDPOINTS ####
@ -92,6 +98,73 @@ async def list_guardrails():
return _get_guardrails_list_response(_guardrails_config)
@router.get(
"/v2/guardrails/list",
tags=["Guardrails"],
dependencies=[Depends(user_api_key_auth)],
response_model=ListGuardrailsResponse,
)
async def list_guardrails_v2():
"""
List the guardrails that are available in the database using GuardrailRegistry
👉 [Guardrail docs](https://docs.litellm.ai/docs/proxy/guardrails/quick_start)
Example Request:
```bash
curl -X GET "http://localhost:4000/v2/guardrails/list" -H "Authorization: Bearer <your_api_key>"
```
Example Response:
```json
{
"guardrails": [
{
"guardrail_id": "123e4567-e89b-12d3-a456-426614174000",
"guardrail_name": "my-bedrock-guard",
"litellm_params": {
"guardrail": "bedrock",
"mode": "pre_call",
"guardrailIdentifier": "ff6ujrregl1q",
"guardrailVersion": "DRAFT",
"default_on": true
},
"guardrail_info": {
"description": "Bedrock content moderation guardrail"
}
}
]
}
```
"""
from litellm.proxy.proxy_server import prisma_client
if prisma_client is None:
raise HTTPException(status_code=500, detail="Prisma client not initialized")
try:
guardrails = await GUARDRAIL_REGISTRY.get_all_guardrails_from_db(
prisma_client=prisma_client
)
guardrail_configs: List[GuardrailInfoResponse] = []
for guardrail in guardrails:
guardrail_configs.append(
GuardrailInfoResponse(
guardrail_name=guardrail.get("guardrail_name"),
litellm_params=guardrail.get("litellm_params"),
guardrail_info=guardrail.get("guardrail_info"),
created_at=guardrail.get("created_at"),
updated_at=guardrail.get("updated_at"),
)
)
return ListGuardrailsResponse(guardrails=guardrail_configs)
except Exception as e:
verbose_proxy_logger.exception(f"Error getting guardrails from db: {e}")
raise HTTPException(status_code=500, detail=str(e))
class CreateGuardrailRequest(BaseModel):
guardrail: Guardrail
@ -160,6 +233,7 @@ async def create_guardrail(request: CreateGuardrailRequest):
)
return result
except Exception as e:
verbose_proxy_logger.exception(f"Error adding guardrail to db: {e}")
raise HTTPException(status_code=500, detail=str(e))
@ -353,3 +427,30 @@ async def delete_guardrail(guardrail_id: str):
raise e
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.get(
"/guardrails/ui/add_guardrail_settings",
tags=["Guardrails"],
dependencies=[Depends(user_api_key_auth)],
)
async def get_guardrail_ui_settings():
"""
Get the UI settings for the guardrails
Returns:
- Supported entities for guardrails
- Supported modes for guardrails
- PII entity categories for UI organization
"""
# 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, "entities": entities})
return GuardrailUIAddGuardrailSettings(
supported_entities=list(PiiEntityType),
supported_actions=list(PiiAction),
supported_modes=list(GuardrailEventHooks),
pii_entity_categories=category_maps,
)

View file

@ -3,6 +3,7 @@
from datetime import datetime, timezone
from typing import List, Optional
from litellm.litellm_core_utils.safe_json_dumps import safe_dumps
from litellm.proxy.utils import PrismaClient
from litellm.types.guardrails import Guardrail, SupportedGuardrailIntegrations
@ -48,8 +49,8 @@ class GuardrailRegistry:
"""
try:
guardrail_name = guardrail.get("guardrail_name")
litellm_params = guardrail.get("litellm_params", {})
guardrail_info = guardrail.get("guardrail_info", {})
litellm_params: str = safe_dumps(guardrail.get("litellm_params", {}))
guardrail_info: str = safe_dumps(guardrail.get("guardrail_info", {}))
# Create guardrail in DB
created_guardrail = await prisma_client.db.litellm_guardrailstable.create(

View file

@ -1,3 +1,4 @@
from datetime import datetime
from enum import Enum
from typing import Any, Dict, List, Literal, Optional, TypedDict, Union
@ -85,6 +86,20 @@ class PiiAction(str, Enum):
MASK = "MASK"
class PiiEntityCategory(str, Enum):
GENERAL = "General"
FINANCE = "Finance"
USA = "USA"
UK = "UK"
SPAIN = "Spain"
ITALY = "Italy"
POLAND = "Poland"
SINGAPORE = "Singapore"
AUSTRALIA = "Australia"
INDIA = "India"
FINLAND = "Finland"
class PiiEntityType(str, Enum):
# General
CREDIT_CARD = "CREDIT_CARD"
@ -137,7 +152,65 @@ class PiiEntityType(str, Enum):
FI_PERSONAL_IDENTITY_CODE = "FI_PERSONAL_IDENTITY_CODE"
class LitellmParams(TypedDict):
# Define mappings of PII entity types by category
PII_ENTITY_CATEGORIES_MAP = {
PiiEntityCategory.GENERAL: [
PiiEntityType.DATE_TIME,
PiiEntityType.EMAIL_ADDRESS,
PiiEntityType.IP_ADDRESS,
PiiEntityType.NRP,
PiiEntityType.LOCATION,
PiiEntityType.PERSON,
PiiEntityType.PHONE_NUMBER,
PiiEntityType.MEDICAL_LICENSE,
PiiEntityType.URL,
],
PiiEntityCategory.FINANCE: [
PiiEntityType.CREDIT_CARD,
PiiEntityType.CRYPTO,
PiiEntityType.IBAN_CODE,
],
PiiEntityCategory.USA: [
PiiEntityType.US_BANK_NUMBER,
PiiEntityType.US_DRIVER_LICENSE,
PiiEntityType.US_ITIN,
PiiEntityType.US_PASSPORT,
PiiEntityType.US_SSN,
],
PiiEntityCategory.UK: [PiiEntityType.UK_NHS, PiiEntityType.UK_NINO],
PiiEntityCategory.SPAIN: [PiiEntityType.ES_NIF, PiiEntityType.ES_NIE],
PiiEntityCategory.ITALY: [
PiiEntityType.IT_FISCAL_CODE,
PiiEntityType.IT_DRIVER_LICENSE,
PiiEntityType.IT_VAT_CODE,
PiiEntityType.IT_PASSPORT,
PiiEntityType.IT_IDENTITY_CARD,
],
PiiEntityCategory.POLAND: [PiiEntityType.PL_PESEL],
PiiEntityCategory.SINGAPORE: [PiiEntityType.SG_NRIC_FIN, PiiEntityType.SG_UEN],
PiiEntityCategory.AUSTRALIA: [
PiiEntityType.AU_ABN,
PiiEntityType.AU_ACN,
PiiEntityType.AU_TFN,
PiiEntityType.AU_MEDICARE,
],
PiiEntityCategory.INDIA: [
PiiEntityType.IN_PAN,
PiiEntityType.IN_AADHAAR,
PiiEntityType.IN_VEHICLE_REGISTRATION,
PiiEntityType.IN_VOTER,
PiiEntityType.IN_PASSPORT,
],
PiiEntityCategory.FINLAND: [PiiEntityType.FI_PERSONAL_IDENTITY_CODE],
}
class PiiEntityCategoryMap(TypedDict):
category: PiiEntityCategory
entities: List[PiiEntityType]
class LitellmParams(TypedDict, total=False):
guardrail: str
mode: str
api_key: Optional[str]
@ -178,6 +251,8 @@ class Guardrail(TypedDict, total=False):
guardrail_name: str
litellm_params: LitellmParams
guardrail_info: Optional[Dict]
created_at: Optional[datetime]
updated_at: Optional[datetime]
class guardrailConfig(TypedDict):
@ -214,6 +289,8 @@ class GuardrailInfoResponse(BaseModel):
guardrail_name: str
litellm_params: GuardrailLiteLLMParamsResponse
guardrail_info: Optional[Dict]
created_at: Optional[datetime]
updated_at: Optional[datetime]
def __init__(self, **kwargs):
super().__init__(**kwargs)
@ -221,3 +298,10 @@ class GuardrailInfoResponse(BaseModel):
class ListGuardrailsResponse(BaseModel):
guardrails: List[GuardrailInfoResponse]
class GuardrailUIAddGuardrailSettings(BaseModel):
supported_entities: List[PiiEntityType]
supported_actions: List[PiiAction]
supported_modes: List[GuardrailEventHooks]
pii_entity_categories: List[PiiEntityCategoryMap]

View file

@ -1,104 +1,140 @@
import React, { useState, useEffect } from "react";
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeaderCell,
TableRow,
Card,
Text,
Button,
Icon,
TextInput,
} from "@tremor/react";
import { getGuardrailsList } from "./networking";
import {
PlusIcon,
} from "@heroicons/react/outline";
import { Modal, message } from "antd";
import { getGuardrailsList, deleteGuardrailCall } from "./networking";
import AddGuardrailForm from "./guardrails/add_guardrail_form";
import GuardrailTable from "./guardrails/guardrail_table";
interface GuardrailsPanelProps {
accessToken: string | null;
}
interface GuardrailsResponse {
guardrails: GuardrailItem[];
interface GuardrailItem {
guardrail_id?: string;
guardrail_name: string | null;
litellm_params: {
guardrail: string;
mode: string;
default_on: boolean;
};
guardrail_info: Record<string, any> | null;
created_at?: string;
updated_at?: string;
}
interface GuardrailItem {
guardrail_name: string | null;
litellm_params: {
guardrail: string;
mode: string;
default_on: boolean;
};
guardrail_info: Record<string, any> | null;
interface GuardrailsResponse {
guardrails: GuardrailItem[];
}
const GuardrailsPanel: React.FC<GuardrailsPanelProps> = ({ accessToken }) => {
const [guardrailsList, setGuardrailsList] = useState<GuardrailItem[]>([]);
const [isAddModalVisible, setIsAddModalVisible] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const [isDeleting, setIsDeleting] = useState(false);
const [guardrailToDelete, setGuardrailToDelete] = useState<{id: string, name: string} | null>(null);
useEffect(() => {
const fetchGuardrails = async () => {
if (!accessToken) {
return;
}
const fetchGuardrails = async () => {
try {
const response: GuardrailsResponse = await getGuardrailsList(accessToken);
console.log(`guardrails: ${JSON.stringify(response)}`);
setGuardrailsList(response.guardrails);
} catch (error) {
console.error('Error fetching guardrails:', error);
}
};
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 = () => {
setIsAddModalVisible(true);
};
const handleCloseModal = () => {
setIsAddModalVisible(false);
};
const handleSuccess = () => {
fetchGuardrails();
};
const handleDeleteClick = (guardrailId: string, guardrailName: string) => {
setGuardrailToDelete({id: guardrailId, name: guardrailName});
};
const handleDeleteConfirm = async () => {
if (!guardrailToDelete || !accessToken) return;
setIsDeleting(true);
try {
await deleteGuardrailCall(accessToken, guardrailToDelete.id);
message.success(`Guardrail "${guardrailToDelete.name}" deleted successfully`);
fetchGuardrails(); // Refresh the list
} catch (error) {
console.error('Error deleting guardrail:', error);
message.error('Failed to delete guardrail');
} finally {
setIsDeleting(false);
setGuardrailToDelete(null);
}
};
const handleDeleteCancel = () => {
setGuardrailToDelete(null);
};
return (
<div className="w-full mx-auto flex-auto overflow-y-auto m-8 p-2">
<Text className="mb-4">
Configured guardrails and their current status. Setup guardrails in config.yaml.{" "}
<a
href="https://docs.litellm.ai/docs/proxy/guardrails/quick_start"
target="_blank"
rel="noopener noreferrer"
className="text-blue-500 hover:text-blue-700 underline"
<div className="flex justify-between items-center mb-4">
<Button
icon={PlusIcon}
onClick={handleAddGuardrail}
disabled={!accessToken}
>
Docs
</a>
</Text>
<Card>
<Table>
<TableHead>
<TableRow>
<TableHeaderCell>Guardrail Name</TableHeaderCell>
<TableHeaderCell>Mode</TableHeaderCell>
<TableHeaderCell>Status</TableHeaderCell>
</TableRow>
</TableHead>
Add Guardrail
</Button>
</div>
<GuardrailTable
guardrailsList={guardrailsList}
isLoading={isLoading}
onDeleteClick={handleDeleteClick}
/>
<TableBody>
{ (!guardrailsList || guardrailsList.length === 0) ? (
<TableRow>
<TableCell colSpan={3} className="mt-4 text-gray-500 text-center py-4">No guardrails configured</TableCell>
</TableRow>
) : (
guardrailsList?.map((guardrail: GuardrailItem, index: number) => (
<TableRow key={index}>
<TableCell>{guardrail.guardrail_name}</TableCell>
<TableCell>{guardrail.litellm_params.mode}</TableCell>
<TableCell>
<div className={`inline-flex rounded-full px-2 py-1 text-xs font-medium
${guardrail.litellm_params.default_on
? 'bg-green-100 text-green-800' // Always On styling
: 'bg-gray-100 text-gray-800' // Per Request styling
}`}>
{guardrail.litellm_params.default_on ? 'Always On' : 'Per Request'}
</div>
</TableCell>
</TableRow>
))
)}
</TableBody>
</Table>
</Card>
<AddGuardrailForm
visible={isAddModalVisible}
onClose={handleCloseModal}
accessToken={accessToken}
onSuccess={handleSuccess}
/>
<Modal
title="Delete Guardrail"
open={guardrailToDelete !== null}
onOk={handleDeleteConfirm}
onCancel={handleDeleteCancel}
confirmLoading={isDeleting}
okText="Delete"
okButtonProps={{ danger: true }}
>
</Modal>
</div>
);
};

View file

@ -0,0 +1,504 @@
import React, { useState, useEffect } from 'react';
import { Card, Form, Typography, Select, Input, Switch, Tooltip, Modal, message, Divider, Space, Tag, Image, Steps } from 'antd';
import { Button, TextInput } from '@tremor/react';
import type { FormInstance } from 'antd';
import { GuardrailProviders, guardrail_provider_map, provider_specific_fields, guardrailLogoMap } from './guardrail_info_helpers';
import { createGuardrailCall, getGuardrailUISettings } from '../networking';
import PiiConfiguration from './pii_configuration';
const { Title, Text, Link } = Typography;
const { Option } = Select;
const { Step } = Steps;
interface AddGuardrailFormProps {
visible: boolean;
onClose: () => void;
accessToken: string | null;
onSuccess: () => void;
}
interface GuardrailSettings {
supported_entities: string[];
supported_actions: string[];
supported_modes: string[];
pii_entity_categories: Array<{
category: string;
entities: string[];
}>;
}
interface LiteLLMParams {
guardrail: string;
mode: string;
default_on: boolean;
[key: string]: any; // Allow additional properties for specific guardrails
}
const AddGuardrailForm: React.FC<AddGuardrailFormProps> = ({
visible,
onClose,
accessToken,
onSuccess
}) => {
const [form] = Form.useForm();
const [loading, setLoading] = useState(false);
const [selectedProvider, setSelectedProvider] = useState<string | null>(null);
const [guardrailSettings, setGuardrailSettings] = useState<GuardrailSettings | null>(null);
const [selectedEntities, setSelectedEntities] = useState<string[]>([]);
const [selectedActions, setSelectedActions] = useState<{[key: string]: string}>({});
const [currentStep, setCurrentStep] = useState(0);
// Fetch guardrail settings when the component mounts
useEffect(() => {
const fetchGuardrailSettings = async () => {
try {
if (!accessToken) return;
const data = await getGuardrailUISettings(accessToken);
setGuardrailSettings(data);
} catch (error) {
console.error('Error fetching guardrail settings:', error);
message.error('Failed to load guardrail settings');
}
};
fetchGuardrailSettings();
}, [accessToken]);
const handleProviderChange = (value: string) => {
setSelectedProvider(value);
// Reset form fields that are provider-specific
form.setFieldsValue({
config: undefined
});
// Reset PII selections when changing provider
setSelectedEntities([]);
setSelectedActions({});
};
const handleEntitySelect = (entity: string) => {
setSelectedEntities(prev => {
if (prev.includes(entity)) {
return prev.filter(e => e !== entity);
} else {
return [...prev, entity];
}
});
};
const handleActionSelect = (entity: string, action: string) => {
setSelectedActions(prev => ({
...prev,
[entity]: action
}));
};
const nextStep = async () => {
try {
// Validate current step fields
if (currentStep === 0) {
await form.validateFields(['guardrail_name', 'provider', 'mode', 'default_on']);
}
setCurrentStep(currentStep + 1);
} catch (error) {
console.error("Form validation failed:", error);
}
};
const prevStep = () => {
setCurrentStep(currentStep - 1);
};
const resetForm = () => {
form.resetFields();
setSelectedProvider(null);
setSelectedEntities([]);
setSelectedActions({});
setCurrentStep(0);
};
const handleClose = () => {
resetForm();
onClose();
};
const handleSubmit = async () => {
try {
setLoading(true);
// First validate currently visible fields (step 1)
await form.validateFields();
// After validation, fetch *all* form values (including those from previous steps)
const values = form.getFieldsValue(true);
// Get the guardrail provider value from the map
const guardrailProvider = guardrail_provider_map[values.provider];
// Prepare the guardrail data with proper typings
const guardrailData: {
guardrail_name: string;
litellm_params: {
guardrail: string;
mode: string;
default_on: boolean;
[key: string]: any; // Allow dynamic properties
};
guardrail_info: any;
} = {
guardrail_name: values.guardrail_name,
litellm_params: {
guardrail: guardrailProvider,
mode: values.mode,
default_on: values.default_on
},
guardrail_info: {}
};
// For Presidio PII, add the entity and action configurations
if (values.provider === 'PresidioPII' && selectedEntities.length > 0) {
const piiEntitiesConfig: {[key: string]: string} = {};
selectedEntities.forEach(entity => {
piiEntitiesConfig[entity] = selectedActions[entity] || 'MASK'; // Default to MASK if no action selected
});
guardrailData.litellm_params.pii_entities_config = piiEntitiesConfig;
}
// Add config values to the guardrail_info if provided
else if (values.config) {
try {
const configObj = JSON.parse(values.config);
// For some guardrails, the config values need to be in litellm_params
// Especially for providers like Bedrock that need guardrailIdentifier and guardrailVersion
if (values.provider === 'Bedrock' && configObj) {
if (configObj.guardrail_id) {
guardrailData.litellm_params.guardrailIdentifier = configObj.guardrail_id;
}
if (configObj.guardrail_version) {
guardrailData.litellm_params.guardrailVersion = configObj.guardrail_version;
}
} else {
// For other providers, add the config to guardrail_info
guardrailData.guardrail_info = configObj;
}
} catch (error) {
message.error('Invalid JSON in configuration');
setLoading(false);
return;
}
}
if (!accessToken) {
throw new Error("No access token available");
}
console.log("Sending guardrail data:", JSON.stringify(guardrailData));
await createGuardrailCall(accessToken, guardrailData);
message.success('Guardrail created successfully');
// Reset form and close modal
resetForm();
onSuccess();
onClose();
} catch (error) {
console.error("Failed to create guardrail:", error);
message.error('Failed to create guardrail: ' + (error instanceof Error ? error.message : String(error)));
} finally {
setLoading(false);
}
};
const renderBasicInfo = () => {
return (
<>
<Form.Item
name="guardrail_name"
label="Guardrail Name"
rules={[{ required: true, message: 'Please enter a guardrail name' }]}
>
<TextInput placeholder="Enter a name for this guardrail" />
</Form.Item>
<Form.Item
name="provider"
label="Guardrail Provider"
rules={[{ required: true, message: 'Please select a provider' }]}
>
<Select
placeholder="Select a guardrail provider"
onChange={handleProviderChange}
optionLabelProp="label"
>
{Object.entries(GuardrailProviders).map(([key, value]) => (
<Option
key={key}
value={key}
label={value}
>
<div style={{ display: 'flex', alignItems: 'center' }}>
{guardrailLogoMap[value] && (
<img
src={guardrailLogoMap[value]}
alt=""
style={{
height: '20px',
width: '20px',
marginRight: '8px',
objectFit: 'contain'
}}
onError={(e) => {
// Hide broken image icon if image fails to load
e.currentTarget.style.display = 'none';
}}
/>
)}
<span>{value}</span>
</div>
</Option>
))}
</Select>
</Form.Item>
<Form.Item
name="mode"
label="Mode"
tooltip="How the guardrail should be applied"
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>
</>
)}
</Select>
</Form.Item>
<Form.Item
name="default_on"
label="Always On"
tooltip="If enabled, this guardrail will be applied to all requests by default"
valuePropName="checked"
>
<Switch />
</Form.Item>
</>
);
};
const renderPiiConfiguration = () => {
if (!guardrailSettings || selectedProvider !== 'PresidioPII') return null;
return (
<PiiConfiguration
entities={guardrailSettings.supported_entities}
actions={guardrailSettings.supported_actions}
selectedEntities={selectedEntities}
selectedActions={selectedActions}
onEntitySelect={handleEntitySelect}
onActionSelect={handleActionSelect}
entityCategories={guardrailSettings.pii_entity_categories}
/>
);
};
const renderProviderSpecificConfig = () => {
if (!selectedProvider || selectedProvider === 'PresidioPII') return null;
switch (selectedProvider) {
case 'Aporia':
return (
<Form.Item
label="Aporia Configuration"
name="config"
tooltip="JSON configuration for Aporia"
>
<Input.TextArea
rows={4}
placeholder={`{
"api_key": "your_aporia_api_key",
"project_name": "your_project_name"
}`}
/>
</Form.Item>
);
case 'AimSecurity':
return (
<Form.Item
label="Aim Security Configuration"
name="config"
tooltip="JSON configuration for Aim Security"
>
<Input.TextArea
rows={4}
placeholder={`{
"api_key": "your_aim_api_key"
}`}
/>
</Form.Item>
);
case 'Bedrock':
return (
<Form.Item
label="Amazon Bedrock Configuration"
name="config"
tooltip="JSON configuration for Amazon Bedrock guardrails"
>
<Input.TextArea
rows={4}
placeholder={`{
"guardrail_id": "your_guardrail_id",
"guardrail_version": "your_guardrail_version"
}`}
/>
</Form.Item>
);
case 'GuardrailsAI':
return (
<Form.Item
label="Guardrails.ai Configuration"
name="config"
tooltip="JSON configuration for Guardrails.ai"
>
<Input.TextArea
rows={4}
placeholder={`{
"api_key": "your_guardrails_api_key",
"guardrail_id": "your_guardrail_id"
}`}
/>
</Form.Item>
);
case 'LakeraAI':
return (
<Form.Item
label="Lakera AI Configuration"
name="config"
tooltip="JSON configuration for Lakera AI"
>
<Input.TextArea
rows={4}
placeholder={`{
"api_key": "your_lakera_api_key"
}`}
/>
</Form.Item>
);
case 'PromptInjection':
return (
<Form.Item
label="Prompt Injection Configuration"
name="config"
tooltip="JSON configuration for prompt injection detection"
>
<Input.TextArea
rows={4}
placeholder={`{
"threshold": 0.8
}`}
/>
</Form.Item>
);
default:
return (
<Form.Item
label="Custom Configuration"
name="config"
tooltip="JSON configuration for your custom guardrail"
>
<Input.TextArea
rows={4}
placeholder={`{
"key1": "value1",
"key2": "value2"
}`}
/>
</Form.Item>
);
}
};
const renderStepContent = () => {
switch (currentStep) {
case 0:
return renderBasicInfo();
case 1:
if (selectedProvider === 'PresidioPII') {
return renderPiiConfiguration();
} else {
return renderProviderSpecificConfig();
}
default:
return null;
}
};
const renderStepButtons = () => {
return (
<div className="flex justify-end space-x-2 mt-4">
{currentStep > 0 && (
<Button
variant="secondary"
onClick={prevStep}
>
Previous
</Button>
)}
{currentStep < 1 && (
<Button
onClick={nextStep}
>
Next
</Button>
)}
{currentStep === 1 && (
<Button
onClick={handleSubmit}
loading={loading}
>
Create Guardrail
</Button>
)}
<Button
variant="secondary"
onClick={handleClose}
>
Cancel
</Button>
</div>
);
};
return (
<Modal
title="Add Guardrail"
open={visible}
onCancel={handleClose}
footer={null}
width={700}
>
<Form
form={form}
layout="vertical"
initialValues={{
mode: guardrailSettings?.supported_modes?.[0] || "pre_call",
default_on: false
}}
>
<Steps current={currentStep} className="mb-6">
<Step title="Basic Info" />
<Step title={selectedProvider === 'PresidioPII' ? "PII Configuration" : "Provider Configuration"} />
</Steps>
{renderStepContent()}
{renderStepButtons()}
</Form>
</Modal>
);
};
export default AddGuardrailForm;

View file

@ -0,0 +1,456 @@
import React, { useState, useEffect } from 'react';
import { Form, Typography, Select, Input, Switch, Modal, message, Divider } from 'antd';
import { Button, TextInput } from '@tremor/react';
import { GuardrailProviders, guardrail_provider_map, provider_specific_fields, guardrailLogoMap } from './guardrail_info_helpers';
import { getGuardrailUISettings } from '../networking';
import PiiConfiguration from './pii_configuration';
const { Title, Text } = Typography;
const { Option } = Select;
interface EditGuardrailFormProps {
visible: boolean;
onClose: () => void;
accessToken: string | null;
onSuccess: () => void;
guardrailId: string;
initialValues: {
guardrail_name: string;
provider: string;
mode: string;
default_on: boolean;
pii_entities_config?: {[key: string]: string};
[key: string]: any;
};
}
interface GuardrailSettings {
supported_entities: string[];
supported_actions: string[];
supported_modes: string[];
pii_entity_categories: Array<{
category: string;
entities: string[];
}>;
}
const EditGuardrailForm: React.FC<EditGuardrailFormProps> = ({
visible,
onClose,
accessToken,
onSuccess,
guardrailId,
initialValues
}) => {
const [form] = Form.useForm();
const [loading, setLoading] = useState(false);
const [selectedProvider, setSelectedProvider] = useState<string | null>(initialValues?.provider || null);
const [guardrailSettings, setGuardrailSettings] = useState<GuardrailSettings | null>(null);
const [selectedEntities, setSelectedEntities] = useState<string[]>([]);
const [selectedActions, setSelectedActions] = useState<{[key: string]: string}>({});
// Fetch guardrail settings when the component mounts
useEffect(() => {
const fetchGuardrailSettings = async () => {
try {
if (!accessToken) return;
const data = await getGuardrailUISettings(accessToken);
setGuardrailSettings(data);
} catch (error) {
console.error('Error fetching guardrail settings:', error);
message.error('Failed to load guardrail settings');
}
};
fetchGuardrailSettings();
}, [accessToken]);
// Initialize selected entities and actions from initialValues
useEffect(() => {
if (initialValues?.pii_entities_config && Object.keys(initialValues.pii_entities_config).length > 0) {
const entities = Object.keys(initialValues.pii_entities_config);
setSelectedEntities(entities);
setSelectedActions(initialValues.pii_entities_config);
}
}, [initialValues]);
const handleProviderChange = (value: string) => {
setSelectedProvider(value);
// Reset form fields that are provider-specific
form.setFieldsValue({
config: undefined
});
// Reset PII selections when changing provider
setSelectedEntities([]);
setSelectedActions({});
};
const handleEntitySelect = (entity: string) => {
setSelectedEntities(prev => {
if (prev.includes(entity)) {
return prev.filter(e => e !== entity);
} else {
return [...prev, entity];
}
});
};
const handleActionSelect = (entity: string, action: string) => {
setSelectedActions(prev => ({
...prev,
[entity]: action
}));
};
const handleSubmit = async () => {
try {
setLoading(true);
const values = await form.validateFields();
// Get the guardrail provider value from the map
const guardrailProvider = guardrail_provider_map[values.provider];
// Prepare the guardrail data with proper types for litellm_params
const guardrailData: {
guardrail_id: string;
guardrail: {
guardrail_name: string;
litellm_params: {
guardrail: string;
mode: string;
default_on: boolean;
[key: string]: any; // Allow dynamic properties
};
guardrail_info: any;
}
} = {
guardrail_id: guardrailId,
guardrail: {
guardrail_name: values.guardrail_name,
litellm_params: {
guardrail: guardrailProvider,
mode: values.mode,
default_on: values.default_on
},
guardrail_info: {}
}
};
// For Presidio PII, add the entity and action configurations
if (values.provider === 'PresidioPII' && selectedEntities.length > 0) {
const piiEntitiesConfig: {[key: string]: string} = {};
selectedEntities.forEach(entity => {
piiEntitiesConfig[entity] = selectedActions[entity] || 'MASK'; // Default to MASK if no action selected
});
guardrailData.guardrail.litellm_params.pii_entities_config = piiEntitiesConfig;
}
// Add config values to the guardrail_info if provided
else if (values.config) {
try {
const configObj = JSON.parse(values.config);
// For some guardrails, the config values need to be in litellm_params
// Especially for providers like Bedrock that need guardrailIdentifier and guardrailVersion
if (values.provider === 'Bedrock' && configObj) {
if (configObj.guardrail_id) {
guardrailData.guardrail.litellm_params.guardrailIdentifier = configObj.guardrail_id;
}
if (configObj.guardrail_version) {
guardrailData.guardrail.litellm_params.guardrailVersion = configObj.guardrail_version;
}
} else {
// For other providers, add the config to guardrail_info
guardrailData.guardrail.guardrail_info = configObj;
}
} catch (error) {
message.error('Invalid JSON in configuration');
setLoading(false);
return;
}
}
if (!accessToken) {
throw new Error("No access token available");
}
console.log("Sending guardrail update data:", JSON.stringify(guardrailData));
// Call the update endpoint
const url = `/guardrails/${guardrailId}`;
const response = await fetch(url, {
method: "PUT",
headers: {
"Authorization": `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify(guardrailData),
});
if (!response.ok) {
const errorData = await response.text();
throw new Error(errorData || "Failed to update guardrail");
}
message.success('Guardrail updated successfully');
// Reset and close
onSuccess();
onClose();
} catch (error) {
console.error("Failed to update guardrail:", error);
message.error('Failed to update guardrail: ' + (error instanceof Error ? error.message : String(error)));
} finally {
setLoading(false);
}
};
const renderPiiConfiguration = () => {
if (!guardrailSettings || !selectedProvider || selectedProvider !== 'PresidioPII') return null;
return (
<PiiConfiguration
entities={guardrailSettings.supported_entities}
actions={guardrailSettings.supported_actions}
selectedEntities={selectedEntities}
selectedActions={selectedActions}
onEntitySelect={handleEntitySelect}
onActionSelect={handleActionSelect}
entityCategories={guardrailSettings.pii_entity_categories}
/>
);
};
const renderProviderSpecificFields = () => {
if (!selectedProvider) return null;
// For Presidio, we use the new PII configuration UI
if (selectedProvider === 'PresidioPII') {
return renderPiiConfiguration();
}
switch (selectedProvider) {
case 'Aporia':
return (
<Form.Item
label="Aporia Configuration"
name="config"
tooltip="JSON configuration for Aporia"
>
<Input.TextArea
rows={4}
placeholder={`{
"api_key": "your_aporia_api_key",
"project_name": "your_project_name"
}`}
/>
</Form.Item>
);
case 'AimSecurity':
return (
<Form.Item
label="Aim Security Configuration"
name="config"
tooltip="JSON configuration for Aim Security"
>
<Input.TextArea
rows={4}
placeholder={`{
"api_key": "your_aim_api_key"
}`}
/>
</Form.Item>
);
case 'Bedrock':
return (
<Form.Item
label="Amazon Bedrock Configuration"
name="config"
tooltip="JSON configuration for Amazon Bedrock guardrails"
>
<Input.TextArea
rows={4}
placeholder={`{
"guardrail_id": "your_guardrail_id",
"guardrail_version": "your_guardrail_version"
}`}
/>
</Form.Item>
);
case 'GuardrailsAI':
return (
<Form.Item
label="Guardrails.ai Configuration"
name="config"
tooltip="JSON configuration for Guardrails.ai"
>
<Input.TextArea
rows={4}
placeholder={`{
"api_key": "your_guardrails_api_key",
"guardrail_id": "your_guardrail_id"
}`}
/>
</Form.Item>
);
case 'LakeraAI':
return (
<Form.Item
label="Lakera AI Configuration"
name="config"
tooltip="JSON configuration for Lakera AI"
>
<Input.TextArea
rows={4}
placeholder={`{
"api_key": "your_lakera_api_key"
}`}
/>
</Form.Item>
);
case 'PromptInjection':
return (
<Form.Item
label="Prompt Injection Configuration"
name="config"
tooltip="JSON configuration for prompt injection detection"
>
<Input.TextArea
rows={4}
placeholder={`{
"threshold": 0.8
}`}
/>
</Form.Item>
);
default:
return (
<Form.Item
label="Custom Configuration"
name="config"
tooltip="JSON configuration for your custom guardrail"
>
<Input.TextArea
rows={4}
placeholder={`{
"key1": "value1",
"key2": "value2"
}`}
/>
</Form.Item>
);
}
};
return (
<Modal
title="Edit Guardrail"
open={visible}
onCancel={onClose}
footer={null}
width={700}
>
<Form
form={form}
layout="vertical"
initialValues={initialValues}
>
<Form.Item
name="guardrail_name"
label="Guardrail Name"
rules={[{ required: true, message: 'Please enter a guardrail name' }]}
>
<TextInput placeholder="Enter a name for this guardrail" />
</Form.Item>
<Form.Item
name="provider"
label="Guardrail Provider"
rules={[{ required: true, message: 'Please select a provider' }]}
>
<Select
placeholder="Select a guardrail provider"
onChange={handleProviderChange}
disabled={true} // Disable changing provider in edit mode
optionLabelProp="label"
>
{Object.entries(GuardrailProviders).map(([key, value]) => (
<Option
key={key}
value={key}
label={value}
>
<div style={{ display: 'flex', alignItems: 'center' }}>
{guardrailLogoMap[value] && (
<img
src={guardrailLogoMap[value]}
alt=""
style={{
height: '20px',
width: '20px',
marginRight: '8px',
objectFit: 'contain'
}}
onError={(e) => {
// Hide broken image icon if image fails to load
e.currentTarget.style.display = 'none';
}}
/>
)}
<span>{value}</span>
</div>
</Option>
))}
</Select>
</Form.Item>
<Form.Item
name="mode"
label="Mode"
tooltip="How the guardrail should be applied"
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>
</>
)}
</Select>
</Form.Item>
<Form.Item
name="default_on"
label="Always On"
tooltip="If enabled, this guardrail will be applied to all requests by default"
valuePropName="checked"
>
<Switch />
</Form.Item>
{renderProviderSpecificFields()}
<div className="flex justify-end space-x-2 mt-4">
<Button
variant="secondary"
onClick={onClose}
>
Cancel
</Button>
<Button
onClick={handleSubmit}
loading={loading}
>
Update Guardrail
</Button>
</div>
</Form>
</Modal>
);
};
export default EditGuardrailForm;

View file

@ -1,34 +1,20 @@
export enum GuardrailProviders {
PresidioPII = "Presidio PII",
Aporia = "Aporia",
AimSecurity = "Aim Security",
Bedrock = "Amazon Bedrock",
GuardrailsAI = "Guardrails.ai",
LakeraAI = "Lakera AI",
Custom = "Custom Guardrail",
PromptInjection = "Prompt Injection Detection",
}
export const guardrail_provider_map: Record<string, string> = {
Aporia: "aporia",
AimSecurity: "aim",
Bedrock: "bedrock",
GuardrailsAI: "guardrails_ai",
LakeraAI: "lakera",
PromptInjection: "detect_prompt_injection",
PresidioPII: "presidio",
};
// Define which providers need specific fields
export const provider_specific_fields: Record<string, string[]> = {
PresidioPII: ["pii_entities", "pii_actions"]
};
const asset_logos_folder = '../ui/assets/logos/';
export const guardrailLogoMap: Record<string, string> = {
[GuardrailProviders.Aporia]: `${asset_logos_folder}aporia.svg`,
[GuardrailProviders.AimSecurity]: `${asset_logos_folder}aim.svg`,
[GuardrailProviders.Bedrock]: `${asset_logos_folder}bedrock.svg`,
[GuardrailProviders.GuardrailsAI]: `${asset_logos_folder}guardrails_ai.svg`,
[GuardrailProviders.LakeraAI]: `${asset_logos_folder}lakera.svg`,
[GuardrailProviders.PromptInjection]: `${asset_logos_folder}prompt_injection.svg`,
[GuardrailProviders.PresidioPII]: `${asset_logos_folder}presidio.svg`
[GuardrailProviders.PresidioPII]: `${asset_logos_folder}presidio.png`
};
export const getGuardrailLogoAndName = (guardrailValue: string): { logo: string, displayName: string } => {

View file

@ -0,0 +1,325 @@
import React, { useState } from "react";
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeaderCell,
TableRow,
Icon,
} from "@tremor/react";
import {
TrashIcon,
SwitchVerticalIcon,
ChevronUpIcon,
ChevronDownIcon,
PencilIcon,
} from "@heroicons/react/outline";
import { Tooltip } from "antd";
import {
ColumnDef,
flexRender,
getCoreRowModel,
getSortedRowModel,
SortingState,
useReactTable,
} from "@tanstack/react-table";
import { getGuardrailLogoAndName, guardrail_provider_map } from "./guardrail_info_helpers";
import EditGuardrailForm from "./edit_guardrail_form";
interface GuardrailItem {
guardrail_id?: string;
guardrail_name: string | null;
litellm_params: {
guardrail: string;
mode: string;
default_on: boolean;
pii_entities_config?: {[key: string]: string};
[key: string]: any;
};
guardrail_info: Record<string, any> | null;
created_at?: string;
updated_at?: string;
}
interface GuardrailTableProps {
guardrailsList: GuardrailItem[];
isLoading: boolean;
onDeleteClick: (guardrailId: string, guardrailName: string) => void;
accessToken: string | null;
onGuardrailUpdated: () => void;
}
const GuardrailTable: React.FC<GuardrailTableProps> = ({
guardrailsList,
isLoading,
onDeleteClick,
accessToken,
onGuardrailUpdated,
}) => {
const [sorting, setSorting] = useState<SortingState>([
{ id: "created_at", desc: true }
]);
const [editModalVisible, setEditModalVisible] = useState(false);
const [selectedGuardrail, setSelectedGuardrail] = useState<GuardrailItem | null>(null);
// Format date helper function
const formatDate = (dateString?: string) => {
if (!dateString) return "-";
const date = new Date(dateString);
return date.toLocaleString();
};
const handleEditClick = (guardrail: GuardrailItem) => {
setSelectedGuardrail(guardrail);
setEditModalVisible(true);
};
const handleEditSuccess = () => {
setEditModalVisible(false);
setSelectedGuardrail(null);
onGuardrailUpdated();
};
const columns: ColumnDef<GuardrailItem>[] = [
{
header: "Name",
accessorKey: "guardrail_name",
cell: ({ row }) => {
const guardrail = row.original;
return (
<Tooltip title={guardrail.guardrail_name}>
<span className="text-xs font-medium">
{guardrail.guardrail_name || "-"}
</span>
</Tooltip>
);
},
},
{
header: "Provider",
accessorKey: "litellm_params.guardrail",
cell: ({ row }) => {
const guardrail = row.original;
const { logo, displayName } = getGuardrailLogoAndName(guardrail.litellm_params.guardrail);
return (
<div className="flex items-center space-x-2">
{logo && (
<img
src={logo}
alt={`${displayName} logo`}
className="w-4 h-4"
onError={(e) => {
// Hide broken image
(e.target as HTMLImageElement).style.display = 'none';
}}
/>
)}
<span className="text-xs">{displayName}</span>
</div>
);
},
},
{
header: "Mode",
accessorKey: "litellm_params.mode",
cell: ({ row }) => {
const guardrail = row.original;
return (
<span className="text-xs">
{guardrail.litellm_params.mode}
</span>
);
},
},
{
header: "Status",
accessorKey: "litellm_params.default_on",
cell: ({ row }) => {
const guardrail = row.original;
return (
<div className={`inline-flex rounded-full px-2 py-1 text-xs font-medium
${guardrail.litellm_params.default_on
? 'bg-green-100 text-green-800' // Always On styling
: 'bg-gray-100 text-gray-800' // Per Request styling
}`}>
{guardrail.litellm_params.default_on ? 'Always On' : 'Per Request'}
</div>
);
},
},
{
header: "Created At",
accessorKey: "created_at",
cell: ({ row }) => {
const guardrail = row.original;
return (
<Tooltip title={guardrail.created_at}>
<span className="text-xs">
{formatDate(guardrail.created_at)}
</span>
</Tooltip>
);
},
},
{
header: "Updated At",
accessorKey: "updated_at",
cell: ({ row }) => {
const guardrail = row.original;
return (
<Tooltip title={guardrail.updated_at}>
<span className="text-xs">
{formatDate(guardrail.updated_at)}
</span>
</Tooltip>
);
},
},
{
id: "actions",
header: "",
cell: ({ row }) => {
const guardrail = row.original;
return (
<div className="flex space-x-2">
<Icon
icon={PencilIcon}
size="sm"
onClick={() => guardrail.guardrail_id && handleEditClick(guardrail)}
className="cursor-pointer hover:text-blue-500"
tooltip="Edit guardrail"
/>
<Icon
icon={TrashIcon}
size="sm"
onClick={() => guardrail.guardrail_id && onDeleteClick(guardrail.guardrail_id, guardrail.guardrail_name || 'Unnamed Guardrail')}
className="cursor-pointer hover:text-red-500"
tooltip="Delete guardrail"
/>
</div>
);
},
},
];
const table = useReactTable({
data: guardrailsList,
columns,
state: {
sorting,
},
onSortingChange: setSorting,
getCoreRowModel: getCoreRowModel(),
getSortedRowModel: getSortedRowModel(),
enableSorting: true,
});
return (
<div className="rounded-lg custom-border relative">
<div className="overflow-x-auto">
<Table className="[&_td]:py-0.5 [&_th]:py-1">
<TableHead>
{table.getHeaderGroups().map((headerGroup) => (
<TableRow key={headerGroup.id}>
{headerGroup.headers.map((header) => (
<TableHeaderCell
key={header.id}
className={`py-1 h-8 ${
header.id === 'actions'
? 'sticky right-0 bg-white shadow-[-4px_0_8px_-6px_rgba(0,0,0,0.1)]'
: ''
}`}
onClick={header.column.getToggleSortingHandler()}
>
<div className="flex items-center justify-between gap-2">
<div className="flex items-center">
{header.isPlaceholder ? null : (
flexRender(
header.column.columnDef.header,
header.getContext()
)
)}
</div>
{header.id !== 'actions' && (
<div className="w-4">
{header.column.getIsSorted() ? (
{
asc: <ChevronUpIcon className="h-4 w-4 text-blue-500" />,
desc: <ChevronDownIcon className="h-4 w-4 text-blue-500" />
}[header.column.getIsSorted() as string]
) : (
<SwitchVerticalIcon className="h-4 w-4 text-gray-400" />
)}
</div>
)}
</div>
</TableHeaderCell>
))}
</TableRow>
))}
</TableHead>
<TableBody>
{isLoading ? (
<TableRow>
<TableCell colSpan={columns.length} className="h-8 text-center">
<div className="text-center text-gray-500">
<p>Loading...</p>
</div>
</TableCell>
</TableRow>
) : guardrailsList.length > 0 ? (
table.getRowModel().rows.map((row) => (
<TableRow key={row.id} className="h-8">
{row.getVisibleCells().map((cell) => (
<TableCell
key={cell.id}
className={`py-0.5 max-h-8 overflow-hidden text-ellipsis whitespace-nowrap ${
cell.column.id === 'actions'
? 'sticky right-0 bg-white shadow-[-4px_0_8px_-6px_rgba(0,0,0,0.1)]'
: ''
}`}
>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</TableCell>
))}
</TableRow>
))
) : (
<TableRow>
<TableCell colSpan={columns.length} className="h-8 text-center">
<div className="text-center text-gray-500">
<p>No guardrails found</p>
</div>
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
</div>
{/* Edit Modal */}
{selectedGuardrail && (
<EditGuardrailForm
visible={editModalVisible}
onClose={() => setEditModalVisible(false)}
accessToken={accessToken}
onSuccess={handleEditSuccess}
guardrailId={selectedGuardrail.guardrail_id || ''}
initialValues={{
guardrail_name: selectedGuardrail.guardrail_name || '',
provider: Object.keys(guardrail_provider_map).find(
key => guardrail_provider_map[key] === selectedGuardrail?.litellm_params.guardrail
) || '',
mode: selectedGuardrail.litellm_params.mode,
default_on: selectedGuardrail.litellm_params.default_on,
pii_entities_config: selectedGuardrail.litellm_params.pii_entities_config,
...selectedGuardrail.guardrail_info
}}
/>
)}
</div>
);
};
export default GuardrailTable;

View file

@ -0,0 +1,206 @@
import React from 'react';
import { Typography, Select, Button, Checkbox, Tooltip, Tag } from 'antd';
import { CloseOutlined, EyeInvisibleOutlined, StopOutlined, FilterOutlined } from '@ant-design/icons';
import { PiiEntityCategory } from './types';
const { Text } = Typography;
const { Option } = Select;
// Helper functions
export const formatEntityName = (name: string) => {
return name.replace(/_/g, ' ');
};
export const getActionIcon = (action: string) => {
switch(action) {
case 'MASK':
return <EyeInvisibleOutlined style={{ marginRight: 4 }} />;
case 'BLOCK':
return <StopOutlined style={{ marginRight: 4 }} />;
default:
return null;
}
};
// CategoryFilter component
export interface CategoryFilterProps {
categories: PiiEntityCategory[];
selectedCategories: string[];
onChange: (categories: string[]) => void;
}
export const CategoryFilter: React.FC<CategoryFilterProps> = ({
categories,
selectedCategories,
onChange
}) => {
return (
<div>
<div className="flex items-center mb-2">
<FilterOutlined className="text-gray-500 mr-1" />
<Text className="text-gray-500 font-medium">Filter by category</Text>
</div>
<Select
mode="multiple"
placeholder="Select categories to filter by"
style={{ width: '100%' }}
onChange={onChange}
value={selectedCategories}
allowClear
showSearch
optionFilterProp="children"
className="mb-4"
tagRender={(props) => (
<Tag
color="blue"
closable={props.closable}
onClose={props.onClose}
className="mr-2 mb-2"
>
{props.label}
</Tag>
)}
>
{categories.map(cat => (
<Option key={cat.category} value={cat.category}>
{cat.category}
</Option>
))}
</Select>
</div>
);
};
// QuickActions component
export interface QuickActionsProps {
onSelectAll: (action: string) => void;
onUnselectAll: () => void;
hasSelectedEntities: boolean;
}
export const QuickActions: React.FC<QuickActionsProps> = ({
onSelectAll,
onUnselectAll,
hasSelectedEntities
}) => {
return (
<div className="bg-gray-50 p-5 rounded-lg mb-6 border border-gray-200 shadow-sm">
<div className="flex items-center justify-between mb-4">
<div className="flex items-center">
<Text strong className="text-gray-700 text-base">Quick Actions</Text>
<Tooltip title="Apply action to all PII types at once">
<div className="ml-2 text-gray-400 cursor-help text-xs">ⓘ</div>
</Tooltip>
</div>
<Button
type="default"
onClick={onUnselectAll}
disabled={!hasSelectedEntities}
icon={<CloseOutlined />}
className="border-gray-300 hover:text-red-600 hover:border-red-300"
>
Unselect All
</Button>
</div>
<div className="grid grid-cols-2 gap-4">
<Button
type="default"
onClick={() => onSelectAll('MASK')}
className="flex items-center justify-center h-10 border-blue-200 hover:border-blue-300 hover:text-blue-700 bg-blue-50 hover:bg-blue-100 text-blue-600"
block
icon={<EyeInvisibleOutlined />}
>
Select All & Mask
</Button>
<Button
type="default"
onClick={() => onSelectAll('BLOCK')}
className="flex items-center justify-center h-10 border-red-200 hover:border-red-300 hover:text-red-700 bg-red-50 hover:bg-red-100 text-red-600"
block
icon={<StopOutlined />}
>
Select All & Block
</Button>
</div>
</div>
);
};
// PiiEntityList component
export interface PiiEntityListProps {
entities: string[];
selectedEntities: string[];
selectedActions: {[key: string]: string};
actions: string[];
onEntitySelect: (entity: string) => void;
onActionSelect: (entity: string, action: string) => void;
entityToCategoryMap: Map<string, string>;
}
export const PiiEntityList: React.FC<PiiEntityListProps> = ({
entities,
selectedEntities,
selectedActions,
actions,
onEntitySelect,
onActionSelect,
entityToCategoryMap
}) => {
return (
<div className="border rounded-lg overflow-hidden shadow-sm">
<div className="bg-gray-50 px-5 py-3 border-b flex">
<Text strong className="flex-1 text-gray-700">PII Type</Text>
<Text strong className="w-32 text-right text-gray-700">Action</Text>
</div>
<div className="max-h-[400px] overflow-y-auto">
{entities.length === 0 ? (
<div className="py-10 text-center text-gray-500">
No PII types match your filter criteria
</div>
) : (
entities.map(entity => (
<div
key={entity}
className={`px-5 py-3 flex items-center justify-between hover:bg-gray-50 border-b ${
selectedEntities.includes(entity) ? 'bg-blue-50' : ''
}`}
>
<div className="flex items-center flex-1">
<Checkbox
checked={selectedEntities.includes(entity)}
onChange={() => onEntitySelect(entity)}
className="mr-3"
/>
<Text className={selectedEntities.includes(entity) ? 'font-medium text-gray-900' : 'text-gray-700'}>
{formatEntityName(entity)}
</Text>
{entityToCategoryMap.get(entity) && (
<Tag className="ml-2 text-xs" color="blue">{entityToCategoryMap.get(entity)}</Tag>
)}
</div>
<div className="w-32">
<Select
value={selectedEntities.includes(entity) ? (selectedActions[entity] || 'MASK') : 'MASK'}
onChange={(value) => onActionSelect(entity, value)}
style={{ width: 120 }}
disabled={!selectedEntities.includes(entity)}
className={`${!selectedEntities.includes(entity) ? 'opacity-50' : ''}`}
dropdownMatchSelectWidth={false}
>
{actions.map(action => (
<Option key={action} value={action}>
<div className="flex items-center">
{getActionIcon(action)}
{action}
</div>
</Option>
))}
</Select>
</div>
</div>
))
)}
</div>
</div>
);
};

View file

@ -0,0 +1,103 @@
import React, { useState } from 'react';
import { Typography, Badge } from 'antd';
import { PiiConfigurationProps } from './types';
import {
CategoryFilter,
QuickActions,
PiiEntityList,
formatEntityName,
getActionIcon
} from './pii_components';
const { Title, Text } = Typography;
/**
* A reusable component for rendering PII entity selection and action configuration
* Used in both add and edit guardrail forms
*/
const PiiConfiguration: React.FC<PiiConfigurationProps> = ({
entities,
actions,
selectedEntities,
selectedActions,
onEntitySelect,
onActionSelect,
entityCategories = []
}) => {
const [selectedCategories, setSelectedCategories] = useState<string[]>([]);
// Create a lookup map to quickly find an entity's category
const entityToCategoryMap = new Map<string, string>();
entityCategories.forEach(category => {
category.entities.forEach(entity => {
entityToCategoryMap.set(entity, category.category);
});
});
// Filter entities based on selected categories
const filteredEntities = entities.filter(entity => {
return selectedCategories.length === 0 ||
selectedCategories.includes(entityToCategoryMap.get(entity) || '');
});
// Select all entities with a specified action
const handleSelectAll = (action: string) => {
entities.forEach(entity => {
if (!selectedEntities.includes(entity)) {
onEntitySelect(entity);
}
onActionSelect(entity, action);
});
};
// Unselect all entities
const handleUnselectAll = () => {
selectedEntities.forEach(entity => {
onEntitySelect(entity);
});
};
return (
<div className="pii-configuration">
<div className="flex justify-between items-center mb-5">
<div className="flex items-center">
<Title level={4} className="mb-0 font-semibold text-gray-800">Configure PII Protection</Title>
</div>
<Badge
count={selectedEntities.length}
showZero
style={{ backgroundColor: selectedEntities.length > 0 ? '#4f46e5' : '#d9d9d9' }}
overflowCount={999}
>
<Text className="text-gray-500">{selectedEntities.length} items selected</Text>
</Badge>
</div>
<div className="mb-6">
<CategoryFilter
categories={entityCategories}
selectedCategories={selectedCategories}
onChange={setSelectedCategories}
/>
<QuickActions
onSelectAll={handleSelectAll}
onUnselectAll={handleUnselectAll}
hasSelectedEntities={selectedEntities.length > 0}
/>
</div>
<PiiEntityList
entities={filteredEntities}
selectedEntities={selectedEntities}
selectedActions={selectedActions}
actions={actions}
onEntitySelect={onEntitySelect}
onActionSelect={onActionSelect}
entityToCategoryMap={entityToCategoryMap}
/>
</div>
);
};
export default PiiConfiguration;

View file

@ -0,0 +1,19 @@
export interface PiiEntity {
name: string;
category: string;
}
export interface PiiEntityCategory {
category: string;
entities: string[];
}
export interface PiiConfigurationProps {
entities: string[];
actions: string[];
selectedEntities: string[];
selectedActions: {[key: string]: string};
onEntitySelect: (entity: string) => void;
onActionSelect: (entity: string, action: string) => void;
entityCategories?: PiiEntityCategory[];
}

View file

@ -4278,8 +4278,7 @@ export const getProxyUISettings = async (
export const getGuardrailsList = async (accessToken: String) => {
try {
let url = proxyBaseUrl ? `${proxyBaseUrl}/guardrails/list` : `/guardrails/list`;
const url = proxyBaseUrl ? `${proxyBaseUrl}/v2/guardrails/list` : `/v2/guardrails/list`;
const response = await fetch(url, {
method: "GET",
headers: {
@ -4295,10 +4294,9 @@ export const getGuardrailsList = async (accessToken: String) => {
}
const data = await response.json();
console.log("Guardrails list response:", data);
return data;
} catch (error) {
console.error("Failed to fetch guardrails list:", error);
console.error("Failed to get guardrails list:", error);
throw error;
}
};
@ -5044,3 +5042,57 @@ export const resetEmailEventSettings = async (accessToken: string) => {
export { type UserInfo } from "./view_users/types"; // Re-export UserInfo
export { type Team } from "./key_team_helpers/key_list"; // Re-export Team
export const deleteGuardrailCall = async (accessToken: string, guardrailId: string) => {
try {
const url = proxyBaseUrl ? `${proxyBaseUrl}/guardrails/${guardrailId}` : `/guardrails/${guardrailId}`;
const response = await fetch(url, {
method: "DELETE",
headers: {
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
});
if (!response.ok) {
const errorData = await response.text();
handleError(errorData);
throw new Error(errorData);
}
const data = await response.json();
console.log("Delete guardrail response:", data);
return data;
} catch (error) {
console.error("Failed to delete guardrail:", error);
throw error;
}
};
export const getGuardrailUISettings = async (accessToken: string) => {
try {
const url = proxyBaseUrl ? `${proxyBaseUrl}/guardrails/ui/add_guardrail_settings` : `/guardrails/ui/add_guardrail_settings`;
const response = await fetch(url, {
method: "GET",
headers: {
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
});
if (!response.ok) {
const errorData = await response.text();
handleError(errorData);
throw new Error("Failed to get guardrail UI settings");
}
const data = await response.json();
console.log("Guardrail UI settings response:", data);
return data;
} catch (error) {
console.error("Failed to get guardrail UI settings:", error);
throw error;
}
};