"""Reranking via Qwen3-Reranker over Ollama.""" import requests import os import re from .config import CONFIG RERANK_MODEL = os.getenv("KB_RERANK_MODEL", "dengcao/Qwen3-Reranker-4B:Q5_K_M") def score_relevance(query: str, document: str) -> float: """Get a relevance score in [0, 1] for (query, document) pair.""" if len(document) > 4000: document = document[:4000] + "..." # Pre-fill as empty so the model jumps straight to answer prompt = ( f"<|im_start|>system\n" f"You evaluate whether a document is relevant to a query about quantitative trading. " f"Be strict: the document must specifically address the query. " f"Output only \"yes\" or \"no\".<|im_end|>\n" f"<|im_start|>user\n" f": {query}\n" f": {document}<|im_end|>\n" f"<|im_start|>assistant\n" f"\n\n\n\n" ) try: resp = requests.post( f"{CONFIG.ollama_url}/api/generate", json={ "model": RERANK_MODEL, "prompt": prompt, "stream": False, "options": { "num_predict": 5, "temperature": 0.0, "top_p": 1.0, }, "raw": True, }, timeout=60, ) resp.raise_for_status() text = resp.json().get("response", "").strip().lower() text = re.sub(r"", "", text).strip() resp.raise_for_status() text = resp.json().get("response", "").strip() text = re.sub(r"", "", text).strip() # DEBUG: print every response so we can see what's happening print(f"DEBUG rerank response: {text[:80]!r}", flush=True) text = text.lower() if text.startswith("yes"): return 1.0 elif text.startswith("no"): return 0.0 else: print(f"Rerank ambiguous response: {text[:100]!r}", flush=True) return 0.5 except Exception as e: print(f"Rerank error: {e}", flush=True) return 0.5 def rerank(query: str, candidates: list, top_k: int = 10) -> list: if not candidates: return [] scored = [] for cand in candidates: chunk_meta = cand.get("chunk_metadata") or {} doc_text = chunk_meta.get("raw_chunk") or cand.get("content", "") score = score_relevance(query, doc_text) cand_with_score = dict(cand) cand_with_score["rerank_score"] = score cand_with_score["original_similarity"] = cand.get("similarity") scored.append(cand_with_score) scored.sort(key=lambda x: x["rerank_score"], reverse=True) return scored[:top_k]