[UI] Allow editing guardrails (#10907)

* ui qa guardrails

* ui fixes guardrail info

* feat: add patch guardrail endpoint

* fix: update_guardrail_in_db

* ui add support for editing guardrails
This commit is contained in:
Ishaan Jaff 2025-05-16 18:35:03 -07:00 committed by GitHub
parent d8525ecbfa
commit 76de6e374c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 185 additions and 43 deletions

View file

@ -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 <your_api_key>" \\
-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"],

View file

@ -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(

View file

@ -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

View file

@ -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<GuardrailInfoProps> = ({
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<GuardrailInfoProps> = ({
</Card>
</Grid>
<Card className="mt-6">
<Text>Provider Configuration</Text>
<div className="mt-2 space-y-2">
{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 (
<div key={key} className="flex">
<Text className="font-medium w-1/3">{key}</Text>
<Text className="w-2/3">
{typeof value === 'object'
? JSON.stringify(value, null, 2)
: String(value)}
</Text>
</div>
);
})}
</div>
</Card>
{guardrailData.guardrail_info && Object.keys(guardrailData.guardrail_info).length > 0 && (
<Card className="mt-6">
<Text>Additional Information</Text>
<Text>Guardrail Info</Text>
<div className="mt-2 space-y-2">
{Object.entries(guardrailData.guardrail_info).map(([key, value]) => (
<div key={key} className="flex">
@ -212,7 +204,7 @@ const GuardrailInfoView: React.FC<GuardrailInfoProps> = ({
name="guardrail_name"
rules={[{ required: true, message: "Please input a guardrail name" }]}
>
<Input />
<TextInput />
</Form.Item>
<Form.Item
@ -226,7 +218,7 @@ const GuardrailInfoView: React.FC<GuardrailInfoProps> = ({
</Form.Item>
<Form.Item
label="Additional Information"
label="Guardrail Information"
name="guardrail_info"
>
<Input.TextArea rows={5} />

View file

@ -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<GuardrailTableProps> = ({
const guardrail = row.original;
return (
<div className="flex space-x-2">
<Icon
icon={PencilIcon}
size="sm"
onClick={() => guardrail.guardrail_id && handleEditClick(guardrail)}
className="cursor-pointer hover:text-blue-500"
tooltip="Edit guardrail"
/>
<Icon
icon={TrashIcon}
size="sm"

View file

@ -5046,7 +5046,7 @@ 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 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<string, any>;
}
) => {
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;
}
};