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
This commit is contained in:
Priyansh Nandwana 2026-08-30 10:47:56 +05:30
parent 98d46ee59d
commit 2851271f34
2 changed files with 45 additions and 5 deletions

View file

@ -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:

View file

@ -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