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

269 statements  

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

1""" 

2VECTOR STORE MANAGEMENT 

3 

4All /vector_store management endpoints 

5 

6/vector_store/new 

7/vector_store/delete 

8/vector_store/list 

9""" 

10 

11import copy 

12import json 

13from typing import TYPE_CHECKING, Any, Final 

14 

15from fastapi import APIRouter, Depends, HTTPException 

16from typing_extensions import ReadOnly, TypedDict 

17 

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

19 from prisma.models import LiteLLM_ManagedVectorStoresTable as _VectorStoreRow 

20 

21 from litellm.proxy.utils import PrismaClient 

22import litellm 

23from litellm._logging import verbose_proxy_logger 

24from litellm.constants import REDACTED_BY_LITELM_STRING 

25from litellm.litellm_core_utils.safe_json_dumps import safe_dumps 

26from litellm.litellm_core_utils.sensitive_data_masker import SensitiveDataMasker 

27from litellm.proxy._types import ( 

28 LiteLLM_ManagedVectorStoresTable, 

29 ResponseLiteLLM_ManagedVectorStore, 

30 UserAPIKeyAuth, 

31) 

32from litellm.proxy.auth.user_api_key_auth import user_api_key_auth 

33from litellm.proxy.common_utils.rbac_utils import check_feature_access_for_user 

34from litellm.proxy.vector_store_endpoints.utils import ( 

35 can_user_access_vector_store, 

36 filter_listable_vector_stores, 

37) 

38from litellm.repositories.prisma_protocols import TableActions 

39from litellm.repositories.table_repositories import ManagedVectorStoresRepository 

40from litellm.types.vector_stores import ( 

41 LiteLLM_ManagedVectorStore, 

42 LiteLLM_ManagedVectorStoreListResponse, 

43 VectorStoreDeleteRequest, 

44 VectorStoreInfoRequest, 

45 VectorStoreUpdateRequest, 

46) 

47from litellm.vector_stores.vector_store_registry import VectorStoreRegistry 

48 

49router: Final = APIRouter() 

50 

51 

52def _vector_store_table(prisma_client: "PrismaClient") -> "TableActions[_VectorStoreRow]": 

53 return ManagedVectorStoresRepository(prisma_client).table 

54 

55 

56def _row_to_vector_store(row: "_VectorStoreRow") -> LiteLLM_ManagedVectorStore: 

57 return LiteLLM_ManagedVectorStore(**row.model_dump()) 

58 

59 

60class _ConfigOwnedDetail(TypedDict): 

61 error: ReadOnly[str] 

62 vector_store_id: ReadOnly[str] 

63 

64 

65def _raise_if_config_owned(vector_store_id: str) -> None: 

66 if litellm.vector_store_registry is None or not litellm.vector_store_registry.is_config_vector_store( 66 ↛ 70line 66 didn't jump to line 70 because the condition on line 66 was always true

67 vector_store_id 

68 ): 

69 return 

70 detail: Final[_ConfigOwnedDetail] = { 

71 "error": ( 

72 f"Vector store {vector_store_id} is defined in the config file, so the config file owns it and it " 

73 "cannot be changed here. Edit the config file to change it, or remove it from the file to let the " 

74 "database own it." 

75 ), 

76 "vector_store_id": vector_store_id, 

77 } 

78 raise HTTPException(status_code=400, detail=detail) 

79 

80 

81def _with_ownership(vector_store: LiteLLM_ManagedVectorStore) -> LiteLLM_ManagedVectorStore: 

82 ownership: Final = LiteLLM_ManagedVectorStore(is_config=vector_store.get("is_config", False)) 

83 return vector_store | ownership 

84 

85 

86_LITELLM_PARAMS_MASKER: Final = SensitiveDataMasker(extra_sensitive_patterns=frozenset(("connection",))) 

87 

88 

89_REDACT_LITELLM_PARAMS_MAX_DEPTH: Final = 10 

90 

91 

92def _redact_sensitive_litellm_params(litellm_params: object, _depth: int = 0) -> Any: 

