<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Smallest AI</title>
    <description>The latest articles on DEV Community by Smallest AI (@smallestai).</description>
    <link>https://dev.to/smallestai</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3854927%2F8dabc078-5e46-402d-ad31-71a95a7a510b.png</url>
      <title>DEV Community: Smallest AI</title>
      <link>https://dev.to/smallestai</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/smallestai"/>
    <language>en</language>
    <item>
      <title>Your Speech-to-Text API Is Only as Good as the Audio You Send It</title>
      <dc:creator>Smallest AI</dc:creator>
      <pubDate>Mon, 03 Aug 2026 18:27:51 +0000</pubDate>
      <link>https://dev.to/smallestai/your-speech-to-text-api-is-only-as-good-as-the-audio-you-send-it-ko3</link>
      <guid>https://dev.to/smallestai/your-speech-to-text-api-is-only-as-good-as-the-audio-you-send-it-ko3</guid>
      <description>&lt;p&gt;A speech-to-text API can perform well on clean benchmark recordings and still struggle with your users’ actual audio.&lt;/p&gt;

&lt;p&gt;That gap is not always caused by the model.&lt;/p&gt;

&lt;p&gt;It can come from:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Background noise&lt;/li&gt;
&lt;li&gt;Incorrect sample rates&lt;/li&gt;
&lt;li&gt;Unsupported audio formats&lt;/li&gt;
&lt;li&gt;Accents and code-switching&lt;/li&gt;
&lt;li&gt;Domain-specific vocabulary&lt;/li&gt;
&lt;li&gt;Choosing batch transcription when the product needs streaming&lt;/li&gt;
&lt;li&gt;Reading the wrong field from the API response&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The fastest way to evaluate speech recognition is not to upload one perfect recording. It is to test the API against the audio your application will encounter in production.&lt;/p&gt;

&lt;p&gt;This guide shows how to integrate the &lt;a href="https://smallest.ai/speech-to-text?utm_source=dev.to&amp;amp;utm_medium=vizup&amp;amp;utm_campaign=streaming-speech-to-text-in-production-handling-dropouts-reconnects-and-duplicates"&gt;Pulse speech-to-text API&lt;/a&gt; from Python and Node.js, process pre-recorded audio, build a real-time WebSocket client, and avoid the response-handling mistakes that often break otherwise successful integrations.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pulse supports two transcription modes
&lt;/h2&gt;

&lt;p&gt;Pulse is the speech-recognition model from &lt;a href="https://smallest.ai/?utm_source=dev.to&amp;amp;utm_medium=vizup&amp;amp;utm_campaign=streaming-speech-to-text-in-production-handling-dropouts-reconnects-and-duplicates"&gt;Smallest AI&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;It supports two distinct integration patterns.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pre-recorded transcription
&lt;/h3&gt;

&lt;p&gt;Use pre-recorded transcription when the entire audio file already exists.&lt;/p&gt;

&lt;p&gt;Common examples include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Call recordings&lt;/li&gt;
&lt;li&gt;Voicemails&lt;/li&gt;
&lt;li&gt;Podcast episodes&lt;/li&gt;
&lt;li&gt;Uploaded videos&lt;/li&gt;
&lt;li&gt;Meeting archives&lt;/li&gt;
&lt;li&gt;Offline transcription jobs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The application sends the complete file through an HTTPS request and receives one structured response.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Audio file
    ↓
HTTPS request
    ↓
Complete transcript
    ↓
Storage or downstream processing
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Real-time transcription
&lt;/h3&gt;

&lt;p&gt;Use real-time transcription when audio is still being captured.&lt;/p&gt;

&lt;p&gt;Common examples include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Voice agents&lt;/li&gt;
&lt;li&gt;Live captions&lt;/li&gt;
&lt;li&gt;Phone calls&lt;/li&gt;
&lt;li&gt;Browser microphones&lt;/li&gt;
&lt;li&gt;Meeting assistants&lt;/li&gt;
&lt;li&gt;Real-time conversation analytics&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The application keeps a WebSocket connection open, sends audio in small binary chunks, and receives partial and final transcript events while the speaker is still talking.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Live audio
    ↓
WebSocket audio chunks
    ↓
Partial transcript events
    ↓
Final transcript events
    ↓
Application action
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The choice is not primarily about which transport looks more advanced.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Use batch transcription when the product can wait. Use streaming when the product must react while the audio is still arriving.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Create and store the API key
&lt;/h2&gt;

&lt;p&gt;Keep the API key in an environment variable rather than hard-coding it into the application.&lt;/p&gt;

&lt;p&gt;Before running the snippet, create a &lt;a href="https://app.smallest.ai/dashboard/api-keys?utm_source=dev.to&amp;amp;utm_medium=vizup&amp;amp;utm_campaign=streaming-speech-to-text-in-production-handling-dropouts-reconnects-and-duplicates"&gt;Smallest.ai API key&lt;/a&gt; in the dashboard and store it in the SMALLEST_API_KEY environment variable.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;SMALLEST_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"your-api-key-here"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every authenticated request sends the value through the &lt;code&gt;Authorization&lt;/code&gt; header:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Authorization: Bearer SMALLEST_API_KEY
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Keep the key on your server. Do not expose it in browser JavaScript, mobile application code, public repositories, screenshots, or client-side logs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Transcribe a pre-recorded file with Python
&lt;/h2&gt;

&lt;p&gt;The current pre-recorded endpoint is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;POST https://api.smallest.ai/waves/v1/stt/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The model, language, and optional features are supplied as query parameters. The audio file is sent as raw bytes in the request body.&lt;/p&gt;

&lt;p&gt;Before running the snippet, create a &lt;a href="https://app.smallest.ai/dashboard/api-keys?utm_source=dev.to&amp;amp;utm_medium=vizup&amp;amp;utm_campaign=streaming-speech-to-text-in-production-handling-dropouts-reconnects-and-duplicates"&gt;Smallest.ai API key&lt;/a&gt; in the dashboard and store it in the SMALLEST_API_KEY environment variable.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pathlib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;typing&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Any&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;


&lt;span class="n"&gt;STT_ENDPOINT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://api.smallest.ai/waves/v1/stt/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;transcribe_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;file_path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;language&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;en&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="n"&gt;audio_path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file_path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;audio_path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_file&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;FileNotFoundError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Audio file not found: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;audio_path&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;audio_path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rb&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;audio_file&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;STT_ENDPOINT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;model&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pulse&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;language&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;language&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;word_timestamps&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;true&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Authorization&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Bearer &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;SMALLEST_API_KEY&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
                &lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Content-Type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;application/octet-stream&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;audio_file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;120&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;raise_for_status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;


