Highlights: - Streaming chat chunks + web search auto (Sherlock/SearXNG) - pino structured logging (43->0 console.log) - Zod validation (19 schemas API + storage) - Qwen3-TTS 0.6B + 33 persona voices - Docling + bge-reranker RAG pipeline - React lazy routes (-50% bundle), virtualized chat - SEC-01->05 all resolved - A11y WCAG (40 ARIA attrs) - Perf + error telemetry endpoints - Systemd units (TTS, LightRAG, reranker) - p-limit Ollama, WS reconnect, signal handlers - createId thread-safe via crypto.randomBytes (lot-59) - Hyperparam bounds validation with Zod schema (lot-60) - DPO extractDPOPairs warns on empty pairs (lot-61) Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
57 lines
2.1 KiB
Python
57 lines
2.1 KiB
Python
#!/usr/bin/env python3
|
|
"""BGE Reranker HTTP Server — reranks query-document pairs."""
|
|
import json, sys, os
|
|
from http.server import HTTPServer, BaseHTTPRequestHandler
|
|
from FlagEmbedding import FlagReranker
|
|
|
|
reranker = FlagReranker('BAAI/bge-reranker-v2-m3', use_fp16=True)
|
|
PORT = int(os.environ.get("RERANKER_PORT", "9500"))
|
|
|
|
class Handler(BaseHTTPRequestHandler):
|
|
def do_GET(self):
|
|
if self.path == "/health":
|
|
self.send_response(200)
|
|
self.send_header("Content-Type", "application/json")
|
|
self.end_headers()
|
|
self.wfile.write(json.dumps({"ok": True, "model": "bge-reranker-v2-m3"}).encode())
|
|
return
|
|
self.send_response(404)
|
|
self.end_headers()
|
|
|
|
def do_POST(self):
|
|
if self.path != "/rerank":
|
|
self.send_response(404)
|
|
self.end_headers()
|
|
return
|
|
length = int(self.headers.get("Content-Length", 0))
|
|
body = json.loads(self.rfile.read(length))
|
|
query = body.get("query", "")
|
|
documents = body.get("documents", [])
|
|
top_k = body.get("top_k", 5)
|
|
|
|
if not query or not documents:
|
|
self.send_response(400)
|
|
self.send_header("Content-Type", "application/json")
|
|
self.end_headers()
|
|
self.wfile.write(json.dumps({"error": "query and documents required"}).encode())
|
|
return
|
|
|
|
pairs = [[query, doc] for doc in documents]
|
|
scores = reranker.compute_score(pairs, normalize=True)
|
|
if isinstance(scores, float):
|
|
scores = [scores]
|
|
|
|
ranked = sorted(zip(documents, scores), key=lambda x: x[1], reverse=True)[:top_k]
|
|
results = [{"text": doc, "score": round(score, 4)} for doc, score in ranked]
|
|
|
|
self.send_response(200)
|
|
self.send_header("Content-Type", "application/json")
|
|
self.end_headers()
|
|
self.wfile.write(json.dumps({"results": results}).encode())
|
|
|
|
def log_message(self, format, *args):
|
|
print(f"[reranker] {args[0]}", file=sys.stderr)
|
|
|
|
print(f"[reranker] BGE reranker-v2-m3 loaded, serving on :{PORT}", file=sys.stderr)
|
|
HTTPServer(("0.0.0.0", PORT), Handler).serve_forever()
|