chore: initial public snapshot for github upload

This commit is contained in:
Your Name
2026-03-26 20:06:14 +08:00
commit 0e5ecd930e
3497 changed files with 1586236 additions and 0 deletions

View File

@@ -0,0 +1,10 @@
"""
DataForSEO Search Module
This module provides search functionality using DataForSEO's SERP API.
DataForSEO offers comprehensive search engine data with high accuracy.
"""
from .transformation import DataForSEOSearchConfig
__all__ = ["DataForSEOSearchConfig"]

View File

@@ -0,0 +1,224 @@
"""
Calls DataForSEO SERP API to search the web.
DataForSEO API Reference: https://docs.dataforseo.com/v3/serp/google/organic/live/advanced/?bash
"""
from typing import Any, Dict, List, Literal, Optional, Union
import httpx
from litellm.constants import DEFAULT_DATAFORSEO_LOCATION_CODE
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 DataForSEOSearchConfig(BaseSearchConfig):
"""
Configuration for DataForSEO SERP API search.
DataForSEO uses HTTP Basic Auth with login:password credentials.
API endpoint: https://api.dataforseo.com/v3/serp/google/organic/live/advanced
"""
DATAFORSEO_API_BASE = (
"https://api.dataforseo.com/v3/serp/google/organic/live/advanced"
)
@staticmethod
def ui_friendly_name() -> str:
return "DataForSEO"
def get_http_method(self) -> Literal["GET", "POST"]:
"""
DataForSEO uses POST requests with JSON body.
"""
return "POST"
def validate_environment(
self,
headers: Dict,
api_key: Optional[str] = None,
api_base: Optional[str] = None,
**kwargs,
) -> Dict:
"""
Validate DataForSEO environment and set up authentication.
DataForSEO uses HTTP Basic Auth with login:password format.
The credentials should be in DATAFORSEO_LOGIN and DATAFORSEO_PASSWORD env vars,
or passed as api_key in "login:password" format.
"""
import base64
# Get login and password
login = get_secret_str("DATAFORSEO_LOGIN")
password = get_secret_str("DATAFORSEO_PASSWORD")
# If api_key is provided in "login:password" format, use it
if api_key and ":" in api_key:
login, password = api_key.split(":", 1)
if not login:
raise ValueError(
"DATAFORSEO_LOGIN is not set. Set `DATAFORSEO_LOGIN` environment variable or pass credentials in api_key parameter."
)
if not password:
raise ValueError(
"DATAFORSEO_PASSWORD is not set. Set `DATAFORSEO_PASSWORD` environment variable or pass credentials in api_key parameter."
)
# Create Basic Auth header
credentials = f"{login}:{password}"
encoded_credentials = base64.b64encode(credentials.encode()).decode()
headers["Authorization"] = f"Basic {encoded_credentials}"
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 DataForSEO SERP API endpoint.
DataForSEO uses POST requests, so no query parameters in URL.
"""
return (
api_base
or get_secret_str("DATAFORSEO_API_BASE")
or self.DATAFORSEO_API_BASE
)
def transform_search_request(
self,
query: Union[str, List[str]],
optional_params: dict,
api_key: Optional[str] = None,
**kwargs,
) -> Union[Dict, List[Dict]]:
"""
Transform Search request to DataForSEO SERP API format.
Args:
query: Search query (string or list of strings). DataForSEO supports single string queries.
optional_params: Optional parameters for the request
- max_results: Maximum number of search results → maps to `depth` (max 700)
- country: Country name → maps to `location_name`
- search_domain_filter: Domain to filter results → maps to `domain`
- Plus any DataForSEO-specific parameters (location_code, language_code, device, os, etc.)
api_key: DataForSEO credentials (login:password format)
Returns:
List[Dict]: Request body for DataForSEO API (array of task objects as required by API)
"""
# DataForSEO expects an array of task objects
task: Dict[str, Any] = {}
# Convert query to string if it's a list
if isinstance(query, list):
query = query[0] if query else ""
# Required field: keyword
task["keyword"] = query
# Map unified parameters to DataForSEO parameters
if "max_results" in optional_params and optional_params["max_results"]:
# DataForSEO uses 'depth' for number of results (max 700)
depth = min(int(optional_params["max_results"]), 700)
task["depth"] = depth
if "country" in optional_params and optional_params["country"]:
# DataForSEO uses location_code (e.g., 2840 for USA)
# For simplicity, we'll use location_name which accepts country names
task["location_name"] = optional_params["country"]
if (
"search_domain_filter" in optional_params
and optional_params["search_domain_filter"]
):
# DataForSEO uses 'domain' parameter to filter by domain
task["domain"] = optional_params["search_domain_filter"]
# Add defaults if not specified
if "language_code" not in task and "language_name" not in task:
task["language_code"] = "en"
# DataForSEO requires a location - use default from constants if not specified
if "location_code" not in task and "location_name" not in task:
task["location_code"] = DEFAULT_DATAFORSEO_LOCATION_CODE
# 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 task
):
task[param] = value
# DataForSEO API expects an array of tasks
return [task]
def transform_search_response(
self,
raw_response: httpx.Response,
logging_obj: LiteLLMLoggingObj,
**kwargs,
) -> SearchResponse:
"""
Transform DataForSEO SERP API response to LiteLLM unified SearchResponse format.
DataForSEO → LiteLLM mappings:
- tasks[0].result[*].items[*].title → SearchResult.title
- tasks[0].result[*].items[*].url → SearchResult.url
- tasks[0].result[*].items[*].description → SearchResult.snippet
- No date/last_updated fields in standard response (set to None)
Args:
raw_response: Raw httpx response from DataForSEO API
logging_obj: Logging object for tracking
Returns:
SearchResponse with standardized format
"""
response_json = raw_response.json()
# Transform results to SearchResult objects
results = []
# DataForSEO wraps results in tasks array
if "tasks" in response_json and len(response_json["tasks"]) > 0:
task = response_json["tasks"][0]
# Check if task was successful
if task.get("status_code") == 20000 and "result" in task:
# Result is an array, take first element
if len(task["result"]) > 0:
result = task["result"][0]
# Items contain the actual search results
for item in result.get("items", []):
# Only process organic search results
if item.get("type") == "organic":
search_result = SearchResult(
title=item.get("title", ""),
url=item.get("url", ""),
snippet=item.get("description", ""),
date=None, # DataForSEO doesn't provide date in standard response
last_updated=None,
)
results.append(search_result)
return SearchResponse(
results=results,
object="search",
)