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
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-10 12:01 +0000
1"""
2VECTOR STORE MANAGEMENT
4All /vector_store management endpoints
6/vector_store/new
7/vector_store/delete
8/vector_store/list
9"""
11import copy
12import json
13from typing import TYPE_CHECKING, Any, Final
15from fastapi import APIRouter, Depends, HTTPException
16from typing_extensions import ReadOnly, TypedDict
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
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
49router: Final = APIRouter()
52def _vector_store_table(prisma_client: "PrismaClient") -> "TableActions[_VectorStoreRow]":
53 return ManagedVectorStoresRepository(prisma_client).table
56def _row_to_vector_store(row: "_VectorStoreRow") -> LiteLLM_ManagedVectorStore:
57 return LiteLLM_ManagedVectorStore(**row.model_dump())
60class _ConfigOwnedDetail(TypedDict):
61 error: ReadOnly[str]
62 vector_store_id: ReadOnly[str]
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)
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
86_LITELLM_PARAMS_MASKER: Final = SensitiveDataMasker(extra_sensitive_patterns=frozenset(("connection",)))
89_REDACT_LITELLM_PARAMS_MAX_DEPTH: Final = 10
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``).
98 Handles three input shapes:
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.
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
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
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.
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)
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.
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
199 Returns:
200 LiteLLM_ManagedVectorStore: The created vector store object
202 Raises:
203 HTTPException: If vector store already exists or database error occurs
204 """
205 from litellm.types.router import GenericLiteLLMParams
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")
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 )
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 }
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
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({})
255 # Create in database
256 _new_vector_store: Final = await _vector_store_table(prisma_client).create(data=data_to_create)
258 new_vector_store: Final[LiteLLM_ManagedVectorStore] = _row_to_vector_store(_new_vector_store)
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)
264 verbose_proxy_logger.info("Vector store %s created in database successfully", vector_store_id)
266 return new_vector_store
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.
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")
293 from litellm.proxy.proxy_server import prisma_client
295 try:
296 vector_store_id: Final = vector_store.get("vector_store_id")
297 custom_llm_provider: Final = vector_store.get("custom_llm_provider")
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)
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
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 )
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"))
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))
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.
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")
374 from litellm.proxy.proxy_server import prisma_client
376 vector_store_map: Final[dict[str, LiteLLM_ManagedVectorStore]] = {}
377 db_vector_store_ids: Final[set] = set()
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)
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)
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)
394 vector_stores_to_delete_from_memory: Final[list[str]] = []
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
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
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)
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 )
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)
435 total_count: Final = len(accessible_vector_stores)
436 total_pages: Final = (total_count + page_size - 1) // page_size
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 )
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))
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.
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")
470 from litellm.proxy.proxy_server import prisma_client
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")
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
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)
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
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)
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 )
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})
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)
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))
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")
545 from litellm.proxy.proxy_server import prisma_client
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")
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 )
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
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}
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))
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")
620 from litellm.proxy.proxy_server import prisma_client
621 from litellm.types.router import GenericLiteLLMParams
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")
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)
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 )
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"])
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)
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 )
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 )
667 updated_vs: Final = _row_to_vector_store(updated)
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 )
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))