Coverage for .venv/lib/python3.13/site-packages/litellm/proxy/_lazy_features.py: 75%

169 statements  

« prev     ^ index     » next       coverage.py v7.15.2, created at 2026-10-10 12:01 +0000

1""" 

2Lazy registration for optional feature routers. Each LAZY_FEATURES entry 

3imports its module only on the first request matching its path prefix, 

4saving ~700 MB at idle for deployments that don't use these features. 

5First hit pays the import cost (1-3 s for heavy modules); /openapi.json 

6omits each feature's routes until the feature is warmed. 

7""" 

8 

9import asyncio 

10import importlib 

11from collections.abc import Callable, Mapping, Sequence 

12from collections.abc import Set as AbstractSet 

13from dataclasses import dataclass, field 

14from itertools import chain 

15from types import MappingProxyType 

16from typing import TYPE_CHECKING, Final 

17 

18from starlette.routing import BaseRoute, Match 

19from starlette.types import ASGIApp, Receive, Scope, Send 

20 

21from litellm._logging import verbose_proxy_logger 

22from litellm.proxy.route_priority import hot_routes_first 

23 

24if TYPE_CHECKING: 24 ↛ 25line 24 didn't jump to line 25 because the condition on line 24 was never true

25 from fastapi import APIRouter, FastAPI 

26 

27 

28def _include_router(attr_name: str = "router") -> Callable[["FastAPI", object], None]: 

29 def _register(app: "FastAPI", module: object) -> None: 

30 app.include_router(getattr(module, attr_name)) 

31 

32 return _register 

33 

34 

35def _mount_app(prefix: str, attr_name: str = "app") -> Callable[["FastAPI", object], None]: 

36 def _register(app: "FastAPI", module: object) -> None: 

37 app.mount(path=prefix, app=getattr(module, attr_name)) 

38 

39 return _register 

40 

41 

42@dataclass(frozen=True) 

43class LazyFeature: 

44 name: str 

45 module_path: str 

46 path_prefixes: tuple[str, ...] 

47 register_fn: Callable[["FastAPI", object], None] = field(default_factory=lambda: _include_router("router")) 

48 # For routes whose path has a leading parameter (e.g. /{server}/authorize) 

49 # — startswith can't match those, so the matcher also checks endswith. 

50 path_suffixes: tuple[str, ...] = () 

51 # Keep the stub injected even after load — for mounted ASGI sub-apps 

52 # whose routes don't appear in the parent app's openapi spec. 

53 persistent_swagger_stub: bool = False 

54 

55 def matches(self, path: str) -> bool: 

56 return any(path.startswith(p) for p in self.path_prefixes) or any(path.endswith(s) for s in self.path_suffixes) 

57 

58 

