[Feat] UI + Management - Allow setting all routing strategies, tag filtering on UI (#16139)

* add ROUTER_SETTINGS_FIELDS

* feat: add router_settings_router on proxy

* add RouterSettingsField

* update RouterSettingsResponse

* add ROUTER_SETTINGS_FIELDS

* add getRouterSettingsCall

* add UI friendly name

* fix router settings

* fix get descriptions

* fix toggles /refator

* fix refactor

* fix refactor

* fix ROUTING_STRATEGY_DESCRIPTIONS

* fix fields
This commit is contained in:
Ishaan Jaff
2025-10-31 11:29:37 -07:00
committed by GitHub
parent 1f10e8e49c
commit b6312e68f0
13 changed files with 1053 additions and 413 deletions
@@ -7073,7 +7073,8 @@
"output_cost_per_token": 6e-07,
"litellm_provider": "deepinfra",
"mode": "chat",
"supports_tool_choice": true
"supports_tool_choice": true,
"supports_vision": true
},
"deepinfra/Qwen/Qwen3-14B": {
"max_tokens": 40960,
@@ -18620,7 +18621,8 @@
"max_tokens": 8192,
"mode": "chat",
"output_cost_per_token": 6.3e-07,
"supports_tool_choice": true
"supports_tool_choice": true,
"supports_vision": true
},
"openrouter/qwen/qwen3-coder": {
"input_cost_per_token": 1e-06,
@@ -21143,16 +21145,6 @@
"mode": "chat",
"output_cost_per_token": 2.4e-07
},
"vercel_ai_gateway/glm-4.6": {
"litellm_provider": "vercel_ai_gateway",
"cache_read_input_token_cost": 1.1e-07,
"input_cost_per_token": 6e-07,
"max_input_tokens": 200000,
"max_output_tokens": 200000,
"max_tokens": 200000,
"mode": "chat",
"output_cost_per_token": 2.2e-06
},
"vercel_ai_gateway/alibaba/qwen-3-235b": {
"input_cost_per_token": 2e-07,
"litellm_provider": "vercel_ai_gateway",
@@ -21986,6 +21978,20 @@
"mode": "chat",
"output_cost_per_token": 1.1e-06
},
"vercel_ai_gateway/zai/glm-4.6": {
"litellm_provider": "vercel_ai_gateway",
"cache_read_input_token_cost": 1.1e-07,
"input_cost_per_token": 4.5e-07,
"max_input_tokens": 200000,
"max_output_tokens": 200000,
"max_tokens": 200000,
"mode": "chat",
"output_cost_per_token": 1.8e-06,
"source": "https://vercel.com/ai-gateway/models/glm-4.6",
"supports_function_calling": true,
"supports_parallel_function_calling": true,
"supports_tool_choice": true
},
"vertex_ai/claude-3-5-haiku": {
"input_cost_per_token": 1e-06,
"litellm_provider": "vertex_ai-anthropic_models",
@@ -0,0 +1,122 @@
"""
ROUTER SETTINGS MANAGEMENT
Endpoints for accessing router configuration and metadata
GET /router/settings - Get router configuration including available routing strategies
"""
import inspect
from typing import Any, Dict, List, get_args
from fastapi import APIRouter, Depends
from pydantic import BaseModel, Field
from litellm._logging import verbose_proxy_logger
from litellm.proxy._types import UserAPIKeyAuth
from litellm.proxy.auth.user_api_key_auth import user_api_key_auth
from litellm.router import Router
from litellm.types.management_endpoints import (
ROUTER_SETTINGS_FIELDS,
ROUTING_STRATEGY_DESCRIPTIONS,
RouterSettingsField,
)
router = APIRouter()
class RouterSettingsResponse(BaseModel):
fields: List[RouterSettingsField] = Field(
description="List of all configurable router settings with metadata"
)
current_values: Dict[str, Any] = Field(
description="Current values of router settings"
)
routing_strategy_descriptions: Dict[str, str] = Field(
description="Descriptions for each routing strategy option"
)
def _get_routing_strategies_from_router_class() -> List[str]:
"""
Dynamically extract routing strategies from the Router class __init__ method.
"""
# Get the __init__ signature
sig = inspect.signature(Router.__init__)
# Get the routing_strategy parameter
routing_strategy_param = sig.parameters.get("routing_strategy")
if routing_strategy_param and routing_strategy_param.annotation:
# Extract Literal values using get_args
literal_values = get_args(routing_strategy_param.annotation)
if literal_values:
return list(literal_values)
raise ValueError("Unable to extract routing strategies from Router class")
@router.get(
"/router/settings",
tags=["Router Settings"],
dependencies=[Depends(user_api_key_auth)],
response_model=RouterSettingsResponse,
)
async def get_router_settings(
user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth),
):
"""
Get router configuration and available settings.
Returns:
- fields: List of all configurable router settings with their metadata (type, description, default, options)
The routing_strategy field includes available options extracted from the Router class
- current_values: Current values of router settings from config
"""
from litellm.proxy.proxy_server import llm_router, proxy_config
try:
# Get available routing strategies dynamically from Router class
available_routing_strategies = _get_routing_strategies_from_router_class()
# Get router settings fields from types file
router_fields = [field.model_copy(deep=True) for field in ROUTER_SETTINGS_FIELDS]
# Populate routing_strategy field with available options and descriptions
for field in router_fields:
if field.field_name == "routing_strategy":
field.options = available_routing_strategies
break
# Try to get router settings from config
config = await proxy_config.get_config()
router_settings_from_config = config.get("router_settings", {})
# Get current values from llm_router if initialized
current_values = {}
if llm_router is not None:
# Check all field names from the fields list
for field in router_fields:
if hasattr(llm_router, field.field_name):
value = getattr(llm_router, field.field_name)
current_values[field.field_name] = value
# Merge with config values (config takes precedence)
current_values.update(router_settings_from_config)
# Update field values with current values
for field in router_fields:
if field.field_name in current_values:
field.field_value = current_values[field.field_name]
return RouterSettingsResponse(
fields=router_fields,
current_values=current_values,
routing_strategy_descriptions=ROUTING_STRATEGY_DESCRIPTIONS,
)
except Exception as e:
verbose_proxy_logger.error(
f"Error fetching router settings: {str(e)}"
)
raise
+4
View File
@@ -263,6 +263,9 @@ from litellm.proxy.management_endpoints.cost_tracking_settings import (
from litellm.proxy.management_endpoints.customer_endpoints import (
router as customer_router,
)
from litellm.proxy.management_endpoints.router_settings_endpoints import (
router as router_settings_router,
)
from litellm.proxy.management_endpoints.internal_user_endpoints import (
router as internal_user_router,
)
@@ -10040,6 +10043,7 @@ app.include_router(budget_management_router)
app.include_router(model_management_router)
app.include_router(tag_management_router)
app.include_router(cost_tracking_settings_router)
app.include_router(router_settings_router)
app.include_router(user_agent_analytics_router)
app.include_router(enterprise_router)
app.include_router(ui_discovery_endpoints_router)
@@ -0,0 +1,16 @@
"""
Types for management endpoints
"""
from .router_settings_endpoints import (
ROUTER_SETTINGS_FIELDS,
ROUTING_STRATEGY_DESCRIPTIONS,
RouterSettingsField,
)
__all__ = [
"ROUTER_SETTINGS_FIELDS",
"ROUTING_STRATEGY_DESCRIPTIONS",
"RouterSettingsField",
]
@@ -0,0 +1,197 @@
"""
Types and field definitions for router settings management endpoints
"""
from typing import Any, Dict, List, Optional
from pydantic import BaseModel
class RouterSettingsField(BaseModel):
field_name: str
field_type: str
field_value: Any
field_description: str
field_default: Any = None
options: Optional[List[str]] = None # For fields with predefined options/enum values
ui_field_name: str # User-friendly display name
link: Optional[str] = None # Documentation link for the field
# Routing strategy descriptions
ROUTING_STRATEGY_DESCRIPTIONS: Dict[str, str] = {
"simple-shuffle": "Randomly picks a deployment from the list. Simple and fast.",
"least-busy": "Routes to the deployment with the lowest number of ongoing requests.",
"latency-based-routing": "Routes to the deployment with the lowest latency over a sliding window.",
"cost-based-routing": "Routes to the deployment with the lowest cost per token.",
"usage-based-routing": "Routes to the deployment with the lowest TPM (Tokens Per Minute) usage. (deprecated)",
"usage-based-routing-v2": "Improved version of usage-based routing with better tracking.",
}
# Define all available router settings fields
ROUTER_SETTINGS_FIELDS: List[RouterSettingsField] = [
RouterSettingsField(
field_name="routing_strategy",
field_type="String",
field_value=None,
field_description="Routing strategy to use for load balancing across deployments",
field_default="simple-shuffle",
options=[], # Will be populated dynamically from Router class
ui_field_name="Routing Strategy",
),
RouterSettingsField(
field_name="routing_strategy_args",
field_type="Dictionary",
field_value=None,
field_description="Arguments to pass to the routing strategy (e.g., ttl, lowest_latency_buffer for latency-based-routing)",
field_default={},
ui_field_name="Routing Strategy Args",
),
RouterSettingsField(
field_name="num_retries",
field_type="Integer",
field_value=None,
field_description="Number of retries for failed requests",
field_default=0,
ui_field_name="Number of Retries",
),
RouterSettingsField(
field_name="timeout",
field_type="Float",
field_value=None,
field_description="Timeout for requests in seconds",
field_default=None,
ui_field_name="Timeout",
),
RouterSettingsField(
field_name="stream_timeout",
field_type="Float",
field_value=None,
field_description="Timeout for streaming requests in seconds",
field_default=None,
ui_field_name="Stream Timeout",
),
RouterSettingsField(
field_name="max_fallbacks",
field_type="Integer",
field_value=None,
field_description="Maximum number of fallbacks to try before exiting the call",
field_default=5,
ui_field_name="Max Fallbacks",
),
RouterSettingsField(
field_name="fallbacks",
field_type="List",
field_value=None,
field_description="List of fallback model mappings",
field_default=[],
ui_field_name="Fallbacks",
),
RouterSettingsField(
field_name="context_window_fallbacks",
field_type="List",
field_value=None,
field_description="List of fallback models for context window errors",
field_default=[],
ui_field_name="Context Window Fallbacks",
),
RouterSettingsField(
field_name="content_policy_fallbacks",
field_type="List",
field_value=None,
field_description="List of fallback models for content policy errors",
field_default=[],
ui_field_name="Content Policy Fallbacks",
),
RouterSettingsField(
field_name="allowed_fails",
field_type="Integer",
field_value=None,
field_description="Number of times a deployment can fail before being added to cooldown",
field_default=None,
ui_field_name="Allowed Fails",
),
RouterSettingsField(
field_name="cooldown_time",
field_type="Float",
field_value=None,
field_description="Time in seconds to cooldown a deployment after failure",
field_default=None,
ui_field_name="Cooldown Time",
),
RouterSettingsField(
field_name="retry_after",
field_type="Integer",
field_value=None,
field_description="Minimum time to wait before retrying a failed request in seconds",
field_default=0,
ui_field_name="Retry After",
),
RouterSettingsField(
field_name="retry_policy",
field_type="Dictionary",
field_value=None,
field_description="Custom retry policy for different exception types",
field_default=None,
ui_field_name="Retry Policy",
),
RouterSettingsField(
field_name="model_group_alias",
field_type="Dictionary",
field_value=None,
field_description="Aliases for model groups",
field_default={},
ui_field_name="Model Group Alias",
),
RouterSettingsField(
field_name="enable_pre_call_checks",
field_type="Boolean",
field_value=None,
field_description="Enable pre-call checks before routing requests",
field_default=False,
ui_field_name="Enable Pre-call Checks",
),
RouterSettingsField(
field_name="default_litellm_params",
field_type="Dictionary",
field_value=None,
field_description="Default parameters for Router.chat.completion.create",
field_default=None,
ui_field_name="Default LiteLLM Params",
),
RouterSettingsField(
field_name="set_verbose",
field_type="Boolean",
field_value=None,
field_description="Enable verbose logging for router",
field_default=False,
ui_field_name="Verbose Logging",
),
RouterSettingsField(
field_name="default_max_parallel_requests",
field_type="Integer",
field_value=None,
field_description="Default maximum parallel requests across all deployments",
field_default=None,
ui_field_name="Max Parallel Requests",
),
RouterSettingsField(
field_name="enable_tag_filtering",
field_type="Boolean",
field_value=None,
field_description="Enable tag-based routing to route requests based on tags",
field_default=False,
ui_field_name="Enable Tag Filtering",
link="https://docs.litellm.ai/docs/proxy/tag_routing",
),
RouterSettingsField(
field_name="disable_cooldowns",
field_type="Boolean",
field_value=None,
field_description="Disable cooldown mechanism for failed deployments",
field_default=None,
ui_field_name="Disable Cooldowns",
),
]
@@ -0,0 +1,169 @@
import React, { useState, useEffect } from "react";
import {
Table,
TableHead,
TableRow,
TableHeaderCell,
TableCell,
TableBody,
Button,
Icon,
} from "@tremor/react";
import {
getCallbacksCall,
setCallbacksCall,
} from "./networking";
import { TrashIcon } from "@heroicons/react/outline";
import AddFallbacks from "./add_fallbacks";
import openai from "openai";
import NotificationsManager from "./molecules/notifications_manager";
interface FallbacksProps {
accessToken: string | null;
userRole: string | null;
userID: string | null;
modelData: any;
}
async function testFallbackModelResponse(selectedModel: string, accessToken: string) {
const isLocal = process.env.NODE_ENV === "development";
if (isLocal != true) {
console.log = function () {};
}
console.log("isLocal:", isLocal);
const proxyBaseUrl = isLocal ? "http://localhost:4000" : window.location.origin;
const client = new openai.OpenAI({
apiKey: accessToken,
baseURL: proxyBaseUrl,
dangerouslyAllowBrowser: true,
});
try {
const response = await client.chat.completions.create({
model: selectedModel,
messages: [
{
role: "user",
content: "Hi, this is a test message",
},
],
// @ts-ignore
mock_testing_fallbacks: true,
});
NotificationsManager.success(
<span>
Test model=<strong>{selectedModel}</strong>, received model=
<strong>{response.model}</strong>. See{" "}
<a
href="#"
onClick={() => window.open("https://docs.litellm.ai/docs/proxy/reliability", "_blank")}
style={{ textDecoration: "underline", color: "blue" }}
>
curl
</a>
</span>,
);
} catch (error) {
NotificationsManager.fromBackend(
`Error occurred while generating model response. Please try again. Error: ${error}`,
);
}
}
const Fallbacks: React.FC<FallbacksProps> = ({ accessToken, userRole, userID, modelData }) => {
const [routerSettings, setRouterSettings] = useState<{ [key: string]: any }>({});
useEffect(() => {
if (!accessToken || !userRole || !userID) {
return;
}
getCallbacksCall(accessToken, userID, userRole).then((data) => {
console.log("callbacks", data);
let router_settings = data.router_settings;
if ("model_group_retry_policy" in router_settings) {
delete router_settings["model_group_retry_policy"];
}
setRouterSettings(router_settings);
});
}, [accessToken, userRole, userID]);
const deleteFallbacks = async (key: string) => {
if (!accessToken) {
return;
}
console.log(`received key: ${key}`);
console.log(`routerSettings['fallbacks']: ${routerSettings["fallbacks"]}`);
const updatedFallbacks = routerSettings["fallbacks"]
.map((dict: { [key: string]: any }) => {
if (key in dict) {
delete dict[key];
}
return dict;
})
.filter((dict: { [key: string]: any }) => Object.keys(dict).length > 0);
const updatedSettings = {
...routerSettings,
fallbacks: updatedFallbacks,
};
const payload = {
router_settings: updatedSettings,
};
try {
await setCallbacksCall(accessToken, payload);
setRouterSettings(updatedSettings);
NotificationsManager.success("Router settings updated successfully");
} catch (error) {
NotificationsManager.fromBackend("Failed to update router settings: " + error);
}
};
if (!accessToken) {
return null;
}
return (
<>
<Table>
<TableHead>
<TableRow>
<TableHeaderCell>Model Name</TableHeaderCell>
<TableHeaderCell>Fallbacks</TableHeaderCell>
</TableRow>
</TableHead>
<TableBody>
{routerSettings["fallbacks"] &&
routerSettings["fallbacks"].map((item: object, index: number) =>
Object.entries(item).map(([key, value]) => (
<TableRow key={index.toString() + key}>
<TableCell>{key}</TableCell>
<TableCell>{Array.isArray(value) ? value.join(", ") : value}</TableCell>
<TableCell>
<Button onClick={() => testFallbackModelResponse(key, accessToken)}>Test Fallback</Button>
</TableCell>
<TableCell>
<Icon icon={TrashIcon} size="sm" onClick={() => deleteFallbacks(key)} />
</TableCell>
</TableRow>
)),
)}
</TableBody>
</Table>
<AddFallbacks
models={modelData?.data ? modelData.data.map((data: any) => data.model_name) : []}
accessToken={accessToken}
routerSettings={routerSettings}
setRouterSettings={setRouterSettings}
/>
</>
);
};
export default Fallbacks;
@@ -10,20 +10,11 @@ import {
TableCell,
TableBody,
Text,
Grid,
Button,
TextInput,
Select as Select2,
SelectItem,
Col,
Accordion,
AccordionBody,
AccordionHeader,
Icon,
} from "@tremor/react";
import { TabPanel, TabPanels, TabGroup, TabList, Tab, Icon } from "@tremor/react";
import { TabPanel, TabPanels, TabGroup, TabList, Tab } from "@tremor/react";
import {
getCallbacksCall,
setCallbacksCall,
getGeneralSettingsCall,
updateConfigFieldSetting,
deleteConfigFieldSetting,
@@ -31,9 +22,8 @@ import {
import { Form, InputNumber } from "antd";
import { TrashIcon, CheckCircleIcon } from "@heroicons/react/outline";
import AddFallbacks from "./add_fallbacks";
import openai from "openai";
import NotificationsManager from "./molecules/notifications_manager";
import RouterSettings from "./router_settings";
import Fallbacks from "./fallbacks";
interface GeneralSettingsPageProps {
accessToken: string | null;
userRole: string | null;
@@ -41,64 +31,6 @@ interface GeneralSettingsPageProps {
modelData: any;
}
async function testFallbackModelResponse(selectedModel: string, accessToken: string) {
// base url should be the current base_url
const isLocal = process.env.NODE_ENV === "development";
if (isLocal != true) {
console.log = function () {};
}
console.log("isLocal:", isLocal);
const proxyBaseUrl = isLocal ? "http://localhost:4000" : window.location.origin;
const client = new openai.OpenAI({
apiKey: accessToken, // Replace with your OpenAI API key
baseURL: proxyBaseUrl, // Replace with your OpenAI API base URL
dangerouslyAllowBrowser: true, // using a temporary litellm proxy key
});
try {
const response = await client.chat.completions.create({
model: selectedModel,
messages: [
{
role: "user",
content: "Hi, this is a test message",
},
],
// @ts-ignore
mock_testing_fallbacks: true,
});
NotificationsManager.success(
<span>
Test model=<strong>{selectedModel}</strong>, received model=
<strong>{response.model}</strong>. See{" "}
<a
href="#"
onClick={() => window.open("https://docs.litellm.ai/docs/proxy/reliability", "_blank")}
style={{ textDecoration: "underline", color: "blue" }}
>
curl
</a>
</span>,
);
} catch (error) {
NotificationsManager.fromBackend(
`Error occurred while generating model response. Please try again. Error: ${error}`,
);
}
}
interface AccordionHeroProps {
selectedStrategy: string | null;
strategyArgs: routingStrategyArgs;
paramExplanation: { [key: string]: string };
}
interface routingStrategyArgs {
ttl?: number;
lowest_latency_buffer?: number;
}
interface generalSettingsItem {
field_name: string;
field_type: string;
@@ -107,152 +39,18 @@ interface generalSettingsItem {
stored_in_db: boolean | null;
}
const defaultLowestLatencyArgs: routingStrategyArgs = {
ttl: 3600,
lowest_latency_buffer: 0,
};
export const AccordionHero: React.FC<AccordionHeroProps> = ({ selectedStrategy, strategyArgs, paramExplanation }) => (
<Accordion>
<AccordionHeader className="text-sm font-medium text-tremor-content-strong dark:text-dark-tremor-content-strong">
Routing Strategy Specific Args
</AccordionHeader>
<AccordionBody>
{selectedStrategy == "latency-based-routing" ? (
<Card>
<Table>
<TableHead>
<TableRow>
<TableHeaderCell>Setting</TableHeaderCell>
<TableHeaderCell>Value</TableHeaderCell>
</TableRow>
</TableHead>
<TableBody>
{Object.entries(strategyArgs).map(([param, value]) => (
<TableRow key={param}>
<TableCell>
<Text>{param}</Text>
<p
style={{
fontSize: "0.65rem",
color: "#808080",
fontStyle: "italic",
}}
className="mt-1"
>
{paramExplanation[param]}
</p>
</TableCell>
<TableCell>
<TextInput
name={param}
defaultValue={typeof value === "object" ? JSON.stringify(value, null, 2) : value.toString()}
/>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</Card>
) : (
<Text>No specific settings</Text>
)}
</AccordionBody>
</Accordion>
);
const GeneralSettings: React.FC<GeneralSettingsPageProps> = ({ accessToken, userRole, userID, modelData }) => {
const [routerSettings, setRouterSettings] = useState<{ [key: string]: any }>({});
const [generalSettingsDict, setGeneralSettingsDict] = useState<{
[key: string]: any;
}>({});
const [generalSettings, setGeneralSettings] = useState<generalSettingsItem[]>([]);
const [isModalVisible, setIsModalVisible] = useState(false);
const [form] = Form.useForm();
const [selectedCallback, setSelectedCallback] = useState<string | null>(null);
const [selectedStrategy, setSelectedStrategy] = useState<string | null>(null);
const [strategySettings, setStrategySettings] = useState<routingStrategyArgs | null>(null);
let paramExplanation: { [key: string]: string } = {
routing_strategy_args: "(dict) Arguments to pass to the routing strategy",
routing_strategy: "(string) Routing strategy to use",
allowed_fails: "(int) Number of times a deployment can fail before being added to cooldown",
cooldown_time: "(int) time in seconds to cooldown a deployment after failure",
num_retries: "(int) Number of retries for failed requests. Defaults to 0.",
timeout: "(float) Timeout for requests. Defaults to None.",
retry_after: "(int) Minimum time to wait before retrying a failed request",
ttl: "(int) Sliding window to look back over when calculating the average latency of a deployment. Default - 1 hour (in seconds).",
lowest_latency_buffer:
"(float) Shuffle between deployments within this % of the lowest latency. Default - 0 (i.e. always pick lowest latency).",
};
useEffect(() => {
if (!accessToken || !userRole || !userID) {
if (!accessToken) {
return;
}
getCallbacksCall(accessToken, userID, userRole).then((data) => {
console.log("callbacks", data);
let router_settings = data.router_settings;
// remove "model_group_retry_policy" from general_settings if exists
if ("model_group_retry_policy" in router_settings) {
delete router_settings["model_group_retry_policy"];
}
setRouterSettings(router_settings);
});
getGeneralSettingsCall(accessToken).then((data) => {
let general_settings = data;
setGeneralSettings(general_settings);
});
}, [accessToken, userRole, userID]);
const handleAddCallback = () => {
console.log("Add callback clicked");
setIsModalVisible(true);
};
const handleCancel = () => {
setIsModalVisible(false);
form.resetFields();
setSelectedCallback(null);
};
const deleteFallbacks = async (key: string) => {
/**
* pop the key from the Object, if it exists
*/
if (!accessToken) {
return;
}
console.log(`received key: ${key}`);
console.log(`routerSettings['fallbacks']: ${routerSettings["fallbacks"]}`);
const updatedFallbacks = routerSettings["fallbacks"]
.map((dict: { [key: string]: any }) => {
if (key in dict) {
delete dict[key];
}
return dict;
})
.filter((dict: { [key: string]: any }) => Object.keys(dict).length > 0);
const updatedSettings = {
...routerSettings,
fallbacks: updatedFallbacks,
};
const payload = {
router_settings: updatedSettings,
};
try {
await setCallbacksCall(accessToken, payload);
setRouterSettings(updatedSettings);
NotificationsManager.success("Router settings updated successfully");
} catch (error) {
NotificationsManager.fromBackend("Failed to update router settings: " + error);
}
};
}, [accessToken]);
const handleInputChange = (fieldName: string, newValue: any) => {
// Update the value in the state
@@ -303,212 +101,33 @@ const GeneralSettings: React.FC<GeneralSettingsPageProps> = ({ accessToken, user
}
};
const handleSaveChanges = (router_settings: any) => {
if (!accessToken) {
return;
}
console.log("router_settings", router_settings);
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) return fallback;
const v = raw.trim();
if (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;
};
const updatedVariables = Object.fromEntries(
Object.entries(router_settings)
.map(([key, value]) => {
if (key !== "routing_strategy_args" && key !== "routing_strategy") {
const inputEl = document.querySelector(`input[name="${key}"]`) as HTMLInputElement | null;
const parsed = parseInputValue(key, inputEl?.value, value);
return [key, parsed];
} else if (key === "routing_strategy") {
return [key, selectedStrategy];
} else if (key === "routing_strategy_args" && selectedStrategy === "latency-based-routing") {
let setRoutingStrategyArgs: routingStrategyArgs = {};
const lowestLatencyBufferElement = document.querySelector(
`input[name="lowest_latency_buffer"]`,
) as HTMLInputElement;
const ttlElement = document.querySelector(`input[name="ttl"]`) as HTMLInputElement;
if (lowestLatencyBufferElement?.value) {
setRoutingStrategyArgs["lowest_latency_buffer"] = Number(lowestLatencyBufferElement.value);
}
if (ttlElement?.value) {
setRoutingStrategyArgs["ttl"] = Number(ttlElement.value);
}
console.log(`setRoutingStrategyArgs: ${setRoutingStrategyArgs}`);
return ["routing_strategy_args", setRoutingStrategyArgs];
}
return null;
})
.filter((entry) => entry !== null && entry !== undefined) as Iterable<[string, unknown]>,
);
console.log("updatedVariables", updatedVariables);
const payload = {
router_settings: updatedVariables,
};
try {
setCallbacksCall(accessToken, payload);
} catch (error) {
NotificationsManager.fromBackend("Failed to update router settings: " + error);
}
NotificationsManager.success("router settings updated successfully");
};
if (!accessToken) {
return null;
}
return (
<div className="w-full mx-4">
<TabGroup className="gap-2 p-8 h-[75vh] w-full mt-2">
<TabList variant="line" defaultValue="1">
<div className="w-full">
<TabGroup className="h-[75vh] w-full">
<TabList variant="line" defaultValue="1" className="px-8 pt-4">
<Tab value="1">Loadbalancing</Tab>
<Tab value="2">Fallbacks</Tab>
<Tab value="3">General</Tab>
</TabList>
<TabPanels>
<TabPanels className="px-8 py-6">
<TabPanel>
<Grid numItems={1} className="gap-2 p-8 w-full mt-2">
<Title>Router Settings</Title>
<Card>
<Table>
<TableHead>
<TableRow>
<TableHeaderCell>Setting</TableHeaderCell>
<TableHeaderCell>Value</TableHeaderCell>
</TableRow>
</TableHead>
<TableBody>
{Object.entries(routerSettings)
.filter(
([param, value]) =>
param != "fallbacks" &&
param != "context_window_fallbacks" &&
param != "routing_strategy_args",
)
.map(([param, value]) => (
<TableRow key={param}>
<TableCell>
<Text>{param}</Text>
<p
style={{
fontSize: "0.65rem",
color: "#808080",
fontStyle: "italic",
}}
className="mt-1"
>
{paramExplanation[param]}
</p>
</TableCell>
<TableCell>
{param == "routing_strategy" ? (
<Select2
defaultValue={value}
className="w-full max-w-md"
onValueChange={setSelectedStrategy}
>
<SelectItem value="usage-based-routing">usage-based-routing</SelectItem>
<SelectItem value="latency-based-routing">latency-based-routing</SelectItem>
<SelectItem value="simple-shuffle">simple-shuffle</SelectItem>
</Select2>
) : (
<TextInput
name={param}
defaultValue={
typeof value === "object" ? JSON.stringify(value, null, 2) : value.toString()
}
/>
)}
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
<AccordionHero
selectedStrategy={selectedStrategy}
strategyArgs={
routerSettings &&
routerSettings["routing_strategy_args"] &&
Object.keys(routerSettings["routing_strategy_args"]).length > 0
? routerSettings["routing_strategy_args"]
: defaultLowestLatencyArgs // default value when keys length is 0
}
paramExplanation={paramExplanation}
/>
</Card>
<Col>
<Button className="mt-2" onClick={() => handleSaveChanges(routerSettings)}>
Save Changes
</Button>
</Col>
</Grid>
<RouterSettings
accessToken={accessToken}
userRole={userRole}
userID={userID}
modelData={modelData}
/>
</TabPanel>
<TabPanel>
<Table>
<TableHead>
<TableRow>
<TableHeaderCell>Model Name</TableHeaderCell>
<TableHeaderCell>Fallbacks</TableHeaderCell>
</TableRow>
</TableHead>
<TableBody>
{routerSettings["fallbacks"] &&
routerSettings["fallbacks"].map((item: object, index: number) =>
Object.entries(item).map(([key, value]) => (
<TableRow key={index.toString() + key}>
<TableCell>{key}</TableCell>
<TableCell>{Array.isArray(value) ? value.join(", ") : value}</TableCell>
<TableCell>
<Button onClick={() => testFallbackModelResponse(key, accessToken)}>Test Fallback</Button>
</TableCell>
<TableCell>
<Icon icon={TrashIcon} size="sm" onClick={() => deleteFallbacks(key)} />
</TableCell>
</TableRow>
)),
)}
</TableBody>
</Table>
<AddFallbacks
models={modelData?.data ? modelData.data.map((data: any) => data.model_name) : []}
<Fallbacks
accessToken={accessToken}
routerSettings={routerSettings}
setRouterSettings={setRouterSettings}
userRole={userRole}
userID={userID}
modelData={modelData}
/>
</TabPanel>
<TabPanel>
@@ -4442,6 +4442,35 @@ export const getGeneralSettingsCall = async (accessToken: string) => {
}
};
export const getRouterSettingsCall = async (accessToken: string) => {
try {
let url = proxyBaseUrl
? `${proxyBaseUrl}/router/settings`
: `/router/settings`;
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 router settings:", error);
throw error;
}
};
export const getPassThroughEndpointsCall = async (accessToken: string, teamId?: string | null) => {
try {
let url = proxyBaseUrl ? `${proxyBaseUrl}/config/pass_through_endpoint` : `/config/pass_through_endpoint`;
@@ -0,0 +1,62 @@
import React from "react";
import { TextInput } from "@tremor/react";
interface routingStrategyArgs {
ttl?: number;
lowest_latency_buffer?: number;
}
const defaultLowestLatencyArgs: routingStrategyArgs = {
ttl: 3600,
lowest_latency_buffer: 0,
};
interface LatencyBasedConfigurationProps {
routingStrategyArgs: { [key: string]: any };
}
const LatencyBasedConfiguration: React.FC<LatencyBasedConfigurationProps> = ({
routingStrategyArgs,
}) => {
const paramExplanation: { [key: string]: string } = {
ttl: "Sliding window to look back over when calculating the average latency of a deployment. Default - 1 hour (in seconds).",
lowest_latency_buffer:
"Shuffle between deployments within this % of the lowest latency. Default - 0 (i.e. always pick lowest latency).",
};
return (
<>
<div className="space-y-6">
<div className="max-w-3xl">
<h3 className="text-sm font-medium text-gray-900">Latency-Based Configuration</h3>
<p className="text-xs text-gray-500 mt-1">Fine-tune latency-based routing behavior</p>
</div>
<div className="grid grid-cols-1 gap-6 lg:grid-cols-2 xl:grid-cols-3">
{Object.entries(routingStrategyArgs || defaultLowestLatencyArgs).map(([param, value]) => (
<div key={param} className="space-y-2">
<label className="block">
<span className="text-xs font-medium text-gray-700 uppercase tracking-wide">
{param.replace(/_/g, " ")}
</span>
<p className="text-xs text-gray-500 mt-0.5 mb-2">
{paramExplanation[param] || ""}
</p>
<TextInput
name={param}
defaultValue={typeof value === "object" ? JSON.stringify(value, null, 2) : value?.toString()}
className="font-mono text-sm w-full"
/>
</label>
</div>
))}
</div>
</div>
<div className="border-t border-gray-200" />
</>
);
};
export default LatencyBasedConfiguration;
@@ -0,0 +1,56 @@
import React from "react";
import { TextInput } from "@tremor/react";
interface ReliabilityRetriesSectionProps {
routerSettings: { [key: string]: any };
routerFieldsMetadata: { [key: string]: any };
}
const ReliabilityRetriesSection: React.FC<ReliabilityRetriesSectionProps> = ({
routerSettings,
routerFieldsMetadata,
}) => {
return (
<div className="space-y-6">
<div className="max-w-3xl">
<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(
([param, value]) =>
param != "fallbacks" &&
param != "context_window_fallbacks" &&
param != "routing_strategy_args" &&
param != "routing_strategy" &&
param != "enable_tag_filtering",
)
.map(([param, value]) => (
<div key={param} className="space-y-2">
<label className="block">
<span className="text-xs font-medium text-gray-700 uppercase tracking-wide">
{routerFieldsMetadata[param]?.ui_field_name || param}
</span>
<p className="text-xs text-gray-500 mt-0.5 mb-2">
{routerFieldsMetadata[param]?.field_description || ""}
</p>
<TextInput
name={param}
defaultValue={
typeof value === "object" ? JSON.stringify(value, null, 2) : value?.toString() || ""
}
placeholder="—"
className="font-mono text-sm w-full"
/>
</label>
</div>
))}
</div>
</div>
);
};
export default ReliabilityRetriesSection;
@@ -0,0 +1,55 @@
import React from "react";
import { Select } from "antd";
interface RoutingStrategySelectorProps {
selectedStrategy: string | null;
availableStrategies: string[];
routingStrategyDescriptions: { [key: string]: string };
routerFieldsMetadata: { [key: string]: any };
onStrategyChange: (strategy: string) => void;
}
const RoutingStrategySelector: React.FC<RoutingStrategySelectorProps> = ({
selectedStrategy,
availableStrategies,
routingStrategyDescriptions,
routerFieldsMetadata,
onStrategyChange,
}) => {
return (
<div className="space-y-2 max-w-3xl">
<div>
<label className="text-xs font-medium text-gray-700 uppercase tracking-wide">
{routerFieldsMetadata["routing_strategy"]?.ui_field_name || "Routing Strategy"}
</label>
<p className="text-xs text-gray-500 mt-0.5 mb-2">
{routerFieldsMetadata["routing_strategy"]?.field_description || ""}
</p>
</div>
<div className="routing-strategy-select max-w-3xl">
<Select
value={selectedStrategy}
onChange={onStrategyChange}
style={{ width: "100%" }}
size="large"
>
{availableStrategies.map((strategy) => (
<Select.Option key={strategy} value={strategy} label={strategy}>
<div className="flex flex-col gap-0.5 py-1">
<span className="font-mono text-sm font-medium">{strategy}</span>
{routingStrategyDescriptions[strategy] && (
<span className="text-xs text-gray-500 font-normal">
{routingStrategyDescriptions[strategy]}
</span>
)}
</div>
</Select.Option>
))}
</Select>
</div>
</div>
);
};
export default RoutingStrategySelector;
@@ -0,0 +1,50 @@
import React from "react";
import { Switch } from "@tremor/react";
interface TagFilteringToggleProps {
enabled: boolean;
routerFieldsMetadata: { [key: string]: any };
onToggle: (enabled: boolean) => void;
}
const TagFilteringToggle: React.FC<TagFilteringToggleProps> = ({
enabled,
routerFieldsMetadata,
onToggle,
}) => {
return (
<div className="space-y-3 max-w-3xl">
<div className="flex items-start justify-between">
<div className="flex-1">
<label className="text-xs font-medium text-gray-700 uppercase tracking-wide">
{routerFieldsMetadata["enable_tag_filtering"]?.ui_field_name || "Enable Tag Filtering"}
</label>
<p className="text-xs text-gray-500 mt-0.5">
{routerFieldsMetadata["enable_tag_filtering"]?.field_description || ""}
{routerFieldsMetadata["enable_tag_filtering"]?.link && (
<>
{" "}
<a
href={routerFieldsMetadata["enable_tag_filtering"].link}
target="_blank"
rel="noopener noreferrer"
className="text-blue-600 hover:text-blue-800 underline"
>
Learn more
</a>
</>
)}
</p>
</div>
<Switch
checked={enabled}
onChange={onToggle}
className="ml-4"
/>
</div>
</div>
);
};
export default TagFilteringToggle;
@@ -0,0 +1,255 @@
import React, { useState, useEffect } from "react";
import {
Button,
TextInput,
} from "@tremor/react";
import {
getCallbacksCall,
setCallbacksCall,
getRouterSettingsCall,
} from "../networking";
import NotificationsManager from "../molecules/notifications_manager";
import RoutingStrategySelector from "./RoutingStrategySelector";
import TagFilteringToggle from "./TagFilteringToggle";
import LatencyBasedConfiguration from "./LatencyBasedConfiguration";
import ReliabilityRetriesSection from "./ReliabilityRetriesSection";
interface RouterSettingsProps {
accessToken: string | null;
userRole: string | null;
userID: string | null;
modelData: any;
}
interface routingStrategyArgs {
ttl?: number;
lowest_latency_buffer?: number;
}
const RouterSettings: React.FC<RouterSettingsProps> = ({ accessToken, userRole, userID, modelData }) => {
const [routerSettings, setRouterSettings] = useState<{ [key: string]: any }>({});
const [selectedStrategy, setSelectedStrategy] = useState<string | null>(null);
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) {
return;
}
getCallbacksCall(accessToken, userID, userRole).then((data) => {
console.log("callbacks", data);
let router_settings = data.router_settings;
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);
}
});
getRouterSettingsCall(accessToken).then((data) => {
console.log("router settings from API", 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);
}
// 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);
}
}
});
}, [accessToken, userRole, userID]);
const handleSaveChanges = (router_settings: any) => {
if (!accessToken) {
return;
}
console.log("router_settings", router_settings);
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) return fallback;
const v = raw.trim();
if (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;
};
// Add enable_tag_filtering to router_settings before processing
const settingsToUpdate = {
...router_settings,
enable_tag_filtering: enableTagFiltering,
};
const updatedVariables = Object.fromEntries(
Object.entries(settingsToUpdate)
.map(([key, value]) => {
if (key !== "routing_strategy_args" && key !== "routing_strategy" && key !== "enable_tag_filtering") {
const inputEl = document.querySelector(`input[name="${key}"]`) as HTMLInputElement | null;
const parsed = parseInputValue(key, inputEl?.value, value);
return [key, parsed];
} else if (key === "routing_strategy") {
return [key, selectedStrategy];
} else if (key === "enable_tag_filtering") {
return [key, enableTagFiltering];
} else if (key === "routing_strategy_args" && selectedStrategy === "latency-based-routing") {
let setRoutingStrategyArgs: routingStrategyArgs = {};
const lowestLatencyBufferElement = document.querySelector(
`input[name="lowest_latency_buffer"]`,
) as HTMLInputElement;
const ttlElement = document.querySelector(`input[name="ttl"]`) as HTMLInputElement;
if (lowestLatencyBufferElement?.value) {
setRoutingStrategyArgs["lowest_latency_buffer"] = Number(lowestLatencyBufferElement.value);
}
if (ttlElement?.value) {
setRoutingStrategyArgs["ttl"] = Number(ttlElement.value);
}
console.log(`setRoutingStrategyArgs: ${setRoutingStrategyArgs}`);
return ["routing_strategy_args", setRoutingStrategyArgs];
}
return null;
})
.filter((entry) => entry !== null && entry !== undefined) as Iterable<[string, unknown]>,
);
console.log("updatedVariables", updatedVariables);
const payload = {
router_settings: updatedVariables,
};
try {
setCallbacksCall(accessToken, payload);
} catch (error) {
NotificationsManager.fromBackend("Failed to update router settings: " + error);
}
NotificationsManager.success("router settings updated successfully");
};
if (!accessToken) {
return null;
}
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}
/>
{/* 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"
>
Save Changes
</Button>
</div>
</div>
);
};
export default RouterSettings;