60db Logo
  • Benchmarks

When lower WER still loses the task

Compare raw word-error counts, literal critical fields and independently observed task outcomes with a runnable paired audit.

Editorial date
Reading time
28 min
A tidy transcript card sits beside a visibly mismatched literal-value card

TL;DR: Word error rate counts transcript edits, not successful tasks. Sum raw edit counts against reference-token counts, audit critical fields under their own literal contracts, and retain independently verified task outcomes across every scheduled case.


A recognition update removes several harmless wording errors from a long utterance but loses the leading zero in a short identifier. Corpus WER improves. The application now acts on the wrong value, so the task gets worse while the transcript metric looks better.

Start with three separate outputs: raw transcript edit counts, exact critical-field comparisons and independently observed task outcomes. Keep their denominators explicit. A field can be correct without a task completing, and a task can recover through clarification despite an imperfect transcript.

The bounded program below compares two declared arms over the same synthetic references. It prints substitution, deletion and insertion counts, literal-field results and task dispositions, including missing observations. It does not run ASR, extract entities from audio or infer business success from text.

This distinction also applies to evaluations of 60db Agent. A useful application benchmark needs evidence at the boundary it claims to measure. The confidence calibration guide explains why a recognition score cannot fill an absent task-outcome field.

Table of contents

  1. Define WER before interpreting it
  2. Identify the critical value separately
  3. Preserve paired arm and run provenance
  4. Declare normalization before scoring
  5. Run the paired transcript and task audit
  6. Recompute the synthetic comparison
  7. Handle empty references honestly
  8. Keep leading zeros and negation
  9. Use task evidence from the owning system
  10. Make evaluation criteria explicit
  11. Inspect workload and cohort weighting
  12. Separate recognition from recovery
  13. Keep adaptation claims narrow
  14. Review a changed normalization policy
  15. Build a release evidence bundle
  16. Frequently asked questions
  17. Begin with the error that changes the action

Define WER before interpreting it

Word error rate is the sum of substitutions, deletions and insertions divided by the number of reference words under a declared tokenization and normalization policy. It measures edit distance between token sequences. It does not know which token controls an important action.

Hugging Face's WER metric documentation states this calculation. For corpus WER, sum the raw edit counts and reference-token counts before dividing. Averaging utterance percentages instead gives each utterance equal weight and answers a different question.

An insertion-heavy hypothesis can produce a rate above one. That is a consequence of the formula, not a reason to clamp the metric into a familiar probability range. Likewise, a zero edit count says the admitted token sequences match; it does not establish that the reference transcript is correct.

Use one documented alignment rule when comparing edit categories. Several minimum-cost alignments can exist, and their substitution, deletion and insertion breakdown can differ even when total distance agrees. The program uses a deterministic local tie priority and prints every raw count.

The audio-boundary guide provides a reminder that transcript errors can originate before recognition. WER alone cannot distinguish a model error from clipped input, incorrect resampling or a malformed audio boundary.

Keep the metric definition next to the result. A number labeled WER without its reference population, normalization policy and treatment of missing hypotheses is difficult to interpret. The paired artifact emits those accounting boundaries explicitly instead of presenting an isolated headline.

The reference is part of the measurement instrument. If it contains a transcription mistake, a recognizer that repeats that mistake can score better than one that captured the actual speech. Prepare references through a documented process and retain unresolved uncertainty. The local program takes supplied reference strings as trusted inputs; its edit-distance tests cannot establish that those strings match an acoustic recording.

Identify the critical value separately

A transcript metric weights edit operations rather than business consequences. Deleting a filler and deleting a negation can each contribute one deletion while producing very different task risk. A literal identifier substitution can be decisive even inside an otherwise accurate utterance.

Define critical-field expectations from the task's approved evidence. An order code may require exact characters; an amount may require value, currency and scale; an appointment may require calendar and timezone context. Do not compare all fields through one generic text-normalization function.

The order-ID guide and ZIP and PIN guide explain why identifiers remain strings. Converting them to numbers before evaluation can erase the very error the benchmark needs to detect.

The local artifact intentionally supports exact string fields only. Each case has an immutable tuple of expected field-name and literal pairs. Each observation supplies extracted values or typed missing values. Semantic amount and date comparison remain external typed adapters with their own policies.

Report field eligibility, exact matches and unknown extracted values separately. A missing extraction is not an exact match, but its absence should remain visible rather than being folded into a vague error total. Preserve the expected and observed synthetic values in the emitted ledger.

Do not infer task completion from field equality. A correctly captured code can still fail authorization or refer to unavailable state. The business-state conflict guide addresses another boundary that a transcript-only metric cannot observe.

Keep field count and case count distinct. One case can contain several required values, while another contains none. The artifact reports field eligibility as the number of declared expected fields in observed cases. Its scheduled-case and missing-observation counts provide the separate coverage boundary. A consumer should not divide exact fields by scheduled cases and label the resulting quantity field accuracy.

