Coverage for .venv/lib/python3.13/site-packages/litellm/proxy/container_endpoints/endpoints.py: 86%

77 statements  

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

1#### Container Endpoints ##### 

2 

3from typing import Final 

4 

5from fastapi import APIRouter, Depends, HTTPException, Request, Response 

6from fastapi.responses import ORJSONResponse 

7 

8from litellm._logging import verbose_proxy_logger 

9from litellm.proxy._types import * 

10from litellm.proxy.auth.user_api_key_auth import UserAPIKeyAuth, user_api_key_auth 

11from litellm.proxy.common_request_processing import ProxyBaseLLMRequestProcessing 

12from litellm.proxy.common_utils.http_parsing_utils import _read_request_body 

13from litellm.proxy.common_utils.openai_endpoint_utils import ( 

14 get_custom_llm_provider_from_request_body, 

15 get_custom_llm_provider_from_request_headers, 

16 get_custom_llm_provider_from_request_query, 

17) 

18from litellm.proxy.common_utils.resource_ownership import is_proxy_admin 

19from litellm.proxy.container_endpoints.ownership import ( 

20 assert_user_can_access_container, 

21 get_container_forwarding_params, 

22 list_owned_containers, 

23 record_container_owner, 

24) 

25 

26router: Final = APIRouter() 

27 

28 

29@router.post( 

30 "/v1/containers", 

31 dependencies=[Depends(user_api_key_auth)], 

32 response_class=ORJSONResponse, 

33 tags=["containers"], 

34) 

35@router.post( 

36 "/containers", 

37 dependencies=[Depends(user_api_key_auth)], 

38 response_class=ORJSONResponse, 

39 tags=["containers"], 

40) 

41async def create_container( 

42 request: Request, 

43 fastapi_response: Response, 

44 user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth), 

45): 

46 """ 

47 Container creation endpoint for creating new containers. 

48  

49 Follows the OpenAI Containers API spec: 

50 https://platform.openai.com/docs/api-reference/containers 

51  

52 Example: 

53 ```bash 

54 curl -X POST "http://localhost:4000/v1/containers" \ 

55 -H "Authorization: Bearer sk-1234" \ 

56 -H "Content-Type: application/json" \ 

57 -d '{ 

58 "name": "My Container", 

59 "expires_after": { 

60 "anchor": "last_active_at", 

61 "minutes": 20 

62 } 

63 }' 

64 ``` 

65  

66 Or specify provider via header: 

67 ```bash 

68 curl -X POST "http://localhost:4000/v1/containers" \ 

69 -H "Authorization: Bearer sk-1234" \ 

70 -H "custom-llm-provider: azure" \ 

71 -H "Content-Type: application/json" \ 

72 -d '{ 

73 "name": "My Container" 

74 }' 

75 ``` 

76 """ 

77 from litellm.proxy.proxy_server import ( 

78 general_settings, 

79 llm_router, 

80 proxy_config, 

81 proxy_logging_obj, 

82 select_data_generator, 

83 user_api_base, 

84 user_max_tokens, 

85 user_model, 

86 user_request_timeout, 

87 user_temperature, 

88 version, 

89 ) 

90 

91 # Read request body 

92 data: Final = await _read_request_body(request=request) 

93 

94 # Extract custom_llm_provider using priority chain 

95 # Priority: headers > query params > request body > default 

96 custom_llm_provider: Final = ( 

97 get_custom_llm_provider_from_request_headers(request=request) 

98 or get_custom_llm_provider_from_request_query(request=request) 

99 or await get_custom_llm_provider_from_request_body(request=request) 

100 or "openai" 

101 ) 

102 

103 # Add custom_llm_provider to data 

104 data["custom_llm_provider"] = custom_llm_provider 

105 

106 # Process request using ProxyBaseLLMRequestProcessing 

107 processor: Final = ProxyBaseLLMRequestProcessing(data=data) 

108 try: 

