|
|
| """Tests for the extractive reader. Pure python, no model, no GPU."""
|
| import sys
|
| import time
|
| from pathlib import Path
|
|
|
| sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
|
| from src.reader import AnswerType, LexicalSpanReader, _stem, detect_answer_type
|
| from src.router import Passage, Route, Router, RouterConfig
|
|
|
| PASS = FAIL = 0
|
| def chk(c, l, d=""):
|
| global PASS, FAIL
|
| if c: PASS += 1; print(f" ok {l}")
|
| else: FAIL += 1; print(f" FAIL {l} {d}")
|
|
|
| R = LexicalSpanReader()
|
| P = [Passage("c1", "Quick Answer. Eagles fly 30 to 55 mph and dive at over 100 mph. "
|
| "Eagles can soar for hours on warm air currents, which conserves energy.", 0.91),
|
| Passage("c2", "The bald eagle is the national bird of the United States. "
|
| "It was adopted as the emblem in 1782.", 0.72)]
|
|
|
|
|
| print("[stemmer: plural must collapse onto singular]")
|
| for a, b in [("eagles", "eagle"), ("boxes", "box"), ("dishes", "dish"),
|
| ("cities", "city"), ("countries", "country"), ("states", "state")]:
|
| chk(_stem(a) == _stem(b), f"{a} == {b}", (_stem(a), _stem(b)))
|
| print(" -- and must NOT over-strip --")
|
| for w in ("class", "glass", "was", "is", "bird", "bus"):
|
| chk(_stem(w) == w, f"{w} unchanged", _stem(w))
|
|
|
|
|
| print("\n[answer-type detection across scripts]")
|
| for q, e in [("how fast does an eagle travel", AnswerType.NUMERIC),
|
| ("how many people live there", AnswerType.NUMERIC),
|
| ("what year was it adopted", AnswerType.NUMERIC),
|
| ("who is the president", AnswerType.PERSON),
|
| ("where is mount everest", AnswerType.LOCATION),
|
| ("effects of climate change", AnswerType.DESCRIPTION),
|
| ("कितने लोग रहते हैं", AnswerType.NUMERIC),
|
| ("யார் இதை இயக்கினார்", AnswerType.PERSON),
|
| ("کتنے لوگ رہتے ہیں", AnswerType.NUMERIC)]:
|
| chk(detect_answer_type(q) == e, f"{q[:34]:36s} -> {e}", detect_answer_type(q))
|
|
|
|
|
| print("\n[extraction finds the right span]")
|
| for q, want in [("how fast does an eagle travel", "30 to 55 mph"),
|
| ("what year was the bald eagle adopted", "1782"),
|
| ("what is the national bird of the united states", "bald eagle"),
|
| ("how do eagles conserve energy", "soar")]:
|
| s = R.read(q, P)
|
| chk(want.lower() in s.text.lower(), f"{q[:42]:44s} -> {want}", s.text[:60])
|
| chk(s.chunk_id in ("c1", "c2"), "cites a real chunk")
|
|
|
|
|
| print("\n[NUMERIC questions prefer spans containing digits]")
|
| s = R.read("how fast does an eagle travel", P)
|
| chk(any(ch.isdigit() for ch in s.text), "digit-bearing span chosen", s.text)
|
|
|
|
|
| chk("bald" not in s.text.lower(), "numeric prior overrode raw lexical overlap")
|
|
|
|
|
| print("\n[confidence separates answerable from unanswerable]")
|
| good = R.read("what is the national bird of the united states", P).score
|
| bad = R.read("what is the capital of mongolia", P).score
|
| chk(good > 0.8, "confident on an answerable query", good)
|
| chk(bad < 0.2, "unconfident on an unanswerable one", bad)
|
| chk(good - bad > 0.5, "wide separation -> usable for conformal", good - bad)
|
|
|
|
|
| print("\n[degenerate inputs]")
|
| chk(R.read("anything", [])[1] == 0.0 if isinstance(R.read("anything", []), tuple)
|
| else R.read("anything", []).score == 0.0, "no passages -> zero confidence")
|
| chk(R.read("", P).score >= 0.0, "empty query does not crash")
|
| chk(R.read("x", [Passage("c", "", 0.5)]).score == 0.0, "empty passage text")
|
| long_q = " ".join(["word"] * 200)
|
| chk(R.read(long_q, P).score >= 0.0, "very long query does not crash")
|
|
|
|
|
| print("\n[latency budget: the reader must be single-digit ms]")
|
| ts = []
|
| for _ in range(500):
|
| t = time.perf_counter_ns()
|
| R.read("how fast does an eagle travel", P)
|
| ts.append((time.perf_counter_ns() - t) / 1e6)
|
| ts.sort()
|
| p50, p100 = ts[250], ts[-1]
|
| chk(p50 < 5.0, f"P50 {p50:.3f} ms < 5 ms")
|
| chk(p100 < 20.0, f"P100 {p100:.3f} ms < 20 ms")
|
| print("")
|
| print("[agglutinative Indic queries actually select a span]")
|
| _q = "ಕಾರ್ಪೋರೇಷನ್ ಎಂದರೇನು"
|
| _hit = "ಕಾರ್ಪೋರೇಷನ್ಗಳು ಎಂದು ಕರೆಯಲಾಗುತ್ತದೆ."
|
| _other = "ಸ್ಟಾಕ್ ಮಾರುಕಟ್ಟೆ ತೆರೆಯಿತು."
|
| _p = [Passage("k:0", _other, 0.60), Passage("k:1", _hit, 0.61)]
|
| _s = LexicalSpanReader().read(_q, _p)
|
|
|
|
|
| chk(_s.score > 0.0, "confidence is non-zero on an inflected Indic match", _s.score)
|
| chk(_s.chunk_id == "k:1", "the passage containing the term is chosen", _s.chunk_id)
|
|
|
| print(f" P50 {p50:.3f} P70 {ts[350]:.3f} P100 {p100:.3f} ms")
|
|
|
|
|
| print("\n[wires into the router end to end]")
|
| def gen(q, ps, cap, constrained):
|
| return (f"generated<={cap}", 0.75)
|
|
|
| router = Router(RouterConfig(tau_retrieval=0.30, tau_extract=0.60), R, gen)
|
| d = router.route("what is the national bird of the united states", P)
|
| chk(d.route is Route.EXTRACT, "confident query takes the EXTRACT path", d.route)
|
| chk("bald eagle" in d.answer.lower(), "answer carried through", d.answer[:50])
|
| chk(d.total_ms < 10, f"end-to-end {d.total_ms:.2f} ms", d.timings_ms)
|
|
|
| d = router.route("what is the capital of mongolia", P)
|
| chk(d.route is not Route.EXTRACT, "unconfident query does not extract", d.route)
|
|
|
| d = router.route("q", [Passage("c", "text", 0.1)])
|
| chk(d.route is Route.ABSTAIN, "low retrieval score still abstains first")
|
|
|
|
|
| print("\n[route mix on a mixed workload resembles the measured bands]")
|
| qs = [("what is the national bird of the united states", P),
|
| ("how fast does an eagle travel", P),
|
| ("what year was the bald eagle adopted", P),
|
| ("how do eagles conserve energy", P),
|
| ("what is the capital of mongolia", P)]
|
| routes = [router.route(q, ps).route for q, ps in qs]
|
| n_ex = sum(r is Route.EXTRACT for r in routes)
|
| chk(n_ex >= 2, f"{n_ex}/5 extracted", [r.value for r in routes])
|
|
|
|
|
|
|
| print("\n[retrieval prior]")
|
| from src.reader import LexicalSpanReader as _LSR
|
|
|
| _G = "এটা ঈগলে ৩০ থেকে ৫৫ মাইল প্রতি ঘণ্টা গতিত উৰিব পাৰে।"
|
| _D = "আপুনি কিমান আগতীয়াকৈ সংৰক্ষণ কৰে আৰু ৰে'লখন কিমান ভৰ্তি হৈ আছে তাৰ ওপৰত নিৰ্ভৰ কৰে।"
|
| _Q = "এটা ঈগলে কিমান দ্ৰুতগতিত ভ্ৰমণ কৰে"
|
|
|
| _hi = [Passage("gold", _G, 0.9, "as"), Passage("dist", _D, 0.1, "as")]
|
| chk(_LSR(prior_weight=0.0).read(_Q, _hi).chunk_id in ("gold", "dist"),
|
| "prior_weight 0 leaves selection to lexical overlap")
|
| chk(_LSR(prior_weight=1.0).read(_Q, _hi).chunk_id == "gold",
|
| "prior_weight 1 follows the retriever")
|
|
|
|
|
| _lo = [Passage("gold", _G, 0.1, "as"), Passage("junk", "কলা ভাল ফল।", 0.9, "as")]
|
| chk(_LSR(prior_weight=1.0).read(_Q, _lo).chunk_id == "gold",
|
| "multiplicative blend: prior cannot rescue a no-overlap passage")
|
|
|
|
|
| _eq = [Passage("a", _G, 0.5, "as"), Passage("b", _D, 0.5, "as")]
|
| chk(_LSR(prior_weight=1.0).read(_Q, _eq).chunk_id == _LSR(prior_weight=0.0).read(_Q, _eq).chunk_id,
|
| "flat retrieval scores are a no-op")
|
| chk(_LSR().prior_weight == 0.0, "default is OFF -- opt in after measuring")
|
|
|
| print(f"\n{'='*54}\n {PASS} passed, {FAIL} failed\n{'='*54}")
|
|
|
| sys.exit(1 if FAIL else 0)
|
|
|