fix(vector-stores): raise BadRequestError (400) for invalid Vertex Search extra_body

Rejecting unsupported or target-selecting extra_body fields previously
raised a bare ValueError, which the vector store error path mapped to a
generic APIConnectionError (HTTP 500). Raise litellm.BadRequestError so
invalid per-request input surfaces as HTTP 400 with a clear message.
This commit is contained in:
ryan-crabbe-berri 2026-06-02 12:05:16 -07:00
parent 6037ac7668
commit d4f515e3f2
2 changed files with 35 additions and 17 deletions

View file

@ -3,6 +3,7 @@ from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple, Union
import httpx
from litellm import get_model_info
from litellm.exceptions import BadRequestError
from litellm.litellm_core_utils.url_utils import encode_url_path_segment
from litellm.llms.base_llm.vector_store.transformation import BaseVectorStoreConfig
from litellm.llms.vertex_ai.vertex_llm_base import VertexBase
@ -85,11 +86,12 @@ class VertexSearchAPIVectorStoreConfig(BaseVectorStoreConfig, VertexBase):
Validate ``extra_body`` against the supported-field allowlist for the
active serving config (engine/app vs data store).
Raises ``ValueError`` if the caller includes a target-selecting field
(e.g. ``servingConfig``) or any field not supported for the active mode,
so the request fails loudly instead of silently searching the wrong
target. Engine-only fields (``dataStoreSpecs``, ``numResultsPerDataStore``)
are rejected in data-store mode where they are meaningless.
Raises ``BadRequestError`` (HTTP 400) if the caller includes a
target-selecting field (e.g. ``servingConfig``) or any field not
supported for the active mode, so the request fails loudly instead of
silently searching the wrong target. Engine-only fields
(``dataStoreSpecs``, ``numResultsPerDataStore``) are rejected in
data-store mode where they are meaningless.
"""
supported = cls.get_supported_extra_body_fields(is_engine=is_engine)
filtered = {
@ -98,18 +100,26 @@ class VertexSearchAPIVectorStoreConfig(BaseVectorStoreConfig, VertexBase):
target_selecting = set(filtered) & VERTEX_SEARCH_TARGET_SELECTING_FIELDS
if target_selecting:
raise ValueError(
"Vertex AI Search extra_body may not set target-selecting fields "
f"{sorted(target_selecting)}: the data store is scoped by "
"vector_store_id / vertex_engine_id and cannot be overridden per request."
raise BadRequestError(
message=(
"Vertex AI Search extra_body may not set target-selecting fields "
f"{sorted(target_selecting)}: the data store is scoped by "
"vector_store_id / vertex_engine_id and cannot be overridden per request."
),
model="vertex_ai/search_api",
llm_provider="vertex_ai",
)
unsupported = set(filtered) - supported
if unsupported:
mode = "engine/app" if is_engine else "data store"
raise ValueError(
f"Unsupported Vertex AI Search extra_body fields {sorted(unsupported)} "
f"for {mode} mode. Supported fields: {sorted(supported)}."
raise BadRequestError(
message=(
f"Unsupported Vertex AI Search extra_body fields {sorted(unsupported)} "
f"for {mode} mode. Supported fields: {sorted(supported)}."
),
model="vertex_ai/search_api",
llm_provider="vertex_ai",
)
return filtered

View file

@ -2,6 +2,7 @@ from types import SimpleNamespace
import pytest
from litellm.exceptions import BadRequestError
from litellm.llms.vertex_ai.vector_stores.search_api.transformation import (
VertexSearchAPIVectorStoreConfig,
)
@ -209,32 +210,39 @@ def test_engine_search_request_forwards_num_results_per_data_store():
def test_datastore_search_request_rejects_datastorespecs():
specs = [{"dataStore": "projects/p/.../dataStores/ds-beta"}]
with pytest.raises(ValueError, match="data store mode"):
with pytest.raises(BadRequestError, match="data store mode"):
_datastore_search_request(extra_body={"dataStoreSpecs": specs})
def test_datastore_search_request_rejects_num_results_per_data_store():
with pytest.raises(ValueError, match="data store mode"):
with pytest.raises(BadRequestError, match="data store mode"):
_datastore_search_request(extra_body={"numResultsPerDataStore": 3})
@pytest.mark.parametrize("field", ["branch", "servingConfig", "entity"])
def test_search_request_rejects_target_selecting_fields(field):
with pytest.raises(ValueError, match="target-selecting"):
with pytest.raises(BadRequestError, match="target-selecting"):
_search_request(extra_body={field: "x"})
@pytest.mark.parametrize("field", ["branch", "servingConfig", "entity"])
def test_datastore_search_request_rejects_target_selecting_fields(field):
with pytest.raises(ValueError, match="target-selecting"):
with pytest.raises(BadRequestError, match="target-selecting"):
_datastore_search_request(extra_body={field: "x"})
def test_search_request_rejects_unsupported_extra_body_field():
with pytest.raises(ValueError, match="Unsupported Vertex AI Search extra_body"):
with pytest.raises(BadRequestError, match="Unsupported Vertex AI Search extra_body"):
_search_request(extra_body={"notARealField": True})
def test_rejected_extra_body_raises_http_400():
with pytest.raises(BadRequestError) as exc_info:
_search_request(extra_body={"notARealField": True})
assert exc_info.value.status_code == 400
def test_search_request_forwards_supported_extra_body_fields():
_, body = _search_request(
extra_body={