All skills
nvidia avatar

/digital-health-clinical-asr-eval

@1ba3403
by NVIDIA Corporationnvidia/skills3.5k stars
424

Stage 3 of Clinical ASR Flywheel. Score a NeMo manifest, produce the five-section KER leaderboard (by-ipa_source diagnostic). Not for ASR auth (/riva-asr).

Use this Skill: https://skilld.dev/gh/nvidia/skills/digital-health-clinical-asr-eval

This session only. Nothing lands on disk.

referencesoffline-asr-recipe.md

≈1.8k tokens on demand. Your agent reads this file only when SKILL.md points to it.

Offline NVCF gRPC ASR — full recipe + catalog

Companion to SKILL.md Step 3b. The compact pointer in SKILL.md is enough to run the happy path against the default Parakeet TDT 0.6B v2 function; this file carries the full Python recipe, the env-var resolution helper, the alternate-NIM catalog, and the Whisper / self-hosted-Riva fallbacks.

Function-ID catalog (swap via ASR_NVCF_FUNCTION_ID)

Function IDs sourced from /riva-asr's catalog. All four below are offline-capable except where noted; only the offline ones drop straight into the recipe in this file. Streaming-shaped NIMs need /riva-asr's streaming call shape instead.

Function ID Model Shape Notes
d3fe9151-442b-4204-a70d-5fcc597fd610 nvidia/parakeet-tdt-0.6b-v2 offline Default. Fastest/cheapest in the catalog; supported in NeMo's stock SFT recipe so Stage 3 baseline and a Stage 4 fine-tune ride the same model family.
b702f636-f60c-4a3d-a6f4-f3568c13bd7d openai/whisper-large-v3 offline Cross-vendor baseline. Drop-in for the Parakeet recipe; pass language_code="en" (not "en-US"). The pragmatic fallback while Parakeet's NVCF backend is faulting.
71203149-d3b7-4460-8231-1be2543a1fca nvidia/parakeet-tdt-1.1b-rnnt-multilingual streaming-shaped Higher accuracy, larger model; pass language_code="multi". Needs /riva-asr's streaming recipe.
1598d209-5e27-4d3c-8079-4751568b1081 nvidia/parakeet-ctc-1.1b-asr streaming-shaped CTC decoder, English; simpler Riva export path. Needs /riva-asr's streaming recipe.
bb0837de-8c7b-481f-9ec8-ef5663e9c1fa nvidia/nemotron-asr-streaming streaming-only Eval-only; do not pair with /digital-health-clinical-asr-finetune (SFT path is unreliable — UNK collapse on validation after step 1). Use /riva-asr Option A's transcribe_file.py for the streaming call shape.

Full recipe

Same auth_for shape as the Stage 1 setup smoke test. The agent harness passes api_key as an explicit argument; the recipe itself reads the three optional env-var overrides (ASR_NVCF_FUNCTION_ID, ASR_MODEL_NAME, ASR_ENDPOINT) at the top so auditors can see the knobs from one place.

import json, os, wave
from pathlib import Path
import riva.client

NVCF_HOST = "grpc.nvcf.nvidia.com:443"

DEFAULT_FUNCTION_ID = "d3fe9151-442b-4204-a70d-5fcc597fd610"  # Parakeet TDT 0.6B v2 (offline)
DEFAULT_MODEL_NAME  = "parakeet-tdt-0.6b-v2"

def resolve_asr_config():
    """Read env-var overrides once; returns (model_name, function_id, endpoint).
    endpoint=None means use hosted NVCF; otherwise use self-hosted gRPC."""
    return (
        os.environ.get("ASR_MODEL_NAME", DEFAULT_MODEL_NAME),
        os.environ.get("ASR_NVCF_FUNCTION_ID", DEFAULT_FUNCTION_ID),
        os.environ.get("ASR_ENDPOINT"),  # e.g. "localhost:50051"
    )

def build_asr_auth(api_key: str, function_id: str, endpoint: str | None):
    """Build a riva.client.Auth pointed at either NVCF (hosted) or a self-hosted gRPC URI."""
    if endpoint:
        # Self-hosted Riva NIM: no NVCF function-id, no NVCF bearer.
        return riva.client.Auth(use_ssl=False, uri=endpoint)
    return riva.client.Auth(
        use_ssl=True, uri=NVCF_HOST,
        metadata_args=[
            ["function-id", function_id],
            ["authorization", f"Bearer {api_key}"],
        ],
    )

def transcribe_row(asr_service, wav_path: str, language_code: str = "en-US") -> str:
    """One-shot offline transcription. Sentences in a clinical manifest are ≤ 30 s,
    so we treat the whole file as one chunk — no streaming or batching needed."""
    with wave.open(wav_path, "rb") as w:
        sr = w.getframerate()
        if w.getnchannels() != 1 or w.getsampwidth() != 2:
            raise ValueError(f"{wav_path}: expected 16-bit mono PCM (got {w.getnchannels()}ch / {w.getsampwidth()*8}-bit)")
        audio_bytes = w.readframes(w.getnframes())
    cfg = riva.client.RecognitionConfig(
        encoding=riva.client.AudioEncoding.LINEAR_PCM,
        sample_rate_hertz=sr, language_code=language_code,
        max_alternatives=1, enable_automatic_punctuation=True,
    )
    resp = asr_service.offline_recognize(audio_bytes, cfg)
    return resp.results[0].alternatives[0].transcript if resp.results else ""

