80 lines
3.1 KiB
Python
80 lines
3.1 KiB
Python
"""
|
|
SearXNG Service Adapter
|
|
Discovers prior art, candidate sources, and web references via SearXNG JSON API.
|
|
"""
|
|
|
|
import json
|
|
import time
|
|
import urllib.parse
|
|
import urllib.request
|
|
import asyncio
|
|
from typing import List, Dict, Any, Optional
|
|
from .base import BaseServiceAdapter, ServiceHealth
|
|
|
|
class SearXNGAdapter(BaseServiceAdapter):
|
|
def __init__(self, endpoint: str = "https://sx.godno.de"):
|
|
super().__init__(service_id="searxng", endpoint=endpoint)
|
|
|
|
async def check_health(self) -> ServiceHealth:
|
|
start = time.time()
|
|
try:
|
|
url = f"{self.endpoint}/search?format=json&q=ping"
|
|
req = urllib.request.Request(
|
|
url,
|
|
headers={"User-Agent": "ThinkStorm-Orchestrator/0.1"}
|
|
)
|
|
# Run in executor to avoid blocking async event loop
|
|
loop = asyncio.get_running_loop()
|
|
def fetch():
|
|
with urllib.request.urlopen(req, timeout=8.0) as resp:
|
|
return resp.read()
|
|
raw = await loop.run_in_executor(None, fetch)
|
|
data = json.loads(raw.decode("utf-8"))
|
|
elapsed = int((time.time() - start) * 1000)
|
|
return ServiceHealth(
|
|
service_id=self.service_id,
|
|
healthy=True,
|
|
endpoint=self.endpoint,
|
|
message=f"SearXNG online (returned {len(data.get('results', []))} results)",
|
|
response_time_ms=elapsed
|
|
)
|
|
except Exception as e:
|
|
elapsed = int((time.time() - start) * 1000)
|
|
return ServiceHealth(
|
|
service_id=self.service_id,
|
|
healthy=False,
|
|
endpoint=self.endpoint,
|
|
message=f"SearXNG connection error: {str(e)}",
|
|
response_time_ms=elapsed
|
|
)
|
|
|
|
async def search(self, query: str, limit: int = 8) -> List[Dict[str, Any]]:
|
|
"""Executes a search query and returns structured results."""
|
|
encoded_query = urllib.parse.quote_plus(query)
|
|
url = f"{self.endpoint}/search?format=json&q={encoded_query}&language=en"
|
|
req = urllib.request.Request(
|
|
url,
|
|
headers={"User-Agent": "ThinkStorm-Orchestrator/0.1"}
|
|
)
|
|
loop = asyncio.get_running_loop()
|
|
def fetch():
|
|
with urllib.request.urlopen(req, timeout=3.0) as resp:
|
|
return resp.read()
|
|
try:
|
|
raw = await asyncio.wait_for(loop.run_in_executor(None, fetch), timeout=3.0)
|
|
data = json.loads(raw.decode("utf-8"))
|
|
raw_results = data.get("results", [])
|
|
results = []
|
|
for r in raw_results[:limit]:
|
|
results.append({
|
|
"title": r.get("title", "Untitled"),
|
|
"url": r.get("url", ""),
|
|
"content": r.get("content", ""),
|
|
"engine": r.get("engine", "searxng"),
|
|
"score": r.get("score", 0.0)
|
|
})
|
|
return results
|
|
except Exception as e:
|
|
print(f"[SearXNG] Search error for query '{query}': {e}")
|
|
return []
|