diff --git a/litellm-proxy-extras/litellm_proxy_extras/migrations/20260228153253_baseline_diff/migration.sql b/litellm-proxy-extras/litellm_proxy_extras/migrations/20260228153253_baseline_diff/migration.sql deleted file mode 100644 index 0a72f2182f4..00000000000 --- a/litellm-proxy-extras/litellm_proxy_extras/migrations/20260228153253_baseline_diff/migration.sql +++ /dev/null @@ -1,6 +0,0 @@ --- DropIndex -DROP INDEX "LiteLLM_GuardrailsTable_guardrail_name_team_id_key"; - --- CreateIndex -CREATE UNIQUE INDEX "LiteLLM_GuardrailsTable_guardrail_name_key" ON "LiteLLM_GuardrailsTable"("guardrail_name"); - diff --git a/litellm-proxy-extras/litellm_proxy_extras/migrations/20260228155047_support_team_based_guardrails/migration.sql b/litellm-proxy-extras/litellm_proxy_extras/migrations/20260228155047_support_team_based_guardrails/migration.sql new file mode 100644 index 00000000000..72018aaa59f --- /dev/null +++ b/litellm-proxy-extras/litellm_proxy_extras/migrations/20260228155047_support_team_based_guardrails/migration.sql @@ -0,0 +1,10 @@ +-- AlterTable +ALTER TABLE "LiteLLM_GuardrailsTable" ADD COLUMN "reviewed_at" TIMESTAMP(3), +ADD COLUMN "status" TEXT NOT NULL DEFAULT 'active', +ADD COLUMN "submitted_at" TIMESTAMP(3), +ADD COLUMN "submitted_by_email" TEXT, +ADD COLUMN "submitted_by_user_id" TEXT; + +-- CreateIndex +CREATE INDEX "LiteLLM_GuardrailsTable_status_idx" ON "LiteLLM_GuardrailsTable"("status"); + diff --git a/litellm-proxy-extras/litellm_proxy_extras/migrations/20260228155437_baseline_diff/migration.sql b/litellm-proxy-extras/litellm_proxy_extras/migrations/20260228155437_baseline_diff/migration.sql new file mode 100644 index 00000000000..2f725d83806 --- /dev/null +++ b/litellm-proxy-extras/litellm_proxy_extras/migrations/20260228155437_baseline_diff/migration.sql @@ -0,0 +1,2 @@ +-- This is an empty migration. + diff --git a/litellm-proxy-extras/litellm_proxy_extras/migrations/20260228160000_add_guardrail_submission_status/migration.sql b/litellm-proxy-extras/litellm_proxy_extras/migrations/20260228160000_add_guardrail_submission_status/migration.sql deleted file mode 100644 index 2ef4901b274..00000000000 --- a/litellm-proxy-extras/litellm_proxy_extras/migrations/20260228160000_add_guardrail_submission_status/migration.sql +++ /dev/null @@ -1,10 +0,0 @@ --- AlterTable: add submission lifecycle columns to LiteLLM_GuardrailsTable --- status: pending_review (team-registered), active (approved), rejected. Default active for existing rows. -ALTER TABLE "LiteLLM_GuardrailsTable" ADD COLUMN "status" TEXT NOT NULL DEFAULT 'active'; -ALTER TABLE "LiteLLM_GuardrailsTable" ADD COLUMN "submitted_by_user_id" TEXT; -ALTER TABLE "LiteLLM_GuardrailsTable" ADD COLUMN "submitted_by_email" TEXT; -ALTER TABLE "LiteLLM_GuardrailsTable" ADD COLUMN "submitted_at" TIMESTAMP(3); -ALTER TABLE "LiteLLM_GuardrailsTable" ADD COLUMN "reviewed_at" TIMESTAMP(3); - --- CreateIndex -CREATE INDEX "LiteLLM_GuardrailsTable_status_idx" ON "LiteLLM_GuardrailsTable"("status"); diff --git a/litellm/proxy/guardrails/guardrail_endpoints.py b/litellm/proxy/guardrails/guardrail_endpoints.py index 6568864e87f..45c509a7f50 100644 --- a/litellm/proxy/guardrails/guardrail_endpoints.py +++ b/litellm/proxy/guardrails/guardrail_endpoints.py @@ -4,6 +4,7 @@ CRUD ENDPOINTS FOR GUARDRAILS import concurrent.futures import inspect +import json from datetime import datetime, timezone from typing import Any, Dict, List, Optional, Type, TypeVar, Union, cast @@ -524,6 +525,387 @@ async def delete_guardrail( raise HTTPException(status_code=500, detail=str(e)) +# --- Team guardrail registration (Generic Guardrail API spec) --- + +GENERIC_GUARDRAIL_API = "generic_guardrail_api" + + +class RegisterGuardrailRequest(BaseModel): + """Request body for POST /guardrails/register. Follows Generic Guardrail API config.""" + + guardrail_name: str + litellm_params: Dict[str, Any] # guardrail, mode, api_base required; api_key, headers, etc. optional + guardrail_info: Optional[Dict[str, Any]] = None + + def get_litellm_params_dict(self) -> Dict[str, Any]: + return dict(self.litellm_params) + + +class RegisterGuardrailResponse(BaseModel): + guardrail_id: str + guardrail_name: str + status: str + submitted_at: Optional[datetime] = None + + +class GuardrailSubmissionSummary(BaseModel): + total: int + pending_review: int + active: int + rejected: int + + +class GuardrailSubmissionItem(BaseModel): + guardrail_id: str + guardrail_name: str + status: str + team_id: Optional[str] = None + litellm_params: Optional[Dict[str, Any]] = None + guardrail_info: Optional[Dict[str, Any]] = None + submitted_by_user_id: Optional[str] = None + submitted_by_email: Optional[str] = None + submitted_at: Optional[datetime] = None + reviewed_at: Optional[datetime] = None + created_at: Optional[datetime] = None + updated_at: Optional[datetime] = None + + +class ListGuardrailSubmissionsResponse(BaseModel): + submissions: List[GuardrailSubmissionItem] + summary: GuardrailSubmissionSummary + + +@router.post( + "/guardrails/register", + tags=["Guardrails"], + response_model=RegisterGuardrailResponse, +) +async def register_guardrail( + request: RegisterGuardrailRequest, + user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth), +): + """ + Register a guardrail for onboarding (team submission). + + Accepts a guardrail config in the + [Generic Guardrail API](https://docs.litellm.ai/docs/adding_provider/generic_guardrail_api) format. + The submission is stored with status `pending_review` until an admin approves it. + """ + from litellm.proxy.proxy_server import prisma_client + + if prisma_client is None: + raise HTTPException(status_code=500, detail="Prisma client not initialized") + + if not user_api_key_dict.team_id: + raise HTTPException( + status_code=400, + detail="Registration requires an API key associated with a team. Use a team-scoped key.", + ) + + params = request.get_litellm_params_dict() + if params.get("guardrail") != GENERIC_GUARDRAIL_API: + raise HTTPException( + status_code=400, + detail=f"Only guardrails with litellm_params.guardrail={GENERIC_GUARDRAIL_API!r} are accepted for registration", + ) + if not params.get("api_base"): + raise HTTPException( + status_code=400, + detail="litellm_params.api_base is required for generic_guardrail_api", + ) + mode = params.get("mode") + if mode is None: + raise HTTPException( + status_code=400, + detail="litellm_params.mode is required (e.g. pre_call, post_call)", + ) + + try: + existing = await prisma_client.db.litellm_guardrailstable.find_unique( + where={"guardrail_name": request.guardrail_name} + ) + if existing is not None: + raise HTTPException( + status_code=400, + detail=f"Guardrail with name {request.guardrail_name!r} already exists", + ) + except HTTPException: + raise + except Exception as e: + verbose_proxy_logger.exception("Error checking guardrail name uniqueness: %s", e) + raise HTTPException(status_code=500, detail=str(e)) + + now = datetime.now(timezone.utc) + litellm_params_str = safe_dumps(params) + guardrail_info_str = safe_dumps(request.guardrail_info or {}) + + try: + created = await prisma_client.db.litellm_guardrailstable.create( + data={ + "guardrail_name": request.guardrail_name, + "litellm_params": litellm_params_str, + "guardrail_info": guardrail_info_str, + "status": "pending_review", + "team_id": user_api_key_dict.team_id, + "submitted_by_user_id": user_api_key_dict.user_id, + "submitted_by_email": user_api_key_dict.user_email, + "submitted_at": now, + "created_at": now, + "updated_at": now, + } + ) + return RegisterGuardrailResponse( + guardrail_id=created.guardrail_id, + guardrail_name=created.guardrail_name, + status=created.status, + submitted_at=created.submitted_at, + ) + except Exception as e: + verbose_proxy_logger.exception("Error registering guardrail: %s", e) + raise HTTPException(status_code=500, detail=str(e)) + + +def _parse_json_field(value: Any) -> Optional[Dict[str, Any]]: + if value is None: + return None + if isinstance(value, dict): + return value + if isinstance(value, str): + try: + return json.loads(value) + except Exception: + return None + return None + + +def _row_to_submission_item(row: Any) -> GuardrailSubmissionItem: + return GuardrailSubmissionItem( + guardrail_id=row.guardrail_id, + guardrail_name=row.guardrail_name, + status=row.status or "active", + team_id=row.team_id, + litellm_params=_parse_json_field(row.litellm_params), + guardrail_info=_parse_json_field(row.guardrail_info), + submitted_by_user_id=getattr(row, "submitted_by_user_id", None), + submitted_by_email=getattr(row, "submitted_by_email", None), + submitted_at=getattr(row, "submitted_at", None), + reviewed_at=getattr(row, "reviewed_at", None), + created_at=row.created_at, + updated_at=row.updated_at, + ) + + +@router.get( + "/guardrails/submissions", + tags=["Guardrails"], + response_model=ListGuardrailSubmissionsResponse, +) +async def list_guardrail_submissions( + status: Optional[str] = None, + team_id: Optional[str] = None, + search: Optional[str] = None, + user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth), +): + """ + List guardrail submissions (admin only). Optional filters: status, team_id, search (name/description). + """ + from litellm.proxy.proxy_server import prisma_client + + if user_api_key_dict.user_role != LitellmUserRoles.PROXY_ADMIN: + raise HTTPException(status_code=403, detail="Admin access required") + + if prisma_client is None: + raise HTTPException(status_code=500, detail="Prisma client not initialized") + + try: + where: Dict[str, Any] = {} + if status: + where["status"] = status + if team_id: + where["team_id"] = team_id + + rows = await prisma_client.db.litellm_guardrailstable.find_many( + where=where, + order={"created_at": "desc"}, + ) + + # Summary counts (all rows for consistent counts) + all_rows = await prisma_client.db.litellm_guardrailstable.find_many() + total = len(all_rows) + pending_review = sum(1 for r in all_rows if (r.status or "active") == "pending_review") + active_count = sum(1 for r in all_rows if (r.status or "active") == "active") + rejected = sum(1 for r in all_rows if (r.status or "active") == "rejected") + + if search: + search_lower = search.lower() + rows = [ + r + for r in rows + if search_lower in (r.guardrail_name or "").lower() + or (isinstance(r.guardrail_info, dict) and search_lower in str((r.guardrail_info or {}).get("description", "")).lower()) + or (isinstance(r.guardrail_info, str) and search_lower in r.guardrail_info.lower()) + ] + + items = [] + for r in rows: + items.append(_row_to_submission_item(r)) + return ListGuardrailSubmissionsResponse( + submissions=items, + summary=GuardrailSubmissionSummary( + total=total, + pending_review=pending_review, + active=active_count, + rejected=rejected, + ), + ) + except Exception as e: + verbose_proxy_logger.exception("Error listing guardrail submissions: %s", e) + raise HTTPException(status_code=500, detail=str(e)) + + +@router.get( + "/guardrails/submissions/{guardrail_id}", + tags=["Guardrails"], + response_model=GuardrailSubmissionItem, +) +async def get_guardrail_submission( + guardrail_id: str, + user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth), +): + """Get a single guardrail submission by id (admin only).""" + from litellm.proxy.proxy_server import prisma_client + + if user_api_key_dict.user_role != LitellmUserRoles.PROXY_ADMIN: + raise HTTPException(status_code=403, detail="Admin access required") + + if prisma_client is None: + raise HTTPException(status_code=500, detail="Prisma client not initialized") + + try: + row = await prisma_client.db.litellm_guardrailstable.find_unique( + where={"guardrail_id": guardrail_id} + ) + if row is None: + raise HTTPException(status_code=404, detail="Guardrail submission not found") + return _row_to_submission_item(row) + except HTTPException: + raise + except Exception as e: + verbose_proxy_logger.exception("Error getting guardrail submission: %s", e) + raise HTTPException(status_code=500, detail=str(e)) + + +@router.post( + "/guardrails/submissions/{guardrail_id}/approve", + tags=["Guardrails"], +) +async def approve_guardrail_submission( + guardrail_id: str, + user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth), +): + """Approve a pending guardrail submission: set status to active and initialize in memory (admin only).""" + from litellm.proxy.guardrails.guardrail_registry import \ + IN_MEMORY_GUARDRAIL_HANDLER + from litellm.proxy.proxy_server import prisma_client + + if user_api_key_dict.user_role != LitellmUserRoles.PROXY_ADMIN: + raise HTTPException(status_code=403, detail="Admin access required") + + if prisma_client is None: + raise HTTPException(status_code=500, detail="Prisma client not initialized") + + try: + row = await prisma_client.db.litellm_guardrailstable.find_unique( + where={"guardrail_id": guardrail_id} + ) + if row is None: + raise HTTPException(status_code=404, detail="Guardrail submission not found") + if row.status != "pending_review": + raise HTTPException( + status_code=400, + detail=f"Guardrail is not pending review (status={row.status})", + ) + + now = datetime.now(timezone.utc) + await prisma_client.db.litellm_guardrailstable.update( + where={"guardrail_id": guardrail_id}, + data={"status": "active", "reviewed_at": now, "updated_at": now}, + ) + + litellm_params = _parse_json_field(row.litellm_params) + guardrail_info = _parse_json_field(row.guardrail_info) + if not litellm_params: + raise HTTPException( + status_code=500, + detail="Guardrail litellm_params is missing or invalid", + ) + guardrail_dict = { + "guardrail_id": row.guardrail_id, + "guardrail_name": row.guardrail_name, + "litellm_params": litellm_params, + "guardrail_info": guardrail_info or {}, + } + try: + IN_MEMORY_GUARDRAIL_HANDLER.initialize_guardrail( + guardrail=cast(Guardrail, guardrail_dict) + ) + verbose_proxy_logger.info( + "Approved guardrail %s (ID: %s) and initialized in memory", + row.guardrail_name, + guardrail_id, + ) + except Exception as init_err: + verbose_proxy_logger.warning( + "Failed to initialize approved guardrail %s in memory: %s", + guardrail_id, + init_err, + ) + + return {"guardrail_id": guardrail_id, "status": "active", "message": "Guardrail approved"} + except HTTPException: + raise + except Exception as e: + verbose_proxy_logger.exception("Error approving guardrail submission: %s", e) + raise HTTPException(status_code=500, detail=str(e)) + + +@router.post( + "/guardrails/submissions/{guardrail_id}/reject", + tags=["Guardrails"], +) +async def reject_guardrail_submission( + guardrail_id: str, + user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth), +): + """Reject a guardrail submission (admin only).""" + from litellm.proxy.proxy_server import prisma_client + + if user_api_key_dict.user_role != LitellmUserRoles.PROXY_ADMIN: + raise HTTPException(status_code=403, detail="Admin access required") + + if prisma_client is None: + raise HTTPException(status_code=500, detail="Prisma client not initialized") + + try: + row = await prisma_client.db.litellm_guardrailstable.find_unique( + where={"guardrail_id": guardrail_id} + ) + if row is None: + raise HTTPException(status_code=404, detail="Guardrail submission not found") + + now = datetime.now(timezone.utc) + await prisma_client.db.litellm_guardrailstable.update( + where={"guardrail_id": guardrail_id}, + data={"status": "rejected", "reviewed_at": now, "updated_at": now}, + ) + return {"guardrail_id": guardrail_id, "status": "rejected", "message": "Guardrail rejected"} + except HTTPException: + raise + except Exception as e: + verbose_proxy_logger.exception("Error rejecting guardrail submission: %s", e) + raise HTTPException(status_code=500, detail=str(e)) + + @router.patch( "/guardrails/{guardrail_id}", tags=["Guardrails"], diff --git a/scripts/test_guardrails_register_endpoints.sh b/scripts/test_guardrails_register_endpoints.sh new file mode 100755 index 00000000000..89fd53b5b8c --- /dev/null +++ b/scripts/test_guardrails_register_endpoints.sh @@ -0,0 +1,126 @@ +#!/usr/bin/env bash +# +# Test guardrail register and submissions endpoints. +# Requires: proxy running with DB (migrations applied), valid admin API key. +# +# Usage: +# export LITELLM_API_KEY="sk-..." # required, use an admin key +# ./scripts/test_guardrails_register_endpoints.sh +# BASE_URL=http://localhost:4000 LITELLM_API_KEY="sk-..." ./scripts/test_guardrails_register_endpoints.sh +# +set -euo pipefail + +BASE_URL="${BASE_URL:-http://localhost:4000}" +API_KEY="${LITELLM_API_KEY:-}" + +if ! command -v jq &>/dev/null; then + echo "Error: jq is required. Install with: brew install jq (macOS) or apt-get install jq (Linux)" + exit 1 +fi + +if [[ -z "$API_KEY" ]]; then + echo "Error: LITELLM_API_KEY is not set. Use an admin key to test list/approve/reject." + exit 1 +fi + +AUTH_HEADER="Authorization: Bearer $API_KEY" +TIMESTAMP=$(date +%s) +NAME_APPROVE="test-guardrail-approve-$TIMESTAMP" +NAME_REJECT="test-guardrail-reject-$TIMESTAMP" + +echo "BASE_URL=$BASE_URL" +echo "Testing guardrail register and submissions endpoints..." +echo "" + +# --- 1. Register a guardrail (will approve later) --- +echo "[1/6] POST /guardrails/register (guardrail: $NAME_APPROVE)" +REGISTER_RESPONSE=$(curl -s -w "\n%{http_code}" -X POST "$BASE_URL/guardrails/register" \ + -H "$AUTH_HEADER" \ + -H "Content-Type: application/json" \ + -d "{ + \"guardrail_name\": \"$NAME_APPROVE\", + \"litellm_params\": { + \"guardrail\": \"generic_guardrail_api\", + \"mode\": \"pre_call\", + \"api_base\": \"https://guardrails.example.com/validate\" + }, + \"guardrail_info\": { \"description\": \"Test guardrail for approve flow\" } + }") +REGISTER_HTTP=$(echo "$REGISTER_RESPONSE" | tail -n1) +REGISTER_BODY=$(echo "$REGISTER_RESPONSE" | sed '$d') +if [[ "$REGISTER_HTTP" -ne 200 ]]; then + echo " FAIL: expected 200, got $REGISTER_HTTP" + echo "$REGISTER_BODY" | jq . 2>/dev/null || echo "$REGISTER_BODY" + exit 1 +fi +GUARDRAIL_ID_APPROVE=$(echo "$REGISTER_BODY" | jq -r '.guardrail_id') +echo " OK (201/200) guardrail_id=$GUARDRAIL_ID_APPROVE" + +# --- 2. Register a second guardrail (will reject later) --- +echo "[2/6] POST /guardrails/register (guardrail: $NAME_REJECT)" +REJECT_RESPONSE=$(curl -s -w "\n%{http_code}" -X POST "$BASE_URL/guardrails/register" \ + -H "$AUTH_HEADER" \ + -H "Content-Type: application/json" \ + -d "{ + \"guardrail_name\": \"$NAME_REJECT\", + \"litellm_params\": { + \"guardrail\": \"generic_guardrail_api\", + \"mode\": \"post_call\", + \"api_base\": \"https://guardrails.example.com/reject-test\" + }, + \"guardrail_info\": { \"description\": \"Test guardrail for reject flow\" } + }") +REJECT_HTTP=$(echo "$REJECT_RESPONSE" | tail -n1) +if [[ "$REJECT_HTTP" -ne 200 ]]; then + echo " FAIL: expected 200, got $REJECT_HTTP" + echo "$REJECT_RESPONSE" | sed '$d' | jq . 2>/dev/null || echo "$REJECT_RESPONSE" + exit 1 +fi +GUARDRAIL_ID_REJECT=$(echo "$REJECT_RESPONSE" | sed '$d' | jq -r '.guardrail_id') +echo " OK guardrail_id=$GUARDRAIL_ID_REJECT" + +# --- 3. List submissions (admin) --- +echo "[3/6] GET /guardrails/submissions" +LIST_RESPONSE=$(curl -s -w "\n%{http_code}" -X GET "$BASE_URL/guardrails/submissions" -H "$AUTH_HEADER") +LIST_HTTP=$(echo "$LIST_RESPONSE" | tail -n1) +LIST_BODY=$(echo "$LIST_RESPONSE" | sed '$d') +if [[ "$LIST_HTTP" -ne 200 ]]; then + echo " FAIL: expected 200, got $LIST_HTTP" + echo "$LIST_BODY" | jq . 2>/dev/null || echo "$LIST_BODY" + exit 1 +fi +echo " OK summary: $(echo "$LIST_BODY" | jq -c '.summary' 2>/dev/null || echo "N/A")" + +# --- 4. Get one submission by id --- +echo "[4/6] GET /guardrails/submissions/$GUARDRAIL_ID_APPROVE" +GET_RESPONSE=$(curl -s -w "\n%{http_code}" -X GET "$BASE_URL/guardrails/submissions/$GUARDRAIL_ID_APPROVE" -H "$AUTH_HEADER") +GET_HTTP=$(echo "$GET_RESPONSE" | tail -n1) +if [[ "$GET_HTTP" -ne 200 ]]; then + echo " FAIL: expected 200, got $GET_HTTP" + exit 1 +fi +echo " OK status=$(echo "$GET_RESPONSE" | sed '$d' | jq -r '.status')" + +# --- 5. Approve first submission --- +echo "[5/6] POST /guardrails/submissions/$GUARDRAIL_ID_APPROVE/approve" +APPROVE_RESPONSE=$(curl -s -w "\n%{http_code}" -X POST "$BASE_URL/guardrails/submissions/$GUARDRAIL_ID_APPROVE/approve" -H "$AUTH_HEADER") +APPROVE_HTTP=$(echo "$APPROVE_RESPONSE" | tail -n1) +if [[ "$APPROVE_HTTP" -ne 200 ]]; then + echo " FAIL: expected 200, got $APPROVE_HTTP" + echo "$APPROVE_RESPONSE" | sed '$d' | jq . 2>/dev/null || echo "$APPROVE_RESPONSE" + exit 1 +fi +echo " OK $(echo "$APPROVE_RESPONSE" | sed '$d' | jq -c '.' 2>/dev/null)" + +# --- 6. Reject second submission --- +echo "[6/6] POST /guardrails/submissions/$GUARDRAIL_ID_REJECT/reject" +REJECT_POST_RESPONSE=$(curl -s -w "\n%{http_code}" -X POST "$BASE_URL/guardrails/submissions/$GUARDRAIL_ID_REJECT/reject" -H "$AUTH_HEADER") +REJECT_POST_HTTP=$(echo "$REJECT_POST_RESPONSE" | tail -n1) +if [[ "$REJECT_POST_HTTP" -ne 200 ]]; then + echo " FAIL: expected 200, got $REJECT_POST_HTTP" + exit 1 +fi +echo " OK $(echo "$REJECT_POST_RESPONSE" | sed '$d' | jq -c '.' 2>/dev/null)" + +echo "" +echo "All 6 requests succeeded. Guardrail register and submissions endpoints are working." diff --git a/tests/test_litellm/proxy/guardrails/test_guardrail_endpoints.py b/tests/test_litellm/proxy/guardrails/test_guardrail_endpoints.py index 0ac3637b380..06fbec2a61b 100644 --- a/tests/test_litellm/proxy/guardrails/test_guardrail_endpoints.py +++ b/tests/test_litellm/proxy/guardrails/test_guardrail_endpoints.py @@ -15,30 +15,19 @@ from fastapi import HTTPException from litellm.proxy._types import LitellmUserRoles, UserAPIKeyAuth from litellm.proxy.guardrails.guardrail_endpoints import ( - CreateGuardrailRequest, - PatchGuardrailRequest, - UpdateGuardrailRequest, - apply_guardrail, - create_guardrail, - delete_guardrail, - get_guardrail_info, - list_guardrails_v2, - patch_guardrail, - update_guardrail, -) + CreateGuardrailRequest, PatchGuardrailRequest, RegisterGuardrailRequest, + UpdateGuardrailRequest, apply_guardrail, approve_guardrail_submission, + create_guardrail, delete_guardrail, get_guardrail_info, + get_guardrail_submission, list_guardrail_submissions, list_guardrails_v2, + patch_guardrail, register_guardrail, reject_guardrail_submission, + update_guardrail) MOCK_ADMIN_USER = UserAPIKeyAuth(user_role=LitellmUserRoles.PROXY_ADMIN) from litellm.proxy.guardrails.guardrail_registry import ( - IN_MEMORY_GUARDRAIL_HANDLER, - InMemoryGuardrailHandler, -) -from litellm.types.guardrails import ( - ApplyGuardrailRequest, - BaseLitellmParams, - Guardrail, - GuardrailInfoResponse, - LitellmParams, -) + IN_MEMORY_GUARDRAIL_HANDLER, InMemoryGuardrailHandler) +from litellm.types.guardrails import (ApplyGuardrailRequest, BaseLitellmParams, + Guardrail, GuardrailInfoResponse, + LitellmParams) # Mock data for testing MOCK_DB_GUARDRAIL = { @@ -320,10 +309,10 @@ async def test_get_guardrail_info_not_found( def test_get_provider_specific_params(): """Test getting provider-specific parameters""" - from litellm.proxy.guardrails.guardrail_endpoints import _get_fields_from_model - from litellm.proxy.guardrails.guardrail_hooks.azure import ( - AzureContentSafetyTextModerationGuardrail, - ) + from litellm.proxy.guardrails.guardrail_endpoints import \ + _get_fields_from_model + from litellm.proxy.guardrails.guardrail_hooks.azure import \ + AzureContentSafetyTextModerationGuardrail config_model = AzureContentSafetyTextModerationGuardrail.get_config_model() if config_model is None: @@ -388,8 +377,10 @@ def test_optional_params_not_returned_when_not_overridden(): from pydantic import BaseModel, Field - from litellm.proxy.guardrails.guardrail_endpoints import _get_fields_from_model - from litellm.types.proxy.guardrails.guardrail_hooks.base import GuardrailConfigModel + from litellm.proxy.guardrails.guardrail_endpoints import \ + _get_fields_from_model + from litellm.types.proxy.guardrails.guardrail_hooks.base import \ + GuardrailConfigModel class TestGuardrailConfig(GuardrailConfigModel): api_key: Optional[str] = Field( @@ -417,8 +408,10 @@ def test_optional_params_returned_when_properly_overridden(): from pydantic import BaseModel, Field - from litellm.proxy.guardrails.guardrail_endpoints import _get_fields_from_model - from litellm.types.proxy.guardrails.guardrail_hooks.base import GuardrailConfigModel + from litellm.proxy.guardrails.guardrail_endpoints import \ + _get_fields_from_model + from litellm.types.proxy.guardrails.guardrail_hooks.base import \ + GuardrailConfigModel # Create specific optional params model class SpecificOptionalParams(BaseModel): @@ -454,9 +447,8 @@ async def test_bedrock_guardrail_prepare_request_with_api_key(): """Test _prepare_request method uses Bearer token when api_key is provided in data""" from unittest.mock import Mock, patch - from litellm.proxy.guardrails.guardrail_hooks.bedrock_guardrails import ( - BedrockGuardrail, - ) + from litellm.proxy.guardrails.guardrail_hooks.bedrock_guardrails import \ + BedrockGuardrail # Setup guardrail hook guardrail_hook = BedrockGuardrail( @@ -491,9 +483,8 @@ async def test_bedrock_guardrail_prepare_request_without_api_key(): """Test _prepare_request method falls back to SigV4 when no api_key is provided""" from unittest.mock import Mock, patch - from litellm.proxy.guardrails.guardrail_hooks.bedrock_guardrails import ( - BedrockGuardrail, - ) + from litellm.proxy.guardrails.guardrail_hooks.bedrock_guardrails import \ + BedrockGuardrail # Setup guardrail hook guardrail_hook = BedrockGuardrail( @@ -544,9 +535,8 @@ async def test_bedrock_guardrail_prepare_request_with_bearer_token_env(): """Test _prepare_request method uses Bearer token from environment when available""" from unittest.mock import Mock, patch - from litellm.proxy.guardrails.guardrail_hooks.bedrock_guardrails import ( - BedrockGuardrail, - ) + from litellm.proxy.guardrails.guardrail_hooks.bedrock_guardrails import \ + BedrockGuardrail # Setup guardrail hook guardrail_hook = BedrockGuardrail( @@ -590,9 +580,8 @@ async def test_bedrock_guardrail_make_api_request_passes_api_key(): """Test make_bedrock_api_request method correctly passes api_key from request_data""" from unittest.mock import AsyncMock, Mock, patch - from litellm.proxy.guardrails.guardrail_hooks.bedrock_guardrails import ( - BedrockGuardrail, - ) + from litellm.proxy.guardrails.guardrail_hooks.bedrock_guardrails import \ + BedrockGuardrail guardrail_hook = BedrockGuardrail( guardrailIdentifier="test-guardrail-id", @@ -1103,4 +1092,205 @@ async def test_get_guardrail_info_endpoint_db_guardrail(mocker): assert isinstance(result, GuardrailInfoResponse) assert result.guardrail_id == "test-db-guardrail" assert result.guardrail_name == "Test DB Guardrail" - assert result.guardrail_definition_location == "db" \ No newline at end of file + assert result.guardrail_definition_location == "db" + + +# --- Team guardrail registration (register / submissions) --- + +MOCK_REGISTER_REQUEST = RegisterGuardrailRequest( + guardrail_name="team-prompt-guard", + litellm_params={ + "guardrail": "generic_guardrail_api", + "mode": "pre_call", + "api_base": "https://guardrails.example.com/validate", + }, + guardrail_info={"description": "Team prompt injection detector"}, +) + + +@pytest.mark.asyncio +async def test_register_guardrail_success(mocker): + """Register creates a row with status pending_review and returns guardrail_id.""" + mock_prisma = mocker.Mock() + mock_prisma.db.litellm_guardrailstable.find_unique = AsyncMock(return_value=None) + created_row = mocker.Mock( + guardrail_id="reg-123", + guardrail_name=MOCK_REGISTER_REQUEST.guardrail_name, + status="pending_review", + submitted_at=datetime.now(), + ) + mock_prisma.db.litellm_guardrailstable.create = AsyncMock(return_value=created_row) + mocker.patch("litellm.proxy.proxy_server.prisma_client", mock_prisma) + + user = UserAPIKeyAuth(user_id="u1", user_email="alice@co.com", team_id="team-1") + result = await register_guardrail(MOCK_REGISTER_REQUEST, user) + + assert result.guardrail_id == "reg-123" + assert result.guardrail_name == MOCK_REGISTER_REQUEST.guardrail_name + assert result.status == "pending_review" + mock_prisma.db.litellm_guardrailstable.create.assert_called_once() + call_data = mock_prisma.db.litellm_guardrailstable.create.call_args[1]["data"] + assert call_data["status"] == "pending_review" + assert call_data["guardrail_name"] == MOCK_REGISTER_REQUEST.guardrail_name + + +@pytest.mark.asyncio +async def test_register_guardrail_rejects_non_generic_api(mocker): + """Register returns 400 when litellm_params.guardrail is not generic_guardrail_api.""" + mocker.patch("litellm.proxy.proxy_server.prisma_client", mocker.Mock()) + req = RegisterGuardrailRequest( + guardrail_name="other-guard", + litellm_params={"guardrail": "bedrock", "mode": "pre_call", "api_base": "https://x.com"}, + ) + user = UserAPIKeyAuth(user_id="u1", user_email="a@b.com") + + with pytest.raises(HTTPException) as exc_info: + await register_guardrail(req, user) + assert exc_info.value.status_code == 400 + assert "generic_guardrail_api" in exc_info.value.detail + + +@pytest.mark.asyncio +async def test_register_guardrail_requires_team_id(mocker): + """Register returns 400 when API key has no associated team_id.""" + mocker.patch("litellm.proxy.proxy_server.prisma_client", mocker.Mock()) + user = UserAPIKeyAuth(user_id="u1", user_email="a@b.com", team_id=None) + + with pytest.raises(HTTPException) as exc_info: + await register_guardrail(MOCK_REGISTER_REQUEST, user) + assert exc_info.value.status_code == 400 + assert "team" in exc_info.value.detail.lower() + + +@pytest.mark.asyncio +async def test_register_guardrail_duplicate_name(mocker): + """Register returns 400 when guardrail_name already exists.""" + mock_prisma = mocker.Mock() + mock_prisma.db.litellm_guardrailstable.find_unique = AsyncMock( + return_value={"guardrail_name": MOCK_REGISTER_REQUEST.guardrail_name} + ) + mocker.patch("litellm.proxy.proxy_server.prisma_client", mock_prisma) + user = UserAPIKeyAuth(user_id="u1", user_email="a@b.com", team_id="team-1") + + with pytest.raises(HTTPException) as exc_info: + await register_guardrail(MOCK_REGISTER_REQUEST, user) + assert exc_info.value.status_code == 400 + assert "already exists" in exc_info.value.detail + + +@pytest.mark.asyncio +async def test_list_guardrail_submissions_requires_admin(mocker): + """List submissions returns 403 when user is not admin.""" + mocker.patch("litellm.proxy.proxy_server.prisma_client", mocker.Mock()) + user = UserAPIKeyAuth(user_role=LitellmUserRoles.INTERNAL_USER) + + with pytest.raises(HTTPException) as exc_info: + await list_guardrail_submissions(user_api_key_dict=user) + assert exc_info.value.status_code == 403 + + +@pytest.mark.asyncio +async def test_list_guardrail_submissions_success(mocker): + """List submissions returns list and summary for admin.""" + mock_prisma = mocker.Mock() + row = mocker.Mock( + guardrail_id="sub-1", + guardrail_name="pending-guard", + status="pending_review", + team_id="t1", + litellm_params={"guardrail": "generic_guardrail_api", "api_base": "https://x.com"}, + guardrail_info={"description": "A guard"}, + submitted_by_user_id="u1", + submitted_by_email="alice@co.com", + submitted_at=datetime.now(), + reviewed_at=None, + created_at=datetime.now(), + updated_at=datetime.now(), + ) + mock_prisma.db.litellm_guardrailstable.find_many = AsyncMock(return_value=[row]) + mocker.patch("litellm.proxy.proxy_server.prisma_client", mock_prisma) + user = UserAPIKeyAuth(user_role=LitellmUserRoles.PROXY_ADMIN) + + result = await list_guardrail_submissions(user_api_key_dict=user) + + assert len(result.submissions) == 1 + assert result.submissions[0].guardrail_id == "sub-1" + assert result.submissions[0].status == "pending_review" + assert result.summary.total >= 1 + assert result.summary.pending_review >= 1 + + +@pytest.mark.asyncio +async def test_get_guardrail_submission_not_found(mocker): + """Get submission returns 404 when guardrail_id does not exist.""" + mock_prisma = mocker.Mock() + mock_prisma.db.litellm_guardrailstable.find_unique = AsyncMock(return_value=None) + mocker.patch("litellm.proxy.proxy_server.prisma_client", mock_prisma) + user = UserAPIKeyAuth(user_role=LitellmUserRoles.PROXY_ADMIN) + + with pytest.raises(HTTPException) as exc_info: + await get_guardrail_submission("nonexistent-id", user) + assert exc_info.value.status_code == 404 + + +@pytest.mark.asyncio +async def test_approve_guardrail_submission_success(mocker): + """Approve sets status to active and initializes guardrail in memory.""" + mock_prisma = mocker.Mock() + row = mocker.Mock( + guardrail_id="approve-me", + guardrail_name="my-guard", + status="pending_review", + litellm_params={"guardrail": "generic_guardrail_api", "mode": "pre_call", "api_base": "https://g.com"}, + guardrail_info={}, + ) + mock_prisma.db.litellm_guardrailstable.find_unique = AsyncMock(return_value=row) + mock_prisma.db.litellm_guardrailstable.update = AsyncMock() + mocker.patch("litellm.proxy.proxy_server.prisma_client", mock_prisma) + mock_handler = mocker.Mock() + mock_handler.initialize_guardrail = mocker.Mock() + mocker.patch( + "litellm.proxy.guardrails.guardrail_registry.IN_MEMORY_GUARDRAIL_HANDLER", + mock_handler, + ) + user = UserAPIKeyAuth(user_role=LitellmUserRoles.PROXY_ADMIN) + + result = await approve_guardrail_submission("approve-me", user) + + assert result["status"] == "active" + assert result["guardrail_id"] == "approve-me" + mock_prisma.db.litellm_guardrailstable.update.assert_called_once() + call_data = mock_prisma.db.litellm_guardrailstable.update.call_args[1]["data"] + assert call_data["status"] == "active" + + +@pytest.mark.asyncio +async def test_approve_guardrail_submission_not_pending(mocker): + """Approve returns 400 when status is not pending_review.""" + mock_prisma = mocker.Mock() + row = mocker.Mock(guardrail_id="x", guardrail_name="y", status="active") + mock_prisma.db.litellm_guardrailstable.find_unique = AsyncMock(return_value=row) + mocker.patch("litellm.proxy.proxy_server.prisma_client", mock_prisma) + user = UserAPIKeyAuth(user_role=LitellmUserRoles.PROXY_ADMIN) + + with pytest.raises(HTTPException) as exc_info: + await approve_guardrail_submission("x", user) + assert exc_info.value.status_code == 400 + + +@pytest.mark.asyncio +async def test_reject_guardrail_submission_success(mocker): + """Reject sets status to rejected.""" + mock_prisma = mocker.Mock() + row = mocker.Mock(guardrail_id="rej-1", guardrail_name="r", status="pending_review") + mock_prisma.db.litellm_guardrailstable.find_unique = AsyncMock(return_value=row) + mock_prisma.db.litellm_guardrailstable.update = AsyncMock() + mocker.patch("litellm.proxy.proxy_server.prisma_client", mock_prisma) + user = UserAPIKeyAuth(user_role=LitellmUserRoles.PROXY_ADMIN) + + result = await reject_guardrail_submission("rej-1", user) + + assert result["status"] == "rejected" + mock_prisma.db.litellm_guardrailstable.update.assert_called_once() + call_data = mock_prisma.db.litellm_guardrailstable.update.call_args[1]["data"] + assert call_data["status"] == "rejected" \ No newline at end of file diff --git a/ui/litellm-dashboard/src/components/guardrails.tsx b/ui/litellm-dashboard/src/components/guardrails.tsx index 9c2475c7aea..a299b410071 100644 --- a/ui/litellm-dashboard/src/components/guardrails.tsx +++ b/ui/litellm-dashboard/src/components/guardrails.tsx @@ -1,15 +1,256 @@ -import React from "react"; -import { GuardrailsPage } from "./guardrails/GuardrailsPage"; +import React, { useState, useEffect } from "react"; +import { Button, TabGroup, TabList, Tab, TabPanels, TabPanel } from "@tremor/react"; +import { Dropdown } from "antd"; +import { DownOutlined, PlusOutlined, CodeOutlined } from "@ant-design/icons"; +import { getGuardrailsList, deleteGuardrailCall } from "./networking"; +import AddGuardrailForm from "./guardrails/add_guardrail_form"; +import GuardrailTable from "./guardrails/guardrail_table"; +import { isAdminRole } from "@/utils/roles"; +import GuardrailInfoView from "./guardrails/guardrail_info"; +import GuardrailTestPlayground from "./guardrails/GuardrailTestPlayground"; +import NotificationsManager from "./molecules/notifications_manager"; +import { Guardrail, GuardrailDefinitionLocation } from "./guardrails/types"; +import DeleteResourceModal from "./common_components/DeleteResourceModal"; +import { getGuardrailLogoAndName } from "./guardrails/guardrail_info_helpers"; +import { CustomCodeModal } from "./guardrails/custom_code"; +import GuardrailGarden from "./guardrails/guardrail_garden"; +import { TeamGuardrailsTab } from "./guardrails/TeamGuardrailsTab"; interface GuardrailsPanelProps { accessToken: string | null; userRole?: string; } -const GuardrailsPanel: React.FC = ({ accessToken }) => { +interface GuardrailItem { + guardrail_id?: string; + guardrail_name: string | null; + litellm_params: { + guardrail: string; + mode: string; + default_on: boolean; + }; + guardrail_info: Record | null; + created_at?: string; + updated_at?: string; + guardrail_definition_location: GuardrailDefinitionLocation; +} + +interface GuardrailsResponse { + guardrails: Guardrail[]; +} + +const GuardrailsPanel: React.FC = ({ accessToken, userRole }) => { + const [guardrailsList, setGuardrailsList] = useState([]); + const [isAddModalVisible, setIsAddModalVisible] = useState(false); + const [isCustomCodeModalVisible, setIsCustomCodeModalVisible] = useState(false); + const [isLoading, setIsLoading] = useState(false); + const [isDeleting, setIsDeleting] = useState(false); + const [guardrailToDelete, setGuardrailToDelete] = useState(null); + const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false); + const [selectedGuardrailId, setSelectedGuardrailId] = useState(null); + const [activeTab, setActiveTab] = useState(0); + + const isAdmin = userRole ? isAdminRole(userRole) : false; + + const fetchGuardrails = async () => { + if (!accessToken) { + return; + } + + setIsLoading(true); + try { + const response: GuardrailsResponse = await getGuardrailsList(accessToken); + console.log(`guardrails: ${JSON.stringify(response)}`); + setGuardrailsList(response.guardrails); + } catch (error) { + console.error("Error fetching guardrails:", error); + } finally { + setIsLoading(false); + } + }; + + useEffect(() => { + fetchGuardrails(); + }, [accessToken]); + + const handleAddGuardrail = () => { + if (selectedGuardrailId) { + setSelectedGuardrailId(null); + } + setIsAddModalVisible(true); + }; + + const handleAddCustomCodeGuardrail = () => { + if (selectedGuardrailId) { + setSelectedGuardrailId(null); + } + setIsCustomCodeModalVisible(true); + }; + + const handleCloseModal = () => { + setIsAddModalVisible(false); + }; + + const handleCloseCustomCodeModal = () => { + setIsCustomCodeModalVisible(false); + }; + + const handleSuccess = () => { + fetchGuardrails(); + }; + + const handleDeleteClick = (guardrailId: string, guardrailName: string) => { + const guardrail = guardrailsList.find((g) => g.guardrail_id === guardrailId) || null; + setGuardrailToDelete(guardrail); + setIsDeleteModalOpen(true); + }; + + const handleDeleteConfirm = async () => { + if (!guardrailToDelete || !accessToken) return; + + setIsDeleting(true); + try { + await deleteGuardrailCall(accessToken, guardrailToDelete.guardrail_id); + NotificationsManager.success(`Guardrail "${guardrailToDelete.guardrail_name}" deleted successfully`); + await fetchGuardrails(); + } catch (error) { + console.error("Error deleting guardrail:", error); + NotificationsManager.fromBackend("Failed to delete guardrail"); + } finally { + setIsDeleting(false); + setIsDeleteModalOpen(false); + setGuardrailToDelete(null); + } + }; + + const handleDeleteCancel = () => { + setIsDeleteModalOpen(false); + setGuardrailToDelete(null); + }; + + const providerDisplayName = + guardrailToDelete && guardrailToDelete.litellm_params + ? getGuardrailLogoAndName(guardrailToDelete.litellm_params.guardrail).displayName + : undefined; + return (
- + + + Guardrail Garden + Guardrails + Test Playground + Team Guardrails + + + + {/* Guardrail Garden Tab */} + + + + + {/* Existing Guardrails Tab */} + +
+ , + label: "Add Provider Guardrail", + onClick: handleAddGuardrail, + }, + { + key: "custom_code", + icon: , + label: "Create Custom Code Guardrail", + onClick: handleAddCustomCodeGuardrail, + }, + ], + }} + trigger={["click"]} + disabled={!accessToken} + > + + +
+ + {selectedGuardrailId ? ( + setSelectedGuardrailId(null)} + accessToken={accessToken} + isAdmin={isAdmin} + /> + ) : ( + setSelectedGuardrailId(id)} + /> + )} + + + + + + +
+ + {/* Test Playground Tab */} + + setActiveTab(0)} + /> + + + {/* Team Guardrails Tab */} + + + +
+
); }; diff --git a/ui/litellm-dashboard/src/components/guardrails/GuardrailGardenTab.tsx b/ui/litellm-dashboard/src/components/guardrails/GuardrailGardenTab.tsx deleted file mode 100644 index 58879db6ece..00000000000 --- a/ui/litellm-dashboard/src/components/guardrails/GuardrailGardenTab.tsx +++ /dev/null @@ -1,177 +0,0 @@ -"use client"; - -import React, { useState } from "react"; -import { - SearchIcon, - ArrowRightIcon, - CheckCircleIcon, - ShieldIcon, -} from "lucide-react"; - -const MOCK_GARDEN_CARDS = [ - { - title: "Denied Financial Advice", - description: - "Detects requests for personalized financial advice, investment recommendations, or financial...", - f1Score: "100%", - testCases: 207, - }, - { - title: "Insults & Personal Attacks", - description: - "Detects insults, name-calling, and personal attacks directed at the chatbot, staff, or other people.", - f1Score: "100%", - testCases: 299, - }, - { - title: "Denied Legal Advice", - description: - "Detects requests for unauthorized legal advice, case analysis, or legal recommendations.", - }, - { - title: "Denied Medical Advice", - description: - "Detects requests for medical diagnosis, treatment recommendations, or health advice.", - }, - { - title: "Harmful Violence", - description: - "Detects content related to violence, criminal planning, attacks, and violent threats.", - }, - { - title: "Harmful Self-Harm", - description: - "Detects content related to self-harm, suicide, and dangerous self-destructive behavior.", - }, - { - title: "Harmful Child Safety", - description: - "Detects content that could endanger child safety or exploit minors.", - }, - { - title: "Harmful Illegal Weapons", - description: - "Detects content related to illegal weapons manufacturing, distribution, or acquisition.", - }, - { - title: "Bias: Gender", - description: - "Detects gender-based discrimination, stereotypes, and biased language.", - }, - { - title: "Bias: Racial", - description: - "Detects racial discrimination, stereotypes, and racially biased content.", - }, -]; - -type GuardrailCardProps = { - title: string; - description: string; - f1Score?: string; - testCases?: number; -}; - -function GuardrailCard({ - title, - description, - f1Score, - testCases, -}: GuardrailCardProps) { - return ( -
-
-
- -
-

- {title} -

-
-

- {description} -

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

- LiteLLM Content Filter -

- -
-

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

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

Guardrails

-

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

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

- Test Playground -

-

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

-
-
-
- -