&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;transcribe_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;recording.wav&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;transcription&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;words&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[])[:&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One response-handling detail matters immediately:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The pre-recorded response uses &lt;code&gt;transcription&lt;/code&gt;, not &lt;code&gt;transcript&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A simplified response can look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"success"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"transcription"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Hello, this is a test transcription."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"words"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"start"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.48&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"end"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1.12&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"word"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Hello,"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"start"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1.12&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"end"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1.28&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"word"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"this"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"utterances"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"start"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.48&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"end"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;3.76&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Hello, this is a test transcription."&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The returned structures support different downstream workflows:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;Useful for&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;transcription&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Complete readable transcript&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;words&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Captions, subtitle alignment and audio navigation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;utterances&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Sentence-level segments and readable conversation records&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;language&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Language-aware routing and transcript metadata&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Speaker fields&lt;/td&gt;
&lt;td&gt;Multi-speaker calls and meetings&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Do not assume every optional field will always be present. Use &lt;code&gt;.get()&lt;/code&gt; or validate the response against a schema before sending it downstream.&lt;/p&gt;

&lt;h2&gt;
  
  
  Transcribe audio from a URL
&lt;/h2&gt;

&lt;p&gt;Pulse can also process an audio file hosted at a publicly accessible URL.&lt;/p&gt;

&lt;p&gt;This is useful when recordings already live in object storage and downloading them to the application server would add unnecessary work.&lt;/p&gt;

&lt;p&gt;Before running the snippet, create a &lt;a href="https://app.smallest.ai/dashboard/api-keys?utm_source=dev.to&amp;amp;utm_medium=vizup&amp;amp;utm_campaign=streaming-speech-to-text-in-production-handling-dropouts-reconnects-and-duplicates"&gt;Smallest.ai API key&lt;/a&gt; in the dashboard and store it in the SMALLEST_API_KEY environment variable.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;typing&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Any&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;


&lt;span class="n"&gt;STT_ENDPOINT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://api.smallest.ai/waves/v1/stt/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;transcribe_from_url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;audio_url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;language&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;en&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;STT_ENDPOINT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;model&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pulse&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;language&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;language&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;word_timestamps&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;true&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Authorization&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Bearer &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;SMALLEST_API_KEY&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Content-Type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;application/json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;url&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;audio_url&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;120&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;raise_for_status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;


&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;transcribe_from_url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://example.com/recordings/customer-call.wav&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;transcription&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The remote file must be accessible to the API. A URL that requires browser cookies, local authentication, or access to a private network will not work without a suitable signed URL or public access policy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make the same request from Node.js
&lt;/h2&gt;

&lt;p&gt;Modern Node.js versions provide &lt;code&gt;fetch&lt;/code&gt; without requiring &lt;code&gt;node-fetch&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Before running the snippet, create a &lt;a href="https://app.smallest.ai/dashboard/api-keys?utm_source=dev.to&amp;amp;utm_medium=vizup&amp;amp;utm_campaign=streaming-speech-to-text-in-production-handling-dropouts-reconnects-and-duplicates"&gt;Smallest.ai API key&lt;/a&gt; in the dashboard and store it in the SMALLEST_API_KEY environment variable.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;readFile&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;node:fs/promises&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;STT_ENDPOINT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://api.smallest.ai/waves/v1/stt/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;transcribeFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filePath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;language&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;en&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;audio&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;readFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filePath&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;URLSearchParams&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;pulse&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;language&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;word_timestamps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;true&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;STT_ENDPOINT&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;?&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;Authorization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Bearer &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SMALLEST_API_KEY&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/octet-stream&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;audio&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;errorBody&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="s2"&gt;`Transcription failed with &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;errorBody&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;transcribeFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;recording.wav&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;transcription&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;words&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="p"&gt;[]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For a remotely hosted file, change the request body and content type.&lt;/p&gt;

&lt;p&gt;Before running the snippet, create a &lt;a href="https://app.smallest.ai/dashboard/api-keys?utm_source=dev.to&amp;amp;utm_medium=vizup&amp;amp;utm_campaign=streaming-speech-to-text-in-production-handling-dropouts-reconnects-and-duplicates"&gt;Smallest.ai API key&lt;/a&gt; in the dashboard and store it in the SMALLEST_API_KEY environment variable.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;STT_ENDPOINT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://api.smallest.ai/waves/v1/stt/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;transcribeFromUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;audioUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;language&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;en&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;URLSearchParams&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;pulse&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;language&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;word_timestamps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;true&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;STT_ENDPOINT&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;?&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;Authorization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Bearer &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SMALLEST_API_KEY&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;audioUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;errorBody&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="s2"&gt;`Transcription failed with &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;errorBody&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;transcribeFromUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://example.com/recordings/customer-call.wav&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;transcription&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Enable only the enrichment features you need
&lt;/h2&gt;

&lt;p&gt;A production transcript often needs more than plain text.&lt;/p&gt;

&lt;p&gt;Pulse supports optional features such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Word timestamps&lt;/li&gt;
&lt;li&gt;Sentence-level utterances&lt;/li&gt;
&lt;li&gt;Speaker diarization&lt;/li&gt;
&lt;li&gt;Emotion detection&lt;/li&gt;
&lt;li&gt;Gender detection&lt;/li&gt;
&lt;li&gt;Language detection&lt;/li&gt;
&lt;li&gt;Keyword boosting&lt;/li&gt;
&lt;li&gt;PII and PCI redaction&lt;/li&gt;
&lt;li&gt;Inverse text normalization&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These capabilities are enabled through query parameters.&lt;/p&gt;

&lt;p&gt;Before running the snippet, create a &lt;a href="https://app.smallest.ai/dashboard/api-keys?utm_source=dev.to&amp;amp;utm_medium=vizup&amp;amp;utm_campaign=streaming-speech-to-text-in-production-handling-dropouts-reconnects-and-duplicates"&gt;Smallest.ai API key&lt;/a&gt; in the dashboard and store it in the SMALLEST_API_KEY environment variable.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;


&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;call_recording.wav&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rb&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;audio_file&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://api.smallest.ai/waves/v1/stt/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;model&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pulse&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;language&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;en&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;word_timestamps&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;true&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;diarize&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;true&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;emotion_detection&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;true&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gender_detection&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;true&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;itn_normalize&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;true&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Authorization&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Bearer &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;SMALLEST_API_KEY&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Content-Type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;application/octet-stream&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;audio_file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;120&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;raise_for_status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;transcription&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Enable features because the workflow requires them, not simply because the parameters exist.&lt;/p&gt;

&lt;h3&gt;
  
  
  Word timestamps
&lt;/h3&gt;

&lt;p&gt;Useful for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Captions&lt;/li&gt;
&lt;li&gt;Subtitle tracks&lt;/li&gt;
&lt;li&gt;Transcript review&lt;/li&gt;
&lt;li&gt;Search&lt;/li&gt;
&lt;li&gt;Audio playback navigation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Timing information is difficult to reconstruct accurately after transcription. Enable it early when the product may eventually need synchronized text.&lt;/p&gt;

&lt;h3&gt;
  
  
  Speaker diarization
&lt;/h3&gt;

&lt;p&gt;Useful for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Customer calls&lt;/li&gt;
&lt;li&gt;Interviews&lt;/li&gt;
&lt;li&gt;Meetings&lt;/li&gt;
&lt;li&gt;Podcasts&lt;/li&gt;
&lt;li&gt;Sales conversations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Diarization converts a wall of text into speaker-attributed turns.&lt;/p&gt;

&lt;h3&gt;
  
  
  Emotion detection
&lt;/h3&gt;

&lt;p&gt;Useful for enriching offline call analysis and quality-assurance workflows.&lt;/p&gt;

&lt;p&gt;Treat emotional scores as model-generated signals rather than definitive statements about a person.&lt;/p&gt;

&lt;h3&gt;
  
  
  Inverse text normalization
&lt;/h3&gt;

&lt;p&gt;Useful when spoken expressions should appear in conventional written form.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"twenty five dollars"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;can become:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"$25"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This matters for dates, currencies, phone numbers, amounts, and other entities that downstream software must parse.&lt;/p&gt;

&lt;h2&gt;
  
  
  Accuracy begins with representative audio
&lt;/h2&gt;

&lt;p&gt;A published benchmark is not a substitute for testing your own workload.&lt;/p&gt;

&lt;p&gt;Build an evaluation set that includes the conditions your users will create:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Clean studio recordings&lt;/li&gt;
&lt;li&gt;Browser microphones&lt;/li&gt;
&lt;li&gt;Mobile devices&lt;/li&gt;
&lt;li&gt;Telephony audio&lt;/li&gt;
&lt;li&gt;Background conversations&lt;/li&gt;
&lt;li&gt;Fast speech&lt;/li&gt;
&lt;li&gt;Hesitations&lt;/li&gt;
&lt;li&gt;Multiple speakers&lt;/li&gt;
&lt;li&gt;Regional accents&lt;/li&gt;
&lt;li&gt;Domain-specific vocabulary&lt;/li&gt;
&lt;li&gt;Mixed-language conversations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Track more than overall Word Error Rate.&lt;/p&gt;

&lt;p&gt;A transcript can be mostly correct and still fail the application by misrecognizing the exact information that matters.&lt;/p&gt;

&lt;p&gt;Evaluate:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Area&lt;/th&gt;
&lt;th&gt;What to inspect&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;General transcription&lt;/td&gt;
&lt;td&gt;Missing, substituted or invented words&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Entity accuracy&lt;/td&gt;
&lt;td&gt;Names, dates, amounts, addresses and identifiers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Domain vocabulary&lt;/td&gt;
&lt;td&gt;Product names, abbreviations and technical terms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Speaker separation&lt;/td&gt;
&lt;td&gt;Whether speaker labels remain consistent&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Timestamp quality&lt;/td&gt;
&lt;td&gt;Alignment and drift&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Language handling&lt;/td&gt;
&lt;td&gt;Accents, language switching and detection&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Failure behavior&lt;/td&gt;
&lt;td&gt;Empty responses, timeouts and invalid files&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Downstream usefulness&lt;/td&gt;
&lt;td&gt;Whether the next system can reliably use the result&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;For domain terms, product names, and uncommon vocabulary, keyword boosting may be more valuable than repeatedly switching providers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Audio format is part of the model input
&lt;/h2&gt;

&lt;p&gt;Speech recognition cannot recover information that the audio pipeline has already damaged.&lt;/p&gt;

&lt;p&gt;Before blaming the API, inspect:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Sample rate&lt;/li&gt;
&lt;li&gt;Channel count&lt;/li&gt;
&lt;li&gt;Codec&lt;/li&gt;
&lt;li&gt;Container format&lt;/li&gt;
&lt;li&gt;Clipping&lt;/li&gt;
&lt;li&gt;Background noise&lt;/li&gt;
&lt;li&gt;Compression artifacts&lt;/li&gt;
&lt;li&gt;Volume&lt;/li&gt;
&lt;li&gt;Packet loss&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For speech workloads, mono audio at an appropriate speech-oriented sample rate is usually more useful than sending oversized stereo files with no additional spoken information.&lt;/p&gt;

&lt;p&gt;Real-time streaming also requires the client to describe the incoming encoding correctly. Sending μ-law audio while declaring &lt;code&gt;linear16&lt;/code&gt;, for example, will produce unusable results even though the WebSocket connection itself succeeds.&lt;/p&gt;

&lt;h2&gt;
  
  
  Language selection should be deliberate
&lt;/h2&gt;

&lt;p&gt;When you know the language, send its explicit language code.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;en
hi
de
fr
es
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When the language is unknown or the conversation may switch between related languages, use the appropriate regional auto-detection option supported by the chosen transcription mode.&lt;/p&gt;

&lt;p&gt;Do not assume that batch and streaming expose identical language sets or regional routes. Check the current model documentation before shipping a multilingual workflow.&lt;/p&gt;

&lt;p&gt;Explicit language selection is usually the safer choice when the source language is known.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real-time transcription changes the application
&lt;/h2&gt;

&lt;p&gt;Batch transcription produces one result after the file is complete.&lt;/p&gt;

&lt;p&gt;Streaming produces a sequence of changing hypotheses.&lt;/p&gt;

&lt;p&gt;The current WebSocket endpoint is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;wss://api.smallest.ai/waves/v1/stt/live?model=pulse
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A client must:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Open and authenticate the connection&lt;/li&gt;
&lt;li&gt;Send correctly encoded binary audio&lt;/li&gt;
&lt;li&gt;Receive events concurrently&lt;/li&gt;
&lt;li&gt;Replace partial hypotheses&lt;/li&gt;
&lt;li&gt;Commit final segments&lt;/li&gt;
&lt;li&gt;Handle connection failures&lt;/li&gt;
&lt;li&gt;Close the stream deliberately&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Before running the snippet, create a &lt;a href="https://app.smallest.ai/dashboard/api-keys?utm_source=dev.to&amp;amp;utm_medium=vizup&amp;amp;utm_campaign=streaming-speech-to-text-in-production-handling-dropouts-reconnects-and-duplicates"&gt;Smallest.ai API key&lt;/a&gt; in the dashboard and store it in the SMALLEST_API_KEY environment variable.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;WebSocket&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ws&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;URLSearchParams&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;pulse&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;language&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;en&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;encoding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;linear16&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;sample_rate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;16000&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;word_timestamps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;true&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;websocketUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="s2"&gt;`wss://api.smallest.ai/waves/v1/stt/live?&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ws&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;WebSocket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;websocketUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;Authorization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Bearer &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SMALLEST_API_KEY&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;ws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;open&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Connected to Pulse STT&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;ws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;message&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rawMessage&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rawMessage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;is_final&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;[FINAL]&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;transcript&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stdout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`\r[PARTIAL] &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;transcript&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;is_last&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;Stream complete&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;ws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;error&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;WebSocket error:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;sendAudioChunk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;audioBuffer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;readyState&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;WebSocket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;OPEN&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;WebSocket is not open&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;ws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;audioBuffer&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;closeStream&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;readyState&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;WebSocket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;OPEN&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;ws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;close_stream&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;}),&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Send audio as binary frames. A commonly recommended chunk size is &lt;code&gt;4096&lt;/code&gt; bytes, although the correct capture and buffering strategy still depends on the source encoding and application.&lt;/p&gt;

&lt;p&gt;When the audio is complete, send:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"close_stream"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This asks the service to flush the remaining buffered audio and return the last event with &lt;code&gt;is_last: true&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stream microphone audio from Python
&lt;/h2&gt;

&lt;p&gt;A real-time microphone client needs separate send and receive loops. Otherwise, waiting for one operation can prevent the other from progressing.&lt;/p&gt;

&lt;p&gt;Before running the snippet, create a &lt;a href="https://app.smallest.ai/dashboard/api-keys?utm_source=dev.to&amp;amp;utm_medium=vizup&amp;amp;utm_campaign=streaming-speech-to-text-in-production-handling-dropouts-reconnects-and-duplicates"&gt;Smallest.ai API key&lt;/a&gt; in the dashboard and store it in the SMALLEST_API_KEY environment variable.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;urllib.parse&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;urlencode&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pyaudio&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;websockets&lt;/span&gt;


&lt;span class="n"&gt;SAMPLE_RATE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;16_000&lt;/span&gt;
&lt;span class="n"&gt;CHUNK_SIZE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;4_096&lt;/span&gt;

