diff --git a/litellm/vector_stores/vector_store_registry.py b/litellm/vector_stores/vector_store_registry.py index 2a7a0b7edd4..2c875eb5e46 100644 --- a/litellm/vector_stores/vector_store_registry.py +++ b/litellm/vector_stores/vector_store_registry.py @@ -94,8 +94,12 @@ class VectorStoreIndexRegistry: class VectorStoreRegistry: - def __init__(self, vector_stores: list[LiteLLM_ManagedVectorStore] = []): - self.vector_stores: list[LiteLLM_ManagedVectorStore] = vector_stores + def __init__(self, vector_stores: list[LiteLLM_ManagedVectorStore] | None = None): + # Never bind a mutable default: the previous `= []` default caused every + # VectorStoreRegistry() with no argument to share the same list, so a + # mutation in one place (test, request handler, etc.) leaked into every + # other caller. + self.vector_stores: list[LiteLLM_ManagedVectorStore] = list(vector_stores) if vector_stores is not None else [] self.vector_store_ids_to_vector_store_map: dict[str, LiteLLM_ManagedVectorStore] = {} # IDs of vector stores that were loaded from config.yaml / kustomization. # These have no DB row, so reconciliation code paths that treat "in memory @@ -400,8 +404,7 @@ class VectorStoreRegistry: list-endpoint reconciliation on the very next reload. """ new_config_ids: set[str] = { - (cfg.get("litellm_params") or {}).get("vector_store_id") - for cfg in vector_stores_config + (cfg.get("litellm_params") or {}).get("vector_store_id") for cfg in vector_stores_config } new_config_ids.discard(None) # ids missing here will re-raise below with a clearer error 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 905928428b7..49ca2db5059 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 @@ -3107,3 +3107,65 @@ class TestAzureAIAnalyzeNamedIndexClassification: user_api_key_dict=self._team_member("analyze", ["read"]), ) assert result is True + + +# --------------------------------------------------------------------------- +# /vector_store/list — config-sourced entries must survive DB reconciliation. +# Regression test for: config.yaml-loaded stores were pruned from the +# in-memory registry on every list call because they had no DB row. +# --------------------------------------------------------------------------- + + +@pytest.mark.asyncio +async def test_list_vector_stores_returns_config_sourced_and_leaves_registry_intact(): + from litellm.proxy.vector_store_endpoints.management_endpoints import list_vector_stores + from litellm.vector_stores.vector_store_registry import VectorStoreRegistry + + registry = VectorStoreRegistry() + registry.load_vector_stores_from_config( + [ + { + "vector_store_name": "docs-kb", + "litellm_params": { + "vector_store_id": "vs_from_config", + "custom_llm_provider": "bedrock", + }, + } + ] + ) + + original_registry = litellm.vector_store_registry + try: + litellm.vector_store_registry = registry + + with ( + # Empty DB — the store exists only in config/memory. + patch( + "litellm.vector_stores.vector_store_registry.VectorStoreRegistry._get_vector_stores_from_db", + new=AsyncMock(return_value=[]), + ), + patch("litellm.proxy.proxy_server.prisma_client", MagicMock()), + patch( + "litellm.proxy.vector_store_endpoints.management_endpoints._check_vector_store_access", + new=AsyncMock(return_value=True), + ), + patch( + "litellm.proxy.vector_store_endpoints.management_endpoints.check_feature_access_for_user", + new=AsyncMock(return_value=None), + ), + ): + response = await list_vector_stores( + user_api_key_dict=UserAPIKeyAuth( + token="sk-test", + key_name="sk-...test", + user_role=LitellmUserRoles.PROXY_ADMIN, + ), + ) + + returned_ids = [vs["vector_store_id"] for vs in response.data] + assert "vs_from_config" in returned_ids + # And, critically, the registry must NOT have been mutated — + # config-loaded stores stay resident for downstream routing. + assert any(vs.get("vector_store_id") == "vs_from_config" for vs in registry.vector_stores) + finally: + litellm.vector_store_registry = original_registry