From 458463a20dbbe09b61e57af9d4078f80ea76c58e Mon Sep 17 00:00:00 2001 From: tin-berri Date: Fri, 30 Oct 2026 17:08:32 -0700 Subject: [PATCH] fix(search): forward Perplexity search filters (#49344) --- .../llms/perplexity/search/transformation.py | 34 +++------ .../test_perplexity_search_transformation.py | 70 +++++++++++++++++++ 2 files changed, 78 insertions(+), 26 deletions(-) diff --git a/litellm/llms/perplexity/search/transformation.py b/litellm/llms/perplexity/search/transformation.py index 1f3b0fe729d..0921a892849 100644 --- a/litellm/llms/perplexity/search/transformation.py +++ b/litellm/llms/perplexity/search/transformation.py @@ -84,9 +84,9 @@ class PerplexitySearchConfig(BaseSearchConfig): def transform_search_request( self, query: str | list[str], - optional_params: dict, - **kwargs, - ) -> dict: + optional_params: dict[str, object], + **kwargs: object, + ) -> dict[str, object]: """ Transform Search request to Perplexity API format. @@ -98,11 +98,7 @@ class PerplexitySearchConfig(BaseSearchConfig): Args: query: Search query (string or list of strings) - optional_params: Optional parameters for the request - - max_results: Maximum number of search results (1-20) - - search_domain_filter: List of domains to filter (max 20) - - max_tokens_per_page: Max tokens per page (default 1024) - - country: Country code filter (e.g., 'US', 'GB', 'DE') + optional_params: Provider parameters, forwarded unchanged when not None. Returns: Dict with typed request data following PerplexitySearchRequest spec @@ -111,24 +107,10 @@ class PerplexitySearchConfig(BaseSearchConfig): "query": query, } - # Add optional parameters following Perplexity API spec (only if not None) - max_results: Final = optional_params.get("max_results") - if max_results is not None: - request_data["max_results"] = max_results - - search_domain_filter: Final = optional_params.get("search_domain_filter") - if search_domain_filter is not None: - request_data["search_domain_filter"] = search_domain_filter - - max_tokens_per_page: Final = optional_params.get("max_tokens_per_page") - if max_tokens_per_page is not None: - request_data["max_tokens_per_page"] = max_tokens_per_page - - country: Final = optional_params.get("country") - if country is not None: - request_data["country"] = country - - return dict(request_data) + return { + **{key: value for key, value in optional_params.items() if value is not None}, + **request_data, + } def transform_search_response( self, diff --git a/tests/unit/llms/perplexity/search/test_perplexity_search_transformation.py b/tests/unit/llms/perplexity/search/test_perplexity_search_transformation.py index b0f1064967a..caf2fcb4c4b 100644 --- a/tests/unit/llms/perplexity/search/test_perplexity_search_transformation.py +++ b/tests/unit/llms/perplexity/search/test_perplexity_search_transformation.py @@ -72,6 +72,72 @@ def test_search_sends_max_results_in_request_body(respx_mock: respx.MockRouter) assert [result.url for result in response.results] == ["https://example.com/ml"] +@pytest.mark.asyncio +@pytest.mark.parametrize("is_async", [False, True]) +async def test_search_forwards_provider_filters_without_internal_params( + respx_mock: respx.MockRouter, httpx_transport: None, is_async: bool +) -> None: + route: Final = respx_mock.post(PERPLEXITY_SEARCH_URL).respond(json={"results": []}) + filters: Final = { + "search_recency_filter": "week", + "search_after_date_filter": "01/01/2026", + "search_before_date_filter": "10/01/2026", + "last_updated_after_filter": "01/01/2026", + "last_updated_before_filter": "10/01/2026", + "search_language_filter": ["en"], + "search_mode": "academic", + "max_tokens": 2048, + "search_domain_filter": ["europa.eu"], + } + params: Final = { + "query": "AI Act obligations for model providers", + "search_provider": "perplexity", + "metadata": {"private": "local-only"}, + "input_cost_per_query": 0.01, + "_litellm_private_marker": "local-only", + **filters, + } + + if is_async: + response = await litellm.asearch(**params) + else: + response = litellm.search(**params) + + assert response.object == "search" + assert json.loads(route.calls.last.request.content) == { + "query": "AI Act obligations for model providers", + **filters, + } + + +@pytest.mark.parametrize("query", ["original query", ["first query", "second query"]]) +def test_search_request_preserves_query_and_present_values_without_mutating_options(query: str | list[str]) -> None: + optional_params: Final[dict[str, object]] = { + "query": "replacement query", + "search_recency_filter": None, + "search_language_filter": [], + "max_tokens": 0, + "provider_option": {"enabled": False}, + } + original: Final = dict(optional_params) + + body: Final = PerplexitySearchConfig().transform_search_request( + query=query, + optional_params=optional_params, + api_key="transport-only", + api_base="https://transport.invalid", + headers={"Authorization": "transport-only"}, + ) + + assert body == { + "query": query, + "search_language_filter": [], + "max_tokens": 0, + "provider_option": {"enabled": False}, + } + assert optional_params == original + + def _transform(payload: object) -> SearchResponse: logging_obj = Logging( model="perplexity/search", @@ -213,11 +279,15 @@ async def test_router_search_with_search_tools( query="latest AI developments", search_tool_name="litellm-search", max_results=3, + search_recency_filter="week", + search_language_filter=["en"], ) assert json.loads(route.calls.last.request.content) == { "query": "latest AI developments", "max_results": 3, + "search_recency_filter": "week", + "search_language_filter": ["en"], } assert response.object == "search" assert [(result.title, result.url, result.snippet) for result in response.results] == [