[Feat] add serxng search API provider (#16259)
* TestFirecrawlSearch * add SearchProviders * add to get_provider_search_config * add FirecrawlSearchConfig * add FirecrawlSearchRequest * add firecrawl API docs * add pricing firecrawl/search * add new search APIs * add SearXNGSearchConfig * add searxng/search * add serxng params * TestSearXNGSearch * docs serxng * docs fix * docs fix * docs serxng
This commit is contained in:
parent
af78a93ecf
commit
60f3a3b0ad
@ -2,7 +2,7 @@
|
||||
|
||||
| Feature | Supported |
|
||||
|---------|-----------|
|
||||
| Supported Providers | `perplexity`, `tavily`, `parallel_ai`, `exa_ai`, `google_pse`, `dataforseo`, `firecrawl` |
|
||||
| Supported Providers | `perplexity`, `tavily`, `parallel_ai`, `exa_ai`, `google_pse`, `dataforseo`, `firecrawl`, `searxng` |
|
||||
| Cost Tracking | ✅ |
|
||||
| Logging | ✅ |
|
||||
| Load Balancing | ❌ |
|
||||
@ -205,7 +205,7 @@ See the [official Perplexity Search documentation](https://docs.perplexity.ai/ap
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|------|----------|-------------|
|
||||
| `query` | string or array | Yes | Search query. Can be a single string or array of strings |
|
||||
| `search_provider` | string | Yes (SDK) | The search provider to use: `"perplexity"`, `"tavily"`, `"parallel_ai"`, `"exa_ai"`, `"google_pse"`, `"dataforseo"`, or `"firecrawl"` |
|
||||
| `search_provider` | string | Yes (SDK) | The search provider to use: `"perplexity"`, `"tavily"`, `"parallel_ai"`, `"exa_ai"`, `"google_pse"`, `"dataforseo"`, `"firecrawl"`, or `"searxng"` |
|
||||
| `search_tool_name` | string | Yes (Proxy) | Name of the search tool configured in `config.yaml` |
|
||||
| `max_results` | integer | No | Maximum number of results to return (1-20). Default: 10 |
|
||||
| `search_domain_filter` | array | No | List of domains to filter results (max 20 domains) |
|
||||
@ -268,6 +268,7 @@ The response follows Perplexity's search format with the following structure:
|
||||
| Google PSE | `GOOGLE_PSE_API_KEY`, `GOOGLE_PSE_ENGINE_ID` | `google_pse` |
|
||||
| DataForSEO | `DATAFORSEO_LOGIN`, `DATAFORSEO_PASSWORD` | `dataforseo` |
|
||||
| Firecrawl | `FIRECRAWL_API_KEY` | `firecrawl` |
|
||||
| SearXNG | `SEARXNG_API_BASE` (required) | `searxng` |
|
||||
|
||||
See the individual provider documentation for detailed setup instructions and provider-specific parameters.
|
||||
|
||||
|
||||
318
docs/my-website/docs/search/searxng.md
Normal file
318
docs/my-website/docs/search/searxng.md
Normal file
@ -0,0 +1,318 @@
|
||||
# SearXNG Search
|
||||
|
||||
**Open Source:** [https://github.com/searxng/searxng](https://github.com/searxng/searxng)
|
||||
|
||||
**Public Instances:** [https://searx.space/](https://searx.space/)
|
||||
|
||||
## Overview
|
||||
|
||||
SearXNG is a free, open-source metasearch engine that aggregates results from multiple search engines while protecting user privacy. It can be self-hosted or used via public instances.
|
||||
|
||||
**Note:** SearXNG returns a fixed number of results per page (~20 by default) and does not support limiting results via the API. The `max_results` parameter is not directly supported by SearXNG.
|
||||
|
||||
## LiteLLM Python SDK
|
||||
|
||||
```python showLineNumbers title="SearXNG Search"
|
||||
import os
|
||||
from litellm import search
|
||||
|
||||
# Set your SearXNG instance URL (REQUIRED)
|
||||
os.environ["SEARXNG_API_BASE"] = "https://serxng-deployment-production.up.railway.app"
|
||||
|
||||
response = search(
|
||||
query="latest AI developments",
|
||||
search_provider="searxng",
|
||||
max_results=10
|
||||
)
|
||||
```
|
||||
|
||||
## LiteLLM AI Gateway
|
||||
|
||||
### 1. Setup config.yaml
|
||||
|
||||
```yaml showLineNumbers title="config.yaml"
|
||||
model_list:
|
||||
- model_name: gpt-4
|
||||
litellm_params:
|
||||
model: gpt-4
|
||||
api_key: os.environ/OPENAI_API_KEY
|
||||
|
||||
search_tools:
|
||||
- search_tool_name: searxng-search
|
||||
litellm_params:
|
||||
search_provider: searxng
|
||||
api_base: https://serxng-deployment-production.up.railway.app
|
||||
```
|
||||
|
||||
### 2. Start the proxy
|
||||
|
||||
```bash
|
||||
litellm --config /path/to/config.yaml
|
||||
|
||||
# RUNNING on http://0.0.0.0:4000
|
||||
```
|
||||
|
||||
### 3. Test the search endpoint
|
||||
|
||||
```bash showLineNumbers title="Test Request"
|
||||
curl http://0.0.0.0:4000/v1/search/searxng-search \
|
||||
-H "Authorization: Bearer sk-1234" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"query": "latest AI developments",
|
||||
"max_results": 10
|
||||
}'
|
||||
```
|
||||
|
||||
## Provider-specific Parameters
|
||||
|
||||
```python showLineNumbers title="SearXNG Search with Provider-specific Parameters"
|
||||
import os
|
||||
from litellm import search
|
||||
|
||||
# REQUIRED: Set your SearXNG instance URL
|
||||
os.environ["SEARXNG_API_BASE"] = "https://serxng-deployment-production.up.railway.app"
|
||||
|
||||
response = search(
|
||||
query="machine learning research",
|
||||
search_provider="searxng",
|
||||
max_results=10,
|
||||
# SearXNG-specific parameters
|
||||
categories="general,science", # Comma-separated categories
|
||||
engines="google,duckduckgo,bing", # Comma-separated engines
|
||||
language="en", # Language code
|
||||
pageno=1, # Page number
|
||||
time_range="month" # Time filter: day, month, year
|
||||
)
|
||||
```
|
||||
|
||||
## Features
|
||||
|
||||
SearXNG provides powerful metasearch capabilities:
|
||||
|
||||
### Multiple Search Engines
|
||||
Aggregate results from multiple search engines simultaneously:
|
||||
- Google, DuckDuckGo, Bing, Brave
|
||||
- Wikipedia, Startpage
|
||||
- And many more
|
||||
|
||||
### Categories
|
||||
Search within specific categories:
|
||||
- `general` - General web search
|
||||
- `science` - Scientific articles and papers
|
||||
- `images` - Image search
|
||||
- `news` - News articles
|
||||
- `videos` - Video content
|
||||
- `music` - Music and audio
|
||||
- `files` - File search
|
||||
- `it` - IT and technology
|
||||
- `map` - Maps and location
|
||||
|
||||
### Time-Based Filtering
|
||||
Filter results by time range:
|
||||
- `day` - Past day
|
||||
- `month` - Past month
|
||||
- `year` - Past year
|
||||
|
||||
### Privacy-Focused
|
||||
- No user tracking
|
||||
- No cookies required
|
||||
- No profiling
|
||||
- No ads
|
||||
|
||||
### Language Support
|
||||
Support for 60+ languages with the `language` parameter.
|
||||
|
||||
## Self-Hosting
|
||||
|
||||
SearXNG can be self-hosted for complete control.
|
||||
|
||||
### Quick Deploy
|
||||
|
||||
Use our pre-configured deployment repository for easy setup:
|
||||
|
||||
**[Fork and Deploy: github.com/BerriAI/serxng-deployment](https://github.com/BerriAI/serxng-deployment)**
|
||||
|
||||
This repository includes:
|
||||
- Docker and Docker Compose setup
|
||||
- JSON API format pre-configured
|
||||
- Ready to deploy
|
||||
|
||||
### Manual Installation
|
||||
|
||||
See the [official SearXNG installation instructions](https://docs.searxng.org/admin/installation.html) for detailed setup.
|
||||
|
||||
**Important:** When you install SearXNG, the only active output format by default is the HTML format. You need to activate the JSON format to use the API.
|
||||
|
||||
Add the following to your `settings.yml` file:
|
||||
|
||||
```yaml
|
||||
search:
|
||||
formats:
|
||||
- html
|
||||
- json
|
||||
```
|
||||
|
||||
Then restart SearXNG:
|
||||
|
||||
```bash
|
||||
# Using Docker
|
||||
docker run -d -p 8080:8080 \
|
||||
-v $(pwd)/settings.yml:/etc/searxng/settings.yml:ro \
|
||||
-e SEARXNG_BASE_URL=http://localhost:8080 \
|
||||
searxng/searxng
|
||||
|
||||
# Then configure LiteLLM to use your instance
|
||||
export SEARXNG_API_BASE=http://localhost:8080
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
### Setting API Base URL (Required)
|
||||
|
||||
You **must** specify a SearXNG instance URL either via environment variable or in the search call:
|
||||
|
||||
```python
|
||||
# Option 1: Environment variable (Recommended)
|
||||
import os
|
||||
os.environ["SEARXNG_API_BASE"] = "https://your-instance.com"
|
||||
|
||||
response = search(
|
||||
query="AI developments",
|
||||
search_provider="searxng"
|
||||
)
|
||||
|
||||
# Option 2: Pass directly in search call
|
||||
response = search(
|
||||
query="AI developments",
|
||||
search_provider="searxng",
|
||||
api_base="https://your-instance.com"
|
||||
)
|
||||
```
|
||||
|
||||
**Note:** There is no default instance URL. You must choose either a [public instance](https://searx.space/) or self-host your own.
|
||||
|
||||
### Optional Authentication
|
||||
|
||||
Some SearXNG instances may require authentication:
|
||||
|
||||
```python
|
||||
import os
|
||||
|
||||
# Set API key if required
|
||||
os.environ["SEARXNG_API_KEY"] = "your-api-key"
|
||||
|
||||
response = search(
|
||||
query="AI developments",
|
||||
search_provider="searxng"
|
||||
)
|
||||
```
|
||||
|
||||
## Cost
|
||||
|
||||
SearXNG is completely free:
|
||||
- **Open source** - No licensing costs
|
||||
- **Self-hosted** - Only infrastructure costs
|
||||
- **Public instances** - Usually free, check instance policies
|
||||
|
||||
## Advanced Usage
|
||||
|
||||
### Custom Engine Selection
|
||||
|
||||
```python
|
||||
response = search(
|
||||
query="Python tutorials",
|
||||
search_provider="searxng",
|
||||
engines="stackoverflow,github,reddit", # Only search these engines
|
||||
categories="it"
|
||||
)
|
||||
```
|
||||
|
||||
### Multi-Category Search
|
||||
|
||||
```python
|
||||
response = search(
|
||||
query="climate change",
|
||||
search_provider="searxng",
|
||||
categories="general,science,news", # Search multiple categories
|
||||
time_range="month"
|
||||
)
|
||||
```
|
||||
|
||||
### Pagination
|
||||
|
||||
```python
|
||||
# Get page 1
|
||||
page1 = search(
|
||||
query="AI research",
|
||||
search_provider="searxng",
|
||||
pageno=1
|
||||
)
|
||||
|
||||
# Get page 2
|
||||
page2 = search(
|
||||
query="AI research",
|
||||
search_provider="searxng",
|
||||
pageno=2
|
||||
)
|
||||
```
|
||||
|
||||
## Response Format
|
||||
|
||||
SearXNG returns results in the standard LiteLLM search format:
|
||||
|
||||
```json
|
||||
{
|
||||
"object": "search",
|
||||
"results": [
|
||||
{
|
||||
"title": "Example Result",
|
||||
"url": "https://example.com",
|
||||
"snippet": "This is the content snippet from the search result...",
|
||||
"date": "2024-01-15",
|
||||
"last_updated": null
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Test Your Instance First
|
||||
|
||||
If LiteLLM with searxng search provider is not working, test your SearXNG instance directly with curl:
|
||||
|
||||
```bash
|
||||
# Test if JSON API is working
|
||||
curl -s "https://your-searxng-instance.com/search?q=test&format=json" | head -50
|
||||
|
||||
# Example with specific instance
|
||||
curl -s "https://serxng-deployment-production.up.railway.app/search?q=test&format=json" | head -50
|
||||
```
|
||||
|
||||
**Expected response**: JSON with search results
|
||||
**If you get HTML**: JSON format is not enabled in the instance's `settings.yml`
|
||||
|
||||
### No Results
|
||||
|
||||
If you get no results:
|
||||
|
||||
1. **Try different engines**: Specify `engines` parameter
|
||||
2. **Broaden categories**: Use multiple categories
|
||||
3. **Adjust language**: Set appropriate `language` parameter
|
||||
|
||||
### JSON Format Not Enabled
|
||||
|
||||
If you get HTML instead of JSON:
|
||||
|
||||
1. **Test with curl**: Use the curl command above to verify JSON output
|
||||
2. **Self-host your own instance**: Use [our deployment repo](https://github.com/BerriAI/serxng-deployment) with JSON pre-configured
|
||||
3. **Check instance configuration**: Not all public instances have JSON enabled
|
||||
4. **Enable JSON manually**: Add to `settings.yml`:
|
||||
```yaml
|
||||
search:
|
||||
formats:
|
||||
- html
|
||||
- json
|
||||
```
|
||||
|
||||
@ -398,6 +398,8 @@ const sidebars = {
|
||||
"search/parallel_ai",
|
||||
"search/google_pse",
|
||||
"search/dataforseo",
|
||||
"search/firecrawl",
|
||||
"search/searxng",
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
7
litellm/llms/searxng/__init__.py
Normal file
7
litellm/llms/searxng/__init__.py
Normal file
@ -0,0 +1,7 @@
|
||||
"""
|
||||
SearXNG API integration module.
|
||||
"""
|
||||
from litellm.llms.searxng.search.transformation import SearXNGSearchConfig
|
||||
|
||||
__all__ = ["SearXNGSearchConfig"]
|
||||
|
||||
7
litellm/llms/searxng/search/__init__.py
Normal file
7
litellm/llms/searxng/search/__init__.py
Normal file
@ -0,0 +1,7 @@
|
||||
"""
|
||||
SearXNG Search API module.
|
||||
"""
|
||||
from litellm.llms.searxng.search.transformation import SearXNGSearchConfig
|
||||
|
||||
__all__ = ["SearXNGSearchConfig"]
|
||||
|
||||
223
litellm/llms/searxng/search/transformation.py
Normal file
223
litellm/llms/searxng/search/transformation.py
Normal file
@ -0,0 +1,223 @@
|
||||
"""
|
||||
Calls SearXNG's /search endpoint to search the web.
|
||||
|
||||
SearXNG API Reference: https://docs.searxng.org/dev/search_api.html
|
||||
"""
|
||||
from typing import Dict, List, Optional, TypedDict, Union
|
||||
|
||||
import httpx
|
||||
|
||||
from litellm.litellm_core_utils.litellm_logging import Logging as LiteLLMLoggingObj
|
||||
from litellm.llms.base_llm.search.transformation import (
|
||||
BaseSearchConfig,
|
||||
SearchResponse,
|
||||
SearchResult,
|
||||
)
|
||||
from litellm.secret_managers.main import get_secret_str
|
||||
|
||||
|
||||
class _SearXNGSearchRequestRequired(TypedDict):
|
||||
"""Required fields for SearXNG Search API request."""
|
||||
q: str # Required - search query
|
||||
|
||||
|
||||
class SearXNGSearchRequest(_SearXNGSearchRequestRequired, total=False):
|
||||
"""
|
||||
SearXNG Search API request format.
|
||||
Based on: https://docs.searxng.org/dev/search_api.html
|
||||
"""
|
||||
categories: str # Optional - comma-separated list of categories
|
||||
engines: str # Optional - comma-separated list of engines
|
||||
language: str # Optional - language code
|
||||
pageno: int # Optional - page number (default 1)
|
||||
time_range: str # Optional - time range filter (day, month, year)
|
||||
format: str # Optional - output format (json, csv, rss) - should be 'json'
|
||||
|
||||
|
||||
class SearXNGSearchConfig(BaseSearchConfig):
|
||||
|
||||
@staticmethod
|
||||
def ui_friendly_name() -> str:
|
||||
return "SearXNG"
|
||||
|
||||
def get_http_method(self):
|
||||
"""
|
||||
SearXNG supports both GET and POST, but we'll use GET for simplicity.
|
||||
"""
|
||||
return "GET"
|
||||
|
||||
def validate_environment(
|
||||
self,
|
||||
headers: Dict,
|
||||
api_key: Optional[str] = None,
|
||||
api_base: Optional[str] = None,
|
||||
**kwargs,
|
||||
) -> Dict:
|
||||
"""
|
||||
Validate environment and return headers.
|
||||
SearXNG is open-source and doesn't require an API key by default.
|
||||
Some instances may require authentication via headers.
|
||||
"""
|
||||
# SearXNG typically doesn't require API keys, but support optional auth
|
||||
api_key = api_key or get_secret_str("SEARXNG_API_KEY")
|
||||
if api_key:
|
||||
headers["Authorization"] = f"Bearer {api_key}"
|
||||
headers["Content-Type"] = "application/json"
|
||||
return headers
|
||||
|
||||
def get_complete_url(
|
||||
self,
|
||||
api_base: Optional[str],
|
||||
optional_params: dict,
|
||||
data: Optional[Union[Dict, List[Dict]]] = None,
|
||||
**kwargs,
|
||||
) -> str:
|
||||
"""
|
||||
Get complete URL for Search endpoint with query parameters.
|
||||
|
||||
SearXNG uses GET requests, so we build the full URL with query params here.
|
||||
The transformed request body (data) contains the parameters needed for the URL.
|
||||
"""
|
||||
from urllib.parse import urlencode
|
||||
|
||||
api_base = api_base or get_secret_str("SEARXNG_API_BASE")
|
||||
|
||||
if not api_base:
|
||||
raise ValueError(
|
||||
"SEARXNG_API_BASE is not set. Please set the `SEARXNG_API_BASE` environment variable "
|
||||
"or pass `api_base` parameter. Example: os.environ['SEARXNG_API_BASE'] = 'https://your-searxng-instance.com'"
|
||||
)
|
||||
|
||||
# Append "/search" to the api base if it's not already there
|
||||
if not api_base.endswith("/search"):
|
||||
if api_base.endswith("/"):
|
||||
api_base = f"{api_base}search"
|
||||
else:
|
||||
api_base = f"{api_base}/search"
|
||||
|
||||
# Build query parameters from the transformed request body
|
||||
if data and isinstance(data, dict) and "_searxng_params" in data:
|
||||
params = data["_searxng_params"]
|
||||
query_string = urlencode(params)
|
||||
return f"{api_base}?{query_string}"
|
||||
|
||||
return api_base
|
||||
|
||||
|
||||
def transform_search_request(
|
||||
self,
|
||||
query: Union[str, List[str]],
|
||||
optional_params: dict,
|
||||
**kwargs,
|
||||
) -> Dict:
|
||||
"""
|
||||
Transform Search request to SearXNG API format.
|
||||
|
||||
Transforms Perplexity unified spec parameters:
|
||||
- query → q
|
||||
- max_results → (handled via pageno, SearXNG returns ~20 results per page)
|
||||
- search_domain_filter → (not directly supported)
|
||||
- country → language (approximate mapping)
|
||||
- max_tokens_per_page → (not applicable, ignored)
|
||||
|
||||
All other SearXNG-specific parameters are passed through as-is.
|
||||
|
||||
Args:
|
||||
query: Search query (string or list of strings). SearXNG only supports single string queries.
|
||||
optional_params: Optional parameters for the request
|
||||
|
||||
Returns:
|
||||
Dict with typed request data following SearXNGSearchRequest spec
|
||||
"""
|
||||
if isinstance(query, list):
|
||||
# SearXNG only supports single string queries, join with spaces
|
||||
query = " ".join(query)
|
||||
|
||||
request_data: SearXNGSearchRequest = {
|
||||
"q": query,
|
||||
"format": "json", # Always request JSON format
|
||||
}
|
||||
|
||||
# Transform Perplexity unified spec parameters to SearXNG format
|
||||
if "country" in optional_params:
|
||||
# Map country code to language (approximate)
|
||||
country = optional_params["country"].lower()
|
||||
if country == "us" or country == "uk":
|
||||
request_data["language"] = "en"
|
||||
elif country == "de":
|
||||
request_data["language"] = "de"
|
||||
elif country == "fr":
|
||||
request_data["language"] = "fr"
|
||||
elif country == "es":
|
||||
request_data["language"] = "es"
|
||||
elif country == "jp":
|
||||
request_data["language"] = "ja"
|
||||
else:
|
||||
request_data["language"] = country # Pass through as-is
|
||||
|
||||
# Handle max_results via pagination (SearXNG returns ~20 results per page by default)
|
||||
# For simplicity, we'll just use page 1 and let SearXNG return its default number of results
|
||||
if "max_results" in optional_params:
|
||||
# Note: We could calculate pageno based on max_results, but for now we'll ignore this
|
||||
# and let SearXNG return its default results
|
||||
pass
|
||||
|
||||
# Convert to dict before dynamic key assignments
|
||||
result_data = dict(request_data)
|
||||
|
||||
# Pass through all other SearXNG-specific 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
|
||||
|
||||
# Store params in special key for GET request URL building
|
||||
# This will be used by get_complete_url to build the query string
|
||||
return {"_searxng_params": result_data}
|
||||
|
||||
def transform_search_response(
|
||||
self,
|
||||
raw_response: httpx.Response,
|
||||
logging_obj: LiteLLMLoggingObj,
|
||||
**kwargs,
|
||||
) -> SearchResponse:
|
||||
"""
|
||||
Transform SearXNG API response to LiteLLM unified SearchResponse format.
|
||||
|
||||
SearXNG → LiteLLM mappings:
|
||||
- results[].title → SearchResult.title
|
||||
- results[].url → SearchResult.url
|
||||
- results[].content → SearchResult.snippet
|
||||
- results[].publishedDate OR results[].pubdate → SearchResult.date
|
||||
- No last_updated field in SearXNG response (set to None)
|
||||
|
||||
Args:
|
||||
raw_response: Raw httpx response from SearXNG API
|
||||
logging_obj: Logging object for tracking
|
||||
|
||||
Returns:
|
||||
SearchResponse with standardized format
|
||||
"""
|
||||
response_json = raw_response.json()
|
||||
|
||||
# Transform results to SearchResult objects
|
||||
# Note: SearXNG doesn't natively support limiting results via API params
|
||||
# It returns ~20 results per page by default
|
||||
results = []
|
||||
for result in response_json.get("results", []):
|
||||
# Get date from either publishedDate or pubdate field
|
||||
date = result.get("publishedDate") or result.get("pubdate")
|
||||
|
||||
search_result = SearchResult(
|
||||
title=result.get("title", ""),
|
||||
url=result.get("url", ""),
|
||||
snippet=result.get("content", ""), # SearXNG uses "content" for snippet
|
||||
date=date,
|
||||
last_updated=None, # SearXNG doesn't provide last_updated in response
|
||||
)
|
||||
results.append(search_result)
|
||||
|
||||
return SearchResponse(
|
||||
results=results,
|
||||
object="search",
|
||||
)
|
||||
|
||||
@ -7971,6 +7971,14 @@
|
||||
"litellm_provider": "perplexity",
|
||||
"mode": "search"
|
||||
},
|
||||
"searxng/search": {
|
||||
"litellm_provider": "searxng",
|
||||
"mode": "search",
|
||||
"input_cost_per_query": 0.0,
|
||||
"metadata": {
|
||||
"notes": "SearXNG is an open-source metasearch engine. Free to use when self-hosted or using public instances."
|
||||
}
|
||||
},
|
||||
"elevenlabs/scribe_v1": {
|
||||
"input_cost_per_second": 6.11e-05,
|
||||
"litellm_provider": "elevenlabs",
|
||||
|
||||
@ -2595,6 +2595,7 @@ class SearchProviders(str, Enum):
|
||||
GOOGLE_PSE = "google_pse"
|
||||
DATAFORSEO = "dataforseo"
|
||||
FIRECRAWL = "firecrawl"
|
||||
SEARXNG = "searxng"
|
||||
|
||||
|
||||
# Create a set of all search provider values for quick lookup
|
||||
|
||||
@ -7733,6 +7733,7 @@ class ProviderConfigManager:
|
||||
ParallelAISearchConfig,
|
||||
)
|
||||
from litellm.llms.perplexity.search.transformation import PerplexitySearchConfig
|
||||
from litellm.llms.searxng.search.transformation import SearXNGSearchConfig
|
||||
from litellm.llms.tavily.search.transformation import TavilySearchConfig
|
||||
|
||||
PROVIDER_TO_CONFIG_MAP = {
|
||||
@ -7743,6 +7744,7 @@ class ProviderConfigManager:
|
||||
SearchProviders.GOOGLE_PSE: GooglePSESearchConfig,
|
||||
SearchProviders.DATAFORSEO: DataForSEOSearchConfig,
|
||||
SearchProviders.FIRECRAWL: FirecrawlSearchConfig,
|
||||
SearchProviders.SEARXNG: SearXNGSearchConfig,
|
||||
}
|
||||
config_class = PROVIDER_TO_CONFIG_MAP.get(provider, None)
|
||||
if config_class is None:
|
||||
|
||||
@ -7971,6 +7971,14 @@
|
||||
"litellm_provider": "perplexity",
|
||||
"mode": "search"
|
||||
},
|
||||
"searxng/search": {
|
||||
"litellm_provider": "searxng",
|
||||
"mode": "search",
|
||||
"input_cost_per_query": 0.0,
|
||||
"metadata": {
|
||||
"notes": "SearXNG is an open-source metasearch engine. Free to use when self-hosted or using public instances."
|
||||
}
|
||||
},
|
||||
"elevenlabs/scribe_v1": {
|
||||
"input_cost_per_second": 6.11e-05,
|
||||
"litellm_provider": "elevenlabs",
|
||||
|
||||
@ -1374,6 +1374,23 @@
|
||||
"rerank": false
|
||||
}
|
||||
},
|
||||
"searxng": {
|
||||
"display_name": "SearXNG (`searxng`)",
|
||||
"url": "https://docs.litellm.ai/docs/search/searxng",
|
||||
"endpoints": {
|
||||
"chat_completions": false,
|
||||
"messages": false,
|
||||
"responses": false,
|
||||
"embeddings": false,
|
||||
"image_generations": false,
|
||||
"audio_transcriptions": false,
|
||||
"audio_speech": false,
|
||||
"moderations": false,
|
||||
"batches": false,
|
||||
"rerank": false,
|
||||
"search": true
|
||||
}
|
||||
},
|
||||
"sambanova": {
|
||||
"display_name": "Sambanova (`sambanova`)",
|
||||
"url": "https://docs.litellm.ai/docs/providers/sambanova",
|
||||
|
||||
109
tests/search_tests/test_searxng_search.py
Normal file
109
tests/search_tests/test_searxng_search.py
Normal file
@ -0,0 +1,109 @@
|
||||
import pytest
|
||||
import litellm
|
||||
import os
|
||||
from typing import List, Union
|
||||
|
||||
from tests.search_tests.base_search_unit_tests import BaseSearchTest
|
||||
|
||||
|
||||
class TestSearXNGSearch(BaseSearchTest):
|
||||
"""
|
||||
Tests for SearXNG Search functionality.
|
||||
"""
|
||||
|
||||
def get_search_provider(self) -> str:
|
||||
"""
|
||||
Return search_provider for SearXNG Search.
|
||||
"""
|
||||
return "searxng"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_basic_search(self):
|
||||
"""
|
||||
Test basic search functionality with a simple query.
|
||||
Override to handle free (0.0 cost) provider.
|
||||
"""
|
||||
os.environ["LITELLM_LOCAL_MODEL_COST_MAP"] = "True"
|
||||
litellm.model_cost = litellm.get_model_cost_map(url="")
|
||||
litellm._turn_on_debug()
|
||||
search_provider = self.get_search_provider()
|
||||
print("Search Provider=", search_provider)
|
||||
|
||||
try:
|
||||
response = await litellm.asearch(
|
||||
query="latest developments in AI",
|
||||
search_provider=search_provider,
|
||||
)
|
||||
print("Search response=", response.model_dump_json(indent=4))
|
||||
|
||||
print(f"\n{'='*80}")
|
||||
print(f"Response type: {type(response)}")
|
||||
print(f"Response object: {response.object if hasattr(response, 'object') else 'N/A'}")
|
||||
|
||||
# Check if response has expected Search format
|
||||
assert hasattr(response, "results"), "Response should have 'results' attribute"
|
||||
assert hasattr(response, "object"), "Response should have 'object' attribute"
|
||||
assert response.object == "search", f"Expected object='search', got '{response.object}'"
|
||||
|
||||
# Validate results structure
|
||||
assert isinstance(response.results, list), "results should be a list"
|
||||
assert len(response.results) > 0, "Should have at least one result"
|
||||
|
||||
# Check first result structure
|
||||
first_result = response.results[0]
|
||||
assert hasattr(first_result, "title"), "Result should have 'title' attribute"
|
||||
assert hasattr(first_result, "url"), "Result should have 'url' attribute"
|
||||
assert hasattr(first_result, "snippet"), "Result should have 'snippet' attribute"
|
||||
|
||||
print(f"Total results: {len(response.results)}")
|
||||
print(f"First result title: {first_result.title}")
|
||||
print(f"First result URL: {first_result.url}")
|
||||
print(f"First result snippet: {first_result.snippet[:100]}...")
|
||||
print(f"{'='*80}\n")
|
||||
|
||||
assert len(first_result.title) > 0, "Title should not be empty"
|
||||
assert len(first_result.url) > 0, "URL should not be empty"
|
||||
assert len(first_result.snippet) > 0, "Snippet should not be empty"
|
||||
|
||||
# Validate cost tracking in _hidden_params
|
||||
# For SearXNG (free provider), cost can be None or 0.0
|
||||
assert hasattr(response, "_hidden_params"), "Response should have '_hidden_params' attribute"
|
||||
hidden_params = response._hidden_params
|
||||
assert "response_cost" in hidden_params, "_hidden_params should contain 'response_cost'"
|
||||
|
||||
response_cost = hidden_params["response_cost"]
|
||||
# SearXNG is free, so cost can be None or 0.0
|
||||
if response_cost is not None:
|
||||
assert isinstance(response_cost, (int, float)), "response_cost should be a number"
|
||||
assert response_cost >= 0, "response_cost should be non-negative"
|
||||
print(f"Cost tracking: ${response_cost:.6f}")
|
||||
else:
|
||||
print(f"Cost tracking: Free (None)")
|
||||
|
||||
except Exception as e:
|
||||
pytest.fail(f"Search call failed: {str(e)}")
|
||||
|
||||
def test_search_with_optional_params(self):
|
||||
"""
|
||||
Test search with optional parameters.
|
||||
Override for SearXNG since it doesn't natively limit results.
|
||||
"""
|
||||
litellm.set_verbose = True
|
||||
search_provider = self.get_search_provider()
|
||||
|
||||
response = litellm.search(
|
||||
query="machine learning",
|
||||
search_provider=search_provider,
|
||||
max_results=5,
|
||||
)
|
||||
|
||||
# Validate response
|
||||
assert hasattr(response, "results"), "Response should have 'results' attribute"
|
||||
assert isinstance(response.results, list), "results should be a list"
|
||||
assert len(response.results) > 0, "Should have at least one result"
|
||||
# Note: SearXNG doesn't natively limit results, so we don't check <= 5
|
||||
|
||||
print(f"\nSearch with optional params validated:")
|
||||
print(f" - Requested max_results: 5")
|
||||
print(f" - Received results: {len(response.results)}")
|
||||
|
||||
Loading…
Reference in New Issue
Block a user