mllm-eval
Design or audit a model-agnostic evaluation harness for an LLM or multimodal LLM on a clinical task (radiology report generation, visual question answering, clinical text extraction/classification) — the adjudicated reference standard, clinical-efficacy metrics (RadGraph-F1 / Che
Install
npx skills add https://github.com/Aperivue/medsci-skills/tree/main/skills/mllm-eval
claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install aperivue-medsci-skills@llmmart
git clone https://github.com/Aperivue/medsci-skills.git
The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole aperivue/medsci-skills collection as a plugin from our marketplace. Git is the plain clone.
Skill manifest
MLLM-Eval Skill
Purpose
This skill makes an LLM / MLLM clinical evaluation defensible: a real adjudicated reference standard, faithfulness measured not assumed, clinical-efficacy metrics beyond n-gram overlap, a pretraining- contamination check, prompt-sensitivity disclosed, and a reader study where text is generated. It is model-agnostic — every check applies to a closed API and to open weights — and read-only (an advisory design/audit skill): it audits the evaluation design and specifies and routes the clinical-efficacy metrics (RadGraph-F1 / CheXbert-F1 via their published extractors) rather than running the model or computing the metrics itself.
It is the LLM/MLLM evaluation-design counterpart in the lane — an auditor that hands the specified
metrics to their extractors and /analyze-stats, parallel to how /model-validation audits an imaging
model's design (the imaging metrics themselves are computed by /model-evaluation). The reviewer-side
audit of a finished manuscript uses the mllm_evaluation.md (ME0–ME8) probe via /self-review and
/peer-review; this skill is the author-side harness design. It routes the reader study to
/design-ai-benchmarking, the sizing to /calc-sample-size, and TRIPOD-LLM / MI-CLEAR-LLM compliance to
/check-reporting.
When to use
- You are designing or auditing an evaluation of an LLM/MLLM on a clinical task and want it to cover the axes a reviewer will check (reference standard, faithfulness, contamination, prompt sensitivity, reader study).
When NOT to use
- AI-vs-human-expert benchmark with a rated rubric →
/design-ai-benchmarking. - Imaging prediction/segmentation model →
/model-evaluation+/model-validation. - Image-to-image generative model → the
image_synthesisprobe. - Training / serving the LLM → out of scope.
- Item-level TRIPOD-LLM / MI-CLEAR-LLM audit of a finished manuscript →
/check-reporting.
Workflow
Phase 1 — Pin the task, model, comparator, decoding (ME0)
State the task (report generation / VQA / extraction-classification), the exact model + version/date (closed API or open-weights id), the decoding settings (temperature, seed, max tokens), and what the outputs are scored against.
Phase 2 — Reference standard + metrics (ME1, ME2)
Require an adjudicated expert reference (not a single unverified report or a model-derived label). For report generation, report a clinical-efficacy metric — RadGraph-F1 (Jain et al., NeurIPS 2021) or CheXbert-F1 (Smit et al., 2020), or the composite RadCliQ (Yu et al., Patterns 2023) — alongside any BLEU/ROUGE, with CIs. For VQA/classification, report accuracy at the real prevalence with a stated answer-matching rule.
Phase 3 — Faithfulness + contamination (ME3, ME4)
Add an atomic-fact faithfulness measure + a false-premise / abstention probe (MedVH, Med-HALT) — report a hallucination rate, not just accuracy. For any public benchmark (VQA-RAD, SLAKE, MIMIC-CXR- derived, MedQA), add a contamination statement: training cutoff vs benchmark release, a held-out / post-cutoff set, or a contamination probe.
Phase 4 — Prompt sensitivity + reader study (ME5, ME7)
Disclose the exact prompt(s), temperature/seed, ≥ 3 runs with variance, and a prompt-robustness
check. For a deployment/utility claim, design a blinded reader study with an error taxonomy (route
the rubric/IRR to /design-ai-benchmarking, ICC/κ to /analyze-stats, sizing to /calc-sample-size).
Phase 5 — Gate the plan (deterministic)
python3 ${CLAUDE_SKILL_DIR}/scripts/check_mllm_eval_completeness.py \
--plan plan.md --task report_generation|vqa|classification --strict
NGRAM_ONLY / FAITHFULNESS_MISSING / REFERENCE_STANDARD_MISSING / CONTAMINATION_UNADDRESSED /
READER_STUDY_MISSING must be resolved.
Phase 6 — Hand off
Methods/Results → /write-paper; compliance (TRIPOD-LLM / MI-CLEAR-LLM) → /check-reporting; reviewer
audit → /self-review (loads ME0–ME8).
Anti-Hallucination
- Never fabricate model outputs, reference labels, or metric scores. Compute only what the supplied outputs allow; if a reference standard or outputs are missing, say so and stop.
- Never report n-gram overlap (BLEU/ROUGE) as clinical correctness — pair it with a clinical-efficacy metric, and flag the n-gram score for what it is.
- Never claim "no contamination" without a stated check when a public benchmark is used.
- If a metric (RadGraph-F1 / CheXbert-F1) or its extractor is uncertain, flag
[VERIFY]and ask rather than inventing a number.
Deterministic gate
scripts/check_mllm_eval_completeness.py — task-aware presence gate on the evaluation plan (stdlib,
network-free). Reproducible challenge:
bash ${CLAUDE_SKILL_DIR}/scripts/mllm_eval_completeness_challenge/verify.sh.
Boundaries
mllm-eval (this skill: harness design + completeness gate, model-agnostic)
├─ design-ai-benchmarking (reader-study rubric / IRR)
├─ calc-sample-size (reader + case sizing)
├─ write-paper + check-reporting (TRIPOD-LLM / MI-CLEAR-LLM)
└─ self-review / peer-review (ME0–ME8 reviewer probe)
Reference Files
${CLAUDE_SKILL_DIR}/references/evaluation_axes.md— the why behind the ME2–ME7 axes: clinical-efficacy metrics beyond n-gram overlap (e.g. RadGraph-F1 / CheXbert-F1 vs BLEU/ROUGE), faithfulness & hallucination, pretraining/benchmark contamination, prompt-sensitivity & determinism, answer-matching, and the reader study — each mapped to its gate verdict. Load on demand during Phases 2–4.
Files (medsci-skills)
-
references
-
evaluation_axes.md 11.3 KB
# Evaluation axes (mllm-eval) Load-on-demand reference behind the ME2–ME7 axes: the clinical-efficacy metrics, faithfulness, contamination, prompt-sensitivity, answer-matching, and reader-study machinery an LLM/MLLM clinical evaluation must cover. Anchored to the radiology-NLP metric literature — **RadGraph** (Jain et al., NeurIPS Datasets & Benchmarks 2021), **CheXbert** (Smit et al., 2020), the rule-based **CheXpert** labeler (Irvin et al., 2019), and **RadCliQ** (Yu et al., *Patterns* 2023) — and to the reporting standards CLAIM 2024, **TRIPOD-LLM**, and **MI-CLEAR-LLM**. This skill **specifies and routes** these metrics to their published extractors and to `/analyze-stats`; it does not run the model or compute the scores itself. Never report n-gram overlap as clinical correctness, and never fabricate a score. ## Clinical-efficacy metrics beyond n-gram overlap (ME2 → `NGRAM_ONLY`) - **Why n-gram overlap fails.** BLEU / ROUGE / METEOR / CIDEr measure surface lexical overlap. A report can score high while inverting laterality or omitting a pneumothorax, and low while paraphrasing a correct finding. Yu et al. (*Patterns* 2023, RadCliQ) showed these metrics correlate weakly with radiologist-assessed clinical error — so an n-gram score is a **fluency proxy, not a correctness claim**. - **RadGraph-F1.** Overlap of the (entity, relation) tuples the RadGraph schema (Jain et al., 2021) extracts from the reference and the candidate report — it rewards getting the *same findings and their relationships*, not the same words. - **CheXbert-F1 / CheXpert labeler.** Agreement on the structured CheXpert observation labels extracted by CheXbert (Smit et al., 2020) or by the rule-based CheXpert labeler (Irvin et al., 2019) — a finding-label–level correctness signal. - **RadCliQ.** A composite (Yu et al., 2023) that combines metrics to better predict the count of radiologist-judged errors. Report it as a composite **alongside** its components, not as a one-number replacement. - **GREEN.** An LLM-based score (Ostmeier et al., EMNLP Findings 2024) that prompts a language model to identify and count **clinically significant errors** between the candidate and reference report and emits an interpretable error notation, aligning better with radiologist judgment than n-gram overlap. Report it as a complement to RadGraph-F1 / CheXbert-F1, not a replacement, and note its dependence on the grading model + prompt (a quantitative metric with a qualitative, human-readable error breakdown — the two halves of report-generation evaluation). - **Advise:** report a clinical-efficacy metric (RadGraph-F1 / CheXbert-F1 / RadCliQ / GREEN) **with bootstrap CIs over reports** and a **per-finding / per-label breakdown**, and present any BLEU/ROUGE explicitly labelled as a surface-overlap measure — never as the headline. ## Faithfulness & hallucination (ME3 → `FAITHFULNESS_MISSING`) - **Fluency is not faithfulness.** A high-overlap, well-formed report can still assert findings the image does not support. Measure faithfulness directly; do not infer it from an accuracy number. - **Atomic-fact decomposition.** Break the generated text into atomic clinical claims and check each against the image / source, then report a **faithfulness (or hallucination) rate** — the fraction of generated claims that are grounded. - **Direction matters.** Separate **omission** (a true finding the model missed) from **fabrication** (a false finding the model asserted); they carry different clinical risk and should be reported separately, not folded into one error count. - **False-premise / abstention probe.** Ask about an absent finding or an unanswerable question; a faithful model abstains rather than confabulates. Named instruments: **MedVH**, **Med-HALT**. - **Advise:** a generation or VQA claim with no faithfulness and no false-premise/abstention evaluation is the central MLLM gap — require both, with rates, before any clinical claim. ## Pretraining / benchmark contamination (ME4 → `CONTAMINATION_UNADDRESSED`) - **Why public benchmarks are suspect.** VQA-RAD, SLAKE, MIMIC-CXR–derived sets, MedQA, PMC-VQA, PathVQA, OpenI, and PubMedQA may sit inside the model's pretraining corpus, so a high score can be **memorisation, not capability**. For a closed API the corpus is undisclosed, so contamination cannot be excluded — only **bounded** and stated. - **Three accepted checks (any one, stated explicitly):** - **Cutoff vs release date** — compare the model's training cutoff against the benchmark's release date; a benchmark that predates the cutoff is at risk. - **Held-out / post-cutoff set** — evaluate on a private, institution-collected, or after-cutoff set the model could not have seen. - **Contamination probe** — canary strings, a perturbed-duplicate performance gap (score on verbatim items vs lightly perturbed copies), or a membership/quiz test (ask the model to reproduce held-out items). - **Advise:** never write "no contamination" (or evaluate on a pre-cutoff public benchmark in silence) without one of the checks above; an acknowledged-but-unmitigated risk is a stated limitation, not a clean result. ## Prompt-sensitivity & determinism (ME5 → `PROMPT_PROVENANCE_MISSING`) - **Outputs move with the prompt and the sampler.** Phrasing, format, the system prompt, temperature, top-p/top-k, and run-to-run sampling all shift results. A single-prompt single-run number overstates stability. - **Closed APIs are non-deterministic even at temperature 0** — identical inputs can yield different outputs across calls. Treat any single-run figure as a point estimate of a distribution, not a fixed value. - **Disclose (MI-CLEAR-LLM transparency):** the **exact prompt(s)** including the system prompt, the **decoding settings** (temperature / top-p / seed), **≥ 3 runs** with reported variance (e.g., mean ± SD), and a **prompt-robustness** check across **≥ 2 phrasings/formats** for the headline result. ## Answer-matching for VQA / classification (ME6 → `ANSWER_MATCHING_MISSING`) - **State the matching rule.** Free-text answers must be mapped to the key by a declared rule: **exact** string match, **normalised** match (case/punctuation/synonym folding), or **LLM-as-judge**. An unspecified rule makes the accuracy unreproducible. - **An LLM judge is itself a model.** If a model adjudicates correctness, **validate the judge against a human-labelled subset** and report its agreement; route judge validation to `/design-ai-benchmarking`. An unvalidated LLM judge can launder the system's own errors. - **Operating discipline.** Report accuracy **at the real clinical prevalence**, not on an artificially balanced QA set, and state **how refusals/abstentions are scored** (counted wrong, excluded, or credited) — the choice can move the headline. ## Reader study for generated reports (ME7 → `READER_STUDY_MISSING`) - **Automated metrics do not establish clinical acceptability.** Even RadGraph-F1 / CheXbert-F1 measure agreement, not whether a clinician would act on the report safely. A deployment or utility claim for generated text needs a **blinded clinical reader study**. - **Design elements:** a pre-defined **error taxonomy** (clinically significant vs insignificant; omission vs fabrication), a **severity scale**, and **inter-reader agreement**. - **Route:** the rubric and the IRR design → `/design-ai-benchmarking`; the ICC/κ computation → `/analyze-stats`; reader and case **sizing** → `/calc-sample-size`. ## Gate mapping The deterministic gate (`scripts/check_mllm_eval_completeness.py`) is a presence check on the plan text, task-aware. This reference is the *why* behind each verdict: | Axis (this doc) | Gate verdict | Severity | |---|---|---| | n-gram only, no clinical metric (report-gen) | `NGRAM_ONLY` | Major | | no adjudicated reference standard (report-gen) | `REFERENCE_STANDARD_MISSING` | Major | | no faithfulness / false-premise (report-gen, vqa) | `FAITHFULNESS_MISSING` | Major | | public benchmark, no contamination handling | `CONTAMINATION_UNADDRESSED` | Major | | no blinded reader study (report-gen) | `READER_STUDY_MISSING` | Major (deploy) / Minor | | prompt / decoding / multi-run incomplete | `PROMPT_PROVENANCE_MISSING` | Minor | | no answer-matching rule (vqa, classification) | `ANSWER_MATCHING_MISSING` | Minor | A Major verdict is a presence gap, not proof the work is wrong — resolve it by adding the axis to the plan (or recording, with a stated reason, why it does not apply). ## Reporting fit & hand-off Methods / Results stub → `/write-paper`. Item-level compliance with **TRIPOD-LLM**, **MI-CLEAR-LLM**, **CLAIM 2024** (and STARD-AI / TRIPOD+AI where a diagnostic/prognostic claim is made) → `/check-reporting`. Reviewer-side audit of a finished manuscript uses the `mllm_evaluation.md` (ME0–ME8) probe via `/self-review` and `/peer-review`. ## Verification notes Each claim here is grounded in a named public method/standard or described qualitatively; no numbers, thresholds, or dataset contents are invented. - **n-gram metrics correlate weakly with clinical error; clinical-efficacy metrics needed** — Yu et al., *Patterns* 2023 (RadCliQ). Named public methods paper (matches the citation already vendored in the `mllm_evaluation.md` probe). - **RadGraph-F1 (entity-relation overlap)** — Jain et al., NeurIPS Datasets & Benchmarks 2021. Named public methods paper. - **GREEN (LLM-graded clinically significant errors)** — Ostmeier et al., EMNLP Findings 2024. Named public methods paper; described qualitatively, no scores invented. - **CheXbert-F1 / CheXpert observation labels** — Smit et al., 2020 (CheXbert); Irvin et al., 2019 (CheXpert labeler). Named public methods papers; the CheXpert label set is a factual artifact, not invented. - **RadCliQ as a composite** — Yu et al., *Patterns* 2023. Named public methods paper. - **Atomic-fact faithfulness, omission-vs-fabrication, false-premise/abstention** — described as established evaluation principles; **MedVH** and **Med-HALT** named as instruments only (as in the probe), no scores invented. - **Contamination of public benchmarks; closed-corpus unknowability; cutoff/held-out/probe checks (canary, perturbed-duplicate gap, membership test)** — stated as accepted principles and practices, qualitatively; benchmark **names** (VQA-RAD, SLAKE, MIMIC-CXR, MedQA, PMC-VQA, PathVQA, OpenI, PubMedQA) are factual public-dataset names, no contents reproduced. - **Closed-API non-determinism even at temperature 0; prompt/format/sampling sensitivity** — described qualitatively as documented behavior; no figure attached. - **Prompt + decoding + ≥3 runs + ≥2 phrasings disclosure** — MI-CLEAR-LLM transparency (named standard); the ≥3 / ≥2 conventions are this skill's own house thresholds (carried from SKILL.md / the ME-probe), not literature values. - **LLM-as-judge must be validated against human labels** — stated as a methodological principle; judge validation routed to `/design-ai-benchmarking`. - **Reader study for deployment/utility claims; error taxonomy, severity, IRR** — consistent with CLAIM 2024 / TRIPOD-LLM reporting expectations (named standards); sizing/IRR routed to `/calc-sample-size` and `/analyze-stats`. - **Metrics Reloaded / CLAIM 2024 / TRIPOD-LLM / MI-CLEAR-LLM / Model Cards (Mitchell 2019) / Datasheets (Gebru 2021)** — named public standards, cited by name only.
-
-
scripts
-
mllm_eval_completeness_challenge
-
fixture
-
plan_bad.md 202 B
# Evaluation plan We evaluate GPT-X on the SLAKE and VQA-RAD benchmarks for chest-radiograph report generation. Report quality is measured with BLEU-4 and ROUGE-L against the original clinical reports. -
plan_good.md 795 B
# Evaluation plan We evaluate GPT-X (version 2026-03) on a held-out, post-cutoff internal set for report generation. The reference standard is set by two radiologists, with disagreements adjudicated by a third. Quality is measured with RadGraph-F1 and CheXbert-F1 alongside BLEU-4, all with 95% CIs. Faithfulness is assessed via atomic-fact checking and a false-premise probe (hallucination rate). We disclose the exact prompt, temperature 0.2 with a fixed seed, and report mean +/- SD across 3 runs with two prompt phrasings. A blinded clinical reader study with an omission/fabrication error taxonomy supports the deployment claim. Where the SLAKE benchmark is used, we report a contamination check (training cutoff versus benchmark release date). Answer matching uses normalised exact match.
-
-
problem.md 1.6 KB
# Challenge card — MLLM evaluation completeness (mllm-eval) ## Problem An LLM/MLLM clinical evaluation is only as good as what it measures. The recurrent failures: report quality reported with **BLEU/ROUGE only** (weakly correlated with clinical correctness — a report can score well while inverting laterality; Yu et al., *Patterns* 2023), **no faithfulness / hallucination** evaluation (a fluent answer is not a faithful one), an **unadjudicated reference standard**, a public benchmark scored with **no pretraining-contamination** statement (a high score may be memorisation), and a deployment claim with **no reader study**. These pass a prose read. ## What the gate does `scripts/check_mllm_eval_completeness.py` is a conservative, task-aware presence linter on the evaluation plan / methods. It flags the missing axes (`NGRAM_ONLY`, `FAITHFULNESS_MISSING`, `REFERENCE_STANDARD_MISSING`, `CONTAMINATION_UNADDRESSED`, `READER_STUDY_MISSING`, `PROMPT_PROVENANCE_MISSING`, `ANSWER_MATCHING_MISSING`). It checks the protocol's coverage, not the results, and is model-agnostic (closed API or open weights). ## Fixture (synthetic only — no real model) - `fixture/plan_bad.md` — report generation scored with BLEU/ROUGE on SLAKE/VQA-RAD; no faithfulness, no adjudicated reference, no contamination check. - `fixture/plan_good.md` — RadGraph-F1/CheXbert-F1 + faithfulness + adjudicated reference + contamination check + prompt/temperature/multi-run + reader study. ## Expected (`verify.sh`, network-free) - `plan_bad` flags `NGRAM_ONLY` + `FAITHFULNESS_MISSING` + `REFERENCE_STANDARD_MISSING` + `CONTAMINATION_UNADDRESSED` (exit 1). - `plan_good` passes (exit 0). -
verify.sh 1.3 KB
#!/usr/bin/env bash # Deterministic verifier for the MLLM-evaluation completeness challenge (mllm-eval). # Network-free, stdlib-only. The gate flags an n-gram-only, faithfulness-free, # contamination-unaddressed plan and clears a complete one. Exit 0 = all hold. set -euo pipefail HERE="$(cd "$(dirname "$0")" && pwd)" DET="$HERE/../check_mllm_eval_completeness.py" TMP="$(mktemp -d)"; trap 'rm -rf "$TMP"' EXIT # (1) bad plan -> exit 1 with the core Major gaps python3 "$DET" --plan "$HERE/fixture/plan_bad.md" --task report_generation --out "$TMP/bad.json" --strict --quiet \ && { echo "FAIL: the bad plan should exit 1" >&2; exit 1; } || true python3 - "$TMP/bad.json" <<'PY' || exit 1 import json, sys v = {c["verdict"] for c in json.load(open(sys.argv[1]))["claims"]} for need in ("NGRAM_ONLY", "FAITHFULNESS_MISSING", "REFERENCE_STANDARD_MISSING", "CONTAMINATION_UNADDRESSED"): assert need in v, f"{need} not flagged" print(" bad plan flagged: NGRAM_ONLY + FAITHFULNESS + REFERENCE_STANDARD + CONTAMINATION") PY # (2) complete plan -> exit 0 python3 "$DET" --plan "$HERE/fixture/plan_good.md" --task report_generation --strict --quiet \ || { echo "FAIL: the complete plan should pass (exit 0)" >&2; exit 1; } echo "PASS: MLLM completeness gate flags the n-gram-only/faithfulness-free plan and clears a complete one."
-
-
check_mllm_eval_completeness.py 9.2 KB
#!/usr/bin/env python3 """LLM / MLLM clinical-evaluation completeness gate (mllm-eval). A task-aware presence linter for the methods / plan of an LLM or multimodal-LLM clinical evaluation. It flags the axes a defensible evaluation must cover that are **absent** from the plan text — it is a presence check on the protocol (the analogue of check_model_card_complete for documentation), not a judge of results. Conservative: each verdict fires only when its concept is clearly missing from the text. CHECKS (verdicts; which apply depends on --task): 1. NGRAM_ONLY (Major) report-gen names BLEU/ROUGE/METEOR but no clinical-efficacy metric (RadGraph-F1 / CheXbert / CheXpert / RadCliQ). 2. FAITHFULNESS_MISSING (Major) no faithfulness / hallucination / false-premise evaluation (report-gen, vqa). 3. REFERENCE_STANDARD_MISSING(Major) no adjudicated reference-standard statement (report-gen). 4. CONTAMINATION_UNADDRESSED (Major) a public benchmark is named but no contamination / training-cutoff / held-out statement. 5. READER_STUDY_MISSING (Major) report-gen with no blinded clinical reader study. 6. PROMPT_PROVENANCE_MISSING (Minor) no prompt + temperature/seed + multi-run disclosure. 7. ANSWER_MATCHING_MISSING (Minor) vqa/classification with no answer-matching rule (exact / normalised / LLM-judge). INPUTS --plan the evaluation plan / methods markdown (required). --task report_generation | vqa | classification (required). OUTPUT A table (stdout) and, with --out, a JSON artifact: {plan, task, claims[{verdict, severity, detail, where}], summary} Stdlib-only (re / json / argparse / pathlib). Exit codes: 0 clean (or report-only), 1 Major claim(s) found (with --strict), 2 input/usage error. """ from __future__ import annotations import argparse import json import re import sys from pathlib import Path # concept -> regex of any phrasing that SATISFIES it (searched case-insensitively) SAT = { "ngram": r"\b(bleu|rouge|meteor|cider)\b", "clinical_metric": r"\b(radgraph|chexbert|chexpert[- ]?label|radcliq|green|f1[\w-]*chexpert|" r"clinical efficacy|factual(?:ity| correctness)|entity[- ]?relation)\b", "faithfulness": r"\b(faithful(?:ness)?|hallucinat\w+|atomic[- ]?fact|false[- ]?premise|" r"fabricat\w+|med-?hal|medvh|med-?halt|confabulat\w+|ungrounded|groundedness|" r"unsupported|clinical(?:ly)?[- ]?grounded|evidence[- ]?support\w*)\b" r"|not supported by the image", "reference_standard": r"\b(reference standard|reference reports?|adjudicat\w+|gold[- ]?standard|" r"expert(?:[- ]?annotat\w+| review| radiologist)|consensus|" r"ground[- ]?truth|radiolog\w+[- ]?author\w+)\b", "benchmark": r"\b(vqa-?rad|slake|mimic-?cxr|medqa|pmc-?vqa|path-?vqa|openi|pubmedqa|" r"public benchmark)\b", "contamination": r"\b(contaminat\w+|(?:training|pre-?training|knowledge)[- ]cut[- ]?off|" r"held[- ]?out|post[- ]?cut[- ]?off|canary|memoris\w+|memoriz\w+|" r"data leakage)\b", "reader_study": r"reader study|blinded read\w+|clinical reader|radiologist review|" r"human evaluation|expert rating|acceptability scale|likert|" r"\d+\s+(?:radiologists?|readers?|clinicians?)[^.\n]{0,60}" r"(?:rated|scored|graded|read|review)|" r"(?:masked|blinded)[^.\n]{0,40}(?:graded|scored|rated)", "prompt": r"\b(prompt)\b", "decoding": r"\b(temperature|top-?p|top-?k|seed|greedy|sampling)\b", "multirun": r"\b(\d+\s*runs|repeated runs|multiple runs|across runs|run-to-run|" r"mean\s*(?:±|\+/-|\+-)\s|standard deviation|variance|bootstrap)\b", "answer_match": r"\b(exact match|normali[sz]ed match|answer matching|llm[- ]?as[- ]?judge|" r"llm judge|string match|keyword match|semantic (?:equivalence|match\w*)|" r"clinician[- ]?adjudicat\w+|adjudicated (?:correct|equivalent)|" r"human[- ]?judg\w+)\b", } DEPLOY_CLAIM = re.compile( r"\b(deploy\w*|clinical use|ready for (?:clinical|practice)|integrat\w+ into (?:practice|workflow)|" r"assist\w* (?:radiologists|clinicians)|in practice)\b", re.IGNORECASE) def has(text: str, concept: str) -> bool: return re.search(SAT[concept], text, re.IGNORECASE) is not None def analyze(plan: str, task: str) -> dict: text = Path(plan).read_text(encoding="utf-8") claims = [] def add(verdict, severity, detail): claims.append({"verdict": verdict, "severity": severity, "detail": detail, "where": Path(plan).name}) is_gen = task == "report_generation" is_vqa = task == "vqa" if is_gen: if has(text, "ngram") and not has(text, "clinical_metric"): add("NGRAM_ONLY", "Major", "report-generation quality is reported with n-gram overlap (BLEU/ROUGE) but no " "clinical-efficacy metric (RadGraph-F1 / CheXbert / RadCliQ) — n-gram overlap is weakly " "correlated with clinical correctness") if not has(text, "reference_standard"): add("REFERENCE_STANDARD_MISSING", "Major", "no adjudicated expert reference-standard statement for the generated reports") if not has(text, "reader_study"): sev = "Major" if DEPLOY_CLAIM.search(text) else "Minor" add("READER_STUDY_MISSING", sev, "no blinded clinical reader study with an error taxonomy" + (" for a deployment/utility claim" if sev == "Major" else " (automated metrics only)")) if is_gen or is_vqa: if not has(text, "faithfulness"): add("FAITHFULNESS_MISSING", "Major", "no faithfulness / hallucination / false-premise evaluation — a fluent answer is not a " "faithful one") if has(text, "benchmark") and not has(text, "contamination"): add("CONTAMINATION_UNADDRESSED", "Major", "a public clinical benchmark is named but pretraining contamination is not addressed " "(training cutoff vs benchmark release, a held-out/post-cutoff set, or a contamination probe)") if not (has(text, "prompt") and has(text, "decoding") and has(text, "multirun")): missing = [m for m, c in (("prompt", "prompt"), ("temperature/seed", "decoding"), ("multi-run variance", "multirun")) if not has(text, c)] add("PROMPT_PROVENANCE_MISSING", "Minor", "prompt-sensitivity provenance incomplete — missing: " + ", ".join(missing)) if (is_vqa or task == "classification") and not has(text, "answer_match"): add("ANSWER_MATCHING_MISSING", "Minor", "no answer-matching rule stated (exact / normalised / LLM-judge) for free-text answers") n_major = sum(1 for c in claims if c["severity"] == "Major") return {"plan": plan, "task": task, "claims": claims, "summary": {"n_claims": len(claims), "n_major": n_major, "verdict": "MAJOR_CANDIDATE" if n_major else "OK"}} def render(result: dict) -> str: lines = ["| Check | Severity | Detail |", "|---|---|---|"] for c in result["claims"]: lines.append(f"| {c['verdict']} | {c['severity']} | {c['detail']} |") if len(lines) == 2: lines.append("| (none) | — | evaluation plan covers the required MLLM axes |") return "\n".join(lines) def main() -> int: ap = argparse.ArgumentParser(description="LLM/MLLM clinical-evaluation completeness gate.") ap.add_argument("--plan", required=True, help="evaluation plan / methods markdown") ap.add_argument("--task", required=True, choices=["report_generation", "vqa", "classification"]) ap.add_argument("--out", help="write JSON artifact to this path") ap.add_argument("--strict", action="store_true", help="exit 1 if any Major claim exists") ap.add_argument("--quiet", action="store_true", help="suppress stdout table") args = ap.parse_args() if not Path(args.plan).is_file(): sys.stderr.write(f"ERROR: --plan not found: {args.plan}\n") return 2 result = analyze(args.plan, args.task) if not args.quiet: print("=" * 41) print(" MLLM Evaluation Completeness") print("=" * 41) print(render(result)) print() s = result["summary"] print(f"MAJOR candidate: {s['n_major']} evaluation-completeness gap(s)." if s["n_major"] else "OK: evaluation plan covers the required MLLM axes.") if args.out: Path(args.out).parent.mkdir(parents=True, exist_ok=True) Path(args.out).write_text(json.dumps({"detector": "check_mllm_eval_completeness", **result}, indent=2), encoding="utf-8") if not args.quiet: print(f"\nwrote {args.out}") return 1 if (args.strict and result["summary"]["n_major"]) else 0 if __name__ == "__main__": sys.exit(main())
-
-
tests
-
test_mllm_eval_completeness.sh 3.1 KB
#!/usr/bin/env bash # Regression test for the MLLM-evaluation completeness gate (mllm-eval). Synthetic, PII-free. set -u HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" DET="$HERE/../scripts/check_mllm_eval_completeness.py" F="$HERE/../scripts/mllm_eval_completeness_challenge/fixture" OUT="$(mktemp -t mec_XXXX).json"; trap 'rm -f "$OUT"' EXIT fail=0 check(){ local l="$1"; shift; if "$@" >/dev/null 2>&1; then printf ' PASS %s\n' "$l"; else printf ' FAIL %s\n' "$l"; fail=$((fail+1)); fi; } has(){ python3 -c "import json;d=json.load(open('$OUT'));assert any(c['verdict']=='$1' for c in d['claims']),'$1'"; } no(){ python3 -c "import json;d=json.load(open('$OUT'));assert not any(c['verdict']=='$1' for c in d['claims']),'$1'"; } [[ -f "$DET" ]] || { echo "ENV-ERR" >&2; exit 2; } python3 "$DET" --plan "$F/plan_bad.md" --task report_generation --out "$OUT" --strict --quiet >/dev/null 2>&1 check "plan_bad exits 1" test "$?" -eq 1 check "NGRAM_ONLY" has NGRAM_ONLY check "FAITHFULNESS_MISSING" has FAITHFULNESS_MISSING check "REFERENCE_STANDARD_MISSING" has REFERENCE_STANDARD_MISSING check "CONTAMINATION_UNADDRESSED" has CONTAMINATION_UNADDRESSED python3 "$DET" --plan "$F/plan_good.md" --task report_generation --out "$OUT" --strict --quiet >/dev/null 2>&1 check "plan_good exits 0" test "$?" -eq 0 check "plan_good no NGRAM_ONLY" no NGRAM_ONLY check "plan_good no FAITHFULNESS_MISSING" no FAITHFULNESS_MISSING # vqa task: a bare VQA-RAD accuracy plan -> CONTAMINATION + FAITHFULNESS + ANSWER_MATCHING TMP="$(mktemp -d)"; trap 'rm -rf "$TMP"; rm -f "$OUT"' EXIT printf 'We report accuracy on VQA-RAD.\n' > "$TMP/vqa.md" python3 "$DET" --plan "$TMP/vqa.md" --task vqa --out "$OUT" --strict --quiet >/dev/null 2>&1 check "vqa bare plan exits 1" test "$?" -eq 1 check "vqa FAITHFULNESS_MISSING" has FAITHFULNESS_MISSING check "vqa CONTAMINATION_UNADDRESSED" has CONTAMINATION_UNADDRESSED check "vqa ANSWER_MATCHING_MISSING" has ANSWER_MATCHING_MISSING # --- false-positive robustness (folded from adversarial review): a complete plan stated in # non-canonical vocabulary must PASS (knowledge cutoff / gold standard / graded-on-scale / # groundedness / semantic match). --- cat > "$TMP/good_vocab.md" <<'MD' We evaluate the model on SLAKE. The reference is the original board-certified radiologists' reports (the gold standard). Quality uses RadGraph-F1 and CheXbert-F1 with 95% CIs. Groundedness is assessed (each statement supported vs unsupported by the image); a false-premise probe gives a hallucination rate. Contamination is controlled against the model's knowledge cutoff versus the benchmark release. Three radiologists graded outputs on an acceptability scale (masked). We disclose the prompt, temperature and seed, and report mean +/- SD across 3 runs. Free-text answers were scored by clinician-adjudicated semantic equivalence. MD python3 "$DET" --plan "$TMP/good_vocab.md" --task report_generation --out "$OUT" --strict --quiet >/dev/null 2>&1 check "FP: complete plan in non-canonical vocabulary passes (exit 0)" test "$?" -eq 0 echo "fail=$fail"; [[ "$fail" -eq 0 ]] && echo "ALL PASS" || echo "FAILURES: $fail"; exit "$fail"
-
-
SKILL.md 6.6 KB
--- name: mllm-eval description: > Design or audit a model-agnostic evaluation harness for an LLM or multimodal LLM on a clinical task (radiology report generation, visual question answering, clinical text extraction/classification) — the adjudicated reference standard, clinical-efficacy metrics (RadGraph-F1 / CheXbert-F1 beyond BLEU/ROUGE), faithfulness and hallucination, pretraining-contamination of public benchmarks, prompt-sensitivity and determinism, answer-matching, and a reader study — and gate the plan for those axes. Works on a closed API or open weights. Never fabricates outputs or scores, and never reports n-gram overlap as clinical correctness. triggers: MLLM evaluation, LLM evaluation, multimodal LLM, report generation, radiology report generation, visual question answering, VQA, RadGraph, CheXbert, faithfulness, hallucination, prompt sensitivity, contamination, GPT, LLaVA-Med, clinical LLM, medical VLM, reader study for reports tools: Read, Write, Edit, Bash, Grep, Glob model: inherit --- # MLLM-Eval Skill ## Purpose This skill makes an **LLM / MLLM clinical evaluation defensible**: a real adjudicated reference standard, faithfulness measured not assumed, clinical-efficacy metrics beyond n-gram overlap, a pretraining- contamination check, prompt-sensitivity disclosed, and a reader study where text is generated. It is **model-agnostic** — every check applies to a closed API and to open weights — and **read-only** (an advisory design/audit skill): it audits the evaluation design and **specifies and routes** the clinical-efficacy metrics (RadGraph-F1 / CheXbert-F1 via their published extractors) rather than running the model or computing the metrics itself. It is the LLM/MLLM **evaluation-design counterpart** in the lane — an auditor that hands the specified metrics to their extractors and `/analyze-stats`, parallel to how `/model-validation` audits an imaging model's design (the imaging metrics themselves are computed by `/model-evaluation`). The reviewer-side audit of a finished manuscript uses the `mllm_evaluation.md` (ME0–ME8) probe via `/self-review` and `/peer-review`; this skill is the author-side harness design. It routes the reader study to `/design-ai-benchmarking`, the sizing to `/calc-sample-size`, and TRIPOD-LLM / MI-CLEAR-LLM compliance to `/check-reporting`. ## When to use - You are designing or auditing an evaluation of an LLM/MLLM on a clinical task and want it to cover the axes a reviewer will check (reference standard, faithfulness, contamination, prompt sensitivity, reader study). ## When NOT to use - AI-vs-human-expert benchmark with a rated rubric → `/design-ai-benchmarking`. - Imaging prediction/segmentation model → `/model-evaluation` + `/model-validation`. - Image-to-image generative model → the `image_synthesis` probe. - Training / serving the LLM → out of scope. - Item-level TRIPOD-LLM / MI-CLEAR-LLM audit of a finished manuscript → `/check-reporting`. ## Workflow ### Phase 1 — Pin the task, model, comparator, decoding (ME0) State the task (report generation / VQA / extraction-classification), the exact model + version/date (closed API or open-weights id), the decoding settings (temperature, seed, max tokens), and what the outputs are scored against. ### Phase 2 — Reference standard + metrics (ME1, ME2) Require an **adjudicated expert reference** (not a single unverified report or a model-derived label). For report generation, report a **clinical-efficacy metric** — **RadGraph-F1** (Jain et al., NeurIPS 2021) or **CheXbert-F1** (Smit et al., 2020), or the composite **RadCliQ** (Yu et al., *Patterns* 2023) — **alongside** any BLEU/ROUGE, with CIs. For VQA/classification, report accuracy at the real prevalence with a stated answer-matching rule. ### Phase 3 — Faithfulness + contamination (ME3, ME4) Add an **atomic-fact faithfulness** measure + a **false-premise / abstention** probe (MedVH, Med-HALT) — report a hallucination rate, not just accuracy. For any public benchmark (VQA-RAD, SLAKE, MIMIC-CXR- derived, MedQA), add a **contamination** statement: training cutoff vs benchmark release, a held-out / post-cutoff set, or a contamination probe. ### Phase 4 — Prompt sensitivity + reader study (ME5, ME7) Disclose the **exact prompt(s)**, temperature/seed, **≥ 3 runs** with variance, and a prompt-robustness check. For a deployment/utility claim, design a **blinded reader study** with an error taxonomy (route the rubric/IRR to `/design-ai-benchmarking`, ICC/κ to `/analyze-stats`, sizing to `/calc-sample-size`). ### Phase 5 — Gate the plan (deterministic) ```bash python3 ${CLAUDE_SKILL_DIR}/scripts/check_mllm_eval_completeness.py \ --plan plan.md --task report_generation|vqa|classification --strict ``` `NGRAM_ONLY` / `FAITHFULNESS_MISSING` / `REFERENCE_STANDARD_MISSING` / `CONTAMINATION_UNADDRESSED` / `READER_STUDY_MISSING` must be resolved. ### Phase 6 — Hand off Methods/Results → `/write-paper`; compliance (TRIPOD-LLM / MI-CLEAR-LLM) → `/check-reporting`; reviewer audit → `/self-review` (loads ME0–ME8). ## Anti-Hallucination - **Never fabricate model outputs, reference labels, or metric scores.** Compute only what the supplied outputs allow; if a reference standard or outputs are missing, say so and stop. - **Never report n-gram overlap (BLEU/ROUGE) as clinical correctness** — pair it with a clinical-efficacy metric, and flag the n-gram score for what it is. - **Never claim "no contamination" without a stated check** when a public benchmark is used. - If a metric (RadGraph-F1 / CheXbert-F1) or its extractor is uncertain, flag `[VERIFY]` and ask rather than inventing a number. ## Deterministic gate `scripts/check_mllm_eval_completeness.py` — task-aware presence gate on the evaluation plan (stdlib, network-free). Reproducible challenge: `bash ${CLAUDE_SKILL_DIR}/scripts/mllm_eval_completeness_challenge/verify.sh`. ## Boundaries ``` mllm-eval (this skill: harness design + completeness gate, model-agnostic) ├─ design-ai-benchmarking (reader-study rubric / IRR) ├─ calc-sample-size (reader + case sizing) ├─ write-paper + check-reporting (TRIPOD-LLM / MI-CLEAR-LLM) └─ self-review / peer-review (ME0–ME8 reviewer probe) ``` ## Reference Files - `${CLAUDE_SKILL_DIR}/references/evaluation_axes.md` — the *why* behind the ME2–ME7 axes: clinical-efficacy metrics beyond n-gram overlap (e.g. RadGraph-F1 / CheXbert-F1 vs BLEU/ROUGE), faithfulness & hallucination, pretraining/benchmark contamination, prompt-sensitivity & determinism, answer-matching, and the reader study — each mapped to its gate verdict. Load on demand during Phases 2–4. -
skill.yml 3.3 KB
schema_version: 2 name: mllm-eval layer: D owner_domain: model_evaluation maturity: official when_to_use: "Design or audit a model-agnostic evaluation harness for an LLM or multimodal LLM on a clinical task (radiology report generation, visual question answering, clinical text extraction/classification) — the reference standard, clinical-efficacy metrics (RadGraph-F1 / CheXbert-F1 beyond BLEU/ROUGE), faithfulness/hallucination, pretraining-contamination of public benchmarks, prompt-sensitivity/determinism, answer-matching, and a reader study — and check the plan covers them. Works on a closed API or open weights." when_NOT_to_use: "Benchmarking against a human-expert panel with a rated rubric (use design-ai-benchmarking); evaluating an imaging prediction/segmentation model (use model-evaluation + model-validation); image-to-image generative models (use the image_synthesis probe); training or serving the LLM (out of scope); item-level TRIPOD-LLM / MI-CLEAR-LLM compliance of a finished manuscript (use check-reporting)." inputs: - "the clinical task + the model(s) under test (API name/version or open-weights id) and decoding settings" - "model outputs (generated reports / VQA answers / labels) and a reference standard (optional, for computable metrics)" - "the evaluation plan / methods draft (for the completeness audit)" outputs: - "an MLLM evaluation-harness design / audit (decision notes), naming the clinical-efficacy metrics it specifies and routes (RadGraph-F1 / CheXbert-F1) and the not-yet-measured axes (faithfulness, contamination, reader study)" - "a Methods stub" - "mllm-eval completeness audit JSON (deterministic)" deterministic_scripts: - scripts/check_mllm_eval_completeness.py side_effects: - writes_decision_notes downstream_consumers: - design-ai-benchmarking - calc-sample-size - check-reporting - write-paper - self-review forbidden_actions: - fabricate_model_outputs_reference_labels_or_metric_scores - report_n_gram_overlap_as_clinical_correctness - claim_no_contamination_without_a_stated_check # v2.1 quality card purpose: "Make an LLM/MLLM clinical evaluation defensible — a real adjudicated reference standard, faithfulness measured not assumed, clinical-efficacy metrics beyond n-gram overlap, a pretraining-contamination check, prompt-sensitivity disclosed, and a reader study where text is generated — and gate the plan for those axes." safety_boundaries: - "Model-agnostic and read-only: it never runs or fine-tunes the model; it audits the eval design and specifies / routes the clinical-efficacy metrics (RadGraph-F1 / CheXbert-F1 via their published extractors) rather than computing them itself." - "It never converts BLEU/ROUGE into a clinical-correctness claim, and never asserts no contamination without a stated check." known_limitations: - "RadGraph-F1 / CheXbert-F1 require their published extractors; the skill specifies and routes to them but does not vendor model weights." - "Closed-API non-determinism and undisclosed pretraining corpora cap how strongly contamination can be excluded." validation_commands: - "python3 scripts/check_mllm_eval_completeness.py --plan <plan.md> --task report_generation|vqa|classification --strict" - "bash scripts/mllm_eval_completeness_challenge/verify.sh # deterministic, network-free" evidence_surface: ci_validator
Comments (0)
Sign in to join the conversation.
Reviews (0)
No reviews yet.
No comments yet.