Preserve paired arm and run provenance

A claim that WER improved while task outcomes worsened needs observations for both arms on the same declared cases. One set of transcripts and one task label per case cannot support a before-and-after comparison, however persuasive the surrounding explanation sounds.

The artifact defines separate immutable arm records and observations. Each observation binds its case identity, reference digest, hypothesis, extracted literals, task disposition, outcome-evidence reference and the arm that produced it. The arm retains model, pipeline, normalization and run identity.

Case identity alone is insufficient. A changed reference transcript under the same case name can change WER without any change in recognition. The program rejects a mismatched reference digest and requires the declared normalization policy to be the supported literal-whitespace policy for both arms.

The judge-drift guide covers a similar measurement issue. If task outcomes are assigned by a changed evaluator, the observed comparison includes instrument change. Preserve the evaluator provenance externally rather than attributing every changed label to ASR.

Missing old or new observations remain in the scheduled ledger. The per-arm corpus metrics use available observations and report missing counts. The paired case list explicitly identifies the shared population. Do not compare different per-arm populations as though they were a controlled pair.

The example's two main cases have both arms, so its arithmetic comparison is paired. The negative fixture removes one observation to verify missing-case retention. A real replay needs an external collector that preserves unattempted, canceled and failed work as well as completed responses.

Do not collapse several attempts into one observation without a declared policy. If a recognizer is retried, retain the original failure and the chosen attempt's provenance in the collector. Selecting the best transcript after seeing a reference would use evaluation evidence to choose the answer and overstate performance. This bounded tool admits one observation per arm and case and rejects duplicates rather than deciding that policy implicitly.

Transcript edits, literal fields and external task outcomes remain separate measurement lanes

Declare normalization before scoring

Normalization defines which textual differences count as errors. Lowercasing, punctuation removal, number-word conversion and Unicode transformations can each change the target. Choose those rules for the evaluation purpose before looking at which policy makes a model appear better.

This reference uses whitespace splitting only. It preserves case, punctuation and digit strings within tokens. The policy is intentionally narrow and is not a linguistically complete tokenizer for every language or a recommendation to use whitespace WER universally.

Keep raw admitted text alongside the declared transformation in real evidence. A normalized token sequence alone may hide whether the input contained an important separator, negative sign or leading zero. The adapter should permit authorized reconstruction of the evaluation decision.

The Arabic digit and currency guide separates digit mapping from field interpretation. A script normalization that is reasonable for display should not automatically become the equality policy for identifiers, currencies or calendars.

The mixed-script text guide describes another representation boundary. Text normalization, pronunciation preparation and evaluation tokenization have different purposes. Reusing a speech-rendering cleanup function as a benchmark normalizer can silently broaden equivalence.

If the desired policy changes, version it and recompute both arms under the same new definition while preserving the old report. A changed metric definition may be useful, but it should appear as a study revision rather than an unexplained performance improvement.

Run the paired transcript and task audit

Save this complete fence as critical-field-audit.py and run it with Python 3.11 or newer. It uses only the standard library. The bounded dynamic program supports at most five hundred whitespace tokens per reference or hypothesis to keep this teaching implementation's allocation explicit.

The trusted adapter supplies correct references, declared model provenance, literal field expectations and authoritative task-evidence references. The code validates shapes and relationships; it cannot authenticate a business-system acknowledgment or determine whether a recording was representative.

from dataclasses import dataclass, replace
from hashlib import sha256
import json


POLICY = "literal-whitespace-v1"


def text(value):
    if type(value) is not str or not value.strip():
        raise ValueError("nonempty text required")


def digest(value):
    return sha256(value.encode("utf-8")).hexdigest()


def fields(value, allow_missing):
    if type(value) is not tuple:
        raise ValueError("immutable field tuple required")
    seen = set()
    for pair in value:
        if type(pair) is not tuple or len(pair) != 2:
            raise ValueError("field pair required")
        key, literal = pair
        text(key)
        if key in seen:
            raise ValueError("duplicate field")
        seen.add(key)
        if literal is None and allow_missing:
            continue
        text(literal)


@dataclass(frozen=True)
class Arm:
    model: str
    pipeline: str
    normalization: str
    run_id: str

    def __post_init__(self):
        for value in (self.model, self.pipeline, self.run_id):
            text(value)
        if type(self.normalization) is not str or self.normalization != POLICY:
            raise ValueError("unsupported normalization policy")


@dataclass(frozen=True)
class Case:
    case_id: str
    reference: str
    expected: tuple[tuple[str, str], ...]

    def __post_init__(self):
        text(self.case_id)
        if type(self.reference) is not str:
            raise ValueError("reference text required; empty allowed")
        fields(self.expected, False)


