From 2079b2e29c37a3788463d675e355dcf28a932bee Mon Sep 17 00:00:00 2001 From: Yujong Lee Date: Sat, 5 Sep 2026 08:28:02 -0700 Subject: [PATCH] fix(vector-stores): reject searches without a query --- .../proxy/vector_store_endpoints/endpoints.py | 2 ++ .../llm_translation/test_vector_stores_e2e.py | 23 +++++-------------- .../test_vector_store_endpoints.py | 20 ++++++++++++++++ 3 files changed, 28 insertions(+), 17 deletions(-) diff --git a/litellm/proxy/vector_store_endpoints/endpoints.py b/litellm/proxy/vector_store_endpoints/endpoints.py index 460dab0aad3..70055201d68 100644 --- a/litellm/proxy/vector_store_endpoints/endpoints.py +++ b/litellm/proxy/vector_store_endpoints/endpoints.py @@ -159,6 +159,8 @@ async def vector_store_search( ) data = await _read_request_body(request=request) + if "query" not in data: + raise HTTPException(status_code=400, detail={"error": "query is required"}) reject_caller_embedding_selection_params(payload=data, source="the search request body") data["vector_store_id"] = vector_store_id diff --git a/tests/e2e/llm_translation/test_vector_stores_e2e.py b/tests/e2e/llm_translation/test_vector_stores_e2e.py index 71015d28d9f..068a3896205 100644 --- a/tests/e2e/llm_translation/test_vector_stores_e2e.py +++ b/tests/e2e/llm_translation/test_vector_stores_e2e.py @@ -200,27 +200,16 @@ class TestVectorStores: assert deleted.id == created.id assert deleted.deleted is True - @pytest.mark.skip( - reason="stage red: product gap, vector store search 500s (asearch TypeError) on missing query instead of 400" - ) + @pytest.mark.parametrize("prefix", ["/v1", ""]) @pytest.mark.covers("llm.vector_stores.openai.input_validation.nonstream.works") - def test_search_missing_query_returns_error(self, proxy: ProxyClient, resources: ResourceManager) -> None: - key = resources.key() - created = unwrap( - proxy.transport.post( - "/v1/vector_stores", - headers=proxy.transport.bearer(key), - json=VectorStoreCreateBody(name=f"e2e-vs-search-{unique_marker()}"), - response_type=VectorStoreObject, - ) - ) - _delete_store_later(proxy, resources, key, created.id) + def test_search_missing_query_returns_error(self, proxy: ProxyClient, scoped_key: str, prefix: str) -> None: result = proxy.transport.send( - f"/v1/vector_stores/{created.id}/search", - headers=proxy.transport.bearer(key), + f"{prefix}/vector_stores/vs_query_validation/search", + headers=proxy.transport.bearer(scoped_key), json=VectorStoreSearchBody(max_num_results=10), ) - assert_client_error(result, "vector store search missing query") + assert result.status_code == 400, result.body + assert "query is required" in result.body @pytest.mark.covers("llm.vector_stores.openai.basic.nonstream.works") def test_file_attach_poll_and_search(self, proxy: ProxyClient, resources: ResourceManager) -> None: 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 1bd267db510..ff4094f9c83 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 @@ -1,5 +1,6 @@ import json from datetime import datetime, timezone +from typing import Final from unittest.mock import AsyncMock, MagicMock, patch import pytest @@ -3556,3 +3557,22 @@ def test_vector_store_search_rejects_caller_embedding_selection_params(blocked_k assert response.status_code == 400, response.json() assert blocked_key in str(response.json()) + + +@pytest.mark.parametrize("prefix", ["/v1", ""]) +def test_vector_store_search_missing_query_returns_400(prefix: str) -> None: + from fastapi import FastAPI + from fastapi.testclient import TestClient + + from litellm.proxy.auth.user_api_key_auth import user_api_key_auth + from litellm.proxy.vector_store_endpoints.endpoints import router + + app: Final = FastAPI() + app.include_router(router) + app.dependency_overrides[user_api_key_auth] = lambda: UserAPIKeyAuth(user_role=LitellmUserRoles.PROXY_ADMIN) + response: Final = TestClient(app, raise_server_exceptions=False).post( + f"{prefix}/vector_stores/documents/search", json={"max_num_results": 2} + ) + + assert response.status_code == 400, response.text + assert "query" in response.text