diff --git a/litellm/types/utils.py b/litellm/types/utils.py index 9b3308a28fc..7b75784326c 100644 --- a/litellm/types/utils.py +++ b/litellm/types/utils.py @@ -2077,6 +2077,7 @@ class StandardCallbackDynamicParams(TypedDict, total=False): # Arize dynamic params arize_api_key: Optional[str] arize_space_key: Optional[str] + arize_space_id: Optional[str] # Logging settings turn_off_message_logging: Optional[bool] # when true will not log messages diff --git a/ui/litellm-dashboard/public/assets/logos/arize.png b/ui/litellm-dashboard/public/assets/logos/arize.png new file mode 100644 index 00000000000..92a7741141c Binary files /dev/null and b/ui/litellm-dashboard/public/assets/logos/arize.png differ diff --git a/ui/litellm-dashboard/src/components/callback_info_helpers.tsx b/ui/litellm-dashboard/src/components/callback_info_helpers.tsx index 17502f103a1..954bb0c8ab8 100644 --- a/ui/litellm-dashboard/src/components/callback_info_helpers.tsx +++ b/ui/litellm-dashboard/src/components/callback_info_helpers.tsx @@ -8,6 +8,7 @@ export enum Callbacks { OpenMeter = "OpenMeter", OTel = "Open Telemetry", S3 = "S3", + Arize = "Arize", } export const callback_map: Record = { @@ -20,18 +21,74 @@ export const callback_map: Record = { OpenMeter: "openmeter", OTel: "otel", S3: "s3", + Arize: "arize", } const asset_logos_folder = '/ui/assets/logos/'; -export const callbackLogoMap: Record = { - [Callbacks.Braintrust]: `${asset_logos_folder}braintrust.png`, - [Callbacks.Datadog]: `${asset_logos_folder}datadog.png`, - [Callbacks.Langfuse]: `${asset_logos_folder}langfuse.png`, - [Callbacks.LangSmith]: `${asset_logos_folder}langsmith.png`, - [Callbacks.Lago]: `${asset_logos_folder}lago.svg`, - [Callbacks.OpenMeter]: `${asset_logos_folder}openmeter.png`, - [Callbacks.OTel]: `${asset_logos_folder}otel.png`, - [Callbacks.S3]: `${asset_logos_folder}aws.svg`, - [Callbacks.CustomCallbackAPI]: `${asset_logos_folder}custom.svg`, +interface CallbackInfo { + logo: string; + supports_key_team_logging: boolean; + dynamic_params: Record; +} + +export const callbackInfo: Record = { + [Callbacks.Langfuse]: { + logo: `${asset_logos_folder}langfuse.png`, + supports_key_team_logging: true, + dynamic_params: { + "langfuse_public_key": "text", + "langfuse_secret_key": "password", + "langfuse_host": "text" + } + }, + [Callbacks.Arize]: { + logo: `${asset_logos_folder}arize.png`, + supports_key_team_logging: true, + dynamic_params: { + "arize_api_key": "password", + "arize_space_id": "text", + } + }, + [Callbacks.Braintrust]: { + logo: `${asset_logos_folder}braintrust.png`, + supports_key_team_logging: false, + dynamic_params: {} + }, + [Callbacks.CustomCallbackAPI]: { + logo: `${asset_logos_folder}custom.svg`, + supports_key_team_logging: false, + dynamic_params: {} + }, + [Callbacks.Datadog]: { + logo: `${asset_logos_folder}datadog.png`, + supports_key_team_logging: false, + dynamic_params: {} + }, + [Callbacks.LangSmith]: { + logo: `${asset_logos_folder}langsmith.png`, + supports_key_team_logging: false, + dynamic_params: {} + }, + [Callbacks.Lago]: { + logo: `${asset_logos_folder}lago.svg`, + supports_key_team_logging: false, + dynamic_params: {} + }, + [Callbacks.OpenMeter]: { + logo: `${asset_logos_folder}openmeter.png`, + supports_key_team_logging: false, + dynamic_params: {} + }, + [Callbacks.OTel]: { + logo: `${asset_logos_folder}otel.png`, + supports_key_team_logging: false, + dynamic_params: {} + }, + [Callbacks.S3]: { + logo: `${asset_logos_folder}aws.svg`, + supports_key_team_logging: false, + dynamic_params: {} + } }; + \ No newline at end of file diff --git a/ui/litellm-dashboard/src/components/settings.tsx b/ui/litellm-dashboard/src/components/settings.tsx index 8d03b92abc8..3c6e21a5de8 100644 --- a/ui/litellm-dashboard/src/components/settings.tsx +++ b/ui/litellm-dashboard/src/components/settings.tsx @@ -46,7 +46,7 @@ import AlertingSettings from "./alerting/alerting_settings"; import FormItem from "antd/es/form/FormItem"; import { callback_map, - callbackLogoMap, + callbackInfo, Callbacks, } from "./callback_info_helpers"; interface SettingsPageProps { @@ -620,28 +620,16 @@ const Settings: React.FC = ({ value={callback_map[callbackEnum]} >
- {callbackLogoMap[callbackDisplayName] ? ( + {callbackInfo[callbackDisplayName]?.logo ? (
{`${callbackEnum} { - if (callbackLogoMap[callbackDisplayName]) { - delete callbackLogoMap[callbackDisplayName]; - } - }} - style={{ - display: callbackLogoMap[callbackDisplayName] - ? "block" - : "none", + onError={(e) => { + e.currentTarget.style.display = 'none'; }} /> - {!callbackLogoMap[callbackDisplayName] && ( -
- {callbackDisplayName.charAt(0).toUpperCase()} -
- )}
) : (
diff --git a/ui/litellm-dashboard/src/components/team/LoggingSettings.tsx b/ui/litellm-dashboard/src/components/team/LoggingSettings.tsx new file mode 100644 index 00000000000..9521f683e5b --- /dev/null +++ b/ui/litellm-dashboard/src/components/team/LoggingSettings.tsx @@ -0,0 +1,259 @@ +import React, { useState } from 'react'; +import { Form, Select, Space, Tooltip, Divider } from 'antd'; +import { InfoCircleOutlined } from '@ant-design/icons'; +import { Button, Card } from '@tremor/react'; +import { PlusIcon, TrashIcon, CogIcon } from '@heroicons/react/outline'; +import { callbackInfo, Callbacks, callback_map } from '../callback_info_helpers'; + +const { Option } = Select; + +interface LoggingConfig { + callback_name: string; + callback_type: string; + callback_vars: Record; +} + +interface LoggingSettingsProps { + value?: LoggingConfig[]; + onChange?: (value: LoggingConfig[]) => void; +} + +const LoggingSettings: React.FC = ({ value = [], onChange }) => { + // Get callbacks that support team and key logging + const supportedCallbacks = Object.entries(callbackInfo) + .filter(([_, info]) => info.supports_key_team_logging) + .map(([name, _]) => name); + + const handleChange = (newValue: LoggingConfig[]) => { + onChange?.(newValue); + }; + + const addLoggingConfig = () => { + const newConfig: LoggingConfig = { + callback_name: '', + callback_type: 'success', + callback_vars: {} + }; + handleChange([...value, newConfig]); + }; + + const removeLoggingConfig = (index: number) => { + const newValue = value.filter((_, i) => i !== index); + handleChange(newValue); + }; + + const updateLoggingConfig = (index: number, field: keyof LoggingConfig, newValue: any) => { + const updatedConfigs = [...value]; + if (field === 'callback_name') { + // Convert display name to callback value and reset callback_vars when callback changes + const callbackValue = callback_map[newValue] || newValue; + updatedConfigs[index] = { + ...updatedConfigs[index], + [field]: callbackValue, + callback_vars: {} + }; + } else { + updatedConfigs[index] = { + ...updatedConfigs[index], + [field]: newValue + }; + } + handleChange(updatedConfigs); + }; + + const updateCallbackVar = (configIndex: number, varName: string, varValue: string) => { + const updatedConfigs = [...value]; + updatedConfigs[configIndex] = { + ...updatedConfigs[configIndex], + callback_vars: { + ...updatedConfigs[configIndex].callback_vars, + [varName]: varValue + } + }; + handleChange(updatedConfigs); + }; + + const renderDynamicParams = (config: LoggingConfig, configIndex: number) => { + if (!config.callback_name) return null; + + // Find the display name for the callback + const callbackDisplayName = Object.entries(callback_map).find( + ([_, value]) => value === config.callback_name + )?.[0]; + + if (!callbackDisplayName) return null; + + const dynamicParams = callbackInfo[callbackDisplayName]?.dynamic_params || {}; + + if (Object.keys(dynamicParams).length === 0) return null; + + return ( +
+
+
+
+
+ Integration Parameters +
+
+ {Object.entries(dynamicParams).map(([paramName, paramType]) => ( +
+ + updateCallbackVar(configIndex, paramName, e.target.value)} + /> +
+ ))} +
+
+ ); + }; + + return ( +
+
+
+ + Logging Integrations + + + +
+ +
+ +
+ {value.map((config, index) => { + const callbackDisplayName = config.callback_name + ? Object.entries(callback_map).find(([_, value]) => value === config.callback_name)?.[0] + : undefined; + const logoUrl = callbackDisplayName ? callbackInfo[callbackDisplayName]?.logo : null; + + return ( + +
+
+ {logoUrl && ( + {callbackDisplayName} + )} + + {callbackDisplayName || 'New Integration'} Configuration + +
+ +
+
+
+
+ + +
+ +
+ + +
+
+ + {renderDynamicParams(config, index)} +
+
+ ); + })} +
+ + {value.length === 0 && ( +
+ +
No logging integrations configured
+
Click "Add Integration" to configure logging for this team
+
+ )} +
+ ); +}; + +export default LoggingSettings; \ No newline at end of file diff --git a/ui/litellm-dashboard/src/components/teams.tsx b/ui/litellm-dashboard/src/components/teams.tsx index cba0c588e3d..11d56007416 100644 --- a/ui/litellm-dashboard/src/components/teams.tsx +++ b/ui/litellm-dashboard/src/components/teams.tsx @@ -60,6 +60,7 @@ import AvailableTeamsPanel from "@/components/team/available_teams"; import VectorStoreSelector from "./vector_store_management/VectorStoreSelector"; import PremiumVectorStoreSelector from "./common_components/PremiumVectorStoreSelector"; import PremiumMCPSelector from "./common_components/PremiumMCPSelector"; +import LoggingSettings from "./team/LoggingSettings"; import type { KeyResponse, Team } from "./key_team_helpers/key_list"; interface TeamProps { @@ -181,6 +182,7 @@ const Teams: React.FC = ({ // Add this state near the other useState declarations const [guardrailsList, setGuardrailsList] = useState([]); const [expandedAccordions, setExpandedAccordions] = useState>({}); + const [loggingSettings, setLoggingSettings] = useState([]); useEffect(() => { console.log(`currentOrgForCreateTeam: ${currentOrgForCreateTeam}`); @@ -234,6 +236,7 @@ const Teams: React.FC = ({ const handleOk = () => { setIsTeamModalVisible(false); form.resetFields(); + setLoggingSettings([]); }; const handleMemberOk = () => { @@ -244,8 +247,8 @@ const Teams: React.FC = ({ const handleCancel = () => { setIsTeamModalVisible(false); - form.resetFields(); + setLoggingSettings([]); }; const handleMemberCancel = () => { @@ -325,6 +328,27 @@ const Teams: React.FC = ({ } message.info("Creating Team"); + + // Handle logging settings in metadata + if (loggingSettings.length > 0) { + let metadata = {}; + if (formValues.metadata) { + try { + metadata = JSON.parse(formValues.metadata); + } catch (e) { + console.warn("Invalid JSON in metadata field, starting with empty object"); + } + } + + // Add logging settings to metadata + metadata = { + ...metadata, + logging: loggingSettings.filter(config => config.callback_name) // Only include configs with callback_name + }; + + formValues.metadata = JSON.stringify(metadata); + } + // Transform allowed_vector_store_ids and allowed_mcp_server_ids into object_permission if (formValues.allowed_vector_store_ids && formValues.allowed_vector_store_ids.length > 0) { formValues.object_permission = { @@ -350,6 +374,7 @@ const Teams: React.FC = ({ console.log(`response for team create call: ${response}`); message.success("Team created"); form.resetFields(); + setLoggingSettings([]); setIsTeamModalVisible(false); } } catch (error) { @@ -911,7 +936,7 @@ const Teams: React.FC = ({ = ({ + + + + Logging Settings + + +
+ +
+
+
Create Team