@dataclass(frozen=True)
class Observation:
    case_id: str
    reference_hash: str
    arm: Arm
    hypothesis: str
    extracted: tuple[tuple[str, str | None], ...]
    task: str
    task_evidence: str | None

    def __post_init__(self):
        text(self.case_id)
        text(self.reference_hash)
        if type(self.arm) is not Arm or type(self.hypothesis) is not str:
            raise ValueError("exact arm and hypothesis text required")
        fields(self.extracted, True)
        if type(self.task) is not str or self.task not in {"success", "failure", "unknown"}:
            raise ValueError("typed task disposition required")
        if self.task == "unknown":
            if self.task_evidence is not None:
                raise ValueError("unknown outcome has no authoritative evidence")
        else:
            text(self.task_evidence)


def index(values, expected):
    owned = tuple(values)
    if any(type(value) is not expected for value in owned):
        raise ValueError("exact records required")
    result = {}
    for value in owned:
        if value.case_id in result:
            raise ValueError("duplicate case identity")
        result[value.case_id] = value
    return result


def edits(reference, hypothesis):
    if type(reference) is not str or type(hypothesis) is not str:
        raise ValueError("raw text required")
    ref, hyp = reference.split(), hypothesis.split()
    if max(len(ref), len(hyp)) > 500:
        raise ValueError("bounded teaching alignment exceeded")
    # Cells contain (total cost, substitutions, deletions, insertions).
    table = [[(0, 0, 0, 0)] * (len(hyp) + 1) for _ in range(len(ref) + 1)]
    for i in range(1, len(ref) + 1):
        table[i][0] = (i, 0, i, 0)
    for j in range(1, len(hyp) + 1):
        table[0][j] = (j, 0, 0, j)
    for i in range(1, len(ref) + 1):
        for j in range(1, len(hyp) + 1):
            diagonal = table[i - 1][j - 1]
            mismatch = int(ref[i - 1] != hyp[j - 1])
            diagonal = (diagonal[0] + mismatch, diagonal[1] + mismatch,
                        diagonal[2], diagonal[3])
            above, left = table[i - 1][j], table[i][j - 1]
            candidates = [diagonal, (above[0] + 1, above[1], above[2] + 1, above[3]),
                          (left[0] + 1, left[1], left[2], left[3] + 1)]
            # Equal-cost priority: diagonal, deletion, insertion.
            table[i][j] = min(enumerate(candidates), key=lambda item: (item[1][0], item[0]))[1]
    cost, substitutions, deletions, insertions = table[-1][-1]
    return {"S": substitutions, "D": deletions, "I": insertions,
            "errors": cost, "reference_tokens": len(ref),
            "wer": cost / len(ref) if ref else None}


def arm_report(arm):
    return {"model": arm.model, "pipeline": arm.pipeline,
            "normalization": arm.normalization, "run_id": arm.run_id}


def audit(cases, old_results, new_results, old_arm, new_arm):
    if type(old_arm) is not Arm or type(new_arm) is not Arm:
        raise ValueError("exact arms required")
    scheduled = index(cases, Case)
    sides = {"old": (index(old_results, Observation), old_arm),
             "new": (index(new_results, Observation), new_arm)}
    reports = {}
    ledger = {key: {"case_id": key, "reference": case.reference,
                    "expected": dict(case.expected)} for key, case in scheduled.items()}
    for side, (observations, arm) in sides.items():
        if set(observations) - set(scheduled):
            raise ValueError("unscheduled observation")
        totals = {key: 0 for key in ("S", "D", "I", "errors", "reference_tokens")}
        summary = {"observed": 0, "missing": 0, "field_eligible": 0,
                   "field_exact": 0, "field_unknown": 0,
                   "empty_reference_cases": 0, "empty_reference_insertions": 0,
                   "task": {"success": 0, "failure": 0, "unknown": 0}}
        for case_id in sorted(scheduled):
            case = scheduled[case_id]
            observation = observations.get(case_id)
            if observation is None:
                summary["missing"] += 1
                summary["task"]["unknown"] += 1
                ledger[case_id][side] = {"disposition": "missing", "task": "unknown"}
                continue
            if observation.arm != arm or observation.reference_hash != digest(case.reference):
                raise ValueError("arm or frozen reference mismatch")
            observed_fields = dict(observation.extracted)
            expected = dict(case.expected)
            if set(observed_fields) - set(expected):
                raise ValueError("undeclared field")
            counts = edits(case.reference, observation.hypothesis)
            for key in totals:
                totals[key] += counts[key]
            summary["observed"] += 1
            summary["task"][observation.task] += 1
            if counts["reference_tokens"] == 0:
                summary["empty_reference_cases"] += 1
                summary["empty_reference_insertions"] += counts["I"]
            field_results = []
            for key, expected_value in case.expected:
                actual = observed_fields.get(key)
                exact = actual == expected_value
                summary["field_eligible"] += 1
                summary["field_exact"] += exact
                summary["field_unknown"] += actual is None
                field_results.append({"field": key, "expected": expected_value,
                                      "actual": actual, "exact": exact})
            ledger[case_id][side] = {"disposition": "observed", "hypothesis": observation.hypothesis,
                                    "counts": counts, "fields": field_results,
                                    "task": observation.task, "task_evidence": observation.task_evidence}
        reports[side] = {"arm": arm_report(arm), **summary, **totals,
                         "corpus_wer": totals["errors"] / totals["reference_tokens"]
                         if totals["reference_tokens"] else None}
    paired_ids = sorted(set(sides["old"][0]) & set(sides["new"][0]))
    return {"evidence": "synthetic paired arithmetic; no ASR performance claim",
            "scheduled": len(scheduled), "paired_ids": paired_ids,
            "arms": reports, "ledger": [ledger[key] for key in sorted(ledger)],
            "task_evidence_authenticity": "trusted external adapter; not verified here"}


