ealeng
Menu

Episode 05 of 10

Testing streaming speech-to-text

Live captions can show the wrong name before they show the right one. Live transcription adds three new questions: how fast do words appear, how often do they change, and when has the speaker finished?

Lab 05 · Streaming Speech-to-Text6 min read
Video transcript6:40

0:00Cold open

0:01

These are live captions, replayed exactly as two speech systems wrote them, while the person was still talking.

0:08

🔊 “Sancho got up with pain enough in his bones, and went after the innkeeper…”

0:14

Did you catch the name? The speaker said: Sancho.

0:18

The top one wrote Sandra, then fixed it two seconds later.

0:22

The bottom one fixed boat to bones. But it kept Sandra, all the way into its final transcript.

0:29

So how do you test something that changes its mind while you watch?

0:33Intro

0:35

Hands-on voice AI evals, episode five.

0:38

Today: testing speech-to-text that works live, word by word.

0:4301 · Batch vs. streaming

0:44

Until now, every test sent a whole recording, and got one transcript back. That’s batch.

0:50

A live voice agent can’t wait for the recording to end. It streams: it sends the audio in small chunks as you speak, and gets text back while you’re still talking.

1:01

So the model has to guess before it hears the end of the sentence. Those guesses are partials, in amber. They can change. A final is a promise: it won’t change.

1:11

Here’s one short sentence, streamed to four systems.

1:15

🔊 “Do you suppose the miniature was a copy of the same thing?”

1:19

They behave differently. Nova-3 sends a guess about once a second. Flux updates four times a second. OpenAI’s live model only adds words, and never takes one back.

1:30

And Whisper, re-run on the growing audio every second, wrote: a copy of the miniature. Then it corrected itself: a copy of the same thing.

1:4002 · The benchmark that lies

1:41

The first rule of testing streaming is about how you send the audio.

1:45

If you send a ten-second recording as fast as your network allows, the service gets all of it in a moment.

1:52

Sent that way, OpenAI’s final text came back before the speaker would even have finished talking: on half of ten clips, more than five seconds early. That can’t happen on a real call.

2:04

So a streaming test sends the audio at the speed it was spoken: one second of audio, every second.

2:10

And it measures each delay from the moment a word was spoken in the recording.

2:1603 · Which “fast”?

2:17

“Fast” can mean four different things. And the systems rank differently on each.

2:22

Word on screen: from the moment a word is spoken, to when it first appears. Flux is the fastest: a fifth of a second.

2:30

Word final: until that word can’t change anymore. Here Flux is the slower of the two Deepgram systems: over five seconds.

2:38

Why? Flux waits until you’ve finished your whole turn, everything you say before you stop for a reply, then locks it in at once. Nova-3 locks in a phrase at a time.

2:49

Speaker stops, to final: what a voice agent waits for before it can answer. Nova-3: half a second.

2:56

OpenAI’s live model doesn’t detect the end of a turn. The app decides, here after two and a half seconds of silence.

3:04

So for live captions, care about words on screen. For an agent that acts on what you said, care about finals.

3:1204 · Flicker

3:13

Partials that keep changing are called flicker. In captions, the text jumps around. And an app that acts on a partial can act on a word that later disappears.

3:23

So I counted how often a word on screen was changed by the next update.

3:28

OpenAI’s live model: four percent, and nearly all of those are half-written words being finished. It only adds text.

3:36

Whisper, re-run every second: about one word in four changed.

3:40

You can buy stability. Show a word only when two guesses in a row agree, and never take it back.

3:46

Rewrites dropped from twenty-seven percent to two. But words appeared later: from under a second, to over two.

3:53

Stability and speed trade against each other, so a test has to report both.

3:5905 · When is the speaker done?

4:00

A voice agent also has to decide when you’ve finished talking, so it knows when to answer.

4:05

The simplest way is a silence timer. Three hundred milliseconds of silence, and your turn is over.

4:12

So I took sentences and put a pause in the middle. Here, eight tenths of a second.

4:17

🔊 “The count had thrown himself back on his seat… leaning his shoulders against the partition…”

4:26

Where do you think the turn ended?

