From f577c7d3a4e089c43a47365690f1cfdd3d17bcd3 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Wed, 2 Sep 2026 16:55:17 +0000 Subject: [PATCH] fix(vector_stores): keep config-defined vector stores when /vector_store/list reconciles with the DB Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- .../management_endpoints.py | 4 +- .../vector_stores/vector_store_registry.py | 3 ++ .../test_vector_store_endpoints.py | 48 +++++++++++++++++++ 3 files changed, 53 insertions(+), 2 deletions(-) diff --git a/litellm/proxy/vector_store_endpoints/management_endpoints.py b/litellm/proxy/vector_store_endpoints/management_endpoints.py index 183a03cc13c..e700c20611f 100644 --- a/litellm/proxy/vector_store_endpoints/management_endpoints.py +++ b/litellm/proxy/vector_store_endpoints/management_endpoints.py @@ -613,6 +613,7 @@ async def list_vector_stores( # Process in-memory vector stores if litellm.vector_store_registry is not None: in_memory_vector_stores: Final = copy.deepcopy(litellm.vector_store_registry.vector_stores) + config_vector_store_ids: Final = litellm.vector_store_registry.config_vector_store_ids vector_stores_to_delete_from_memory: Final[list[str]] = [] @@ -621,8 +622,7 @@ async def list_vector_stores( if not vector_store_id: continue - # If vector store is in memory but NOT in database, it was deleted - if vector_store_id not in db_vector_store_ids: + if vector_store_id not in db_vector_store_ids and vector_store_id not in config_vector_store_ids: verbose_proxy_logger.info( "Vector store %s exists in memory but not in database - marking for deletion from cache", vector_store_id, diff --git a/litellm/vector_stores/vector_store_registry.py b/litellm/vector_stores/vector_store_registry.py index b71d6784873..efb6405cb5c 100644 --- a/litellm/vector_stores/vector_store_registry.py +++ b/litellm/vector_stores/vector_store_registry.py @@ -104,6 +104,7 @@ class VectorStoreRegistry: def __init__(self, vector_stores: list[LiteLLM_ManagedVectorStore] = []): self.vector_stores: list[LiteLLM_ManagedVectorStore] = vector_stores self.vector_store_ids_to_vector_store_map: dict[str, LiteLLM_ManagedVectorStore] = {} + self.config_vector_store_ids: set[str] = set() def _extract_tool_params(self, tool: dict) -> VectorStoreToolParams: """ @@ -429,6 +430,7 @@ class VectorStoreRegistry: updated_at=datetime.now(timezone.utc), ) self.vector_stores.append(litellm_managed_vector_store) + self.config_vector_store_ids.add(vector_store_id) verbose_logger.debug( "all loaded vector stores = %s", @@ -474,6 +476,7 @@ class VectorStoreRegistry: for vector_store in self.vector_stores if vector_store.get("vector_store_id") != vector_store_id ] + self.config_vector_store_ids.discard(vector_store_id) def update_vector_store_in_registry(self, vector_store_id: str, updated_data: LiteLLM_ManagedVectorStore): """Update or add a vector store in the registry""" diff --git a/tests/test_litellm/proxy/vector_store_endpoints/test_vector_store_endpoints.py b/tests/test_litellm/proxy/vector_store_endpoints/test_vector_store_endpoints.py index eae6f90863a..995144783dc 100644 --- a/tests/test_litellm/proxy/vector_store_endpoints/test_vector_store_endpoints.py +++ b/tests/test_litellm/proxy/vector_store_endpoints/test_vector_store_endpoints.py @@ -2002,6 +2002,54 @@ async def test_vector_store_update_and_list_synchronization(): ), "List should return updated data from database" +@pytest.mark.asyncio +async def test_list_vector_stores_keeps_config_defined_store_missing_from_db(): + """ + Stores from config.yaml `vector_store_registry` are only loaded in memory and never written + to the DB. Listing must not treat that DB miss as a deletion and evict them, while a + DB-sourced store that disappeared from the DB is still evicted. + """ + from litellm.proxy.vector_store_endpoints.management_endpoints import list_vector_stores + from litellm.vector_stores.vector_store_registry import VectorStoreRegistry + + registry = VectorStoreRegistry(vector_stores=[]) + registry.load_vector_stores_from_config( + [ + { + "vector_store_name": "config-search-store", + "litellm_params": { + "vector_store_id": "config-search-store", + "custom_llm_provider": "azure_ai", + "api_key": "fake", + "api_base": "https://example.search.windows.net", + }, + } + ] + ) + stale_db_store: LiteLLM_ManagedVectorStore = { + "vector_store_id": "deleted-elsewhere", + "custom_llm_provider": "bedrock", + "litellm_params": {"vector_store_id": "deleted-elsewhere", "custom_llm_provider": "bedrock"}, + } + registry.add_vector_store_to_registry(vector_store=stale_db_store) + + admin = UserAPIKeyAuth(user_role=LitellmUserRoles.PROXY_ADMIN.value, user_id="admin") + with ( + patch.dict("litellm.proxy.proxy_server.general_settings", {}, clear=True), + patch("litellm.proxy.proxy_server.prisma_client", MagicMock()), + patch.object(litellm, "vector_store_registry", registry), + patch( + "litellm.proxy.vector_store_endpoints.management_endpoints.VectorStoreRegistry._get_vector_stores_from_db", + new=AsyncMock(return_value=[]), + ), + ): + response = await list_vector_stores(user_api_key_dict=admin) + + assert [vs.get("vector_store_id") for vs in response["data"]] == ["config-search-store"] + assert registry.get_litellm_managed_vector_store_from_registry_by_name("config-search-store") is not None + assert registry.get_litellm_managed_vector_store_from_registry("deleted-elsewhere") is None + + @pytest.mark.asyncio async def test_resolve_embedding_config_from_db(): """Test that _resolve_embedding_config_from_db correctly resolves embedding config from database."""