59LAZY_FEATURES: Final[tuple[LazyFeature, ...]] = ( 

60 LazyFeature( 

61 name="guardrails", 

62 module_path="litellm.proxy.guardrails.guardrail_endpoints", 

63 path_prefixes=( 

64 "/guardrails", 

65 "/v2/guardrails", 

66 "/apply_guardrail", 

67 "/policies/usage", 

68 ), 

69 ), 

70 LazyFeature( 

71 name="policies", 

72 module_path="litellm.proxy.management_endpoints.policy_endpoints", 

73 # Trailing slash to avoid matching /policies/... (policy_engine). 

74 path_prefixes=("/policy/", "/utils/test_policies_and_guardrails"), 

75 ), 

76 LazyFeature( 

77 name="policy_engine", 

78 module_path="litellm.proxy.policy_engine.policy_endpoints", 

79 path_prefixes=("/policies",), 

80 ), 

81 LazyFeature( 

82 name="policy_resolve", 

83 module_path="litellm.proxy.policy_engine.policy_resolve_endpoints", 

84 path_prefixes=("/policies/resolve", "/policies/attachments/estimate-impact"), 

85 ), 

86 LazyFeature( 

87 name="agents", 

88 module_path="litellm.proxy.agent_endpoints.endpoints", 

89 path_prefixes=("/v1/agents", "/agents", "/agent/"), 

90 ), 

91 LazyFeature( 

92 name="gemini_agents", 

93 module_path="litellm.proxy.google_endpoints.agents_endpoints", 

94 path_prefixes=("/v1beta/agents",), 

95 ), 

96 LazyFeature( 

97 name="a2a", 

98 module_path="litellm.proxy.agent_endpoints.a2a_endpoints", 

99 # ``/v1/a2a/{agent_id}/message/send`` is caught via the suffix so the 

100 # ``/v1/a2a`` prefix doesn't subsume the discover prefix below. 

101 path_prefixes=("/a2a",), 

102 path_suffixes=("/message/send",), 

103 ), 

104 LazyFeature( 

105 name="a2a_registration", 

106 module_path="litellm.proxy.a2a.endpoints", 

107 path_prefixes=("/v1/a2a/discover",), 

108 ), 

109 LazyFeature( 

110 name="vector_stores", 

111 module_path="litellm.proxy.vector_store_endpoints.endpoints", 

112 path_prefixes=("/v1/vector_stores", "/vector_stores", "/v1/indexes"), 

113 ), 

114 LazyFeature( 

115 name="vector_store_management", 

116 module_path="litellm.proxy.vector_store_endpoints.management_endpoints", 

117 # Trailing slash to avoid matching /vector_stores/... (vector_stores). 

118 path_prefixes=("/vector_store/", "/v1/vector_store/"), 

119 ), 

120 LazyFeature( 

121 name="vector_store_files", 

122 # Routes appear under both /v1/vector_stores/{id}/files and the 

123 # un-versioned form, so both prefixes must trigger the load. 

124 module_path="litellm.proxy.vector_store_files_endpoints.endpoints", 

125 path_prefixes=("/v1/vector_stores", "/vector_stores"), 

126 ), 

127 LazyFeature( 

128 name="tools", 

129 module_path="litellm.proxy.management_endpoints.tool_management_endpoints", 

130 path_prefixes=("/v1/tool", "/tool"), 

131 ), 

132 LazyFeature( 

133 name="search_tools", 

134 module_path="litellm.proxy.search_endpoints.search_tool_management", 

135 path_prefixes=("/search_tools",), 

136 ), 

137 # mcp_management owns most /v1/mcp/* admin routes; mcp_app is the mounted 

138 # streaming sub-app at /mcp. 

139 LazyFeature( 

140 name="mcp_management", 

141 module_path="litellm.proxy.management_endpoints.mcp_management_endpoints", 

142 path_prefixes=("/v1/mcp/",), 

143 ), 

144 LazyFeature( 

145 # Also serves /.well-known/oauth-* (OAuth metadata discovery). 

146 # No /mcp/oauth prefix here: the mounted /mcp sub-app would 

147 # shadow it, and there are no actual routes there anyway. 

148 name="mcp_byok_oauth", 

149 module_path="litellm.proxy._experimental.mcp_server.byok_oauth_endpoints", 

150 path_prefixes=("/v1/mcp/oauth", "/.well-known/oauth-"), 

151 ), 

152 LazyFeature( 

153 # Serves OAuth dance endpoints (/authorize, /token, /callback, 

154 # /register) plus several /.well-known/ discovery URLs at the proxy 

155 # root — needed for MCP-over-OAuth flows even before /mcp is hit. 

156 name="mcp_discoverable", 

157 module_path="litellm.proxy._experimental.mcp_server.discoverable_endpoints", 

158 path_prefixes=( 

159 "/.well-known/oauth-", 

160 "/.well-known/openid-configuration", 

161 "/.well-known/jwks.json", 

162 "/.well-known/litellm-cli-auth", 

163 "/authorize", 

164 "/token", 

165 "/callback", 

166 "/register", 

167 "/revoke", 

168 "/introspect", 

169 ), 

170 # Catches the /{mcp_server_name}/authorize|token|register variants. 

171 path_suffixes=("/authorize", "/token", "/register"), 

172 ), 

173 LazyFeature( 

174 name="mcp_rest", 

175 module_path="litellm.proxy._experimental.mcp_server.rest_endpoints", 

176 path_prefixes=("/mcp-rest",), 

177 ), 

178 LazyFeature( 

179 # Hardcoded /mcp matches BASE_MCP_ROUTE; importing the constant 

180 # here would defeat lazy loading. 

181 name="mcp_app", 

182 module_path="litellm.proxy._experimental.mcp_server.server", 

183 path_prefixes=("/mcp",), 

184 register_fn=_mount_app("/mcp", attr_name="app"), 

185 persistent_swagger_stub=True, 

186 ), 

187 LazyFeature( 

188 name="config_overrides", 

189 module_path="litellm.proxy.management_endpoints.config_override_endpoints", 

190 path_prefixes=("/config_overrides",), 

191 ), 

192 LazyFeature( 

193 name="llm_passthrough", 

194 module_path="litellm.proxy.pass_through_endpoints.llm_passthrough_endpoints", 

195 path_prefixes=( 

196 "/anthropic/", 

197 "/assemblyai/", 

198 "/azure/", 

199 "/azure_ai/", 

200 "/azure_speech/", 

201 "/bedrock/", 

202 "/cohere/", 

203 "/comprehendmedical", 

204 "/cursor/", 

205 "/deepgram/", 

206 "/eu.assemblyai/", 

207 "/fal_ai/", 

208 "/gemini/", 

209 "/gigachat/", 

210 "/milvus/", 

211 "/mistral/", 

212 "/nvidia_nim/", 

213 "/openai/", 

214 "/openai_passthrough/", 

215 "/tinyfish/", 

216 "/transcribe", 

217 "/typesafe/", 

218 "/openrouter/", 

219 "/vertex-ai/", 

220 "/vertex_ai/", 

221 "/vllm/", 

222 "/watsonx/", 

223 ), 

224 ), 

225 LazyFeature( 

226 name="realtime", 

227 module_path="litellm.proxy.realtime_endpoints.endpoints", 

228 path_prefixes=("/openai/v1/realtime", "/v1/realtime", "/realtime"), 

229 ), 

230 LazyFeature( 

231 name="anthropic_passthrough", 

232 module_path="litellm.proxy.anthropic_endpoints.endpoints", 

233 path_prefixes=("/v1/messages", "/anthropic", "/api/event_logging"), 

234 ), 

235 LazyFeature( 

236 name="anthropic_skills", 

237 module_path="litellm.proxy.anthropic_endpoints.skills_endpoints", 

238 path_prefixes=("/v1/skills", "/skills"), 

239 ), 

240 LazyFeature( 

241 name="claude_code_gateway", 

242 module_path="litellm.proxy.anthropic_endpoints.gateway_endpoints", 

243 path_prefixes=("/claude_code_gateway",), 

244 ), 

245 LazyFeature( 

246 name="langfuse_passthrough", 

247 module_path="litellm.proxy.vertex_ai_endpoints.langfuse_endpoints", 

248 path_prefixes=("/langfuse",), 

249 ), 

250 LazyFeature( 

251 name="evals", 

252 module_path="litellm.proxy.openai_evals_endpoints.endpoints", 

253 path_prefixes=("/v1/evals", "/evals"), 

254 ), 

255 LazyFeature( 

256 name="decisions", 

257 module_path="litellm.proxy.decisions_endpoints.endpoints", 

258 path_prefixes=("/v1/decisions", "/decisions", "/v1/systemone", "/systemone"), 

259 ), 

260 LazyFeature( 

261 name="claude_code_marketplace", 

262 module_path="litellm.proxy.anthropic_endpoints.claude_code_endpoints", 

263 path_prefixes=("/claude-code",), 

264 register_fn=_include_router("claude_code_marketplace_router"), 

265 ), 

266 LazyFeature( 

267 name="scim", 

268 module_path="litellm.proxy.management_endpoints.scim.scim_v2", 

269 path_prefixes=("/scim",), 

270 register_fn=_include_router("scim_router"), 

271 ), 

272 LazyFeature( 

273 name="cloudzero", 

274 module_path="litellm.proxy.spend_tracking.cloudzero_endpoints", 

275 path_prefixes=("/cloudzero",), 

276 ), 

277 LazyFeature( 

278 name="vantage", 

279 module_path="litellm.proxy.spend_tracking.vantage_endpoints", 

280 path_prefixes=("/vantage",), 

281 ), 

282 LazyFeature( 

283 name="usage_ai", 

284 module_path="litellm.proxy.management_endpoints.usage_endpoints", 

285 path_prefixes=("/usage/ai",), 

286 ), 

287 LazyFeature( 

288 name="prompts", 

289 module_path="litellm.proxy.prompts.prompt_endpoints", 

290 path_prefixes=("/prompts", "/utils/dotprompt_json_converter"), 

291 ), 

292 LazyFeature( 

293 name="jwt_mappings", 

294 module_path="litellm.proxy.management_endpoints.jwt_key_mapping_endpoints", 

295 path_prefixes=("/jwt/key/mapping",), 

296 ), 

297 LazyFeature( 

298 name="compliance", 

299 module_path="litellm.proxy.management_endpoints.compliance_endpoints", 

300 path_prefixes=("/compliance",), 

301 ), 

302 LazyFeature( 

303 name="access_groups", 

304 module_path="litellm.proxy.management_endpoints.access_group_endpoints", 

305 path_prefixes=("/access_group", "/v1/access_group", "/v1/unified_access_group"), 

306 ), 

307) 

