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

142 statements  

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

1from collections.abc import Mapping 

2from types import MappingProxyType 

3from typing import ( 

4 Annotated, 

5 Any, # noqa: TID251 # jsonify_object in proxy/utils.py is annotated with a bare dict 

6 Final, 

7 cast, # noqa: TID251 # jsonify_object in proxy/utils.py is annotated with a bare dict 

8) 

9 

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

11 

12from litellm.integrations.vector_store_integrations.vector_store_pre_call_hook import ( 

13 LiteLLM_ManagedVectorStore, 

14) 

15from litellm.proxy._types import CommonProxyErrors, UserAPIKeyAuth 

16from litellm.proxy.auth.user_api_key_auth import user_api_key_auth 

17from litellm.proxy.common_request_processing import ProxyBaseLLMRequestProcessing 

18from litellm.proxy.utils import jsonify_object 

19from litellm.proxy.vector_store_endpoints.utils import ( 

20 assert_proxy_admin_for_vector_store_index_management, 

21 assert_user_can_access_vector_store, 

22 get_litellm_managed_vector_store, 

23) 

24from litellm.repositories.table_repositories import ManagedVectorStoreIndexRepository 

25from litellm.types.vector_stores import IndexCreateRequest, IndexListResponse 

26from litellm.vector_stores.vector_store_registry import VectorStoreIndexRegistry 

27 

28router: Final = APIRouter() 

29 

30BLOCKED_QUERY_EMBEDDING_SELECTION_PARAMS: Final = frozenset( 

31 { 

32 "embedding_model", 

33 "litellm_embedding_model", 

34 "litellm_embedding_config", 

35 "litellm_credential_name", 

36 } 

37) 

38 

39 

40def reject_caller_embedding_selection_params(payload: Mapping[str, object], source: str) -> None: 

41 blocked: Final = sorted(BLOCKED_QUERY_EMBEDDING_SELECTION_PARAMS & payload.keys()) 

42 if blocked: 42 ↛ 43line 42 didn't jump to line 43 because the condition on line 42 was never true

43 raise HTTPException( 

44 status_code=400, 

45 detail={ 

46 "error": f"'{blocked[0]}' cannot be set in {source}. " 

47 "Embedding configuration comes from the vector store's server-side registration." 

48 }, 

49 ) 

50 

51 

52######################################################## 

53# OpenAI Compatible Endpoints 

54######################################################## 

55 

56 

57def build_request_data_from_managed_vector_store( 

58 vector_store: LiteLLM_ManagedVectorStore, 

59) -> Mapping[str, object]: 

60 top_level: Final = MappingProxyType( 

61 { 

62 key: vector_store.get(key) 

63 for key in ("custom_llm_provider", "litellm_credential_name") 

64 if key in vector_store 

65 } 

66 ) 

67 litellm_params: Final = vector_store.get("litellm_params") or MappingProxyType({}) 

68 return MappingProxyType({**top_level, **litellm_params}) 

69 

70 

71async def _update_request_data_with_litellm_managed_vector_store_registry( 

72 data: dict, 

73 vector_store_id: str, 

74 user_api_key_dict: UserAPIKeyAuth | None = None, 

75) -> dict: 

76 """ 

77 Update the request data with the litellm managed vector store registry. 

78 

79 Args: 

80 data: Request data to update 

81 vector_store_id: ID of the vector store 

82 user_api_key_dict: User API key authentication info for access control 

83 

84 Raises: 

85 HTTPException: If user doesn't have access to the vector store 

86 """ 

87 vector_store_to_run: Final[LiteLLM_ManagedVectorStore | None] = await get_litellm_managed_vector_store( 

88 vector_store_id=vector_store_id 

89 ) 

90 if vector_store_to_run is None: 

91 return data 

92 if user_api_key_dict is not None: 92 ↛ 97line 92 didn't jump to line 97 because the condition on line 92 was always true