93 """ 

94 Replace credential-bearing values in ``litellm_params`` with 

95 ``REDACTED_BY_LITELM`` while preserving non-secret keys (``api_base``, 

96 ``region``, ``model``, ``api_version``). 

97 

98 Handles three input shapes: 

99 

100 * ``dict`` — recurse into nested dicts (e.g. ``litellm_embedding_config`` 

101 which itself carries ``api_key`` / ``aws_*`` / ``vertex_credentials``). 

102 * ``str`` — the in-memory registry occasionally holds the params as a 

103 JSON-serialized string. Parse, redact, re-serialize. If parsing 

104 fails, return the redaction sentinel rather than echo the value 

105 back verbatim. 

106 * Anything else, or ``None`` — passed through. 

107 

108 Recursion depth is bounded by ``_REDACT_LITELLM_PARAMS_MAX_DEPTH`` — 

109 matching the convention of other allowlisted recursive helpers in the 

110 repo (see ``tests/code_coverage_tests/recursive_detector.py``). 

111 """ 

112 if _depth >= _REDACT_LITELLM_PARAMS_MAX_DEPTH: 112 ↛ 113line 112 didn't jump to line 113 because the condition on line 112 was never true

113 return REDACTED_BY_LITELM_STRING 

114 if litellm_params is None: 114 ↛ 115line 114 didn't jump to line 115 because the condition on line 114 was never true

115 return None 

116 if isinstance(litellm_params, str): 116 ↛ 117line 116 didn't jump to line 117 because the condition on line 116 was never true

117 try: 

118 parsed: Final[object] = json.loads(litellm_params) 

119 except (TypeError, ValueError): 

120 return REDACTED_BY_LITELM_STRING 

121 return json.dumps(_redact_sensitive_litellm_params(parsed, _depth + 1)) 

122 if not isinstance(litellm_params, dict): 122 ↛ 123line 122 didn't jump to line 123 because the condition on line 122 was never true

123 return litellm_params 

124 out: Final[dict[str, object]] = {} 

125 for k, v in litellm_params.items(): 

126 if _LITELLM_PARAMS_MASKER.is_sensitive_key(k): 126 ↛ 127line 126 didn't jump to line 127 because the condition on line 126 was never true

127 out[k] = REDACTED_BY_LITELM_STRING 

128 elif isinstance(v, dict): 

129 out[k] = _redact_sensitive_litellm_params(v, _depth + 1) 

130 else: 

131 out[k] = v 

132 return out 

133 

134 

135async def _fetch_and_authorize_vector_store( 

136 vector_store_id: str, 

137 user_api_key_dict: UserAPIKeyAuth, 

138 prisma_client: "PrismaClient", 

139) -> "LiteLLM_ManagedVectorStore": 

140 """ 

141 Look up a vector store by id and confirm the caller can access it. 

142 Raises HTTPException(404) on miss and HTTPException(403) on access 

143 denial. 

144 """ 

145 row: Final = await _vector_store_table(prisma_client).find_unique(where={"vector_store_id": vector_store_id}) 

146 if row is None: 

147 raise HTTPException( 

148 status_code=404, 

149 detail=f"Vector store with ID {vector_store_id} not found", 

150 ) 

151 typed: Final = _row_to_vector_store(row) 

152 if not await _check_vector_store_access(typed, user_api_key_dict): 152 ↛ 153line 152 didn't jump to line 153 because the condition on line 152 was never true

153 raise HTTPException( 

154 status_code=403, 

155 detail="Access denied: You do not have permission to access this vector store", 

156 ) 

157 return typed 

158 

159 

160######################################################## 

161# Helper Functions 

162######################################################## 

163async def _check_vector_store_access( 

164 vector_store: LiteLLM_ManagedVectorStore, 

165 user_api_key_dict: UserAPIKeyAuth, 

166) -> bool: 

167 """ 

168 Check if the user has access to the vector store. 

169 

170 Delegates to :func:`can_user_access_vector_store`, which honors: 

171 - PROXY_ADMIN bypass 

172 - legacy vector stores with no team_id 

173 - key-level and team-level ``object_permission.vector_stores`` allowlists 

174 - team_id match between key and store 

175 """ 

