feat(search): begin unified start_date/end_date support for search providers

This commit is contained in:
HenryHYeary 2026-09-08 21:16:57 -05:00
parent 9bc9104102
commit bb863a9440
4 changed files with 100 additions and 11 deletions

View file

@ -104,6 +104,8 @@ class LinkupSearchConfig(BaseSearchConfig):
- max_results -> maxResults
- search_domain_filter -> includeDomains
- country -> (not directly supported)
- start_date -> fromDate
- end_date -> toDate
- max_tokens_per_page -> (not applicable)
All other Linkup-specific parameters are passed through as-is.
@ -119,26 +121,33 @@ class LinkupSearchConfig(BaseSearchConfig):
# Linkup only supports single string queries, join with spaces
query = " ".join(query)
# Copy for passthrough data (Done this way to avoid having to change / add to Perplexity unified spec parameters)
remaining = dict(optional_params)
request_data: Final[LinkupSearchRequest] = {
"q": query,
"depth": optional_params.get("depth", "standard"),
"outputType": optional_params.get("outputType", "searchResults"),
"depth": remaining.pop("depth", "standard"),
"outputType": remaining.pop("outputType", "searchResults"),
}
# Transform Perplexity unified spec parameters to Linkup format
if "max_results" in optional_params:
request_data["maxResults"] = optional_params["max_results"]
if "max_results" in remaining:
request_data["maxResults"] = remaining.pop("max_results")
if "search_domain_filter" in optional_params:
request_data["includeDomains"] = optional_params["search_domain_filter"]
if "search_domain_filter" in remaining:
request_data["includeDomains"] = remaining.pop("search_domain_filter")
if "start_date" in remaining:
request_data["fromDate"] = remaining.pop("start_date")
if "end_date" in remaining:
request_data["toDate"] = remaining.pop("end_date")
# Convert to dict before dynamic key assignments
result_data: Final = dict(request_data)
# pass through all other parameters as-is
for param, value in optional_params.items():
if param not in self.get_supported_perplexity_optional_params() and param not in result_data:
result_data[param] = value
# Pass through any unhandled data
result_data.update(remaining)
return result_data
@ -200,4 +209,4 @@ class LinkupSearchConfig(BaseSearchConfig):
return SearchResponse(
results=results,
object="search",
)
)

View file