93 await assert_user_can_access_vector_store( 

94 vector_store=vector_store_to_run, 

95 user_api_key_dict=user_api_key_dict, 

96 ) 

97 return {**data, **build_request_data_from_managed_vector_store(vector_store_to_run)} 

98 

99 

100@router.post( 

101 "/v1/vector_stores/{vector_store_id:path}/search", 

102 dependencies=[Depends(user_api_key_auth)], 

103) 

104@router.post( 

105 "/vector_stores/{vector_store_id:path}/search", 

106 dependencies=[Depends(user_api_key_auth)], 

107) 

108async def vector_store_search( 

109 request: Request, 

110 vector_store_id: str, 

111 fastapi_response: Response, 

112 user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth), 

113): 

114 """ 

115 Search a vector store. 

116 

117 API Reference: 

118 https://platform.openai.com/docs/api-reference/vector-stores/search 

119 """ 

120 from litellm.proxy.proxy_server import ( 

121 _read_request_body, 

122 general_settings, 

123 llm_router, 

124 proxy_config, 

125 proxy_logging_obj, 

126 select_data_generator, 

127 user_api_base, 

128 user_max_tokens, 

129 user_model, 

130 user_request_timeout, 

131 user_temperature, 

132 version, 

133 ) 

134 

135 data = await _read_request_body(request=request) 

136 reject_caller_embedding_selection_params(payload=data, source="the search request body") 

137 data["vector_store_id"] = vector_store_id 

138 

139 # Check for legacy vector store registry (non-managed vector stores) 

140 data = await _update_request_data_with_litellm_managed_vector_store_registry( 

141 data=data, vector_store_id=vector_store_id, user_api_key_dict=user_api_key_dict 

142 ) 

143 

144 # The managed_vector_stores pre-call hook will handle: 

145 # 1. Decoding managed vector store IDs 

146 # 2. Extracting model and provider resource ID 

147 # 3. Setting up proper routing 

148 # 4. Authentication checks 

149 

150 processor: Final = ProxyBaseLLMRequestProcessing(data=data) 

151 try: 

152 return await processor.base_process_llm_request( 

153 request=request, 

154 fastapi_response=fastapi_response, 

155 user_api_key_dict=user_api_key_dict, 

156 route_type="avector_store_search", 

157 proxy_logging_obj=proxy_logging_obj, 

158 llm_router=llm_router, 

159 general_settings=general_settings, 

160 proxy_config=proxy_config, 

161 select_data_generator=select_data_generator, 

162 model=None, 

163 user_model=user_model, 

164 user_temperature=user_temperature, 

165 user_request_timeout=user_request_timeout, 

166 user_max_tokens=user_max_tokens, 

167 user_api_base=user_api_base, 

168 version=version, 

169 ) 

170 except Exception as e: 

171 raise await processor._handle_llm_api_exception( 

172 e=e, 

173 user_api_key_dict=user_api_key_dict, 

174 proxy_logging_obj=proxy_logging_obj, 

175 version=version, 

176 ) 

177 

178 

179@router.post("/v1/vector_stores", dependencies=[Depends(user_api_key_auth)]) 

180@router.post("/vector_stores", dependencies=[Depends(user_api_key_auth)]) 

181async def vector_store_create( 

182 request: Request, 

183 fastapi_response: Response, 

184 user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth), 

185): 

186 """ 

187 Create a vector store. 

188 

189 API Reference: 

190 https://platform.openai.com/docs/api-reference/vector-stores/create 

191 

192 Supports target_model_names parameter for creating vector stores across multiple models: 

193 ```json 

194 { 

195 "name": "my-vector-store", 

196 "target_model_names": "gpt-4,gemini-2.0" 

197 } 

198 ``` 

199 """ 

200 from litellm.proxy.proxy_server import ( 

201 _read_request_body, 

202 general_settings, 

203 llm_router, 

204 proxy_config, 

205 proxy_logging_obj, 

206 select_data_generator, 

207 user_api_base, 

208 user_max_tokens, 

209 user_model, 

210 user_request_timeout, 

211 user_temperature, 

212 version, 

213 ) 