109 response: Final[object] = await processor.base_process_llm_request( 

110 request=request, 

111 fastapi_response=fastapi_response, 

112 user_api_key_dict=user_api_key_dict, 

113 route_type="acreate_container", 

114 proxy_logging_obj=proxy_logging_obj, 

115 llm_router=llm_router, 

116 general_settings=general_settings, 

117 proxy_config=proxy_config, 

118 select_data_generator=select_data_generator, 

119 model=None, 

120 user_model=user_model, 

121 user_temperature=user_temperature, 

122 user_request_timeout=user_request_timeout, 

123 user_max_tokens=user_max_tokens, 

124 user_api_base=user_api_base, 

125 version=version, 

126 ) 

127 except Exception as e: 

128 raise await processor._handle_llm_api_exception( 

129 e=e, 

130 user_api_key_dict=user_api_key_dict, 

131 proxy_logging_obj=proxy_logging_obj, 

132 version=version, 

133 ) 

134 

135 # Ownership recording sits between the upstream create and the response 

136 # to the caller. The recorder swallows DB errors internally (falling 

137 # back to in-memory tracking) and only surfaces HTTPException on auth 

138 # conflicts; we let those propagate verbatim so the client sees the 

139 # real status code rather than a generic LLM error wrapper. 

140 try: 

141 return await record_container_owner( 

142 response=response, 

143 user_api_key_dict=user_api_key_dict, 

144 custom_llm_provider=custom_llm_provider, 

145 ) 

146 except HTTPException: 

147 raise 

148 except Exception as e: 

149 # Unexpected (non-HTTPException) failure after upstream create. 

150 # The container exists upstream but is now untracked — log loudly 

151 # so an operator can reconcile, and return the response so the 

152 # caller does not get charged for a resource they cannot use. 

153 verbose_proxy_logger.exception( 

154 "Container ownership recording failed after upstream create; " 

155 "returning response with untracked ownership: %s", 

156 e, 

157 ) 

158 return response 

159 

160 

161@router.get( 

162 "/v1/containers", 

163 dependencies=[Depends(user_api_key_auth)], 

164 response_class=ORJSONResponse, 

165 tags=["containers"], 

166) 

167@router.get( 

168 "/containers", 

169 dependencies=[Depends(user_api_key_auth)], 

170 response_class=ORJSONResponse, 

171 tags=["containers"], 

172) 

173async def list_containers( 

174 request: Request, 

175 fastapi_response: Response, 

176 user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth), 

177 after: str | None = None, 

178 limit: int | None = None, 

179 order: str | None = None, 

180): 

181 """ 

182 Container list endpoint for retrieving a list of containers. 

183  

184 Follows the OpenAI Containers API spec: 

185 https://platform.openai.com/docs/api-reference/containers 

186  

187 Example: 

188 ```bash 

189 curl -X GET "http://localhost:4000/v1/containers?limit=20&order=desc" \ 

190 -H "Authorization: Bearer sk-1234" 

191 ``` 

192  

193 Or specify provider via header or query param: 

194 ```bash 

195 curl -X GET "http://localhost:4000/v1/containers?custom_llm_provider=azure" \ 

196 -H "Authorization: Bearer sk-1234" 

197 ``` 

198 """ 

199 from litellm.proxy.proxy_server import ( 

200 general_settings, 

201 llm_router, 

202 proxy_config, 

203 proxy_logging_obj, 

204 select_data_generator, 

205 user_api_base, 

206 user_max_tokens, 

207 user_model, 

208 user_request_timeout, 

209 user_temperature, 

210 version, 

211 ) 

212 

213 query_params: Final = dict(request.query_params) 

214 custom_llm_provider: Final = ( 

215 get_custom_llm_provider_from_request_headers(request=request) 

216 or get_custom_llm_provider_from_request_query(request=request) 

217 or "openai" 

218 ) 

219 data: Final[dict[str, object]] = { 

220 "query_params": query_params, 

221 "model": query_params.get("model"), 

222 "order": order, 

223 "custom_llm_provider": custom_llm_provider, 

224 } 

225 

226 async def fetch_page(page_after: str | None, page_limit: int | None) -> object: 

227 processor: Final = ProxyBaseLLMRequestProcessing(data={**data, "after": page_after, "limit": page_limit}) 

228 try: 

