Part II: Pipeline

Metrics and evaluation

The evaluation infrastructure is written from scratch in this repository. No evaluation library is used: edit distance, error rates, latency accounting, response scoring, hallucination classification and bootstrap intervals are all written in this repository.

Text normalization

Every reference and hypothesis passes through the same function before scoring: Unicode NFC normalization, then whitespace collapsed to single spaces. Case and punctuation are kept (Telugu has no case; punctuation differences count). NFC matters for Telugu because vowel signs and combining marks can be encoded in more than one equivalent way.

WER and CER

WER = word_edits / reference_words
CER = character_edits / reference_characters

word units      = normalized text split on whitespace
character units = Unicode code points of the text with whitespace removed
edits           = unit-cost insertions + deletions + substitutions (Levenshtein)

Corpus vs. per-sample

Corpus WER/CER (the headline) = total edits over all samples divided by total reference length, so longer questions weigh more. The report also gives per-sample mean, median (p50) and 95th percentile, using linear interpolation between ranks.

Latency and real-time factor

MetricDefinition
asr_seconds, llm_seconds, total_secondsWall clock with CUDA synchronization. Includes decoding, resampling, optional denoising and generation. Excludes model loading and metadata reads.
rtftotal_seconds / audio_seconds for one sample. Below 1 means faster than real time.
asr_rtfasr_seconds / audio_seconds. ASR stage alone.
corpus_rtfTotal pipeline seconds divided by total audio seconds over successful samples.
DistributionsMean, p50, p95 of each stage and of end-to-end time.

Audio duration is read from file metadata (frames / sample rate) without decoding samples. The extra "clean transcript" LLM call used for the paired comparison is excluded from pipeline latency and RTF, and reported separately (clean_llm_seconds).

Response quality

The default score is multiset word-overlap F1 in [0, 1], a transparent lexical proxy:

overlap = sum over words of min(count in reference, count in hypothesis)
F1      = 2 * overlap / (|reference words| + |hypothesis words|)

both empty -> 1;  exactly one empty -> 0;  word order is ignored

It is not semantic correctness: a valid paraphrase or a different inflection can score low, and a wrong answer that shares words with the reference can score higher than it deserves. That is why the stricter fact test exists. A custom scorer (higher is better, finite, in [0, 1]) can be passed programmatically and is then applied to both arms.

Exact-match rates (normalized equality with the reference answer) are also reported.

The paired error-propagation comparison

Each question is answered twice by the same LLM, with deterministic decoding and no chat history, and both answers are scored against the same human reference answer:

Audio
→
ASR
→
LLM
→
answer from ASR transcript
Human transcript
→
same LLM
→
answer from perfect transcript

This isolates ASR's downstream effect. When the two arms score about the same, the LLM, not the recognizer, is what limits quality.

Correctness and hallucination rate

The requirements ask for "response correctness and hallucination rate using clearly defined evaluation criteria". Because each reference answer states exactly one fact, a response can be classified against it with a rule that needs no human judge. hallucination.py does this post hoc from saved report.json files, with no model inference.

ItemDefinition
Key factFor a question_cap-* audio file: the capital name (last word of the reference answer). For question_sum-*: the sum word (the word before వస్తుంది).
correctThe normalized response contains the key fact as a substring. For sums, the digit form also counts, matched as a whole number (so "5" does not match inside "15").
abstainedNot correct, but the response contains an uncertainty marker: తెలియదు ("don't know"), క్షమించండి ("sorry"), or స్పష్టంగా లేదు ("not clear").
hallucinatedNeither: a wrong or off-topic answer asserted without hedging, despite the system prompt telling the model to ask for clarification and not invent facts.
correct_rate       = correct / samples
hallucination_rate = hallucinated / samples

It is computed for both arms (answer from ASR transcript; answer from human transcript), so ASR-induced and LLM-induced failures can be told apart.

Known limits of this test It is a lexical heuristic. A correct answer in a different inflection of the key fact is miscounted as hallucinated, so the rate is an upper bound. A wrong answer that happens to contain the key fact inside longer text counts as correct. It only applies to this template corpus, since it reads the cap-/sum- markers in the audio file names. Manual inspection of samples shows the answers are genuinely off-topic, not merely re-inflected.

Confidence intervals

bootstrap.py gives a 95% paired bootstrap interval for the difference in corpus WER or CER between two systems:

  1. Pair the two reports' rows by sample ID (same question in both).
  2. Resample the question indices with replacement, 10,000 times, with a fixed seed.
  3. For each resample, recompute each system's corpus rate (sum of edits / sum of reference units) and take the difference.
  4. Report the point difference and the 2.5th and 97.5th percentiles.

An interval that contains 0 means the difference is not distinguishable from question-sampling noise. It does not capture speaker or recording variation, since the question set has none.

Failure handling

A sample whose pipeline raises (for example an empty ASR transcript) is recorded in failures.csv and excluded from every aggregate. samples_failed and failure_rate sit next to the metrics, because a successful-only average can look better than the truth. The CLI still saves partial reports and exits with status 1; --fail-fast stops at the first failure instead. In the reported runs, 0 of 129 samples failed in every condition.

Evaluator outputs

FileContents
report.jsonMetadata, configuration, summary metrics, every paired output, per-sample measurements and failures. Strict UTF-8 JSON.
samples.csvOne row per sample with all measurements (UTF-8 with BOM for spreadsheets).
failures.csvSample id, path, error type and message.
wer_vs_quality.csvPlotting points: WER, ASR and clean response quality, quality drop.