214 

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

216 

217 # Check for target_model_names parameter 

218 target_model_names: Final = data.pop("target_model_names", None) 

219 

220 if target_model_names: 220 ↛ 222line 220 didn't jump to line 222 because the condition on line 220 was never true

221 # Use managed vector stores for multi-model support 

222 if isinstance(target_model_names, str): 

223 target_model_names_list = [m.strip() for m in target_model_names.split(",")] 

224 elif isinstance(target_model_names, list): 

225 target_model_names_list = target_model_names 

226 else: 

227 raise HTTPException( 

228 status_code=400, 

229 detail="target_model_names must be a comma-separated string or list of model names", 

230 ) 

231 

232 # Get managed vector stores hook 

233 managed_vector_stores: Final[Any] = proxy_logging_obj.get_proxy_hook("managed_vector_stores") 

234 if managed_vector_stores is None: 

235 raise HTTPException( 

236 status_code=500, 

237 detail="Managed vector stores not configured. Please ensure the proxy is initialized with database support.", 

238 ) 

239 

240 if llm_router is None: 

241 raise HTTPException( 

242 status_code=500, 

243 detail="LLM Router not initialized. Ensure models are added to proxy.", 

244 ) 

245 

246 # Create vector store across multiple models 

247 response: Final[object] = await managed_vector_stores.acreate_vector_store( 

248 create_request=data, 

249 llm_router=llm_router, 

250 target_model_names_list=target_model_names_list, 

251 litellm_parent_otel_span=user_api_key_dict.parent_otel_span, 

252 user_api_key_dict=user_api_key_dict, 

253 ) 

254 

255 return response 

256 

257 processor: Final = ProxyBaseLLMRequestProcessing(data=data) 

258 try: 

259 return await processor.base_process_llm_request( 

260 request=request, 

261 fastapi_response=fastapi_response, 

262 user_api_key_dict=user_api_key_dict, 

263 route_type="avector_store_create", 

264 proxy_logging_obj=proxy_logging_obj, 

265 llm_router=llm_router, 

266 general_settings=general_settings, 

267 proxy_config=proxy_config, 

268 select_data_generator=select_data_generator, 

269 model=None, 

270 user_model=user_model, 

271 user_temperature=user_temperature, 

272 user_request_timeout=user_request_timeout, 

273 user_max_tokens=user_max_tokens, 

274 user_api_base=user_api_base, 

275 version=version, 

276 ) 

277 except Exception as e: 

278 raise await processor._handle_llm_api_exception( 

279 e=e, 

280 user_api_key_dict=user_api_key_dict, 

281 proxy_logging_obj=proxy_logging_obj, 

282 version=version, 

283 ) 

284 

285 

286@router.get("/v1/vector_stores/{vector_store_id}", dependencies=[Depends(user_api_key_auth)]) 

287@router.get("/vector_stores/{vector_store_id}", dependencies=[Depends(user_api_key_auth)]) 

288async def vector_store_retrieve( 

289 request: Request, 

290 vector_store_id: str, 

291 fastapi_response: Response, 

292 user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth), 

293): 

294 """ 

295 Retrieve a vector store. 

296 

297 API Reference: 

298 https://platform.openai.com/docs/api-reference/vector-stores/retrieve 

299 """ 

300 from litellm.proxy.proxy_server import ( 

301 general_settings, 

302 llm_router, 

303 proxy_config, 

304 proxy_logging_obj, 

305 select_data_generator, 

306 user_api_base, 

307 user_max_tokens, 

308 user_model, 

309 user_request_timeout, 

310 user_temperature, 

311 version, 

312 ) 

313 

314 data = {"vector_store_id": vector_store_id} 

315 

316 data = await _update_request_data_with_litellm_managed_vector_store_registry( 

317 data=data, vector_store_id=vector_store_id, user_api_key_dict=user_api_key_dict 

318 ) 

