diff --git a/ui/litellm-dashboard/src/components/networking.tsx b/ui/litellm-dashboard/src/components/networking.tsx index f3b23ababbc..ffc0f80911d 100644 --- a/ui/litellm-dashboard/src/components/networking.tsx +++ b/ui/litellm-dashboard/src/components/networking.tsx @@ -5564,6 +5564,32 @@ export const deletePolicyAttachmentCall = async (accessToken: string, attachment } }; +export const getResolvedGuardrails = async (accessToken: string, policyId: string) => { + try { + const url = proxyBaseUrl ? `${proxyBaseUrl}/policies/${policyId}/resolved-guardrails` : `/policies/${policyId}/resolved-guardrails`; + const response = await fetch(url, { + method: "GET", + headers: { + [globalLitellmHeaderName]: `Bearer ${accessToken}`, + "Content-Type": "application/json", + }, + }); + + if (!response.ok) { + const errorData = await response.json(); + const errorMessage = deriveErrorMessage(errorData); + handleError(errorMessage); + throw new Error(errorMessage); + } + + const data = await response.json(); + return data; + } catch (error) { + console.error("Failed to get resolved guardrails:", error); + throw error; + } +}; + export const getPromptsList = async (accessToken: string): Promise => { try { const url = proxyBaseUrl ? `${proxyBaseUrl}/prompts/list` : `/prompts/list`; diff --git a/ui/litellm-dashboard/src/components/playground/chat_ui/ChatUI.tsx b/ui/litellm-dashboard/src/components/playground/chat_ui/ChatUI.tsx index 3a7f8fec650..b02ccbc8648 100644 --- a/ui/litellm-dashboard/src/components/playground/chat_ui/ChatUI.tsx +++ b/ui/litellm-dashboard/src/components/playground/chat_ui/ChatUI.tsx @@ -30,6 +30,7 @@ import { coy } from "react-syntax-highlighter/dist/esm/styles/prism"; import { v4 as uuidv4 } from "uuid"; import { truncateString } from "../../../utils/textUtils"; import GuardrailSelector from "../../guardrails/GuardrailSelector"; +import PolicySelector from "../../policies/PolicySelector"; import { MCPServer } from "../../mcp_tools/types"; import NotificationsManager from "../../molecules/notifications_manager"; import { fetchMCPServers, listMCPTools } from "../../networking"; @@ -188,6 +189,15 @@ const ChatUI: React.FC = ({ return []; } }); + const [selectedPolicies, setSelectedPolicies] = useState(() => { + const saved = sessionStorage.getItem("selectedPolicies"); + try { + return saved ? JSON.parse(saved) : []; + } catch (error) { + console.error("Error parsing selectedPolicies from sessionStorage", error); + return []; + } + }); const [messageTraceId, setMessageTraceId] = useState( () => sessionStorage.getItem("messageTraceId") || null, ); @@ -261,6 +271,7 @@ const ChatUI: React.FC = ({ selectedTags, selectedVectorStores, selectedGuardrails, + selectedPolicies, selectedMCPServers, mcpServers, mcpServerToolRestrictions, @@ -283,6 +294,7 @@ const ChatUI: React.FC = ({ selectedTags, selectedVectorStores, selectedGuardrails, + selectedPolicies, selectedMCPServers, mcpServers, mcpServerToolRestrictions, @@ -308,6 +320,7 @@ const ChatUI: React.FC = ({ sessionStorage.setItem("selectedTags", JSON.stringify(selectedTags)); sessionStorage.setItem("selectedVectorStores", JSON.stringify(selectedVectorStores)); sessionStorage.setItem("selectedGuardrails", JSON.stringify(selectedGuardrails)); + sessionStorage.setItem("selectedPolicies", JSON.stringify(selectedPolicies)); sessionStorage.setItem("selectedMCPServers", JSON.stringify(selectedMCPServers)); sessionStorage.setItem("mcpServerToolRestrictions", JSON.stringify(mcpServerToolRestrictions)); sessionStorage.setItem("selectedVoice", selectedVoice); @@ -897,6 +910,7 @@ const ChatUI: React.FC = ({ traceId, selectedVectorStores.length > 0 ? selectedVectorStores : undefined, selectedGuardrails.length > 0 ? selectedGuardrails : undefined, + selectedPolicies.length > 0 ? selectedPolicies : undefined, selectedMCPServers, updateChatImageUI, updateSearchResults, @@ -977,6 +991,7 @@ const ChatUI: React.FC = ({ traceId, selectedVectorStores.length > 0 ? selectedVectorStores : undefined, selectedGuardrails.length > 0 ? selectedGuardrails : undefined, + selectedPolicies.length > 0 ? selectedPolicies : undefined, selectedMCPServers, // Pass the selected servers array useApiSessionManagement ? responsesSessionId : null, // Only pass session ID if API mode is enabled handleResponseId, // Pass callback to capture new response ID @@ -1008,6 +1023,7 @@ const ChatUI: React.FC = ({ traceId, selectedVectorStores.length > 0 ? selectedVectorStores : undefined, selectedGuardrails.length > 0 ? selectedGuardrails : undefined, + selectedPolicies.length > 0 ? selectedPolicies : undefined, selectedMCPServers, // Pass the selected tools array customProxyBaseUrl || undefined, ); @@ -1587,6 +1603,32 @@ const ChatUI: React.FC = ({ /> +
+ + Policies + + Select policy/policies to apply to this LLM API call. Policies define which guardrails are applied based on conditions. You can set up your policies{" "} + + here + + . + + } + > + + + + +
+ {/* Code Interpreter Toggle - Only for Responses endpoint */} {endpointType === EndpointType.RESPONSES && (
diff --git a/ui/litellm-dashboard/src/components/playground/chat_ui/CodeSnippets.tsx b/ui/litellm-dashboard/src/components/playground/chat_ui/CodeSnippets.tsx index 84f7f6b9e1c..6998d542401 100644 --- a/ui/litellm-dashboard/src/components/playground/chat_ui/CodeSnippets.tsx +++ b/ui/litellm-dashboard/src/components/playground/chat_ui/CodeSnippets.tsx @@ -6,6 +6,7 @@ interface CodeGenMetadata { tags?: string[]; vector_stores?: string[]; guardrails?: string[]; + policies?: string[]; } interface GenerateCodeParams { @@ -17,6 +18,7 @@ interface GenerateCodeParams { selectedTags: string[]; selectedVectorStores: string[]; selectedGuardrails: string[]; + selectedPolicies: string[]; selectedMCPServers: string[]; mcpServers?: MCPServer[]; mcpServerToolRestrictions?: Record; @@ -40,6 +42,7 @@ export const generateCodeSnippet = (params: GenerateCodeParams): string => { selectedTags, selectedVectorStores, selectedGuardrails, + selectedPolicies, selectedMCPServers, mcpServers, mcpServerToolRestrictions, @@ -72,6 +75,7 @@ export const generateCodeSnippet = (params: GenerateCodeParams): string => { if (selectedTags.length > 0) metadata.tags = selectedTags; if (selectedVectorStores.length > 0) metadata.vector_stores = selectedVectorStores; if (selectedGuardrails.length > 0) metadata.guardrails = selectedGuardrails; + if (selectedPolicies.length > 0) metadata.policies = selectedPolicies; const modelNameForCode = selectedModel || "your-model-name"; diff --git a/ui/litellm-dashboard/src/components/playground/llm_calls/anthropic_messages.tsx b/ui/litellm-dashboard/src/components/playground/llm_calls/anthropic_messages.tsx index 2e8b0be88bb..5570c7408fa 100644 --- a/ui/litellm-dashboard/src/components/playground/llm_calls/anthropic_messages.tsx +++ b/ui/litellm-dashboard/src/components/playground/llm_calls/anthropic_messages.tsx @@ -17,6 +17,7 @@ export async function makeAnthropicMessagesRequest( traceId?: string, vector_store_ids?: string[], guardrails?: string[], + policies?: string[], selectedMCPTools?: string[], customBaseUrl?: string, ) { @@ -59,6 +60,7 @@ export async function makeAnthropicMessagesRequest( if (vector_store_ids) requestBody.vector_store_ids = vector_store_ids; if (guardrails) requestBody.guardrails = guardrails; + if (policies) requestBody.policies = policies; // Use the streaming helper method for cleaner async iteration // @ts-ignore - The SDK types might not include all litellm-specific parameters const stream = client.messages.stream(requestBody, { signal }); diff --git a/ui/litellm-dashboard/src/components/playground/llm_calls/chat_completion.tsx b/ui/litellm-dashboard/src/components/playground/llm_calls/chat_completion.tsx index c3c623c25c3..61d232082e0 100644 --- a/ui/litellm-dashboard/src/components/playground/llm_calls/chat_completion.tsx +++ b/ui/litellm-dashboard/src/components/playground/llm_calls/chat_completion.tsx @@ -19,6 +19,7 @@ export async function makeOpenAIChatCompletionRequest( traceId?: string, vector_store_ids?: string[], guardrails?: string[], + policies?: string[], selectedMCPServers?: string[], onImageGenerated?: (imageUrl: string, model?: string) => void, onSearchResults?: (searchResults: VectorStoreSearchResponse[]) => void, @@ -110,6 +111,7 @@ export async function makeOpenAIChatCompletionRequest( messages: chatHistory as ChatCompletionMessageParam[], ...(vector_store_ids ? { vector_store_ids } : {}), ...(guardrails ? { guardrails } : {}), + ...(policies ? { policies } : {}), ...(tools.length > 0 ? { tools, tool_choice: "auto" } : {}), ...(temperature !== undefined ? { temperature } : {}), ...(max_tokens !== undefined ? { max_tokens } : {}), diff --git a/ui/litellm-dashboard/src/components/playground/llm_calls/responses_api.tsx b/ui/litellm-dashboard/src/components/playground/llm_calls/responses_api.tsx index b658610a21e..c69f82a37bf 100644 --- a/ui/litellm-dashboard/src/components/playground/llm_calls/responses_api.tsx +++ b/ui/litellm-dashboard/src/components/playground/llm_calls/responses_api.tsx @@ -27,6 +27,7 @@ export async function makeOpenAIResponsesRequest( traceId?: string, vector_store_ids?: string[], guardrails?: string[], + policies?: string[], selectedMCPServers?: string[], previousResponseId?: string | null, onResponseId?: (responseId: string) => void, @@ -137,6 +138,7 @@ export async function makeOpenAIResponsesRequest( ...(previousResponseId ? { previous_response_id: previousResponseId } : {}), ...(vector_store_ids ? { vector_store_ids } : {}), ...(guardrails ? { guardrails } : {}), + ...(policies ? { policies } : {}), ...(tools.length > 0 ? { tools, tool_choice: "auto" } : {}), }, { signal }, diff --git a/ui/litellm-dashboard/src/components/policies/PolicySelector.tsx b/ui/litellm-dashboard/src/components/policies/PolicySelector.tsx new file mode 100644 index 00000000000..fd20d9330a3 --- /dev/null +++ b/ui/litellm-dashboard/src/components/policies/PolicySelector.tsx @@ -0,0 +1,77 @@ +import React, { useEffect, useState } from "react"; +import { Select } from "antd"; +import { Policy } from "./types"; +import { getPoliciesList } from "../networking"; + +interface PolicySelectorProps { + onChange: (selectedPolicies: string[]) => void; + value?: string[]; + className?: string; + accessToken: string; + disabled?: boolean; +} + +const PolicySelector: React.FC = ({ + onChange, + value, + className, + accessToken, + disabled +}) => { + const [policies, setPolicies] = useState([]); + const [loading, setLoading] = useState(false); + + useEffect(() => { + const fetchPolicies = async () => { + if (!accessToken) return; + + setLoading(true); + try { + const response = await getPoliciesList(accessToken); + console.log("Policies response:", response); + if (response.policies) { + console.log("Policies data:", response.policies); + setPolicies(response.policies); + } + } catch (error) { + console.error("Error fetching policies:", error); + } finally { + setLoading(false); + } + }; + + fetchPolicies(); + }, [accessToken]); + + const handlePolicyChange = (selectedValues: string[]) => { + console.log("Selected policies:", selectedValues); + onChange(selectedValues); + }; + + return ( +
+ ({ + label: team, + value: team, + }))} tokenSeparators={[","]} + showSearch + filterOption={(input, option) => + (option?.label ?? "").toLowerCase().includes(input.toLowerCase()) + } style={{ width: "100%" }} /> @@ -148,12 +203,21 @@ const AddAttachmentForm: React.FC = ({ ({ + label: model, + value: model, + }))} + filterOption={(input, option) => + (option?.label ?? "").toLowerCase().includes(input.toLowerCase()) + } + style={{ width: "100%" }} + /> + ) : ( + + )}
diff --git a/ui/litellm-dashboard/src/components/policies/policy_info.tsx b/ui/litellm-dashboard/src/components/policies/policy_info.tsx index 7acd0ff1464..a7ea4703ee5 100644 --- a/ui/litellm-dashboard/src/components/policies/policy_info.tsx +++ b/ui/litellm-dashboard/src/components/policies/policy_info.tsx @@ -1,9 +1,9 @@ import React, { useState, useEffect, useCallback } from "react"; import { Card, Badge, Button } from "@tremor/react"; import { ArrowLeftIcon, PencilIcon } from "@heroicons/react/outline"; -import { Descriptions, Tag, Spin, Divider, Typography } from "antd"; +import { Descriptions, Tag, Spin, Divider, Typography, Alert } from "antd"; import { Policy } from "./types"; -import { getPolicyInfo } from "../networking"; +import { getPolicyInfo, getResolvedGuardrails } from "../networking"; const { Title, Text } = Typography; @@ -24,6 +24,8 @@ const PolicyInfoView: React.FC = ({ }) => { const [policy, setPolicy] = useState(null); const [isLoading, setIsLoading] = useState(true); + const [resolvedGuardrails, setResolvedGuardrails] = useState([]); + const [isLoadingResolved, setIsLoadingResolved] = useState(false); const fetchPolicy = useCallback(async () => { if (!accessToken || !policyId) return; @@ -32,6 +34,17 @@ const PolicyInfoView: React.FC = ({ try { const data = await getPolicyInfo(accessToken, policyId); setPolicy(data); + + // Also fetch resolved guardrails + setIsLoadingResolved(true); + try { + const resolvedData = await getResolvedGuardrails(accessToken, policyId); + setResolvedGuardrails(resolvedData.resolved_guardrails || []); + } catch (error) { + console.error("Error fetching resolved guardrails:", error); + } finally { + setIsLoadingResolved(false); + } } catch (error) { console.error("Error fetching policy:", error); } finally { @@ -116,6 +129,29 @@ const PolicyInfoView: React.FC = ({ Guardrails Configuration + {resolvedGuardrails.length > 0 && ( + + + Final guardrails that will be applied (including inheritance): + +
+ {resolvedGuardrails.map((g) => ( + + {g} + + ))} +
+
+ } + type="info" + showIcon + style={{ marginBottom: 16 }} + /> + )} +