Spaces:
Running on Zero
Running on Zero
File size: 4,500 Bytes
42f6abb 184d40c 42f6abb 184d40c 42f6abb 184d40c 42f6abb 184d40c 42f6abb 184d40c 42f6abb d409fec 42f6abb 79019c5 42f6abb d409fec 42f6abb 184d40c 558fffa 184d40c | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 | """SearXNG bridge — runs a SearXNG metasearch instance inside the Space
container (userspace venv, no root) and proxies queries to it.
Mounted under /respite/v2/searx/*:
/status → bootstrap state of the runtime, and of the reranker
/search → proxy ?q=... queries to SearXNG's JSON API
/rerank → score/reorder/drop a result set for a query (see rerank.py)
Engine selection comes from searxng_settings.yml (DDG + Google enabled);
the frontend picks engines per request via the standard `engines` param.
/rerank lives under the same prefix as /search on purpose: the Space's key gate
guards /respite/v2/searx/*, so the reranker inherits that authentication rather
than needing a second one to be got right.
"""
import asyncio
import json
import httpx
from fastapi import Request
from fastapi.responses import JSONResponse
import rerank
import searx_runtime
# Load the reranker's checkpoint as soon as the container boots, not on the
# first search request. The first request has a ~1s deadline and the load takes
# minutes, so without this the reranker can never apply on a cold container and
# the only symptom is a timeout. A daemon thread, so a failure or a slow mirror
# cannot hold the Space's startup hostage.
rerank.warm()
def register_routes(fa_app):
@fa_app.get("/respite/v2/searx/status")
def _status():
return JSONResponse({
"runtime_dir": searx_runtime.RUNTIME_DIR,
"installed": searx_runtime.searxng_ready(),
"boot_error": searx_runtime.boot_error(),
"port": searx_runtime.SEARXNG_PORT,
"reranker": rerank.status(), })
@fa_app.get("/respite/v2/searx/search")
async def _search(request: Request):
if not searx_runtime.searxng_ready():
ok = searx_runtime.start_searxng()
if not ok:
return JSONResponse({"error": "searxng not running"}, status_code=503)
params = dict(request.query_params)
# SearXNG's JSON API lives at /search with format=json
params.setdefault("format", "json")
url = f"http://127.0.0.1:{searx_runtime.SEARXNG_PORT}/search"
try:
async with httpx.AsyncClient(timeout=httpx.Timeout(30.0, connect=5.0)) as client:
r = await client.get(url, params=params)
return JSONResponse(r.json(), status_code=r.status_code)
except Exception as e:
return JSONResponse({"error": str(e)}, status_code=502)
@fa_app.post("/respite/v2/searx/rerank")
async def _rerank(request: Request):
"""Rerank a result set. Always answers 200 with the results in some
order — the client is a search path, and an exception here would look
like an outage to whoever is trying to read the page."""
try:
body = await request.body()
payload = json.loads(body or b"{}")
except Exception as e:
return JSONResponse({"ok": False, "error": f"bad request: {e}", "results": []})
q = str(payload.get("q") or "").strip()
results = payload.get("results")
if not q or not isinstance(results, list) or not results:
return JSONResponse({"ok": False, "error": "q and results are required", "results": results or []})
budget_ms = int(payload.get("budget_ms") or 800)
# rerank() is synchronous and CPU-bound (torch), so it must not run on
# the event loop — that would stall every other Space request, audio
# generation included, for as long as the model takes.
try:
out = await asyncio.to_thread(rerank.rerank, q, results, budget_ms)
except Exception as e: # noqa: BLE001
# `no_opinion` as well as `error`: the client reports the former, so
# a crash that never reached rerank() must not read as "the model
# had nothing to say about your query".
return JSONResponse(
{
"ok": False,
"error": f"rerank failed: {e}",
"no_opinion": f"{type(e).__name__}: {e}"[:300],
"results": results,
}
)
return JSONResponse(out)
@fa_app.get("/respite/v2/searx/rerank/recent")
def _rerank_recent(limit: int = 20):
"""Recent relevance scores, for choosing DROP_BELOW from real traffic
rather than from a guess."""
return JSONResponse({"scores": rerank.recent_scores(limit)})
|