[Feat] UI - allow adding pass through guardrails through UI (#17226)

* add PassThroughGuardrailsConfig

* init JsonPathExtractor

* feat PassthroughGuardrailHandler

* feat pt guardrails

* pt guardrails

* add Pass-Through Endpoint Guardrail Translation

* add PassThroughEndpointHandler

* execute simple guardrail config and dict settings

* TestPassthroughGuardrailHandlerNormalizeConfig

* add passthrough_guardrails_config on litellm logging obj

* add LiteLLMLoggingObj to base trasaltino

* cleaner _get_guardrail_settings

* update guardrails settings

* docs pt guardrail

* docs Guardrails on Pass-Through Endpoints

* fix typing

* fix typing

* test_no_fields_set_sends_full_body

* fix typing

* init add pass through guardrails

* ui allow setting target fields on gd

* docs ui settings guardrails
This commit is contained in:
Ishaan Jaff
2025-11-27 12:27:16 -08:00
committed by GitHub
parent d612d71ef4
commit ffb75b04fd
8 changed files with 349 additions and 7 deletions
@@ -1,5 +1,7 @@
# Guardrails on Pass-Through Endpoints
import Image from '@theme/IdealImage';
## Overview
| Property | Details |
@@ -10,7 +12,41 @@
## Quick Start
### 1. Define guardrails and pass-through endpoint
You can configure guardrails on pass-through endpoints either via the **UI** (recommended) or **config file**.
### Using the UI
#### 1. Navigate to Pass-Through Endpoints
Go to **Models + Endpoints** → Click **+ Add Pass-Through Endpoint**
<Image img={require('../../img/pt_guard1.png')} alt="Add guardrails to pass-through endpoint" />
Scroll to the **Guardrails** section and select which guardrails to enforce.
:::tip Default Behavior
By default, you don't need to specify fields - LiteLLM will JSON dump the entire request/response payload and send it to the guardrail.
:::
#### 2. Target Specific Fields (Optional)
<Image img={require('../../img/pt_guard2.png')} alt="Configure field-level targeting" />
To check only specific fields instead of the entire payload:
1. Select your guardrails
2. In **Field Targeting (Optional)**, specify fields for each guardrail
3. Use the quick-add buttons (`+ query`, `+ documents[*]`) or type custom JSONPath expressions
4. **Request Fields (pre_call)**: Fields to check before sending to target API
5. **Response Fields (post_call)**: Fields to check in the response from target API
**Example**: In the screenshot above, we set `query` as a request field, so only the `query` field is sent to the guardrail instead of the entire request.
---
### Using Config File
#### 1. Define guardrails and pass-through endpoint
```yaml showLineNumbers title="config.yaml"
guardrails:
@@ -31,13 +67,13 @@ general_settings:
pii-guard:
```
### 2. Start proxy
#### 2. Start proxy
```bash
litellm --config config.yaml
```
### 3. Test request
#### 3. Test request
```bash
curl -X POST "http://localhost:4000/v1/rerank" \
Binary file not shown.

After

Width:  |  Height:  |  Size: 769 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 548 KiB

@@ -261,7 +261,7 @@ class PassthroughGuardrailHandler:
for guardrail_name in normalized_config.keys():
guardrails_to_run[guardrail_name] = True
verbose_proxy_logger.debug(
"Added passthrough-specific guardrail"
"Added passthrough-specific guardrail: %s", guardrail_name
)
# Add org/team/key level guardrails using shared helper
@@ -279,12 +279,12 @@ class PassthroughGuardrailHandler:
if guardrail_name not in guardrails_to_run:
guardrails_to_run[guardrail_name] = True
verbose_proxy_logger.debug(
"Added inherited guardrail (key/team level)"
"Added inherited guardrail (key/team level): %s", guardrail_name
)
verbose_proxy_logger.debug(
"Collected total guardrails for passthrough endpoint: %d",
len(guardrails_to_run),
"Collected guardrails for passthrough endpoint: %s",
list(guardrails_to_run.keys()),
)
return guardrails_to_run if guardrails_to_run else None
@@ -27,6 +27,7 @@ import { passThroughItem } from "./pass_through_settings";
import RoutePreview from "./route_preview";
import NotificationsManager from "./molecules/notifications_manager";
import PassThroughSecuritySection from "./common_components/PassThroughSecuritySection";
import PassThroughGuardrailsSection from "./common_components/PassThroughGuardrailsSection";
const { Option } = Select2;
interface AddFallbacksProps {
@@ -51,11 +52,13 @@ const AddPassThroughEndpoint: React.FC<AddFallbacksProps> = ({
const [targetValue, setTargetValue] = useState("");
const [includeSubpath, setIncludeSubpath] = useState(true);
const [authEnabled, setAuthEnabled] = useState(false);
const [guardrails, setGuardrails] = useState<Record<string, { request_fields?: string[]; response_fields?: string[] } | null>>({});
const handleCancel = () => {
form.resetFields();
setPathValue("");
setTargetValue("");
setIncludeSubpath(true);
setGuardrails({});
setIsModalVisible(false);
};
@@ -77,6 +80,12 @@ const AddPassThroughEndpoint: React.FC<AddFallbacksProps> = ({
if (!premiumUser && 'auth' in formValues) {
delete formValues.auth;
}
// Add guardrails to formValues (only if not empty)
if (guardrails && Object.keys(guardrails).length > 0) {
formValues.guardrails = guardrails;
}
console.log(`formValues: ${JSON.stringify(formValues)}`);
const response = await createPassThroughEndpoint(accessToken, formValues);
@@ -92,6 +101,7 @@ const AddPassThroughEndpoint: React.FC<AddFallbacksProps> = ({
setPathValue("");
setTargetValue("");
setIncludeSubpath(true);
setGuardrails({});
setIsModalVisible(false);
} catch (error) {
NotificationsManager.fromBackend("Error creating pass-through endpoint: " + error);
@@ -249,6 +259,14 @@ const AddPassThroughEndpoint: React.FC<AddFallbacksProps> = ({
form.setFieldsValue({ auth: checked });
}}
/>
{/* Guardrails Section */}
<PassThroughGuardrailsSection
accessToken={accessToken}
value={guardrails}
onChange={setGuardrails}
/>
{/* Billing Section */}
<Card className="p-6">
<Title className="text-lg font-semibold text-gray-900 mb-2">Billing</Title>
@@ -0,0 +1,246 @@
import React, { useState, useEffect } from "react";
import { Card, Title, Subtitle } from "@tremor/react";
import { Form, Input, Select, Tooltip, Alert } from "antd";
import { InfoCircleOutlined, PlusOutlined, DeleteOutlined } from "@ant-design/icons";
import GuardrailSelector from "../guardrails/GuardrailSelector";
interface PassThroughGuardrailsSectionProps {
accessToken: string;
value?: Record<string, { request_fields?: string[]; response_fields?: string[] } | null>;
onChange?: (guardrails: Record<string, { request_fields?: string[]; response_fields?: string[] } | null>) => void;
disabled?: boolean;
}
const PassThroughGuardrailsSection: React.FC<PassThroughGuardrailsSectionProps> = ({
accessToken,
value = {},
onChange,
disabled = false,
}) => {
const [selectedGuardrails, setSelectedGuardrails] = useState<string[]>(Object.keys(value));
const [guardrailSettings, setGuardrailSettings] = useState<
Record<string, { request_fields?: string[]; response_fields?: string[] } | null>
>(value);
// Sync external value changes
useEffect(() => {
setGuardrailSettings(value);
setSelectedGuardrails(Object.keys(value));
}, [value]);
const handleGuardrailChange = (guardrails: string[]) => {
setSelectedGuardrails(guardrails);
// Create new settings object with selected guardrails
const newSettings: Record<string, { request_fields?: string[]; response_fields?: string[] } | null> = {};
guardrails.forEach((name) => {
// Preserve existing settings or set to null (uses entire payload)
newSettings[name] = guardrailSettings[name] || null;
});
setGuardrailSettings(newSettings);
if (onChange) {
onChange(newSettings);
}
};
const handleFieldChange = (
guardrailName: string,
fieldType: "request_fields" | "response_fields",
fields: string[]
) => {
const currentSettings = guardrailSettings[guardrailName] || {};
const newSettings = {
...guardrailSettings,
[guardrailName]: {
...currentSettings,
[fieldType]: fields.length > 0 ? fields : undefined,
},
};
// If no fields are set, set to null (entire payload)
if (!newSettings[guardrailName]?.request_fields && !newSettings[guardrailName]?.response_fields) {
newSettings[guardrailName] = null;
}
setGuardrailSettings(newSettings);
if (onChange) {
onChange(newSettings);
}
};
return (
<Card className="p-6">
<Title className="text-lg font-semibold text-gray-900 mb-2">Guardrails</Title>
<Subtitle className="text-gray-600 mb-6">
Configure guardrails to enforce policies on requests and responses. Guardrails are opt-in for passthrough
endpoints.
</Subtitle>
<Alert
message={
<span>
Field-Level Targeting{" "}
<a
href="https://docs.litellm.ai/docs/proxy/pass_through_guardrails#field-level-targeting"
target="_blank"
rel="noopener noreferrer"
className="text-blue-600 hover:text-blue-800 underline"
>
(Learn More)
</a>
</span>
}
description={
<div className="space-y-2">
<div>
Optionally specify which fields to check. If left empty, the entire request/response is sent to the guardrail.
</div>
<div className="text-xs space-y-1 mt-2">
<div className="font-medium">Common Examples:</div>
<div> <code className="bg-gray-100 px-1 rounded">query</code> - Single field</div>
<div> <code className="bg-gray-100 px-1 rounded">documents[*].text</code> - All text in documents array</div>
<div> <code className="bg-gray-100 px-1 rounded">messages[*].content</code> - All message contents</div>
</div>
</div>
}
type="info"
showIcon
className="mb-4"
/>
<Form.Item
label={
<span className="text-sm font-medium text-gray-700 flex items-center">
Select Guardrails
<Tooltip title="Choose which guardrails should run on this endpoint. Org/team/key level guardrails will also be included.">
<InfoCircleOutlined className="ml-2 text-blue-400 hover:text-blue-600 cursor-help" />
</Tooltip>
</span>
}
>
<GuardrailSelector
accessToken={accessToken}
value={selectedGuardrails}
onChange={handleGuardrailChange}
disabled={disabled}
/>
</Form.Item>
{selectedGuardrails.length > 0 && (
<div className="mt-6 space-y-4">
<div className="flex items-center justify-between mb-3">
<div className="text-sm font-medium text-gray-700">Field Targeting (Optional)</div>
<div className="text-xs text-gray-500">
💡 Tip: Leave empty to check entire payload
</div>
</div>
{selectedGuardrails.map((guardrailName) => (
<Card key={guardrailName} className="p-4 bg-gray-50">
<div className="text-sm font-medium text-gray-900 mb-3">{guardrailName}</div>
<div className="space-y-3">
<div>
<div className="flex items-center justify-between mb-1">
<label className="text-xs text-gray-600 flex items-center">
Request Fields (pre_call)
<Tooltip title={
<div>
<div className="font-medium mb-1">Specify which request fields to check</div>
<div className="text-xs space-y-1">
<div>Examples:</div>
<div> query</div>
<div> documents[*].text</div>
<div> messages[*].content</div>
</div>
</div>
}>
<InfoCircleOutlined className="ml-1 text-gray-400" />
</Tooltip>
</label>
<div className="flex gap-1">
<button
type="button"
onClick={() => {
const current = guardrailSettings[guardrailName]?.request_fields || [];
handleFieldChange(guardrailName, "request_fields", [...current, "query"]);
}}
className="text-xs px-2 py-1 bg-white border border-gray-300 rounded hover:bg-gray-50"
disabled={disabled}
>
+ query
</button>
<button
type="button"
onClick={() => {
const current = guardrailSettings[guardrailName]?.request_fields || [];
handleFieldChange(guardrailName, "request_fields", [...current, "documents[*]"]);
}}
className="text-xs px-2 py-1 bg-white border border-gray-300 rounded hover:bg-gray-50"
disabled={disabled}
>
+ documents[*]
</button>
</div>
</div>
<Select
mode="tags"
style={{ width: "100%" }}
placeholder="Type field name or use + buttons above (e.g., query, documents[*].text)"
value={guardrailSettings[guardrailName]?.request_fields || []}
onChange={(fields) => handleFieldChange(guardrailName, "request_fields", fields)}
disabled={disabled}
tokenSeparators={[","]}
/>
</div>
<div>
<div className="flex items-center justify-between mb-1">
<label className="text-xs text-gray-600 flex items-center">
Response Fields (post_call)
<Tooltip title={
<div>
<div className="font-medium mb-1">Specify which response fields to check</div>
<div className="text-xs space-y-1">
<div>Examples:</div>
<div> results[*].text</div>
<div> choices[*].message.content</div>
</div>
</div>
}>
<InfoCircleOutlined className="ml-1 text-gray-400" />
</Tooltip>
</label>
<div className="flex gap-1">
<button
type="button"
onClick={() => {
const current = guardrailSettings[guardrailName]?.response_fields || [];
handleFieldChange(guardrailName, "response_fields", [...current, "results[*]"]);
}}
className="text-xs px-2 py-1 bg-white border border-gray-300 rounded hover:bg-gray-50"
disabled={disabled}
>
+ results[*]
</button>
</div>
</div>
<Select
mode="tags"
style={{ width: "100%" }}
placeholder="Type field name or use + buttons above (e.g., results[*].text)"
value={guardrailSettings[guardrailName]?.response_fields || []}
onChange={(fields) => handleFieldChange(guardrailName, "response_fields", fields)}
disabled={disabled}
tokenSeparators={[","]}
/>
</div>
</div>
</Card>
))}
</div>
)}
</Card>
);
};
export default PassThroughGuardrailsSection;
@@ -19,6 +19,7 @@ import { Eye, EyeOff } from "lucide-react";
import RoutePreview from "./route_preview";
import NotificationsManager from "./molecules/notifications_manager";
import PassThroughSecuritySection from "./common_components/PassThroughSecuritySection";
import PassThroughGuardrailsSection from "./common_components/PassThroughGuardrailsSection";
export interface PassThroughInfoProps {
endpointData: PassThroughEndpoint;
@@ -37,6 +38,7 @@ interface PassThroughEndpoint {
include_subpath?: boolean;
cost_per_request?: number;
auth?: boolean;
guardrails?: Record<string, { request_fields?: string[]; response_fields?: string[] } | null>;
}
// Password field component for headers
@@ -68,6 +70,9 @@ const PassThroughInfoView: React.FC<PassThroughInfoProps> = ({
const [loading, setLoading] = useState(false);
const [isEditing, setIsEditing] = useState(false);
const [authEnabled, setAuthEnabled] = useState(initialEndpointData?.auth || false);
const [guardrails, setGuardrails] = useState<Record<string, { request_fields?: string[]; response_fields?: string[] } | null>>(
initialEndpointData?.guardrails || {}
);
const [form] = Form.useForm();
const handleEndpointUpdate = async (values: any) => {
@@ -92,6 +97,7 @@ const PassThroughInfoView: React.FC<PassThroughInfoProps> = ({
include_subpath: values.include_subpath,
cost_per_request: values.cost_per_request,
auth: premiumUser ? values.auth : undefined,
guardrails: guardrails && Object.keys(guardrails).length > 0 ? guardrails : undefined,
};
await updatePassThroughEndpoint(accessToken, endpointData.id, updateData);
@@ -214,6 +220,33 @@ const PassThroughInfoView: React.FC<PassThroughInfoProps> = ({
</div>
</Card>
)}
{endpointData.guardrails && Object.keys(endpointData.guardrails).length > 0 && (
<Card className="mt-6">
<div className="flex justify-between items-center">
<Text className="font-medium">Guardrails</Text>
<Badge color="purple">{Object.keys(endpointData.guardrails).length} guardrails configured</Badge>
</div>
<div className="mt-4 space-y-2">
{Object.entries(endpointData.guardrails).map(([name, settings]) => (
<div key={name} className="p-3 bg-gray-50 rounded">
<div className="font-medium text-sm">{name}</div>
{settings && (settings.request_fields || settings.response_fields) && (
<div className="mt-2 text-xs text-gray-600 space-y-1">
{settings.request_fields && (
<div>Request fields: {settings.request_fields.join(", ")}</div>
)}
{settings.response_fields && (
<div>Response fields: {settings.response_fields.join(", ")}</div>
)}
</div>
)}
{!settings && <div className="text-xs text-gray-600 mt-1">Uses entire payload</div>}
</div>
))}
</div>
</Card>
)}
</TabPanel>
{/* Settings Panel (only for admins) */}
@@ -279,6 +312,14 @@ const PassThroughInfoView: React.FC<PassThroughInfoProps> = ({
}}
/>
<div className="mt-4">
<PassThroughGuardrailsSection
accessToken={accessToken || ""}
value={guardrails}
onChange={setGuardrails}
/>
</div>
<div className="flex justify-end gap-2 mt-6">
<Button onClick={() => setIsEditing(false)}>Cancel</Button>
<TremorButton>Save Changes</TremorButton>
@@ -39,6 +39,7 @@ export interface passThroughItem {
include_subpath?: boolean;
cost_per_request?: number;
auth?: boolean;
guardrails?: Record<string, { request_fields?: string[]; response_fields?: string[] } | null>;
}
// Password field component for headers