@ -29,6 +29,8 @@ def _build_search_optional_params(
search_domain_filter: list[str] | None = None,
max_tokens_per_page: int | None = None,
country: str | None = None,
start_date: str | None = None,
end_date: str | None = None,
) -> dict[str, Any]:
"""
Helper function to build optional_params dict from Perplexity Search API parameters.
@ -38,6 +40,8 @@ def _build_search_optional_params(
search_domain_filter: List of domains to filter (max 20)
max_tokens_per_page: Max tokens per page
country: Country code filter
start_date: Start date for results (YYYY-MM-DD)
end_date: End date for results (YYYY-MM-DD)
Returns:
Dict with non-None optional parameters
@ -52,6 +56,10 @@ def _build_search_optional_params(
optional_params["max_tokens_per_page"] = max_tokens_per_page
if country is not None:
optional_params["country"] = country
if start_date is not None:
optional_params["start_date"] = start_date
if end_date is not None:
optional_params["end_date"] = end_date
return optional_params
@ -64,6 +72,8 @@ async def asearch(
search_domain_filter: list[str] | None = None,
max_tokens_per_page: int | None = None,
country: str | None = None,
start_date: str | None = None,
end_date: str | None = None,
api_key: str | None = None,
api_base: str | None = None,
timeout: float | httpx.Timeout | None = None,
@ -80,6 +90,8 @@ async def asearch(
search_domain_filter: Optional list of domains to filter (max 20)
max_tokens_per_page: Optional max tokens per page, default 1024
country: Optional country code filter (e.g., 'US', 'GB', 'DE')
start_date: Optional start date for results (YYYY-MM-DD)
end_date: Optional end date for results (YYYY-MM-DD)
api_key: Optional API key
api_base: Optional API base URL
timeout: Optional timeout
@ -128,6 +140,8 @@ async def asearch(
search_domain_filter=search_domain_filter,
max_tokens_per_page=max_tokens_per_page,
country=country,
start_date=start_date,
end_date=end_date,
api_key=api_key,
api_base=api_base,
timeout=timeout,
@ -167,6 +181,8 @@ def search(
search_domain_filter: list[str] | None = None,
max_tokens_per_page: int | None = None,
country: str | None = None,
start_date: str | None = None,
end_date: str | None = None,
api_key: str | None = None,
api_base: str | None = None,
timeout: float | httpx.Timeout | None = None,
@ -183,6 +199,8 @@ def search(
search_domain_filter: Optional list of domains to filter (max 20)
max_tokens_per_page: Optional max tokens per page, default 1024
country: Optional country code filter (e.g., 'US', 'GB', 'DE')
start_date: Optional start date for results (YYYY-MM-DD)
end_date: Optional end date for results (YYYY-MM-DD)
api_key: Optional API key
api_base: Optional API base URL
timeout: Optional timeout
@ -255,6 +273,8 @@ def search(
search_domain_filter=search_domain_filter,
max_tokens_per_page=max_tokens_per_page,
country=country,
start_date=start_date,
end_date=end_date,
)
# Filter out internal LiteLLM parameters from kwargs

View file

@ -0,0 +1,52 @@
import json
from unittest.mock import Mock
import pytest
from litellm.llms.linkup.search.transformation import LinkupSearchConfig
def _config() -> LinkupSearchConfig:
return LinkupSearchConfig()
def test_transform_search_request_maps_start_date_to_from_date():
data = _config().transform_search_request("q", {"start_date": "1999-03-20"})
assert data["fromDate"] == "1999-03-20"
assert "start_date" not in data
def test_transform_search_request_maps_end_date_to_to_date():
data = _config().transform_search_request("q", {"end_date": "1999-04-20"})
assert data["toDate"] == "1999-04-20"
assert "end_date" not in data
def test_transform_search_request_date_range_together():
data = _config().transform_search_request(
"q", {"start_date": "1999-03-20", "end_date": "1999-04-20"}
)
assert data["fromDate"] == "1999-03-20"
assert data["toDate"] == "1999-04-20"
def test_transform_search_request_without_dates_omits_both():
data = _config().transform_search_request("q", {"max_results": 5})
assert "fromDate" not in data
assert "toDate" not in data
def test_transform_search_request_passes_through_unhandled_kwargs():
"""A param this function doesn't explicitly handle should still reach Linkup unchanged."""
data = _config().transform_search_request("q", {"includeImages": True})
assert data["includeImages"] is True
def test_transform_search_request_joins_list_query():
assert _config().transform_search_request(["foo", "bar"], {})["q"] == "foo bar"
def test_transform_search_request_defaults_depth_and_output_type():
data = _config().transform_search_request("q", {})
assert data["depth"] == "standard"
assert data["outputType"] == "searchResults"

View file

@ -100,6 +100,14 @@ def test_transform_search_request_max_results_is_not_clamped():
def test_transform_search_request_uppercases_country():
assert _config().transform_search_request("q", {"country": "us"})["country"] == "US"
def test_transform_search_request_date_range_passes_through_native_names():
"""Nimble already uses the unified spec's own field names for date filtering."""
data = _config().transform_search_request(
"q", {"start_date": "1999-03-20", "end_date": "1999-04-20"}
)
assert data["start_date"] == "1999-03-20"
assert data["end_date"] == "1999-04-20"
def test_transform_search_request_drops_max_tokens_per_page():
assert "max_tokens_per_page" not in _config().transform_search_request("q", {"max_tokens_per_page": 1024})