319 

320 processor: Final = ProxyBaseLLMRequestProcessing(data=data) 

321 try: 

322 return await processor.base_process_llm_request( 

323 request=request, 

324 fastapi_response=fastapi_response, 

325 user_api_key_dict=user_api_key_dict, 

326 route_type="avector_store_retrieve", 

327 proxy_logging_obj=proxy_logging_obj, 

328 llm_router=llm_router, 

329 general_settings=general_settings, 

330 proxy_config=proxy_config, 

331 select_data_generator=select_data_generator, 

332 model=None, 

333 user_model=user_model, 

334 user_temperature=user_temperature, 

335 user_request_timeout=user_request_timeout, 

336 user_max_tokens=user_max_tokens, 

337 user_api_base=user_api_base, 

338 version=version, 

339 ) 

340 except Exception as e: 

341 raise await processor._handle_llm_api_exception( 

342 e=e, 

343 user_api_key_dict=user_api_key_dict, 

344 proxy_logging_obj=proxy_logging_obj, 

345 version=version, 

346 ) 

347 

348 

349@router.get("/v1/vector_stores", dependencies=[Depends(user_api_key_auth)]) 

350@router.get("/vector_stores", dependencies=[Depends(user_api_key_auth)]) 

351async def vector_store_list( 

352 request: Request, 

353 fastapi_response: Response, 

354 after: str | None = None, 

355 before: str | None = None, 

356 limit: int | None = 20, 

357 order: str | None = "desc", 

358 user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth), 

359): 

360 """ 

361 List vector stores. 

362 

363 API Reference: 

364 https://platform.openai.com/docs/api-reference/vector-stores/list 

365 """ 

366 from litellm.proxy.proxy_server import ( 

367 general_settings, 

368 llm_router, 

369 proxy_config, 

370 proxy_logging_obj, 

371 select_data_generator, 

372 user_api_base, 

373 user_max_tokens, 

374 user_model, 

375 user_request_timeout, 

376 user_temperature, 

377 version, 

378 ) 

379 

380 data: Final[dict] = {} 

381 if after is not None: 

382 data["after"] = after 

383 if before is not None: 

384 data["before"] = before 

385 if limit is not None: 385 ↛ 387line 385 didn't jump to line 387 because the condition on line 385 was always true

386 data["limit"] = limit 

387 if order is not None: 387 ↛ 390line 387 didn't jump to line 390 because the condition on line 387 was always true

388 data["order"] = order 

389 

390 processor: Final = ProxyBaseLLMRequestProcessing(data=data) 

391 try: 

392 return await processor.base_process_llm_request( 

393 request=request, 

394 fastapi_response=fastapi_response, 

395 user_api_key_dict=user_api_key_dict, 

396 route_type="avector_store_list", 

397 proxy_logging_obj=proxy_logging_obj, 

398 llm_router=llm_router, 

399 general_settings=general_settings, 

400 proxy_config=proxy_config, 

401 select_data_generator=select_data_generator, 

402 model=None, 

403 user_model=user_model, 

404 user_temperature=user_temperature, 

405 user_request_timeout=user_request_timeout, 

406 user_max_tokens=user_max_tokens, 

407 user_api_base=user_api_base, 

408 version=version, 

409 ) 

410 except Exception as e: 

411 raise await processor._handle_llm_api_exception( 

412 e=e, 

413 user_api_key_dict=user_api_key_dict, 

414 proxy_logging_obj=proxy_logging_obj, 

415 version=version, 

416 ) 

417 

418 

419@router.post("/v1/vector_stores/{vector_store_id}", dependencies=[Depends(user_api_key_auth)]) 

420@router.post("/vector_stores/{vector_store_id}", dependencies=[Depends(user_api_key_auth)]) 

421async def vector_store_update( 

422 request: Request, 

423 vector_store_id: str, 

424 fastapi_response: Response, 

425 user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth), 

