From 232b9e8e78ff5b7d14376bf7d72173cb469a05f6 Mon Sep 17 00:00:00 2001 From: milan Date: Thu, 23 Jul 2026 21:04:42 +0000 Subject: [PATCH] fix(rag): consume top-level filters kwarg to avoid duplicate keyword in search Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- litellm/rag/main.py | 3 ++- tests/test_litellm/rag/test_main.py | 35 +++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/litellm/rag/main.py b/litellm/rag/main.py index a1a5bbd38e9..628680e8a62 100644 --- a/litellm/rag/main.py +++ b/litellm/rag/main.py @@ -233,7 +233,8 @@ async def _execute_query_pipeline( raise ValueError("No query found in messages for RAG query") # 2. Search vector store - filters = retrieval_config.get("retrieval_filter") or retrieval_config.get("filters") + kwargs_filters = kwargs.pop("filters", None) + filters = retrieval_config.get("retrieval_filter") or retrieval_config.get("filters") or kwargs_filters with _suppressed_sub_call_billing(): search_response = await litellm.vector_stores.asearch( vector_store_id=retrieval_config["vector_store_id"], diff --git a/tests/test_litellm/rag/test_main.py b/tests/test_litellm/rag/test_main.py index 30ff0b76cd8..796f8c38750 100644 --- a/tests/test_litellm/rag/test_main.py +++ b/tests/test_litellm/rag/test_main.py @@ -301,6 +301,41 @@ async def test_aquery_forwards_retrieval_filter_to_vector_store_search(filter_ke assert fake_search.await_args.kwargs["max_num_results"] == 50 +@pytest.mark.asyncio +async def test_aquery_top_level_filters_kwarg_does_not_collide(): + """ + An SDK caller may pass a top-level `filters` kwarg (it used to flow to the + search via **kwargs). Now that the pipeline passes `filters` explicitly, the + top-level kwarg must be consumed rather than forwarded twice, otherwise + asearch raises TypeError for a duplicate keyword before any search runs. + """ + from litellm.types.vector_stores import VectorStoreSearchResponse + + top_level_filter = {"equals": {"key": "tenant", "value": "a"}} + + fake_search = AsyncMock( + return_value=VectorStoreSearchResponse( + object="vector_store.search_results.page", + search_query="q", + data=[], + ) + ) + + with patch("litellm.vector_stores.asearch", new=fake_search): + response = await litellm.aquery( + model="gpt-4o-mini", + messages=[{"role": "user", "content": "hello"}], + retrieval_config={"vector_store_id": "vs_test_123", "custom_llm_provider": "openai"}, + filters=top_level_filter, + mock_response="hi", + ) + + assert isinstance(response, ModelResponse) + fake_search.assert_awaited_once() + assert fake_search.await_args.kwargs["filters"] == top_level_filter + assert "filters" not in fake_search.await_args.kwargs.get("kwargs", {}) + + @pytest.mark.asyncio async def test_aquery_without_filter_forwards_none(): """