def transcribe_manifest(api_key: str, manifest_path: str, out_path: str,
                        language_code: str = "en-US") -> str:
    """Iterate manifest.jsonl, write per_sample.json. Returns the resolved model name
    for downstream leaderboard labelling."""
    model_name, function_id, endpoint = resolve_asr_config()
    target = endpoint if endpoint else f"NVCF function-id {function_id}"
    print(f"ASR target: {model_name} -> {target}")  # pre-flight echo

    auth = build_asr_auth(api_key, function_id, endpoint)
    asr = riva.client.ASRService(auth)

    n_done = 0
    with open(manifest_path) as f_in, open(out_path, "w") as f_out:
        for line in f_in:
            row = json.loads(line)
            wav = row["audio_filepath"]
            hyp = transcribe_row(asr, wav, language_code=language_code)
            f_out.write(json.dumps({
                "audio_filepath":  wav,
                "ref":             row["text"],
                "hyp":             hyp,
                "term":            row.get("term"),
                "entity_category": row.get("entity_category"),
                "ipa_source":      row.get("ipa_source"),
                "voice_id":        row.get("voice_id"),
                "noise_level":     row.get("noise_level"),
                "context_type":    row.get("context_type"),
            }) + "\n")
            n_done += 1
    print(f"Wrote {n_done} rows -> {out_path}")
    return model_name

# Invoke from the agent (api_key sourced by the harness, not by this code):
# transcribe_manifest(api_key=<NVIDIA_API_KEY>, manifest_path="cycle1/manifest.jsonl",
#                     out_path="cycle1/per_sample.json")

Whisper fallback (when Parakeet NVCF is faulting)

Whisper Large v3 is also offline; the recipe runs unchanged with two env-var nudges:

export ASR_NVCF_FUNCTION_ID=b702f636-f60c-4a3d-a6f4-f3568c13bd7d
export ASR_MODEL_NAME=whisper-large-v3
# Then call transcribe_manifest(..., language_code="en") instead of "en-US".

Symptom that warrants the fallback: StatusCode.INVALID_ARGUMENT with CUDA illegal memory access from Triton on Parakeet's NVCF function — that's a backend fault, not your environment. Switch to Whisper, complete the cycle, switch back later. The leaderboard ASR_MODEL_NAME label keeps cycles auditable.

Self-hosted Riva NIM

Set ASR_ENDPOINT=<host:port> (e.g. localhost:50051); the recipe builds a non-SSL Auth and skips NVCF entirely.

export ASR_ENDPOINT=localhost:50051
# transcribe_manifest(...) now hits the local NIM instead of NVCF.

See /riva-asr Option B for deploying a self-hosted NIM.

Resilience knobs

If NVCF returns RESOURCE_EXHAUSTED mid-batch, the loop raises on that row; re-run from the failing row or slice the manifest. Streaming/batching/retry-with-backoff are out of scope for this skill — see /riva-asr if you need them.

Source: SKILL.md on GitHub

1 warning3mo3 checks · Risk SAFE
  • Gen Agent Trust Hub3mo

    This skill provides a workflow for evaluating Clinical Automatic Speech Recognition (ASR) performance. It is well-documented, follows security best practices, and communicates exclusively with official NVIDIA infrastructure for its ASR transcription tasks.

  • Socket3mo

    No alerts

  • Snyk3mo

    Risk: MEDIUM · 1 issue

Signed by skilld at 1ba3403. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub yesterday.

Activeupdated 4 months ago
version
1.1.0
author
Ben Randoing <brandoing@nvidia.com>
tags
[
  "clinical-asr",
  "eval",
  "ker",
  "leaderboard",
  "flywheel"
]
Other metadata
tools
[
  "Read",
  "Write",
  "Bash",
  "Skill"
]
compatibility
NVIDIA_API_KEY (required) for hosted ASR NIMs via NVCF. A NeMo-format manifest produced by /digital-health-clinical-asr-build (or an externally-provided manifest carrying the clinical-extension fields). All ASR call shapes and WER/CER/KER/SER scoring recipes are inlined — no sibling agent skill required.
metadata
{
  "author": "Ben Randoing <brandoing@nvidia.com>",
  "tags": [
    "clinical-asr",
    "flywheel",
    "eval",
    "ker",
    "leaderboard"
  ],
  "team": "healthcare-tme",
  "domain": "ai-ml",
  "stage": 3,
  "previous_skill": "digital-health-clinical-asr-build",
  "next_skill": "digital-health-clinical-asr-finetune"
}

README badge

README badge for nvidia/skills/digital-health-clinical-asr-eval