&lt;span class="n"&gt;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;model&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pulse&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;language&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;en&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;encoding&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;linear16&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sample_rate&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SAMPLE_RATE&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;word_timestamps&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;true&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;websocket_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;wss://api.smallest.ai/waves/v1/stt/live?&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;urlencode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Authorization&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Bearer &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;SMALLEST_API_KEY&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;stream_microphone&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;audio&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pyaudio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;PyAudio&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;microphone&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;audio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;pyaudio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;paInt16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;channels&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;rate&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;SAMPLE_RATE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nb"&gt;input&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;frames_per_buffer&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;CHUNK_SIZE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;websockets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;websocket_url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;additional_headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;websocket&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Listening. Press Ctrl+C to stop.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;send_audio&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                    &lt;span class="n"&gt;chunk&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;microphone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                        &lt;span class="n"&gt;CHUNK_SIZE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                        &lt;span class="n"&gt;exception_on_overflow&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;websocket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.01&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CancelledError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;websocket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                        &lt;span class="p"&gt;{&lt;/span&gt;
                            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;close_stream&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                        &lt;span class="p"&gt;}&lt;/span&gt;
                    &lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="k"&gt;raise&lt;/span&gt;

        &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;receive_events&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;websocket&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;event&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="n"&gt;transcript&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;transcript&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;is_final&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
                    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;[FINAL] &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;transcript&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                        &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\r&lt;/span&gt;&lt;span class="s"&gt;[PARTIAL] &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;transcript&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                        &lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                        &lt;span class="n"&gt;flush&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="p"&gt;)&lt;/span&gt;

                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;is_last&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
                    &lt;span class="k"&gt;return&lt;/span&gt;

        &lt;span class="n"&gt;sender&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create_task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;send_audio&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;

        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;receive_events&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;finally&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;sender&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cancel&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;gather&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;sender&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;return_exceptions&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;

            &lt;span class="n"&gt;microphone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stop_stream&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="n"&gt;microphone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="n"&gt;audio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;terminate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;


&lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;stream_microphone&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Production code should also handle:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Operating-system audio permissions&lt;/li&gt;
&lt;li&gt;Device disconnection&lt;/li&gt;
&lt;li&gt;Queue limits&lt;/li&gt;
&lt;li&gt;Slow network writes&lt;/li&gt;
&lt;li&gt;Reconnection&lt;/li&gt;
&lt;li&gt;Session timeouts&lt;/li&gt;
&lt;li&gt;Duplicate segments&lt;/li&gt;
&lt;li&gt;Graceful shutdown&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Partial transcripts are not permanent text
&lt;/h2&gt;

&lt;p&gt;A streaming recognizer may emit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;I need to update
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then revise it to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;I need to update my address
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then finalize:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;I need to update my billing address.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Do not append every partial result to the transcript.&lt;/p&gt;

&lt;p&gt;Maintain two states:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Stable final segments
Current replaceable partial segment
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Only persist or act on text after the API marks the segment final, unless the product explicitly accepts the risks of acting on provisional text.&lt;/p&gt;

&lt;p&gt;The source article’s related guide on &lt;a href="https://smallest.ai/blog/streaming-speech-to-text-in-production-handling-dropouts-reconnects-and-duplicates?utm_source=dev.to&amp;amp;utm_medium=vizup&amp;amp;utm_campaign=streaming-speech-to-text-in-production-handling-dropouts-reconnects-and-duplicates"&gt;handling reconnects, dropouts and duplicated streaming transcripts&lt;/a&gt; covers the next layer of production complexity.&lt;/p&gt;

&lt;h2&gt;
  
  
  Batch and streaming return different structures
&lt;/h2&gt;

&lt;p&gt;This distinction frequently causes silent bugs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pre-recorded response
&lt;/h3&gt;

&lt;p&gt;Expect fields such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;transcription
words
utterances
language
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Real-time response
&lt;/h3&gt;

&lt;p&gt;Expect a sequence of events containing fields such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;transcript
is_final
is_last
session_id
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice the difference:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Batch:     transcription
Streaming: transcript
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Do not route both modes through one untested parser and assume their payloads are interchangeable.&lt;/p&gt;

&lt;p&gt;A useful application-level interface can normalize both into your own internal format:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;text
is_final
start_time
end_time
speaker
language
source_mode
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That keeps provider-specific response details at the integration boundary.&lt;/p&gt;

&lt;h2&gt;
  
  
  Choose the mode based on the product’s clock
&lt;/h2&gt;

&lt;p&gt;Use pre-recorded transcription when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The full file already exists.&lt;/li&gt;
&lt;li&gt;The workflow can wait for one complete response.&lt;/li&gt;
&lt;li&gt;Simpler HTTP request-response code is preferable.&lt;/li&gt;
&lt;li&gt;You are processing archived audio.&lt;/li&gt;
&lt;li&gt;You are running offline batch jobs.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Use real-time transcription when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Partial words must appear while the user speaks.&lt;/li&gt;
&lt;li&gt;A voice agent must begin reasoning before the turn is complete.&lt;/li&gt;
&lt;li&gt;Live captions must update continuously.&lt;/li&gt;
&lt;li&gt;Turn detection drives application logic.&lt;/li&gt;
&lt;li&gt;You are processing a live phone call or microphone.&lt;/li&gt;
&lt;li&gt;User experience depends on low first-transcript latency.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A WebSocket is not automatically a better architecture.&lt;/p&gt;

&lt;p&gt;It is justified only when waiting for the complete recording would prevent the product from meeting its user-facing requirement.&lt;/p&gt;

&lt;h2&gt;
  
  
  Production readiness is more than a successful transcript
&lt;/h2&gt;

&lt;p&gt;Before launch, test the complete pipeline.&lt;/p&gt;

&lt;h3&gt;
  
  
  Input
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Are audio formats validated?&lt;/li&gt;
&lt;li&gt;Is the declared encoding accurate?&lt;/li&gt;
&lt;li&gt;Are sample rates and channels appropriate?&lt;/li&gt;
&lt;li&gt;Are unsupported or corrupted files rejected clearly?&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Authentication
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Is the API key server-side?&lt;/li&gt;
&lt;li&gt;Are secrets excluded from logs?&lt;/li&gt;
&lt;li&gt;Can keys be rotated safely?&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Response handling
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Does the application distinguish &lt;code&gt;transcription&lt;/code&gt; from &lt;code&gt;transcript&lt;/code&gt;?&lt;/li&gt;
&lt;li&gt;Are optional fields handled safely?&lt;/li&gt;
&lt;li&gt;Are partial results replaced instead of appended?&lt;/li&gt;
&lt;li&gt;Are final events persisted exactly once?&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Reliability
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Are timeouts configured?&lt;/li&gt;
&lt;li&gt;Are transient failures retried?&lt;/li&gt;
&lt;li&gt;Can retries create duplicate processing?&lt;/li&gt;
&lt;li&gt;Can the WebSocket reconnect safely?&lt;/li&gt;
&lt;li&gt;Are queues bounded?&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Evaluation
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Does the test set represent real users?&lt;/li&gt;
&lt;li&gt;Are names, numbers and domain terms measured separately?&lt;/li&gt;
&lt;li&gt;Are latency percentiles tracked?&lt;/li&gt;
&lt;li&gt;Are failures visible through logs and metrics?&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Start with one representative recording
&lt;/h2&gt;

&lt;p&gt;The first useful integration does not need a microphone, animated waveform, or persistent WebSocket.&lt;/p&gt;

&lt;p&gt;Start with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;One representative audio file
        ↓
One authenticated HTTP request
        ↓
One structured transcript
        ↓
Evaluation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then inspect:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Accuracy&lt;/li&gt;
&lt;li&gt;Entity preservation&lt;/li&gt;
&lt;li&gt;Speaker labels&lt;/li&gt;
&lt;li&gt;Timestamps&lt;/li&gt;
&lt;li&gt;Language handling&lt;/li&gt;
&lt;li&gt;Response structure&lt;/li&gt;
&lt;li&gt;Failure behavior&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Move to real-time streaming only when the application genuinely needs results before the audio ends.&lt;/p&gt;

&lt;p&gt;That progression keeps the integration understandable and gives every layer of complexity a clear reason to exist.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build with Pulse STT
&lt;/h2&gt;

&lt;p&gt;To test the workflow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;a href="https://app.smallest.ai/dashboard/api-keys?utm_source=dev.to&amp;amp;utm_medium=vizup&amp;amp;utm_campaign=streaming-speech-to-text-in-production-handling-dropouts-reconnects-and-duplicates"&gt;Create a Smallest AI API key&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Open the &lt;a href="https://smallest.ai/speech-to-text?utm_source=dev.to&amp;amp;utm_medium=vizup&amp;amp;utm_campaign=streaming-speech-to-text-in-production-handling-dropouts-reconnects-and-duplicates"&gt;Pulse speech-to-text product page&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Start with one recording captured under realistic conditions.&lt;/li&gt;
&lt;li&gt;Validate the transcript and response fields.&lt;/li&gt;
&lt;li&gt;Add timestamps or diarization only when the workflow needs them.&lt;/li&gt;
&lt;li&gt;Move to WebSocket streaming when the product requires live results.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You can also explore the &lt;a href="https://smallest.ai/blog/speech-to-text-api-integration-guide-for-python-node-and-streaming?utm_source=dev.to&amp;amp;utm_medium=vizup&amp;amp;utm_campaign=streaming-speech-to-text-in-production-handling-dropouts-reconnects-and-duplicates"&gt;original Smallest AI integration guide&lt;/a&gt; and the &lt;a href="https://github.com/smallest-inc/cookbook" rel="noopener noreferrer"&gt;Smallest AI Cookbook&lt;/a&gt; for additional examples.&lt;/p&gt;

&lt;p&gt;What caused the hardest speech-to-text failure in your application: noisy audio, response handling, language coverage, or streaming state?&lt;/p&gt;

</description>
      <category>speechtotext</category>
      <category>ai</category>
      <category>api</category>
      <category>podcast</category>
    </item>
    <item>
      <title>Your TTS Isn’t Robotic. Your Delivery Pipeline Is.</title>
      <dc:creator>Smallest AI</dc:creator>
      <pubDate>Mon, 03 Aug 2026 18:08:32 +0000</pubDate>
      <link>https://dev.to/smallestai/your-tts-isnt-robotic-your-delivery-pipeline-is-515g</link>
      <guid>https://dev.to/smallestai/your-tts-isnt-robotic-your-delivery-pipeline-is-515g</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8dx9wc99zrv42dklkjk5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8dx9wc99zrv42dklkjk5.png" alt="Abstract Python data transforming into a mint-green voice waveform and luminous human profile" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Natural speech emerges when code, timing, and expression are designed as one system.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;A text-to-speech demo can be impressive for exactly thirty seconds. Then someone types a date, an abbreviation, a long sentence, or a line that needs actual feeling. The voice suddenly rushes through a pause, stresses the wrong word, or waits so long to begin that the illusion breaks.&lt;/p&gt;

&lt;p&gt;That moment reveals an uncomfortable truth: realistic speech is not mainly a model-selection problem. It is a delivery problem.&lt;/p&gt;

&lt;p&gt;Python makes it easy to send text to a speech service and save a WAV file. The hard part is deciding how the text should be normalized, where phrases should break, how quickly audio should begin, and what happens when many requests arrive together. Those choices determine whether a voice feels present or merely audible.&lt;/p&gt;

&lt;h2&gt;
  
  
  Naturalness lives between the words
&lt;/h2&gt;