def fixture():
    old_arm = Arm("model-old", "pipeline-1", POLICY, "old-run")
    new_arm = replace(old_arm, model="model-new", run_id="new-run")
    cases = [Case("critical", "use code 0072", (("code", "0072"),)),
             Case("ordinary", "please read this ordinary sentence slowly and clearly today", ())]
    def observation(case, arm, hypothesis, values, task):
        return Observation(case.case_id, digest(case.reference), arm, hypothesis, values,
                           task, "synthetic-evidence:" + arm.run_id + ":" + case.case_id)
    old = [observation(cases[0], old_arm, cases[0].reference, (("code", "0072"),), "success"),
           observation(cases[1], old_arm, "read ordinary sentence slowly clearly today", (), "success")]
    new = [observation(cases[0], new_arm, "use code 72", (("code", "72"),), "failure"),
           observation(cases[1], new_arm, cases[1].reference, (), "success")]
    return cases, old, new, old_arm, new_arm


def expect_rejected(function):
    try:
        function()
    except ValueError:
        return
    raise AssertionError("expected rejection")


def tests():
    cases, old, new, old_arm, new_arm = fixture()
    result = audit(cases, old, new, old_arm, new_arm)
    assert result["arms"]["new"]["corpus_wer"] < result["arms"]["old"]["corpus_wer"]
    assert result["arms"]["old"]["field_exact"] == 1
    assert result["arms"]["new"]["field_exact"] == 0
    assert result["arms"]["new"]["task"]["failure"] == 1
    assert result["arms"]["old"]["reference_tokens"] == 12
    assert result["arms"]["old"]["D"] == 3
    assert result["arms"]["new"]["S"] == 1
    assert result == audit(reversed(cases), reversed(old), reversed(new), old_arm, new_arm)
    assert edits("do not cancel", "do cancel")["D"] == 1
    assert edits("", "extra words")["wer"] is None
    assert edits("", "extra words")["I"] == 2
    assert edits("one", "one extra extra")["wer"] == 2
    empty_case = Case("empty", "", ())
    empty_obs = Observation("empty", digest(""), old_arm, "extra", (), "unknown", None)
    empty_report = audit([empty_case], [empty_obs], [], old_arm, new_arm)
    assert empty_report["arms"]["old"]["corpus_wer"] is None
    assert empty_report["arms"]["old"]["empty_reference_insertions"] == 1
    missing = audit(cases, old, new[:1], old_arm, new_arm)
    assert missing["arms"]["new"]["missing"] == 1
    assert missing["arms"]["new"]["task"]["unknown"] == 1
    unknown = replace(new[0], extracted=(("code", None),), task="unknown", task_evidence=None)
    assert audit(cases, old, [unknown, new[1]], old_arm, new_arm)["arms"]["new"]["field_unknown"] == 1
    expect_rejected(lambda: audit(cases + [cases[0]], old, new, old_arm, new_arm))
    expect_rejected(lambda: audit(cases, old, new + [new[0]], old_arm, new_arm))
    expect_rejected(lambda: audit(cases, old, [replace(new[0], reference_hash=digest("changed"))], old_arm, new_arm))
    expect_rejected(lambda: replace(new_arm, normalization="remove-identifier-punctuation"))
    expect_rejected(lambda: replace(new[0], task=True))
    expect_rejected(lambda: audit(cases, old, [replace(new[0], arm=old_arm)], old_arm, new_arm))
    expect_rejected(lambda: replace(new[0], task_evidence=None))
    expect_rejected(lambda: replace(cases[0], expected=(("code", 72),)))
    expect_rejected(lambda: edits("x " * 501, ""))
    snapshot = result["ledger"]
    cases.clear()
    assert len(snapshot) == 2


if __name__ == "__main__":
    tests()
    print(json.dumps(audit(*fixture()), indent=2, allow_nan=False))

The task-evidence strings are visibly synthetic references, not real acknowledgments. Their presence demonstrates the required shape for a trusted adapter. A nonempty string cannot prove that an external action occurred, so the output explicitly records that authenticity limitation.