229 return await processor.base_process_llm_request( 

230 request=request, 

231 fastapi_response=fastapi_response, 

232 user_api_key_dict=user_api_key_dict, 

233 route_type="alist_containers", 

234 proxy_logging_obj=proxy_logging_obj, 

235 llm_router=llm_router, 

236 general_settings=general_settings, 

237 proxy_config=proxy_config, 

238 select_data_generator=select_data_generator, 

239 model=None, 

240 user_model=user_model, 

241 user_temperature=user_temperature, 

242 user_request_timeout=user_request_timeout, 

243 user_max_tokens=user_max_tokens, 

244 user_api_base=user_api_base, 

245 version=version, 

246 ) 

247 except Exception as e: 

248 raise await processor._handle_llm_api_exception( 

249 e=e, 

250 user_api_key_dict=user_api_key_dict, 

251 proxy_logging_obj=proxy_logging_obj, 

252 version=version, 

253 ) 

254 

255 if is_proxy_admin(user_api_key_dict): 255 ↛ 257line 255 didn't jump to line 257 because the condition on line 255 was always true

256 return await fetch_page(after, limit) 

257 return await list_owned_containers( 

258 fetch_page=fetch_page, 

259 after=after, 

260 limit=limit, 

261 user_api_key_dict=user_api_key_dict, 

262 custom_llm_provider=custom_llm_provider, 

263 ) 

264 

265 

266@router.get( 

267 "/v1/containers/{container_id}", 

268 dependencies=[Depends(user_api_key_auth)], 

269 response_class=ORJSONResponse, 

270 tags=["containers"], 

271) 

272@router.get( 

273 "/containers/{container_id}", 

274 dependencies=[Depends(user_api_key_auth)], 

275 response_class=ORJSONResponse, 

276 tags=["containers"], 

277) 

278async def retrieve_container( 

279 request: Request, 

280 container_id: str, 

281 fastapi_response: Response, 

282 user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth), 

283): 

284 """ 

285 Container retrieve endpoint for getting details of a specific container. 

286  

287 Follows the OpenAI Containers API spec: 

288 https://platform.openai.com/docs/api-reference/containers 

289  

290 Example: 

291 ```bash 

292 curl -X GET "http://localhost:4000/v1/containers/cntr_123" \ 

293 -H "Authorization: Bearer sk-1234" 

294 ``` 

295  

296 Or specify provider via header: 

297 ```bash 

298 curl -X GET "http://localhost:4000/v1/containers/cntr_123" \ 

299 -H "Authorization: Bearer sk-1234" \ 

300 -H "custom-llm-provider: azure" 

301 ``` 

302 """ 

303 from litellm.proxy.proxy_server import ( 

304 general_settings, 

305 llm_router, 

306 proxy_config, 

307 proxy_logging_obj, 

308 select_data_generator, 

309 user_api_base, 

310 user_max_tokens, 

311 user_model, 

312 user_request_timeout, 

313 user_temperature, 

314 version, 

315 ) 

316 

317 # Include container_id in request data 

318 data: Final[dict[str, object]] = {"container_id": container_id} 

319 

320 # Extract custom_llm_provider using priority chain 

321 custom_llm_provider = ( 

322 get_custom_llm_provider_from_request_headers(request=request) 

323 or get_custom_llm_provider_from_request_query(request=request) 

324 or "openai" 

325 ) 

326 

327 # Add custom_llm_provider to data 

328 original_container_id, custom_llm_provider = await assert_user_can_access_container( 

329 container_id=container_id, 

330 user_api_key_dict=user_api_key_dict, 

331 custom_llm_provider=custom_llm_provider, 

332 ) 

333 data.update( 

334 await get_container_forwarding_params( 

335 container_id, 

336 original_container_id, 

337 custom_llm_provider, 

338 ) 

339 ) 

340 

341 # Process request using ProxyBaseLLMRequestProcessing 

342 processor: Final = ProxyBaseLLMRequestProcessing(data=data) 

343 try: 

344 container: Final[object] = await processor.base_process_llm_request( 

345 request=request, 

346 fastapi_response=fastapi_response, 

347 user_api_key_dict=user_api_key_dict, 

348 route_type="aretrieve_container", 

349 proxy_logging_obj=proxy_logging_obj, 

350 llm_router=llm_router, 

351 general_settings=general_settings, 

352 proxy_config=proxy_config, 

353 select_data_generator=select_data_generator, 

354 model=None, 

355 user_model=user_model, 

356 user_temperature=user_temperature, 

357 user_request_timeout=user_request_timeout, 

358 user_max_tokens=user_max_tokens, 

359 user_api_base=user_api_base, 

360 version=version, 

361 ) 

