Skip to content

Commit fc65a83

Browse files
shaypal5claude
andauthored
spike(eval): M17 Phase C — multimodal LLM judge gates (script + report stub) (#113)
* spike(eval): M17 Phase C — multimodal LLM judge gates (script + report stub) Mirrors the Phase A spike protocol (scripts/m17_phase_a_validation.py + docs/m17_phase_a_validation_report.md) for E5 — the Multimodal LLM Judge evaluator from docs/automated_eval_design.md §E5. This unlocks the eval loop that issues #92 and #97 are currently blocked on (both require native-speaker listening which doesn't scale). Script (scripts/m17_phase_c_validation.py): - 6-clip set: 4 corpus typology spans (SV/IT/NEG/NEU, all M2a-wettest agg_m_30-45_001) + 2 degraded variants of sp_sv_a_0001_00 (white noise at +10 dB and -10 dB SNR, RMS-matched to the clean source so the judge is scoring spectral content, not amplitude). - Two models: gemini-2.5-pro (audio input) and gpt-4o-audio-preview. - Two reruns per (clip, model) for within-model variance measurement. - Structured JSON output via a fixed schema (8 quality dimensions on a 1-5 scale + artifacts_detected + confidence_in_assessment + summary). - Four gate outcomes per model: refusal_gate ≥ 5/6 clips scored under DV-research framing discrimination_gate mean(corpus) − mean(severe-degraded) ≥ 0.5 variance_gate per-dim std across 2 reruns ≤ 0.5 shay_correlation_gate Spearman ρ ≥ 0.3 vs encoded expected ranking - Gemini BLOCK_NONE safety per design doc §E5 content-sensitivity guidance (research framing only — DV-content tolerance is required to avoid refusals on the metadata; the audio is entirely synthetic and contains no real persons). - Hard budget cap via SYNTHBANSHEE_LLM_SPIKE_BUDGET_USD (default $5); aborts pre-flight on estimate, aborts mid-run on cumulative actual. - --dry-run flag prints the prompt + schema without calling any API, for pre-flight prompt audit. - Lazy imports for google-genai / openai so the module is cheap to import even without the deps installed. Report stub (docs/m17_phase_c_validation_report.md): - Same structure as the Phase A report (TL;DR gate table, Reproduce, Clip-set manifest, Gate definitions, Per-model results, Failure-mode notes, Recommendation matrix, Limitations, Cost report). - All numeric cells marked TBD pending real-run results. - Recommendation matrix is pre-filled per gate-outcome scenario so the go/no-go decision is mechanical once the script runs. Cost estimate at default settings: ~$3.05 total ($0.35 Gemini + $2.70 GPT-4o), well within the $5 cap. Dry-run verified end-to-end. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix(spike/m17-phase-c): address all critical+high review findings before running Critical: - Replace wn_snr_+10db mid-anchor with synth_rate_slow_0.7x — tests synthesis defect detection (unnatural tempo + pitch-shift + truncated arc) instead of measuring only whether the model can hear noise - Add --probe-metadata-bias flag: runs a no_arc prompt variant on corpus clips to measure whether emotional_expression / escalation_arc are scored from audio or inferred from the intensity-arc label; delta is reported per-clip per-dim in both results.json and report_auto.md High: - Add failure_reason field to JudgeResult ("ok" / "content_refusal" / "json_parse_error" / "api_error") so only content refusals count against the refusal gate; api_error and json_parse_error are retried, not penalised - Incremental write: results_partial.jsonl is appended after each call; --resume loads prior results and skips completed (clip, model, run, variant) tuples — an API failure or budget-cap abort no longer loses all spend - Drop shay_correlation and variance from overall_pass (advisory only); add notes: variance is trivially PASS at TEMPERATURE=0 greedy decoding, Shay rho is not statistically significant at n=4 - Discrimination gate now tests two independent arms — noise_corruption and synth_failure — reporting both separations; passing either arm clears the gate Low: - Fix report stub: gate table columns aligned with report_auto.md output, hand-written per-run tables removed (replaced with pointers to auto-report), limitations section updated to reflect all of the above Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * docs(spike/m17-phase-c): fix module docstring — reflect two-arm discrimination gate Update the gate bullet-list in the module docstring to match what the code actually does after the review-fixes commit: two discrimination arms (noise_corruption comparability + synth_failure real target), advisory notes on variance and Shay-correlation, failure_reason semantics for refusal gate. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
1 parent 1ea48f3 commit fc65a83

2 files changed

Lines changed: 1444 additions & 0 deletions

File tree

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
# M17 Phase C Validation Report — Multimodal LLM Judge on Hebrew Clips
2+
3+
> **Status:** spike stub. Filled-in numbers land after the script runs against
4+
> real API keys. The gate tables below are skeletons — final values come from
5+
> `state/spikes/m17_phase_c/results.json` and the auto-generated
6+
> `state/spikes/m17_phase_c/report_auto.md`.
7+
8+
This report validates the Phase C acceptance criteria from the M17 design
9+
doc (`docs/automated_eval_design.md` §E5 — Multimodal LLM Judge) before the
10+
E5 evaluator module skeleton lands. Mirrors the Phase A spike protocol that
11+
produced `docs/m17_phase_a_validation_report.md`.
12+
13+
Generated: TBD. Raw data: `state/spikes/m17_phase_c/results.json` (gitignored).
14+
Auto-tables: `state/spikes/m17_phase_c/report_auto.md`. Degraded audio cached
15+
under `state/spikes/m17_phase_c/*.wav`.
16+
17+
## TL;DR
18+
19+
<!-- report_auto.md §"Gate outcomes" table goes here after the run. -->
20+
21+
| Evaluator (model) | Refusal | Disc (noise) | Disc (synth) | Phase C |
22+
|---|---|---|---|---|
23+
| **E5a — `gemini-2.5-pro`** | TBD | TBD | TBD | TBD |
24+
| **E5b — `gpt-4o-audio-preview`** | TBD | TBD | TBD | TBD |
25+
26+
**Phase C status: TBD.** `overall_pass = refusal AND discrimination`. Variance
27+
and Shay-correlation gates are advisory (see Gate definitions below). One model
28+
passing is sufficient to advance E5 to MVP.
29+
30+
## Reproduce
31+
32+
```bash
33+
uv pip install --python .venv/bin/python \
34+
google-genai openai pydantic soundfile numpy scipy
35+
36+
export GEMINI_API_KEY=...
37+
export OPENAI_API_KEY=...
38+
export SYNTHBANSHEE_LLM_SPIKE_BUDGET_USD=5.0 # hard cap
39+
40+
# Optional dry run — prints the prompt + JSON schema, no API calls, no spend.
41+
.venv/bin/python scripts/m17_phase_c_validation.py --dry-run
42+
43+
# Full run.
44+
.venv/bin/python scripts/m17_phase_c_validation.py
45+
46+
# With metadata-bias probe (adds ~4 no-arc calls per model, ~$0.10–0.40 extra).
47+
.venv/bin/python scripts/m17_phase_c_validation.py --probe-metadata-bias
48+
49+
# Resume a partial run after a failure or budget-cap abort.
50+
.venv/bin/python scripts/m17_phase_c_validation.py --resume
51+
```
52+
53+
The script prepares 6 clip records (4 corpus typology spans + 2 degraded
54+
variants of `sp_sv_a_0001_00`), sends each to each model twice under the
55+
`with_metadata` prompt variant, parses structured JSON responses, writes partial
56+
results to `results_partial.jsonl` after each call, and writes final gate
57+
outcomes to `results.json` + an auto-generated markdown summary.
58+
59+
## Clip set
60+
61+
Source dir defaults to `data/m2a_wettest/agg_m_30-45_001/` (8-clip M2a-wettest
62+
batch — same source set as the Phase A spike). Override via
63+
`SYNTHBANSHEE_LLM_SPIKE_CLIP_DIR`.
64+
65+
| Label | Source clip | Kind | Typology | Notes |
66+
|-------|-------------|------|----------|-------|
67+
| `sp_sv_a_0001_00` | sp_sv_a_0001_00 | corpus | SV | The canonical SV scene reviewed by Shay + Gemini + Claude + ChatGPT in `docs/debug_run_1/llm_feedbacks.md` |
68+
| `sp_it_a_0001_00` | sp_it_a_0001_00 | corpus | IT | The IT scene whose I3–I5 distress absence drove issue #97 |
69+
| `sp_neg_a_0001_00` | sp_neg_a_0001_00 | corpus | NEG | Hard-negative class — intense but `has_violence: false` |
70+
| `sp_neu_a_0001_00` | sp_neu_a_0001_00 | corpus | NEU | Neutral baseline; expected highest perceived quality |
71+
| `sp_sv_a_0001_00__synth_rate_slow_0.7x` | sp_sv_a_0001_00 resampled to 0.7× speed, trimmed | degraded | SV | **Synthesis-failure anchor.** Simulates over-slow TTS: unnatural tempo, pitch-shifted down, scene cut off mid-escalation. Tests whether the model can hear synthesis defects, not just noise. |
72+
| `sp_sv_a_0001_00__wn_snr_-10db` | sp_sv_a_0001_00 + severe white noise | degraded | SV | **Signal-corruption anchor.** RMS-matched to clean. Kept for comparability with Phase A E2/UTMOS. If a model passes this but fails the synth anchor, it can detect noise but not synthesis defects. |
73+
74+
Shay-correlation check uses `expected_quality_rank` per clip (encoded in
75+
`CLIP_SOURCES`), grounded in:
76+
- The May-3 listening test memo (M12 breathiness failed gate; systemic
77+
TTS naturalness issues).
78+
- The May-6 listening test (#92, aggregated naturalness backlog).
79+
- The 2026-04-14 multi-LLM review of `sp_it_a_0001` (`docs/debug_run_1/llm_feedbacks.md`).
80+
- Issue #97 finding that VIC at I3–I5 doesn't sound distressed under
81+
rate+pitch alone.
82+
83+
If/when new listening tests update the priors, update `CLIP_SOURCES` ranks
84+
and re-run.
85+
86+
## Gate definitions
87+
88+
Gates are computed **per model**. `overall_pass = refusal AND discrimination`.
89+
90+
| Gate | Criterion | Included in `overall_pass`? | Why |
91+
|------|-----------|:---:|-----|
92+
| **Refusal** | ≥ 5/6 clips score successfully under the DV-research framing; only `content_refusal` failures count against this gate (`api_error` and `json_parse_error` are retryable infrastructure failures) | ✅ | A model that refuses on DV content under research framing can't be a production E5 backend. |
93+
| **Discrimination** | At least one anchor arm clears: `mean(corpus) − mean(severe degraded) ≥ 0.5`. Two arms: (1) noise-corruption (`wn_snr_-10db`), (2) synthesis-failure (`synth_rate_slow_0.7x`). A model that passes only the noise arm cannot detect synthesis defects. | ✅ | The actual failure modes we need to catch are synthesis defects, not SNR. |
94+
| **Variance** _(advisory)_ | Per-dimension std across 2 reruns ≤ 0.5 | ❌ | At `TEMPERATURE=0.0`, greedy decoding is deterministic → std=0 trivially. Re-run with `TEMPERATURE=0.1` and `N_RERUNS=4` for a meaningful estimate. |
95+
| **Shay correlation** _(advisory)_ | Spearman ρ ≥ 0.3 between model `overall_quality` and Shay's encoded rank on the 4 corpus clips | ❌ | n=4 is not statistically significant (need n≥7 for p<0.05). Treat as a directional red-flag check only — a strongly negative ρ is a hard warning signal. |
96+
97+
## Per-clip mean overall_quality (TBD)
98+
99+
<!-- report_auto.md §"Per-clip mean overall_quality" table goes here. -->
100+
101+
## Metadata-bias probe (TBD)
102+
103+
Run with `--probe-metadata-bias`. The probe sends the 4 corpus clips under a
104+
`no_arc` prompt variant that omits the `intended intensity arc` and
105+
`typology_long` metadata. The delta on `emotional_expression` and
106+
`escalation_arc` between `with_metadata` and `no_arc` measures how much of the
107+
score is read from the label vs. from the audio.
108+
109+
<!-- report_auto.md §"Metadata-bias probe" table goes here. -->
110+
111+
A delta > 0.5 on either dimension is a warning that the model is inflating
112+
those scores based on the label. If the delta is large, the discrimination gate
113+
result on those dimensions should be treated as unreliable.
114+
115+
## Failure-mode notes (TBD post-run)
116+
117+
- **Refusal posture under DV-research framing:** TBD — record any
118+
`content_refusal` failures here, with the verbatim refusal text and the clip
119+
that triggered it. Distinguish from `api_error` / `json_parse_error`. If
120+
Gemini's BLOCK_NONE setting still produces content refusals, the design doc's
121+
mitigation list (alternative framings, GPT-4o fallback) becomes the
122+
recommended path.
123+
- **Noise-only discriminator:** TBD — if a model passes the noise arm but fails
124+
the synth arm, record it here. This model can detect SNR changes but not
125+
synthesis quality — it cannot serve as E5 backend.
126+
- **Score collapse / no-variance:** TBD — does any model give the same score
127+
on every dimension across all 6 clips? If so the model isn't actually
128+
listening to the audio.
129+
- **Metadata-bias:** TBD — compare `emotional_expression` and `escalation_arc`
130+
deltas from the metadata-bias probe. If delta > 0.5, note which model and
131+
which clips are affected.
132+
- **Anchor recommendation for MVP:** TBD — given the discrimination separation
133+
pattern (noise vs synth), recommend whether the MVP needs in-prompt
134+
calibration anchors (Phase 2b anchor protocol) or can rely on absolute
135+
scoring alone.
136+
137+
## Recommendation (TBD)
138+
139+
| Outcome | Next action |
140+
|---------|-------------|
141+
| Both models pass refusal + **both** discrimination arms | Ship E5 MVP behind Gemini primary, GPT-4o fallback. Open follow-up issue for anchor-set protocol + regression CI workflow. |
142+
| Both models pass refusal + noise arm only | E5 can detect gross signal corruption but not synthesis defects. Defer MVP; re-spike with synthesis-failure anchors as calibration points in the prompt. |
143+
| One model passes refusal + synthesis discrimination arm | Ship MVP with that model as primary. Document the other model's failure mode in a follow-up issue. |
144+
| Either model fails REFUSAL on content_refusal | Try alternative framings before declaring failure; record framing experiments in this report. |
145+
| Both models FAIL synthesis discrimination | Phase C is a NO-GO for synthesis-quality gating. Pivot the eval roadmap: either commit to paid linguistic raters / crowd evaluation, or wait for next-generation multimodal audio models. |
146+
147+
## Limitations
148+
149+
- **n=4 corpus clips for the Shay-correlation check.** Spearman ρ at n=4 is
150+
directional, not statistically significant. A future MVP-phase calibration
151+
step should use ≥ 20 clips spanning the full corpus once more listening-test
152+
ground truth is available.
153+
- **n=2 reruns at TEMPERATURE=0.** The variance gate trivially passes under
154+
greedy decoding. To get a real reproducibility estimate, re-run with
155+
`TEMPERATURE=0.1` and `N_RERUNS_PER_CLIP=4`.
156+
- **Synthesis-failure proxy via resampling.** The `synth_rate_slow_0.7x`
157+
degradation changes both tempo and pitch simultaneously (a naive resampling
158+
artifact), which is not exactly how TTS synthesis defects manifest. It is a
159+
better proxy than white noise but still not identical to over-smoothed
160+
prosody, wrong gender forms, or robotic timbre. The MVP-phase anchor set
161+
should include real defective TTS renders, not just signal-domain proxies.
162+
- **English-trained models on Hebrew audio.** Both Gemini and GPT-4o have
163+
unknown Hebrew-audio comprehension depth. If discrimination passes for both,
164+
that's reassuring; if only one passes, document the Hebrew-specific failure
165+
mode.
166+
- **Prompt v1.** Iterating the prompt is cheap — `PROMPT_VERSION` is bumped in
167+
the script and recorded in every result row, so an A/B between prompt
168+
versions is straightforward without losing the v1 baseline.
169+
170+
## Cost report (TBD)
171+
172+
<!-- report_auto.md §"Spend" block goes here. -->
173+
174+
| Model | Audio min sent | Estimated $ | Actual $ |
175+
|-------|---------------:|------------:|---------:|
176+
| `gemini-2.5-pro` | TBD | TBD | TBD |
177+
| `gpt-4o-audio-preview` | TBD | TBD | TBD |
178+
| **Total** | — | TBD | TBD |
179+
180+
Hard cap was `SYNTHBANSHEE_LLM_SPIKE_BUDGET_USD = $5.00`. Actual spend
181+
within budget: TBD.

0 commit comments

Comments
 (0)