176 return await can_user_access_vector_store(vector_store=vector_store, user_api_key_dict=user_api_key_dict) 

177 

178 

179async def create_vector_store_in_db( 

180 vector_store_id: str, 

181 custom_llm_provider: str, 

182 prisma_client: "PrismaClient | None", 

183 vector_store_name: str | None = None, 

184 vector_store_description: str | None = None, 

185 vector_store_metadata: dict | None = None, 

186 litellm_params: dict | None = None, 

187 litellm_credential_name: str | None = None, 

188 team_id: str | None = None, 

189 user_id: str | None = None, 

190) -> LiteLLM_ManagedVectorStore: 

191 """ 

192 Helper function to create a vector store in the database. 

193 

194 This function handles: 

195 - Checking if vector store already exists 

196 - Creating the vector store in the database 

197 - Adding it to the vector store registry 

198 

199 Returns: 

200 LiteLLM_ManagedVectorStore: The created vector store object 

201 

202 Raises: 

203 HTTPException: If vector store already exists or database error occurs 

204 """ 

205 from litellm.types.router import GenericLiteLLMParams 

206 

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

208 raise HTTPException(status_code=500, detail="Database not connected") 

209 

210 # Check if vector store already exists 

211 existing_vector_store: Final = await _vector_store_table(prisma_client).find_unique( 

212 where={"vector_store_id": vector_store_id} 

213 ) 

214 if existing_vector_store is not None: 

215 raise HTTPException( 

216 status_code=400, 

217 detail=f"Vector store with ID {vector_store_id} already exists", 

218 ) 

219 

220 # Prepare data for database 

221 data_to_create: Final[dict[str, object]] = { 

222 "vector_store_id": vector_store_id, 

223 "custom_llm_provider": custom_llm_provider, 

224 } 

225 

226 if vector_store_name is not None: 226 ↛ 228line 226 didn't jump to line 228 because the condition on line 226 was always true

227 data_to_create["vector_store_name"] = vector_store_name 

228 if vector_store_description is not None: 

229 data_to_create["vector_store_description"] = vector_store_description 

230 if vector_store_metadata is not None: 

231 data_to_create["vector_store_metadata"] = safe_dumps(vector_store_metadata) 

232 if litellm_credential_name is not None: 232 ↛ 234line 232 didn't jump to line 234 because the condition on line 232 was always true

233 data_to_create["litellm_credential_name"] = litellm_credential_name 

234 if team_id is not None: 234 ↛ 235line 234 didn't jump to line 235 because the condition on line 234 was never true

235 data_to_create["team_id"] = team_id 

236 if user_id is not None: 236 ↛ 248line 236 didn't jump to line 248 because the condition on line 236 was always true

237 data_to_create["user_id"] = user_id 

238 

239 # Handle litellm_params - always provide at least an empty dict. 

240 # The earlier behaviour resolved ``litellm_embedding_config`` from the 

241 # admin-configured router/DB model and persisted the cleartext result 

242 # (``api_key``, ``api_base``, ``api_version``) into this row. That 

243 # exposed every env-stored embedding-model credential on the 

244 # ``/vector_store/{new,info,update,list}`` responses. Keep the user's 

245 # raw ``litellm_embedding_model`` reference; each search embeds the 

246 # query through the router at request time, so the credentials stay 

247 # on the deployment and never reach the database. 

248 if litellm_params: 

249 litellm_params_dict: Final = GenericLiteLLMParams(**litellm_params).model_dump(exclude_none=True) 

250 data_to_create["litellm_params"] = safe_dumps(litellm_params_dict) 

251 else: 

252 # Provide empty dict if no litellm_params provided 

253 data_to_create["litellm_params"] = safe_dumps({}) 

254 

255 # Create in database 

256 _new_vector_store: Final = await _vector_store_table(prisma_client).create(data=data_to_create) 

257 

