diff --git a/litellm/proxy/guardrails/guardrail_endpoints.py b/litellm/proxy/guardrails/guardrail_endpoints.py index d9736220c16..a2d30339eda 100644 --- a/litellm/proxy/guardrails/guardrail_endpoints.py +++ b/litellm/proxy/guardrails/guardrail_endpoints.py @@ -19,6 +19,7 @@ from litellm.types.guardrails import ( GuardrailUIAddGuardrailSettings, LakeraV2GuardrailConfigModel, ListGuardrailsResponse, + PatchGuardrailRequest, PiiAction, PiiEntityType, PresidioConfigModel, @@ -434,6 +435,115 @@ async def delete_guardrail(guardrail_id: str): raise HTTPException(status_code=500, detail=str(e)) +@router.patch( + "/guardrails/{guardrail_id}", + tags=["Guardrails"], + dependencies=[Depends(user_api_key_auth)], +) +async def patch_guardrail(guardrail_id: str, request: PatchGuardrailRequest): + """ + Partially update an existing guardrail + + 👉 [Guardrail docs](https://docs.litellm.ai/docs/proxy/guardrails/quick_start) + + This endpoint allows updating specific fields of a guardrail without sending the entire object. + Only the following fields can be updated: + - guardrail_name: The name of the guardrail + - default_on: Whether the guardrail is enabled by default + - guardrail_info: Additional information about the guardrail + + Example Request: + ```bash + curl -X PATCH "http://localhost:4000/guardrails/123e4567-e89b-12d3-a456-426614174000" \\ + -H "Authorization: Bearer " \\ + -H "Content-Type: application/json" \\ + -d '{ + "guardrail_name": "updated-name", + "default_on": true, + "guardrail_info": { + "description": "Updated description" + } + }' + ``` + + Example Response: + ```json + { + "guardrail_id": "123e4567-e89b-12d3-a456-426614174000", + "guardrail_name": "updated-name", + "litellm_params": { + "guardrail": "bedrock", + "mode": "pre_call", + "guardrailIdentifier": "ff6ujrregl1q", + "guardrailVersion": "DRAFT", + "default_on": true + }, + "guardrail_info": { + "description": "Updated description" + }, + "created_at": "2023-11-09T12:34:56.789Z", + "updated_at": "2023-11-09T14:22:33.456Z" + } + ``` + """ + from litellm.proxy.proxy_server import prisma_client + + if prisma_client is None: + raise HTTPException(status_code=500, detail="Prisma client not initialized") + + try: + # Check if guardrail exists and get current data + existing_guardrail = await GUARDRAIL_REGISTRY.get_guardrail_by_id_from_db( + guardrail_id=guardrail_id, prisma_client=prisma_client + ) + + if existing_guardrail is None: + raise HTTPException( + status_code=404, detail=f"Guardrail with ID {guardrail_id} not found" + ) + + # Create updated guardrail object + guardrail_name = ( + request.guardrail_name + if request.guardrail_name is not None + else existing_guardrail.get("guardrail_name") + ) + + # Update litellm_params if default_on is provided + litellm_params = dict(existing_guardrail.get("litellm_params", {})) + if ( + request.litellm_params is not None + and request.litellm_params.default_on is not None + ): + litellm_params["default_on"] = request.litellm_params.default_on + + # Update guardrail_info if provided + guardrail_info = ( + request.guardrail_info + if request.guardrail_info is not None + else existing_guardrail.get("guardrail_info", {}) + ) + + # Create the guardrail object + updated_guardrail = { + "guardrail_name": guardrail_name, + "litellm_params": litellm_params, + "guardrail_info": guardrail_info, + } + + result = await GUARDRAIL_REGISTRY.update_guardrail_in_db( + guardrail_id=guardrail_id, + guardrail=Guardrail(**updated_guardrail), + prisma_client=prisma_client, + ) + return result + except HTTPException as e: + raise e + except Exception as e: + verbose_proxy_logger.exception(f"Error updating guardrail: {e}") + raise HTTPException(status_code=500, detail=str(e)) + + @router.get( "/guardrails/{guardrail_id}/info", tags=["Guardrails"], diff --git a/litellm/proxy/guardrails/guardrail_registry.py b/litellm/proxy/guardrails/guardrail_registry.py index 78332ebecde..1f853f3c025 100644 --- a/litellm/proxy/guardrails/guardrail_registry.py +++ b/litellm/proxy/guardrails/guardrail_registry.py @@ -120,7 +120,7 @@ class GuardrailRegistry: try: guardrail_name = guardrail.get("guardrail_name") litellm_params: str = safe_dumps(dict(guardrail.get("litellm_params", {}))) - guardrail_info = guardrail.get("guardrail_info", {}) + guardrail_info: str = safe_dumps(guardrail.get("guardrail_info", {})) # Update in DB updated_guardrail = await prisma_client.db.litellm_guardrailstable.update( diff --git a/litellm/types/guardrails.py b/litellm/types/guardrails.py index baa1cd9c54a..11b8aceee78 100644 --- a/litellm/types/guardrails.py +++ b/litellm/types/guardrails.py @@ -432,3 +432,13 @@ class ApplyGuardrailRequest(BaseModel): class ApplyGuardrailResponse(BaseModel): response_text: str + + +class PatchGuardrailLitellmParams(BaseModel): + default_on: Optional[bool] = None + + +class PatchGuardrailRequest(BaseModel): + guardrail_name: Optional[str] = None + litellm_params: Optional[PatchGuardrailLitellmParams] = None + guardrail_info: Optional[Dict[str, Any]] = None diff --git a/ui/litellm-dashboard/src/components/guardrails/guardrail_info.tsx b/ui/litellm-dashboard/src/components/guardrails/guardrail_info.tsx index fe07bf30959..eae0097b983 100644 --- a/ui/litellm-dashboard/src/components/guardrails/guardrail_info.tsx +++ b/ui/litellm-dashboard/src/components/guardrails/guardrail_info.tsx @@ -11,10 +11,11 @@ import { TabList, TabPanel, TabPanels, + TextInput, } from "@tremor/react"; import { Button, Form, Input, Select, message, Tooltip } from "antd"; import { InfoCircleOutlined } from '@ant-design/icons'; -import { getGuardrailInfo } from "@/components/networking"; +import { getGuardrailInfo, updateGuardrailCall } from "@/components/networking"; import { getGuardrailLogoAndName } from "./guardrail_info_helpers"; export interface GuardrailInfoProps { @@ -56,11 +57,23 @@ const GuardrailInfoView: React.FC = ({ const handleGuardrailUpdate = async (values: any) => { try { - // Not implemented yet - will be added in the future - message.info("Guardrail update functionality coming soon"); + if (!accessToken) return; + + const updateData = { + guardrail_name: values.guardrail_name, + litellm_params: { + default_on: values.default_on + }, + guardrail_info: values.guardrail_info ? JSON.parse(values.guardrail_info) : undefined + }; + + await updateGuardrailCall(accessToken, guardrailId, updateData); + message.success("Guardrail updated successfully"); + fetchGuardrailInfo(); setIsEditing(false); } catch (error) { console.error("Error updating guardrail:", error); + message.error("Failed to update guardrail"); } }; @@ -139,30 +152,9 @@ const GuardrailInfoView: React.FC = ({ - - Provider Configuration -
- {Object.entries(guardrailData.litellm_params || {}).map(([key, value]) => { - // Skip mode and guardrail as they're displayed above - if (key === 'mode' || key === 'guardrail' || key === 'default_on') return null; - - return ( -
- {key} - - {typeof value === 'object' - ? JSON.stringify(value, null, 2) - : String(value)} - -
- ); - })} -
-
- {guardrailData.guardrail_info && Object.keys(guardrailData.guardrail_info).length > 0 && ( - Additional Information + Guardrail Info
{Object.entries(guardrailData.guardrail_info).map(([key, value]) => (
@@ -212,7 +204,7 @@ const GuardrailInfoView: React.FC = ({ name="guardrail_name" rules={[{ required: true, message: "Please input a guardrail name" }]} > - + = ({ diff --git a/ui/litellm-dashboard/src/components/guardrails/guardrail_table.tsx b/ui/litellm-dashboard/src/components/guardrails/guardrail_table.tsx index 00a1c91e0ad..c7c01e29718 100644 --- a/ui/litellm-dashboard/src/components/guardrails/guardrail_table.tsx +++ b/ui/litellm-dashboard/src/components/guardrails/guardrail_table.tsx @@ -14,7 +14,6 @@ import { SwitchVerticalIcon, ChevronUpIcon, ChevronDownIcon, - PencilIcon, } from "@heroicons/react/outline"; import { Tooltip } from "antd"; import { @@ -210,13 +209,6 @@ const GuardrailTable: React.FC = ({ const guardrail = row.original; return (
- guardrail.guardrail_id && handleEditClick(guardrail)} - className="cursor-pointer hover:text-blue-500" - tooltip="Edit guardrail" - /> { try { - const url = proxyBaseUrl ? `${proxyBaseUrl}/guardrails/${guardrailId}` : `/guardrails/${guardrailId}`; + const url = proxyBaseUrl ? `${proxyBaseUrl}/v2/guardrails/${guardrailId}` : `/v2/guardrails/${guardrailId}`; const response = await fetch(url, { method: "DELETE", @@ -5126,26 +5126,64 @@ export const getGuardrailProviderSpecificParams = async (accessToken: string) => }; export const getGuardrailInfo = async (accessToken: string, guardrailId: string) => { - const url = proxyBaseUrl ? `${proxyBaseUrl}/guardrails/${guardrailId}/info` : `/guardrails/${guardrailId}/info`; - try { + const url = proxyBaseUrl ? `${proxyBaseUrl}/guardrails/${guardrailId}/info` : `/guardrails/${guardrailId}/info`; + const response = await fetch(url, { method: "GET", headers: { + [globalLitellmHeaderName]: `Bearer ${accessToken}`, "Content-Type": "application/json", - ...(accessToken ? { "Authorization": `Bearer ${accessToken}` } : {}) - } + }, }); if (!response.ok) { const errorData = await response.text(); - throw new Error(errorData); + handleError(errorData); + throw new Error("Failed to get guardrail info"); } const data = await response.json(); + console.log("Guardrail info response:", data); return data; } catch (error) { - console.error("Error fetching guardrail info:", error); + console.error("Failed to get guardrail info:", error); + throw error; + } +}; + +export const updateGuardrailCall = async ( + accessToken: string, + guardrailId: string, + updateData: { + guardrail_name?: string; + default_on?: boolean; + guardrail_info?: Record; + } +) => { + try { + const url = proxyBaseUrl ? `${proxyBaseUrl}/guardrails/${guardrailId}` : `/guardrails/${guardrailId}`; + + const response = await fetch(url, { + method: "PATCH", + headers: { + [globalLitellmHeaderName]: `Bearer ${accessToken}`, + "Content-Type": "application/json", + }, + body: JSON.stringify(updateData), + }); + + if (!response.ok) { + const errorData = await response.text(); + handleError(errorData); + throw new Error("Failed to update guardrail"); + } + + const data = await response.json(); + console.log("Update guardrail response:", data); + return data; + } catch (error) { + console.error("Failed to update guardrail:", error); throw error; } };