308 

309 

310class LazyFeatureMiddleware: 

311 """ASGI middleware that imports + registers a feature router on first 

312 matching request. Idempotent; once loaded, subsequent requests skip.""" 

313 

314 def __init__( 

315 self, 

316 app: ASGIApp, 

317 fastapi_app: "FastAPI", 

318 features: tuple[LazyFeature, ...] = LAZY_FEATURES, 

319 ): 

320 self.app = app 

321 self._fastapi_app = fastapi_app 

322 self._features = features 

323 # SERVER_ROOT_PATH is a process-startup env var, cache the normalized 

324 # form once instead of recomputing per request. Lazy import to avoid 

325 # pulling proxy.utils into this module's import graph at startup 

326 # (proxy_server imports both). 

327 from litellm.proxy.utils import get_server_root_path 

328 

329 self._root_path = get_server_root_path().rstrip("/") 

330 # Loaded set / per-feature locks live on app.state so the warm endpoint 

331 # and the middleware share them — preventing duplicate registrations 

332 # when both paths fire for the same feature. 

333 if not hasattr(fastapi_app.state, "lazy_loaded"): 333 ↛ exitline 333 didn't return from function '__init__' because the condition on line 333 was always true

334 fastapi_app.state.lazy_loaded = set() 

335 fastapi_app.state.lazy_locks = {} 