258 new_vector_store: Final[LiteLLM_ManagedVectorStore] = _row_to_vector_store(_new_vector_store) 

259 

260 # Add vector store to registry 

261 if litellm.vector_store_registry is not None: 

262 litellm.vector_store_registry.add_vector_store_to_registry(vector_store=new_vector_store) 

263 

264 verbose_proxy_logger.info("Vector store %s created in database successfully", vector_store_id) 

265 

266 return new_vector_store 

267 

268 

269######################################################## 

270# Management Endpoints 

271######################################################## 

272@router.post( 

273 "/vector_store/new", 

274 tags=["vector store management"], 

275 dependencies=[Depends(user_api_key_auth)], 

276) 

277async def new_vector_store( 

278 vector_store: LiteLLM_ManagedVectorStore, 

279 user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth), 

280): 

281 """ 

282 Create a new vector store. 

283 

284 Parameters: 

285 - vector_store_id: str - Unique identifier for the vector store 

286 - custom_llm_provider: str - Provider of the vector store 

287 - vector_store_name: Optional[str] - Name of the vector store 

288 - vector_store_description: Optional[str] - Description of the vector store 

289 - vector_store_metadata: Optional[Dict] - Additional metadata for the vector store 

290 """ 

291 await check_feature_access_for_user(user_api_key_dict, "vector_stores") 

292 

293 from litellm.proxy.proxy_server import prisma_client 

294 

295 try: 

296 vector_store_id: Final = vector_store.get("vector_store_id") 

297 custom_llm_provider: Final = vector_store.get("custom_llm_provider") 

298 

299 if not vector_store_id or not custom_llm_provider: 

300 raise HTTPException( 

301 status_code=400, 

302 detail="vector_store_id and custom_llm_provider are required", 

303 ) 

304 _raise_if_config_owned(vector_store_id) 

305 

306 # Extract and validate metadata 

307 metadata: Final = vector_store.get("vector_store_metadata") 

308 validated_metadata: dict | None = None 

309 if metadata is not None and isinstance(metadata, dict): 

310 validated_metadata = metadata 

311 

312 new_vector_store: Final = await create_vector_store_in_db( 

313 vector_store_id=vector_store_id, 

314 custom_llm_provider=custom_llm_provider, 

315 prisma_client=prisma_client, 

316 vector_store_name=vector_store.get("vector_store_name"), 

317 vector_store_description=vector_store.get("vector_store_description"), 

318 vector_store_metadata=validated_metadata, 

319 litellm_params=vector_store.get("litellm_params"), 

320 litellm_credential_name=vector_store.get("litellm_credential_name"), 

321 team_id=user_api_key_dict.team_id, 

322 user_id=user_api_key_dict.user_id, 

323 ) 

324 

325 # Apply the same litellm_params redaction the list / info / update 

326 # endpoints already use, so a caller-supplied credential or a 

327 # cleartext value persisted by an earlier proxy version doesn't 

328 # come back in the response. 

329 response_vs: Final = LiteLLM_ManagedVectorStore(**new_vector_store) 

330 response_vs["litellm_params"] = _redact_sensitive_litellm_params(new_vector_store.get("litellm_params")) 

331 

332 return { 

333 "status": "success", 

334 "message": f"Vector store {vector_store.get('vector_store_id')} created successfully", 

335 "vector_store": response_vs, 

336 } 

337 except HTTPException: 

338 raise 

339 except Exception as e: 

340 verbose_proxy_logger.exception("Error creating vector store: %s", e) 

341 raise HTTPException(status_code=500, detail=str(e)) 

342 

343 

344@router.get( 

345 "/vector_store/list", 

346 tags=["vector store management"], 

347 dependencies=[Depends(user_api_key_auth)], 

348 response_model=LiteLLM_ManagedVectorStoreListResponse, 

349) 

350@router.get( 

351 "/v1/vector_store/list", 

352 tags=["vector store management"], 

353 dependencies=[Depends(user_api_key_auth)], 

354 response_model=LiteLLM_ManagedVectorStoreListResponse, 

355) 