Returned dictionaries are fresh report values; the frozen cases and arm records remain the evidence inputs. The input collections are snapshotted before indexing, and the boundary tests clear a caller collection after reporting. This demonstrates ownership for the supported record shape. It is not a durable execution ledger, a database transaction or a guarantee against an external collector modifying its own stored evidence.

Recompute the synthetic comparison

The first case contains a literal code. The old hypothesis preserves it, while the new hypothesis drops its leading zeros. The second case is an ordinary sentence where the old hypothesis omits several words and the new hypothesis matches the reference.

The new arm therefore has fewer total transcript edits across the same reference population, but its one critical field no longer matches and its supplied task disposition changes from success to failure. These are deliberately constructed arithmetic observations, not results from either named provider or a real ASR model.

The printed ledger contains raw references and hypotheses for these synthetic cases, plus per-case edit counts, expected and observed field values and per-arm task evidence references. A reader can recompute the corpus numerator and denominator without trusting a summary assertion.

Look at the paired identities before comparing the two corpus rates. In the main fixture both cases are present in both arms. In the missing-result fixture, the shared population changes and the output exposes that change. A downstream comparison should either use a declared shared subset or explain the different populations.

The regression-attribution guide develops the next question: what changed between arms? A difference in outcome is observable here, but the synthetic audit cannot identify whether a model, prompt, extractor or channel caused it.

Retain the full output rather than a single lower-is-better scalar. A release reviewer needs to see whether transcript improvement came with a literal-field regression, a larger unknown task population or a changed number of observed cases. Those are different engineering decisions.

The main fixture's two arms share twelve reference tokens. The old arm has three deletions and the new arm has one substitution. Those exact counts are asserted by the runnable tests and are visible in the report. Their purpose is reproducibility of this constructed example, not an estimate of any real application's error distribution or expected benefit from an update.

Handle empty references honestly

An empty reference has zero reference words. A nonempty hypothesis can still contain insertions, but dividing by zero cannot yield a conventional finite WER. The program emits null for that per-case rate while preserving insertion counts.

If every observed reference is empty, corpus WER is null too. The report separately counts empty-reference cases and their insertions. It does not label silence handling perfect merely because the normal denominator is unavailable.

JiWER's documentation describes its own handling of empty references and hypotheses. Library behavior can be useful for a particular workflow, but this reference declares a different explicit reporting contract: raw counts remain available while a zero-denominator WER field remains null.

In a mixed corpus, insertion errors from empty-reference cases still contribute to the summed error numerator, while the reference-token denominator comes from nonempty references. State that policy because excluding silent cases would answer a different question about recognition behavior.

Silence itself is not always a missing recording. A valid silent fixture, a failed capture and an absent provider response are different states. The trusted collector must distinguish them before supplying a transcript observation. The no-input prompt guide addresses the corresponding interaction boundary.

Keep null distinct from zero in storage and charting. A dashboard that converts unavailable rates to zero can manufacture an apparent improvement when recordings or references disappear. Test serialization and downstream aggregation as well as the local calculation.

Inspect the silent-case workload before drawing a conclusion. A deliberately silent synthetic fixture checks a recognizer's behavior on that fixture; it does not establish how a real microphone, echo path or telephone endpoint represents silence. Record the capture and preprocessing conditions separately. An absent audio payload should never be admitted as a valid silent recording merely because both can produce an empty transcript.

Synthetic leading-zero and negation examples show why one token can change the intended action

Keep leading zeros and negation

Literal values often carry semantics that ordinary language normalization removes. A code with a leading zero is a different string from its numeric conversion. A minus sign, decimal separator or email punctuation can also be part of the admitted value.

The phone-number capture guide explains why country context and literal spelling must be preserved. The field comparator here refuses numeric expected values so a caller cannot quietly turn a literal identifier into an integer equality check.

Negation illustrates a different failure. Removing one word from a cancellation instruction can reverse the intended action while contributing only one deletion to WER. The fixture tests this edit count, but it does not implement semantic negation interpretation or authorization logic.

The qualified-confirmation guide shows why task evidence must account for repair language. A transcript containing an affirmative word does not prove that the caller accepted the original candidate when a correction follows it.

Separate literal comparison from human-readable display. A UI can format a value for legibility while retaining its original canonical field contract. The RTL interface guide demonstrates why display order and underlying identity are distinct concerns.

When an extracted value is missing, preserve that absence. Do not substitute the expected value for convenience, use the transcript's most plausible token or claim that a later successful task proves the original extraction was correct. Recovery and initial capture are separate observations.

If the application permits multiple acceptable literal forms, define that set before evaluation and preserve the selected interpretation. Do not broaden equality after inspecting a failed case simply to convert it into a match. Some representation changes can be legitimate, but the policy must show why they preserve the task's identity and must include negative cases that remain different despite looking superficially similar.