336 

337 @property 

338 def _loaded(self) -> set: 

339 return self._fastapi_app.state.lazy_loaded 

340 

341 async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None: 

342 # Short-circuit once every feature has loaded. 

343 if scope["type"] in ("http", "websocket") and len(self._loaded) < len(self._features): 

344 path = scope.get("path", "") 

345 # Strip the request's root_path so prefix matching works under a 

346 # server root path. Without this, requests like /api/v1/policies/... 

347 # never match the registered prefixes (/policies/...) and lazy 

348 # features stay unloaded — every endpoint under them returns 404. 

349 # scope["root_path"] wins over the cached env scalar: FastAPI 

350 # stamps SERVER_ROOT_PATH there, and PerRequestRootPathMiddleware 

351 # resolves SERVER_ROOT_PATHS prefixes there per request. The 

352 # `+ "/"` boundary prevents false-positive matches (e.g. /apiv2 

353 # against root /api); a pre-stripped path is left alone. 

354 root_path: Final = str(scope.get("root_path", "")).rstrip("/") or self._root_path 

355 if root_path and path.startswith(root_path + "/"): 355 ↛ 356line 355 didn't jump to line 356 because the condition on line 355 was never true

356 path = path[len(root_path) :] # rebind-ok: local strip after the boundary check above 

357 for feat in self._features: 

358 if feat.module_path in self._loaded or not feat.matches(path): 

359 continue 

360 if _eager_route_wins(self._fastapi_app, feat, scope): 

