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
268 lines
9.4 KiB
Python
268 lines
9.4 KiB
Python
"""Download pinned corpus files and record a frozen manifest.
|
|
|
|
Upstream LFS pointers are unreliable: some declare a wrong size, some name an
|
|
oid that does not match the served blob. Files are hashed as served, every
|
|
pointer discrepancy is recorded in the manifest, and the corpus builder drops
|
|
documents whose annotations reference pages the served file lacks.
|
|
"""
|
|
|
|
import argparse
|
|
import hashlib
|
|
import json
|
|
import subprocess
|
|
import sys
|
|
import urllib.error
|
|
import urllib.parse
|
|
import urllib.request
|
|
from datetime import datetime, timezone
|
|
from pathlib import Path
|
|
|
|
from bench.corpus_sources import GIT_REPOS, SOURCES
|
|
from bench.sets_common import parse_json_line
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
CORPUS = ROOT / "bench" / "corpus"
|
|
MANIFEST = CORPUS / "manifest.jsonl"
|
|
UA = {"User-Agent": "ely-eye-bench"}
|
|
OPENER = urllib.request.build_opener(urllib.request.HTTPSHandler())
|
|
|
|
|
|
def http_get(url: str) -> tuple[bytes, dict[str, str]]:
|
|
if urllib.parse.urlsplit(url).scheme != "https" or urllib.parse.urlsplit(url).netloc != "huggingface.co":
|
|
raise SystemExit(f"refusing non-HF url: {url}")
|
|
try:
|
|
resp = OPENER.open(urllib.request.Request(url, headers=UA), timeout=300) # noqa: S310
|
|
except urllib.error.HTTPError as exc:
|
|
raise SystemExit(f"HTTP {exc.code} {url}") from exc
|
|
except urllib.error.URLError as exc:
|
|
raise SystemExit(f"network {exc.reason} {url}") from exc
|
|
with resp:
|
|
return resp.read(), dict(resp.headers)
|
|
|
|
|
|
def sha256_bytes(data: bytes) -> str:
|
|
return hashlib.sha256(data).hexdigest()
|
|
|
|
|
|
def api(url: str) -> tuple[list, dict[str, str]]:
|
|
body, headers = http_get(url)
|
|
try:
|
|
payload = json.loads(body.decode("utf-8"))
|
|
except (json.JSONDecodeError, UnicodeDecodeError) as exc:
|
|
raise SystemExit(f"bad JSON from {url}: {exc}") from exc
|
|
return payload, headers
|
|
|
|
|
|
def next_link(headers: dict[str, str]) -> str | None:
|
|
header = headers.get("Link")
|
|
if not header:
|
|
return None
|
|
for part in header.split(","):
|
|
head, _, rel = part.partition(">")
|
|
if 'rel="next"' in rel:
|
|
return head.strip().strip("<>")
|
|
return None
|
|
|
|
|
|
def list_files(dataset: str, revision: str) -> list[dict]:
|
|
out: list[dict] = []
|
|
url: str | None = f"https://huggingface.co/api/datasets/{dataset}/tree/{revision}?recursive=true"
|
|
while url:
|
|
batch, headers = api(url)
|
|
out += [f for f in batch if f["type"] == "file"]
|
|
url = next_link(headers)
|
|
return out
|
|
|
|
|
|
def file_digest(path: Path) -> tuple[int, str]:
|
|
digest = hashlib.sha256()
|
|
size = 0
|
|
with path.open("rb") as handle:
|
|
for block in iter(lambda: handle.read(1 << 20), b""):
|
|
digest.update(block)
|
|
size += len(block)
|
|
return size, digest.hexdigest()
|
|
|
|
|
|
def download_to(url: str, dest: Path) -> tuple[int, str]:
|
|
dest.parent.mkdir(parents=True, exist_ok=True)
|
|
tmp = dest.with_suffix(dest.suffix + ".part")
|
|
digest = hashlib.sha256()
|
|
size = 0
|
|
try:
|
|
resp = OPENER.open(urllib.request.Request(url, headers=UA), timeout=600) # noqa: S310
|
|
except urllib.error.HTTPError as exc:
|
|
raise SystemExit(f"HTTP {exc.code} {url}") from exc
|
|
except urllib.error.URLError as exc:
|
|
raise SystemExit(f"network {exc.reason} {url}") from exc
|
|
try:
|
|
with resp, tmp.open("wb") as handle:
|
|
for block in iter(lambda: resp.read(1 << 20), b""):
|
|
digest.update(block)
|
|
size += len(block)
|
|
handle.write(block)
|
|
except OSError as exc:
|
|
tmp.unlink(missing_ok=True)
|
|
raise SystemExit(f"write failed for {url}: {exc}") from exc
|
|
tmp.replace(dest)
|
|
return size, digest.hexdigest()
|
|
|
|
|
|
def check_path(path: str) -> str:
|
|
if path.startswith("/") or ".." in Path(path).parts:
|
|
raise SystemExit(f"unsafe corpus path: {path}")
|
|
return path
|
|
|
|
|
|
def pointer_for(dataset: str, revision: str, path: str) -> tuple[str | None, int | None]:
|
|
raw, _ = http_get(f"https://huggingface.co/datasets/{dataset}/raw/{revision}/{path}")
|
|
if not raw.startswith(b"version https://git-lfs"):
|
|
return None, None
|
|
fields = dict(
|
|
line.decode().split(" ", 1) for line in raw.splitlines() if b" " in line
|
|
)
|
|
oid = fields.get("oid", "").removeprefix("sha256:") or None
|
|
if "size" not in fields:
|
|
return oid, None
|
|
try:
|
|
size = int(fields["size"])
|
|
except ValueError as exc:
|
|
raise SystemExit(f"bad LFS pointer size for {dataset}/{path}: {fields['size']!r}") from exc
|
|
return oid, size
|
|
|
|
|
|
def fetch_file(dataset: str, revision: str, path: str) -> dict:
|
|
check_path(path)
|
|
oid, declared = pointer_for(dataset, revision, path)
|
|
url = f"https://huggingface.co/datasets/{dataset}/resolve/{revision}/{path}"
|
|
target = CORPUS / dataset / path
|
|
if target.exists() and target.stat().st_size > 0:
|
|
size, actual = file_digest(target)
|
|
fresh = False
|
|
else:
|
|
size, actual = download_to(url, target)
|
|
fresh = True
|
|
return {
|
|
"dataset": dataset,
|
|
"revision": revision,
|
|
"path": path,
|
|
"url": url,
|
|
"bytes": size,
|
|
"sha256": actual,
|
|
"lfs_pointer_oid": oid,
|
|
"lfs_pointer_bytes": declared,
|
|
"oid_match": oid is None or actual == oid,
|
|
"newly_downloaded": fresh,
|
|
"retrieved_at": datetime.now(timezone.utc).isoformat(timespec="seconds"),
|
|
}
|
|
|
|
|
|
def fetch_git(repo: dict) -> dict:
|
|
name = repo["repo"].rsplit("/", 1)[-1]
|
|
target = CORPUS / "_git" / name
|
|
fresh = not target.exists()
|
|
if fresh:
|
|
subprocess.run(
|
|
["git", "clone", "--depth", "1", "--branch", repo["ref"], repo["repo"], str(target)],
|
|
check=True,
|
|
stdout=subprocess.DEVNULL,
|
|
)
|
|
head = subprocess.run(
|
|
["git", "-C", str(target), "rev-parse", "HEAD"], check=True, capture_output=True, text=True
|
|
).stdout.strip()
|
|
pinned = repo.get("sha")
|
|
if pinned and head != pinned:
|
|
raise SystemExit(
|
|
f"{repo['repo']}: checked-out HEAD {head} != pinned sha {pinned}; "
|
|
"re-fetch at the pinned commit or re-pin deliberately"
|
|
)
|
|
tracked = [f for f in target.rglob("*") if f.is_file() and ".git" not in f.parts]
|
|
return {
|
|
"dataset": repo["repo"],
|
|
"revision": head,
|
|
"path": f"_git/{name}",
|
|
"url": f"{repo['repo']}/archive/{head}.tar.gz",
|
|
"bytes": sum(f.stat().st_size for f in tracked),
|
|
"sha256": head,
|
|
"lfs_pointer_oid": None,
|
|
"lfs_pointer_bytes": None,
|
|
"oid_match": True,
|
|
"newly_downloaded": fresh,
|
|
"retrieved_at": datetime.now(timezone.utc).isoformat(timespec="seconds"),
|
|
"file_count": len(tracked),
|
|
}
|
|
|
|
|
|
def existing_rows(exclude: set[str]) -> list[dict]:
|
|
"""Keep previously fetched rows for datasets this run does not re-fetch."""
|
|
if not MANIFEST.is_file():
|
|
return []
|
|
rows = []
|
|
for number, line in enumerate(MANIFEST.read_text(encoding="utf-8").splitlines(), start=1):
|
|
if not line.strip():
|
|
continue
|
|
row = parse_json_line(line, f"{MANIFEST}:{number}")
|
|
if row["dataset"] not in exclude:
|
|
rows.append(row)
|
|
return rows
|
|
|
|
|
|
def main() -> int:
|
|
ap = argparse.ArgumentParser()
|
|
ap.add_argument("--only", help="fetch one dataset id")
|
|
ap.add_argument("--skip-git", action="store_true")
|
|
args = ap.parse_args()
|
|
|
|
CORPUS.mkdir(parents=True, exist_ok=True)
|
|
sources = [s for s in SOURCES if not args.only or s["dataset"] == args.only]
|
|
covered = {source["dataset"] for source in sources}
|
|
if not args.only and not args.skip_git:
|
|
covered |= {repo["repo"] for repo in GIT_REPOS}
|
|
rows = existing_rows(exclude=covered)
|
|
for source in sources:
|
|
if args.only and source["dataset"] != args.only:
|
|
continue
|
|
if source["files"] == "ALL":
|
|
names = [f["path"] for f in list_files(source["dataset"], source["revision"])]
|
|
else:
|
|
names = source["files"]
|
|
print(f"fetching {source['dataset']} ({len(names)} files)", flush=True)
|
|
for name in names:
|
|
row = fetch_file(source["dataset"], source["revision"], name)
|
|
flags = ""
|
|
if not row["oid_match"]:
|
|
flags += " <-- LFS oid mismatch"
|
|
if row["lfs_pointer_bytes"] not in (None, row["bytes"]):
|
|
flags += " <-- LFS size mismatch"
|
|
print(f" {row['bytes']:>10} {name}{flags}", flush=True)
|
|
rows.append(
|
|
{
|
|
**row,
|
|
"modality": source["modality"],
|
|
"license": source["license"],
|
|
"snapshot_at": source["snapshot_at"],
|
|
"annotation_source": source["annotation_source"],
|
|
}
|
|
)
|
|
if not args.only and not args.skip_git:
|
|
for repo in GIT_REPOS:
|
|
print(f"cloning {repo['repo']}", flush=True)
|
|
rows.append(
|
|
{
|
|
**fetch_git(repo),
|
|
"modality": repo["modality"],
|
|
"license": "see-repo",
|
|
"snapshot_at": None,
|
|
"annotation_source": repo["annotation_source"],
|
|
}
|
|
)
|
|
|
|
rows.sort(key=lambda r: (r["dataset"], r["path"]))
|
|
MANIFEST.write_text("".join(json.dumps(r, sort_keys=True) + "\n" for r in rows), encoding="utf-8")
|
|
print(f"manifest: {len(rows)} files, {sum(r['bytes'] for r in rows) / 1e9:.2f} GB -> {MANIFEST}")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|