426): 

427 """ 

428 Update a vector store. 

429 

430 API Reference: 

431 https://platform.openai.com/docs/api-reference/vector-stores/modify 

432 """ 

433 from litellm.proxy.proxy_server import ( 

434 _read_request_body, 

435 general_settings, 

436 llm_router, 

437 proxy_config, 

438 proxy_logging_obj, 

439 select_data_generator, 

440 user_api_base, 

441 user_max_tokens, 

442 user_model, 

443 user_request_timeout, 

444 user_temperature, 

445 version, 

446 ) 

447 

448 data = await _read_request_body(request=request) 

449 if "vector_store_id" not in data: 449 ↛ 452line 449 didn't jump to line 452 because the condition on line 449 was always true

450 data["vector_store_id"] = vector_store_id 

451 

452 data = await _update_request_data_with_litellm_managed_vector_store_registry( 

453 data=data, vector_store_id=vector_store_id, user_api_key_dict=user_api_key_dict 

454 ) 

455 

456 processor: Final = ProxyBaseLLMRequestProcessing(data=data) 

457 try: 

458 return await processor.base_process_llm_request( 

459 request=request, 

460 fastapi_response=fastapi_response, 

461 user_api_key_dict=user_api_key_dict, 

462 route_type="avector_store_update", 

463 proxy_logging_obj=proxy_logging_obj, 

464 llm_router=llm_router, 

465 general_settings=general_settings, 

466 proxy_config=proxy_config, 

467 select_data_generator=select_data_generator, 

468 model=None, 

469 user_model=user_model, 

470 user_temperature=user_temperature, 

471 user_request_timeout=user_request_timeout, 

472 user_max_tokens=user_max_tokens, 

473 user_api_base=user_api_base, 

474 version=version, 

475 ) 

476 except Exception as e: 

477 raise await processor._handle_llm_api_exception( 

478 e=e, 

479 user_api_key_dict=user_api_key_dict, 

480 proxy_logging_obj=proxy_logging_obj, 

481 version=version, 

482 ) 

483 

484 

485@router.delete("/v1/vector_stores/{vector_store_id}", dependencies=[Depends(user_api_key_auth)]) 

486@router.delete("/vector_stores/{vector_store_id}", dependencies=[Depends(user_api_key_auth)]) 

487async def vector_store_delete( 

488 request: Request, 

489 vector_store_id: str, 

490 fastapi_response: Response, 

491 user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth), 

492): 

493 """ 

494 Delete a vector store. 

495 

496 API Reference: 

497 https://platform.openai.com/docs/api-reference/vector-stores/delete 

498 """ 

499 from litellm.proxy.proxy_server import ( 

500 general_settings, 

501 llm_router, 

502 proxy_config, 

503 proxy_logging_obj, 

504 select_data_generator, 

505 user_api_base, 

506 user_max_tokens, 

507 user_model, 

508 user_request_timeout, 

509 user_temperature, 

510 version, 

511 ) 

512 

513 data = {"vector_store_id": vector_store_id} 

514 

515 data = await _update_request_data_with_litellm_managed_vector_store_registry( 

516 data=data, vector_store_id=vector_store_id, user_api_key_dict=user_api_key_dict 

517 ) 

518 

519 processor: Final = ProxyBaseLLMRequestProcessing(data=data) 

520 try: 

521 return await processor.base_process_llm_request( 

522 request=request, 

523 fastapi_response=fastapi_response, 

524 user_api_key_dict=user_api_key_dict, 

525 route_type="avector_store_delete", 

526 proxy_logging_obj=proxy_logging_obj, 

527 llm_router=llm_router, 

528 general_settings=general_settings, 

529 proxy_config=proxy_config, 

530 select_data_generator=select_data_generator, 

531 model=None, 

532 user_model=user_model, 

533 user_temperature=user_temperature, 

534 user_request_timeout=user_request_timeout, 

535 user_max_tokens=user_max_tokens, 

536 user_api_base=user_api_base, 

537 version=version, 

538 ) 