361 continue 

362 await _force_load(self._fastapi_app, feat, self._features) 

363 await self.app(scope, receive, send) 

364 

365 

366def _lazy_slots(app: "FastAPI") -> Mapping[str, BaseRoute | None]: 

367 return app.state.lazy_slots if hasattr(app.state, "lazy_slots") else MappingProxyType({}) 

368 

369 

370def _lazy_routes(app: "FastAPI") -> Mapping[str, tuple[BaseRoute, ...]]: 

371 return app.state.lazy_routes if hasattr(app.state, "lazy_routes") else MappingProxyType({}) 

372 

373 

374def reserve_lazy_slot(app: "FastAPI", name: str, features: tuple[LazyFeature, ...] = LAZY_FEATURES) -> None: 

375 """Record the route the feature's router used to be included after, so its routes 

376 are spliced back in there once it loads and keep the same precedence. Anchoring on 

377 the route rather than its index survives later reordering of the table.""" 

378 feat: Final = next(f for f in features if f.name == name) 

379 anchor: Final = app.router.routes[-1] if app.router.routes else None 

380 app.state.lazy_slots = MappingProxyType({**_lazy_slots(app), feat.module_path: anchor}) 

381 

382 

383def _slot_index(routes: Sequence[BaseRoute], anchor: BaseRoute | None) -> int: 

384 if anchor is None: 384 ↛ 385line 384 didn't jump to line 385 because the condition on line 384 was never true

385 return 0 

386 return next((i + 1 for i, route in enumerate(routes) if route is anchor), len(routes)) 

387 

388 

389def _eager_route_wins(app: "FastAPI", feat: LazyFeature, scope: Scope) -> bool: 

390 """Routes ahead of a feature's reserved slot beat its routes in Starlette's scan, 

391 so a request one of them fully matches never needs the feature loaded.""" 

392 slots: Final = _lazy_slots(app) 

393 if feat.module_path not in slots: 

394 return False 

395 ahead: Final = app.router.routes[: _slot_index(app.router.routes, slots[feat.module_path])] 

396 return any(route.matches(scope)[0] is Match.FULL for route in ahead) 

397 

398 

399def _in_registry_order( 

400 routes: Sequence[BaseRoute], 

401 lazy_routes: Mapping[str, tuple[BaseRoute, ...]], 

402 features: tuple[LazyFeature, ...], 

403 slots: Mapping[str, BaseRoute | None], 

404) -> tuple[BaseRoute, ...]: 

405 """Lazy routers land in registry order, not first-request order, so overlapping 

406 paths (/openai/{endpoint:path} vs /openai/v1/realtime/calls) resolve the same 

407 way no matter which feature a deployment happens to hit first. Features with a 

408 reserved slot go back where they were eagerly included; the rest follow every 

409 eager route.""" 

410 rank: Final = MappingProxyType({f.module_path: i for i, f in enumerate(features)}) 

411 modules: Final = tuple(sorted(lazy_routes, key=lambda m: rank.get(m, len(rank)))) 

412 lazy_ids: Final = frozenset(id(route) for module_path in modules for route in lazy_routes[module_path]) 

413 eager: Final = tuple(route for route in routes if id(route) not in lazy_ids) 

414 

415 def slot_of(module_path: str) -> int: 

416 return _slot_index(eager, slots[module_path]) if module_path in slots else len(eager) 

417 

418 return tuple( 

419 route 

420 for index in range(len(eager) + 1) 

421 for route in ( 

422 *(r for module_path in modules if slot_of(module_path) == index for r in lazy_routes[module_path]), 

423 *eager[index : index + 1], 

424 ) 

425 ) 

426 

427 

428async def _force_load(app: "FastAPI", feat: LazyFeature, features: tuple[LazyFeature, ...] = LAZY_FEATURES) -> bool: 

429 """Import + register a lazy feature exactly once per (app, module). 

430 Shared by the middleware and the /lazy/warm endpoint.""" 

431 if not hasattr(app.state, "lazy_loaded"): 431 ↛ 432line 431 didn't jump to line 432 because the condition on line 431 was never true

