mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-28 01:32:17 +00:00
Merge acf6a72ada into e26a6450c8
This commit is contained in:
commit
d31d50ec45
2 changed files with 83 additions and 0 deletions
|
|
@ -204,6 +204,17 @@ class SearXNGSearchConfig(BaseSearchConfig):
|
|||
Returns:
|
||||
SearchResponse with standardized format
|
||||
"""
|
||||
if not 200 <= raw_response.status_code < 300:
|
||||
# The HTTP handlers for this provider do not call raise_for_status,
|
||||
# so an upstream 429/503 would otherwise be parsed as a successful
|
||||
# response with zero results. The upstream body is deliberately not
|
||||
# echoed, because it can contain the original query.
|
||||
raise self.get_error_class(
|
||||
error_message=(f"SearXNG search provider returned HTTP {raw_response.status_code}"),
|
||||
status_code=raw_response.status_code,
|
||||
headers=dict(raw_response.headers), # mutable-ok: get_error_class takes a plain dict
|
||||
)
|
||||
|
||||
response_json: Final = raw_response.json()
|
||||
|
||||
# Transform results to SearchResult objects
|
||||
|
|
|
|||
|
|
@ -0,0 +1,72 @@
|
|||
"""
|
||||
Regression tests for HTTP status handling in ``SearXNGSearchConfig``.
|
||||
|
||||
The adapter used to build a ``SearchResponse`` from ``raw_response.json()``
|
||||
without inspecting the status code. A 429 or 503 has no ``results`` key, so the
|
||||
caller received a successful response with zero results — indistinguishable
|
||||
from a query that genuinely matched nothing, which meant fallback between
|
||||
configured search tools never fired.
|
||||
"""
|
||||
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
import httpx
|
||||
import pytest
|
||||
|
||||
from litellm.llms.base_llm.chat.transformation import BaseLLMException
|
||||
from litellm.llms.searxng.search.transformation import SearXNGSearchConfig
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def config() -> SearXNGSearchConfig:
|
||||
return SearXNGSearchConfig()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def logging_obj() -> MagicMock:
|
||||
return MagicMock()
|
||||
|
||||
|
||||
def _response(status_code: int, payload: dict) -> httpx.Response:
|
||||
return httpx.Response(
|
||||
status_code=status_code,
|
||||
json=payload,
|
||||
request=httpx.Request("GET", "https://searxng.example.com/search"),
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("status_code", [429, 503])
|
||||
def test_retryable_status_is_raised_not_parsed_as_empty(
|
||||
config: SearXNGSearchConfig, logging_obj: MagicMock, status_code: int
|
||||
) -> None:
|
||||
"""A rate-limited or unavailable instance must surface as an error."""
|
||||
with pytest.raises(BaseLLMException) as exc_info:
|
||||
config.transform_search_response(
|
||||
raw_response=_response(status_code, {"error": "upstream unavailable"}),
|
||||
logging_obj=logging_obj,
|
||||
)
|
||||
|
||||
assert exc_info.value.status_code == status_code
|
||||
assert str(status_code) in str(exc_info.value)
|
||||
|
||||
|
||||
def test_error_message_does_not_echo_upstream_body(config: SearXNGSearchConfig, logging_obj: MagicMock) -> None:
|
||||
"""The upstream body can echo the original query; keep it out of the error."""
|
||||
with pytest.raises(BaseLLMException) as exc_info:
|
||||
config.transform_search_response(
|
||||
raw_response=_response(500, {"error": "failed searching for confidential-term"}),
|
||||
logging_obj=logging_obj,
|
||||
)
|
||||
|
||||
assert "confidential-term" not in str(exc_info.value)
|
||||
|
||||
|
||||
def test_success_status_still_parses_normally(config: SearXNGSearchConfig, logging_obj: MagicMock) -> None:
|
||||
"""2xx behaviour is unchanged."""
|
||||
response = config.transform_search_response(
|
||||
raw_response=_response(200, {"results": [{"title": "Still works", "url": "https://example.com"}]}),
|
||||
logging_obj=logging_obj,
|
||||
)
|
||||
|
||||
assert len(response.results) == 1
|
||||
assert response.results[0].title == "Still works"
|
||||
Loading…
Add table
Reference in a new issue