539 except Exception as e: 

540 raise await processor._handle_llm_api_exception( 

541 e=e, 

542 user_api_key_dict=user_api_key_dict, 

543 proxy_logging_obj=proxy_logging_obj, 

544 version=version, 

545 ) 

546 

547 

548@router.post( 

549 "/v1/indexes", 

550 dependencies=[Depends(user_api_key_auth)], 

551) 

552async def index_create( 

553 request: Request, 

554 index_create_request: IndexCreateRequest, 

555 fastapi_response: Response, 

556 user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth), 

557): 

558 """ 

559 Create an index. Just writes the index to the database. 

560 

561 ```bash 

562 curl -L -X POST 'http://0.0.0.0:4000/v1/indexes' \ 

563 -H 'Content-Type: application/json' \ 

564 -H 'Authorization: Bearer sk-1234' \ 

565 -d '{ 

566 "index_name": "dall-e-3", 

567 "litellm_params": { 

568 "vector_store_index": "real-index-name", 

569 "vector_store_name": "azure-ai-search" 

570 } 

571 }' 

572 ``` 

573 """ 

574 from litellm.proxy.proxy_server import prisma_client 

575 

576 assert_proxy_admin_for_vector_store_index_management( 

577 user_api_key_dict, 

578 operation="create", 

579 ) 

580 

581 if prisma_client is None: 581 ↛ 582line 581 didn't jump to line 582 because the condition on line 581 was never true

582 raise HTTPException( 

583 status_code=500, 

584 detail=CommonProxyErrors.db_not_connected_error.value, 

585 ) 

586 ## 1. check if index already exists 

587 existing_index: Final = await ManagedVectorStoreIndexRepository(prisma_client).table.find_unique( 

588 where={"index_name": index_create_request.index_name} 

589 ) 

590 

591 ## 2. set created_by and updated_by 

592 

593 if existing_index is not None: 

594 raise HTTPException( 

595 status_code=400, 

596 detail=f"Index {index_create_request.index_name} already exists", 

597 ) 

598 

599 ## 2. create index 

600 index_data: Final = index_create_request.model_dump(exclude_none=True) 

601 index_data["created_by"] = user_api_key_dict.user_id 

602 index_data["updated_by"] = user_api_key_dict.user_id 

603 new_index = await ManagedVectorStoreIndexRepository(prisma_client).table.create( 

604 data=cast( # cast-ok: jsonify_object deep-copies a model_dump, so keys are str and values plain objects 

605 "dict[str, object]", jsonify_object(index_data) 

606 ) 

607 ) 

608 

609 return new_index.model_dump() 

610 

611 

612@router.get( 

613 "/v1/indexes", 

614 dependencies=[Depends(user_api_key_auth)], 

615 response_model=IndexListResponse, 

616) 

617async def index_list( 

618 user_api_key_dict: Annotated[UserAPIKeyAuth, Depends(user_api_key_auth)], 

619) -> IndexListResponse: 

620 """ 

621 List all vector store indexes. Proxy admin only. 

622 

623 ```bash 

624 curl -L -X GET 'http://0.0.0.0:4000/v1/indexes' \ 

625 -H 'Authorization: Bearer sk-1234' 

626 ``` 

627 """ 

628 from litellm.proxy.proxy_server import prisma_client 

629 

630 assert_proxy_admin_for_vector_store_index_management( 

631 user_api_key_dict, 

632 operation="list", 

633 ) 

634 

635 if prisma_client is None: 635 ↛ 636line 635 didn't jump to line 636 because the condition on line 635 was never true

636 raise HTTPException( 

637 status_code=500, 

638 detail=CommonProxyErrors.db_not_connected_error.value, 

639 ) 

640 

641 indexes: Final = await VectorStoreIndexRegistry._get_vector_store_indexes_from_db(prisma_client) 

642 return IndexListResponse(data=indexes)