Claude Skill

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

LLM Mart · 0 points · 3 views 0 listing impressions 0 install-command copies
Virus-scanned Reviewed automatically before listing.

Full trust report

Download aperivue-medsci-skills-skills_mllm-eval-815765c.zip · 17 KB
Part of aperivue/medsci-skills — 47 skills

Install

skills CLI npx skills add https://github.com/Aperivue/medsci-skills/tree/main/skills/mllm-eval
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install aperivue-medsci-skills@llmmart
Git 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_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)

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.

No comments yet.

Reviews (0)

No reviews yet.

Related