The 500-line rule and the one-line-comment rule were enforced only from
memory, so they drifted. bench/filesize_check.py now checks the budget on
every tracked source file, and it passes.
Split, with behaviour proven unchanged by comparing each moved function body
against HEAD byte for byte (53 training bodies, 21 benchmark bodies, 0
differences):
db.py 503 -> 396 store_search.py 105
bench/run.py 609 -> 351 query_set.py 80, report.py 211
benchmarks.py 619 -> 424 benchmark_scoring.py 262
training.py 1630 -> 94 training_{common,encode,metrics,datasets,lora,retrieval}
TrainingService stays as a thin facade for the CLI; the facade's parameter
lists are pinned against the module functions by inspect, which is what
catches a leaked self.
Deleted the eight top-level functions no file called, after fixing the scan
that had reported 201 false positives by skipping the defining file. The
v1/v2/v3 sweep found nothing to delete and says so: every remaining version
tag is an on-disk format marker, a migration step, a fixture name, or a
third-party model name.
Collapsed 39 multi-line comment blocks (86 lines) to one line each, keeping
the measured facts and error strings that justify the code.
269 tests pass; ruff clean; mypy 24 errors in 9 files against a 24-in-6
baseline, net zero; pyright 4, the same four HEAD measures.
312 lines
11 KiB
Python
312 lines
11 KiB
Python
"""Shared helpers for building and validating benchmark annotation sets."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import hashlib
|
|
import json
|
|
import os
|
|
import re
|
|
from collections.abc import Iterable, Iterator
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
ROOT = Path(__file__).resolve().parent
|
|
CORPUS = ROOT / "corpus"
|
|
SETS = ROOT / "sets"
|
|
# Derived media (parquet images, video frames, crops) lives outside the repository: tens of thousands of new files inside it swamps repo tooling.
|
|
DATA_ROOT = Path(os.environ.get("ELY_EYE_BENCH_DATA", str(Path.home() / "ely_eye_bench_data")))
|
|
CORPUS_ROOTS = {"corpus": CORPUS, "derived": DATA_ROOT}
|
|
MODALITIES = ("pdf", "image", "ui_screenshot", "video", "code", "conversation")
|
|
REQUIRED_QUERY_FIELDS = (
|
|
"query_id",
|
|
"modality",
|
|
"question",
|
|
"answer",
|
|
"answer_format",
|
|
"gold",
|
|
"scope",
|
|
"as_of",
|
|
"unanswerable",
|
|
"origin",
|
|
"split",
|
|
)
|
|
REQUIRED_CORPUS_FIELDS = (
|
|
"source_id",
|
|
"modality",
|
|
"path",
|
|
"root",
|
|
"title",
|
|
"observed_at",
|
|
"holder",
|
|
"scenario",
|
|
)
|
|
|
|
|
|
def sha256_file(path: Path) -> str:
|
|
digest = hashlib.sha256()
|
|
with path.open("rb") as handle:
|
|
for block in iter(lambda: handle.read(1 << 20), b""):
|
|
digest.update(block)
|
|
return digest.hexdigest()
|
|
|
|
|
|
SAFE_ID = re.compile(r"^[A-Za-z0-9_-][A-Za-z0-9._-]*$")
|
|
UNSAFE = re.compile(r"[^A-Za-z0-9._-]+")
|
|
|
|
|
|
def safe_id(value: str, where: str) -> str:
|
|
"""Reject upstream identifiers that could escape their directory when used as a name."""
|
|
if not SAFE_ID.match(value) or ".." in value:
|
|
raise SystemExit(f"{where}: unsafe identifier {value!r}")
|
|
return value
|
|
|
|
|
|
def safe_segment(value: str, where: str) -> str:
|
|
"""Rewrite an upstream path segment so it cannot escape its directory.
|
|
|
|
Unlike safe_id this keeps every dataset row: real repositories contain braces,
|
|
spaces, and unicode. Uniqueness is the caller's job (append a hash of the
|
|
original value when several values could collide).
|
|
"""
|
|
cleaned = UNSAFE.sub("-", value).strip(".-")
|
|
if not cleaned or cleaned == "..":
|
|
raise SystemExit(f"{where}: no usable name in {value!r}")
|
|
return cleaned
|
|
|
|
|
|
def as_int(value: Any, where: str) -> int:
|
|
"""Parse a third-party numeric field, naming the offending value on failure."""
|
|
try:
|
|
return int(value)
|
|
except (TypeError, ValueError) as exc:
|
|
raise SystemExit(f"{where}: expected an integer, got {value!r}") from exc
|
|
|
|
|
|
def parse_json_object(text: str, where: str) -> dict[str, Any]:
|
|
try:
|
|
parsed = json.loads(text)
|
|
except json.JSONDecodeError as exc:
|
|
raise SystemExit(f"malformed json at {where}: {exc}") from exc
|
|
if not isinstance(parsed, dict):
|
|
raise SystemExit(f"expected a json object at {where}")
|
|
return parsed
|
|
|
|
|
|
def parse_json_line(text: str, where: str) -> dict[str, Any]:
|
|
try:
|
|
return json.loads(text)
|
|
except json.JSONDecodeError as exc:
|
|
raise SystemExit(f"malformed jsonl at {where}: {exc}") from exc
|
|
|
|
|
|
def parse_json_array(text: str, where: str) -> list[Any]:
|
|
try:
|
|
parsed = json.loads(text)
|
|
except json.JSONDecodeError as exc:
|
|
raise SystemExit(f"malformed json at {where}: {exc}") from exc
|
|
if not isinstance(parsed, list):
|
|
raise SystemExit(f"expected a json array at {where}")
|
|
return parsed
|
|
|
|
|
|
def load_manifest() -> list[dict[str, Any]]:
|
|
manifest = CORPUS / "manifest.jsonl"
|
|
if not manifest.is_file():
|
|
raise SystemExit(f"missing corpus manifest: {manifest} (run bench.fetch_corpus)")
|
|
return [
|
|
parse_json_line(line, f"{manifest}:{number}")
|
|
for number, line in enumerate(manifest.read_text().splitlines(), start=1)
|
|
if line.strip()
|
|
]
|
|
|
|
|
|
def manifest_index() -> dict[tuple[str, str], dict[str, Any]]:
|
|
return {(row["dataset"], row["path"]): row for row in load_manifest()}
|
|
|
|
|
|
def corpus_path(dataset: str, path: str) -> Path:
|
|
entry = manifest_index().get((dataset, path))
|
|
if entry is None:
|
|
raise SystemExit(f"{dataset}/{path} is not in the frozen corpus manifest")
|
|
target = CORPUS / dataset / path
|
|
if not target.is_file():
|
|
raise SystemExit(f"manifest entry missing on disk: {target}")
|
|
return target
|
|
|
|
|
|
def record_path(record: dict[str, Any]) -> Path:
|
|
"""Resolve a corpus record's path against the root named by its 'root' field."""
|
|
root_name = record.get("root")
|
|
if root_name not in CORPUS_ROOTS:
|
|
raise SystemExit(f"unknown corpus root {root_name!r} for {record.get('source_id')}")
|
|
relative = str(record["path"])
|
|
if Path(relative).is_absolute():
|
|
raise SystemExit(f"corpus path must be relative: {relative}")
|
|
return CORPUS_ROOTS[str(root_name)] / relative
|
|
|
|
|
|
def split_for(key: str, dev_modulus: int = 3) -> str:
|
|
"""Deterministic split: no RNG, so re-running the builder cannot reshuffle."""
|
|
digest = hashlib.sha256(key.encode()).digest()
|
|
return "dev" if digest[0] % dev_modulus == 0 else "test"
|
|
|
|
|
|
def query_record(
|
|
*,
|
|
query_id: str,
|
|
modality: str,
|
|
question: str,
|
|
answer: str | None,
|
|
answer_format: str,
|
|
gold: list[dict[str, Any]],
|
|
holder: str,
|
|
scenario: str,
|
|
as_of: str,
|
|
origin: dict[str, Any],
|
|
session: str | None = None,
|
|
) -> dict[str, Any]:
|
|
unanswerable = answer is None
|
|
if unanswerable and gold:
|
|
raise SystemExit(f"{query_id}: unanswerable queries must have empty gold")
|
|
if not unanswerable and not gold:
|
|
raise SystemExit(f"{query_id}: answerable queries need at least one gold region")
|
|
if modality not in MODALITIES:
|
|
raise SystemExit(f"{query_id}: unknown modality {modality!r}")
|
|
return {
|
|
"query_id": query_id,
|
|
"modality": modality,
|
|
"question": question,
|
|
"answer": answer,
|
|
"answer_format": answer_format,
|
|
"gold": gold,
|
|
"scope": {"holder": holder, "session": session, "scenario": scenario},
|
|
"as_of": as_of,
|
|
"unanswerable": unanswerable,
|
|
"origin": origin,
|
|
"split": split_for(query_id),
|
|
}
|
|
|
|
|
|
def sample_by_holder(
|
|
queries: list[dict[str, Any]], per_holder: int = 40
|
|
) -> list[dict[str, Any]]:
|
|
"""Deterministic stratified pick so every holder is represented at a fixed size."""
|
|
by_holder: dict[str, list[dict[str, Any]]] = {}
|
|
for query in queries:
|
|
by_holder.setdefault(query["scope"]["holder"], []).append(query)
|
|
picked: list[dict[str, Any]] = []
|
|
for holder in sorted(by_holder):
|
|
rows = sorted(by_holder[holder], key=lambda row: row["query_id"])
|
|
step = max(1, len(rows) // per_holder)
|
|
picked.extend(rows[index] for index in range(0, len(rows), step)[:per_holder])
|
|
return picked
|
|
|
|
|
|
def corpus_record(
|
|
*,
|
|
source_id: str,
|
|
modality: str,
|
|
path: str,
|
|
title: str,
|
|
observed_at: str,
|
|
holder: str,
|
|
scenario: str,
|
|
root: str = "corpus",
|
|
extra: dict[str, Any] | None = None,
|
|
) -> dict[str, Any]:
|
|
if modality not in MODALITIES:
|
|
raise SystemExit(f"{source_id}: unknown modality {modality!r}")
|
|
if root not in CORPUS_ROOTS:
|
|
raise SystemExit(f"{source_id}: unknown corpus root {root!r}")
|
|
record: dict[str, Any] = {
|
|
"source_id": source_id,
|
|
"modality": modality,
|
|
"path": path,
|
|
"root": root,
|
|
"title": title,
|
|
"observed_at": observed_at,
|
|
"holder": holder,
|
|
"scenario": scenario,
|
|
}
|
|
record.update(extra or {})
|
|
return record
|
|
|
|
|
|
def write_jsonl(path: Path, records: Iterable[dict[str, Any]]) -> tuple[int, str]:
|
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
count = 0
|
|
with path.open("w", encoding="utf-8") as handle:
|
|
for record in records:
|
|
handle.write(json.dumps(record, ensure_ascii=False, sort_keys=True) + "\n")
|
|
count += 1
|
|
if count == 0:
|
|
raise SystemExit(f"{path.name}: builder produced zero records")
|
|
return count, sha256_file(path)
|
|
|
|
|
|
def read_jsonl(path: Path) -> Iterator[dict[str, Any]]:
|
|
with path.open(encoding="utf-8") as handle:
|
|
for number, line in enumerate(handle, start=1):
|
|
if line.strip():
|
|
yield parse_json_line(line, f"{path}:{number}")
|
|
|
|
|
|
def validate_query(record: dict[str, Any]) -> list[str]:
|
|
problems: list[str] = []
|
|
missing = [key for key in REQUIRED_QUERY_FIELDS if key not in record]
|
|
if missing:
|
|
problems.append(f"missing fields {missing}")
|
|
return problems
|
|
if record["modality"] not in MODALITIES:
|
|
problems.append(f"unknown modality {record['modality']!r}")
|
|
if record["unanswerable"] != (record["answer"] is None):
|
|
problems.append("unanswerable flag disagrees with answer presence")
|
|
if record["unanswerable"] and record["gold"]:
|
|
problems.append("unanswerable query carries gold regions")
|
|
if not record["unanswerable"] and not record["gold"]:
|
|
problems.append("answerable query has no gold regions")
|
|
for gold in record["gold"]:
|
|
if "source_id" not in gold or "region" not in gold:
|
|
problems.append(f"gold entry missing source_id/region: {gold}")
|
|
continue
|
|
problems.extend(validate_region(record["modality"], gold["region"]))
|
|
scope = record["scope"]
|
|
if not scope.get("holder"):
|
|
problems.append("scope.holder is required")
|
|
if not record["origin"].get("dataset") or not record["origin"].get("revision"):
|
|
problems.append("origin.dataset and origin.revision are required")
|
|
if record["split"] not in ("dev", "test"):
|
|
problems.append(f"bad split {record['split']!r}")
|
|
return problems
|
|
|
|
|
|
def validate_region(modality: str, region: dict[str, Any]) -> list[str]:
|
|
keys = set(region)
|
|
if modality == "pdf" and not keys <= {"page", "block"}:
|
|
return [f"pdf region has unexpected keys {sorted(keys)}"]
|
|
if modality == "ui_screenshot" and keys != {"bbox"}:
|
|
return [f"ui_screenshot region must be exactly {{bbox}}, got {sorted(keys)}"]
|
|
if modality == "video" and not keys <= {"t_start", "t_end"}:
|
|
return [f"video region has unexpected keys {sorted(keys)}"]
|
|
if modality == "code" and keys not in ({"path", "symbol"}, {"path", "line"}):
|
|
return [f"code region must be {{path,symbol}} or {{path,line}}, got {sorted(keys)}"]
|
|
if modality == "conversation" and not keys <= {"turn"}:
|
|
# Empty means the whole session: LongMemEval marks some answers only at session level.
|
|
return [f"conversation region may only hold {{turn}}, got {sorted(keys)}"]
|
|
if modality == "image" and keys:
|
|
return [f"image region must be empty, got {sorted(keys)}"]
|
|
return []
|
|
|
|
|
|
def validate_corpus(record: dict[str, Any]) -> list[str]:
|
|
missing = [key for key in REQUIRED_CORPUS_FIELDS if key not in record]
|
|
if missing:
|
|
return [f"missing fields {missing}"]
|
|
if record["modality"] not in MODALITIES:
|
|
return [f"unknown modality {record['modality']!r}"]
|
|
if record["root"] not in CORPUS_ROOTS:
|
|
return [f"unknown corpus root {record['root']!r}"]
|
|
if not record_path(record).is_file():
|
|
return [f"file missing: {record_path(record)}"]
|
|
return []
|