362 except Exception as e: 

363 raise await processor._handle_llm_api_exception( 

364 e=e, 

365 user_api_key_dict=user_api_key_dict, 

366 proxy_logging_obj=proxy_logging_obj, 

367 version=version, 

368 ) 

369 return container 

370 

371 

372@router.delete( 

373 "/v1/containers/{container_id}", 

374 dependencies=[Depends(user_api_key_auth)], 

375 response_class=ORJSONResponse, 

376 tags=["containers"], 

377) 

378@router.delete( 

379 "/containers/{container_id}", 

380 dependencies=[Depends(user_api_key_auth)], 

381 response_class=ORJSONResponse, 

382 tags=["containers"], 

383) 

384async def delete_container( 

385 request: Request, 

386 container_id: str, 

387 fastapi_response: Response, 

388 user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth), 

389): 

390 """ 

391 Container delete endpoint for deleting a specific container. 

392  

393 Follows the OpenAI Containers API spec: 

394 https://platform.openai.com/docs/api-reference/containers 

395  

396 Example: 

397 ```bash 

398 curl -X DELETE "http://localhost:4000/v1/containers/cntr_123" \ 

399 -H "Authorization: Bearer sk-1234" 

400 ``` 

401  

402 Or specify provider via header: 

403 ```bash 

404 curl -X DELETE "http://localhost:4000/v1/containers/cntr_123" \ 

405 -H "Authorization: Bearer sk-1234" \ 

406 -H "custom-llm-provider: azure" 

407 ``` 

408 """ 

409 from litellm.proxy.proxy_server import ( 

410 general_settings, 

411 llm_router, 

412 proxy_config, 

413 proxy_logging_obj, 

414 select_data_generator, 

415 user_api_base, 

416 user_max_tokens, 

417 user_model, 

418 user_request_timeout, 

419 user_temperature, 

420 version, 

421 ) 

422 

423 # Include container_id in request data 

424 data: Final[dict[str, object]] = {"container_id": container_id} 

425 

426 # Extract custom_llm_provider using priority chain 

427 custom_llm_provider = ( 

428 get_custom_llm_provider_from_request_headers(request=request) 

429 or get_custom_llm_provider_from_request_query(request=request) 

430 or "openai" 

431 ) 

432 

433 # Add custom_llm_provider to data 

434 original_container_id, custom_llm_provider = await assert_user_can_access_container( 

435 container_id=container_id, 

436 user_api_key_dict=user_api_key_dict, 

437 custom_llm_provider=custom_llm_provider, 

438 ) 

439 data.update( 

440 await get_container_forwarding_params( 

441 container_id, 

442 original_container_id, 

443 custom_llm_provider, 

444 ) 

445 ) 

446 

447 # Process request using ProxyBaseLLMRequestProcessing 

448 processor: Final = ProxyBaseLLMRequestProcessing(data=data) 

449 try: 

450 deleted_container: Final[object] = await processor.base_process_llm_request( 

451 request=request, 

452 fastapi_response=fastapi_response, 

453 user_api_key_dict=user_api_key_dict, 

454 route_type="adelete_container", 

455 proxy_logging_obj=proxy_logging_obj, 

456 llm_router=llm_router, 

457 general_settings=general_settings, 

458 proxy_config=proxy_config, 

459 select_data_generator=select_data_generator, 

460 model=None, 

461 user_model=user_model, 

462 user_temperature=user_temperature, 

463 user_request_timeout=user_request_timeout, 

464 user_max_tokens=user_max_tokens, 

465 user_api_base=user_api_base, 

466 version=version, 

467 ) 

468 except Exception as e: 

469 raise await processor._handle_llm_api_exception( 

470 e=e, 

471 user_api_key_dict=user_api_key_dict, 

472 proxy_logging_obj=proxy_logging_obj, 

473 version=version, 

474 ) 

475 return deleted_container 

476 

477 

478# Register JSON-configured container file endpoints 

479from litellm.proxy.container_endpoints.handler_factory import ( 

480 register_container_file_endpoints, 

481) 

482 

483register_container_file_endpoints(router)