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

1""" 

2ROUTER SETTINGS MANAGEMENT 

3 

4Endpoints for accessing router configuration and metadata 

5 

6GET /router/settings - Get router configuration including available routing strategies 

7GET /router/fields - Get router settings field definitions without values (for UI rendering) 

8""" 

9 

10import inspect 

11from collections.abc import Mapping 

12from types import MappingProxyType 

13from typing import Any, Final, get_args 

14 

15from fastapi import APIRouter, Depends 

16from pydantic import BaseModel, Field 

17 

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) 

28 

29router: Final = APIRouter() 

30 

31 

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") 

40 

41 

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 ) 

50 

51 

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" 

62 

63 

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__) 

70 

71 # Get the routing_strategy parameter 

72 routing_strategy_param: Final = sig.parameters.get("routing_strategy") 

73 

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) 

79 

80 raise ValueError("Unable to extract routing strategies from Router class") 

81 

82 

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. 

94 

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 

101 

102 try: 

103 # Get available routing strategies dynamically from Router class 

104 available_routing_strategies: Final = _get_routing_strategies_from_router_class() 

105 

106 # Get router settings fields from types file 

107 router_fields: Final = [field.model_copy(deep=True) for field in ROUTER_SETTINGS_FIELDS] 

108 

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 

114 

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", {}) 

118 

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 

133 

134 # Merge with config values (config takes precedence) 

135 current_values.update(router_settings_from_config) 

136 

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] 

140 

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 

165 

166 

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. 

178 

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. 

182 

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() 

192 

193 # Get router settings fields from types file 

194 router_fields: Final = [field.model_copy(deep=True) for field in ROUTER_SETTINGS_FIELDS] 

195 

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 

201 

202 # Ensure field_value is None for all fields (don't populate values) 

203 for field in router_fields: 

204 field.field_value = None 

205 

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