fix(vector-stores): fall back to annotate when the failure mode is unrecognized

litellm_settings keys are set on the litellm module with no allowlist, so a typo
in vector_store_search_failure_mode reached assert_never and turned every
vector-store request into a 500. Validate the configured value and fall back to
the permissive default with a warning naming the supported modes.
This commit is contained in:
mateo-berri 2026-09-03 01:23:28 -07:00
parent 6499ca349f
commit e464e749e0
2 changed files with 49 additions and 2 deletions

View file

@ -7,8 +7,9 @@ It searches the vector store for relevant context and appends it to the messages
from collections.abc import Awaitable, Callable, Mapping, Sequence
from dataclasses import dataclass
from typing import TYPE_CHECKING, Any, Final, Protocol, cast
from typing import TYPE_CHECKING, Any, Final, Protocol, cast, get_args
from pydantic import TypeAdapter, ValidationError
from typing_extensions import assert_never
import litellm
@ -22,6 +23,7 @@ from litellm.types.utils import CallTypes, StandardCallbackDynamicParams
from litellm.types.vector_stores import (
LiteLLM_ManagedVectorStore,
VectorStoreSearchFailure,
VectorStoreSearchFailureMode,
VectorStoreSearchResponse,
VectorStoreSearchResult,
)
@ -34,6 +36,8 @@ else:
LiteLLMLoggingObj = Any
SEARCH_FAILURES_FIELD: Final = "vector_store_search_failures"
_DEFAULT_FAILURE_MODE: Final[VectorStoreSearchFailureMode] = "annotate"
_FAILURE_MODE_ADAPTER: Final = TypeAdapter(VectorStoreSearchFailureMode)
class ProxyRuntime(Protocol):
@ -153,7 +157,7 @@ class VectorStorePreCallHook(CustomLogger):
litellm_logging_obj.model_call_details[detail] = value
if augmentation.failures:
match litellm.vector_store_search_failure_mode:
match _configured_failure_mode():
case "error":
raise VectorStoreSearchError(failures=augmentation.failures, model=model)
case "annotate":
@ -435,3 +439,16 @@ def _requested_vector_store_ids(non_default_params: Mapping[str, object]) -> tup
if not isinstance(requested, (list, tuple)):
return ()
return tuple(str(vector_store_id) for vector_store_id in requested)
def _configured_failure_mode() -> VectorStoreSearchFailureMode:
try:
return _FAILURE_MODE_ADAPTER.validate_python(litellm.vector_store_search_failure_mode)
except ValidationError:
verbose_logger.warning(
"Unsupported vector_store_search_failure_mode=%r, falling back to %r. Supported modes: %s",
litellm.vector_store_search_failure_mode,
_DEFAULT_FAILURE_MODE,
", ".join(get_args(VectorStoreSearchFailureMode)),
)
return _DEFAULT_FAILURE_MODE

View file

@ -425,6 +425,36 @@ async def test_error_mode_fails_the_request_instead_of_answering_without_the_kno
assert "vs-broken: litellm.BadRequestError: no healthy deployments for vs-broken" in raised.value.message
@pytest.mark.asyncio
async def test_a_misspelled_failure_mode_annotates_instead_of_erroring_the_request(
registry_with: RegisterStores,
monkeypatch: pytest.MonkeyPatch,
warnings: list[logging.LogRecord],
) -> None:
"""Regression (LIT-6809): litellm_settings takes any value, so a typo must not become a 500."""
registry_with("vs-broken")
monkeypatch.setattr(litellm, "vector_store_search_failure_mode", "erorr")
logging_obj = FakeLoggingObj({})
_, messages, _ = await _run_hook(
VectorStorePreCallHook(
proxy_runtime=FakeProxyRuntime(router=RecordingRouter(failing_vector_store_ids=frozenset({"vs-broken"})))
),
["vs-broken"],
logging_obj,
)
assert messages[0]["content"] == "what is litellm?"
assert logging_obj.model_call_details["vector_store_search_failures"] == (
{
"vector_store_id": "vs-broken",
"custom_llm_provider": "bedrock",
"error": "litellm.BadRequestError: no healthy deployments for vs-broken",
},
)
assert any("erorr" in record.getMessage() for record in warnings)
@pytest.mark.asyncio
async def test_error_mode_leaves_a_fully_healthy_request_alone(
registry_with: RegisterStores,