356async def list_vector_stores( 

357 user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth), 

358 page: int = 1, 

359 page_size: int = 100, 

360): 

361 """ 

362 List all available vector stores with optional filtering and pagination. 

363 Combines both in-memory vector stores and those stored in the database. 

364 Database is the source of truth for stores it owns: deleted stores are removed from memory, updated stores 

365 sync to memory. Stores declared in the config file are owned by the config file, are always listed, and are 

366 never overwritten by database rows. 

367 

368 Parameters: 

369 - page: int - Page number for pagination (default: 1) 

370 - page_size: int - Number of items per page (default: 100) 

371 """ 

372 await check_feature_access_for_user(user_api_key_dict, "vector_stores") 

373 

374 from litellm.proxy.proxy_server import prisma_client 

375 

376 vector_store_map: Final[dict[str, LiteLLM_ManagedVectorStore]] = {} 

377 db_vector_store_ids: Final[set] = set() 

378 

379 try: 

380 # Get vector stores from database first (source of truth) 

381 vector_stores_from_db: Final = await VectorStoreRegistry._get_vector_stores_from_db(prisma_client=prisma_client) 

382 

383 # Build map from database vector stores 

384 for vector_store in vector_stores_from_db: 

385 vector_store_id = vector_store.get("vector_store_id", None) 

386 if vector_store_id: 386 ↛ 384line 386 didn't jump to line 384 because the condition on line 386 was always true

387 vector_store_map[vector_store_id] = vector_store 

388 db_vector_store_ids.add(vector_store_id) 

389 

390 # Process in-memory vector stores 

391 if litellm.vector_store_registry is not None: 

392 in_memory_vector_stores: Final = copy.deepcopy(litellm.vector_store_registry.vector_stores) 

393 

394 vector_stores_to_delete_from_memory: Final[list[str]] = [] 

395 

396 for vector_store in in_memory_vector_stores: 

397 vector_store_id = vector_store.get("vector_store_id", None) 

398 if not vector_store_id: 398 ↛ 399line 398 didn't jump to line 399 because the condition on line 398 was never true

399 continue 

400 

401 if vector_store.get("is_config", False): 401 ↛ 402line 401 didn't jump to line 402 because the condition on line 401 was never true

402 vector_store_map[vector_store_id] = vector_store 

403 # If vector store is in memory but NOT in database, it was deleted 

404 elif vector_store_id not in db_vector_store_ids: 404 ↛ 405line 404 didn't jump to line 405 because the condition on line 404 was never true

405 verbose_proxy_logger.info( 

406 "Vector store %s exists in memory but not in database - marking for deletion from cache", 

407 vector_store_id, 

408 ) 

409 vector_stores_to_delete_from_memory.append(vector_store_id) 

410 # If not in our map yet, add it (only in-memory, not in DB) 

411 elif vector_store_id not in vector_store_map: 411 ↛ 412line 411 didn't jump to line 412 because the condition on line 411 was never true

412 vector_store_map[vector_store_id] = vector_store 

413 

414 # Synchronize in-memory registry with database 

415 # 1. Remove deleted vector stores from memory 

416 for vs_id in vector_stores_to_delete_from_memory: 416 ↛ 417line 416 didn't jump to line 417 because the loop on line 416 never started

417 litellm.vector_store_registry.delete_vector_store_from_registry(vector_store_id=vs_id) 

418 verbose_proxy_logger.debug("Removed deleted vector store %s from in-memory registry", vs_id) 

419 

420 # 2. Update in-memory registry with database versions (for updates) 

421 for vector_store in vector_stores_from_db: 

422 vector_store_id = vector_store.get("vector_store_id", None) 

423 if vector_store_id: 423 ↛ 421line 423 didn't jump to line 421 because the condition on line 423 was always true

424 litellm.vector_store_registry.update_vector_store_in_registry( 

425 vector_store_id=vector_store_id, updated_data=vector_store 

426 ) 

427 

428 # Filter vector stores based on access control 

429 accessible_vector_stores: Final = [] 

