From 2851271f3472a1e9457b3190bb870e069580db29 Mon Sep 17 00:00:00 2001 From: Priyansh Nandwana Date: Sun, 30 Aug 2026 10:47:56 +0530 Subject: [PATCH] fix(vector_stores): stop sharing one list across default-constructed registries VectorStoreIndexRegistry and VectorStoreRegistry took a mutable list as their default argument. Python evaluates that once at class definition, so every registry built without arguments shared one list. The proxy builds one that way during startup, so an upsert into it leaked into every registry created after, and into the class default itself. Default to None and build a fresh list per instance. A caller-supplied list is still used as given, including an empty one, so nothing that passes its own list changes behaviour. Fixes #38874 --- .../vector_stores/vector_store_registry.py | 10 +++-- .../test_vector_store_registry.py | 40 ++++++++++++++++++- 2 files changed, 45 insertions(+), 5 deletions(-) diff --git a/litellm/vector_stores/vector_store_registry.py b/litellm/vector_stores/vector_store_registry.py index b71d6784873..40551894d46 100644 --- a/litellm/vector_stores/vector_store_registry.py +++ b/litellm/vector_stores/vector_store_registry.py @@ -32,8 +32,10 @@ else: class VectorStoreIndexRegistry: - def __init__(self, vector_store_indexes: list[LiteLLM_ManagedVectorStoreIndex] = []): - self.vector_store_indexes: list[LiteLLM_ManagedVectorStoreIndex] = vector_store_indexes + def __init__(self, vector_store_indexes: list[LiteLLM_ManagedVectorStoreIndex] | None = None): + self.vector_store_indexes: list[LiteLLM_ManagedVectorStoreIndex] = ( + vector_store_indexes if vector_store_indexes is not None else [] + ) def get_vector_store_indexes(self) -> list[LiteLLM_ManagedVectorStoreIndex]: """ @@ -101,8 +103,8 @@ 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): + self.vector_stores: list[LiteLLM_ManagedVectorStore] = vector_stores if vector_stores is not None else [] self.vector_store_ids_to_vector_store_map: dict[str, LiteLLM_ManagedVectorStore] = {} def _extract_tool_params(self, tool: dict) -> VectorStoreToolParams: diff --git a/tests/test_litellm/vector_stores/test_vector_store_registry.py b/tests/test_litellm/vector_stores/test_vector_store_registry.py index f19c3706845..ab274b41e5a 100644 --- a/tests/test_litellm/vector_stores/test_vector_store_registry.py +++ b/tests/test_litellm/vector_stores/test_vector_store_registry.py @@ -13,7 +13,10 @@ from unittest.mock import MagicMock import litellm from litellm.types.vector_stores import LiteLLM_ManagedVectorStore from litellm.vector_stores.main import search -from litellm.vector_stores.vector_store_registry import VectorStoreRegistry +from litellm.vector_stores.vector_store_registry import ( + VectorStoreIndexRegistry, + VectorStoreRegistry, +) @pytest.fixture(autouse=True) @@ -182,3 +185,38 @@ def test_search_uses_registry_credentials(): assert getattr(called_params, "aws_region_name") == "us-east-1" finally: litellm.vector_store_registry = original_registry + + +class TestRegistriesDoNotShareDefaultState: + """#38874: a mutable default argument made every default-constructed registry share + one list, so upserting into one polluted every other instance and the class default.""" + + def test_vector_store_registries_do_not_share_a_list(self): + first = VectorStoreRegistry() + second = VectorStoreRegistry() + assert first.vector_stores is not second.vector_stores + + def test_index_registries_do_not_share_a_list(self): + first = VectorStoreIndexRegistry() + second = VectorStoreIndexRegistry() + assert first.vector_store_indexes is not second.vector_store_indexes + + def test_mutating_one_registry_leaves_the_next_one_empty(self): + first = VectorStoreRegistry() + first.vector_stores.append( + LiteLLM_ManagedVectorStore(vector_store_id="vs-leak", custom_llm_provider="bedrock") + ) + assert VectorStoreRegistry().vector_stores == [] + + def test_mutating_one_index_registry_leaves_the_next_one_empty(self): + first = VectorStoreIndexRegistry() + first.vector_store_indexes.append({"index_name": "leak"}) + assert VectorStoreIndexRegistry().get_vector_store_indexes() == [] + + def test_a_supplied_list_is_still_used_as_given(self): + supplied = [LiteLLM_ManagedVectorStore(vector_store_id="vs-1", custom_llm_provider="bedrock")] + assert VectorStoreRegistry(vector_stores=supplied).vector_stores is supplied + + def test_a_supplied_empty_list_is_still_used_as_given(self): + supplied: list = [] + assert VectorStoreRegistry(vector_stores=supplied).vector_stores is supplied