"""Hit table for Meilisearch: keyword, vector-only (semanticRatio 1), hybrid at 0.5 (the documented default), plus a ratio sweep.""" import json from common import EXACT, FETCH, TOP, hit_rank, meili, pages, qvec, questions QS = questions() + [EXACT] def search(q, ratio=None, vec=True, filt=None, key=None, extra=None): body = {"q": q, "limit": FETCH, "attributesToRetrieve": ["url", "heading"]} if ratio is not None: body["hybrid"] = {"embedder": "nomic", "semanticRatio": ratio} body["vector"] = qvec(q) if filt: body["filter"] = filt body.update(extra or {}) return meili("POST", "/indexes/docs/search", body, key=key) def urls(res): return [h["url"] for h in res["hits"]] def vector_only(q): # q is passed too; at ratio 1.0 only the vector matters return search(q, 1.0) if __name__ == "__main__": print("keyword settings: defaults (matchingStrategy last, typoTolerance on, prefix search on); fetch", FETCH, "chunks, top", TOP, "pages") print("modes: keyword = no hybrid; vector = semanticRatio 1.0; hybrid = semanticRatio 0.5 (docs default)") modes = {"keyword": lambda q: search(q), "vector": lambda q: search(q, 1.0), "hybrid": lambda q: search(q, 0.5)} print(f"{'question':<70} " + " ".join(f"{m:>8}" for m in modes)) tot = {m: 0 for m in modes} tot6 = {m: 0 for m in modes} top5 = {} for i, q in enumerate(QS): row = [] for m, fn in modes.items(): res = fn(q["question"]) r = hit_rank(urls(res), q["expected_url_contains"]) tot[m] += r is not None tot6[m] += r is not None and i < 6 row.append(f"{r or '-':>8}") if m == "vector": top5[q["question"]] = pages(urls(res))[:TOP] print(f"{q['question'][:68]:<70} " + " ".join(row)) print("hits x/6 (six questions):", {m: f"{v}/6" for m, v in tot6.items()}) print("429 rank:", {m: hit_rank(urls(fn('429')), EXACT['expected_url_contains']) for m, fn in modes.items()}) json.dump(top5, open("meili_vector_top5.json", "w"), indent=1) print("\n--- semanticRatio sweep: rank of expected page (- = miss), last column = x/6 hits, then 429 rank ---") for ratio in (0.0, 0.25, 0.5, 0.75, 1.0): ranks = [hit_rank(urls(search(q["question"], ratio)), q["expected_url_contains"]) for q in QS] print(f"ratio {ratio:<5}", " ".join(f"{r or '-':>2}" for r in ranks[:6]), f"| {sum(r is not None for r in ranks[:6])}/6 | 429: {ranks[6] or '-'}") print("\n--- hybrid semanticHitCount at ratio 0.5 (of", FETCH, "requested) ---") for q in QS: res = search(q["question"], 0.5) print(f"{q['question'][:60]:<62} semanticHitCount {res.get('semanticHitCount')} estimatedTotalHits {res.get('estimatedTotalHits')}")