mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-08 03:08:45 +00:00
Adding router settings to create team and key
This commit is contained in:
parent
56883add3c
commit
9850dbe934
9 changed files with 584 additions and 65 deletions
|
|
@ -42,6 +42,7 @@ import AgentSelector from "./agent_management/AgentSelector";
|
|||
import { fetchTeams } from "./common_components/fetch_teams";
|
||||
import ModelAliasManager from "./common_components/ModelAliasManager";
|
||||
import PremiumLoggingSettings from "./common_components/PremiumLoggingSettings";
|
||||
import RouterSettingsAccordion, { RouterSettingsAccordionValue } from "./common_components/RouterSettingsAccordion";
|
||||
import {
|
||||
fetchAvailableModelsForTeamOrKey,
|
||||
getModelDisplayName,
|
||||
|
|
@ -227,6 +228,8 @@ const Teams: React.FC<TeamProps> = ({
|
|||
const [mcpAccessGroups, setMcpAccessGroups] = useState<string[]>([]);
|
||||
const [mcpAccessGroupsLoaded, setMcpAccessGroupsLoaded] = useState(false);
|
||||
const [modelAliases, setModelAliases] = useState<{ [key: string]: string }>({});
|
||||
const [routerSettings, setRouterSettings] = useState<RouterSettingsAccordionValue | null>(null);
|
||||
const [routerSettingsKey, setRouterSettingsKey] = useState<number>(0);
|
||||
|
||||
useEffect(() => {
|
||||
console.log(`currentOrgForCreateTeam: ${currentOrgForCreateTeam}`);
|
||||
|
|
@ -317,6 +320,8 @@ const Teams: React.FC<TeamProps> = ({
|
|||
form.resetFields();
|
||||
setLoggingSettings([]);
|
||||
setModelAliases({});
|
||||
setRouterSettings(null);
|
||||
setRouterSettingsKey((prev) => prev + 1);
|
||||
};
|
||||
|
||||
const handleMemberOk = () => {
|
||||
|
|
@ -330,6 +335,8 @@ const Teams: React.FC<TeamProps> = ({
|
|||
form.resetFields();
|
||||
setLoggingSettings([]);
|
||||
setModelAliases({});
|
||||
setRouterSettings(null);
|
||||
setRouterSettingsKey((prev) => prev + 1);
|
||||
};
|
||||
|
||||
const handleMemberCancel = () => {
|
||||
|
|
@ -503,6 +510,17 @@ const Teams: React.FC<TeamProps> = ({
|
|||
formValues.model_aliases = modelAliases;
|
||||
}
|
||||
|
||||
// Add router_settings if any are defined
|
||||
if (routerSettings?.router_settings) {
|
||||
// Only include router_settings if it has at least one non-null value
|
||||
const hasValues = Object.values(routerSettings.router_settings).some(
|
||||
(value) => value !== null && value !== undefined && value !== "",
|
||||
);
|
||||
if (hasValues) {
|
||||
formValues.router_settings = routerSettings.router_settings;
|
||||
}
|
||||
}
|
||||
|
||||
const response: any = await teamCreateCall(accessToken, formValues);
|
||||
if (teams !== null) {
|
||||
setTeams([...teams, response]);
|
||||
|
|
@ -514,6 +532,8 @@ const Teams: React.FC<TeamProps> = ({
|
|||
form.resetFields();
|
||||
setLoggingSettings([]);
|
||||
setModelAliases({});
|
||||
setRouterSettings(null);
|
||||
setRouterSettingsKey((prev) => prev + 1);
|
||||
setIsTeamModalVisible(false);
|
||||
}
|
||||
} catch (error) {
|
||||
|
|
@ -1428,6 +1448,23 @@ const Teams: React.FC<TeamProps> = ({
|
|||
</AccordionBody>
|
||||
</Accordion>
|
||||
|
||||
<Accordion key={`router-settings-accordion-${routerSettingsKey}`} className="mt-8 mb-8">
|
||||
<AccordionHeader>
|
||||
<b>Router Settings</b>
|
||||
</AccordionHeader>
|
||||
<AccordionBody>
|
||||
<div className="mt-4 w-full">
|
||||
<RouterSettingsAccordion
|
||||
key={routerSettingsKey}
|
||||
accessToken={accessToken || ""}
|
||||
value={routerSettings || undefined}
|
||||
onChange={setRouterSettings}
|
||||
modelData={userModels.length > 0 ? { data: userModels.map((model) => ({ model_name: model })) } : undefined}
|
||||
/>
|
||||
</div>
|
||||
</AccordionBody>
|
||||
</Accordion>
|
||||
|
||||
<Accordion className="mt-8 mb-8">
|
||||
<AccordionHeader>
|
||||
<b>Model Aliases</b>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,373 @@
|
|||
import React, { useEffect, useState, useImperativeHandle, forwardRef, useRef } from "react";
|
||||
import { TabPanel, TabPanels, TabGroup, TabList, Tab } from "@tremor/react";
|
||||
import { getRouterSettingsCall } from "../networking";
|
||||
import RouterSettingsForm, { RouterSettingsFormValue } from "../router_settings/RouterSettingsForm";
|
||||
import { Fallbacks } from "../Settings/RouterSettings/Fallbacks/AddFallbacks";
|
||||
import { FallbackSelectionForm } from "../Settings/RouterSettings/Fallbacks/FallbackSelectionForm";
|
||||
import { FallbackGroup } from "../Settings/RouterSettings/Fallbacks/FallbackGroupConfig";
|
||||
import { fetchAvailableModels, ModelGroup } from "../playground/llm_calls/fetch_models";
|
||||
|
||||
export interface RouterSettingsAccordionValue {
|
||||
router_settings: {
|
||||
routing_strategy?: string | null;
|
||||
allowed_fails?: number | null;
|
||||
cooldown_time?: number | null;
|
||||
num_retries?: number | null;
|
||||
timeout?: number | null;
|
||||
retry_after?: number | null;
|
||||
fallbacks?: Fallbacks | null;
|
||||
context_window_fallbacks?: any | null;
|
||||
retry_policy?: any | null;
|
||||
model_group_alias?: { [key: string]: any } | null;
|
||||
enable_tag_filtering?: boolean;
|
||||
routing_strategy_args?: { [key: string]: any } | null;
|
||||
};
|
||||
}
|
||||
|
||||
interface RouterSettingsAccordionProps {
|
||||
accessToken: string;
|
||||
value?: RouterSettingsAccordionValue;
|
||||
onChange?: (value: RouterSettingsAccordionValue) => void;
|
||||
modelData?: any;
|
||||
}
|
||||
|
||||
export interface RouterSettingsAccordionRef {
|
||||
getValue: () => RouterSettingsAccordionValue;
|
||||
}
|
||||
|
||||
const RouterSettingsAccordion = forwardRef<RouterSettingsAccordionRef, RouterSettingsAccordionProps>(
|
||||
({ accessToken, value, onChange, modelData }, ref) => {
|
||||
const [formValue, setFormValue] = useState<RouterSettingsFormValue>({
|
||||
routerSettings: {},
|
||||
selectedStrategy: null,
|
||||
enableTagFiltering: false,
|
||||
});
|
||||
const [fallbacks, setFallbacks] = useState<Fallbacks>([]);
|
||||
const [fallbackGroups, setFallbackGroups] = useState<FallbackGroup[]>([]);
|
||||
const [modelInfo, setModelInfo] = useState<ModelGroup[]>([]);
|
||||
const [availableRoutingStrategies, setAvailableRoutingStrategies] = useState<string[]>([]);
|
||||
const [routerFieldsMetadata, setRouterFieldsMetadata] = useState<{ [key: string]: any }>({});
|
||||
const [routingStrategyDescriptions, setRoutingStrategyDescriptions] = useState<{ [key: string]: string }>({});
|
||||
const isInternalUpdateRef = useRef(false);
|
||||
const lastInitializedValueRef = useRef<string | null>(null);
|
||||
|
||||
// Convert fallbacks format to groups format
|
||||
const fallbacksToGroups = (fallbacks: Fallbacks): FallbackGroup[] => {
|
||||
if (!fallbacks || fallbacks.length === 0) {
|
||||
return [
|
||||
{
|
||||
id: "1",
|
||||
primaryModel: null,
|
||||
fallbackModels: [],
|
||||
},
|
||||
];
|
||||
}
|
||||
return fallbacks.map((entry, index) => {
|
||||
const [primaryModel, fallbackModels] = Object.entries(entry)[0];
|
||||
return {
|
||||
id: (index + 1).toString(),
|
||||
primaryModel: primaryModel || null,
|
||||
fallbackModels: fallbackModels || [],
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
// Convert groups format to fallbacks format
|
||||
const groupsToFallbacks = (groups: FallbackGroup[]): Fallbacks => {
|
||||
return groups
|
||||
.filter((g) => g.primaryModel && g.fallbackModels.length > 0)
|
||||
.map((g) => ({
|
||||
[g.primaryModel!]: g.fallbackModels,
|
||||
}));
|
||||
};
|
||||
|
||||
// Initialize from value prop if provided (only when value actually changes externally)
|
||||
useEffect(() => {
|
||||
// Create a stable key from the value to detect actual external changes
|
||||
const valueKey = value?.router_settings
|
||||
? JSON.stringify({
|
||||
routing_strategy: value.router_settings.routing_strategy,
|
||||
fallbacks: value.router_settings.fallbacks,
|
||||
enable_tag_filtering: value.router_settings.enable_tag_filtering,
|
||||
})
|
||||
: null;
|
||||
|
||||
// Skip if this is an internal update (from our own onChange) and the value hasn't actually changed
|
||||
if (isInternalUpdateRef.current && valueKey === lastInitializedValueRef.current) {
|
||||
isInternalUpdateRef.current = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Reset the flag if value actually changed externally
|
||||
if (isInternalUpdateRef.current && valueKey !== lastInitializedValueRef.current) {
|
||||
isInternalUpdateRef.current = false;
|
||||
}
|
||||
|
||||
// Only update if the value actually changed from an external source
|
||||
if (valueKey === lastInitializedValueRef.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
lastInitializedValueRef.current = valueKey;
|
||||
|
||||
if (value?.router_settings) {
|
||||
const rs = value.router_settings;
|
||||
const { fallbacks: _, ...routerSettingsWithoutFallbacks } = rs;
|
||||
setFormValue({
|
||||
routerSettings: routerSettingsWithoutFallbacks as { [key: string]: any },
|
||||
selectedStrategy: rs.routing_strategy || null,
|
||||
enableTagFiltering: rs.enable_tag_filtering ?? false,
|
||||
});
|
||||
const initialFallbacks = rs.fallbacks || [];
|
||||
setFallbacks(initialFallbacks);
|
||||
setFallbackGroups(fallbacksToGroups(initialFallbacks));
|
||||
} else {
|
||||
// Initialize with empty defaults if no value provided
|
||||
setFormValue({
|
||||
routerSettings: {},
|
||||
selectedStrategy: null,
|
||||
enableTagFiltering: false,
|
||||
});
|
||||
setFallbacks([]);
|
||||
setFallbackGroups([
|
||||
{
|
||||
id: "1",
|
||||
primaryModel: null,
|
||||
fallbackModels: [],
|
||||
},
|
||||
]);
|
||||
}
|
||||
}, [value]);
|
||||
|
||||
// Fetch router settings metadata
|
||||
useEffect(() => {
|
||||
if (!accessToken) {
|
||||
return;
|
||||
}
|
||||
getRouterSettingsCall(accessToken).then((data) => {
|
||||
if (data.fields) {
|
||||
// Build metadata map for easy lookup
|
||||
const fieldsMap: { [key: string]: any } = {};
|
||||
data.fields.forEach((field: any) => {
|
||||
fieldsMap[field.field_name] = {
|
||||
ui_field_name: field.ui_field_name,
|
||||
field_description: field.field_description,
|
||||
options: field.options,
|
||||
link: field.link,
|
||||
};
|
||||
});
|
||||
setRouterFieldsMetadata(fieldsMap);
|
||||
|
||||
// Extract routing strategies from the routing_strategy field's options
|
||||
const routingStrategyField = data.fields.find((field: any) => field.field_name === "routing_strategy");
|
||||
if (routingStrategyField?.options) {
|
||||
setAvailableRoutingStrategies(routingStrategyField.options);
|
||||
}
|
||||
|
||||
// Store routing strategy descriptions
|
||||
if (data.routing_strategy_descriptions) {
|
||||
setRoutingStrategyDescriptions(data.routing_strategy_descriptions);
|
||||
}
|
||||
}
|
||||
});
|
||||
}, [accessToken]);
|
||||
|
||||
// Fetch available models for fallbacks
|
||||
useEffect(() => {
|
||||
if (!accessToken) {
|
||||
return;
|
||||
}
|
||||
const loadModels = async () => {
|
||||
try {
|
||||
const uniqueModels = await fetchAvailableModels(accessToken);
|
||||
setModelInfo(uniqueModels);
|
||||
} catch (error) {
|
||||
console.error("Error fetching model info for fallbacks:", error);
|
||||
}
|
||||
};
|
||||
loadModels();
|
||||
}, [accessToken]);
|
||||
|
||||
// Helper function to build router_settings from current state
|
||||
const buildRouterSettings = (): RouterSettingsAccordionValue["router_settings"] => {
|
||||
// Parse input values from DOM (similar to RouterSettings component)
|
||||
const numberKeys = new Set(["allowed_fails", "cooldown_time", "num_retries", "timeout", "retry_after"]);
|
||||
const jsonKeys = new Set(["model_group_alias", "retry_policy"]);
|
||||
|
||||
const parseInputValue = (key: string, raw: string | undefined, fallback: unknown) => {
|
||||
if (raw === undefined || raw === null) return fallback;
|
||||
|
||||
const v = String(raw).trim();
|
||||
|
||||
if (v === "" || v.toLowerCase() === "null") return null;
|
||||
|
||||
if (numberKeys.has(key)) {
|
||||
const n = Number(v);
|
||||
return Number.isNaN(n) ? fallback : n;
|
||||
}
|
||||
|
||||
if (jsonKeys.has(key)) {
|
||||
if (v === "") return null;
|
||||
try {
|
||||
return JSON.parse(v);
|
||||
} catch {
|
||||
return fallback;
|
||||
}
|
||||
}
|
||||
|
||||
if (v.toLowerCase() === "true") return true;
|
||||
if (v.toLowerCase() === "false") return false;
|
||||
|
||||
return v;
|
||||
};
|
||||
|
||||
// Build router_settings object
|
||||
const routerSettings = formValue.routerSettings;
|
||||
const settingsToUpdate: { [key: string]: any } = {
|
||||
...routerSettings,
|
||||
enable_tag_filtering: formValue.enableTagFiltering,
|
||||
routing_strategy: formValue.selectedStrategy,
|
||||
fallbacks: fallbacks.length > 0 ? fallbacks : null,
|
||||
};
|
||||
|
||||
// Parse values from DOM inputs for reliability/retry fields
|
||||
const updatedVariables = Object.fromEntries(
|
||||
Object.entries(settingsToUpdate)
|
||||
.map(([key, value]) => {
|
||||
if (
|
||||
key !== "routing_strategy_args" &&
|
||||
key !== "routing_strategy" &&
|
||||
key !== "enable_tag_filtering" &&
|
||||
key !== "fallbacks"
|
||||
) {
|
||||
const inputEl = document.querySelector(`input[name="${key}"]`) as HTMLInputElement | null;
|
||||
if (inputEl && inputEl.value !== undefined && inputEl.value !== "") {
|
||||
const parsed = parseInputValue(key, inputEl.value, value);
|
||||
return [key, parsed];
|
||||
}
|
||||
return [key, value];
|
||||
} else if (key === "routing_strategy") {
|
||||
return [key, formValue.selectedStrategy];
|
||||
} else if (key === "enable_tag_filtering") {
|
||||
return [key, formValue.enableTagFiltering];
|
||||
} else if (key === "fallbacks") {
|
||||
return [key, fallbacks.length > 0 ? fallbacks : null];
|
||||
} else if (key === "routing_strategy_args" && formValue.selectedStrategy === "latency-based-routing") {
|
||||
const lowestLatencyBufferElement = document.querySelector(
|
||||
`input[name="lowest_latency_buffer"]`,
|
||||
) as HTMLInputElement;
|
||||
const ttlElement = document.querySelector(`input[name="ttl"]`) as HTMLInputElement;
|
||||
|
||||
const routingStrategyArgs: { [key: string]: any } = {};
|
||||
if (lowestLatencyBufferElement?.value) {
|
||||
routingStrategyArgs["lowest_latency_buffer"] = Number(lowestLatencyBufferElement.value);
|
||||
}
|
||||
if (ttlElement?.value) {
|
||||
routingStrategyArgs["ttl"] = Number(ttlElement.value);
|
||||
}
|
||||
return ["routing_strategy_args", Object.keys(routingStrategyArgs).length > 0 ? routingStrategyArgs : null];
|
||||
}
|
||||
return [key, value];
|
||||
})
|
||||
.filter((entry) => entry !== null && entry !== undefined) as Iterable<[string, unknown]>,
|
||||
);
|
||||
|
||||
// Ensure all required fields are present with null defaults if not set
|
||||
// Convert empty objects/undefined to null for proper type safety
|
||||
const normalizeValue = (val: any, isNumber = false): any => {
|
||||
if (val === undefined || val === null) return null;
|
||||
if (typeof val === "object" && !Array.isArray(val) && Object.keys(val).length === 0) return null;
|
||||
if (isNumber && (typeof val !== "number" || Number.isNaN(val))) return null;
|
||||
return val;
|
||||
};
|
||||
|
||||
return {
|
||||
routing_strategy: normalizeValue(updatedVariables.routing_strategy) as string | null | undefined,
|
||||
allowed_fails: normalizeValue(updatedVariables.allowed_fails, true) as number | null | undefined,
|
||||
cooldown_time: normalizeValue(updatedVariables.cooldown_time, true) as number | null | undefined,
|
||||
num_retries: normalizeValue(updatedVariables.num_retries, true) as number | null | undefined,
|
||||
timeout: normalizeValue(updatedVariables.timeout, true) as number | null | undefined,
|
||||
retry_after: normalizeValue(updatedVariables.retry_after, true) as number | null | undefined,
|
||||
fallbacks: fallbacks.length > 0 ? fallbacks : null,
|
||||
context_window_fallbacks: normalizeValue(updatedVariables.context_window_fallbacks),
|
||||
retry_policy: normalizeValue(updatedVariables.retry_policy),
|
||||
model_group_alias: normalizeValue(updatedVariables.model_group_alias),
|
||||
enable_tag_filtering: formValue.enableTagFiltering,
|
||||
routing_strategy_args: normalizeValue(updatedVariables.routing_strategy_args),
|
||||
};
|
||||
};
|
||||
|
||||
// Update parent when form values change (with debounce to avoid infinite loops)
|
||||
useEffect(() => {
|
||||
if (!onChange) {
|
||||
return;
|
||||
}
|
||||
|
||||
const timeoutId = setTimeout(() => {
|
||||
isInternalUpdateRef.current = true;
|
||||
const finalRouterSettings = buildRouterSettings();
|
||||
onChange({
|
||||
router_settings: finalRouterSettings,
|
||||
});
|
||||
}, 100);
|
||||
|
||||
return () => clearTimeout(timeoutId);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [formValue, fallbacks]);
|
||||
|
||||
const handleFallbackGroupsChange = (newGroups: FallbackGroup[]): void => {
|
||||
setFallbackGroups(newGroups);
|
||||
const newFallbacks = groupsToFallbacks(newGroups);
|
||||
setFallbacks(newFallbacks);
|
||||
};
|
||||
|
||||
const availableModels = Array.from(new Set(modelInfo.map((option) => option.model_group))).sort();
|
||||
|
||||
// Expose getValue method via ref
|
||||
useImperativeHandle(ref, () => ({
|
||||
getValue: () => {
|
||||
return {
|
||||
router_settings: buildRouterSettings(),
|
||||
};
|
||||
},
|
||||
}));
|
||||
|
||||
if (!accessToken) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="w-full">
|
||||
<TabGroup className="w-full">
|
||||
<TabList variant="line" defaultValue="1" className="px-8 pt-4">
|
||||
<Tab value="1">Loadbalancing</Tab>
|
||||
<Tab value="2">Fallbacks</Tab>
|
||||
</TabList>
|
||||
<TabPanels className="px-8 py-6">
|
||||
<TabPanel>
|
||||
<RouterSettingsForm
|
||||
value={formValue}
|
||||
onChange={setFormValue}
|
||||
routerFieldsMetadata={routerFieldsMetadata}
|
||||
availableRoutingStrategies={availableRoutingStrategies}
|
||||
routingStrategyDescriptions={routingStrategyDescriptions}
|
||||
/>
|
||||
</TabPanel>
|
||||
<TabPanel>
|
||||
<FallbackSelectionForm
|
||||
groups={fallbackGroups}
|
||||
onGroupsChange={handleFallbackGroupsChange}
|
||||
availableModels={availableModels}
|
||||
maxFallbacks={5}
|
||||
maxGroups={5}
|
||||
/>
|
||||
</TabPanel>
|
||||
</TabPanels>
|
||||
</TabGroup>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
RouterSettingsAccordion.displayName = "RouterSettingsAccordion";
|
||||
|
||||
export default RouterSettingsAccordion;
|
||||
|
|
@ -19,6 +19,7 @@ import ModelAliasManager from "../common_components/ModelAliasManager";
|
|||
import PassThroughRoutesSelector from "../common_components/PassThroughRoutesSelector";
|
||||
import PremiumLoggingSettings from "../common_components/PremiumLoggingSettings";
|
||||
import RateLimitTypeFormItem from "../common_components/RateLimitTypeFormItem";
|
||||
import RouterSettingsAccordion, { RouterSettingsAccordionValue } from "../common_components/RouterSettingsAccordion";
|
||||
import TeamDropdown from "../common_components/team_dropdown";
|
||||
import Createuser from "../create_user_button";
|
||||
import { getModelDisplayName } from "../key_team_helpers/fetch_available_models_team_key";
|
||||
|
|
@ -163,6 +164,8 @@ const CreateKey: React.FC<CreateKeyProps> = ({ team, teams, data, addKey }) => {
|
|||
const [modelAliases, setModelAliases] = useState<{ [key: string]: string }>({});
|
||||
const [autoRotationEnabled, setAutoRotationEnabled] = useState<boolean>(false);
|
||||
const [rotationInterval, setRotationInterval] = useState<string>("30d");
|
||||
const [routerSettings, setRouterSettings] = useState<RouterSettingsAccordionValue | null>(null);
|
||||
const [routerSettingsKey, setRouterSettingsKey] = useState<number>(0);
|
||||
const handleOk = () => {
|
||||
setIsModalVisible(false);
|
||||
form.resetFields();
|
||||
|
|
@ -172,6 +175,8 @@ const CreateKey: React.FC<CreateKeyProps> = ({ team, teams, data, addKey }) => {
|
|||
setModelAliases({});
|
||||
setAutoRotationEnabled(false);
|
||||
setRotationInterval("30d");
|
||||
setRouterSettings(null);
|
||||
setRouterSettingsKey((prev) => prev + 1);
|
||||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
|
|
@ -185,6 +190,8 @@ const CreateKey: React.FC<CreateKeyProps> = ({ team, teams, data, addKey }) => {
|
|||
setModelAliases({});
|
||||
setAutoRotationEnabled(false);
|
||||
setRotationInterval("30d");
|
||||
setRouterSettings(null);
|
||||
setRouterSettingsKey((prev) => prev + 1);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
|
|
@ -383,6 +390,17 @@ const CreateKey: React.FC<CreateKeyProps> = ({ team, teams, data, addKey }) => {
|
|||
formValues.aliases = JSON.stringify(modelAliases);
|
||||
}
|
||||
|
||||
// Add router_settings if any are defined
|
||||
if (routerSettings?.router_settings) {
|
||||
// Only include router_settings if it has at least one non-null value
|
||||
const hasValues = Object.values(routerSettings.router_settings).some(
|
||||
(value) => value !== null && value !== undefined && value !== "",
|
||||
);
|
||||
if (hasValues) {
|
||||
formValues.router_settings = routerSettings.router_settings;
|
||||
}
|
||||
}
|
||||
|
||||
let response;
|
||||
if (keyOwner === "service_account") {
|
||||
response = await keyCreateServiceAccountCall(accessToken, formValues);
|
||||
|
|
@ -1154,6 +1172,23 @@ const CreateKey: React.FC<CreateKeyProps> = ({ team, teams, data, addKey }) => {
|
|||
</Tooltip>
|
||||
)}
|
||||
|
||||
<Accordion key={`router-settings-accordion-${routerSettingsKey}`} className="mt-4 mb-4">
|
||||
<AccordionHeader>
|
||||
<b>Router Settings</b>
|
||||
</AccordionHeader>
|
||||
<AccordionBody>
|
||||
<div className="mt-4 w-full">
|
||||
<RouterSettingsAccordion
|
||||
key={routerSettingsKey}
|
||||
accessToken={accessToken || ""}
|
||||
value={routerSettings || undefined}
|
||||
onChange={setRouterSettings}
|
||||
modelData={userModels.length > 0 ? { data: userModels.map((model) => ({ model_name: model })) } : undefined}
|
||||
/>
|
||||
</div>
|
||||
</AccordionBody>
|
||||
</Accordion>
|
||||
|
||||
<Accordion className="mt-4 mb-4">
|
||||
<AccordionHeader>
|
||||
<b>Model Aliases</b>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import React, { useState, useEffect } from "react";
|
|||
import { Modal, Form, Select, Radio, Divider, Typography } from "antd";
|
||||
import { Button } from "@tremor/react";
|
||||
import { Policy, PolicyAttachmentCreateRequest } from "./types";
|
||||
import { createPolicyAttachmentCall, teamListCall, keyInfoCall } from "../networking";
|
||||
import { teamListCall, keyInfoCall } from "../networking";
|
||||
import NotificationsManager from "../molecules/notifications_manager";
|
||||
|
||||
const { Text } = Typography;
|
||||
|
|
@ -13,6 +13,7 @@ interface AddAttachmentFormProps {
|
|||
onSuccess: () => void;
|
||||
accessToken: string | null;
|
||||
policies: Policy[];
|
||||
createAttachment: (accessToken: string, attachmentData: any) => Promise<any>;
|
||||
}
|
||||
|
||||
const AddAttachmentForm: React.FC<AddAttachmentFormProps> = ({
|
||||
|
|
@ -21,6 +22,7 @@ const AddAttachmentForm: React.FC<AddAttachmentFormProps> = ({
|
|||
onSuccess,
|
||||
accessToken,
|
||||
policies,
|
||||
createAttachment,
|
||||
}) => {
|
||||
const [form] = Form.useForm();
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
|
|
@ -111,7 +113,7 @@ const AddAttachmentForm: React.FC<AddAttachmentFormProps> = ({
|
|||
}
|
||||
}
|
||||
|
||||
await createPolicyAttachmentCall(accessToken, data);
|
||||
await createAttachment(accessToken, data);
|
||||
NotificationsManager.success("Attachment created successfully");
|
||||
|
||||
resetForm();
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { Form, Select, Modal, Divider, Typography, Tag, Alert, Radio } from "ant
|
|||
import { Button, TextInput, Textarea } from "@tremor/react";
|
||||
import { Policy, PolicyCreateRequest, PolicyUpdateRequest } from "./types";
|
||||
import { Guardrail } from "../guardrails/types";
|
||||
import { createPolicyCall, updatePolicyCall, getResolvedGuardrails, modelAvailableCall } from "../networking";
|
||||
import { getResolvedGuardrails, modelAvailableCall } from "../networking";
|
||||
import NotificationsManager from "../molecules/notifications_manager";
|
||||
|
||||
const { Text } = Typography;
|
||||
|
|
@ -17,6 +17,8 @@ interface AddPolicyFormProps {
|
|||
editingPolicy?: Policy | null;
|
||||
existingPolicies: Policy[];
|
||||
availableGuardrails: Guardrail[];
|
||||
createPolicy: (accessToken: string, policyData: any) => Promise<any>;
|
||||
updatePolicy: (accessToken: string, policyId: string, policyData: any) => Promise<any>;
|
||||
}
|
||||
|
||||
const AddPolicyForm: React.FC<AddPolicyFormProps> = ({
|
||||
|
|
@ -27,6 +29,8 @@ const AddPolicyForm: React.FC<AddPolicyFormProps> = ({
|
|||
editingPolicy,
|
||||
existingPolicies,
|
||||
availableGuardrails,
|
||||
createPolicy,
|
||||
updatePolicy,
|
||||
}) => {
|
||||
const [form] = Form.useForm();
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
|
|
@ -188,10 +192,10 @@ const AddPolicyForm: React.FC<AddPolicyFormProps> = ({
|
|||
};
|
||||
|
||||
if (isEditing && editingPolicy) {
|
||||
await updatePolicyCall(accessToken, editingPolicy.policy_id, data as PolicyUpdateRequest);
|
||||
await updatePolicy(accessToken, editingPolicy.policy_id, data as PolicyUpdateRequest);
|
||||
NotificationsManager.success("Policy updated successfully");
|
||||
} else {
|
||||
await createPolicyCall(accessToken, data as PolicyCreateRequest);
|
||||
await createPolicy(accessToken, data as PolicyCreateRequest);
|
||||
NotificationsManager.success("Policy created successfully");
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { Card, Badge, Button } from "@tremor/react";
|
|||
import { ArrowLeftIcon, PencilIcon } from "@heroicons/react/outline";
|
||||
import { Descriptions, Tag, Spin, Divider, Typography, Alert } from "antd";
|
||||
import { Policy } from "./types";
|
||||
import { getPolicyInfo, getResolvedGuardrails } from "../networking";
|
||||
import { getResolvedGuardrails } from "../networking";
|
||||
|
||||
const { Title, Text } = Typography;
|
||||
|
||||
|
|
@ -13,6 +13,7 @@ interface PolicyInfoViewProps {
|
|||
onEdit: (policy: Policy) => void;
|
||||
accessToken: string | null;
|
||||
isAdmin: boolean;
|
||||
getPolicy: (accessToken: string, policyId: string) => Promise<any>;
|
||||
}
|
||||
|
||||
const PolicyInfoView: React.FC<PolicyInfoViewProps> = ({
|
||||
|
|
@ -21,6 +22,7 @@ const PolicyInfoView: React.FC<PolicyInfoViewProps> = ({
|
|||
onEdit,
|
||||
accessToken,
|
||||
isAdmin,
|
||||
getPolicy,
|
||||
}) => {
|
||||
const [policy, setPolicy] = useState<Policy | null>(null);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
|
|
@ -32,7 +34,7 @@ const PolicyInfoView: React.FC<PolicyInfoViewProps> = ({
|
|||
|
||||
setIsLoading(true);
|
||||
try {
|
||||
const data = await getPolicyInfo(accessToken, policyId);
|
||||
const data = await getPolicy(accessToken, policyId);
|
||||
setPolicy(data);
|
||||
|
||||
// Also fetch resolved guardrails
|
||||
|
|
@ -50,7 +52,7 @@ const PolicyInfoView: React.FC<PolicyInfoViewProps> = ({
|
|||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
}, [policyId, accessToken]);
|
||||
}, [policyId, accessToken, getPolicy]);
|
||||
|
||||
useEffect(() => {
|
||||
fetchPolicy();
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ const ReliabilityRetriesSection: React.FC<ReliabilityRetriesSectionProps> = ({
|
|||
<h3 className="text-sm font-medium text-gray-900">Reliability & Retries</h3>
|
||||
<p className="text-xs text-gray-500 mt-1">Configure retry logic and failure handling</p>
|
||||
</div>
|
||||
|
||||
|
||||
<div className="grid grid-cols-1 gap-6 lg:grid-cols-2 xl:grid-cols-3">
|
||||
{Object.entries(routerSettings)
|
||||
.filter(
|
||||
|
|
@ -39,7 +39,11 @@ const ReliabilityRetriesSection: React.FC<ReliabilityRetriesSectionProps> = ({
|
|||
<TextInput
|
||||
name={param}
|
||||
defaultValue={
|
||||
typeof value === "object" ? JSON.stringify(value, null, 2) : value?.toString() || ""
|
||||
value === null || value === undefined || value === "null"
|
||||
? ""
|
||||
: typeof value === "object"
|
||||
? JSON.stringify(value, null, 2)
|
||||
: value?.toString() || ""
|
||||
}
|
||||
placeholder="—"
|
||||
className="font-mono text-sm w-full"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,84 @@
|
|||
import React from "react";
|
||||
import LatencyBasedConfiguration from "./LatencyBasedConfiguration";
|
||||
import ReliabilityRetriesSection from "./ReliabilityRetriesSection";
|
||||
import RoutingStrategySelector from "./RoutingStrategySelector";
|
||||
import TagFilteringToggle from "./TagFilteringToggle";
|
||||
|
||||
export interface RouterSettingsFormValue {
|
||||
routerSettings: { [key: string]: any };
|
||||
selectedStrategy: string | null;
|
||||
enableTagFiltering: boolean;
|
||||
}
|
||||
|
||||
interface RouterSettingsFormProps {
|
||||
value: RouterSettingsFormValue;
|
||||
onChange: (value: RouterSettingsFormValue) => void;
|
||||
routerFieldsMetadata: { [key: string]: any };
|
||||
availableRoutingStrategies: string[];
|
||||
routingStrategyDescriptions: { [key: string]: string };
|
||||
}
|
||||
|
||||
const RouterSettingsForm: React.FC<RouterSettingsFormProps> = ({
|
||||
value,
|
||||
onChange,
|
||||
routerFieldsMetadata,
|
||||
availableRoutingStrategies,
|
||||
routingStrategyDescriptions,
|
||||
}) => {
|
||||
const handleStrategyChange = (strategy: string) => {
|
||||
onChange({
|
||||
...value,
|
||||
selectedStrategy: strategy,
|
||||
});
|
||||
};
|
||||
|
||||
const handleTagFilteringToggle = (enabled: boolean) => {
|
||||
onChange({
|
||||
...value,
|
||||
enableTagFiltering: enabled,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="w-full space-y-8 py-2">
|
||||
{/* Routing Settings Section */}
|
||||
<div className="space-y-6">
|
||||
<div className="max-w-3xl">
|
||||
<h3 className="text-sm font-medium text-gray-900">Routing Settings</h3>
|
||||
<p className="text-xs text-gray-500 mt-1">Configure how requests are routed to deployments</p>
|
||||
</div>
|
||||
|
||||
{/* Routing Strategy */}
|
||||
{availableRoutingStrategies.length > 0 && (
|
||||
<RoutingStrategySelector
|
||||
selectedStrategy={value.selectedStrategy || value.routerSettings.routing_strategy || null}
|
||||
availableStrategies={availableRoutingStrategies}
|
||||
routingStrategyDescriptions={routingStrategyDescriptions}
|
||||
routerFieldsMetadata={routerFieldsMetadata}
|
||||
onStrategyChange={handleStrategyChange}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Tag Filtering */}
|
||||
<TagFilteringToggle
|
||||
enabled={value.enableTagFiltering}
|
||||
routerFieldsMetadata={routerFieldsMetadata}
|
||||
onToggle={handleTagFilteringToggle}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Divider */}
|
||||
<div className="border-t border-gray-200" />
|
||||
|
||||
{/* Strategy-Specific Args - Show immediately after strategy if latency-based */}
|
||||
{value.selectedStrategy === "latency-based-routing" && (
|
||||
<LatencyBasedConfiguration routingStrategyArgs={value.routerSettings["routing_strategy_args"]} />
|
||||
)}
|
||||
|
||||
{/* Other Settings */}
|
||||
<ReliabilityRetriesSection routerSettings={value.routerSettings} routerFieldsMetadata={routerFieldsMetadata} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default RouterSettingsForm;
|
||||
|
|
@ -2,10 +2,7 @@ import { Button } from "@tremor/react";
|
|||
import React, { useEffect, useState } from "react";
|
||||
import NotificationsManager from "../molecules/notifications_manager";
|
||||
import { getCallbacksCall, getRouterSettingsCall, setCallbacksCall } from "../networking";
|
||||
import LatencyBasedConfiguration from "./LatencyBasedConfiguration";
|
||||
import ReliabilityRetriesSection from "./ReliabilityRetriesSection";
|
||||
import RoutingStrategySelector from "./RoutingStrategySelector";
|
||||
import TagFilteringToggle from "./TagFilteringToggle";
|
||||
import RouterSettingsForm, { RouterSettingsFormValue } from "./RouterSettingsForm";
|
||||
|
||||
interface RouterSettingsProps {
|
||||
accessToken: string | null;
|
||||
|
|
@ -20,12 +17,14 @@ interface routingStrategyArgs {
|
|||
}
|
||||
|
||||
const RouterSettings: React.FC<RouterSettingsProps> = ({ accessToken, userRole, userID, modelData }) => {
|
||||
const [routerSettings, setRouterSettings] = useState<{ [key: string]: any }>({});
|
||||
const [selectedStrategy, setSelectedStrategy] = useState<string | null>(null);
|
||||
const [formValue, setFormValue] = useState<RouterSettingsFormValue>({
|
||||
routerSettings: {},
|
||||
selectedStrategy: null,
|
||||
enableTagFiltering: false,
|
||||
});
|
||||
const [availableRoutingStrategies, setAvailableRoutingStrategies] = useState<string[]>([]);
|
||||
const [routerFieldsMetadata, setRouterFieldsMetadata] = useState<{ [key: string]: any }>({});
|
||||
const [routingStrategyDescriptions, setRoutingStrategyDescriptions] = useState<{ [key: string]: string }>({});
|
||||
const [enableTagFiltering, setEnableTagFiltering] = useState<boolean>(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (!accessToken || !userRole || !userID) {
|
||||
|
|
@ -37,11 +36,13 @@ const RouterSettings: React.FC<RouterSettingsProps> = ({ accessToken, userRole,
|
|||
if ("model_group_retry_policy" in router_settings) {
|
||||
delete router_settings["model_group_retry_policy"];
|
||||
}
|
||||
setRouterSettings(router_settings);
|
||||
// Set initial selected strategy
|
||||
if (router_settings.routing_strategy) {
|
||||
setSelectedStrategy(router_settings.routing_strategy);
|
||||
}
|
||||
const initialStrategy = router_settings.routing_strategy || null;
|
||||
setFormValue((prev) => ({
|
||||
...prev,
|
||||
routerSettings: router_settings,
|
||||
selectedStrategy: initialStrategy,
|
||||
}));
|
||||
});
|
||||
getRouterSettingsCall(accessToken).then((data) => {
|
||||
console.log("router settings from API", data);
|
||||
|
|
@ -72,17 +73,21 @@ const RouterSettings: React.FC<RouterSettingsProps> = ({ accessToken, userRole,
|
|||
// Set enable_tag_filtering value
|
||||
const tagFilteringField = data.fields.find((field: any) => field.field_name === "enable_tag_filtering");
|
||||
if (tagFilteringField?.field_value !== null && tagFilteringField?.field_value !== undefined) {
|
||||
setEnableTagFiltering(tagFilteringField.field_value);
|
||||
setFormValue((prev) => ({
|
||||
...prev,
|
||||
enableTagFiltering: tagFilteringField.field_value,
|
||||
}));
|
||||
}
|
||||
}
|
||||
});
|
||||
}, [accessToken, userRole, userID]);
|
||||
|
||||
const handleSaveChanges = (router_settings: any) => {
|
||||
const handleSaveChanges = () => {
|
||||
if (!accessToken) {
|
||||
return;
|
||||
}
|
||||
|
||||
const router_settings = formValue.routerSettings;
|
||||
console.log("router_settings", router_settings);
|
||||
|
||||
const numberKeys = new Set(["allowed_fails", "cooldown_time", "num_retries", "timeout", "retry_after"]);
|
||||
|
|
@ -118,7 +123,7 @@ const RouterSettings: React.FC<RouterSettingsProps> = ({ accessToken, userRole,
|
|||
// Add enable_tag_filtering to router_settings before processing
|
||||
const settingsToUpdate = {
|
||||
...router_settings,
|
||||
enable_tag_filtering: enableTagFiltering,
|
||||
enable_tag_filtering: formValue.enableTagFiltering,
|
||||
};
|
||||
|
||||
const updatedVariables = Object.fromEntries(
|
||||
|
|
@ -129,10 +134,10 @@ const RouterSettings: React.FC<RouterSettingsProps> = ({ accessToken, userRole,
|
|||
const parsed = parseInputValue(key, inputEl?.value, value);
|
||||
return [key, parsed];
|
||||
} else if (key === "routing_strategy") {
|
||||
return [key, selectedStrategy];
|
||||
return [key, formValue.selectedStrategy];
|
||||
} else if (key === "enable_tag_filtering") {
|
||||
return [key, enableTagFiltering];
|
||||
} else if (key === "routing_strategy_args" && selectedStrategy === "latency-based-routing") {
|
||||
return [key, formValue.enableTagFiltering];
|
||||
} else if (key === "routing_strategy_args" && formValue.selectedStrategy === "latency-based-routing") {
|
||||
let setRoutingStrategyArgs: routingStrategyArgs = {};
|
||||
|
||||
const lowestLatencyBufferElement = document.querySelector(
|
||||
|
|
@ -175,50 +180,21 @@ const RouterSettings: React.FC<RouterSettingsProps> = ({ accessToken, userRole,
|
|||
}
|
||||
|
||||
return (
|
||||
<div className="w-full space-y-8 py-2">
|
||||
{/* Routing Settings Section */}
|
||||
<div className="space-y-6">
|
||||
<div className="max-w-3xl">
|
||||
<h3 className="text-sm font-medium text-gray-900">Routing Settings</h3>
|
||||
<p className="text-xs text-gray-500 mt-1">Configure how requests are routed to deployments</p>
|
||||
</div>
|
||||
|
||||
{/* Routing Strategy */}
|
||||
{routerSettings.routing_strategy && (
|
||||
<RoutingStrategySelector
|
||||
selectedStrategy={selectedStrategy || routerSettings.routing_strategy}
|
||||
availableStrategies={availableRoutingStrategies}
|
||||
routingStrategyDescriptions={routingStrategyDescriptions}
|
||||
routerFieldsMetadata={routerFieldsMetadata}
|
||||
onStrategyChange={setSelectedStrategy}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Tag Filtering */}
|
||||
<TagFilteringToggle
|
||||
enabled={enableTagFiltering}
|
||||
routerFieldsMetadata={routerFieldsMetadata}
|
||||
onToggle={setEnableTagFiltering}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Divider */}
|
||||
<div className="border-t border-gray-200" />
|
||||
|
||||
{/* Strategy-Specific Args - Show immediately after strategy if latency-based */}
|
||||
{selectedStrategy === "latency-based-routing" && (
|
||||
<LatencyBasedConfiguration routingStrategyArgs={routerSettings["routing_strategy_args"]} />
|
||||
)}
|
||||
|
||||
{/* Other Settings */}
|
||||
<ReliabilityRetriesSection routerSettings={routerSettings} routerFieldsMetadata={routerFieldsMetadata} />
|
||||
<div className="w-full">
|
||||
<RouterSettingsForm
|
||||
value={formValue}
|
||||
onChange={setFormValue}
|
||||
routerFieldsMetadata={routerFieldsMetadata}
|
||||
availableRoutingStrategies={availableRoutingStrategies}
|
||||
routingStrategyDescriptions={routingStrategyDescriptions}
|
||||
/>
|
||||
|
||||
{/* Actions - Sticky at bottom */}
|
||||
<div className="border-t border-gray-200 pt-6 flex justify-end gap-3">
|
||||
<Button variant="secondary" size="sm" onClick={() => window.location.reload()} className="text-sm">
|
||||
Reset
|
||||
</Button>
|
||||
<Button size="sm" onClick={() => handleSaveChanges(routerSettings)} className="text-sm font-medium">
|
||||
<Button size="sm" onClick={handleSaveChanges} className="text-sm font-medium">
|
||||
Save Changes
|
||||
</Button>
|
||||
</div>
|
||||
|
|
@ -227,3 +203,5 @@ const RouterSettings: React.FC<RouterSettingsProps> = ({ accessToken, userRole,
|
|||
};
|
||||
|
||||
export default RouterSettings;
|
||||
export { RouterSettingsForm };
|
||||
export type { RouterSettingsFormValue };
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue