# HumOmni 2026 · Track 2 (ProactivEval) — proactive streaming video QA Watch a video at 2 fps, given one question at t=0, and decide **when** to speak and **what** to say — causally (using only frames with timestamp ≤ t). Ranking score = **0.9·PAUC + 0.1·(1−duplicate)**; hidden judge = Gemini 3.1 Flash-Lite. Baseline MMDuet2 = 53.3; public leader at the time = 60.21. This repository is the **Phase-1 system**: a causal streaming orchestrator around a hosted VLM (`qwen/qwen3.7-plus` via OpenRouter). No training and no local large models — it runs on a single 8 GB GPU. ## The method in one paragraph The hidden judge is **recall-only** — it asks whether the accumulated response text *covers* the reference answer's key points, with **no precision penalty** for extra detail. So the winning strategy is **comprehensive coverage**: fire on every frame (gate off), prompt the VLM to describe the scene exhaustively and read all on-screen text verbatim, and strip only true repeats with an LLM-entailment dedup. The final config queries a small **prompt ensemble** per frame — `v5` (exhaustive + OCR) + `v3` (comprehensive) + `v8` (few-shot, teach-by-example) — and merges their drafts. The big lever was the **prompt**, not timing or a bigger model. ## Result (WEB dev set, faithful Gemini judge, untouched holdout `gold[400:500]`) | stage | score | note | |---|---|---| | baseline (focused prompt + throttle gate) | ~0.61 | the original leaderboard submission | | + gate-off (fire every frame) | 0.625 | throttling loses coverage | | + comprehensive prompt (`v3`) | 0.733 | recall-only judge ⇒ coverage wins | | + exhaustive + OCR (`v5`), recent_k=16 | 0.7761 | best single prompt | | + `v5`+`v3` ensemble @ recent_k=32 | 0.7909 | robust 2× config | | + `v8` few-shot (`v5`+`v3`+`v8` @ k40) | **0.8018** | **the final submission** (gate off) | | + perception gate (novelty ≥ 16, silence 3 s) | 0.7853 | optional lower-cost operating point (−51% calls) | **Caveat:** WEB-dev is the leakage-free dev set, **not** the phase1 test set (which has no ground truth). The `v8` gain is partly holdout-overfit — the robust gain over `v5`+`v3` is ~+0.002–0.005. Our PAUC harness is verified bit-exact against the official scorer, and the grader is confirmed Gemini 3.1 Flash-Lite. ## Project layout ``` src/humomni/ core/ streaming_driver · context · pauc_eval · judges · emb_cache · env_util · validate_submission phase1/ vlm_client · dedup · policy_api · policies · perception_gate tuning/ cache · faithful_eval scripts/ build_submission.py · run_inference.py · infer_simple.py tests/ test_causality.py · test_inference_parity.py config.json .env.example pyproject.toml ``` Run every command **from the repo root** — modules use repo-root-relative `data/` and `cache/` paths. ## Setup ```bash python -m venv .venv && .venv\Scripts\activate # Windows (use: source .venv/bin/activate on *nix) pip install -e . # installs humomni.* + deps (pyproject.toml) cp .env.example .env # (cmd.exe: copy) then fill in the keys (below) ``` `.env` needs `OPENROUTER_API_KEY` (the VLM) and `VERTEX_KEY` (the Gemini judge used for the LLM-entailment dedup) — only for regenerating decisions from frames; the offline replay needs no keys. Put the test set (the official Track-2 phase-1 release) at `data/phase1/data//` — frames `0.5.jpg, 1.0.jpg, …` plus `question.json` per sample. (This is the `data//` layout the report appendix refers to, rooted at `data/phase1/data/`; a different location needs `--data_dir`, and a wrong path now aborts loudly.) ## Build the submission — one command `config.json` holds the entire method (`"ensemble": ["v5","v3","v8"] @ 40`, `dedup`/`theta`, and the perception `"gate"` disabled by default — `use_gate:false` gives the shipped max-score point; enabling it at novelty ≥ 16 / silence 3 s selects a lower-cost operating point): ```bash python scripts/build_submission.py --out submissions/tryanderror2.jsonl ``` For each ensemble prompt it ensures a per-prompt VLM decision cache — **building it from the test frames if missing** (paid: ~1 VLM call per prompt per frame; resumable, skips cached frames) — then replays the merge + LLM-entailment dedup and **validates** the output (all 500 ids present, times multiples of 0.5). Re-runs are free once the caches exist. The live equivalent produces byte-identical output (paid on every run — use when caches are absent): ```bash python scripts/run_inference.py --policy api --workers 8 --out submissions/tryanderror2.jsonl python scripts/infer_simple.py --policy api --out submissions/tryanderror2.jsonl # minimal twin ``` Both drive the same `APIPolicy` from `config.json`, so all three commands point at the same method (live ≡ replay). Upload is manual: the file must be named exactly your Team ID. ## Tests ```bash python tests/test_causality.py # causality assert fires on shuffled / out-of-order frames python tests/test_inference_parity.py # run_inference == infer_simple; ensemble multi-emit per frame ```