&lt;p&gt;Developers often judge TTS by voice identity and sample quality. Those matter, but listeners notice rhythm first. A believable voice varies pitch, emphasis, pauses, and pace in ways that support meaning. This is prosody: the structure that turns a sequence of words into an utterance.&lt;/p&gt;

&lt;p&gt;A higher sample rate can preserve more audio detail, but it cannot repair flat phrasing. Likewise, a convincing cloned voice can still sound mechanical if every sentence has the same contour. The model creates the acoustic material; the pipeline decides how that material reaches the listener.&lt;/p&gt;

&lt;p&gt;Modern neural systems typically normalize text, predict timing and acoustic features, and use a neural vocoder to produce the waveform. If you want a deeper technical explanation of that process, Smallest.ai’s guide to &lt;a href="https://smallest.ai/blog/neural-tts-what-it-is-how-it-works-and-why-it-matters?utm_source=dev.to&amp;amp;utm_medium=Vizup&amp;amp;utm_campaign=realistic_tts_python&amp;amp;utm_content=neural_tts_guide"&gt;how neural TTS works&lt;/a&gt; provides the useful model-level context.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fagshziv0379mgj4zpu2w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fagshziv0379mgj4zpu2w.png" alt="A rigid synthetic waveform transforms into a fluid, expressive waveform, illustrating natural rhythm, pitch, pacing, and emotion." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Realism is the difference between producing audio and shaping an expressive utterance.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Start with one honest Python request
&lt;/h2&gt;

&lt;p&gt;The best first test is deliberately small: one sentence, one voice, one output file. That isolates synthesis from playback, networking, and conversational orchestration. It also gives you a repeatable artifact to listen to and compare.&lt;/p&gt;

&lt;p&gt;Before running the snippet, &lt;a href="https://docs.smallest.ai/voice-agents/platform/account/api-keys?utm_source=dev.to&amp;amp;utm_medium=Vizup&amp;amp;utm_campaign=realistic_tts_python&amp;amp;utm_content=api_key_setup"&gt;create a Smallest.ai API key in the dashboard&lt;/a&gt; and store it in the &lt;code&gt;SMALLEST_API_KEY&lt;/code&gt; environment variable.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;

&lt;span class="n"&gt;api_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SMALLEST_API_KEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://api.smallest.ai/waves/v1/tts&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Authorization&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Bearer &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Content-Type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;application/json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Accept&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;audio/wav&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;A natural voice is shaped by timing, emphasis, &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;and the space between phrases.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;voice_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;meher&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;model&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;lightning_v3.1_pro&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sample_rate&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;24000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;output_format&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;wav&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;raise_for_status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;output.wav&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;wb&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;audio_file&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;audio_file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Saved generated speech to output.wav&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is enough to validate authentication, voice selection, file output, and the basic sound of the model. The current &lt;a href="https://smallest.ai/text-to-speech?utm_source=dev.to&amp;amp;utm_medium=Vizup&amp;amp;utm_campaign=realistic_tts_python&amp;amp;utm_content=tts_product"&gt;Lightning text-to-speech product&lt;/a&gt; is the relevant Smallest.ai product surface for this workflow.&lt;/p&gt;

&lt;h2&gt;
  
  
  Do not confuse a successful file with a production system
&lt;/h2&gt;

&lt;p&gt;A synchronous request is perfectly reasonable for narration, prototypes, and offline generation. It becomes a problem when your application needs to synthesize many responses or begin playback before the entire utterance exists.&lt;/p&gt;

&lt;p&gt;For batch work, concurrency matters because blocking calls serialize the queue. For live applications, streaming matters because the listener experiences the delay before the first playable audio—not the total time required to finish the sentence.&lt;/p&gt;

&lt;p&gt;The practical design question is therefore not “Does this API support streaming?” It is “Where can my application safely begin?” Sending tiny text fragments may reduce delay but can damage phrasing. Waiting for a complete paragraph preserves context but creates an obvious pause. A stable clause or short sentence is often the useful unit: enough context for expression, but not so much that playback feels late.&lt;/p&gt;

&lt;p&gt;This is also why real-time voice systems are asynchronous by nature. Text arrives in chunks, speech is synthesized in chunks, audio is buffered, and stale output may need to be cancelled. Smallest.ai’s article on &lt;a href="https://smallest.ai/blog/why-streaming-architecture-is-non-negotiable-for-real-time-voice-agents?utm_source=dev.to&amp;amp;utm_medium=Vizup&amp;amp;utm_campaign=realistic_tts_python&amp;amp;utm_content=streaming_architecture"&gt;streaming architecture for real-time voice agents&lt;/a&gt; expands on that coordination problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  Production quality is a set of trade-offs
&lt;/h2&gt;

&lt;p&gt;Once the basic request works, realism becomes an operational discipline. Test numbers, dates, acronyms, currency, names, and mixed-language phrases. Listen for awkward joins between streamed chunks. Measure time to first audio as well as total synthesis time. Retry transient failures without generating duplicate playback. Keep API keys on the server, never in browser or mobile code.&lt;/p&gt;

&lt;p&gt;Cost should be evaluated against the real workload, not a single headline rate. Character or byte volume, concurrency, caching, output format, and repeated prompts all change the bill. Long-form narration and conversational agents may use the same TTS model but need very different buffering and delivery strategies.&lt;/p&gt;

&lt;p&gt;The broader voice stack matters too. In an assistant, speech recognition, reasoning, tool calls, and TTS share one latency budget. Optimizing synthesis in isolation may produce a faster component without producing a faster conversation. The guide to &lt;a href="https://smallest.ai/blog/designing-voice-assistants-stt-llm-tts-tools-and-latency-budget?utm_source=dev.to&amp;amp;utm_medium=Vizup&amp;amp;utm_campaign=realistic_tts_python&amp;amp;utm_content=latency_budget"&gt;designing voice assistants around a full latency budget&lt;/a&gt; is a useful next step when TTS becomes part of a larger agent.&lt;/p&gt;

&lt;h2&gt;
  
  
  The most realistic voice is the one that arrives correctly
&lt;/h2&gt;

&lt;p&gt;The Python call is the easy part. A believable speech experience comes from everything around it: clean text normalization, sensible phrase boundaries, expressive prosody, early but stable streaming, secure credentials, and measurement under real load.&lt;/p&gt;

&lt;p&gt;That is the memorable test for any TTS system: not whether one sample sounds human, but whether the voice still feels human when the text is messy, the network is busy, and the application has to respond now.&lt;/p&gt;

&lt;p&gt;If you are working through the same trade-offs, share what has been hardest in the comments, explore the &lt;a href="https://smallest.ai/blog/building-realistic-text-to-speech-in-python-libraries-apis-and-production-setup?utm_source=dev.to&amp;amp;utm_medium=Vizup&amp;amp;utm_campaign=realistic_tts_python&amp;amp;utm_content=original_article"&gt;original Python TTS guide&lt;/a&gt;, or follow more voice AI work from &lt;a href="https://smallest.ai/?utm_source=dev.to&amp;amp;utm_medium=Vizup&amp;amp;utm_campaign=realistic_tts_python&amp;amp;utm_content=homepage"&gt;Smallest.ai&lt;/a&gt;.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Voice Assistants Are Designed in the Silence</title>
      <dc:creator>Smallest AI</dc:creator>
      <pubDate>Mon, 27 Jul 2026 16:17:29 +0000</pubDate>
      <link>https://dev.to/smallestai/voice-assistants-are-designed-in-the-silence-4f5m</link>
      <guid>https://dev.to/smallestai/voice-assistants-are-designed-in-the-silence-4f5m</guid>
      <description>&lt;p&gt;A &lt;a href="https://smallest.ai/?utm_source=dev.to&amp;amp;utm_medium=vizup&amp;amp;utm_campaign=designing-voice-assistants-stt-llm-tts-tools-and-latency-budget"&gt;voice assistant&lt;/a&gt; can be technically correct and still feel broken.&lt;/p&gt;

&lt;p&gt;The failure often begins after the user finishes speaking.&lt;/p&gt;

&lt;p&gt;Nothing dramatic happens. There is simply a pause—long enough for the user to wonder whether the system heard them, but short enough for every individual service dashboard to report an acceptable result.&lt;/p&gt;

&lt;p&gt;That pause is the product.&lt;/p&gt;

&lt;p&gt;Users do not experience speech recognition, a language model, a tool call, and speech synthesis as separate services. They experience one conversational turn.&lt;/p&gt;

&lt;p&gt;When any handoff is late, uncertain, or difficult to cancel, the interaction starts to feel more like a phone tree than a conversation.&lt;/p&gt;

&lt;p&gt;The useful question is not:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Which model is fastest?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;How quickly can the system begin a trustworthy, speakable response?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This guide focuses on what developers can measure, instrument, and improve in a production voice pipeline.&lt;/p&gt;

&lt;h2&gt;
  
  
  Measure One Conversational Turn, Not Four Services
&lt;/h2&gt;

&lt;p&gt;A typical cascaded &lt;a href="https://smallest.ai/voice-agents?utm_source=dev.to&amp;amp;utm_medium=vizup&amp;amp;utm_campaign=designing-voice-assistants-stt-llm-tts-tools-and-latency-budget"&gt;voice assistant&lt;/a&gt; looks simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Microphone
  ↓
Streaming speech-to-text
  ↓
Turn decision
  ↓
Language model and optional tools
  ↓
Stable text buffer
  ↓
Streaming text-to-speech
  ↓
Interruptible playback
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This diagram becomes misleading when every arrow is treated as a clean, serial boundary.&lt;/p&gt;

&lt;p&gt;In a responsive system, useful work overlaps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Audio is transcribed while the user is speaking.&lt;/li&gt;
&lt;li&gt;The model may begin after the turn is committed but before every transcript artifact is finalized.&lt;/li&gt;
&lt;li&gt;TTS can start when a stable, speakable clause exists instead of waiting for the complete response.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Do not compress the entire pipeline into one ambiguous time to first audio metric.&lt;/p&gt;

&lt;p&gt;Record the important boundaries separately.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;What it measures&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Turn-decision delay&lt;/td&gt;
&lt;td&gt;Time between the user’s last speech frame and the system committing the turn&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;STT finalization delay&lt;/td&gt;
&lt;td&gt;Time required to produce a transcript that is safe to send downstream&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LLM time to first token&lt;/td&gt;
&lt;td&gt;Time between the model request and its first generated token&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Time to first speakable chunk&lt;/td&gt;
&lt;td&gt;Time between the model request and the first stable clause that TTS can safely render&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TTS time to first audio&lt;/td&gt;
&lt;td&gt;Time between the synthesis request and the first playable audio chunk&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;End-of-turn to playback&lt;/td&gt;
&lt;td&gt;Time between the user’s last speech frame and audio beginning on the client&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The last metric represents the user-facing experience.&lt;/p&gt;

&lt;p&gt;The other metrics explain why that experience occurred.&lt;/p&gt;

&lt;h2&gt;
  
  
  Turn Detection Is Not the Same as VAD
&lt;/h2&gt;

&lt;p&gt;Voice activity detection answers a narrow question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Does this audio frame contain speech?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Turn detection answers a harder question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Has the speaker finished their thought?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;VAD can contribute evidence, but silence alone is not enough.&lt;/p&gt;