4:30

Right in the pause, after: the count had thrown himself back on his seat. An agent would start answering in the middle of the sentence.

4:38

With pauses that long, the three hundred millisecond timer cut off all twelve sentences.

4:44

A one-second timer cut off none of them. But replies came more than twice as slowly.

4:49

Flux uses a model that listens to what’s being said. By default, it cut off four of the twelve, and ended turns in about a second.

4:58

That’s the trade-off: fewer cut-offs, or faster replies. So test it with pauses like the ones your callers make.

5:0606 · One stream in 488

5:07

Last, I checked every stream I recorded for problems: four hundred and eighty-eight of them.

5:13

One failed. A Flux stream, on a clip of an earnings call that ends mid-sentence: our Chief Executive Officer, and…

5:21

The screen showed the words as they were spoken. But the stored transcript was empty.

5:27

Flux never ended the turn. Two and a half seconds of silence went by, then another second, and it was still waiting, probably because the sentence wasn’t finished. Then my test closed the stream, and nothing was ever finalized.

5:41

Remember the empty answers from episode two? The same result for the user, from a different cause.

5:47

One more: if the app goes quiet for twelve seconds, mid-stream, the service closes the connection. None of the six transcripts was complete.

5:56

Sending a small keep-alive message while quiet fixed all six.

6:0107 · What I’d measure

6:02

So, for a voice agent, here’s what I’d measure. Send audio in real time. And time the event your app acts on: words on screen, finals, or the end of the turn.

6:13

Count how often words change. Test pauses in the middle of sentences. And check that every stream ends with a final.

6:21

Next time, the other direction: turning text into speech. Does it say the right thing, and how fast does it start talking?Yuki: Does it say the right thing? 🤔

6:29Summary

6:30

Streaming adds a new question: not only what was said, but when. Measure both.

6:35

Then your live captions keep up, and stay right.Yuki: Thanks! 😄

Key takeaways

  • ✓Stream audio in real time and measure latency against the audio itself. Sending faster made results arrive "before" the speaker finished: fiction.
  • ✓There are four latencies, and they disagree: Flux showed words fastest (0.21 s) but finalized them slowest (5.25 s).
  • ✓Partials flicker. Showing only words two guesses agree on cut rewrites from 27% to 2%, and delayed words on screen from 0.72 s to 2.23 s.
  • ✓Streaming costs accuracy: Nova-3 streaming was about 1 WER point worse than Nova-3 batch on the same clips.
  • ✓A silence timer cuts off every pause longer than itself. Test endpointing with pauses of known length.

Everything so far has been batch transcription: send a file, get a transcript. Live captions and voice agents use streaming: audio is sent in small chunks as it's recorded, and the text comes back while the person is still talking. That changes what you have to measure.

Batch Streaming
Input The whole file Small chunks (80 ms here) over a WebSocket
Output One transcript A running series of results
Context The model hears the future of every word It must commit before hearing what comes next

A stream sends back three kinds of event. A partial is the current best guess, and it can change. A final won't change, so an app can act on it. A turn event says the speaker seems to be done. Systems differ in all three, so the first question of any streaming benchmark is "which event are we timing?"

Measure on the audio clock

Two rules make streaming numbers honest:

  • Send audio in real time. Chunk k leaves at k × 80 ms. In the lab, sending recorded audio as fast as possible made results arrive "before" the speaker finished: Nova-3's time from speech end to final went from +0.42 s to −1.37 s, and OpenAI's live model from +3.20 s to −5.43 s. The transcripts were identical either way. Only the timing lied.
  • Measure against the audio. "When was this word spoken?" is a position in the audio, not a timestamp in a log. The lab caught itself twice here: one version timed a simulated model against when it ran, not when the words were said, and another took speech boundaries from a VAD tuned for transcription, whose 0.4 s padding made Nova-3's first word look 1.00 s late instead of 0.76 s.

Four latencies, four answers

"How fast is it?" has at least four answers, and they disagree.

Streaming STT architecture: the client sends 80 ms audio chunks over a WebSocket; the server returns partials, finals, and turn events. A timeline compares batch latency to the four streaming latencies: first word, word on screen, word final, and turn event. Partials, finals, and turn events — and the four clocks they produce.