Use task evidence from the owning system

Task completion requires a declared external outcome, such as an authoritative booking record or a verified workflow result. Neither a fluent response nor a matching field establishes that the downstream system accepted and completed the action.

The artifact accepts success, failure or unknown and requires an evidence reference for a known task outcome. The adapter is trusted to make that reference meaningful. The code cannot contact the owning system or validate the authenticity of a supplied acknowledgment.

Keep pending work unknown until the required observation arrives. A timeout can mean the result was not observed, not that the operation failed. The truthful waiting-speech guide explains why the spoken response should preserve that uncertainty too.

A successful downstream action does not prove that every intermediate transcript token was correct. The application may have clarified the value, used a verified lookup or recovered through another modality. Preserve those recovery steps when interpreting the relationship between transcript quality and task success.

The DTMF and voice guide provides an example of a recovery path that changes the capture channel. Credit the completed task appropriately while retaining which channel actually resolved the critical value.

Conversely, a perfect transcript can accompany a failed action because authorization expired, state changed or the business system rejected the request. Those outcomes belong in the task metric without being mislabeled as recognition errors. The evaluation should help route the failure to the boundary that owns it.

Make evaluation criteria explicit

In this table, ✓ means the bounded artifact checks or emits the stated evidence. ✗ means another adapter, process or measurement is required. The symbols describe implementation scope rather than a provider ranking.

Criterion Local artifact Boundary
Corpus WER uses summed raw counts ✓ Declared whitespace-token observations
Empty-reference insertions remain visible ✓ Null rate with zero denominator
Paired arms share case and reference identity ✓ Declared digests and immutable arm records
Critical literals preserve leading zeros ✓ Exact string field contract
Missing observations remain scheduled ✓ Per-arm missing counts and ledger
Reference transcripts are acoustically correct ✗ Independent reference preparation
Task evidence is authentic ✗ Trusted owning-system adapter
Synthetic comparison predicts real-caller performance ✗ Appropriately designed real-speech study

OpenAI's evaluation best practices support defining evaluations around the actual task. For a voice workflow, that means retaining transcript quality as one measurement while directly evaluating important fields and outcomes at their own boundaries.

NIST's AI Risk Management Framework provides broader context for measurement and evaluation under the intended use. It does not supply a universal WER threshold that guarantees safe application behavior.

Corpus WER sums edits over reference tokens while fields and task outcomes use separate denominators

Inspect workload and cohort weighting

Corpus WER weights by reference-token count. Long utterances contribute more denominator than short ones. That is appropriate for the defined corpus metric, but it can obscure a short critical command whose failure has greater operational significance.

Report task-relevant slices and critical-case outcomes alongside the aggregate. A collection dominated by ordinary read sentences may provide little evidence for spelled identifiers, interruptions or corrections. Do not interpret its size as coverage of a task it barely contains.

The accent-cohort guide explains why cohort design should reflect evidence and avoid stereotypes. A broad speaker label cannot replace the specific channel, task and recording conditions needed to interpret an error.

For code switching, declare the representation and reference policy. The Hindi-English language-hint guide and Tamil-English name guide examine distinct sources of ambiguity that a single aggregate WER cannot explain.

Keep missing hypotheses visible by slice. A recognizer that times out on difficult cases can appear better if the benchmark scores only completed outputs. The artifact's missing count is a starting point; the external collection ledger should retain the reason and scheduled population.

Do not compare independent vendor tables as if they shared a controlled workload. Different reference policies, languages, channels and case selection can dominate the difference. Use public results for their stated scope and run an appropriate paired study for application-specific claims.

Keep repeated sources from inflating apparent support. Several transformations of one recording can be useful software fixtures while remaining dependent evidence about one original source. A large token denominator does not automatically mean broad speaker or task coverage. Record lineage in the external collection manifest and review source-disjoint evidence when the claim depends on generalization beyond familiar recordings.

Separate recognition from recovery

A task can succeed after an initial recognition error. The evaluation should retain both facts. Otherwise a robust clarification path can look like poor recognition with no business value, or successful recovery can erase the initial error from the reliability record.

Record when a value was first captured, when it changed and which candidate was confirmed. The self-correction guide keeps later intent from being overwritten by an earlier candidate that happened to receive a high score.

The bounded artifact compares supplied final field values and task dispositions; it does not model the entire repair timeline. A real adapter that claims recovery performance must provide the intermediate candidate and confirmation evidence under a separate trace contract.

Clarification is not automatically failure. For an unfamiliar surname, asking a precise question can be the correct policy. The surname clarification guide explains why silently substituting a familiar name can produce a smoother but less correct interaction.

Measure recovery cost separately when relevant, including additional turns, unresolved outcomes or delay under a declared clock boundary. Do not add a made-up penalty to WER and call it task success. An explicit multi-metric report is easier to audit than a composite score with hidden weights.

