v1.10.0 — Variant C figure_ids hard constraint + normalize BS3+BS4
Highlights since v1.9.2
- Variant C — figure_ids hard constraint ships as v1.10's headline change. Picks the winner of a 3-variant × 9-paper × 3-audit-cycle test (33 LLM runs across 3 git worktrees).
normalize_ocr_latexBS3+BS4 lifts verifier accuracy across all variants by collapsing LaTeX escape sequences (\%→%) and Unicode super/subscripts (³→3).- 3-cycle audit committee (each cycle = 3 specialist auditors in parallel) caught 5 bugs in variant C, all fixed before ship.
- 280/280 tests pass (+25 from v1.9.2's 255).
Full report: docs/v1_10_variant_comparison.md · External reference survey: docs/v1_10_external_reference.md
Variant C — figure_ids hard constraint
Before v1.10, figure embedding in the rendered output depended on the LLM voluntarily writing Fig. N literally in the prose. On figure-rich papers (ML papers with ≥10 figs, materials reviews with ≥20 figs), the LLM cited <30% of available figures, and the s09 binding silently dropped the rest.
Variant C makes figure citation a schema-level + verifier-level constraint:
GroundedClaim.figure_ids: list[str]— schema field tells the binding which figures the claim cites.- Prompt rule — for each figure listed in
section_figures, the LLM must emit a claim withfigure_ids=["Fig. N"]AND include the literalFig. N/图Nin text. - Verify advisory — accepted claims whose figure_ids don't appear literally in text get recorded as
figure_hint_unmetfor audit visibility (claim still kept). - Figure-retry pass — when ≥50% of available figures aren't mentioned post-verify, one strengthened LLM call adds them. Three swap guards (parity with retry-when-short β#3): more figures AND ≥1 verifier-accepted claim AND required-mention coverage not regressed.
- Env-gated whitelist —
LAZY_PAPER_FIGURE_ID_WHITELIST=1strips unknown fig_ids; default OFF because cycle 1+2 evidence (reading original PDFs) shows "unknown" fig_ids are usually s04_figures OCR-vs-paper-actual numbering misalignment, NOT LLM hallucination.
Validation — 33 LLM runs across 3 variants
M2 figure embed ratio (true distinct-figure count, not panels)
| Paper | available | A (env tuning) | B (cap tiering) | C (figure_ids) |
|---|---|---|---|---|
| ali2025_flash | 26 | 5 (19%) | 4 (15%) | 26 (100%) |
| he2023 | 8 | 4 (50%) | 7 (88%) | 8 (100%) |
| meng2024 | 7 | 4 (57%) | 5 (71%) | 7 (100%) |
| hif_1 (Adv Mat 62p) | 20 | 2 (10%) | 4 (20%) | 20 (100%) |
| hif_2 (DALL-E 2) | 17 | 3 (18%) | 5 (29%) | 17 (100%) |
M1 zero-variance probe (meng2024 × 3 runs)
| Variant | per run | mean | stdev |
|---|---|---|---|
| baseline (v1.9.2) | 10117 / 12742 / 13666 | 12175 | 1503 |
| A | 12819 / 12065 / 12521 | 12468 | 310 |
| B | 13740 / 13012 / 12002 | 12918 | 713 |
| C | 12703 / 13525 / 12849 | 13026 | 358 |
All 3 variants stay well below the spec §7 floor (baseline stdev = 1503).
M4 TestCase scores
| TestCase (max) | baseline | A | B | C |
|---|---|---|---|---|
| meng2024 T1 ch01 benchmark recovery (17) | 9 / 9 / 9 (stdev 0) | 5 / 9 / 9 (stdev 1.88) | 5 / 17 / 15 (stdev 5.25) | 9 / 9 / 9 (stdev 0) |
| ali2025_flash T4 ch14 comparison depth (5) | 4 / 3 | 4 | 3 | 5 |
| chai2026 T6 ch01 (4) | 4 / 4 | 3 | 3 | 4 |
| yang2025 T2 ch01 (3) | 3 / 3 | 3 | 3 | 3 |
| meng2024 T3 ch10 (5) | 3 / 3 / 5 | 4 / 5 / 4 | 5 / 4 / 4 | 4 / 4 / 4 |
Variant C is the only variant that preserves the meng2024 T1 = 9/9/9 zero-variance achievement of v1.9.x AND breaks baseline on ali2025_flash T4 (4 → 5).
Why not Variant A or B
- A (env tuning) introduces M4 variance (stdev 1.88 on T1) and Auditor 1 caught a content-quality error in cycle 2 (called PbZrO₃ "lead-free" — chemical-naming mistake). The env tuning is real lift; it'll be recommended in
.env.exampleas aLAZY_PAPER_MIN_SECTION_CHARS=1200 LAZY_PAPER_BEST_OF_N=3combo, not as a separate variant flag. - B (cap tiering) had the worst M4 variance (stdev 5.25 — can reach 17/17 peak but unstable) AND lowered M3 coverage rate (meng2024: A 71% vs B 64%; gaur2022: A 53% vs B 34%). cap=12 spreads LLM attention too thin. Deferred to v1.11 for redesign as
dynamic cap = min(comparator_count, 10).
normalize_ocr_latex BS3+BS4
Auditor 2 (cycle 1) inventoried 4 normalize blindspots causing 41-74 verifier false-rejects per s08 run. Cycle 2 broke them into BS1-4 with asymmetric fix difficulty:
- BS3 — LaTeX escape sequences:
\%,\&,\_,\^,\$lose their leading backslash so they match the LLM's unescaped quote. - BS4 — Unicode super/subscript folding: NFKD decomposes
³→3,₂→2. Greek letters (α/β/π) NOT decomposed. NFKD doesn't fold U+2212 / U+2013 / U+2014 to ASCII-, so an explicit_UNICODE_DASHsubstitution follows. - BS1+BS2 (letter-spaced subscript) deferred to v1.11 due to inherent OCR↔LLM asymmetry.
11 unit tests in stages/_common/tests/test_normalize.py lock in the new behavior.
3 audit cycles — caught & fixed bugs in variant C
| Cycle | Auditor | Bug | Fix commit |
|---|---|---|---|
| 1 | Auditor 3 | figure_ids advisory ran on rejected claims (double-report) |
2e30e1c |
| 1.5 | Auditor 3.2 | figure-retry missing C-1 coverage guard | 0cc056d |
| 1.5 | Auditor 3.2 | figure-retry missing C-2 min-accepted guard | 0cc056d (same) |
| 2 | Auditor 2 | advisory entries inflated audit log's "verifier_rejects" count | 535d035 |
| 3 | Final ship gate | env-gated whitelist needed for cycle 3 visibility | e3cb9e1 |
3 cycles ran the audit committee in parallel (each cycle = 3 specialist auditors with non-overlapping scopes, reading original PDFs + code + reference docs).
Side effects
M2_figures_embeddedmetric (scripts/collect_variant_metrics.py) was inflating values by counting<img>tags (which includes every panel of a multi-panel figure). Fixed to count<figure>blocks (one per distinct figure). Cycle 1 caught this — earlier "17/17 = 100%" readings on multi-panel-heavy papers were panel-count artifacts.- New
M2_figures_hallucinated_countfield surfaces s04_figures ↔ LLM numbering misalignment (v1.11 #2 candidate fix). M4 TestCase scoresare deterministic (regex + size + fuzzy substring — no LLM judge), so baseline recheck took 0 LLM cost and confirmed zero drift from v1.9.x scores.
Deferred to v1.11 (ranked by ROI)
| # | Item | Complexity |
|---|---|---|
| 1 | normalize_ocr_latex BS1+BS2 (letter-spaced subscript) | M |
| 2 | s04_figures caption-aware numbering (fix OCR vs paper number misalign) | M |
| 3 | prompt comparator gap — Jiang/Ma et al. systematically missed | M |
| 4 | template-vs-paper subject-mismatch graceful degrade | L |
| 5 | Variant B redesign: dynamic cap = min(comparator_count, 10) | S |
| 6 | real-time LLM cost meter into metrics.yaml (M6) | S |
| 7 | DOCX HYPERLINK dead-code fix (thread sources into renderers) | M |
| 8 | _merge_drafts 60-char prefix + (author, value) dedup | S |
| 9 | 6 hardcodes → env vars | S |
Release trail (since v1.9.2)
- v1.9.2 — 8-bug fix + 18-paper validation
- v1.10.0 — Variant C ship + normalize BS3+BS4 + 3-cycle audit
Full details: CHANGELOG.md