mirror of
https://github.com/andrewyng/openworker.git
synced 2026-09-04 16:42:35 +00:00
Imported from andrewyng/aisuite@1b4bbf303e (contents of its platform/ directory, hoisted to the repo root). Development history prior to this commit lives in that repository. Co-authored-by: Devika <devikaverma11@gmail.com>
129 lines
3.5 KiB
Python
129 lines
3.5 KiB
Python
"""Web search providers — a keyless default + pluggable third-party services.
|
|
|
|
`duckduckgo` works with no API key (our "starting version of our own"). `tavily` and `brave`
|
|
give better results but need a key (configured via the SecretStore / env). All providers
|
|
return a uniform `list[SearchResult]`; the heavy client libs are lazy-imported.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from abc import ABC, abstractmethod
|
|
from dataclasses import dataclass
|
|
from typing import Optional
|
|
|
|
_TIMEOUT = 20.0
|
|
|
|
|
|
@dataclass
|
|
class SearchResult:
|
|
title: str
|
|
url: str
|
|
snippet: str
|
|
|
|
def to_dict(self) -> dict:
|
|
return {"title": self.title, "url": self.url, "snippet": self.snippet}
|
|
|
|
|
|
class WebSearchProvider(ABC):
|
|
name: str = "base"
|
|
requires_key: bool = False
|
|
|
|
@abstractmethod
|
|
def search(self, query: str, max_results: int = 5) -> list[SearchResult]: ...
|
|
|
|
|
|
class DuckDuckGoProvider(WebSearchProvider):
|
|
"""Keyless default via the `ddgs` library."""
|
|
|
|
name = "duckduckgo"
|
|
requires_key = False
|
|
|
|
def search(self, query: str, max_results: int = 5) -> list[SearchResult]:
|
|
from ddgs import DDGS
|
|
|
|
rows = DDGS().text(query, max_results=max_results) or []
|
|
return [
|
|
SearchResult(
|
|
title=r.get("title", ""),
|
|
url=r.get("href", "") or r.get("url", ""),
|
|
snippet=r.get("body", "") or r.get("snippet", ""),
|
|
)
|
|
for r in rows
|
|
]
|
|
|
|
|
|
class TavilyProvider(WebSearchProvider):
|
|
name = "tavily"
|
|
requires_key = True
|
|
|
|
def __init__(self, api_key: str) -> None:
|
|
self.api_key = api_key
|
|
|
|
def search(self, query: str, max_results: int = 5) -> list[SearchResult]:
|
|
import httpx
|
|
|
|
resp = httpx.post(
|
|
"https://api.tavily.com/search",
|
|
json={"api_key": self.api_key, "query": query, "max_results": max_results},
|
|
timeout=_TIMEOUT,
|
|
)
|
|
data = resp.json()
|
|
return [
|
|
SearchResult(
|
|
title=r.get("title", ""),
|
|
url=r.get("url", ""),
|
|
snippet=r.get("content", ""),
|
|
)
|
|
for r in data.get("results", [])
|
|
]
|
|
|
|
|
|
class BraveProvider(WebSearchProvider):
|
|
name = "brave"
|
|
requires_key = True
|
|
|
|
def __init__(self, api_key: str) -> None:
|
|
self.api_key = api_key
|
|
|
|
def search(self, query: str, max_results: int = 5) -> list[SearchResult]:
|
|
import httpx
|
|
|
|
resp = httpx.get(
|
|
"https://api.search.brave.com/res/v1/web/search",
|
|
headers={
|
|
"X-Subscription-Token": self.api_key,
|
|
"Accept": "application/json",
|
|
},
|
|
params={"q": query, "count": max_results},
|
|
timeout=_TIMEOUT,
|
|
)
|
|
data = resp.json()
|
|
return [
|
|
SearchResult(
|
|
title=r.get("title", ""),
|
|
url=r.get("url", ""),
|
|
snippet=r.get("description", ""),
|
|
)
|
|
for r in (data.get("web", {}) or {}).get("results", [])
|
|
]
|
|
|
|
|
|
_PROVIDERS = {
|
|
"duckduckgo": DuckDuckGoProvider,
|
|
"tavily": TavilyProvider,
|
|
"brave": BraveProvider,
|
|
}
|
|
|
|
|
|
def build_provider(name: str, api_key: Optional[str] = None) -> WebSearchProvider:
|
|
cls = _PROVIDERS.get(name, DuckDuckGoProvider)
|
|
if cls.requires_key:
|
|
if not api_key:
|
|
raise ValueError(f"web search provider '{name}' needs an API key")
|
|
return cls(api_key) # type: ignore[call-arg]
|
|
return cls() # type: ignore[call-arg]
|
|
|
|
|
|
def provider_names() -> list[str]:
|
|
return list(_PROVIDERS)
|