SurfSense/surfsense_evals/scripts/check_uploaded_status.py

78 lines
2.3 KiB
Python
Raw Normal View History

feat(evals): publish multimodal_doc parser_compare benchmark + n=171 report Adds the full parser_compare experiment for the multimodal_doc suite: six arms compared on 30 PDFs / 171 questions from MMLongBench-Doc with anthropic/claude-sonnet-4.5 across the board. Source code: - core/parsers/{azure_di,llamacloud,pdf_pages}.py: direct parser SDK callers (Azure Document Intelligence prebuilt-read/layout, LlamaParse parse_page_with_llm/parse_page_with_agent) used by the LC arms, bypassing the SurfSense backend so each (basic/premium) extraction is a clean A/B independent of backend ETL routing. - suites/multimodal_doc/parser_compare/{ingest,runner,prompt}.py: six-arm benchmark (native_pdf, azure_basic_lc, azure_premium_lc, llamacloud_basic_lc, llamacloud_premium_lc, surfsense_agentic) with byte-identical prompts per question, deterministic grader, Wilson CIs, and the per-page preprocessing tariff cost overlay. Reproducibility: - pyproject.toml + uv.lock pin pypdf, azure-ai-documentintelligence, llama-cloud-services as new deps. - .env.example documents the AZURE_DI_* and LLAMA_CLOUD_API_KEY env vars now required for parser_compare. - 12 analysis scripts under scripts/: retry pass with exponential backoff, post-retry accuracy merge, McNemar / latency / per-PDF stats, context-overflow hypothesis test, etc. Each produces one number cited by the blog report. Citation surface: - reports/blog/multimodal_doc_parser_compare_n171_report.md: 1219-line technical writeup (16 sections) covering headline accuracy, per-format accuracy, McNemar pairwise significance, latency / token / per-PDF distributions, error analysis, retry experiment, post-retry final accuracy, cost amortization model with closed-form derivation, threats to validity, and reproducibility appendix. - data/multimodal_doc/runs/2026-05-14T00-53-19Z/parser_compare/{raw, raw_retries,raw_post_retry}.jsonl + run_artifact.json + retry summary whitelisted via data/.gitignore as the verifiable numbers source. Gitignore: - ignore logs_*.txt + retry_run.log; structured artifacts cover the citation surface, debug logs are noise. - data/.gitignore default-ignores everything, whitelists the n=171 run artifacts only (parser manifest left ignored to avoid leaking local Windows usernames in absolute paths; manifest is fully regenerable via 'ingest multimodal_doc parser_compare'). - reports/.gitignore now whitelists hand-curated reports/blog/. Also retires the abandoned CRAG Task 3 implementation (download script, streaming Task 3 ingest, CragTask3Benchmark + tests) and trims the runner / ingest module APIs to match. Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-14 19:54:41 -07:00
"""Query SurfSense for the status of every MMLongBench PDF in scope.
Uses the existing SurfSense documents client to query
``/documents/status?document_ids=...`` for both the known-existing 5
PDFs (doc ids 5219-5223) and the recently-uploaded mmlongbench batch
(7577-7600 range).
"""
from __future__ import annotations
import asyncio
import os
from pathlib import Path
import httpx
from dotenv import load_dotenv
REPO = Path(__file__).resolve().parents[1]
PDF_DIR = REPO / "data" / "multimodal_doc" / "mmlongbench" / "pdfs"
async def main() -> None:
load_dotenv(REPO / ".env")
base = os.environ.get("SURFSENSE_API_BASE", "http://localhost:8000").rstrip("/")
token = os.environ.get("SURFSENSE_JWT")
if not token:
raise SystemExit("SURFSENSE_JWT missing from .env")
pdf_names = sorted(p.name for p in PDF_DIR.glob("*.pdf"))
print(f"local cached PDFs: {len(pdf_names)}")
candidate_ids = list(range(5219, 5224)) + list(range(7577, 7625))
headers = {
"Authorization": f"Bearer {token}",
"Accept": "application/json",
}
async with httpx.AsyncClient(timeout=30.0) as http:
r = await http.get(
f"{base}/api/v1/documents/status",
params={
"search_space_id": 55,
"document_ids": ",".join(str(d) for d in candidate_ids),
},
headers=headers,
)
r.raise_for_status()
items = r.json().get("items", [])
by_title: dict[str, dict] = {}
for it in items:
by_title[it.get("title", "")] = {
"id": it.get("id"),
"state": (it.get("status") or {}).get("state"),
"reason": (it.get("status") or {}).get("reason"),
}
by_state: dict[str, int] = {}
print()
for name in pdf_names:
info = by_title.get(name)
if info is None:
print(f" [missing ] {name}")
by_state["missing"] = by_state.get("missing", 0) + 1
else:
tag = info["state"] or "?"
print(f" [{tag:13s}] doc_id={info['id']:>5} {name}")
by_state[tag] = by_state.get(tag, 0) + 1
print()
print("summary:")
for k, v in sorted(by_state.items()):
print(f" {k}: {v}")
if __name__ == "__main__":
asyncio.run(main())