Ground truth comes only from third-party annotations (MMLongBench-Doc
evidence_page_ids, VisRAG qrels, ScreenSpot-v2 bboxes, Video-MME QA,
LongMemEval answer_session_ids, CodeSearchNet func_path+func_name) and
from document structure; no model judgement is used anywhere.
- bench/corpus/manifest.jsonl: 28 sources with url, retrieved_at, sha256,
annotation_source, license, files[]; verified by bench.manifest_check
- bench/sets/{pdf,image,ui_screenshot,video,code,conversation}.{jsonl} plus
corpus indexes and a manifest that freezes every file's sha256
- 2,928 answerable + 2,380 unanswerable queries over 854 holders; every
query carries scope {holder, scenario, as_of} for transactive routing
- bench/validate_sets.py fails on missing gold, holder mismatch between
gold and query, gold observed after as_of, and manifest digest drift
142 lines
5.5 KiB
Python
142 lines
5.5 KiB
Python
"""Prove the generated sets are schema-clean, solvable, and honest about their gold.
|
|
|
|
A benchmark is only worth running if its answers are reachable: every answerable
|
|
query must name a corpus source that exists, belongs to the holder being asked,
|
|
and was observed no later than the moment the question was asked. Unanswerable
|
|
queries must say how they were made unanswerable.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import sys
|
|
from datetime import datetime
|
|
from typing import Any
|
|
|
|
from bench.sets_common import (
|
|
MODALITIES,
|
|
SETS,
|
|
read_jsonl,
|
|
sha256_file,
|
|
validate_corpus,
|
|
validate_query,
|
|
)
|
|
|
|
MAX_REPORTED = 20
|
|
|
|
|
|
def parse_time(value: str, where: str) -> datetime:
|
|
try:
|
|
return datetime.fromisoformat(value)
|
|
except ValueError as error:
|
|
raise SystemExit(f"{where}: unparseable timestamp {value!r}") from error
|
|
|
|
|
|
def check_modality(modality: str, problems: list[str]) -> dict[str, int]:
|
|
corpus = list(read_jsonl(SETS / "corpus" / f"{modality}.jsonl"))
|
|
queries = list(read_jsonl(SETS / f"{modality}.jsonl"))
|
|
sources: dict[str, dict[str, Any]] = {}
|
|
for record in corpus:
|
|
problems.extend(f"{modality} corpus {record.get('source_id')}: {p}" for p in validate_corpus(record))
|
|
source_id = str(record["source_id"])
|
|
if source_id in sources:
|
|
problems.append(f"{modality} corpus: duplicate source_id {source_id}")
|
|
sources[source_id] = record
|
|
|
|
query_ids: set[str] = set()
|
|
answerable = 0
|
|
for query in queries:
|
|
where = f"{modality} query {query.get('query_id')}"
|
|
problems.extend(f"{where}: {p}" for p in validate_query(query))
|
|
query_id = str(query["query_id"])
|
|
if query_id in query_ids:
|
|
problems.append(f"{modality}: duplicate query_id {query_id}")
|
|
query_ids.add(query_id)
|
|
|
|
if query["unanswerable"]:
|
|
if not query["origin"].get("construction"):
|
|
problems.append(f"{where}: unanswerable query does not say how it was built")
|
|
continue
|
|
answerable += 1
|
|
asked_at = parse_time(query["as_of"], where)
|
|
for gold in query["gold"]:
|
|
source = sources.get(gold["source_id"])
|
|
if source is None:
|
|
problems.append(f"{where}: gold names {gold['source_id']}, absent from the corpus")
|
|
continue
|
|
if source["holder"] != query["scope"]["holder"]:
|
|
problems.append(
|
|
f"{where}: gold {gold['source_id']} lives with holder {source['holder']}, "
|
|
f"but the query asks holder {query['scope']['holder']}"
|
|
)
|
|
observed = parse_time(source["observed_at"], f"{where} gold {gold['source_id']}")
|
|
if observed > asked_at:
|
|
problems.append(
|
|
f"{where}: gold {gold['source_id']} was observed {source['observed_at']}, "
|
|
f"after the question was asked at {query['as_of']}"
|
|
)
|
|
return {
|
|
"corpus": len(corpus),
|
|
"queries": len(queries),
|
|
"answerable": answerable,
|
|
"unanswerable": len(queries) - answerable,
|
|
"holders": len({record["holder"] for record in corpus}),
|
|
}
|
|
|
|
|
|
def check_manifest(table: dict[str, dict[str, int]], problems: list[str]) -> None:
|
|
"""The frozen manifest must still describe the files on disk."""
|
|
path = SETS / "manifest.json"
|
|
try:
|
|
manifest = json.loads(path.read_text(encoding="utf-8"))
|
|
except FileNotFoundError:
|
|
problems.append(f"{path}: missing; run bench.build_sets")
|
|
return
|
|
except (OSError, json.JSONDecodeError) as error:
|
|
problems.append(f"{path}: unreadable: {error}")
|
|
return
|
|
for modality, row in manifest["modalities"].items():
|
|
for field, location in (("corpus", row["corpus_file"]), ("queries", row["queries_file"])):
|
|
digest = sha256_file(SETS / location)
|
|
if digest != row[f"{field}_sha256"]:
|
|
problems.append(
|
|
f"{path}: {location} digest {digest[:12]} no longer matches the manifest "
|
|
f"{row[f'{field}_sha256'][:12]}; the suite was rebuilt without re-freezing"
|
|
)
|
|
if row["corpus_records"] != table[modality]["corpus"]:
|
|
problems.append(
|
|
f"{path}: {modality} manifest says {row['corpus_records']} sources, "
|
|
f"file holds {table[modality]['corpus']}"
|
|
)
|
|
if row["queries"] != table[modality]["queries"]:
|
|
problems.append(
|
|
f"{path}: {modality} manifest says {row['queries']} queries, "
|
|
f"file holds {table[modality]['queries']}"
|
|
)
|
|
|
|
|
|
def main() -> int:
|
|
problems: list[str] = []
|
|
table: dict[str, dict[str, int]] = {}
|
|
for modality in MODALITIES:
|
|
table[modality] = check_modality(modality, problems)
|
|
|
|
totals = {key: sum(row[key] for row in table.values()) for key in table[MODALITIES[0]]}
|
|
print(json.dumps({"modalities": table, "totals": totals}, indent=1, sort_keys=True))
|
|
if totals["answerable"] < 300:
|
|
problems.append(f"only {totals['answerable']} answerable queries; the plan demands at least 300")
|
|
check_manifest(table, problems)
|
|
|
|
if problems:
|
|
for problem in problems[:MAX_REPORTED]:
|
|
print(f"PROBLEM {problem}", file=sys.stderr)
|
|
print(f"{len(problems)} problem(s) total", file=sys.stderr)
|
|
return 1
|
|
print(f"OK: {totals['corpus']} sources, {totals['answerable']} answerable queries, "
|
|
f"{totals['unanswerable']} unanswerable")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|