&lt;p&gt;A production turn detector may also consider:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Finalized and interim transcript timing&lt;/li&gt;
&lt;li&gt;Transcript confidence or stability&lt;/li&gt;
&lt;li&gt;Punctuation and semantic completeness&lt;/li&gt;
&lt;li&gt;Domain-specific patterns&lt;/li&gt;
&lt;li&gt;The user’s speaking rate&lt;/li&gt;
&lt;li&gt;Recent pause behaviour&lt;/li&gt;
&lt;li&gt;The cost of interrupting versus waiting&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example, a phone number may continue after a brief pause. A support caller may hesitate before stating an account identifier. A user may pause naturally in the middle of a longer question.&lt;/p&gt;

&lt;p&gt;A fixed silence threshold creates two opposite failure modes:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Commit too quickly: the assistant cuts off a user who paused mid-sentence.&lt;/li&gt;
&lt;li&gt;Wait too long: every response feels hesitant.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Endpointing remains heuristic.&lt;/p&gt;

&lt;p&gt;Background noise can prevent reliable silence detection, while transcript-based gap detection behaves differently and may perform better for some utterances.&lt;/p&gt;

&lt;p&gt;There is no universal endpointing threshold. Tune it using real audio from the intended deployment environment.&lt;/p&gt;

&lt;h2&gt;
  
  
  STT Accuracy Should Focus on Consequential Errors
&lt;/h2&gt;

&lt;p&gt;Word error rate is useful, but it should not be treated as a universal pass-or-fail score for a voice assistant.&lt;/p&gt;

&lt;p&gt;An incorrect filler word may have no downstream effect.&lt;/p&gt;

&lt;p&gt;One incorrect digit in an account number, a misspelled surname, or a reversed negation may change the entire action.&lt;/p&gt;

&lt;p&gt;Conventional WER assigns similar importance to errors that have very different effects on meaning and task completion.&lt;/p&gt;

&lt;p&gt;For a voice agent, evaluate at least three layers.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Transcription quality
&lt;/h3&gt;

&lt;p&gt;Measure WER or another suitable ASR metric.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Entity accuracy
&lt;/h3&gt;

&lt;p&gt;Evaluate high-impact entities such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Names&lt;/li&gt;
&lt;li&gt;Dates&lt;/li&gt;
&lt;li&gt;Amounts&lt;/li&gt;
&lt;li&gt;Phone numbers&lt;/li&gt;
&lt;li&gt;Account identifiers&lt;/li&gt;
&lt;li&gt;Addresses&lt;/li&gt;
&lt;li&gt;Domain-specific terminology&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. Task success
&lt;/h3&gt;

&lt;p&gt;Determine whether the downstream system:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Understood the user’s intent&lt;/li&gt;
&lt;li&gt;Extracted the correct information&lt;/li&gt;
&lt;li&gt;Performed the correct action&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Test using the audio users will actually produce:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Noisy rooms&lt;/li&gt;
&lt;li&gt;Phone codecs&lt;/li&gt;
&lt;li&gt;Weak connections&lt;/li&gt;
&lt;li&gt;Accents&lt;/li&gt;
&lt;li&gt;Hesitations&lt;/li&gt;
&lt;li&gt;Overlapping speech&lt;/li&gt;
&lt;li&gt;Low-quality microphones&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A clean studio recording is not a substitute for a deployment test set.&lt;/p&gt;

&lt;p&gt;Streaming ASR can reduce emission delay, but it also introduces unstable partial hypotheses. Latency and recognition quality must therefore be evaluated together.&lt;/p&gt;

&lt;h2&gt;
  
  
  The First Token Is Not Yet a Spoken Answer
&lt;/h2&gt;

&lt;p&gt;LLM time to first token is important, but it does not represent the end of the model stage.&lt;/p&gt;

&lt;p&gt;Suppose the model begins with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Sure — let me...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first token arrived quickly, but the assistant still has nothing useful to say.&lt;/p&gt;

&lt;p&gt;TTS may also need to wait for a stable clause so it does not synthesize an opening that later becomes awkward, incomplete, or incorrect.&lt;/p&gt;

&lt;p&gt;A stronger voice response front-loads the answer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Your appointment is confirmed for Thursday at 3 PM.

I can also send a reminder.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first sentence is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Complete&lt;/li&gt;
&lt;li&gt;Useful&lt;/li&gt;
&lt;li&gt;Independently speakable&lt;/li&gt;
&lt;li&gt;Safe to send to TTS&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This creates another useful metric:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Time to first speakable chunk&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It includes the model’s TTFT plus the time required to accumulate a safe synthesis boundary.&lt;/p&gt;

&lt;p&gt;A simple clause buffer may look like this:&lt;/p&gt;

&lt;p&gt;Before running the snippet, create a &lt;a href="https://app.smallest.ai/?utm_source=dev.to&amp;amp;utm_medium=vizup&amp;amp;utm_campaign=designing-voice-assistants-stt-llm-tts-tools-and-latency-budget"&gt;Smallest.ai API&lt;/a&gt; key in the dashboard and store it in the SMALLEST_API_KEY environment variable.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;boundary&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;[&lt;/span&gt;&lt;span class="sr"&gt;.!?&lt;/span&gt;&lt;span class="se"&gt;]\s&lt;/span&gt;&lt;span class="sr"&gt;$|&lt;/span&gt;&lt;span class="se"&gt;[&lt;/span&gt;&lt;span class="sr"&gt;,;:&lt;/span&gt;&lt;span class="se"&gt;]\s&lt;/span&gt;&lt;span class="sr"&gt;$/&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;pending&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;onModelToken&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;pending&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;enoughText&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;pending&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;24&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;hasBoundary&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;boundary&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pending&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;enoughText&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;hasBoundary&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;tts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;enqueue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pending&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;pending&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;onModelComplete&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pending&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;tts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;enqueue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pending&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This example is intentionally simple.&lt;/p&gt;

&lt;p&gt;A production buffer should also consider:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Abbreviations&lt;/li&gt;
&lt;li&gt;Numbers&lt;/li&gt;
&lt;li&gt;Markdown or other markup&lt;/li&gt;
&lt;li&gt;Pronunciation hints&lt;/li&gt;
&lt;li&gt;Language-specific punctuation&lt;/li&gt;
&lt;li&gt;Maximum waiting time&lt;/li&gt;
&lt;li&gt;Whether enqueued speech can still be cancelled&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Tool Latency Only Hurts When It Blocks the Critical Path
&lt;/h2&gt;

&lt;p&gt;Tool calls are often described as additive latency.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A 300 ms lookup adds 300 ms to the response.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is true only when the lookup sits directly on the serial critical path.&lt;/p&gt;

&lt;p&gt;Some tool latency can be hidden or reduced by:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Prefetching likely context after a session starts&lt;/li&gt;
&lt;li&gt;Caching results with a clear freshness policy&lt;/li&gt;
&lt;li&gt;Running independent tools in parallel&lt;/li&gt;
&lt;li&gt;Starting a truthful acknowledgement while a slow operation continues&lt;/li&gt;
&lt;li&gt;Using deterministic routing when a model decision is unnecessary&lt;/li&gt;
&lt;li&gt;Cancelling stale work when the user changes direction&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A useful acknowledgement might be:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;I’ll check the live inventory now.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However, filler speech should not be used merely to disguise arbitrary latency.&lt;/p&gt;

&lt;p&gt;An acknowledgement is useful only when it communicates real progress and does not make an unsupported promise.&lt;/p&gt;

&lt;p&gt;Trace every tool call with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Queue time&lt;/li&gt;
&lt;li&gt;Network time&lt;/li&gt;
&lt;li&gt;Server processing time&lt;/li&gt;
&lt;li&gt;Result size&lt;/li&gt;
&lt;li&gt;Cache status&lt;/li&gt;
&lt;li&gt;Retry count&lt;/li&gt;
&lt;li&gt;Whether it blocked the first speakable chunk&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The final field is often more useful than the tool’s total duration.&lt;/p&gt;

&lt;h2&gt;
  
  
  Streaming Is Controlled Overlap
&lt;/h2&gt;

&lt;p&gt;“Stream every stage” sounds attractive, but it is too absolute.&lt;/p&gt;

&lt;p&gt;Partial output is valuable only when it is useful and safely reversible.&lt;/p&gt;

&lt;p&gt;Potential problems include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Partial transcripts changing&lt;/li&gt;
&lt;li&gt;Tools returning only atomic results&lt;/li&gt;
&lt;li&gt;TTS speaking text the model would have revised&lt;/li&gt;
&lt;li&gt;Speculative work increasing cost&lt;/li&gt;
&lt;li&gt;Cancellation becoming harder&lt;/li&gt;
&lt;li&gt;Stale results entering the conversation state&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A better rule is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Stream when partial output is useful, and overlap work only when errors can be contained or reversed.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Incremental TTS can begin synthesis from partial text while later segments are still being generated.&lt;/p&gt;

&lt;p&gt;However, no research result or benchmark justifies assuming the same millisecond improvement across every:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Model&lt;/li&gt;
&lt;li&gt;Sentence&lt;/li&gt;
&lt;li&gt;Device&lt;/li&gt;
&lt;li&gt;Language&lt;/li&gt;
&lt;li&gt;Network&lt;/li&gt;
&lt;li&gt;Deployment architecture&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Low-latency engineering also extends beyond model inference.&lt;/p&gt;

&lt;p&gt;Media transport, jitter, packet loss, WebRTC behaviour, session ownership, routing, and infrastructure placement all influence the pause.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build an Explicit Latency Budget
&lt;/h2&gt;

&lt;p&gt;A &lt;a href="https://smallest.ai/blog/designing-voice-assistants-stt-llm-tts-tools-and-latency-budget?utm_source=dev.to&amp;amp;utm_medium=vizup&amp;amp;utm_campaign=designing-voice-assistants-stt-llm-tts-tools-and-latency-budget"&gt;latency budget&lt;/a&gt; should be a design constraint, not a chart created after launch.&lt;/p&gt;

&lt;p&gt;Start with an end-of-turn-to-playback target, then assign provisional limits to each stage on the critical path.&lt;/p&gt;

&lt;p&gt;Here is an illustrative and deliberately aggressive 800 ms budget.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This is a planning example, not an industry benchmark.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Stage&lt;/th&gt;
&lt;th&gt;Example budget&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Turn decision&lt;/td&gt;
&lt;td&gt;150 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Transcript stabilization&lt;/td&gt;
&lt;td&gt;100 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;First speakable model chunk&lt;/td&gt;
&lt;td&gt;300 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;First playable TTS audio&lt;/td&gt;
&lt;td&gt;150 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Transport and client buffering&lt;/td&gt;
&lt;td&gt;100 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Total&lt;/td&gt;
&lt;td&gt;800 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The allocation should change based on the product.&lt;/p&gt;

&lt;p&gt;A hands-free command may prioritize speed.&lt;/p&gt;

&lt;p&gt;A medical intake flow may tolerate a longer delay to reduce the chance of interrupting the user.&lt;/p&gt;

&lt;p&gt;A tool-heavy transaction may need a short acknowledgement before the final answer.&lt;/p&gt;