430 for vs in await filter_listable_vector_stores(vector_store_map.values(), user_api_key_dict): 

431 redacted = _with_ownership(vs) 

432 redacted["litellm_params"] = _redact_sensitive_litellm_params(vs.get("litellm_params")) 

433 accessible_vector_stores.append(redacted) 

434 

435 total_count: Final = len(accessible_vector_stores) 

436 total_pages: Final = (total_count + page_size - 1) // page_size 

437 

438 # Format response using LiteLLM_ManagedVectorStoreListResponse 

439 response: Final = LiteLLM_ManagedVectorStoreListResponse( 

440 object="list", 

441 data=accessible_vector_stores, 

442 total_count=total_count, 

443 current_page=page, 

444 total_pages=total_pages, 

445 ) 

446 

447 return response 

448 except Exception as e: 

449 verbose_proxy_logger.exception("Error listing vector stores: %s", e) 

450 raise HTTPException(status_code=500, detail=str(e)) 

451 

452 

453@router.post( 

454 "/vector_store/delete", 

455 tags=["vector store management"], 

456 dependencies=[Depends(user_api_key_auth)], 

457) 

458async def delete_vector_store( 

459 data: VectorStoreDeleteRequest, 

460 user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth), 

461): 

462 """ 

463 Delete a vector store from both database and in-memory registry. 

464 

465 Parameters: 

466 - vector_store_id: str - ID of the vector store to delete 

467 """ 

468 await check_feature_access_for_user(user_api_key_dict, "vector_stores") 

469 

470 from litellm.proxy.proxy_server import prisma_client 

471 

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

473 raise HTTPException(status_code=500, detail="Database not connected") 

474 

475 try: 

476 # Check if vector store exists in database or in-memory registry 

477 db_vector_store_exists = False 

478 memory_vector_store_exists = False 

479 vector_store_to_check = None 

480 

481 existing_vector_store: Final = await _vector_store_table(prisma_client).find_unique( 

482 where={"vector_store_id": data.vector_store_id} 

483 ) 

484 if existing_vector_store is not None: 

485 db_vector_store_exists = True 

486 vector_store_to_check = _row_to_vector_store(existing_vector_store) 

487 

488 # Check in-memory registry 

489 if litellm.vector_store_registry is not None: 

490 memory_vector_store: Final = litellm.vector_store_registry.get_litellm_managed_vector_store_from_registry( 

491 vector_store_id=data.vector_store_id 

492 ) 

493 if memory_vector_store is not None: 

494 memory_vector_store_exists = True 

495 if vector_store_to_check is None: 495 ↛ 496line 495 didn't jump to line 496 because the condition on line 495 was never true

496 vector_store_to_check = memory_vector_store 

497 

498 # If not found in either location, raise 404 

499 if not db_vector_store_exists and not memory_vector_store_exists: 

500 raise HTTPException( 

501 status_code=404, 

502 detail=f"Vector store with ID {data.vector_store_id} not found", 

503 ) 

504 _raise_if_config_owned(data.vector_store_id) 

505 

506 # Check access control 

507 if vector_store_to_check and not await _check_vector_store_access(vector_store_to_check, user_api_key_dict): 507 ↛ 508line 507 didn't jump to line 508 because the condition on line 507 was never true

508 raise HTTPException( 

509 status_code=403, 

510 detail="Access denied: You do not have permission to delete this vector store", 

511 ) 

512 

513 # Delete from database if exists 

514 if db_vector_store_exists: 514 ↛ 518line 514 didn't jump to line 518 because the condition on line 514 was always true

515 await _vector_store_table(prisma_client).delete(where={"vector_store_id": data.vector_store_id}) 

516 

517 # Delete from in-memory registry if exists 

518 if memory_vector_store_exists and litellm.vector_store_registry is not None: 518 ↛ 521line 518 didn't jump to line 521 because the condition on line 518 was always true

519 litellm.vector_store_registry.delete_vector_store_from_registry(vector_store_id=data.vector_store_id) 

520 