Try it · How long until the words appear?Real results from Lab 05 · medians

A word is spoken → it first appears (as a partial). What live captions feel like.

  • nova-30.57 s
  • flux0.21 s
  • live-openai0.92 s
  • local-whisper (sim.)0.72 s

Flux shows words fastest (0.21 s) but finalizes them slowest among Deepgram's models (5.25 s), because it finalizes a whole turn at once. Which number matters depends on whether your product acts on partials, finals, or turns.

OpenAI's delay setting · 20 clips

minimal
0.33 s
WER 8%
medium (default)
0.92 s
WER 4.5%
xhigh
1.34 s
WER 3.5%

A clean trade: three times faster on screen, twice the errors.

The ranking flips depending on the question. Flux shows words fastest, but finalizes them slowest among Deepgram's models, because it finalizes a whole turn at once. And "speech end → final" depends on who decides the speaker is done: the service (Nova-3, Flux) or the app (OpenAI's live model has no turn detection, so the app commits after silence). That's a system-design fact, not model quality.

Partials that change their mind

Partials that keep changing (flicker) look broken in captions, and an app that acts on a partial can act on a word that later disappears. This is the chapter's title in action: in the lab, Nova-3 and Flux both started with "Sandra got up with paid enough in his boat" before correcting it to "Sancho got up with pain enough in his bones".

The rewrite rate is the share of shown words that a later update changed or removed. It varied a lot: OpenAI's live model (4–5%) mostly appends; Nova-3 rewrote 12–14%, Flux 19–25%, and re-running Whisper on growing audio up to 34%.

You can buy stability with latency. Local agreement shows a word only once two consecutive guesses agree on it, and never takes it back:

Try it · Partials that change their mindAn illustrative stream, from the lab's real example
Live captions3.1 s · final

Sanchogotupwithpainenoughinhisbones

Words rewritten
3
Rewrite rate
25%
“Sancho” first shown
1.0 s

Orange words just changed. Showing every guess is fast but flickers: words appear, then turn into other words. In the lab, local agreement cut rewrites from 27% to 2% and delayed words on screen from 0.72 s to 2.23 s.

One more subtlety: for Flux and OpenAI, the final just restates the last partial. Nova-3's final is a separate, more careful pass that changed 10.3% of the words. So "treat the last partial as final" is free on two systems and costly on the third.

What streaming costs in accuracy

A streaming model must commit before hearing what comes next, so it usually costs accuracy. On the same 59 clips, with speakers resampled (Chapter 4), Nova-3 streaming was +1.01 WER points worse than Nova-3 batch (95% CI +0.14 to +1.78): same model family, same clips, so that's the real price of streaming. The other comparisons were "can't tell".

Be careful what you compare. The lab's local Whisper pair seemed to stream better than batch, but the streaming side used a VAD, which removes hallucinations (Chapter 3). Name every difference between the two sides before calling a gap "the cost of streaming".

When is the speaker done?

Deciding that the speaker has finished (endpointing) is a trade-off with no free answer. A silence timer ends the turn after N ms of silence: short timers cut people off at pauses, long timers make every reply slow. An end-of-turn model (Flux) uses what's being said: "moved the appointment to…" is unfinished.

The lab tested it the right way: 12 sentences, each with a pause of known length spliced in mid-sentence. A turn that ends during the pause is a cut-off.

Try it · When is the speaker done?Real results from Lab 05 · 48 clips
0.4 s pause
1/12 cut off
0.8 s pause
12/12 cut off
1.5 s pause
12/12 cut off
3 s pause
12/12 cut off

Turn ends after speech: p50 0.50 s · p90 0.90 s

The silence-timer rule

“I'd like to move my appointment to pause Thursday.”

  • 0.4 s: waits
  • 0.8 s: cut off
  • 1.5 s: cut off
  • 3 s: cut off

A timer cuts off every pause longer than itself, whatever is being said, and every reply waits at least 700 ms. Short timers cut people off; long timers make every reply slow. An end-of-turn model (Flux) uses what's being said instead: “move my appointment to…” is unfinished.