&lt;h3&gt;
  
  
  Measure distributions, not averages
&lt;/h3&gt;

&lt;p&gt;At minimum, report:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;p50&lt;/li&gt;
&lt;li&gt;p95&lt;/li&gt;
&lt;li&gt;p99&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Break these metrics down by:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Interaction type&lt;/li&gt;
&lt;li&gt;Geography&lt;/li&gt;
&lt;li&gt;Network type&lt;/li&gt;
&lt;li&gt;Language&lt;/li&gt;
&lt;li&gt;Device&lt;/li&gt;
&lt;li&gt;Telephony provider&lt;/li&gt;
&lt;li&gt;Tool-free versus tool-dependent turns&lt;/li&gt;
&lt;li&gt;Successful turns&lt;/li&gt;
&lt;li&gt;Interrupted turns&lt;/li&gt;
&lt;li&gt;Cancelled turns&lt;/li&gt;
&lt;li&gt;Retried turns&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;An acceptable p50 can hide a painful p95.&lt;/p&gt;

&lt;h2&gt;
  
  
  Instrument the Complete Turn
&lt;/h2&gt;

&lt;p&gt;The following TypeScript example records useful event boundaries without coupling the implementation to a specific STT, LLM, or TTS provider.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;marks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;mark&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;at&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;performance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;marks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;at&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;start&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;end&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;startTime&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;marks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;start&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;endTime&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;marks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;end&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;startTime&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;endTime&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;endTime&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;startTime&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;voiceTurnMetrics&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;turnDecisionMs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;speech_last_frame&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;turn_committed&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;

    &lt;span class="na"&gt;transcriptReadyMs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;turn_committed&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;stt_final&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;

    &lt;span class="na"&gt;llmTtftMs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;llm_started&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;llm_first_token&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;

    &lt;span class="na"&gt;firstSpeakableMs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;llm_started&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;first_speakable_chunk&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;

    &lt;span class="na"&gt;ttsFirstAudioMs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;tts_started&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;tts_first_audio&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;

    &lt;span class="na"&gt;clientBufferMs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;tts_first_audio&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;playback_started&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;

    &lt;span class="na"&gt;endToEndMs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;speech_last_frame&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;playback_started&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Call &lt;code&gt;mark()&lt;/code&gt; from the actual callbacks in your pipeline:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;mark&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;speech_last_frame&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;vad&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lastSpeechTimestamp&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="nf"&gt;mark&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;turn_committed&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nf"&gt;mark&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;stt_final&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nf"&gt;mark&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;llm_started&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nf"&gt;mark&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;llm_first_token&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nf"&gt;mark&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;first_speakable_chunk&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nf"&gt;mark&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;tts_started&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nf"&gt;mark&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;tts_first_audio&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nf"&gt;mark&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;playback_started&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use one trace ID across:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Browser or phone gateway&lt;/li&gt;
&lt;li&gt;STT service&lt;/li&gt;
&lt;li&gt;Orchestration layer&lt;/li&gt;
&lt;li&gt;External tools&lt;/li&gt;
&lt;li&gt;Language model&lt;/li&gt;
&lt;li&gt;TTS service&lt;/li&gt;
&lt;li&gt;Playback client&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Without cross-service correlation, teams often optimize the service with the most visible dashboard instead of the stage responsible for the user’s wait.&lt;/p&gt;

&lt;p&gt;When comparing a managed voice stack with a custom pipeline, apply the same:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Event boundaries&lt;/li&gt;
&lt;li&gt;p50 measurements&lt;/li&gt;
&lt;li&gt;p95 measurements&lt;/li&gt;
&lt;li&gt;p99 measurements&lt;/li&gt;
&lt;li&gt;Accuracy tests&lt;/li&gt;
&lt;li&gt;Cancellation tests&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;An integrated stack should not automatically be assumed to have low end-to-end latency.&lt;/p&gt;

&lt;h2&gt;
  
  
  Barge-In Must Cancel the Old Turn
&lt;/h2&gt;

&lt;p&gt;In a full-duplex assistant, inbound audio capture and speech detection should normally remain active while the assistant is speaking.&lt;/p&gt;

&lt;p&gt;When new user speech is confirmed, the system must stop treating the old response as current.&lt;/p&gt;

&lt;p&gt;The control path may resemble this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;onUserSpeechStarted&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;generation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;abort&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;tts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cancel&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="nx"&gt;playback&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stopAndFlush&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="nx"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cancelNonReusableWork&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="nx"&gt;conversation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;truncateAssistantMessage&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;toPlayedAudioTimestamp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;playback&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lastPlayedTimestamp&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The exact APIs will differ, but the responsibilities remain similar.&lt;/p&gt;

&lt;p&gt;The system should:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Cancel model generation.&lt;/li&gt;
&lt;li&gt;Cancel speech synthesis.&lt;/li&gt;
&lt;li&gt;Stop and flush queued playback.&lt;/li&gt;
&lt;li&gt;Discard tool results that are no longer relevant.&lt;/li&gt;
&lt;li&gt;Preserve only the response portion the user actually heard.&lt;/li&gt;
&lt;li&gt;Continue processing incoming speech without clipping its beginning.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Barge-in cannot be added cleanly as a final UI feature.&lt;/p&gt;

&lt;p&gt;It affects:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Audio capture&lt;/li&gt;
&lt;li&gt;Session state&lt;/li&gt;
&lt;li&gt;Model context&lt;/li&gt;
&lt;li&gt;Tool cancellation&lt;/li&gt;
&lt;li&gt;Playback architecture&lt;/li&gt;
&lt;li&gt;Conversation history&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Production Starts Where the Demo Ends
&lt;/h2&gt;

&lt;p&gt;A convincing demo proves that the happy path can answer.&lt;/p&gt;

&lt;p&gt;Production requires testing for real human behaviour and imperfect infrastructure.&lt;/p&gt;

&lt;p&gt;Before launch, test:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Quiet audio&lt;/li&gt;
&lt;li&gt;Noisy audio&lt;/li&gt;
&lt;li&gt;Reverberant rooms&lt;/li&gt;
&lt;li&gt;Low-bitrate audio&lt;/li&gt;
&lt;li&gt;Short commands&lt;/li&gt;
&lt;li&gt;Long and hesitant utterances&lt;/li&gt;
&lt;li&gt;Mid-sentence pauses&lt;/li&gt;
&lt;li&gt;Self-corrections&lt;/li&gt;
&lt;li&gt;Names and phone numbers&lt;/li&gt;
&lt;li&gt;Dates and monetary amounts&lt;/li&gt;
&lt;li&gt;Domain-specific terminology&lt;/li&gt;
&lt;li&gt;Tool-free calls&lt;/li&gt;
&lt;li&gt;Cached tool calls&lt;/li&gt;
&lt;li&gt;Slow tool calls&lt;/li&gt;
&lt;li&gt;Failed and retried tool calls&lt;/li&gt;
&lt;li&gt;Barge-in during early playback&lt;/li&gt;
&lt;li&gt;Barge-in during late playback&lt;/li&gt;
&lt;li&gt;Packet loss&lt;/li&gt;
&lt;li&gt;Jitter&lt;/li&gt;
&lt;li&gt;Reconnects&lt;/li&gt;
&lt;li&gt;Duplicated events&lt;/li&gt;
&lt;li&gt;Multiple languages&lt;/li&gt;
&lt;li&gt;Code-switching&lt;/li&gt;
&lt;li&gt;Long conversations&lt;/li&gt;
&lt;li&gt;Context compaction&lt;/li&gt;
&lt;li&gt;Realistic concurrency&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Also test failure semantics.&lt;/p&gt;

&lt;p&gt;Ask questions such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What happens if a tool succeeds after the user interrupts?&lt;/li&gt;
&lt;li&gt;What happens if TTS emits audio after cancellation?&lt;/li&gt;
&lt;li&gt;What happens if two final transcripts arrive?&lt;/li&gt;
&lt;li&gt;What happens if the client reconnects while audio remains queued?&lt;/li&gt;
&lt;li&gt;What happens if a stale tool result returns after the conversation has changed?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A low-latency system that performs stale actions is not a good system.&lt;/p&gt;

&lt;p&gt;When reviewing an implementation, study how it represents:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Processors&lt;/li&gt;
&lt;li&gt;Transports&lt;/li&gt;
&lt;li&gt;Interruptions&lt;/li&gt;
&lt;li&gt;Events&lt;/li&gt;
&lt;li&gt;Cancellation&lt;/li&gt;
&lt;li&gt;Session state&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Do not copy default settings blindly.&lt;/p&gt;

&lt;p&gt;Validate each design choice against your own traces, traffic, users, and failure modes.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Pause Is the Architecture
&lt;/h2&gt;

&lt;p&gt;The fastest model will not rescue a pipeline with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Slow turn detection&lt;/li&gt;
&lt;li&gt;Serial tool calls&lt;/li&gt;
&lt;li&gt;Unstable partial transcripts&lt;/li&gt;
&lt;li&gt;Excessive client buffering&lt;/li&gt;
&lt;li&gt;Weak cancellation&lt;/li&gt;
&lt;li&gt;Poor session state management&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Measure the complete turn from the user’s last speech frame to audible playback.&lt;/p&gt;

&lt;p&gt;Separate that duration into named stages.&lt;/p&gt;

&lt;p&gt;Optimize the stage dominating p95, then test the change against:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Recognition accuracy&lt;/li&gt;
&lt;li&gt;Entity accuracy&lt;/li&gt;
&lt;li&gt;Task success&lt;/li&gt;
&lt;li&gt;Cancellation behaviour&lt;/li&gt;
&lt;li&gt;User interruption patterns&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A natural voice assistant is not simply a collection of fast components.&lt;/p&gt;

&lt;p&gt;It is one coordinated participant whose timing, state, and failure modes have been designed as a whole.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build and Measure a Voice Agent
&lt;/h2&gt;

&lt;p&gt;Ready to test this architecture in a working voice stack?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://app.smallest.ai/?utm_source=dev.to&amp;amp;utm_medium=vizup&amp;amp;utm_campaign=designing-voice-assistants-stt-llm-tts-tools-and-latency-budget"&gt;Build a voice agent with the Smallest.ai API&lt;/a&gt; and instrument the event boundaries described above.&lt;/p&gt;

&lt;p&gt;Compare your:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;p50 end-to-end latency&lt;/li&gt;
&lt;li&gt;p95 end-to-end latency&lt;/li&gt;
&lt;li&gt;Time to first speakable chunk&lt;/li&gt;
&lt;li&gt;Barge-in cancellation time&lt;/li&gt;
&lt;li&gt;Tool-dependent latency&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;against your current production pipeline.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>architecture</category>
      <category>performance</category>
    </item>
    <item>
      <title>Your First Voice AI App Shouldn’t Start With a WebSocket</title>
      <dc:creator>Smallest AI</dc:creator>
      <pubDate>Mon, 27 Jul 2026 16:16:46 +0000</pubDate>
      <link>https://dev.to/smallestai/your-first-voice-ai-app-shouldnt-start-with-a-websocket-3ie0</link>
      <guid>https://dev.to/smallestai/your-first-voice-ai-app-shouldnt-start-with-a-websocket-3ie0</guid>
      <description>&lt;p&gt;The first successful voice feature usually looks unimpressive:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;One audio file goes in, and one useful transcript comes out.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is not a toy result.&lt;/p&gt;