521 return { 

522 "status": "success", 

523 "message": f"Vector store {data.vector_store_id} deleted successfully", 

524 } 

525 except HTTPException: 

526 raise 

527 except Exception as e: 

528 verbose_proxy_logger.exception("Error deleting vector store: %s", e) 

529 raise HTTPException(status_code=500, detail=str(e)) 

530 

531 

532@router.post( 

533 "/vector_store/info", 

534 tags=["vector store management"], 

535 dependencies=[Depends(user_api_key_auth)], 

536 response_model=ResponseLiteLLM_ManagedVectorStore, 

537) 

538async def get_vector_store_info( 

539 data: VectorStoreInfoRequest, 

540 user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth), 

541): 

542 """Return a single vector store's details""" 

543 await check_feature_access_for_user(user_api_key_dict, "vector_stores") 

544 

545 from litellm.proxy.proxy_server import prisma_client 

546 

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

548 raise HTTPException(status_code=500, detail="Database not connected") 

549 

550 try: 

551 if litellm.vector_store_registry is not None: 

552 vector_store: Final = litellm.vector_store_registry.get_litellm_managed_vector_store_from_registry( 

553 vector_store_id=data.vector_store_id 

554 ) 

555 if vector_store is not None: 555 ↛ 557line 555 didn't jump to line 557 because the condition on line 555 was never true

556 # Check access control 

557 if not await _check_vector_store_access(vector_store, user_api_key_dict): 

558 raise HTTPException( 

559 status_code=403, 

560 detail="Access denied: You do not have permission to access this vector store", 

561 ) 

562 

563 vector_store_metadata: Final = vector_store.get("vector_store_metadata") 

564 # Parse metadata if it's a JSON string 

565 parsed_metadata: dict | None = None 

566 if isinstance(vector_store_metadata, str): 

567 parsed_metadata = json.loads(vector_store_metadata) 

568 elif isinstance(vector_store_metadata, dict): 

569 parsed_metadata = vector_store_metadata 

570 

571 vector_store_pydantic_obj: Final = LiteLLM_ManagedVectorStoresTable( 

572 vector_store_id=vector_store.get("vector_store_id") or "", 

573 custom_llm_provider=vector_store.get("custom_llm_provider") or "", 

574 vector_store_name=vector_store.get("vector_store_name") or None, 

575 vector_store_description=vector_store.get("vector_store_description") or None, 

576 vector_store_metadata=parsed_metadata, 

577 created_at=vector_store.get("created_at") or None, 

578 updated_at=vector_store.get("updated_at") or None, 

579 litellm_credential_name=vector_store.get("litellm_credential_name"), 

580 litellm_params=_redact_sensitive_litellm_params(vector_store.get("litellm_params")), 

581 team_id=vector_store.get("team_id") or None, 

582 user_id=vector_store.get("user_id") or None, 

583 is_config=vector_store.get("is_config", False), 

584 ) 

585 return {"vector_store": vector_store_pydantic_obj} 

586 

587 vector_store_typed: Final = await _fetch_and_authorize_vector_store( 

588 vector_store_id=data.vector_store_id, 

589 user_api_key_dict=user_api_key_dict, 

590 prisma_client=prisma_client, 

591 ) 

592 vector_store_dict: Final = dict(vector_store_typed) 

593 if "litellm_params" in vector_store_dict: 

594 vector_store_dict["litellm_params"] = _redact_sensitive_litellm_params(vector_store_dict["litellm_params"]) 

595 return {"vector_store": vector_store_dict} 

596 except HTTPException: 

597 # Preserve 403/404 from the access-control / not-found checks above; 

598 # the catch-all below would otherwise rewrite them as 500. 

599 raise 

600 except Exception as e: 

601 verbose_proxy_logger.exception("Error getting vector store info: %s", e) 

602 raise HTTPException(status_code=500, detail=str(e)) 

603 

604 

605@router.post( 

606 "/vector_store/update", 

607 tags=["vector store management"], 

608 dependencies=[Depends(user_api_key_auth)], 

609) 

