From 07a8077f97ee663799304a96e131f66a5de78ab7 Mon Sep 17 00:00:00 2001 From: sakenuGOD Date: Mon, 20 Apr 2026 19:29:51 +0300 Subject: [PATCH] review(#25947): close the request-path gap Greptile flagged on PR #26118 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit pop_vector_stores_to_run_with_db_fallback applied the same "in memory but not in DB -> evict" reconcile as list_vector_stores. The listing fix surfaced config-declared stores in the UI, but the request path still evicted them on the first inference call, so they appeared visible but silently unusable. Extend the same from_litellm_config guard to the request path: skip the DB round-trip and the eviction branch for config-declared entries — they are authoritative from proxy_config.yaml and will never be in the DB table. Runtime entries keep the old semantics unchanged. Two new tests: - test_pop_vector_stores_preserves_config_loaded_on_db_miss — direct coverage for the gap: config store is returned, stays in the registry, and no DB lookup is issued. - test_pop_vector_stores_still_evicts_runtime_on_db_miss — regression guard: runtime entries without the flag are still evicted, as before. 55 tests passing across the registry and endpoint suites (7 new, 48 pre-existing, no regressions). --- .../vector_stores/vector_store_registry.py | 15 ++- .../test_vector_store_registry.py | 93 ++++++++++++++++++- 2 files changed, 105 insertions(+), 3 deletions(-) diff --git a/litellm/vector_stores/vector_store_registry.py b/litellm/vector_stores/vector_store_registry.py index eb9930cb79e..19efa0b49e5 100644 --- a/litellm/vector_stores/vector_store_registry.py +++ b/litellm/vector_stores/vector_store_registry.py @@ -370,8 +370,19 @@ class VectorStoreRegistry: break # Verify vector store still exists in database (if we have DB access) - # This ensures deleted vector stores are removed from cache - if vector_store is not None and prisma_client is not None: + # This ensures deleted vector stores are removed from cache. + # + # Vector stores declared in proxy_config.yaml are intentionally + # never written to the DB table — verifying them against the DB + # would always miss and they would be evicted on first use, + # making them visible in /vector_store/list (see #25947) but + # silently unreachable during inference. Skip the DB check for + # those entries; they are authoritative from the config file. + if ( + vector_store is not None + and prisma_client is not None + and vector_store.get("from_litellm_config") is not True + ): try: # Check if it still exists in database db_vector_store = await prisma_client.db.litellm_managedvectorstorestable.find_unique( 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 0a86b850e00..de46123624e 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,7 @@ sys.path.insert( ) # Adds the parent directory to the system path from datetime import datetime, timezone -from unittest.mock import MagicMock, patch +from unittest.mock import AsyncMock, MagicMock, patch import litellm from litellm.types.vector_stores import LiteLLM_ManagedVectorStore @@ -229,3 +229,94 @@ def test_add_vector_store_to_registry_does_not_mark_from_litellm_config(): assert len(registry.vector_stores) == 1 assert registry.vector_stores[0].get("from_litellm_config") is not True + + +@pytest.mark.asyncio +async def test_pop_vector_stores_preserves_config_loaded_on_db_miss(): + """ + Request-path regression for #25947 (Greptile follow-up): a + vector store loaded from proxy_config.yaml must still be usable + during inference when it is missing from the DB — the DB-reconcile + eviction that applies to runtime entries must not fire for + config-declared ones, or they get listed in the UI but silently + evicted on first use. + """ + registry = VectorStoreRegistry(vector_stores=[]) + registry.load_vector_stores_from_config( + [ + { + "vector_store_name": "config-demo", + "litellm_params": { + "custom_llm_provider": "pg_vector", + "vector_store_id": "vs-config-path", + }, + } + ] + ) + + prisma = MagicMock() + # Any DB lookup returns "not found" — mimics a proxy running without + # the managed vector_store table populated. + prisma.db.litellm_managedvectorstorestable.find_unique = AsyncMock( + return_value=None + ) + + with patch.object( + registry, + "get_litellm_managed_vector_store_from_registry_or_db", + new=AsyncMock(return_value=None), + ): + result = await registry.pop_vector_stores_to_run_with_db_fallback( + non_default_params={"vector_store_ids": ["vs-config-path"]}, + prisma_client=prisma, + ) + + assert len(result) == 1 + assert result[0]["vector_store_id"] == "vs-config-path" + # Still in the registry — not evicted by the DB-miss reconcile. + ids_after = [vs.get("vector_store_id") for vs in registry.vector_stores] + assert "vs-config-path" in ids_after + # DB path should not have been consulted for a config-declared store — + # we know it will never be there, so asking wastes a round-trip. + prisma.db.litellm_managedvectorstorestable.find_unique.assert_not_called() + + +@pytest.mark.asyncio +async def test_pop_vector_stores_still_evicts_runtime_on_db_miss(): + """ + Regression guard: runtime-inserted entries (no from_litellm_config + flag) must keep the old eviction semantics, otherwise stale cache + after a runtime deletion would never be cleaned up. + """ + from unittest.mock import AsyncMock as _AsyncMock + + registry = VectorStoreRegistry(vector_stores=[]) + registry.add_vector_store_to_registry( + LiteLLM_ManagedVectorStore( + vector_store_id="vs-runtime-gone", + custom_llm_provider="openai", + vector_store_name="runtime-gone", + created_at=datetime.now(timezone.utc), + updated_at=datetime.now(timezone.utc), + ) + ) + + prisma = MagicMock() + prisma.db.litellm_managedvectorstorestable.find_unique = _AsyncMock( + return_value=None + ) + + with patch.object( + registry, + "get_litellm_managed_vector_store_from_registry_or_db", + new=_AsyncMock(return_value=None), + ): + await registry.pop_vector_stores_to_run_with_db_fallback( + non_default_params={"vector_store_ids": ["vs-runtime-gone"]}, + prisma_client=prisma, + ) + + # Evicted from the in-memory registry, as before the fix. + ids_after = [vs.get("vector_store_id") for vs in registry.vector_stores] + assert "vs-runtime-gone" not in ids_after + prisma.db.litellm_managedvectorstorestable.find_unique.assert_called_once()