&lt;p&gt;It is the shortest route to the questions that determine whether a voice product will actually work:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Does the transcript preserve names and numbers?&lt;/li&gt;
&lt;li&gt;Are speaker turns separated correctly?&lt;/li&gt;
&lt;li&gt;Are timestamps useful downstream?&lt;/li&gt;
&lt;li&gt;Can the system handle the audio your users will really produce?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It is tempting to begin with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Live microphones&lt;/li&gt;
&lt;li&gt;Persistent connections&lt;/li&gt;
&lt;li&gt;Partial transcripts&lt;/li&gt;
&lt;li&gt;Browser permissions&lt;/li&gt;
&lt;li&gt;Animated interfaces&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Those pieces feel like voice AI.&lt;/p&gt;

&lt;p&gt;They also make failures harder to isolate.&lt;/p&gt;

&lt;p&gt;A safer rule is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Prove the transcript’s value first. Then earn the complexity of streaming.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Start With the Product’s Clock
&lt;/h2&gt;

&lt;p&gt;The important architectural choice is not Python versus another language.&lt;/p&gt;

&lt;p&gt;It is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Batch or real time?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If you are processing a:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Voicemail&lt;/li&gt;
&lt;li&gt;Podcast&lt;/li&gt;
&lt;li&gt;Meeting recording&lt;/li&gt;
&lt;li&gt;Customer call archive&lt;/li&gt;
&lt;li&gt;Uploaded audio file&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;then the entire recording already exists.&lt;/p&gt;

&lt;p&gt;An HTTP request matches the problem:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Upload audio
   ↓
Wait for processing
   ↓
Receive transcript
   ↓
Store or analyze result
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There is no benefit in pretending a completed file is a live conversation.&lt;/p&gt;

&lt;p&gt;A voice assistant, live-captioning tool, or real-time call workflow runs on a different clock.&lt;/p&gt;

&lt;p&gt;It needs to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Receive small audio frames&lt;/li&gt;
&lt;li&gt;Return partial transcripts while the user is speaking&lt;/li&gt;
&lt;li&gt;Decide when an utterance is final&lt;/li&gt;
&lt;li&gt;Recover when the connection drops&lt;/li&gt;
&lt;li&gt;Handle interruptions and timing-sensitive actions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is where a WebSocket belongs.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://smallest.ai/?utm_source=dev.to&amp;amp;utm_medium=vizup&amp;amp;utm_campaign=streaming-speech-to-text-in-production-handling-dropouts-reconnects-and-duplicates"&gt;Smallest AI&lt;/a&gt; publishes a roughly 64 ms time-to-first-transcript figure for &lt;a href="https://smallest.ai/speech-to-text?utm_source=dev.to&amp;amp;utm_medium=vizup&amp;amp;utm_campaign=streaming-speech-to-text-in-production-handling-dropouts-reconnects-and-duplicates"&gt;Pulse streaming&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Treat that as a provider specification, not a guarantee for your application.&lt;/p&gt;

&lt;p&gt;Your actual latency will depend on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Network path&lt;/li&gt;
&lt;li&gt;Audio framing&lt;/li&gt;
&lt;li&gt;Region&lt;/li&gt;
&lt;li&gt;Application load&lt;/li&gt;
&lt;li&gt;Buffering&lt;/li&gt;
&lt;li&gt;Transcript-stability requirements&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The provider specification is useful.&lt;/p&gt;

&lt;p&gt;Your own p50 and p95 traces should make the architecture decision.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Smallest Useful Python Proof
&lt;/h2&gt;

&lt;p&gt;A batch prototype should stay intentionally boring. Put the API key in an environment variable, read a representative audio file, request only the metadata you need, and inspect the returned JSON.&lt;/p&gt;

&lt;p&gt;Before running the snippet, create a &lt;a href="https://app.smallest.ai/?utm_source=dev.to&amp;amp;utm_medium=vizup&amp;amp;utm_campaign=streaming-speech-to-text-in-production-handling-dropouts-reconnects-and-duplicates"&gt;Smallest.ai API key&lt;/a&gt; in the dashboard and store it in the SMALLEST_API_KEY environment variable.&lt;/p&gt;

&lt;p&gt;It should:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Read a representative audio file.&lt;/li&gt;
&lt;li&gt;Send it to the speech-to-text API.&lt;/li&gt;
&lt;li&gt;Request only the metadata you need.&lt;/li&gt;
&lt;li&gt;Inspect the returned JSON.&lt;/li&gt;
&lt;li&gt;Record failures.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here is a minimal Python example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;

&lt;span class="n"&gt;endpoint&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://api.smallest.ai/waves/v1/stt/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="n"&gt;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;model&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pulse&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;language&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;en&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;word_timestamps&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;true&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;diarize&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;true&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Authorization&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Bearer &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;SMALLEST_API_KEY&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Content-Type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;application/octet-stream&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;meeting.wav&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rb&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;audio&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;audio&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;120&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;raise_for_status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;transcription&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;words&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[])[:&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This uses the unified endpoint documented in the pre-recorded STT API reference.&lt;/p&gt;

&lt;p&gt;In production, also handle:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Timeouts&lt;/li&gt;
&lt;li&gt;Retries&lt;/li&gt;
&lt;li&gt;Rate limits&lt;/li&gt;
&lt;li&gt;Invalid audio&lt;/li&gt;
&lt;li&gt;Empty transcripts&lt;/li&gt;
&lt;li&gt;Response-schema changes&lt;/li&gt;
&lt;li&gt;Request identifiers&lt;/li&gt;
&lt;li&gt;Unexpected status codes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Do not log the authorization header.&lt;/p&gt;

&lt;p&gt;Do not log raw audio by default.&lt;/p&gt;

&lt;p&gt;That short loop already gives you a useful evaluation surface.&lt;/p&gt;

&lt;p&gt;Test:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Multiple accents&lt;/li&gt;
&lt;li&gt;Different speaking styles&lt;/li&gt;
&lt;li&gt;Clean recordings&lt;/li&gt;
&lt;li&gt;Noisy recordings&lt;/li&gt;
&lt;li&gt;Telephony audio&lt;/li&gt;
&lt;li&gt;Fast speech&lt;/li&gt;
&lt;li&gt;Hesitant speech&lt;/li&gt;
&lt;li&gt;Multiple speakers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Do not evaluate only the sentence printed to the terminal.&lt;/p&gt;

&lt;p&gt;Inspect the JSON structure your next component must consume.&lt;/p&gt;

&lt;h2&gt;
  
  
  Measure the Transcript You Need
&lt;/h2&gt;

&lt;p&gt;A single successful file proves connectivity.&lt;/p&gt;

&lt;p&gt;A small evaluation set begins to prove usefulness.&lt;/p&gt;

&lt;p&gt;Use audio captured from the:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Microphones&lt;/li&gt;
&lt;li&gt;Codecs&lt;/li&gt;
&lt;li&gt;Channels&lt;/li&gt;
&lt;li&gt;Devices&lt;/li&gt;
&lt;li&gt;Rooms&lt;/li&gt;
&lt;li&gt;Networks&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;your users will actually have.&lt;/p&gt;

&lt;p&gt;Track failures involving:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Names&lt;/li&gt;
&lt;li&gt;Phone numbers&lt;/li&gt;
&lt;li&gt;Dates&lt;/li&gt;
&lt;li&gt;Amounts&lt;/li&gt;
&lt;li&gt;Abbreviations&lt;/li&gt;
&lt;li&gt;Product names&lt;/li&gt;
&lt;li&gt;Domain terminology&lt;/li&gt;
&lt;li&gt;Code-switching&lt;/li&gt;
&lt;li&gt;Speaker changes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Do not measure only average text quality.&lt;/p&gt;

&lt;p&gt;A transcript can look mostly correct while still failing on the information your application needs most.&lt;/p&gt;

&lt;h3&gt;
  
  
  Useful evaluation areas
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Area&lt;/th&gt;
&lt;th&gt;What to inspect&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Transcription quality&lt;/td&gt;
&lt;td&gt;Missing, substituted, or hallucinated words&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Entity accuracy&lt;/td&gt;
&lt;td&gt;Names, numbers, dates, addresses, identifiers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Speaker separation&lt;/td&gt;
&lt;td&gt;Whether participants are assigned consistently&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Timestamp quality&lt;/td&gt;
&lt;td&gt;Drift, missing words, alignment usefulness&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Latency&lt;/td&gt;
&lt;td&gt;p50, p95, and timeout rate&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Failure handling&lt;/td&gt;
&lt;td&gt;Rejected files, empty output, malformed responses&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Downstream usefulness&lt;/td&gt;
&lt;td&gt;Whether the next system can use the transcript reliably&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This stage also tells you whether streaming is necessary.&lt;/p&gt;

&lt;p&gt;If users upload recordings and return later, a persistent connection may add operational cost without improving the product.&lt;/p&gt;

&lt;p&gt;If the transcript drives a live response, batch processing will eventually reveal its limit.&lt;/p&gt;

&lt;h2&gt;
  
  
  Streaming Is Not a Faster POST Request
&lt;/h2&gt;

&lt;p&gt;Moving from batch to streaming changes the application.&lt;/p&gt;

&lt;p&gt;It does not merely change the transport.&lt;/p&gt;

&lt;p&gt;A &lt;a href="https://smallest.ai/blog/streaming-speech-to-text-in-production-handling-dropouts-reconnects-and-duplicates?utm_source=dev.to&amp;amp;utm_medium=vizup&amp;amp;utm_campaign=streaming-speech-to-text-in-production-handling-dropouts-reconnects-and-duplicates"&gt;streaming client&lt;/a&gt; must:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Send audio frames continuously&lt;/li&gt;
&lt;li&gt;Receive transcript events concurrently&lt;/li&gt;
&lt;li&gt;Handle partial and final results&lt;/li&gt;
&lt;li&gt;Manage connection state&lt;/li&gt;
&lt;li&gt;Apply backpressure&lt;/li&gt;
&lt;li&gt;Recover from failures&lt;/li&gt;
&lt;li&gt;Avoid duplicated segments&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Batch waits for the complete recording.&lt;/p&gt;

&lt;p&gt;Streaming turns a moving signal into usable partial results.&lt;/p&gt;

&lt;p&gt;Here is a simplified Python example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;urllib.parse&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;urlencode&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;websockets&lt;/span&gt;

&lt;span class="n"&gt;CHUNK_SIZE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;4096&lt;/span&gt;

&lt;span class="n"&gt;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;language&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;en&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;encoding&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;linear16&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sample_rate&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;16000&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;word_timestamps&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;true&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;wss://api.smallest.ai/waves/v1/stt/live?model=pulse&amp;amp;&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;urlencode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Authorization&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Bearer &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;SMALLEST_API_KEY&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;send_audio&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ws&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rb&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;audio&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;chunk&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;audio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CHUNK_SIZE&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;ws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.05&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;ws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;close_stream&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;receive_transcripts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ws&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;ws&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;event&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;label&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;final&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;is_final&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;partial&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="n"&gt;transcript&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;transcript&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;transcript&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;is_last&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;


