themis / phase1 /scripts /19_benchmark_themis.py
vg15o2's picture
Moonley backend (HF Space build)
1d9bd9b
Raw
History Blame Contribute Delete
2.99 kB
"""Benchmark harness — Themis side. Run all bench_queries through the eSCR pipeline
(loads the persisted index ONCE), save top-K per query for scoring against CaseMine.
Run on Thor: python3 19_benchmark_themis.py (expects bench_queries.json alongside)
Out: themis_bench_results.json [{id,intent,query,results:[{neutral_citation,case_name,date,disposition,rr,passage}]}]
"""
import json, os, re, time
from collections import defaultdict
import numpy as np, torch
from sentence_transformers import SentenceTransformer, CrossEncoder
from rank_bm25 import BM25Okapi
TOPK = int(os.getenv("TOPK", "10")); K = 60; CAND = 40
BGE_Q = "Represent this sentence for searching relevant passages: "
def tok(s): return re.sub(r"[^a-z0-9 ]", " ", (s or "").lower()).split()
queries = json.load(open("bench_queries.json"))
print("loading index...", flush=True)
chunks = [json.loads(l) for l in open("escr_chunks.jsonl")]
texts = [c["text"] for c in chunks]; chunk_doc = [c["doc_id"] for c in chunks]
M = np.load("escr_vectors.npy")
meta = {}
for l in open("escr_meta.jsonl"):
m = json.loads(l); meta[m["doc_id"]] = m
st = SentenceTransformer("BAAI/bge-small-en-v1.5", device="cuda", model_kwargs={"torch_dtype": torch.bfloat16})
ce = CrossEncoder("cross-encoder/ms-marco-MiniLM-L-6-v2", device="cuda")
t = time.time(); bm25 = BM25Okapi([tok(t_) for t_ in texts]); print(f"index+bm25 ready ({len(chunks)} chunks) in {time.time()-t:.0f}s", flush=True)
def search(q):
qv = st.encode(BGE_Q + q, normalize_embeddings=True, convert_to_numpy=True).astype(np.float32)
dense = M @ qv
d_top = np.argpartition(-dense, 60)[:60]; d_top = d_top[np.argsort(-dense[d_top])]
bs = bm25.get_scores(tok(q))
b_top = sorted(range(len(bs)), key=lambda i: bs[i], reverse=True)[:60]
sc = defaultdict(float)
for r, ci in enumerate(d_top): sc[int(ci)] += 1.0 / (K + r)
for r, ci in enumerate(b_top):
if bs[ci] > 0: sc[ci] += 1.0 / (K + r)
cand = [ci for ci, _ in sorted(sc.items(), key=lambda x: x[1], reverse=True)[:CAND]]
rr = ce.predict([(q, texts[ci]) for ci in cand])
best = {}
for ci, s in zip(cand, rr):
d = chunk_doc[ci]
if d not in best or s > best[d][0]: best[d] = (float(s), ci)
out = []
for d, (s, ci) in sorted(best.items(), key=lambda x: x[1][0], reverse=True)[:TOPK]:
m = meta.get(d, {})
out.append({"neutral_citation": m.get("neutral_citation"), "case_name": m.get("case_name"),
"date": m.get("date"), "disposition": m.get("disposition"),
"rr": round(s, 2), "passage": re.sub(r"\s+", " ", texts[ci])[:350]})
return out
results = []
for qd in queries:
res = search(qd["query"])
results.append({**qd, "results": res})
print(f" {qd['id']:12s} top1: {res[0]['case_name'][:50] if res else '-'}", flush=True)
json.dump(results, open("themis_bench_results.json", "w"), ensure_ascii=False, indent=1)
print("wrote themis_bench_results.json", flush=True)