432 app.state.lazy_loaded = set() 

433 app.state.lazy_locks = {} 

434 lock: Final = app.state.lazy_locks.setdefault(feat.module_path, asyncio.Lock()) 

435 async with lock: 

436 if feat.module_path in app.state.lazy_loaded: 436 ↛ 437line 436 didn't jump to line 437 because the condition on line 436 was never true

437 return False 

438 try: 

439 # Import on a thread (heavy modules take 1-3 s). register_fn 

440 # mutates app.router.routes, so it stays on the loop thread. 

441 loop: Final = asyncio.get_running_loop() 

442 module: Final = await loop.run_in_executor(None, importlib.import_module, feat.module_path) 

443 before: Final = len(app.router.routes) 

444 feat.register_fn(app, module) 

445 previous: Final[Mapping[str, tuple[BaseRoute, ...]]] = ( 

446 app.state.lazy_routes if hasattr(app.state, "lazy_routes") else MappingProxyType({}) 

447 ) 

448 lazy_routes: Final[Mapping[str, tuple[BaseRoute, ...]]] = MappingProxyType( 

449 {**previous, feat.module_path: tuple(app.router.routes[before:])} 

450 ) 

451 app.state.lazy_routes = lazy_routes # rebind-ok: the app owns the record of which routes each feature added 

452 app.router.routes[:] = hot_routes_first( # rebind-ok: the app owns its route table 

453 _in_registry_order(app.router.routes, lazy_routes, features, _lazy_slots(app)) 

454 ) 

455 app.state.lazy_loaded.add(feat.module_path) 

456 app.openapi_schema = None 

457 verbose_proxy_logger.info( 

458 "Lazy-loaded optional feature %r (module: %s)", 

459 feat.name, 

460 feat.module_path, 

461 ) 

462 return True 

463 except Exception as exc: 

464 # Mark loaded anyway so we don't retry on every request. 

465 app.state.lazy_loaded.add(feat.module_path) 

466 verbose_proxy_logger.warning( 

467 "Failed to lazy-load optional feature %r (module: %s): %s. " 

468 "This feature's endpoints will return 404 until restart.", 

469 feat.name, 

470 feat.module_path, 

471 exc, 

472 ) 

473 return False 

474 

475 

476def attach_lazy_features(app: "FastAPI") -> None: 

477 app.include_router(_make_warmup_router(app)) 

478 app.add_middleware(LazyFeatureMiddleware, fastapi_app=app) 

479 

480 

481def _make_warmup_router(app: "FastAPI") -> "APIRouter": 

482 """POST /lazy/warm/{name}: load a feature and return its partial openapi 

483 so the Swagger plugin can merge in-place without a full /openapi.json refetch. 

484 Requires auth — anyone who can hit the proxy can already trigger the same 

485 imports by sending a real request to a feature's prefix, but gating this 

486 debug endpoint avoids unauthenticated callers forcing the import chain.""" 

487 from fastapi import APIRouter, Depends, HTTPException 

488 from fastapi.openapi.utils import get_openapi 

489 

490 from litellm.proxy.auth.user_api_key_auth import user_api_key_auth 

491 

492 router: Final = APIRouter() 

493 

494 @router.post( 

495 "/lazy/warm/{name}", 

496 include_in_schema=False, 

497 dependencies=[Depends(user_api_key_auth)], 

498 ) 

499 async def warm(name: str): 

500 feat: Final = next((f for f in LAZY_FEATURES if f.name == name), None) 

501 if feat is None: 

502 raise HTTPException(404, f"unknown lazy feature: {name}") 

503 if feat.persistent_swagger_stub: 

504 return {"stub_path": None, "paths": {}, "components": {"schemas": {}}} 

505 

506 await _force_load(app, feat) 

507 

508 feat_routes: Final = [r for r in app.routes if feat.matches(getattr(r, "path", ""))] 

509 full: Final = get_openapi(title=app.title, version=app.version, routes=feat_routes) 

510 # Force all operations under one tag so they group under a single Swagger 

511 # section — many lazy modules tag routes inconsistently. 