&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;transcribe_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;websockets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;additional_headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;ws&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;sender&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create_task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;send_audio&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ws&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="n"&gt;receiver&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create_task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;receive_transcripts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ws&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;gather&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sender&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;receiver&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;transcribe_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;audio.pcm&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The current real-time WebSocket documentation recommends:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;4096&lt;/code&gt;-byte chunks&lt;/li&gt;
&lt;li&gt;A &lt;code&gt;close_stream&lt;/code&gt; control message when the stream is complete&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The example expects:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Headerless audio
16-bit linear PCM
16 kHz
Mono
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A WAV file normally includes a container header.&lt;/p&gt;

&lt;p&gt;Do not rename a &lt;code&gt;.wav&lt;/code&gt; file to &lt;code&gt;.pcm&lt;/code&gt; and assume the bytes are equivalent.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Concurrency Model Matters More Than the Socket
&lt;/h2&gt;

&lt;p&gt;A production microphone flow should separate responsibilities.&lt;/p&gt;

&lt;h3&gt;
  
  
  Audio producer
&lt;/h3&gt;

&lt;p&gt;Reads fixed-size frames and places them in a bounded queue.&lt;/p&gt;

&lt;h3&gt;
  
  
  Network sender
&lt;/h3&gt;

&lt;p&gt;Writes frames to the provider, handles backpressure, and stops cleanly.&lt;/p&gt;

&lt;h3&gt;
  
  
  Transcript receiver
&lt;/h3&gt;

&lt;p&gt;Receives partial and final transcript events.&lt;/p&gt;

&lt;h3&gt;
  
  
  Application state
&lt;/h3&gt;

&lt;p&gt;Stores stable transcript segments and exposes connection status.&lt;/p&gt;

&lt;h3&gt;
  
  
  Recovery path
&lt;/h3&gt;

&lt;p&gt;Reconnects deliberately and prevents duplicate or out-of-order segments.&lt;/p&gt;

&lt;p&gt;A useful architecture might look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Microphone capture
      ↓
Bounded audio queue
      ↓
Network sender ───────────────┐
                              │
Provider WebSocket            │
                              │
Transcript receiver ◀─────────┘
      ↓
Partial transcript state
      ↓
Final transcript state
      ↓
UI or downstream workflow
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If one task blocks the others, buffers grow.&lt;/p&gt;

&lt;p&gt;The interface then feels stale even when speech recognition itself is fast.&lt;/p&gt;

&lt;h2&gt;
  
  
  Partial Transcripts Are a Preview
&lt;/h2&gt;

&lt;p&gt;Partial transcripts are not an append-only log.&lt;/p&gt;

&lt;p&gt;Suppose the server emits:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;I need to update
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;I need to update my address
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;I need to update my billing address
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Appending every partial result would produce duplicated text.&lt;/p&gt;

&lt;p&gt;Instead:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Replace the current partial segment when a new hypothesis arrives.&lt;/li&gt;
&lt;li&gt;Display it as provisional.&lt;/li&gt;
&lt;li&gt;Persist it only when the server marks it final.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A simple state model could look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;stable_segments&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="n"&gt;current_partial&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;handle_event&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;global&lt;/span&gt; &lt;span class="n"&gt;current_partial&lt;/span&gt;

    &lt;span class="n"&gt;transcript&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;transcript&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;is_final&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;stable_segments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;transcript&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;current_partial&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;current_partial&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;transcript&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This distinction matters beyond the interface.&lt;/p&gt;

&lt;p&gt;Downstream systems should normally process stable text, not every revisable hypothesis.&lt;/p&gt;

&lt;p&gt;Otherwise, the application may act on text the recognizer later corrects.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep the Browser Simple
&lt;/h2&gt;

&lt;p&gt;A browser can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Capture microphone audio&lt;/li&gt;
&lt;li&gt;Display transcript updates&lt;/li&gt;
&lt;li&gt;Show connection state&lt;/li&gt;
&lt;li&gt;Let the user stop or restart a session&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It should not receive a long-lived service API key.&lt;/p&gt;

&lt;p&gt;Put the authenticated provider connection behind your server.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Browser
   ↓
Your authenticated application server
   ↓
Speech-to-text provider
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The browser connects to your application.&lt;/p&gt;

&lt;p&gt;Your server:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Authorizes the user&lt;/li&gt;
&lt;li&gt;Opens the provider connection&lt;/li&gt;
&lt;li&gt;Applies rate limits&lt;/li&gt;
&lt;li&gt;Enforces session duration&lt;/li&gt;
&lt;li&gt;Validates input format&lt;/li&gt;
&lt;li&gt;Controls concurrency&lt;/li&gt;
&lt;li&gt;Applies logging policy&lt;/li&gt;
&lt;li&gt;Forwards only the events the interface needs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This creates a clean progression:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Uploaded audio
→ normal server route
→ batch transcription

Microphone audio
→ streaming proxy
→ live transcription
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Batch and live transcription remain two deliberate modes instead of one over-engineered pipeline.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Transcript Is Rarely the Final Product
&lt;/h2&gt;

&lt;p&gt;Raw text is enough for a connectivity demo.&lt;/p&gt;

&lt;p&gt;Production workflows usually need structure.&lt;/p&gt;

&lt;h3&gt;
  
  
  Word timestamps
&lt;/h3&gt;

&lt;p&gt;Useful for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Captions&lt;/li&gt;
&lt;li&gt;Transcript review&lt;/li&gt;
&lt;li&gt;Search&lt;/li&gt;
&lt;li&gt;Audio navigation&lt;/li&gt;
&lt;li&gt;Highlight reels&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Speaker diarization
&lt;/h3&gt;

&lt;p&gt;Useful for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Meetings&lt;/li&gt;
&lt;li&gt;Interviews&lt;/li&gt;
&lt;li&gt;Support calls&lt;/li&gt;
&lt;li&gt;Sales conversations&lt;/li&gt;
&lt;li&gt;Multi-participant recordings&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Redaction
&lt;/h3&gt;

&lt;p&gt;Useful for reducing sensitive information before sending transcripts downstream.&lt;/p&gt;

&lt;h3&gt;
  
  
  Confidence or stability signals
&lt;/h3&gt;

&lt;p&gt;Useful when the application must decide whether to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Accept a result&lt;/li&gt;
&lt;li&gt;Ask for confirmation&lt;/li&gt;
&lt;li&gt;Flag a segment for review&lt;/li&gt;
&lt;li&gt;Delay a downstream action&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each feature should exist because a workflow requires it.&lt;/p&gt;

&lt;p&gt;Do not enable every parameter merely because it is available.&lt;/p&gt;

&lt;h2&gt;
  
  
  Know When the Prototype Has Earned Real Time
&lt;/h2&gt;

&lt;p&gt;Move from batch to streaming when at least one requirement cannot be met by waiting for the complete file.&lt;/p&gt;

&lt;p&gt;Streaming is justified when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The user needs words on screen while speaking.&lt;/li&gt;
&lt;li&gt;A conversational system must begin reasoning before the full recording exists.&lt;/li&gt;
&lt;li&gt;Turn-taking depends on partial results.&lt;/li&gt;
&lt;li&gt;Interruption handling depends on live audio.&lt;/li&gt;
&lt;li&gt;Live routing depends on the speaker’s current words.&lt;/li&gt;
&lt;li&gt;The recording is too long for upload-then-process.&lt;/li&gt;
&lt;li&gt;Waiting for completion creates unacceptable product delay.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If none of those requirements apply, batch may still be the better architecture.&lt;/p&gt;

&lt;h2&gt;
  
  
  Measure the Complete Streaming Path
&lt;/h2&gt;

&lt;p&gt;Once you move to real time, measure more than model latency.&lt;/p&gt;

&lt;p&gt;Record:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Stage&lt;/th&gt;
&lt;th&gt;What it captures&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Audio capture delay&lt;/td&gt;
&lt;td&gt;Time between speech and frame availability&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Queue delay&lt;/td&gt;
&lt;td&gt;Time waiting before frames are sent&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Network transit&lt;/td&gt;
&lt;td&gt;Time spent reaching the provider&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;First usable partial&lt;/td&gt;
&lt;td&gt;Time until text becomes useful to the interface&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Finalization delay&lt;/td&gt;
&lt;td&gt;Time until the utterance becomes stable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Downstream processing&lt;/td&gt;
&lt;td&gt;Time used by search, tools, or an LLM&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;First audible response&lt;/td&gt;
&lt;td&gt;Time until the user hears the system respond&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Optimizing only the model’s headline latency can hide the stage users are actually waiting on.&lt;/p&gt;

&lt;p&gt;A useful first-partial metric should begin at the audio-capture boundary:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;First usable partial latency
=
first useful transcript timestamp
-
first relevant audio-frame timestamp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For conversational systems, the full user-facing measurement should extend to audible playback.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build Outward From One Trustworthy Transcript
&lt;/h2&gt;

&lt;p&gt;A voice product does not become serious when it opens a WebSocket.&lt;/p&gt;

&lt;p&gt;It becomes serious when every added layer solves a problem the simpler version exposed.&lt;/p&gt;

&lt;p&gt;Start with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Representative audio
      ↓
One API request
      ↓
One structured transcript
      ↓
Evaluation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then verify:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Transcript accuracy&lt;/li&gt;
&lt;li&gt;Entity preservation&lt;/li&gt;
&lt;li&gt;Speaker labels&lt;/li&gt;
&lt;li&gt;Timestamps&lt;/li&gt;
&lt;li&gt;Failure modes&lt;/li&gt;
&lt;li&gt;Response structure&lt;/li&gt;
&lt;li&gt;Batch latency&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Add streaming only when the user’s clock demands it.&lt;/p&gt;

&lt;p&gt;Then:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Keep the API key behind your server.&lt;/li&gt;
&lt;li&gt;Separate sending and receiving.&lt;/li&gt;
&lt;li&gt;Treat partial text as revisable.&lt;/li&gt;
&lt;li&gt;Add bounded buffering.&lt;/li&gt;
&lt;li&gt;Design recovery deliberately.&lt;/li&gt;
&lt;li&gt;Measure the whole path under realistic load.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;The goal is not to use a WebSocket. The goal is to build a voice feature whose complexity matches the problem.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Try the Batch-to-Streaming Path
&lt;/h2&gt;

&lt;p&gt;To test the same progression with Smallest AI:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open the Smallest AI self-serve application.&lt;/li&gt;
&lt;li&gt;Create an API key.&lt;/li&gt;
&lt;li&gt;Start with one representative recording.&lt;/li&gt;
&lt;li&gt;Validate the returned transcript and metadata.&lt;/li&gt;
&lt;li&gt;Move to streaming only when the product requires live results.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://app.smallest.ai/?utm_source=dev.to&amp;amp;utm_medium=vizup&amp;amp;utm_campaign=streaming-speech-to-text-in-production-handling-dropouts-reconnects-and-duplicates"&gt;Create an API key with Smallest AI&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;What requirement in your product genuinely forces the architecture to become real time?&lt;/p&gt;

</description>
      <category>ai</category>
      <category>architecture</category>
      <category>software</category>
    </item>
  </channel>
</rss>
