From b9c3a3835cbecf78d516205922376a195ce8fd56 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Wed, 14 May 2025 14:21:35 -0700 Subject: [PATCH] [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 --- ...odel_prices_and_context_window_backup.json | 230 +++++--- .../proxy/guardrails/guardrail_endpoints.py | 101 ++++ .../proxy/guardrails/guardrail_registry.py | 5 +- litellm/types/guardrails.py | 86 ++- .../src/components/guardrails.tsx | 180 ++++--- .../guardrails/add_guardrail_form.tsx | 504 ++++++++++++++++++ .../guardrails/edit_guardrail_form.tsx | 456 ++++++++++++++++ .../guardrails/guardrail_info_helpers.tsx | 26 +- .../components/guardrails/guardrail_table.tsx | 325 +++++++++++ .../components/guardrails/pii_components.tsx | 206 +++++++ .../guardrails/pii_configuration.tsx | 103 ++++ .../src/components/guardrails/types.ts | 19 + .../src/components/networking.tsx | 60 ++- 13 files changed, 2121 insertions(+), 180 deletions(-) create mode 100644 ui/litellm-dashboard/src/components/guardrails/add_guardrail_form.tsx create mode 100644 ui/litellm-dashboard/src/components/guardrails/edit_guardrail_form.tsx create mode 100644 ui/litellm-dashboard/src/components/guardrails/guardrail_table.tsx create mode 100644 ui/litellm-dashboard/src/components/guardrails/pii_components.tsx create mode 100644 ui/litellm-dashboard/src/components/guardrails/pii_configuration.tsx create mode 100644 ui/litellm-dashboard/src/components/guardrails/types.ts diff --git a/litellm/model_prices_and_context_window_backup.json b/litellm/model_prices_and_context_window_backup.json index 66a4db16db6..3c37b037729 100644 --- a/litellm/model_prices_and_context_window_backup.json +++ b/litellm/model_prices_and_context_window_backup.json @@ -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, diff --git a/litellm/proxy/guardrails/guardrail_endpoints.py b/litellm/proxy/guardrails/guardrail_endpoints.py index 75bbd5f7104..9e4cf4c3c0a 100644 --- a/litellm/proxy/guardrails/guardrail_endpoints.py +++ b/litellm/proxy/guardrails/guardrail_endpoints.py @@ -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 " + ``` + + 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, + ) diff --git a/litellm/proxy/guardrails/guardrail_registry.py b/litellm/proxy/guardrails/guardrail_registry.py index 0557e908d43..2d7bce7b704 100644 --- a/litellm/proxy/guardrails/guardrail_registry.py +++ b/litellm/proxy/guardrails/guardrail_registry.py @@ -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( diff --git a/litellm/types/guardrails.py b/litellm/types/guardrails.py index 1925c6371d8..4a730327b22 100644 --- a/litellm/types/guardrails.py +++ b/litellm/types/guardrails.py @@ -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] diff --git a/ui/litellm-dashboard/src/components/guardrails.tsx b/ui/litellm-dashboard/src/components/guardrails.tsx index 54e47156842..867c356e701 100644 --- a/ui/litellm-dashboard/src/components/guardrails.tsx +++ b/ui/litellm-dashboard/src/components/guardrails.tsx @@ -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 | 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 | null; +interface GuardrailsResponse { + guardrails: GuardrailItem[]; } const GuardrailsPanel: React.FC = ({ accessToken }) => { const [guardrailsList, setGuardrailsList] = useState([]); + 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 (
- - Configured guardrails and their current status. Setup guardrails in config.yaml.{" "} - +
); }; diff --git a/ui/litellm-dashboard/src/components/guardrails/add_guardrail_form.tsx b/ui/litellm-dashboard/src/components/guardrails/add_guardrail_form.tsx new file mode 100644 index 00000000000..d606172bff0 --- /dev/null +++ b/ui/litellm-dashboard/src/components/guardrails/add_guardrail_form.tsx @@ -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 = ({ + visible, + onClose, + accessToken, + onSuccess +}) => { + const [form] = Form.useForm(); + const [loading, setLoading] = useState(false); + const [selectedProvider, setSelectedProvider] = useState(null); + const [guardrailSettings, setGuardrailSettings] = useState(null); + const [selectedEntities, setSelectedEntities] = useState([]); + 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 ( + <> + + + + + + + + + + + + + + + + + ); + }; + + const renderPiiConfiguration = () => { + if (!guardrailSettings || selectedProvider !== 'PresidioPII') return null; + + return ( + + ); + }; + + const renderProviderSpecificConfig = () => { + if (!selectedProvider || selectedProvider === 'PresidioPII') return null; + + switch (selectedProvider) { + case 'Aporia': + return ( + + + + ); + case 'AimSecurity': + return ( + + + + ); + case 'Bedrock': + return ( + + + + ); + case 'GuardrailsAI': + return ( + + + + ); + case 'LakeraAI': + return ( + + + + ); + case 'PromptInjection': + return ( + + + + ); + default: + return ( + + + + ); + } + }; + + const renderStepContent = () => { + switch (currentStep) { + case 0: + return renderBasicInfo(); + case 1: + if (selectedProvider === 'PresidioPII') { + return renderPiiConfiguration(); + } else { + return renderProviderSpecificConfig(); + } + default: + return null; + } + }; + + const renderStepButtons = () => { + return ( +
+ {currentStep > 0 && ( + + )} + {currentStep < 1 && ( + + )} + {currentStep === 1 && ( + + )} + +
+ ); + }; + + return ( + +
+ + + + + + {renderStepContent()} + {renderStepButtons()} +
+
+ ); +}; + +export default AddGuardrailForm; \ No newline at end of file diff --git a/ui/litellm-dashboard/src/components/guardrails/edit_guardrail_form.tsx b/ui/litellm-dashboard/src/components/guardrails/edit_guardrail_form.tsx new file mode 100644 index 00000000000..f6ebd807308 --- /dev/null +++ b/ui/litellm-dashboard/src/components/guardrails/edit_guardrail_form.tsx @@ -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 = ({ + visible, + onClose, + accessToken, + onSuccess, + guardrailId, + initialValues +}) => { + const [form] = Form.useForm(); + const [loading, setLoading] = useState(false); + const [selectedProvider, setSelectedProvider] = useState(initialValues?.provider || null); + const [guardrailSettings, setGuardrailSettings] = useState(null); + const [selectedEntities, setSelectedEntities] = useState([]); + 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 ( + + ); + }; + + 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 ( + + + + ); + case 'AimSecurity': + return ( + + + + ); + case 'Bedrock': + return ( + + + + ); + case 'GuardrailsAI': + return ( + + + + ); + case 'LakeraAI': + return ( + + + + ); + case 'PromptInjection': + return ( + + + + ); + default: + return ( + + + + ); + } + }; + + return ( + +
+ + + + + + + + + + + + + + + + + {renderProviderSpecificFields()} + +
+ + +
+
+
+ ); +}; + +export default EditGuardrailForm; \ No newline at end of file diff --git a/ui/litellm-dashboard/src/components/guardrails/guardrail_info_helpers.tsx b/ui/litellm-dashboard/src/components/guardrails/guardrail_info_helpers.tsx index 2c8f6108d6a..1c0a9908fc2 100644 --- a/ui/litellm-dashboard/src/components/guardrails/guardrail_info_helpers.tsx +++ b/ui/litellm-dashboard/src/components/guardrails/guardrail_info_helpers.tsx @@ -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 = { - 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 = { + PresidioPII: ["pii_entities", "pii_actions"] +}; + const asset_logos_folder = '../ui/assets/logos/'; export const guardrailLogoMap: Record = { - [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 } => { diff --git a/ui/litellm-dashboard/src/components/guardrails/guardrail_table.tsx b/ui/litellm-dashboard/src/components/guardrails/guardrail_table.tsx new file mode 100644 index 00000000000..b52c29d6c35 --- /dev/null +++ b/ui/litellm-dashboard/src/components/guardrails/guardrail_table.tsx @@ -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 | 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 = ({ + guardrailsList, + isLoading, + onDeleteClick, + accessToken, + onGuardrailUpdated, +}) => { + const [sorting, setSorting] = useState([ + { id: "created_at", desc: true } + ]); + const [editModalVisible, setEditModalVisible] = useState(false); + const [selectedGuardrail, setSelectedGuardrail] = useState(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[] = [ + { + header: "Name", + accessorKey: "guardrail_name", + cell: ({ row }) => { + const guardrail = row.original; + return ( + + + {guardrail.guardrail_name || "-"} + + + ); + }, + }, + { + header: "Provider", + accessorKey: "litellm_params.guardrail", + cell: ({ row }) => { + const guardrail = row.original; + const { logo, displayName } = getGuardrailLogoAndName(guardrail.litellm_params.guardrail); + return ( +
+ {logo && ( + {`${displayName} { + // Hide broken image + (e.target as HTMLImageElement).style.display = 'none'; + }} + /> + )} + {displayName} +
+ ); + }, + }, + { + header: "Mode", + accessorKey: "litellm_params.mode", + cell: ({ row }) => { + const guardrail = row.original; + return ( + + {guardrail.litellm_params.mode} + + ); + }, + }, + { + header: "Status", + accessorKey: "litellm_params.default_on", + cell: ({ row }) => { + const guardrail = row.original; + return ( +
+ {guardrail.litellm_params.default_on ? 'Always On' : 'Per Request'} +
+ ); + }, + }, + { + header: "Created At", + accessorKey: "created_at", + cell: ({ row }) => { + const guardrail = row.original; + return ( + + + {formatDate(guardrail.created_at)} + + + ); + }, + }, + { + header: "Updated At", + accessorKey: "updated_at", + cell: ({ row }) => { + const guardrail = row.original; + return ( + + + {formatDate(guardrail.updated_at)} + + + ); + }, + }, + { + id: "actions", + header: "", + cell: ({ row }) => { + const guardrail = row.original; + return ( +
+ guardrail.guardrail_id && handleEditClick(guardrail)} + className="cursor-pointer hover:text-blue-500" + tooltip="Edit guardrail" + /> + guardrail.guardrail_id && onDeleteClick(guardrail.guardrail_id, guardrail.guardrail_name || 'Unnamed Guardrail')} + className="cursor-pointer hover:text-red-500" + tooltip="Delete guardrail" + /> +
+ ); + }, + }, + ]; + + const table = useReactTable({ + data: guardrailsList, + columns, + state: { + sorting, + }, + onSortingChange: setSorting, + getCoreRowModel: getCoreRowModel(), + getSortedRowModel: getSortedRowModel(), + enableSorting: true, + }); + + return ( +
+
+ + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => ( + +
+
+ {header.isPlaceholder ? null : ( + flexRender( + header.column.columnDef.header, + header.getContext() + ) + )} +
+ {header.id !== 'actions' && ( +
+ {header.column.getIsSorted() ? ( + { + asc: , + desc: + }[header.column.getIsSorted() as string] + ) : ( + + )} +
+ )} +
+
+ ))} +
+ ))} +
+ + {isLoading ? ( + + +
+

Loading...

+
+
+
+ ) : guardrailsList.length > 0 ? ( + table.getRowModel().rows.map((row) => ( + + {row.getVisibleCells().map((cell) => ( + + {flexRender(cell.column.columnDef.cell, cell.getContext())} + + ))} + + )) + ) : ( + + +
+

No guardrails found

+
+
+
+ )} +
+
+
+ + {/* Edit Modal */} + {selectedGuardrail && ( + 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 + }} + /> + )} +
+ ); +}; + +export default GuardrailTable; \ No newline at end of file diff --git a/ui/litellm-dashboard/src/components/guardrails/pii_components.tsx b/ui/litellm-dashboard/src/components/guardrails/pii_components.tsx new file mode 100644 index 00000000000..7c120500e01 --- /dev/null +++ b/ui/litellm-dashboard/src/components/guardrails/pii_components.tsx @@ -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 ; + case 'BLOCK': + return ; + default: + return null; + } +}; + +// CategoryFilter component +export interface CategoryFilterProps { + categories: PiiEntityCategory[]; + selectedCategories: string[]; + onChange: (categories: string[]) => void; +} + +export const CategoryFilter: React.FC = ({ + categories, + selectedCategories, + onChange +}) => { + return ( +
+
+ + Filter by category +
+ +
+ ); +}; + +// QuickActions component +export interface QuickActionsProps { + onSelectAll: (action: string) => void; + onUnselectAll: () => void; + hasSelectedEntities: boolean; +} + +export const QuickActions: React.FC = ({ + onSelectAll, + onUnselectAll, + hasSelectedEntities +}) => { + return ( +
+
+
+ Quick Actions + +
ⓘ
+
+
+ +
+
+ + +
+
+ ); +}; + +// 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; +} + +export const PiiEntityList: React.FC = ({ + entities, + selectedEntities, + selectedActions, + actions, + onEntitySelect, + onActionSelect, + entityToCategoryMap +}) => { + return ( +
+
+ PII Type + Action +
+
+ {entities.length === 0 ? ( +
+ No PII types match your filter criteria +
+ ) : ( + entities.map(entity => ( +
+
+ onEntitySelect(entity)} + className="mr-3" + /> + + {formatEntityName(entity)} + + {entityToCategoryMap.get(entity) && ( + {entityToCategoryMap.get(entity)} + )} +
+
+ +
+
+ )) + )} +
+
+ ); +}; \ No newline at end of file diff --git a/ui/litellm-dashboard/src/components/guardrails/pii_configuration.tsx b/ui/litellm-dashboard/src/components/guardrails/pii_configuration.tsx new file mode 100644 index 00000000000..23873803fed --- /dev/null +++ b/ui/litellm-dashboard/src/components/guardrails/pii_configuration.tsx @@ -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 = ({ + entities, + actions, + selectedEntities, + selectedActions, + onEntitySelect, + onActionSelect, + entityCategories = [] +}) => { + const [selectedCategories, setSelectedCategories] = useState([]); + + // Create a lookup map to quickly find an entity's category + const entityToCategoryMap = new Map(); + 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 ( +
+
+
+ Configure PII Protection +
+ 0 ? '#4f46e5' : '#d9d9d9' }} + overflowCount={999} + > + {selectedEntities.length} items selected + +
+ +
+ + + 0} + /> +
+ + +
+ ); +}; + +export default PiiConfiguration; \ No newline at end of file diff --git a/ui/litellm-dashboard/src/components/guardrails/types.ts b/ui/litellm-dashboard/src/components/guardrails/types.ts new file mode 100644 index 00000000000..333b987f58e --- /dev/null +++ b/ui/litellm-dashboard/src/components/guardrails/types.ts @@ -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[]; +} \ No newline at end of file diff --git a/ui/litellm-dashboard/src/components/networking.tsx b/ui/litellm-dashboard/src/components/networking.tsx index 4af134fa1ad..5d12e0c026a 100644 --- a/ui/litellm-dashboard/src/components/networking.tsx +++ b/ui/litellm-dashboard/src/components/networking.tsx @@ -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; + } +};