Coverage for .venv/lib/python3.13/site-packages/litellm/proxy/management_endpoints/router_settings_endpoints.py: 85%
82 statements
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-10 12:01 +0000
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-10 12:01 +0000
1"""
2ROUTER SETTINGS MANAGEMENT
4Endpoints for accessing router configuration and metadata
6GET /router/settings - Get router configuration including available routing strategies
7GET /router/fields - Get router settings field definitions without values (for UI rendering)
8"""
10import inspect
11from collections.abc import Mapping
12from types import MappingProxyType
13from typing import Any, Final, get_args
15from fastapi import APIRouter, Depends
16from pydantic import BaseModel, Field
18from litellm._logging import verbose_proxy_logger
19from litellm.proxy._types import UserAPIKeyAuth
20from litellm.proxy.auth.user_api_key_auth import user_api_key_auth
21from litellm.proxy.config_resolvers import FieldSource, SettingsStore, source_for
22from litellm.router import Router
23from litellm.types.management_endpoints import (
24 ROUTER_SETTINGS_FIELDS,
25 ROUTING_STRATEGY_DESCRIPTIONS,
26 RouterSettingsField,
27)
29router: Final = APIRouter()
32class RouterSettingsResponse(BaseModel):
33 fields: list[RouterSettingsField] = Field(description="List of all configurable router settings with metadata")
34 current_values: dict[str, Any] = Field(description="Current values of router settings")
35 routing_strategy_descriptions: dict[str, str] = Field(description="Descriptions for each routing strategy option")
36 routing_group_strategies: tuple[str, ...] = Field(
37 description="Strategies supported when constructing a routing group"
38 )
39 source: dict[str, FieldSource] = Field(description="Source of each current router setting")
42class RouterFieldsResponse(BaseModel):
43 fields: list[RouterSettingsField] = Field(
44 description="List of all configurable router settings with metadata (without field values)"
45 )
46 routing_strategy_descriptions: dict[str, str] = Field(description="Descriptions for each routing strategy option")
47 routing_group_strategies: tuple[str, ...] = Field(
48 description="Strategies supported when constructing a routing group"
49 )
52def _router_setting_source(
53 settings: SettingsStore,
54 key: str,
55 current_value: object,
56 field_default: object,
57) -> FieldSource:
58 source: Final = source_for(settings, key, field_default)
59 if source != "unset":
60 return source
61 return "default" if current_value is not None else "unset"
64def _get_routing_strategies_from_router_class() -> list[str]:
65 """
66 Dynamically extract routing strategies from the Router class __init__ method.
67 """
68 # Get the __init__ signature
69 sig: Final = inspect.signature(Router.__init__)
71 # Get the routing_strategy parameter
72 routing_strategy_param: Final = sig.parameters.get("routing_strategy")
74 if routing_strategy_param and routing_strategy_param.annotation: 74 ↛ 80line 74 didn't jump to line 80 because the condition on line 74 was always true
75 # Extract Literal values using get_args
76 literal_values: Final = get_args(routing_strategy_param.annotation)
77 if literal_values: 77 ↛ 80line 77 didn't jump to line 80 because the condition on line 77 was always true
78 return list(literal_values)
80 raise ValueError("Unable to extract routing strategies from Router class")
83@router.get(
84 "/router/settings",
85 tags=["Router Settings"],
86 dependencies=[Depends(user_api_key_auth)],
87 response_model=RouterSettingsResponse,
88)
89async def get_router_settings(
90 user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth),
91):
92 """
93 Get router configuration and available settings.
95 Returns:
96 - fields: List of all configurable router settings with their metadata (type, description, default, options)
97 The routing_strategy field includes available options extracted from the Router class
98 - current_values: Current values of router settings from config
99 """
100 from litellm.proxy.proxy_server import llm_router, proxy_config
102 try:
103 # Get available routing strategies dynamically from Router class
104 available_routing_strategies: Final = _get_routing_strategies_from_router_class()
106 # Get router settings fields from types file
107 router_fields: Final = [field.model_copy(deep=True) for field in ROUTER_SETTINGS_FIELDS]
109 # Populate routing_strategy field with available options and descriptions
110 for field in router_fields: 110 ↛ 116line 110 didn't jump to line 116 because the loop on line 110 didn't complete
111 if field.field_name == "routing_strategy": 111 ↛ 110line 111 didn't jump to line 110 because the condition on line 111 was always true
112 field.options = available_routing_strategies
113 break
115 # Try to get router settings from config
116 config: Final = await proxy_config.get_config()
117 router_settings_from_config: Final = config.get("router_settings", {})
119 current_values: Final[dict[str, Any]] = {}
120 if llm_router is not None: 120 ↛ 135line 120 didn't jump to line 135 because the condition on line 120 was always true
121 # Router exposes routing groups as private `_routing_groups`; the
122 # generic `hasattr` loop below would miss them.
123 current_values["routing_groups"] = [
124 group.model_dump(exclude=frozenset(("model_priorities",)) if group.model_priorities is None else None)
125 for group in llm_router._routing_groups.values()
126 ]
127 for field in router_fields:
128 if field.field_name == "routing_groups":
129 continue
130 if hasattr(llm_router, field.field_name): 130 ↛ 127line 130 didn't jump to line 127 because the condition on line 130 was always true
131 value = getattr(llm_router, field.field_name)
132 current_values[field.field_name] = value
134 # Merge with config values (config takes precedence)
135 current_values.update(router_settings_from_config)
137 for field in router_fields:
138 if field.field_name in current_values: 138 ↛ 137line 138 didn't jump to line 137 because the condition on line 138 was always true
139 field.field_value = current_values[field.field_name]
141 field_defaults: Final[Mapping[str, object]] = MappingProxyType(
142 {field.field_name: field.field_default for field in router_fields}
143 )
144 source: Final[Mapping[str, FieldSource]] = MappingProxyType(
145 {
146 key: _router_setting_source(
147 proxy_config.router_settings,
148 key,
149 current_values[key],
150 field_defaults.get(key),
151 )
152 for key in current_values
153 }
154 )
155 return RouterSettingsResponse(
156 fields=router_fields,
157 current_values=current_values,
158 routing_strategy_descriptions=ROUTING_STRATEGY_DESCRIPTIONS,
159 routing_group_strategies=(*available_routing_strategies, "priority"),
160 source=source,
161 )
162 except Exception as e:
163 verbose_proxy_logger.error("Error fetching router settings: %s", e)
164 raise
167@router.get(
168 "/router/fields",
169 tags=["Router Settings"],
170 dependencies=[Depends(user_api_key_auth)],
171 response_model=RouterFieldsResponse,
172)
173async def get_router_fields(
174 user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth),
175):
176 """
177 Get router settings field definitions without values.
179 Returns only the field metadata (type, description, default, options) without
180 populating field_value. This is useful for UI components that need to know
181 what fields to render, but will get the actual values from a different endpoint.
183 Returns:
184 - fields: List of all configurable router settings with their metadata (type, description, default, options)
185 The routing_strategy field includes available options extracted from the Router class
186 Note: field_value will be None for all fields
187 - routing_strategy_descriptions: Descriptions for each routing strategy option
188 """
189 try:
190 # Get available routing strategies dynamically from Router class
191 available_routing_strategies: Final = _get_routing_strategies_from_router_class()
193 # Get router settings fields from types file
194 router_fields: Final = [field.model_copy(deep=True) for field in ROUTER_SETTINGS_FIELDS]
196 # Populate routing_strategy field with available options
197 for field in router_fields: 197 ↛ 203line 197 didn't jump to line 203 because the loop on line 197 didn't complete
198 if field.field_name == "routing_strategy": 198 ↛ 197line 198 didn't jump to line 197 because the condition on line 198 was always true
199 field.options = available_routing_strategies
200 break
202 # Ensure field_value is None for all fields (don't populate values)
203 for field in router_fields:
204 field.field_value = None
206 return RouterFieldsResponse(
207 fields=router_fields,
208 routing_strategy_descriptions=ROUTING_STRATEGY_DESCRIPTIONS,
209 routing_group_strategies=(*available_routing_strategies, "priority"),
210 )
211 except Exception as e:
212 verbose_proxy_logger.error("Error fetching router fields: %s", e)
213 raise