512 for path_ops in full.get("paths", {}).values(): 

513 for op in path_ops.values(): 

514 if isinstance(op, dict): 

515 op["tags"] = [feat.name] 

516 return { 

517 "stub_path": feat.path_prefixes[0], 

518 "paths": full.get("paths", {}), 

519 "components": {"schemas": full.get("components", {}).get("schemas", {})}, 

520 } 

521 

522 return router 

523 

524 

525def lazy_owned_routes(app: "FastAPI") -> frozenset[int]: 

526 """ids of the routes lazy features have registered on this app. A route added later at 

527 one of their paths (a config pass-through at /v1/decisions) goes ahead of them, the 

528 precedence lazy mode gives it when the feature has not loaded by the time the config is read.""" 

529 return frozenset(id(route) for route in chain.from_iterable(_lazy_routes(app).values())) 

530 

531 

532def loaded_lazy_modules(app: "FastAPI") -> frozenset[str]: 

533 """The set of lazy feature modules whose routers are actually registered 

534 on this app (tracked by _force_load), empty before the middleware ever ran. 

535 sys.modules is the wrong signal: boot code imports several feature modules 

536 (mcp_management, cloudzero, vantage, config_overrides) without mounting 

537 their routers, and their stubs must still be injected.""" 

538 loaded: Final = getattr(app.state, "lazy_loaded", None) 

539 if not isinstance(loaded, set): 539 ↛ 540line 539 didn't jump to line 540 because the condition on line 539 was never true

540 return frozenset() 

541 return frozenset(m for m in loaded if isinstance(m, str)) 

542 

543 

544def inject_lazy_stubs( 

545 schema: dict, 

546 loaded_modules: AbstractSet[str], 

547 features: tuple[LazyFeature, ...] = LAZY_FEATURES, 

548) -> dict: 

549 """Inject openapi entries for features not in loaded_modules. Uses the 

550 snapshot file when available (full route info), otherwise falls back to a 

551 single placeholder per feature. Any failure logs and returns the schema 

552 unchanged so /openapi.json never 500s on a cosmetic injection bug.""" 

553 try: 

554 from litellm.proxy._lazy_openapi_snapshot import load_snapshot 

555 

556 snapshot: Final = load_snapshot() 

557 paths: Final = schema.setdefault("paths", {}) 

558 schemas: Final = schema.setdefault("components", {}).setdefault("schemas", {}) 

559 

560 for feat in features: 

561 if feat.module_path in loaded_modules and not feat.persistent_swagger_stub: 561 ↛ 562line 561 didn't jump to line 562 because the condition on line 561 was never true

562 continue 

563 

564 fragment = (snapshot or {}).get(feat.name) 

565 if fragment: 565 ↛ 572line 565 didn't jump to line 572 because the condition on line 565 was always true

566 for p, ops in fragment.get("paths", {}).items(): 

567 paths.setdefault(p, ops) 

568 for name, sch in fragment.get("components", {}).get("schemas", {}).items(): 

569 schemas.setdefault(name, sch) 

570 continue 

571 

572 prefix = feat.path_prefixes[0] 

573 if prefix in paths: 

574 continue 

575 paths[prefix] = { 

576 "get": { 

577 "tags": [feat.name], 

578 "summary": feat.name, 

579 "responses": {"200": {"description": "OK"}}, 

580 } 

581 } 

582 except Exception as exc: 

583 verbose_proxy_logger.warning("inject_lazy_stubs failed: %s", exc) 

584 return schema 

585 

586 

587def lazy_tag_to_prefix() -> dict[str, str]: 

588 """feature.name -> first prefix, used by the Swagger warmup JS plugin. 

589 Returns empty when the snapshot is loaded — the plugin is unnecessary 

590 because /openapi.json already has full route info.""" 

591 from litellm.proxy._lazy_openapi_snapshot import load_snapshot 

592 

593 if load_snapshot(): 593 ↛ 595line 593 didn't jump to line 595 because the condition on line 593 was always true

594 return {} 

595 return {feat.name: feat.path_prefixes[0] for feat in LAZY_FEATURES if not feat.persistent_swagger_stub}