Keep safety-critical invariants outside a fluency judge where deterministic checks are possible. Exact identifier equality, admitted currency context and confirmation revision binding can be checked directly. A model judge may assist with other rubric questions, but it should not replace observable field evidence.

Keep adaptation claims narrow

Recognition adaptation can help a system favor task-relevant vocabulary under the documented interface. It does not establish that a recognized entity is correct, belongs to the caller or is authorized for the requested action.

Google's adaptation documentation describes mechanisms for influencing recognition of words and phrases. Those are decoding-related capabilities, not a guarantee that a captured code or name is the intended business value.

Whisper's public repository documents its models and published evaluation context, including language-dependent performance. Public transcript metrics should be read under their stated data and normalization scope rather than converted into task-completion promises.

Preserve adaptation configuration in the external arm provenance when it changes. A model comparison with different phrase hints is a pipeline comparison too. The local pipeline field is a controlled reference to that configuration; its short string is not proof of identical upstream behavior.

A real developer question about numeral transcription appears in Whisper's discussion. Treat it as historical context for representation policy. Do not reuse another person's identifiers or recordings, and do not treat the thread as a current controlled model comparison.

Test adaptation on negative cases as well as favored vocabulary. A policy that makes one term easier to emit may introduce false substitutions elsewhere. The right evaluation includes task-relevant confusions and preserves original failures rather than selecting only improved examples.

Review a changed normalization policy

A normalization revision is a measurement change. If a new rule equates digit words with numerals, strips punctuation or folds case, recompute comparable arms under an explicitly versioned policy and explain which distinctions it removes.

For critical fields, preserve the admitted literal before any transformation. The money and quantity guide shows why apparently equivalent numeric surfaces can still lack a currency or unit. A text normalizer cannot supply that missing context.

Dates require similar care. The locale and timezone guide separates a date-only value from an instant. A reference policy that invents missing time context can make incorrect extraction appear exact.

Use explicit negative fixtures for every broadened equivalence. If punctuation is ignored for conversational WER, verify that the critical-field comparator still preserves punctuation where identity requires it. Keep metric normalization separate from the parser that prepares an authorized action.

Review the reference preparation process too. A revised transcription convention can change every arm's denominator or edit count. That is not evidence that recognizers changed. Retain old references and version the new study instead of overwriting historical results in place.

The local artifact rejects unsupported normalization names rather than pretending to implement them. Extend it only when the new policy has a precise contract and meaningful fixtures. A small honest subset is more useful than a universal cleanup function whose semantic effects are unclear.

Keep normalization provenance with both the human reference and the hypothesis. Applying a transformation to only one side changes the comparison and can create systematic errors unrelated to recognition. The artifact admits raw strings under one shared policy and checks reference identity before alignment. A production adapter that performs additional preprocessing should preserve its version and enough permitted source evidence to explain the transformed tokens.

When reviewers disagree about an equivalence, retain the original failure until the policy is resolved. It may become a reference correction, an accepted alternate form or a genuine model error. Those dispositions require different follow-up. Replacing the reference with the model's answer before deciding which interpretation is supported would turn the benchmark into an unrecorded selection process.

Conversational token normalization and exact literal-field comparison remain separate policies

Build a release evidence bundle

Store the scheduled case manifest, reference policy, arm provenance, raw counts, per-case fields, task dispositions and evidence limitations together. A reviewer should be able to determine which population supports each metric without reconstructing it from a dashboard screenshot.

Use synthetic fixtures for public reproducibility and appropriately governed real-speech evidence for empirical claims. The synthetic-versus-real guide keeps software regression coverage separate from real-caller representativeness.

Before a release, inspect the cases where transcript quality and task evidence disagree. Those cases reveal whether a recognizer improvement hides a field regression, whether recovery is effective or whether business-system failures dominate the task result.

If a run is interrupted, preserve every scheduled identity and mark the observation state. A lower WER over survivors should not become the headline for a run that failed to produce many difficult cases. Keep execution reliability and transcript scoring as separate reported outcomes.

Decide the release rule explicitly. It may require no unresolved regression in a predefined critical-case set, sufficient coverage for a required task slice or an investigated change in unknown outcomes. This article supplies the accounting method, not a universal threshold for every workflow.

Keep metric computation failures visible as their own run outcomes. A malformed reference, unsupported field type or exceeded alignment bound should stop that audit invocation with a clear error. The surrounding evaluation runner can continue unrelated work, but it must retain the failed identity and reason. Returning a zero rate for an audit that never completed would confuse software reliability with successful recognition.

Assign each release concern to an owner and an observation boundary. A changed literal points toward capture or extraction review; a matching field with unknown completion points toward downstream evidence collection; missing transcripts point toward execution reliability. This routing is more useful than asking one aggregate WER number to explain every failure in a voice workflow.