Real systems are fuzzier near the threshold: the timer starts when the service decides sound has stopped, so Nova-3's 300 ms timer cut off only 1 of 12 sentences at a 0.4 s pause.

Flux's model shifted the trade-off: with its default setting it ended turns faster than Nova-3's 1-second timer (0.96 s vs 1.28 s) with about the same number of cut-offs. With a stricter threshold it waited through every 1.5 s pause. One caveat the lab names: these pauses are digital silence spliced into read speech. Real pauses carry breath and room noise, and real people pause where the grammar invites it.

Reliability is part of quality

Over many streams, connections drop and turns don't finalize. Across 488 recorded streams the lab found one problem, and it's instructive: an earnings clip ended mid-sentence ("…our Chief Executive Officer and"). Flux's model heard an unfinished thought and kept waiting; the client closed after about 3.5 s of silence. The screen showed the words, and the stored transcript was empty. That's the flip side of resisting cut-offs: a client using a turn model must wait out the timeout, or force the end.

And a 12-second stall (a caller on hold) closed all 6 streams without KeepAlive messages, losing everything after the stall. With KeepAlive, all 6 were complete.

Practice: measure a stream

Implement the measurement side of streaming: the real-time send schedule, the rewrite count, local agreement, and a silence-timer endpointer. The tests use the stream from the widget above, built from the lab's real "Sandra → Sancho" example.

Try it · Practice in PythonRuns in your browser · ⌘/Ctrl + Enter to run
streaming_practice.py57 lines

Quiz

Try it · Check your understanding0 of 6 answered
  1. 1.You benchmark a streaming API by sending a recorded file as fast as the connection allows. The speech-end-to-final latency comes out at −1.37 s. What happened?

  2. 2.Your product is a voice agent. Which latency limits how soon it can start replying?

  3. 3.Flux shows words on screen in 0.21 s, but its words become final after 5.25 s. Nova-3: 0.57 s on screen, 2.52 s final. Which is better?

  4. 4.Your app treats the last partial result as if it were final, to save time. On which system is that costly?

  5. 5.You set the silence timer to 300 ms so replies feel snappy. What happens to a caller who pauses for 0.8 s mid-sentence to find their account number?

  6. 6.A stream stalls for 12 seconds (the user put the call on hold). What did the lab find?

Check your understanding

Try each question before opening its answer.

How do batch and streaming STT differ, and why does streaming usually cost accuracy?

Batch sees the full file before transcribing; streaming processes real-time chunks and must commit with less future context. Compare batch and streaming on the same clips to measure the cost.

What's the difference between partial and final results, and which should an app act on?

Partials can change; finals are committed. Captions may display partials, but actions and stored records should use finals or turn events unless the product accepts the risk of acting early.

Define first-word latency, per-word latency, and time to final. Which matters most for a voice agent?

First-word latency is speech start to first text; per-word latency is when a spoken word appears; time to final is speaker stop to final transcript. A voice agent usually cares most about end-of-turn/time-to-final before replying.

Why send audio in real time, and how do you measure when a word was spoken?

Faster-than-real-time playback can make output appear before a live speaker could have finished. Schedule chunks on the audio clock and align result timestamps to word positions in the audio.

How do you measure partial stability, and what does stability cost?

Count shown words later changed or removed and divide by words shown. Local agreement reduces rewrites, but delays words appearing on screen.

What's the endpointing trade-off, and how do you test a turn detector?

Short waits cut callers off at pauses; long waits delay replies. Insert known pauses mid-sentence, count cut-offs, and measure delay from actual speech end to detected turn end.

Which operational failures belong in a streaming quality report?

Track connection drops, turns without finals, words displayed but never finalized, duplicate or out-of-order messages, and stall/reconnect behavior. Report rates with uncertainty, especially when samples are small.

Next up: the other direction. Chapter 6 tests text-to-speech, where a voice said "negative nine quetzals" for a password.

Learning something useful?

New labs, articles, and videos on evaluating and testing AI systems come out regularly. Follow along so you don't miss the next one.