610async def update_vector_store( 

611 data: VectorStoreUpdateRequest, 

612 user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth), 

613): 

614 """ 

615 Update vector store details in both database and in-memory registry. 

616 The updated data is immediately synchronized to the in-memory registry. 

617 """ 

618 await check_feature_access_for_user(user_api_key_dict, "vector_stores") 

619 

620 from litellm.proxy.proxy_server import prisma_client 

621 from litellm.types.router import GenericLiteLLMParams 

622 

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

624 raise HTTPException(status_code=500, detail="Database not connected") 

625 

626 try: 

627 update_data: Final = data.model_dump(exclude_unset=True) 

628 vector_store_id: Final[str] = data.vector_store_id 

629 update_data.pop("vector_store_id") 

630 _raise_if_config_owned(vector_store_id) 

631 

632 # Per-store access control: anyone authenticated who passes the 

633 # premium-feature gate could otherwise update *any* vector store — 

634 # including stores belonging to other teams. 

635 await _fetch_and_authorize_vector_store( 

636 vector_store_id=vector_store_id, 

637 user_api_key_dict=user_api_key_dict, 

638 prisma_client=prisma_client, 

639 ) 

640 

641 # Handle metadata serialization 

642 if update_data.get("vector_store_metadata") is not None: 

643 update_data["vector_store_metadata"] = safe_dumps(update_data["vector_store_metadata"]) 

644 

645 # Handle litellm_params if provided. As with the create path, the 

646 # embedding-config auto-resolve previously persisted cleartext 

647 # credentials into the row; each search now embeds the query 

648 # through the router at request time, so this row only ever stores 

649 # the user-supplied ``litellm_embedding_model`` reference. 

650 if "litellm_params" in update_data: 650 ↛ 651line 650 didn't jump to line 651 because the condition on line 650 was never true

651 _input_litellm_params: Final[dict] = update_data.get("litellm_params", {}) or {} 

652 litellm_params_dict: Final = GenericLiteLLMParams(**_input_litellm_params).model_dump(exclude_none=True) 

653 update_data["litellm_params"] = safe_dumps(litellm_params_dict) 

654 

655 # Update in database 

656 updated: Final = await _vector_store_table(prisma_client).update( 

657 where={"vector_store_id": vector_store_id}, 

658 data=update_data, 

659 ) 

660 

661 if updated is None: 661 ↛ 662line 661 didn't jump to line 662 because the condition on line 661 was never true

662 raise HTTPException( 

663 status_code=404, 

664 detail=f"Vector store with ID {vector_store_id} not found", 

665 ) 

666 

667 updated_vs: Final = _row_to_vector_store(updated) 

668 

669 # Immediately update in-memory registry to keep it in sync 

670 if litellm.vector_store_registry is not None: 

671 litellm.vector_store_registry.update_vector_store_in_registry( 

672 vector_store_id=vector_store_id, 

673 updated_data=updated_vs, 

674 ) 

675 verbose_proxy_logger.debug( 

676 "Updated vector store %s in both database and in-memory registry", vector_store_id 

677 ) 

678 

679 # The DB row is returned in full, so the response would otherwise 

680 # echo the persisted ``litellm_params`` (including provider 

681 # credentials) back to the caller — even when the caller only 

682 # changed unrelated fields like ``vector_store_description``. 

683 response_vs: Final = LiteLLM_ManagedVectorStore(**updated_vs) 

684 response_vs["litellm_params"] = _redact_sensitive_litellm_params(updated_vs.get("litellm_params")) 

685 return { 

686 "status": "success", 

687 "message": f"Vector store {vector_store_id} updated successfully", 

688 "vector_store": response_vs, 

689 } 

690 except HTTPException: 

691 # Preserve 403/404 responses from the access-control / not-found 

692 # checks above; the catch-all below would otherwise rewrite them 

693 # as 500 with the original status code embedded in the detail. 

694 raise 

695 except Exception as e: 

696 verbose_proxy_logger.exception("Error updating vector store: %s", e) 

697 raise HTTPException(status_code=500, detail=str(e))