Success, failure and unknown task outcomes require independent evidence and remain distinct from transcript quality

Frequently asked questions

What does word error rate measure?

It measures token edit operations against a reference under a declared normalization and tokenization policy. It counts substitutions, deletions and insertions. It does not directly measure field semantics, authorization, caller understanding or business completion.

Why can lower WER produce worse tasks?

Many harmless transcript edits can disappear while one critical identifier or negation becomes wrong. The synthetic paired fixture demonstrates that arithmetic. Its task labels are supplied independently rather than inferred from the lower transcript score.

How should corpus WER be aggregated?

Sum raw substitution, deletion and insertion counts, then divide by summed reference-token counts. Averaging per-utterance percentages produces a different weighting. Keep the numerator, denominator and observed case population with the reported rate.

What happens when the reference is empty?

Preserve insertion counts and mark the per-case WER unavailable under this article's contract. If the whole corpus reference denominator is zero, its WER is null too. Other libraries may declare different empty-input reporting conventions.

Can WER exceed one hundred percent?

Yes. Insertions can make the error numerator exceed the reference-token denominator. Do not clamp the result into a probability range. Keep the raw counts so the reported value can be recomputed and interpreted correctly.

Which fields deserve separate metrics?

Fields whose exact or typed meaning affects the task, including identifiers, amounts, dates and important negation. Define each field's grammar and equality policy explicitly. This artifact demonstrates exact string fields only, with other semantic adapters external.

How should leading zeros be evaluated?

Preserve literal identifier strings and compare them under the declared field contract. Converting to integers can erase a meaningful difference. The fixture deliberately loses zeros in one arm and retains the expected and observed values in the ledger.

Why are negation errors important?

A single deleted word can reverse the requested action while contributing only one transcript edit. WER exposes the deletion count but not its operational meaning. Evaluate intent, candidate confirmation and authorized action under their own contracts.

Should number words become digits?

Only under a declared evaluation or field policy appropriate to the task. Record the transformation and retain raw input. Currency, unit, identifier identity and calendar context cannot be inferred merely because a numeric conversion succeeds.

How does CER differ from WER?

Character error rate applies edit accounting to declared character units rather than word tokens. It can illuminate different representation errors, but it still does not measure business success. Define Unicode and normalization conventions before comparing results.

Does phrase adaptation verify an entity?

No. It can influence recognition under a documented interface. Verification requires the task's external evidence and identity context. A favored spelling can still be wrong for the caller or unauthorized for the requested action.

Can transcription confidence establish task success?

No. Its event and calibration evidence differ from a completed business operation. Keep confidence, critical-field correctness and task outcome separate. An unavailable task acknowledgment remains unknown even when the recognition score is high.

How should unconfirmed fields be counted?

Preserve their explicit observation state and avoid calling them confirmed values. This artifact records missing extracted literals separately. A real confirmation-performance claim also needs candidate revision and caller-response evidence from an external interaction trace.

What is authoritative completion evidence?

Evidence from the system that owns the declared outcome, interpreted under a defined task contract. A judge label or spoken success message is insufficient. The local code trusts evidence references and explicitly does not authenticate them.

How should multilingual tokenization be declared?

State the supported language and segmentation policy, preprocessing and reference representation. Whitespace splitting is a narrow teaching policy here. It should not be described as a complete tokenizer for every script or a universal multilingual benchmark convention.

Can long utterances dominate the aggregate?

They contribute more reference tokens to corpus WER. That is the metric's defined weighting, but it can obscure short critical commands. Retain critical-case and task-slice outcomes alongside the aggregate rather than changing its meaning silently.

How do paired cases reveal regressions?

They compare both arms on the same frozen reference and expected fields. Separate run and pipeline provenance exposes what was declared to change. Missing observations remain visible, and changed reference identities are rejected before arithmetic.

Should synthetic examples rank ASR providers?

No. They verify local accounting and boundary behavior. This example does not invoke any recognizer. Real provider comparisons need a controlled workload, permitted recordings, appropriate references and clearly stated scope and uncertainty.

How should corrections change expectations?

Version the intended candidate and bind confirmation to the current revision. Preserve the earlier evidence for audit. A final-field comparison can evaluate the supplied final target, but it cannot reconstruct the repair timeline without an external trace.

Which failures require escalation?

Use task-specific policies for unresolved identity, ambiguous values, authorization failure and unavailable outcomes. Retrying recognition cannot repair every boundary. Preserve the reason for escalation separately from transcript edits so the metric remains interpretable.

Begin with the error that changes the action

Choose one critical field in the workflow and write its literal or typed equality contract. Pair old and new observations over frozen references, run the complete fence and inspect raw counts before comparing rates.

Then connect task outcomes to independent evidence from the owning system. Keep missing observations and recovery behavior visible. A useful release report can show improved transcription while honestly blocking a critical-field regression, because it measures the decision the application actually needs to make.