<?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>Building Real-Time Voice AI: How STT, LLM, TTS, and Telephony Work Together</title>
      <dc:creator>Smallest AI</dc:creator>
      <pubDate>Mon, 07 Sep 2026 09:16:29 +0000</pubDate>
      <link>https://dev.to/smallestai/building-real-time-voice-ai-how-stt-llm-tts-and-telephony-work-together-5fl</link>
      <guid>https://dev.to/smallestai/building-real-time-voice-ai-how-stt-llm-tts-and-telephony-work-together-5fl</guid>
      <description>&lt;p&gt;A voice agent can have excellent speech recognition, a capable language model, and natural text-to-speech, then still feel broken.&lt;/p&gt;

&lt;p&gt;The reason is simple: users experience the system as one conversation, not as four separate services.&lt;/p&gt;

&lt;p&gt;They notice the pause after they stop speaking. They notice when the agent talks over them. They notice when a transcription error sends the conversation in the wrong direction. They notice when high-quality synthetic speech arrives too late.&lt;/p&gt;

&lt;p&gt;That makes real-time voice AI an architecture problem as much as a model problem.&lt;/p&gt;

&lt;p&gt;A typical system has four core layers:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Speech-to-text (STT)&lt;/li&gt;
&lt;li&gt;A large language model (LLM)&lt;/li&gt;
&lt;li&gt;Text-to-speech (TTS)&lt;/li&gt;
&lt;li&gt;Telephony or another real-time audio transport&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The interesting engineering happens in the boundaries between them.&lt;/p&gt;

&lt;h2&gt;
  
  
  The architecture in one sentence
&lt;/h2&gt;

&lt;p&gt;At its simplest, the pipeline looks 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;User speech
 ↓
Streaming STT
 ↓
LLM
 ↓
Streaming TTS
 ↓
Telephony / WebRTC
 ↓
User hears response
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That diagram is useful, but it can also be misleading.&lt;/p&gt;

&lt;p&gt;A production voice system should not behave like a serial batch-processing pipeline where STT finishes completely, then the LLM starts, then TTS starts, then audio is finally delivered.&lt;/p&gt;

&lt;p&gt;If every stage waits for the previous one to finish, latency compounds.&lt;/p&gt;

&lt;p&gt;Responsive systems stream and overlap work wherever possible.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. STT turns audio into usable state
&lt;/h2&gt;

&lt;p&gt;Speech-to-text, also called automatic speech recognition or ASR, converts incoming audio into text that the rest of the system can process. Developers evaluating this layer can compare the architecture against a production &lt;a href="https://smallest.ai/speech-to-text?utm_source=dev.to&amp;amp;utm_medium=vizup&amp;amp;utm_campaign=real-time-voice-ai-architecture-stt-llm-tts-telephony"&gt;speech-to-text system&lt;/a&gt; rather than treating recognition as an isolated offline task.&lt;/p&gt;

&lt;p&gt;For offline transcription, waiting for a complete recording may be fine. Real-time conversations do not have that luxury.&lt;/p&gt;

&lt;p&gt;A streaming STT engine emits partial transcription hypotheses while the user is still speaking. That gives downstream components something to work with before the utterance has completely finished.&lt;/p&gt;

&lt;p&gt;There is a tradeoff.&lt;/p&gt;

&lt;p&gt;Partial transcripts can change as more audio arrives. If the system commits too early, one incorrectly recognized word can steer the LLM toward the wrong intent.&lt;/p&gt;

&lt;p&gt;This is why STT quality is only part of the problem.&lt;/p&gt;

&lt;h3&gt;
  
  
  VAD can make a fast model feel slow
&lt;/h3&gt;

&lt;p&gt;Voice Activity Detection, or VAD, determines whether the user is currently speaking.&lt;/p&gt;

&lt;p&gt;It also helps answer a critical question:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When has the user actually finished their turn?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If the endpointer is conservative, it waits longer before deciding that speech has ended.&lt;/p&gt;

&lt;p&gt;To the infrastructure, that might be a few hundred milliseconds of uncertainty.&lt;/p&gt;

&lt;p&gt;To the user, it feels like the AI is thinking too slowly.&lt;/p&gt;

&lt;p&gt;A surprising number of apparent model-latency problems are actually endpointing problems. Smallest AI's guide to &lt;a href="https://smallest.ai/blog/voice-activity-detection-for-real-time-voice-apps-latency-false-triggers-and-production-tuning?utm_source=dev.to&amp;amp;utm_medium=vizup&amp;amp;utm_campaign=real-time-voice-ai-architecture-stt-llm-tts-telephony"&gt;Voice Activity Detection for real-time voice apps&lt;/a&gt; goes deeper into latency, false triggers, and production tuning around this layer.&lt;/p&gt;

&lt;p&gt;Aggressive VAD settings create the opposite failure mode. The system may decide that the user has finished during a natural pause and start generating a response too early.&lt;/p&gt;

&lt;p&gt;Real-time speech systems therefore have to balance responsiveness with turn-detection accuracy.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. The LLM is the reasoning layer, not the whole voice system
&lt;/h2&gt;

&lt;p&gt;Once enough transcript is available, the LLM determines what the system should say next.&lt;/p&gt;

&lt;p&gt;For many voice architectures, this is the largest individual source of compute latency.&lt;/p&gt;

&lt;p&gt;A large general-purpose model may provide excellent reasoning, but that capability comes with an inference cost. In a voice application, every extra delay becomes visible because the user is waiting for speech to resume.&lt;/p&gt;

&lt;p&gt;Two architectural choices help.&lt;/p&gt;

&lt;p&gt;First, structured workflows do not always require the largest available model. Customer support, appointment scheduling, qualification, routing, and similar flows may benefit from smaller task-focused models when their capabilities fit the workflow.&lt;/p&gt;

&lt;p&gt;Second, the LLM should stream output.&lt;/p&gt;

&lt;p&gt;If the application waits until the complete response has been generated, the TTS layer sits idle.&lt;/p&gt;

&lt;p&gt;With token streaming, the system can send usable text fragments downstream while the remainder of the response is still being generated.&lt;/p&gt;

&lt;p&gt;The LLM is important, but optimizing it while ignoring the rest of the stack is a mistake. Users judge the complete conversation.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. TTS determines when the response becomes real
&lt;/h2&gt;

&lt;p&gt;Text-to-speech converts the LLM response back into audio. A productionctext-to-speech layer has to be evaluated by how quickly it can begin returning usable audio, not only by the quality of a completed file.&lt;/p&gt;

&lt;p&gt;For a real-time system, the critical question is not simply:&lt;/p&gt;

&lt;p&gt;"How long does synthesis take?"&lt;/p&gt;

&lt;p&gt;A more useful question is:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How quickly can the system produce the first playable audio?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is commonly measured as Time to First Audio Byte, or TTFAB.&lt;/p&gt;

&lt;p&gt;A streaming TTS system begins emitting audio chunks before the entire response is available. That allows playback to start while synthesis continues.&lt;/p&gt;

&lt;p&gt;Without streaming, even a fast LLM can be followed by a noticeable pause while the speech engine waits for the complete sentence and synthesizes it.&lt;/p&gt;

&lt;p&gt;This is another reason component benchmarks cannot be evaluated in isolation. What matters is how quickly usable output moves across the entire chain.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Telephony is part of the architecture, not plumbing
&lt;/h2&gt;

&lt;p&gt;The final layer carries audio between your application and the user.&lt;/p&gt;

&lt;p&gt;Depending on the product, that might involve:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The public telephone network&lt;/li&gt;
&lt;li&gt;SIP&lt;/li&gt;
&lt;li&gt;VoIP infrastructure&lt;/li&gt;
&lt;li&gt;A PBX or cloud phone system&lt;/li&gt;
&lt;li&gt;WebRTC in a browser or application&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This layer creates its own failure modes.&lt;/p&gt;

&lt;p&gt;Networks introduce jitter and variable delay. Codecs compress audio. Packet loss affects intelligibility. Transcoding can alter the audio reaching the STT system.&lt;/p&gt;

&lt;p&gt;Those effects often appear only under production traffic.&lt;/p&gt;

&lt;p&gt;A speech recognition model that performs well on clean microphone recordings may behave differently after audio has passed through a telephone codec.&lt;/p&gt;

&lt;p&gt;Similarly, perfect TTS output generated in the backend is irrelevant if the delivery path degrades it before the caller hears it.&lt;/p&gt;

&lt;p&gt;Telephony therefore belongs inside the performance budget.&lt;/p&gt;

&lt;h2&gt;
  
  
  Streaming changes the shape of the pipeline
&lt;/h2&gt;

&lt;p&gt;The most important architectural idea in real-time voice AI is overlap.&lt;/p&gt;

&lt;p&gt;Instead of this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;STT completes
 ↓
LLM completes
 ↓
TTS completes
 ↓
Audio plays
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;you want behavior closer to this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;STT ===============
LLM =============
TTS =============
Playback ===========
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The stages remain logically separate, but execution overlaps.&lt;/p&gt;

&lt;p&gt;STT can emit partial text while the user is speaking.&lt;/p&gt;

&lt;p&gt;The LLM can begin once enough stable context exists.&lt;/p&gt;

&lt;p&gt;TTS can begin when it receives a usable text fragment.&lt;/p&gt;

&lt;p&gt;Playback can begin as soon as the first synthesized audio reaches the transport layer.&lt;/p&gt;

&lt;p&gt;This architecture turns latency from a simple sum into a coordination problem.&lt;/p&gt;

&lt;p&gt;For a deeper treatment of this pattern, the Smallest AI guide to &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=real-time-voice-ai-architecture-stt-llm-tts-telephony"&gt;streaming architecture for real-time voice agents&lt;/a&gt; explains why streaming has to extend across the pipeline rather than being added to only one component.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the latency budget goes
&lt;/h2&gt;

&lt;p&gt;An illustrative latency budget might look like this:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Component&lt;/th&gt;
&lt;th&gt;Typical contribution&lt;/th&gt;
&lt;th&gt;Primary optimization lever&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Streaming STT&lt;/td&gt;
&lt;td&gt;50-100 ms&lt;/td&gt;
&lt;td&gt;Streaming transcription and VAD tuning&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LLM inference&lt;/td&gt;
&lt;td&gt;150-300 ms&lt;/td&gt;
&lt;td&gt;Smaller models, token streaming, caching&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TTS synthesis&lt;/td&gt;
&lt;td&gt;50-150 ms to first audio&lt;/td&gt;
&lt;td&gt;Streaming TTS and low-latency models&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Telephony / network&lt;/td&gt;
&lt;td&gt;20-80 ms&lt;/td&gt;
&lt;td&gt;Deployment location, codec choice, WebRTC&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;These values should be treated as architectural estimates, not universal guarantees. Real performance varies with model size, infrastructure, geography, audio conditions, and the transport path.&lt;/p&gt;

&lt;p&gt;The important pattern is the distribution.&lt;/p&gt;

&lt;p&gt;The LLM is commonly the largest individual contributor, but optimizing only the LLM does not guarantee a responsive conversation.&lt;/p&gt;

&lt;p&gt;You might reduce inference time and still lose the improvement because VAD waits too long.&lt;/p&gt;

&lt;p&gt;You might deploy faster TTS and then add network latency between regions.&lt;/p&gt;

&lt;p&gt;You might improve STT accuracy while choosing a model that processes audio too slowly for the desired interaction.&lt;/p&gt;

&lt;p&gt;Voice latency is an end-to-end property.&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%2Frqjxnwkn0k3qzy79wxfs.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%2Frqjxnwkn0k3qzy79wxfs.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What a production deployment actually looks like
&lt;/h2&gt;

&lt;p&gt;Consider a contact-center workflow.&lt;/p&gt;

&lt;p&gt;A caller enters through SIP into an existing PBX or cloud telephony environment.&lt;/p&gt;

&lt;p&gt;Streaming STT begins processing audio as it arrives.&lt;/p&gt;

&lt;p&gt;The LLM receives the transcript and determines the next response. When the workflow requires external information, the application can consult a knowledge base, CRM, scheduling system, or another business tool through application logic or function calling.&lt;/p&gt;

&lt;p&gt;The response is streamed toward TTS.&lt;/p&gt;

&lt;p&gt;Synthesized audio is then returned through the telephony layer.&lt;/p&gt;

&lt;p&gt;If the conversation moves outside the system's permitted scope or requires human intervention, the call can be escalated according to the application's fallback logic.&lt;/p&gt;

&lt;p&gt;The same architectural pattern can be used in healthcare scheduling, support, commerce, sales, and other conversational applications. The requirements change, but the underlying coordination problem remains similar.&lt;/p&gt;

&lt;p&gt;For teams that prefer an integrated implementation path rather than assembling every layer independently, the &lt;a href="https://smallest.ai/voice-agents?utm_source=dev.to&amp;amp;utm_medium=vizup&amp;amp;utm_campaign=real-time-voice-ai-architecture-stt-llm-tts-telephony"&gt;Smallest AI voice-agent platform&lt;/a&gt; brings agent configuration, telephony, integrations, and deployment workflows into one environment. The related guide to &lt;a href="https://smallest.ai/blog/mastering-voice-bot-architecture-a-deep-dive-with-smallest-ai-s-atoms-sdk?utm_source=dev.to&amp;amp;utm_medium=vizup&amp;amp;utm_campaign=real-time-voice-ai-architecture-stt-llm-tts-telephony"&gt;voice bot architecture&lt;/a&gt; shows how the speech and orchestration layers can be connected inside a production SDK.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three architecture misconceptions worth avoiding
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Misconception 1: Better accuracy automatically creates a better real-time system
&lt;/h3&gt;

&lt;p&gt;Accuracy and latency have to be evaluated together.&lt;/p&gt;

&lt;p&gt;A larger speech model may improve recognition but require more processing per audio chunk.&lt;/p&gt;

&lt;p&gt;For transcription workloads where accuracy dominates, that trade can make sense.&lt;/p&gt;

&lt;p&gt;For a live customer conversation, a modest accuracy improvement may not justify a delay that repeatedly disrupts turn-taking.&lt;/p&gt;

&lt;p&gt;There is no single correct tradeoff. It depends on the application.&lt;/p&gt;

&lt;p&gt;The important point is that an offline accuracy benchmark cannot tell you whether a model is appropriate for a real-time conversation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Misconception 2: The LLM is the voice AI
&lt;/h3&gt;

&lt;p&gt;It is not.&lt;/p&gt;

&lt;p&gt;A powerful LLM connected to poor STT can reason about the wrong transcript.&lt;/p&gt;

&lt;p&gt;A powerful LLM connected to slow TTS still feels slow.&lt;/p&gt;

&lt;p&gt;A powerful LLM behind badly configured VAD may constantly interrupt users or leave awkward pauses.&lt;/p&gt;

&lt;p&gt;A powerful LLM sent through an unreliable telephony layer still produces an unreliable product.&lt;/p&gt;

&lt;p&gt;The voice agent is the system formed by all of these components.&lt;/p&gt;

&lt;h3&gt;
  
  
  Misconception 3: Speech-to-speech eliminates the modular pipeline everywhere
&lt;/h3&gt;

&lt;p&gt;Speech-to-speech models can accept audio and produce audio without exposing explicit text stages in the same way as a traditional STT + LLM + TTS architecture.&lt;/p&gt;

&lt;p&gt;That can be useful in latency-sensitive applications.&lt;/p&gt;

&lt;p&gt;It does not automatically make modular architectures obsolete.&lt;/p&gt;

&lt;p&gt;Many production systems still require clear insertion points for business logic, predictable workflows, auditability, tool execution, and explicit control over individual stages.&lt;/p&gt;

&lt;p&gt;For those systems, keeping STT, reasoning, and TTS as identifiable components can be valuable.&lt;/p&gt;

&lt;p&gt;Speech-to-speech is therefore another architectural option, not a universal replacement.&lt;/p&gt;

&lt;h2&gt;
  
  
  Production problems start where the happy path ends
&lt;/h2&gt;

&lt;p&gt;Choosing models is only the beginning.&lt;/p&gt;

&lt;p&gt;Real users interrupt, hesitate, change their minds, speak over the system, call from noisy environments, and ask questions the application was never designed to answer.&lt;/p&gt;

&lt;p&gt;Several decisions become critical once a system reaches production. Smallest AI's article on &lt;a href="https://smallest.ai/blog/ai-voice-agents-architecture-voice-models-use-cases-and-safety-guardrails?utm_source=dev.to&amp;amp;utm_medium=vizup&amp;amp;utm_campaign=real-time-voice-ai-architecture-stt-llm-tts-telephony"&gt;designing AI voice agents&lt;/a&gt; provides additional context on architecture, use cases, and safety guardrails beyond the basic STT + LLM + TTS pipeline.&lt;/p&gt;

&lt;h3&gt;
  
  
  Interruption handling
&lt;/h3&gt;

&lt;p&gt;Users will talk while the AI is speaking.&lt;/p&gt;

&lt;p&gt;The system needs to detect the new speech, cancel or stop the active TTS output, update the conversational state, and process the interruption.&lt;/p&gt;

&lt;p&gt;If it cannot, the agent talks over people.&lt;/p&gt;

&lt;p&gt;That immediately makes the experience feel mechanical.&lt;/p&gt;

&lt;h3&gt;
  
  
  Context management
&lt;/h3&gt;

&lt;p&gt;Conversation history grows on every turn.&lt;/p&gt;

&lt;p&gt;Sending an indefinitely growing context back to the LLM can increase latency and processing requirements.&lt;/p&gt;

&lt;p&gt;Common approaches include summarizing older turns or maintaining a sliding context window while preserving important application state.&lt;/p&gt;

&lt;p&gt;The right approach depends on how much historical context the workflow genuinely needs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Fallback and escalation
&lt;/h3&gt;

&lt;p&gt;A production system needs defined behavior for situations such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Low-confidence transcription&lt;/li&gt;
&lt;li&gt;Unsupported requests&lt;/li&gt;
&lt;li&gt;Missing business data&lt;/li&gt;
&lt;li&gt;Tool failures&lt;/li&gt;
&lt;li&gt;Requests that require human handling&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Fallback logic should be designed as part of the architecture, not added after deployment.&lt;/p&gt;

&lt;h3&gt;
  
  
  Edge versus cloud placement
&lt;/h3&gt;

&lt;p&gt;Every network hop matters.&lt;/p&gt;

&lt;p&gt;Moving audio between regions can consume part of the latency budget before the models have done any work.&lt;/p&gt;

&lt;p&gt;Some architectures move latency-sensitive processing closer to the user while keeping other components in centralized cloud infrastructure.&lt;/p&gt;

&lt;p&gt;The correct boundary depends on infrastructure, model requirements, geography, and operational complexity.&lt;/p&gt;

&lt;h3&gt;
  
  
  Codec and audio quality
&lt;/h3&gt;

&lt;p&gt;Phone audio is not the same as clean studio audio.&lt;/p&gt;

&lt;p&gt;Codecs such as G.711 and Opus affect what reaches the speech recognition system and what the caller ultimately hears.&lt;/p&gt;

&lt;p&gt;When production accuracy suddenly differs from testing, inspect the audio path before assuming the model itself has regressed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prototyping the stack with Smallest AI
&lt;/h2&gt;

&lt;p&gt;The current &lt;a href="https://smallest.ai/?utm_source=dev.to&amp;amp;utm_medium=vizup&amp;amp;utm_campaign=real-time-voice-ai-architecture-stt-llm-tts-telephony"&gt;Smallest AI voice platform&lt;/a&gt; spans speech recognition, speech generation, speech-to-speech, and voice-agent workflows. For developers evaluating this architecture programmatically, the &lt;a href="https://app.smallest.ai/?utm_source=dev.to&amp;amp;utm_medium=vizup&amp;amp;utm_campaign=real-time-voice-ai-architecture-stt-llm-tts-telephony"&gt;Smallest AI API&lt;/a&gt; provides the developer entry point for working with the relevant voice models and services.&lt;/p&gt;

&lt;p&gt;The useful way to test a real-time architecture is not to evaluate only isolated model output.&lt;/p&gt;

&lt;p&gt;Run the system using representative audio, your actual deployment geography, the codecs your application will use, realistic conversation lengths, and real interruption behavior.&lt;/p&gt;

&lt;p&gt;Measure at least:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Time from user speech ending to the first LLM output&lt;/li&gt;
&lt;li&gt;Time to first synthesized audio&lt;/li&gt;
&lt;li&gt;End-to-end turn latency&lt;/li&gt;
&lt;li&gt;STT errors under real audio conditions&lt;/li&gt;
&lt;li&gt;False VAD triggers&lt;/li&gt;
&lt;li&gt;Missed end-of-turn events&lt;/li&gt;
&lt;li&gt;Barge-in behavior&lt;/li&gt;
&lt;li&gt;Network and telephony delay&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If authenticated API requests are added to your implementation, keep API credentials on the server. Do not expose them in browser JavaScript, mobile application code, public repositories, screenshots, URLs, or client-side logs.&lt;/p&gt;

&lt;h2&gt;
  
  
  The architecture is the product
&lt;/h2&gt;

&lt;p&gt;The failure mode in real-time voice AI is rarely one component completely breaking.&lt;/p&gt;

&lt;p&gt;More often, the experience degrades through accumulation.&lt;/p&gt;

&lt;p&gt;A little VAD delay.&lt;/p&gt;

&lt;p&gt;A slow first LLM token.&lt;/p&gt;

&lt;p&gt;A TTS buffer that waits too long.&lt;/p&gt;

&lt;p&gt;An unnecessary network hop.&lt;/p&gt;

&lt;p&gt;A codec mismatch.&lt;/p&gt;

&lt;p&gt;A barge-in handler that does not cancel playback quickly enough.&lt;/p&gt;

&lt;p&gt;Each problem may look small in isolation. Together, they determine whether the conversation feels natural.&lt;/p&gt;

&lt;p&gt;That is the central architectural lesson: optimize the handoffs, not just the parts.&lt;/p&gt;

&lt;p&gt;STT has to stream.&lt;/p&gt;

&lt;p&gt;The reasoning layer has to produce output early enough for downstream synthesis.&lt;/p&gt;

&lt;p&gt;TTS has to return playable audio incrementally.&lt;/p&gt;

&lt;p&gt;The delivery layer has to preserve both timing and audio quality.&lt;/p&gt;

&lt;p&gt;And the entire system has to survive interruptions, imperfect networks, growing context, and unpredictable users.&lt;/p&gt;

&lt;p&gt;If you are building this pipeline, the best test is your own application under realistic conditions. &lt;a href="https://app.smallest.ai/?utm_source=dev.to&amp;amp;utm_medium=vizup&amp;amp;utm_campaign=real-time-voice-ai-architecture-stt-llm-tts-telephony"&gt;Create an API key and prototype the voice workflow with Smallest AI&lt;/a&gt;, then measure the complete path from live audio input to the first audio returned to the user.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>voiceai</category>
      <category>machinelearning</category>
      <category>webdev</category>
    </item>
    <item>
      <title>How to Choose a Voice Agent API: Architecture, Latency, Streaming, and Stack Trade-Offs</title>
      <dc:creator>Smallest AI</dc:creator>
      <pubDate>Wed, 26 Aug 2026 07:54:28 +0000</pubDate>
      <link>https://dev.to/smallestai/how-to-choose-a-voice-agent-api-architecture-latency-streaming-and-stack-trade-offs-3mhp</link>
      <guid>https://dev.to/smallestai/how-to-choose-a-voice-agent-api-architecture-latency-streaming-and-stack-trade-offs-3mhp</guid>
      <description>&lt;p&gt;A voice interface used to mean a phone tree: press 1 for billing, press 2 for support, and wait for the next prompt.&lt;/p&gt;

&lt;p&gt;Modern voice applications have a much higher bar. Users expect quick turn-taking, context that survives across multiple turns, and responses that start before the silence feels like a failure.&lt;/p&gt;

&lt;p&gt;That changes voice from a simple speech feature into a real-time systems problem.&lt;/p&gt;

&lt;p&gt;A Voice Agent API sits at the infrastructure layer of that problem. Instead of exposing transcription or speech generation as isolated utilities, it helps coordinate the conversational loop: listen, understand, reason, and respond.&lt;/p&gt;

&lt;p&gt;For developers, the difficult part is rarely getting each individual component to work. The harder problem is getting the entire pipeline to behave like one responsive system.&lt;/p&gt;

&lt;p&gt;This guide breaks down that architecture, where latency enters the stack, why streaming matters, and what to evaluate before committing to a production setup.&lt;/p&gt;

&lt;p&gt;For a broader architectural view, Smallest AI's &lt;a href="https://smallest.ai/blog/ai-voice-agents-architecture-voice-models-use-cases-and-safety-guardrails?utm_source=dev.to&amp;amp;utm_medium=vizup&amp;amp;utm_campaign=how-to-build-an-ai-voice-agent-using-atoms-api"&gt;AI voice agent architecture guide&lt;/a&gt; covers the underlying voice models, common use cases, and deployment considerations.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is a Voice Agent API?
&lt;/h2&gt;

&lt;p&gt;A Voice Agent API is a programmatic interface for building conversational systems that can listen to speech, reason about what was said, and respond with synthesized audio inside a coordinated pipeline.&lt;/p&gt;

&lt;p&gt;The distinction from standalone speech APIs matters.&lt;/p&gt;

&lt;p&gt;A text-to-speech API handles:&lt;/p&gt;

&lt;p&gt;text → audio&lt;/p&gt;

&lt;p&gt;A speech-to-text API handles:&lt;/p&gt;

&lt;p&gt;audio → text&lt;/p&gt;

&lt;p&gt;A typical voice-agent pipeline handles:&lt;/p&gt;

&lt;p&gt;speech → transcription → reasoning → response text → synthesized speech&lt;/p&gt;

&lt;p&gt;It also needs to preserve conversation state between turns and handle interaction patterns that one-shot speech APIs do not need to solve.&lt;/p&gt;

&lt;p&gt;Examples include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;A caller correcting something they said earlier.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The user interrupting the agent while it is speaking.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A later question depending on information from an earlier turn.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;An ambiguous request requiring clarification.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A tool or backend action taking long enough that the conversation still needs to feel responsive.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The important idea is that the "agent" is not simply STT plus TTS. It is the orchestration of speech recognition, reasoning, state, and speech generation into one conversational system.&lt;/p&gt;

&lt;h2&gt;
  
  
  The stack behind a voice agent
&lt;/h2&gt;

&lt;p&gt;The most common architecture is a cascading pipeline:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Audio
↓
Speech-to-Text
↓
Language Model
↓
Text-to-Speech
↓
Audio
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The concept is straightforward.&lt;/p&gt;

&lt;p&gt;The latency behavior is not.&lt;/p&gt;

&lt;p&gt;Every boundary introduces work: network transport, buffering, model inference, serialization, queueing, and coordination between services.&lt;/p&gt;

&lt;p&gt;If every stage waits for the previous stage to finish completely, those delays accumulate.&lt;/p&gt;

&lt;p&gt;That is why production voice-agent architecture is usually less about optimizing one model in isolation and more about optimizing the entire path from the end of the user's turn to the beginning of the agent's response.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why streaming changes the latency budget
&lt;/h2&gt;

&lt;p&gt;Consider two implementations.&lt;/p&gt;

&lt;h3&gt;
  
  
  Batch pipeline
&lt;/h3&gt;

&lt;p&gt;A batch-oriented system might behave like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Wait for the user to finish speaking.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Upload or finalize the complete audio segment.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Wait for the complete transcript.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Send the transcript to the language model.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Wait for the complete model response.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Send the complete response to TTS.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Wait for enough synthesized audio.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Start playback.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Each stage blocks the next.&lt;/p&gt;

&lt;p&gt;That architecture can work for offline transcription or generated narration. It is poorly suited to natural turn-taking.&lt;/p&gt;

&lt;h3&gt;
  
  
  Streaming pipeline
&lt;/h3&gt;

&lt;p&gt;A streaming architecture allows useful partial results to move downstream as soon as they become available.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User speech
↓ partial audio
Streaming STT
↓ incremental/final transcript
Reasoning layer
↓ streamed response tokens
Streaming TTS
↓ audio chunks
Playback
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The major difference is overlap.&lt;/p&gt;

&lt;p&gt;The reasoning layer can begin processing as soon as sufficient speech context exists. TTS can begin receiving generated text without waiting for an entire paragraph. Playback can start while later audio is still being synthesized.&lt;/p&gt;

&lt;p&gt;Streaming does not remove latency from individual models.&lt;/p&gt;

&lt;p&gt;It stops the system from unnecessarily serializing every unit of work.&lt;/p&gt;

&lt;p&gt;For real-time voice applications, that distinction is fundamental.&lt;/p&gt;

&lt;h2&gt;
  
  
  Speech-to-text: the listening layer
&lt;/h2&gt;

&lt;p&gt;The STT system converts incoming audio into machine-readable text.&lt;/p&gt;

&lt;p&gt;Its quality limits everything downstream.&lt;/p&gt;

&lt;p&gt;If the recognizer repeatedly mishears names, numbers, accented speech, or domain-specific terminology, the reasoning layer starts from corrupted input.&lt;/p&gt;

&lt;p&gt;The result can be:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Incorrect answers.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Wrong tool calls.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Extra clarification turns.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Failed task completion.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Increased conversation length.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A common transcription metric is Word Error Rate, or WER. Smallest AI has a separate &lt;a href="https://smallest.ai/blog/word-error-rate-explained-why-it-matters-for-voice-agent-quality?utm_source=dev.to&amp;amp;utm_medium=vizup&amp;amp;utm_campaign=how-to-build-an-ai-voice-agent-using-atoms-api"&gt;guide to Word Error Rate for voice agents&lt;/a&gt; if you want to go deeper into evaluation.&lt;/p&gt;

&lt;p&gt;For voice agents, however, aggregate transcription accuracy is not the only consideration.&lt;/p&gt;

&lt;p&gt;You also need to evaluate how the STT system behaves while audio is still arriving.&lt;/p&gt;

&lt;p&gt;Questions worth testing include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;How quickly do partial transcripts arrive?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;How stable are those partial transcripts?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;How does endpoint detection behave?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;What happens with background noise?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;How are interruptions handled?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;What happens when the network briefly degrades?&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Smallest AI's current model stack uses Pulse as its streaming STT component.&lt;/p&gt;

&lt;h2&gt;
  
  
  The reasoning layer
&lt;/h2&gt;

&lt;p&gt;The transcript then moves into a conversational model.&lt;/p&gt;

&lt;p&gt;That model has several responsibilities:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Understand what the user means.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Maintain conversation context.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Decide what should happen next.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Potentially call tools or external services.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Produce the text that will become speech.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For text applications, users may tolerate visible generation.&lt;/p&gt;

&lt;p&gt;Voice is less forgiving.&lt;/p&gt;

&lt;p&gt;A delay that looks normal in a chat interface becomes dead air in a phone call.&lt;/p&gt;

&lt;p&gt;That makes time-to-first-token important, but optimizing only that number is not enough. A voice-agent latency budget can also include:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;End-of-user-turn detection
+ STT finalization
+ orchestration
+ model time-to-first-token
+ tool execution when required
+ TTS startup
+ network and playback buffering
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The right question is therefore not:&lt;/p&gt;

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

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

&lt;blockquote&gt;
&lt;p&gt;How much time passes between the user's conversational turn and the first useful audio response?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Smaller conversational models can be useful when reducing reasoning startup time is more important than maximizing general-purpose generation capability.&lt;/p&gt;

&lt;p&gt;Smallest AI uses Electron as the conversational model in its own Pulse → Electron → Lightning pipeline.&lt;/p&gt;

&lt;h2&gt;
  
  
  Text-to-speech: the voice layer
&lt;/h2&gt;

&lt;p&gt;TTS turns the generated response into audible speech.&lt;/p&gt;

&lt;p&gt;This is where the assistant becomes a voice experience rather than a text system with audio attached.&lt;/p&gt;

&lt;p&gt;Two properties matter immediately.&lt;/p&gt;

&lt;p&gt;First, the synthesized output needs to sound appropriate for the application.&lt;/p&gt;

&lt;p&gt;Second, audio needs to become available quickly enough to preserve conversational rhythm.&lt;/p&gt;

&lt;p&gt;Streaming TTS is valuable because the synthesizer can begin producing audio from partial generated text instead of waiting for the language model to finish an entire response.&lt;/p&gt;

&lt;p&gt;That creates another opportunity for overlap:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;LLM: "I can help you..."
↓
TTS: begins synthesis
LLM: "...reschedule that appointment..."
↓
TTS: continues synthesis
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Smallest AI uses Lightning for streaming TTS in its cascading voice-agent stack. Its documentation also describes Hydra as a speech-to-speech model within the broader speech stack.&lt;/p&gt;

&lt;p&gt;For more detail on this part of the latency budget, see the &lt;a href="https://smallest.ai/blog/neural-tts-latency-explained-how-to-build-faster-ai-voice-agents?utm_source=dev.to&amp;amp;utm_medium=vizup&amp;amp;utm_campaign=how-to-build-an-ai-voice-agent-using-atoms-api"&gt;neural TTS latency guide&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Voice Agent API architectures are not all the same
&lt;/h2&gt;

&lt;p&gt;Voice APIs generally fall into a few architectural patterns.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;Approach&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Setup effort&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Control&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Latency implications&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Good fit&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Managed voice-agent platform&lt;/td&gt;
&lt;td&gt;Lower&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;td&gt;Orchestration handled by platform&lt;/td&gt;
&lt;td&gt;Teams that want to deploy complete agents quickly&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Modular STT + LLM + TTS&lt;/td&gt;
&lt;td&gt;Higher&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;More inter-service boundaries to manage&lt;/td&gt;
&lt;td&gt;Teams that need deep control over individual components&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Speech-to-speech&lt;/td&gt;
&lt;td&gt;Varies&lt;/td&gt;
&lt;td&gt;Depends on implementation&lt;/td&gt;
&lt;td&gt;Can reduce intermediate text-stage overhead&lt;/td&gt;
&lt;td&gt;Workloads suited to direct audio-to-audio interaction&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;None is universally correct.&lt;/p&gt;

&lt;p&gt;A managed system reduces orchestration work, but gives the platform more responsibility for implementation details.&lt;/p&gt;

&lt;p&gt;A modular stack gives developers more freedom to choose each component independently, but they also inherit retries, network hops, observability, synchronization, interruption handling, and failure recovery between services.&lt;/p&gt;

&lt;p&gt;Speech-to-speech can compress portions of the traditional cascade, but the available control, tooling, and state-management model depends heavily on the implementation.&lt;/p&gt;

&lt;p&gt;The choice should follow the application's actual requirements rather than the architecture that looks simplest in a demo.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the Smallest AI API fits
&lt;/h2&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%2Fieuo595sc0khh9rf3kt6.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%2Fieuo595sc0khh9rf3kt6.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
For a concrete example of a vertically integrated stack, &lt;a href="https://smallest.ai/?utm_source=dev.to&amp;amp;utm_medium=vizup&amp;amp;utm_campaign=how-to-build-an-ai-voice-agent-using-atoms-api"&gt;Smallest AI&lt;/a&gt; exposes speech models and voice-agent infrastructure within the same ecosystem.&lt;/p&gt;

&lt;p&gt;The current stack includes Pulse for STT, Electron for conversational reasoning, and Lightning for TTS, while Atoms provides the higher-level voice-agent platform.&lt;/p&gt;

&lt;p&gt;Developers who want to evaluate the stack programmatically can use the &lt;a href="https://app.smallest.ai/?utm_source=dev.to&amp;amp;utm_medium=vizup&amp;amp;utm_campaign=how-to-build-an-ai-voice-agent-using-atoms-api"&gt;Smallest AI API&lt;/a&gt;, while &lt;a href="https://smallest.ai/voice-agents?utm_source=dev.to&amp;amp;utm_medium=vizup&amp;amp;utm_campaign=how-to-build-an-ai-voice-agent-using-atoms-api"&gt;Smallest AI Voice Agents&lt;/a&gt; is the relevant product layer for building and deploying complete agents.&lt;/p&gt;

&lt;p&gt;If you want an implementation-oriented follow-up, the &lt;a href="https://smallest.ai/blog/how-to-build-an-ai-voice-agent-using-atoms-api?utm_source=dev.to&amp;amp;utm_medium=vizup&amp;amp;utm_campaign=how-to-build-an-ai-voice-agent-using-atoms-api"&gt;Atoms API voice-agent tutorial&lt;/a&gt; walks through the production-minded setup in more detail.&lt;/p&gt;
&lt;h2&gt;
  
  
  Where voice-agent APIs are being used
&lt;/h2&gt;

&lt;p&gt;The architecture applies anywhere a machine needs to carry on a real-time spoken conversation rather than simply transcribe or narrate.&lt;/p&gt;
&lt;h3&gt;
  
  
  Customer support
&lt;/h3&gt;

&lt;p&gt;An inbound voice agent might:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Answer common questions.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Retrieve account information.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Perform a structured workflow.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Route a complex case to a human.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The interesting engineering challenge is not just answering correctly. The system also needs interruption handling, escalation logic, reliable tool execution, and predictable response timing.&lt;/p&gt;
&lt;h3&gt;
  
  
  Healthcare scheduling and intake
&lt;/h3&gt;

&lt;p&gt;Structured tasks such as appointment confirmation, rescheduling, and intake can map naturally to conversational workflows.&lt;/p&gt;

&lt;p&gt;These deployments also raise stronger requirements around data handling, security, escalation, and failure behavior.&lt;/p&gt;
&lt;h3&gt;
  
  
  Sales development
&lt;/h3&gt;

&lt;p&gt;Outbound agents can handle structured qualification conversations, respond to common objections, collect information, and schedule the next step.&lt;/p&gt;

&lt;p&gt;The underlying architecture is still the same: listen, understand, decide, act, and respond without introducing unnatural pauses between stages.&lt;/p&gt;
&lt;h3&gt;
  
  
  Accessibility
&lt;/h3&gt;

&lt;p&gt;Voice interfaces can also provide hands-free interaction for people who cannot efficiently use conventional input methods.&lt;/p&gt;

&lt;p&gt;In that context, responsiveness is more than polish. Latency and predictable turn-taking can directly affect usability.&lt;/p&gt;
&lt;h2&gt;
  
  
  Three voice-agent mistakes developers make
&lt;/h2&gt;
&lt;h3&gt;
  
  
  1. Treating latency as a TTS-only problem
&lt;/h3&gt;

&lt;p&gt;TTS is visible because it is the final step before the user hears something.&lt;/p&gt;

&lt;p&gt;That makes it an easy component to blame.&lt;/p&gt;

&lt;p&gt;But the total delay can come from several places:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Endpoint detection
→ STT finalization
→ orchestration
→ model startup
→ tools
→ TTS
→ buffering
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Optimizing a fast synthesizer will not rescue a pipeline that spends most of its latency budget somewhere upstream.&lt;/p&gt;

&lt;p&gt;Measure the full turn.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Assuming a strong LLM automatically creates a strong voice agent
&lt;/h3&gt;

&lt;p&gt;A voice agent is an end-to-end system.&lt;/p&gt;

&lt;p&gt;An excellent reasoning model cannot fully compensate for poor transcription, unreliable endpointing, slow tool calls, unstable networking, or unnatural synthesis.&lt;/p&gt;

&lt;p&gt;For production systems, evaluate the interaction rather than ranking components independently.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Treating voice identity as an afterthought
&lt;/h3&gt;

&lt;p&gt;For brand-facing voice applications, the selected voice affects how users perceive the experience.&lt;/p&gt;

&lt;p&gt;Developers should evaluate:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Pronunciation consistency.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Prosody.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Speaking rate.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Stability across longer responses.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Voice customization requirements.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Whether cloned or custom voices are actually needed.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Voice identity also creates trust and security considerations.&lt;/p&gt;

&lt;p&gt;The W3C's &lt;a href="https://www.w3.org/2025/10/smartagents-workshop/report.html" rel="noopener noreferrer"&gt;Smart Voice Agents workshop report&lt;/a&gt; highlights areas such as privacy-preserving authentication, user identification, accessibility, real-time interaction, and interoperability as important issues for voice-agent systems.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to evaluate before choosing a Voice Agent API
&lt;/h2&gt;

&lt;p&gt;A polished demo can hide architecture problems that become obvious under production traffic.&lt;/p&gt;

&lt;p&gt;Before selecting a stack, test the following areas.&lt;/p&gt;

&lt;h3&gt;
  
  
  End-to-end response latency
&lt;/h3&gt;

&lt;p&gt;Do not evaluate only model inference numbers.&lt;/p&gt;

&lt;p&gt;Measure the complete conversation path:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;user finishes speaking
↓
agent detects turn boundary
↓
transcription completes
↓
reasoning begins
↓
response starts
↓
speech synthesis starts
↓
first audio reaches user
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Test under realistic network conditions and expected concurrency.&lt;/p&gt;

&lt;h3&gt;
  
  
  Streaming at every relevant stage
&lt;/h3&gt;

&lt;p&gt;A system that streams TTS but batches STT still has a major blocking stage.&lt;/p&gt;

&lt;p&gt;Verify how streaming works for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Incoming audio.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Partial transcripts.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Reasoning output.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Tool execution where relevant.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Synthesized audio.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Playback.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Also check what happens when a stream disconnects or a caller interrupts.&lt;/p&gt;

&lt;h3&gt;
  
  
  Interruption and turn-taking behavior
&lt;/h3&gt;

&lt;p&gt;Real users do not wait politely for an agent to finish.&lt;/p&gt;

&lt;p&gt;They pause.&lt;/p&gt;

&lt;p&gt;They restart sentences.&lt;/p&gt;

&lt;p&gt;They interrupt.&lt;/p&gt;

&lt;p&gt;They say "actually, never mind."&lt;/p&gt;

&lt;p&gt;A production voice agent needs explicit behavior for those cases.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Barge-in.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;False endpoint detection.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Long pauses inside a sentence.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Consecutive short utterances.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Double-talk.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Network jitter.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Voice customization
&lt;/h3&gt;

&lt;p&gt;If voice consistency matters to the product, determine what can actually be controlled.&lt;/p&gt;

&lt;p&gt;Possible considerations include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Voice selection.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Voice cloning.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Prosody.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Speaking rate.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Pronunciation behavior.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Language support.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Do not assume every API exposes the same controls.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cost under realistic conversations
&lt;/h3&gt;

&lt;p&gt;Pricing can be based on different units: minutes, characters, requests, individual model usage, hosting, telephony, or combinations of these.&lt;/p&gt;

&lt;p&gt;The useful calculation is not simply the advertised unit price.&lt;/p&gt;

&lt;p&gt;Model the workload you expect to run.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;daily conversations
× average conversation duration
× turns per conversation
× speech/model usage
× infrastructure and telephony costs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Smallest AI guide to &lt;a href="https://smallest.ai/blog/what-are-the-true-costs-associated-with-operating-a-voice-agent-at-scale?utm_source=dev.to&amp;amp;utm_medium=vizup&amp;amp;utm_campaign=how-to-build-an-ai-voice-agent-using-atoms-api"&gt;voice-agent operating costs at scale&lt;/a&gt; goes deeper into that planning problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the stack itself matters
&lt;/h2&gt;

&lt;p&gt;A common voice-agent prototype starts with individually strong components.&lt;/p&gt;

&lt;p&gt;The team chooses an STT provider, an LLM, and a TTS service. Each performs well on its own.&lt;/p&gt;

&lt;p&gt;Then the components are connected.&lt;/p&gt;

&lt;p&gt;That is when the invisible overhead becomes visible.&lt;/p&gt;

&lt;p&gt;Suppose one stage needs to wait for an endpointing decision. Another service introduces network latency. The language model takes time before generating its first useful output. TTS then needs additional time before audio playback can begin.&lt;/p&gt;

&lt;p&gt;No individual service has to be dramatically slow for the combined interaction to feel sluggish.&lt;/p&gt;

&lt;p&gt;That is the "latency tax" of orchestration.&lt;/p&gt;

&lt;p&gt;A unified architecture can reduce some of those boundaries because the components are designed to work together. A modular architecture can still perform extremely well, but developers have to engineer those boundaries themselves.&lt;/p&gt;

&lt;p&gt;Smallest AI's current stack takes the integrated approach: Pulse, Electron, and Lightning can be used as one streaming pipeline, while Atoms adds the voice-agent orchestration layer.&lt;/p&gt;

&lt;p&gt;The relevant question when comparing this against a modular stack is not whether one architecture is theoretically superior.&lt;/p&gt;

&lt;p&gt;It is whether you want to own:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Streaming coordination.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Turn detection.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Inter-service communication.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Retry behavior.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Observability.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;State management.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Interruption handling.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Latency tuning at every boundary.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That decision often matters more than choosing between two models with similar benchmark numbers.&lt;/p&gt;

&lt;h2&gt;
  
  
  A practical stack-selection checklist
&lt;/h2&gt;

&lt;p&gt;Before locking in a provider, answer these questions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Can every latency-critical stage stream?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;What is the end-to-end time to first useful audio?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;How does the system handle barge-in?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Can you observe latency by component?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;What happens when STT, reasoning, or TTS fails?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Can conversations recover from network interruptions?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;How is conversation state preserved?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Which components can be replaced later?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;What voice controls are actually exposed?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;How does pricing behave for your expected conversation length?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Are security and compliance requirements compatible with your deployment?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Can you reproduce realistic production conditions during testing?&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You should be able to answer those questions before committing significant application logic to the stack.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What is the difference between a Voice Agent API and a TTS API?
&lt;/h3&gt;

&lt;p&gt;A TTS API converts text into synthesized audio.&lt;/p&gt;

&lt;p&gt;A Voice Agent API coordinates a larger conversational loop that includes listening, reasoning, dialogue state, and speech output.&lt;/p&gt;

&lt;p&gt;TTS is one component of that architecture.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does a voice agent require separate STT, LLM, and TTS providers?
&lt;/h3&gt;

&lt;p&gt;No.&lt;/p&gt;

&lt;p&gt;You can assemble the components independently or use a platform that coordinates several stages for you.&lt;/p&gt;

&lt;p&gt;A modular architecture provides more component-level control. A unified platform can reduce integration work and the number of service boundaries you have to manage.&lt;/p&gt;

&lt;h3&gt;
  
  
  How much latency is acceptable?
&lt;/h3&gt;

&lt;p&gt;There is no single number that fits every application.&lt;/p&gt;

&lt;p&gt;Instead of optimizing toward an isolated benchmark, measure the complete turn from the end of the user's speech to the first useful agent audio.&lt;/p&gt;

&lt;p&gt;For conversational applications, lower and more predictable latency generally produces better turn-taking than a pipeline with long or inconsistent pauses.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is streaming TTS enough?
&lt;/h3&gt;

&lt;p&gt;Usually not.&lt;/p&gt;

&lt;p&gt;If STT or the reasoning layer still waits for complete inputs and outputs, those stages remain sequential bottlenecks.&lt;/p&gt;

&lt;p&gt;The biggest benefit comes from designing the entire latency-sensitive path around incremental processing.&lt;/p&gt;

&lt;h3&gt;
  
  
  Should I use speech-to-speech instead?
&lt;/h3&gt;

&lt;p&gt;It depends on the application.&lt;/p&gt;

&lt;p&gt;Speech-to-speech architectures can reduce or reorganize parts of the traditional STT → LLM → TTS cascade, but they may expose a different set of controls, debugging surfaces, state-management mechanisms, and integration options.&lt;/p&gt;

&lt;p&gt;Evaluate the architecture against your workflow rather than latency alone.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final takeaway
&lt;/h2&gt;

&lt;p&gt;A Voice Agent API is not simply another speech endpoint.&lt;/p&gt;

&lt;p&gt;It is the infrastructure around a real-time feedback loop.&lt;/p&gt;

&lt;p&gt;The strongest production architecture is usually the one that treats latency, streaming, turn-taking, state, error recovery, and speech quality as one system rather than separate model-selection problems.&lt;/p&gt;

&lt;p&gt;If you are building that loop yourself, measure every handoff.&lt;/p&gt;

&lt;p&gt;If you would rather start with an integrated stack, you can &lt;a href="https://app.smallest.ai/?utm_source=dev.to&amp;amp;utm_medium=vizup&amp;amp;utm_campaign=how-to-build-an-ai-voice-agent-using-atoms-api"&gt;start building with the Smallest AI API&lt;/a&gt; and evaluate the pipeline with your own conversational workloads.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>voiceai</category>
      <category>speechrecognition</category>
      <category>texttospeech</category>
    </item>
    <item>
      <title>How to Build Reliable Streaming Speech-to-Text in Production</title>
      <dc:creator>Smallest AI</dc:creator>
      <pubDate>Wed, 26 Aug 2026 07:37:35 +0000</pubDate>
      <link>https://dev.to/smallestai/how-to-build-reliable-streaming-speech-to-text-in-production-419g</link>
      <guid>https://dev.to/smallestai/how-to-build-reliable-streaming-speech-to-text-in-production-419g</guid>
      <description>&lt;p&gt;A streaming speech-to-text demo is usually the easy part.&lt;/p&gt;

&lt;p&gt;You connect a microphone, send audio frames, receive partial transcripts, and everything feels instant.&lt;/p&gt;

&lt;p&gt;Production is where things get complicated.&lt;/p&gt;

&lt;p&gt;Real users do not have perfect networks. WebSocket connections drop. Audio packets arrive late. Partial transcripts appear out of order. Reconnecting can create duplicate text. And downstream systems need a way to decide whether a transcript is trustworthy.&lt;/p&gt;

&lt;p&gt;A production-grade streaming transcription system needs to handle these failure modes intentionally.&lt;/p&gt;

&lt;p&gt;This guide covers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;How streaming speech-to-text systems process audio&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;How to recover from network dropouts&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;How to reconnect without corrupting transcripts&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;How to remove duplicate segments&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;How to evaluate transcript quality in real time&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How streaming speech-to-text works
&lt;/h2&gt;

&lt;p&gt;Streaming transcription is not a simple request-response workflow.&lt;/p&gt;

&lt;p&gt;Instead, it is a continuous bidirectional connection where:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Audio frames are sent to the recognition engine.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The engine processes the incoming stream.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Partial and final transcript segments are returned asynchronously.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Most production systems use persistent connections such as WebSockets because they avoid repeated connection overhead.&lt;/p&gt;

&lt;p&gt;A typical pipeline looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Microphone
    ↓
Audio frames
    ↓
Streaming connection
    ↓
Speech recognition engine
    ↓
Interim + final transcript segments
    ↓
Application logic
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Streaming systems usually return two types of results:&lt;/p&gt;

&lt;h3&gt;
  
  
  Interim results
&lt;/h3&gt;

&lt;p&gt;Interim results are temporary predictions.&lt;/p&gt;

&lt;p&gt;They are useful for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Live captions&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Real-time interfaces&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Voice assistants&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;However, they can change as more audio arrives.&lt;/p&gt;

&lt;h3&gt;
  
  
  Final results
&lt;/h3&gt;

&lt;p&gt;Final results are committed transcript segments.&lt;/p&gt;

&lt;p&gt;They should be used for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Storage&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Search indexing&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Compliance workflows&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Analytics pipelines&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Treating interim results as final is one of the most common causes of unreliable transcript experiences.&lt;/p&gt;

&lt;h2&gt;
  
  
  Handling dropout events
&lt;/h2&gt;

&lt;p&gt;A dropout happens whenever the continuous audio stream is interrupted.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;WebSocket disconnections&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Packet loss&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Server timeouts&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Microphone permission changes&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Device switching&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Mobile application backgrounding&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The first thing to measure is not only whether a disconnect happened, but how long the interruption lasted.&lt;/p&gt;

&lt;p&gt;A practical approach:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Short gaps can often be recovered with buffered audio.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Medium gaps may require context rebuilding.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Longer interruptions should usually trigger a fresh session.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Build dropout-aware buffering
&lt;/h2&gt;

&lt;p&gt;A client-side audio buffer helps recover from temporary interruptions.&lt;/p&gt;

&lt;p&gt;A production implementation should:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Keep a rolling audio buffer.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Track disconnect start and end times.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Store transcript segments with timing metadata.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Emit connection-state events to the application layer.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example metadata:&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;"session_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"session_123"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"segment_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"timestamp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1710000000&lt;/span&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;"final"&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 goal is not only reconnecting the network connection.&lt;/p&gt;

&lt;p&gt;The goal is preserving transcript continuity.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reconnect logic without transcript corruption
&lt;/h2&gt;

&lt;p&gt;A common mistake is treating reconnecting as:&lt;/p&gt;

&lt;p&gt;disconnect → reconnect → continue sending audio&lt;/p&gt;

&lt;p&gt;The connection may recover, but transcript consistency may not.&lt;/p&gt;

&lt;p&gt;After reconnecting, you now have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;A previous session&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A new session&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Potentially overlapping audio&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Without tracking session boundaries, your transcript assembler cannot know whether a segment is new or duplicated.&lt;/p&gt;

&lt;p&gt;A better approach is to attach:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Session ID&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Segment sequence number&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Absolute timestamp&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then assemble transcripts using timestamps rather than arrival order.&lt;/p&gt;

&lt;p&gt;Network delays can cause older segments to arrive after newer ones.&lt;/p&gt;

&lt;h2&gt;
  
  
  Removing duplicate transcript segments
&lt;/h2&gt;

&lt;p&gt;Duplicate text usually comes from two situations.&lt;/p&gt;

&lt;h3&gt;
  
  
  Interim-to-final promotion
&lt;/h3&gt;

&lt;p&gt;Example:&lt;/p&gt;

&lt;p&gt;Interim:&lt;/p&gt;

&lt;p&gt;"The meeting will start"&lt;/p&gt;

&lt;p&gt;Final:&lt;/p&gt;

&lt;p&gt;"The meeting will start at three"&lt;/p&gt;

&lt;p&gt;Appending both creates:&lt;/p&gt;

&lt;p&gt;The meeting will start The meeting will start at three&lt;/p&gt;

&lt;p&gt;The solution:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Track committed final positions.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Replace interim text instead of appending it.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Replayed audio after reconnect
&lt;/h3&gt;

&lt;p&gt;When buffered audio is replayed after a dropout, the speech engine may transcribe the same audio again.&lt;/p&gt;

&lt;p&gt;Exact string matching is unreliable because transcripts may differ slightly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Capitalization&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Punctuation&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Minor wording changes&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A better method is token-overlap comparison.&lt;/p&gt;

&lt;p&gt;If two segments:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Have overlapping timestamps&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Share a high percentage of tokens&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Represent the same spoken content&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Keep the higher-confidence version.&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%2Fxxk1o2v7szw99t952dj6.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%2Fxxk1o2v7szw99t952dj6.png" alt=" " width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Measuring transcript quality
&lt;/h2&gt;

&lt;p&gt;Word Error Rate (WER) is the standard metric for evaluating speech recognition accuracy.&lt;/p&gt;

&lt;p&gt;WER measures:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Substitutions&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Insertions&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Deletions&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;However, WER requires a reference transcript, so it is not useful during a live conversation.&lt;/p&gt;

&lt;p&gt;For real-time systems, confidence scores are more practical.&lt;/p&gt;

&lt;p&gt;A production pipeline can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Calculate segment confidence.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Set thresholds.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Route uncertain segments for review.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;High confidence
→ Display immediately

Low confidence
→ Delay or review before downstream processing
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For deeper evaluation methods, developers can also explore guides on &lt;a href="https://smallest.ai/blog/how-to-evaluate-asr-in-2026?utm_source=dev.to&amp;amp;utm_medium=vizup&amp;amp;utm_campaign=streaming-speech-to-text-production"&gt;evaluating ASR systems&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Handling context loss after reconnects
&lt;/h2&gt;

&lt;p&gt;A reconnect does more than restore a network connection.&lt;/p&gt;

&lt;p&gt;It can also reset:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Acoustic context&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Language model context&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Conversation history&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This matters when users discuss:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Technical terminology&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Medical terms&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Legal vocabulary&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Industry-specific language&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Passing vocabulary hints or context information when starting a new session can improve recovery quality.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building production-ready streaming STT systems
&lt;/h2&gt;

&lt;p&gt;A reliable streaming speech-to-text implementation should include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;WebSocket event monitoring&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Audio buffering&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Session tracking&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Timestamp-based ordering&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Duplicate detection&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Confidence-based routing&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Context restoration strategies&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The recognition API is only one part of the system.&lt;/p&gt;

&lt;p&gt;The application layer determines whether the final experience feels reliable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building with Smallest AI
&lt;/h2&gt;

&lt;p&gt;Developers building real-time transcription workflows can use the &lt;a href="https://app.smallest.ai/?utm_source=dev.to&amp;amp;utm_medium=vizup&amp;amp;utm_campaign=streaming-speech-to-text-production"&gt;Smallest AI speech-to-text API&lt;/a&gt; to integrate speech recognition into production applications.&lt;/p&gt;

&lt;p&gt;For developers designing complete voice pipelines, the &lt;a href="https://app.smallest.ai/?utm_source=dev.to&amp;amp;utm_medium=vizup&amp;amp;utm_campaign=streaming-speech-to-text-production"&gt;Smallest AI API&lt;/a&gt; provides programmatic access for building speech-based applications.&lt;/p&gt;

&lt;p&gt;You can also explore the broader &lt;a href="https://smallest.ai/blog/top-10-speech-to-text-transcription-software-picks-for-2026?utm_source=dev.to&amp;amp;utm_medium=vizup&amp;amp;utm_campaign=streaming-speech-to-text-production"&gt;speech-to-text transcription software landscape&lt;/a&gt; and compare different approaches for production deployments.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final thoughts
&lt;/h2&gt;

&lt;p&gt;Streaming speech-to-text becomes challenging when real-world conditions appear.&lt;/p&gt;

&lt;p&gt;Networks fail. Sessions restart. Audio overlaps. Confidence varies.&lt;/p&gt;

&lt;p&gt;The difference between a demo and a production system is having the engineering discipline to handle those cases.&lt;/p&gt;

&lt;p&gt;Design around failures from the beginning, and your transcription pipeline will remain reliable even when users and networks are unpredictable.&lt;/p&gt;

&lt;p&gt;Start building your own voice workflow with the &lt;a href="https://app.smallest.ai/?utm_source=dev.to&amp;amp;utm_medium=vizup&amp;amp;utm_campaign=streaming-speech-to-text-production"&gt;Smallest AI API&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>websockets</category>
      <category>realtime</category>
      <category>speechrecognition</category>
    </item>
    <item>
      <title>How to Build a Low-Latency Voice Bot with Streaming STT and TTS</title>
      <dc:creator>Smallest AI</dc:creator>
      <pubDate>Wed, 26 Aug 2026 07:20:48 +0000</pubDate>
      <link>https://dev.to/smallestai/how-to-build-a-low-latency-voice-bot-with-streaming-stt-and-tts-524j</link>
      <guid>https://dev.to/smallestai/how-to-build-a-low-latency-voice-bot-with-streaming-stt-and-tts-524j</guid>
      <description>&lt;p&gt;A voice bot can be described in three steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Listen to the user.&lt;/li&gt;
&lt;li&gt;Decide what to say.&lt;/li&gt;
&lt;li&gt;Speak the response.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That sounds straightforward until you try to make the loop happen in real time.&lt;/p&gt;

&lt;p&gt;Speech recognition has latency. Your reasoning layer has latency. Speech synthesis has latency. Network transit adds more. Audio arrives continuously rather than as neat request-response messages, and users expect to interrupt, hesitate, change direction, and speak over the system.&lt;/p&gt;

&lt;p&gt;That is why building a usable voice bot is less about connecting three APIs and more about designing the entire audio pipeline around streaming, turn-taking, cancellation, and latency.&lt;/p&gt;

&lt;p&gt;This article walks through the architecture behind that pipeline, using the same STT → reasoning → TTS pattern that powers many production voice applications. &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;Smallest AI&lt;/a&gt; provides these layers through Pulse, Electron, Lightning, and its voice-agent platform, but the architectural principles apply regardless of which components you choose.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a voice bot actually does
&lt;/h2&gt;

&lt;p&gt;At the highest level, the loop looks 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;User audio
    ↓
Speech-to-Text
    ↓
Transcript
    ↓
LLM or deterministic logic
    ↓
Response text
    ↓
Text-to-Speech
    ↓
Audio response
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The architecture is easy to understand.&lt;/p&gt;

&lt;p&gt;The difficulty is that every boundary adds delay.&lt;/p&gt;

&lt;p&gt;If you treat the system as a conventional sequence of synchronous API calls, you can easily end up with this behavior:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;record full utterance
→ wait for transcription
→ wait for complete model response
→ wait for complete speech generation
→ begin playback
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every stage blocks the next.&lt;/p&gt;

&lt;p&gt;For offline processing, that may be acceptable. For a live conversation, it feels slow.&lt;/p&gt;

&lt;p&gt;A real-time voice bot should instead try to overlap work wherever possible:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;audio ───────────────►
       STT ──────────►
            LLM ─────►
                 TTS ─────────► audio
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Streaming is what turns a serial pipeline into an overlapping one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Choose the stack before writing the orchestration layer
&lt;/h2&gt;

&lt;p&gt;Three components determine most of the behavior of a traditional voice bot:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Speech-to-text for incoming audio&lt;/li&gt;
&lt;li&gt;A reasoning or decision layer&lt;/li&gt;
&lt;li&gt;Text-to-speech for outgoing audio&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Choosing each component independently gives you flexibility, but it also creates more interfaces to manage.&lt;/p&gt;

&lt;p&gt;Authentication, connection lifecycle, audio formats, retries, versioning, latency measurement, observability, and billing can all become integration concerns.&lt;/p&gt;

&lt;p&gt;For some applications, that control is worth it. For others, a unified platform reduces the amount of orchestration code your team has to own.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pick STT for conversations, not offline transcription
&lt;/h3&gt;

&lt;p&gt;A good transcription model for prerecorded files is not automatically a good STT engine for a voice bot.&lt;/p&gt;

&lt;p&gt;For real-time use, you should care about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Streaming transcription&lt;/li&gt;
&lt;li&gt;Stable partial transcripts&lt;/li&gt;
&lt;li&gt;Endpoint detection&lt;/li&gt;
&lt;li&gt;Word timestamps when required&lt;/li&gt;
&lt;li&gt;Language coverage&lt;/li&gt;
&lt;li&gt;Performance on your actual acoustic environment&lt;/li&gt;
&lt;li&gt;Whether processing can keep up with live audio&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One useful metric is real-time factor, or RTF.&lt;/p&gt;

&lt;p&gt;If a recognizer takes one second to process one second of speech, its RTF is 1.0. For a live system, you generally want processing comfortably below that level so work does not accumulate behind the incoming audio stream.&lt;/p&gt;

&lt;p&gt;Smallest AI’s Pulse is designed for real-time and prerecorded speech recognition. For a voice bot, the important architectural capability is that transcription can happen while speech is still arriving rather than after an entire recording has been uploaded.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pick TTS for time to first audio
&lt;/h3&gt;

&lt;p&gt;For a live voice application, speech quality is only one part of TTS performance.&lt;/p&gt;

&lt;p&gt;You also need to know how quickly playback can begin.&lt;/p&gt;

&lt;p&gt;A system that eventually produces excellent audio but leaves a long silence before the first syllable still feels broken in conversation.&lt;/p&gt;

&lt;p&gt;Streaming TTS addresses this by returning audio incrementally instead of waiting for synthesis of the entire response.&lt;/p&gt;

&lt;p&gt;Smallest AI’s Lightning is designed around this streaming model and can be used as the speech-generation layer of a real-time pipeline.&lt;/p&gt;

&lt;p&gt;Other practical considerations include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Voice consistency&lt;/li&gt;
&lt;li&gt;Language support&lt;/li&gt;
&lt;li&gt;Audio encoding&lt;/li&gt;
&lt;li&gt;Streaming behavior&lt;/li&gt;
&lt;li&gt;Prosody and pacing&lt;/li&gt;
&lt;li&gt;Voice cloning when your application requires a consistent custom voice&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Decide whether you actually need an LLM
&lt;/h3&gt;

&lt;p&gt;Not every voice bot needs a general-purpose language model.&lt;/p&gt;

&lt;p&gt;Consider a phone router that only needs to recognize requests 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;billing
technical support
cancel subscription
check order
speak to an agent
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A deterministic intent layer may be faster, cheaper, and easier to audit than a large model.&lt;/p&gt;

&lt;p&gt;An LLM becomes more useful when users can phrase requests unpredictably or when the system needs multi-turn reasoning, tool calls, or context-sensitive responses.&lt;/p&gt;

&lt;p&gt;Smallest AI’s Electron can act as the conversational reasoning layer in this architecture. You can also use another compatible reasoning system if your application requires it.&lt;/p&gt;

&lt;p&gt;If you do not want to assemble the orchestration and agent infrastructure yourself, the &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;Smallest AI voice-agent platform&lt;/a&gt; provides a managed path for building and deploying voice agents.&lt;/p&gt;

&lt;h2&gt;
  
  
  Architecture blueprint: make every stage stream
&lt;/h2&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%2F52a54qysb66zma6wj3g5.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%2F52a54qysb66zma6wj3g5.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
A practical voice bot architecture looks roughly 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 / Call ───►│ Streaming STT   │
                      └────────┬────────┘
                               │
                        partial + final
                          transcripts
                               │
                               ▼
                      ┌─────────────────┐
                      │ Orchestrator    │
                      │ VAD / state /   │
                      │ cancellation    │
                      └────────┬────────┘
                               │
                               ▼
                      ┌─────────────────┐
                      │ LLM or rules    │
                      └────────┬────────┘
                               │
                         streamed text
                               │
                               ▼
                      ┌─────────────────┐
                      │ Streaming TTS   │
                      └────────┬────────┘
                               │
                               ▼
                         Audio playback
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;WebSockets are a natural transport for this kind of architecture because the connection stays open while audio and events move continuously.&lt;/p&gt;

&lt;p&gt;The important idea is not WebSockets specifically. It is avoiding a workflow where every stage waits for a complete payload from the previous one.&lt;/p&gt;

&lt;p&gt;For a deeper breakdown of how the STT, LLM, TTS, tools, and latency budget interact, Smallest AI’s 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=designing-voice-assistants-stt-llm-tts-tools-and-latency-budget"&gt;designing voice assistants around STT, LLMs, TTS, and latency&lt;/a&gt; is a useful companion to this implementation view.&lt;/p&gt;

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

&lt;p&gt;If you are prototyping the pipeline with the &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;, 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/?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 key&lt;/a&gt; in the dashboard 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 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 &amp;lt;SMALLEST_API_KEY value&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Keep the key on your server.&lt;/p&gt;

&lt;p&gt;Do not expose it in browser JavaScript, mobile application code, public repositories, screenshots, query parameters, or client-side logs.&lt;/p&gt;

&lt;p&gt;For production deployments, put the value in the secret-management system used by your infrastructure rather than a checked-in configuration file.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building the core voice loop
&lt;/h2&gt;

&lt;p&gt;The easiest way to reason about the application is as several concurrent tasks rather than one long function.&lt;/p&gt;

&lt;p&gt;You typically have independent flows for:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;microphone → STT
STT events → conversation state
conversation state → reasoning
reasoning output → TTS
TTS audio → playback
user interruption → cancellation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An asynchronous runtime such as Python’s &lt;code&gt;asyncio&lt;/code&gt;, Node.js, or another event-driven environment maps naturally to this model.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Capture and stream incoming audio
&lt;/h3&gt;

&lt;p&gt;Start with the audio source.&lt;/p&gt;

&lt;p&gt;Depending on the application, that may be:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A browser microphone&lt;/li&gt;
&lt;li&gt;A native mobile microphone&lt;/li&gt;
&lt;li&gt;A WebRTC session&lt;/li&gt;
&lt;li&gt;A telephony stream&lt;/li&gt;
&lt;li&gt;A SIP connection&lt;/li&gt;
&lt;li&gt;Another realtime media transport&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Do not accumulate several seconds of speech before beginning transcription.&lt;/p&gt;

&lt;p&gt;Send audio frames to STT continuously.&lt;/p&gt;

&lt;p&gt;Frame sizes in the tens-of-milliseconds range are common because they balance responsiveness against packet and processing overhead. A 16 kHz stream is also a common baseline for speech-recognition workloads, although your actual settings should match the STT service and source audio you are using.&lt;/p&gt;

&lt;p&gt;The recognizer can then begin returning partial transcription results while the person is still speaking.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Handle VAD and endpointing separately
&lt;/h3&gt;

&lt;p&gt;Recognizing words is only part of the problem.&lt;/p&gt;

&lt;p&gt;The bot also has to determine when the user has finished a turn.&lt;/p&gt;

&lt;p&gt;This is where voice activity detection and endpointing become critical.&lt;/p&gt;

&lt;p&gt;Consider someone saying:&lt;/p&gt;

&lt;p&gt;“Can you… uh… move my appointment to Friday?”&lt;/p&gt;

&lt;p&gt;A simplistic silence timer may decide the utterance ended after “Can you.”&lt;/p&gt;

&lt;p&gt;Set the threshold too aggressively and the bot interrupts natural pauses.&lt;/p&gt;

&lt;p&gt;Set it too conservatively and every turn gains an uncomfortable delay.&lt;/p&gt;

&lt;p&gt;The correct value depends heavily on the environment and task.&lt;/p&gt;

&lt;p&gt;A tightly scripted support call may tolerate aggressive turn-taking. A conversational assistant whose users frequently pause to think may need more patience.&lt;/p&gt;

&lt;p&gt;Do not tune endpointing exclusively with clean test recordings. Use audio that represents actual microphones, background noise, accents, network conditions, and speaking patterns from your deployment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Do not wait for the entire model response
&lt;/h2&gt;

&lt;p&gt;After STT produces a finalized user turn, the transcript enters the reasoning layer.&lt;/p&gt;

&lt;p&gt;A common first implementation looks 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;complete transcript
        ↓
generate entire LLM response
        ↓
send complete response to TTS
        ↓
play audio
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It works, but it wastes time.&lt;/p&gt;

&lt;p&gt;If your reasoning system supports streaming output, begin preparing speech before the entire answer exists.&lt;/p&gt;

&lt;p&gt;You do not necessarily want to synthesize individual tokens. TTS needs enough context to produce natural phrasing and prosody.&lt;/p&gt;

&lt;p&gt;A better strategy is to buffer the model output into speakable chunks such as complete clauses or sentences:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;LLM tokens
   ↓
small text buffer
   ↓
natural speech boundary
   ↓
TTS
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;While TTS is synthesizing the first chunk, the model can continue generating the next.&lt;/p&gt;

&lt;p&gt;That overlap is one of the most important ways to reduce perceived response time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stream synthesized audio back immediately
&lt;/h2&gt;

&lt;p&gt;The same principle applies after text enters the TTS layer.&lt;/p&gt;

&lt;p&gt;Do not wait for the complete audio file if your provider supports streaming.&lt;/p&gt;

&lt;p&gt;Start playback as soon as enough audio has arrived.&lt;/p&gt;

&lt;p&gt;The end-to-end flow becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User still speaking
       │
       ├── STT processing audio
       │
User finishes
       │
       ├── final transcript
       │
       ├── reasoning begins
       │
       ├── first usable text chunk
       │
       ├── TTS begins
       │
       └── first audio begins playing
              while later text
              and audio are still
              being generated
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For network delivery, codecs designed for interactive audio are usually better suited to real-time conversations than formats optimized primarily for downloadable media.&lt;/p&gt;

&lt;p&gt;Opus is commonly used in real-time communication because it performs well at relatively low bitrates and is designed for interactive audio. Raw PCM is convenient when debugging or when downstream systems need uncompressed samples.&lt;/p&gt;

&lt;p&gt;Whatever format you choose, avoid unnecessary encode/decode conversions between services. Every conversion creates another place for buffering, CPU work, or format mismatches.&lt;/p&gt;

&lt;h2&gt;
  
  
  Barge-in changes the architecture
&lt;/h2&gt;

&lt;p&gt;A voice bot that cannot be interrupted will quickly feel artificial.&lt;/p&gt;

&lt;p&gt;Suppose the bot starts saying:&lt;/p&gt;

&lt;p&gt;“Your current subscription includes—”&lt;/p&gt;

&lt;p&gt;and the user interrupts:&lt;/p&gt;

&lt;p&gt;“Actually, I want to cancel it.”&lt;/p&gt;

&lt;p&gt;The system should stop the outgoing response and process the new utterance.&lt;/p&gt;

&lt;p&gt;That means several things have to happen almost simultaneously:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;detect user speech
→ stop or cancel TTS generation
→ flush queued playback audio
→ preserve the new incoming speech
→ return control to the listening state
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important consequence is that your STT path cannot simply disappear while the bot is speaking.&lt;/p&gt;

&lt;p&gt;You need to continue monitoring incoming audio so the system can detect the interruption.&lt;/p&gt;

&lt;p&gt;This is also why echo cancellation matters. Without it, the microphone may feed the bot’s own synthesized speech back into STT, and the system can mistake its response for a new user utterance.&lt;/p&gt;

&lt;p&gt;Barge-in is not a small feature you bolt onto the end of development. It affects the audio pipeline, state machine, playback buffer, VAD behavior, and cancellation strategy.&lt;/p&gt;

&lt;p&gt;Design for it early.&lt;/p&gt;

&lt;h2&gt;
  
  
  Treat latency as a pipeline budget
&lt;/h2&gt;

&lt;p&gt;Developers often optimize whichever component has the most obvious latency number.&lt;/p&gt;

&lt;p&gt;That is not enough.&lt;/p&gt;

&lt;p&gt;A conversational delay is the sum of several pieces:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;endpoint detection
+ STT
+ network
+ reasoning
+ tool calls
+ TTS startup
+ playback buffering
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Improving one component may not noticeably change the experience if another stage dominates the total.&lt;/p&gt;

&lt;p&gt;Instrument the boundaries individually.&lt;/p&gt;

&lt;p&gt;For each turn, record timestamps 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;speech_started
speech_ended
final_transcript_received
llm_started
first_llm_chunk_received
tts_started
first_audio_received
playback_started
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then measure distributions rather than only averages.&lt;/p&gt;

&lt;p&gt;P50 tells you what a typical user experiences.&lt;/p&gt;

&lt;p&gt;P90 and P99 reveal the slow turns that users are more likely to remember as failures.&lt;/p&gt;

&lt;p&gt;A related metric is task completion. A low-latency system that misunderstands users or fails to complete the requested workflow is not successful simply because it responds quickly.&lt;/p&gt;

&lt;p&gt;Latency and task success have to be evaluated together.&lt;/p&gt;

&lt;h2&gt;
  
  
  Four mistakes that break voice bots outside the demo
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Building a chatbot with a microphone attached
&lt;/h3&gt;

&lt;p&gt;Voice is not just a different input widget for a text application.&lt;/p&gt;

&lt;p&gt;You now have to handle:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Silence&lt;/li&gt;
&lt;li&gt;Natural pauses&lt;/li&gt;
&lt;li&gt;Background noise&lt;/li&gt;
&lt;li&gt;Echo&lt;/li&gt;
&lt;li&gt;Interruptions&lt;/li&gt;
&lt;li&gt;Audio encoding&lt;/li&gt;
&lt;li&gt;Turn detection&lt;/li&gt;
&lt;li&gt;Playback state&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Ignoring these problems is why many prototypes work perfectly at a developer’s desk and fall apart during real calls.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Batching every stage
&lt;/h3&gt;

&lt;p&gt;If the system waits for:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;full audio
→ full transcript
→ full model response
→ full TTS file
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;all of the delays are serialized.&lt;/p&gt;

&lt;p&gt;Stream wherever the component supports it.&lt;/p&gt;

&lt;p&gt;The goal is not merely faster APIs. It is overlapping stages.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Load testing HTTP requests instead of conversations
&lt;/h3&gt;

&lt;p&gt;Fifty text API requests are not equivalent to fifty simultaneous calls.&lt;/p&gt;

&lt;p&gt;Each live conversation can involve:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A persistent connection&lt;/li&gt;
&lt;li&gt;Continuous audio&lt;/li&gt;
&lt;li&gt;STT state&lt;/li&gt;
&lt;li&gt;VAD state&lt;/li&gt;
&lt;li&gt;Conversation history&lt;/li&gt;
&lt;li&gt;TTS generation&lt;/li&gt;
&lt;li&gt;Audio playback&lt;/li&gt;
&lt;li&gt;Cancellation events&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Load test the architecture using realistic concurrent audio sessions.&lt;/p&gt;

&lt;p&gt;Watch memory, open connections, queue depth, model latency, reconnection behavior, and tail latency.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Treating one voice as correct for every workflow
&lt;/h3&gt;

&lt;p&gt;The voice itself is part of the interface.&lt;/p&gt;

&lt;p&gt;A scheduling assistant, support agent, sales workflow, and collections system may have very different tone requirements.&lt;/p&gt;

&lt;p&gt;If your application needs a consistent custom voice, voice cloning is one option, but it should be evaluated alongside latency, language coverage, intelligibility, and the context in which the bot will speak.&lt;/p&gt;

&lt;h2&gt;
  
  
  Production features that the MVP usually skips
&lt;/h2&gt;

&lt;p&gt;Getting audio through STT, a model, and TTS proves the basic pipeline.&lt;/p&gt;

&lt;p&gt;It does not make the bot production-ready.&lt;/p&gt;

&lt;h3&gt;
  
  
  Multi-turn conversation state
&lt;/h3&gt;

&lt;p&gt;A useful voice bot has to remember what happened earlier in the conversation.&lt;/p&gt;

&lt;p&gt;For shorter sessions, you can carry recent turns into the model context.&lt;/p&gt;

&lt;p&gt;Longer interactions require a strategy to keep context from growing indefinitely.&lt;/p&gt;

&lt;p&gt;Common approaches include:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sliding window
summarization
retrieval of relevant past context
structured application state
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Do not rely entirely on a raw transcript when the application has business state such as an appointment date, order ID, account selection, or workflow stage.&lt;/p&gt;

&lt;p&gt;Store important state explicitly.&lt;/p&gt;

&lt;h3&gt;
  
  
  Telephony integration
&lt;/h3&gt;

&lt;p&gt;If the bot will handle phone calls, speech generation is only part of the system.&lt;/p&gt;

&lt;p&gt;You eventually encounter:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SIP/PSTN connectivity&lt;/li&gt;
&lt;li&gt;Phone-number management&lt;/li&gt;
&lt;li&gt;DTMF&lt;/li&gt;
&lt;li&gt;Transfers&lt;/li&gt;
&lt;li&gt;Hold behavior&lt;/li&gt;
&lt;li&gt;Recording&lt;/li&gt;
&lt;li&gt;Regional routing&lt;/li&gt;
&lt;li&gt;Compliance requirements&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At this point, teams must decide whether they want to own the telephony and orchestration infrastructure themselves or use an agent platform that abstracts some of it.&lt;/p&gt;

&lt;p&gt;Smallest AI’s managed voice-agent platform is one option when you want the speech stack and agent infrastructure integrated rather than assembled independently.&lt;/p&gt;

&lt;h3&gt;
  
  
  Observability
&lt;/h3&gt;

&lt;p&gt;Log enough information to reconstruct why a bad conversation happened.&lt;/p&gt;

&lt;p&gt;Useful measurements include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;STT latency&lt;/li&gt;
&lt;li&gt;Endpointing delay&lt;/li&gt;
&lt;li&gt;LLM time to first output&lt;/li&gt;
&lt;li&gt;Tool-call latency&lt;/li&gt;
&lt;li&gt;TTS startup latency&lt;/li&gt;
&lt;li&gt;End-to-end time to first audio&lt;/li&gt;
&lt;li&gt;Interruption frequency&lt;/li&gt;
&lt;li&gt;Connection failures&lt;/li&gt;
&lt;li&gt;Task-completion rate&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Be thoughtful about what you store.&lt;/p&gt;

&lt;p&gt;Voice interactions can contain sensitive information, and observability should not become an excuse to dump raw transcripts, credentials, or unnecessary user data into logs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Should you build the stack or use a managed platform?
&lt;/h2&gt;

&lt;p&gt;There is no universal answer.&lt;/p&gt;

&lt;p&gt;Building the pipeline yourself gives you direct control over:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;audio transport
STT
reasoning
tool execution
TTS
state
telephony
observability
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That control can be valuable if voice is a core part of your product infrastructure.&lt;/p&gt;

&lt;p&gt;But you also inherit every integration boundary.&lt;/p&gt;

&lt;p&gt;A managed platform moves more of that responsibility into the provider.&lt;/p&gt;

&lt;p&gt;Smallest AI’s stack combines Pulse for STT, Electron for conversational reasoning, Lightning for TTS, and its voice-agent tooling for orchestration and deployment.&lt;/p&gt;

&lt;p&gt;The important engineering question is not whether a unified or modular architecture is always better.&lt;/p&gt;

&lt;p&gt;It is how much of the real-time voice infrastructure your team wants to build, operate, monitor, and debug itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  A practical build checklist
&lt;/h2&gt;

&lt;p&gt;Before calling the voice bot production-ready, verify that you have made explicit decisions about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Streaming versus batching at every stage&lt;/li&gt;
&lt;li&gt;STT endpointing and VAD thresholds&lt;/li&gt;
&lt;li&gt;Audio sample rate and encoding&lt;/li&gt;
&lt;li&gt;LLM versus deterministic routing&lt;/li&gt;
&lt;li&gt;TTS chunk boundaries&lt;/li&gt;
&lt;li&gt;Barge-in and cancellation&lt;/li&gt;
&lt;li&gt;Echo handling&lt;/li&gt;
&lt;li&gt;Conversation-state management&lt;/li&gt;
&lt;li&gt;Tool-call latency&lt;/li&gt;
&lt;li&gt;Reconnection behavior&lt;/li&gt;
&lt;li&gt;Concurrent session limits&lt;/li&gt;
&lt;li&gt;P50, P90, and P99 latency measurement&lt;/li&gt;
&lt;li&gt;Secret management&lt;/li&gt;
&lt;li&gt;Sensitive transcript logging&lt;/li&gt;
&lt;li&gt;Real-world audio testing&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Most production failures happen because one of these areas was treated as an implementation detail instead of an architectural decision.&lt;/p&gt;

&lt;h2&gt;
  
  
  The real optimization is overlap
&lt;/h2&gt;

&lt;p&gt;The basic voice-bot loop has not changed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;listen → think → speak
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What separates a responsive voice application from a sluggish one is how much work happens concurrently.&lt;/p&gt;

&lt;p&gt;Stream audio into STT instead of waiting for a recording.&lt;/p&gt;

&lt;p&gt;Start reasoning as soon as the user’s turn is complete.&lt;/p&gt;

&lt;p&gt;Stream model output toward TTS instead of waiting for the full response.&lt;/p&gt;

&lt;p&gt;Start playback as soon as audio is available.&lt;/p&gt;

&lt;p&gt;Keep listening while the bot speaks so users can interrupt.&lt;/p&gt;

&lt;p&gt;Measure every boundary instead of treating latency as one opaque number.&lt;/p&gt;

&lt;p&gt;Once those pieces are in place, the technology choices become easier to evaluate because you can judge them inside the architecture that will actually run in production.&lt;/p&gt;

&lt;p&gt;If you want to prototype the pipeline using Pulse, Electron, and Lightning, &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;start building with the Smallest AI API&lt;/a&gt; and test it against your own audio, latency targets, and conversation patterns.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Which programming language should I use for a voice bot?
&lt;/h3&gt;

&lt;p&gt;Python is a practical choice when you are already working with AI APIs and asynchronous processing. Node.js is also a strong option for browser, WebSocket, and WebRTC-heavy backends.&lt;/p&gt;

&lt;p&gt;For high-concurrency deployments, other languages may make sense depending on your infrastructure.&lt;/p&gt;

&lt;p&gt;The best choice is usually the runtime your team can operate reliably while handling persistent connections and concurrent audio streams.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do I reduce voice-bot latency?
&lt;/h3&gt;

&lt;p&gt;Start with architecture rather than micro-optimizations.&lt;/p&gt;

&lt;p&gt;Stream STT, reasoning output, and TTS. Tune endpointing carefully. Avoid unnecessary audio transformations. Reduce network hops where possible. Measure time to first output for each component, and inspect tail latency rather than relying only on averages.&lt;/p&gt;

&lt;h3&gt;
  
  
  Do I need an LLM for every voice bot?
&lt;/h3&gt;

&lt;p&gt;No.&lt;/p&gt;

&lt;p&gt;A deterministic intent or rules layer can be the better choice for narrow, predictable workflows.&lt;/p&gt;

&lt;p&gt;Use an LLM when the application needs flexible language understanding, multi-turn reasoning, dynamic responses, or tool selection.&lt;/p&gt;

&lt;p&gt;Many production systems use a combination of deterministic logic and models.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is the difference between a voice bot and a voice assistant?
&lt;/h3&gt;

&lt;p&gt;The terms overlap.&lt;/p&gt;

&lt;p&gt;A voice bot usually describes a more task-specific system: customer support, scheduling, routing, qualification, or another bounded workflow.&lt;/p&gt;

&lt;p&gt;A voice assistant often implies broader, more open-ended interaction.&lt;/p&gt;

&lt;p&gt;Both can use the same underlying STT → reasoning → TTS architecture. The main difference is usually the scope of the jobs they are expected to handle.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>speechrecognition</category>
      <category>texttospeech</category>
      <category>websockets</category>
    </item>
    <item>
      <title>How to Build a Production-Ready Audio Transcription Pipeline in Python</title>
      <dc:creator>Smallest AI</dc:creator>
      <pubDate>Wed, 26 Aug 2026 07:01:53 +0000</pubDate>
      <link>https://dev.to/smallestai/how-to-build-a-production-ready-audio-transcription-pipeline-in-python-2of9</link>
      <guid>https://dev.to/smallestai/how-to-build-a-production-ready-audio-transcription-pipeline-in-python-2of9</guid>
      <description>&lt;p&gt;Transcribing an audio file from Python looks simple in a demo:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open a file.&lt;/li&gt;
&lt;li&gt;Send it to an API.&lt;/li&gt;
&lt;li&gt;Print the returned text.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Then real audio arrives.&lt;/p&gt;

&lt;p&gt;A user uploads an MP3 instead of a WAV. A phone recording is narrowband. Two speakers interrupt each other. A 90-minute recording times out halfway through. The network returns a transient error. Your application needs timestamps rather than one giant string.&lt;/p&gt;

&lt;p&gt;That is where transcription stops being an API-call problem and becomes a pipeline problem.&lt;/p&gt;

&lt;p&gt;This guide builds that pipeline step by step. We will preprocess audio, send pre-recorded files through a speech-to-text API, handle hosted audio, work with structured transcription data, add speaker diarization, split long recordings, and introduce safer retry patterns.&lt;/p&gt;

&lt;p&gt;For the implementation examples, we’ll use &lt;a href="https://smallest.ai/?utm_source=dev.to&amp;amp;utm_medium=vizup&amp;amp;utm_campaign=audio-to-text-api-how-to-convert-recorded-audio-into-accurate-transcripts-programmatically"&gt;Smallest AI&lt;/a&gt; and its &lt;a href="https://smallest.ai/speech-to-text?utm_source=dev.to&amp;amp;utm_medium=vizup&amp;amp;utm_campaign=audio-to-text-api-how-to-convert-recorded-audio-into-accurate-transcripts-programmatically"&gt;Pulse speech-to-text models&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start with the full transcription pipeline
&lt;/h2&gt;

&lt;p&gt;A production transcription workflow is more than “audio in, text out.”&lt;/p&gt;

&lt;p&gt;A useful mental model is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Receive or locate the audio.&lt;/li&gt;
&lt;li&gt;Inspect and normalize it when necessary.&lt;/li&gt;
&lt;li&gt;Choose the transcription mode and model.&lt;/li&gt;
&lt;li&gt;Authenticate the request securely.&lt;/li&gt;
&lt;li&gt;Send the audio bytes or hosted URL.&lt;/li&gt;
&lt;li&gt;Receive structured JSON.&lt;/li&gt;
&lt;li&gt;Extract transcript, timestamps, speakers, and metadata.&lt;/li&gt;
&lt;li&gt;Store or post-process the result.&lt;/li&gt;
&lt;li&gt;Handle failures, retries, duplicates, and long-running jobs.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That last part matters.&lt;/p&gt;

&lt;p&gt;The actual HTTP request may only occupy a few lines of Python. Most of the engineering work happens around it.&lt;/p&gt;

&lt;p&gt;A transcript might eventually power search, captions, meeting notes, call QA, downstream automation, analytics, or a voice application. Once that happens, timestamps, speaker boundaries, error handling, and repeatable input formats become as important as the transcript string itself.&lt;/p&gt;

&lt;p&gt;For a more API-focused look at recorded audio ingestion, the &lt;a href="https://smallest.ai/blog/audio-to-text-api-how-to-convert-recorded-audio-into-accurate-transcripts-programmatically?utm_source=dev.to&amp;amp;utm_medium=vizup&amp;amp;utm_campaign=audio-to-text-api-how-to-convert-recorded-audio-into-accurate-transcripts-programmatically"&gt;programmatic audio-to-text API workflow&lt;/a&gt; covers the broader recorded-audio pattern.&lt;/p&gt;

&lt;h2&gt;
  
  
  What “transcribe” means at the API boundary
&lt;/h2&gt;

&lt;p&gt;At the application layer, your responsibility is usually straightforward: provide valid audio and enough configuration for the transcription service to interpret it correctly.&lt;/p&gt;

&lt;p&gt;Behind that API boundary, an ASR system has to map an acoustic signal into linguistic units, decode those units into likely text, and produce a usable output representation.&lt;/p&gt;

&lt;p&gt;Depending on the system and configuration, that output can include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the complete transcript&lt;/li&gt;
&lt;li&gt;word-level timestamps&lt;/li&gt;
&lt;li&gt;utterance boundaries&lt;/li&gt;
&lt;li&gt;speaker labels&lt;/li&gt;
&lt;li&gt;language information&lt;/li&gt;
&lt;li&gt;confidence information&lt;/li&gt;
&lt;li&gt;processing metadata&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The structured fields are what make transcription useful to software.&lt;/p&gt;

&lt;p&gt;If you are building captions, for example, you need timing. If you are processing customer calls, you may need speaker labels. If transcripts trigger downstream actions, you may want to inspect confidence or other quality signals before trusting every token automatically.&lt;/p&gt;

&lt;p&gt;This also explains why testing only on clean demo audio is risky. Acoustic conditions, microphones, codecs, accents, background noise, overlapping speech, and domain-specific terminology can all change what the model receives.&lt;/p&gt;

&lt;h2&gt;
  
  
  Set up the Python environment
&lt;/h2&gt;

&lt;p&gt;Start with an isolated virtual environment:&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=audio-to-text-api-how-to-convert-recorded-audio-into-accurate-transcripts-programmatically"&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;python &lt;span class="nt"&gt;-m&lt;/span&gt; venv stt-env

&lt;span class="c"&gt;# macOS / Linux&lt;/span&gt;
&lt;span class="nb"&gt;source &lt;/span&gt;stt-env/bin/activate

&lt;span class="c"&gt;# Windows&lt;/span&gt;
stt-env&lt;span class="se"&gt;\S&lt;/span&gt;cripts&lt;span class="se"&gt;\a&lt;/span&gt;ctivate

pip &lt;span class="nb"&gt;install &lt;/span&gt;requests python-dotenv pydub tenacity
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;requests handles the HTTP calls, python-dotenv is useful during local development, pydub handles common audio preprocessing tasks, and tenacity gives us controlled retry behavior later.&lt;/p&gt;

&lt;p&gt;If you use pydub with compressed formats such as MP3, make sure FFmpeg is installed on the machine running the application.&lt;/p&gt;

&lt;h2&gt;
  
  
  Normalize unpredictable audio before transcription
&lt;/h2&gt;

&lt;p&gt;Tutorial audio is usually clean.&lt;/p&gt;

&lt;p&gt;Production audio rarely is.&lt;/p&gt;

&lt;p&gt;You may receive MP3, WAV, compressed call recordings, extracted video audio, stereo conversations, or recordings with inconsistent sample rates.&lt;/p&gt;

&lt;p&gt;For a predictable baseline, converting incoming files to mono, 16 kHz WAV is useful. Smallest AI’s current Pulse documentation recommends a 16 kHz sample rate, and converting to a known format also removes one variable when you debug failures.&lt;/p&gt;

&lt;p&gt;Do not interpret resampling as a way to recreate information that was never captured. Converting an 8 kHz telephone recording to 16 kHz does not restore frequencies lost during recording. The point is predictable input, not magic audio repair.&lt;/p&gt;

&lt;p&gt;Here is a small preprocessing function:&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=audio-to-text-api-how-to-convert-recorded-audio-into-accurate-transcripts-programmatically"&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;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;pydub&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;AudioSegment&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;preprocess_audio&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input_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;output_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="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Convert an audio file to mono, 16 kHz, 16-bit PCM WAV.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;source&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;input_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;source&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;source&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;audio&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;AudioSegment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;source&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;audio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set_channels&lt;/span&gt;&lt;span class="p"&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;audio&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;set_frame_rate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;16_000&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;audio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set_sample_width&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&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;export&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;output_path&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="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="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;output_path&lt;/span&gt;


&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;preprocess_audio&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;input_audio.mp3&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;preprocessed_audio.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 local preprocessing only, so no API credentials are involved.&lt;/p&gt;

&lt;p&gt;You also should not preprocess blindly.&lt;/p&gt;

&lt;p&gt;If your input is already in a supported, appropriate format, another lossy conversion can do more harm than good. Inspect the audio first and normalize when your pipeline actually needs it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Choose the right transcription mode
&lt;/h2&gt;

&lt;p&gt;Smallest AI currently exposes Pulse and Pulse Pro through the same pre-recorded speech-to-text endpoint.&lt;/p&gt;

&lt;p&gt;The important distinction for this workflow is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;pulse-pro is intended for pre-recorded English transcription.&lt;/li&gt;
&lt;li&gt;pulse supports multilingual transcription and is also used when you need capabilities such as audio-by-URL, streaming, or speaker diarization.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both use the unified pre-recorded endpoint:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;The model is selected through the model query parameter.&lt;/p&gt;

&lt;p&gt;For developers implementing the pipeline, the &lt;a href="https://app.smallest.ai/?utm_source=dev.to&amp;amp;utm_medium=vizup&amp;amp;utm_campaign=audio-to-text-api-how-to-convert-recorded-audio-into-accurate-transcripts-programmatically"&gt;Smallest AI speech-to-text API&lt;/a&gt; provides the programmatic entry point used by these examples.&lt;/p&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/?utm_source=dev.to&amp;amp;utm_medium=vizup&amp;amp;utm_campaign=audio-to-text-api-how-to-convert-recorded-audio-into-accurate-transcripts-programmatically"&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 Authorization 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 &amp;lt;SMALLEST_API_KEY value&amp;gt;
&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;p&gt;For production deployments, store it in a server-side secrets manager provided by your cloud or infrastructure platform rather than committing a .env file to the repository.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make the first transcription request from Python
&lt;/h2&gt;

&lt;p&gt;For a pre-recorded English file, we can send the raw file bytes using Pulse Pro.&lt;/p&gt;

&lt;p&gt;The request uses application/octet-stream and asks for word timestamps so the response can carry more structure than plain text.&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=audio-to-text-api-how-to-convert-recorded-audio-into-accurate-transcripts-programmatically"&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;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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;transcribe_audio&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="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="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Transcribe a pre-recorded English audio file with Pulse Pro.&lt;/span&gt;&lt;span class="sh"&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="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;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-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;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="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/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="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;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_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="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&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_audio&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;preprocessed_audio.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="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;transcription&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;raise_for_status() deserves to stay in even the smallest example.&lt;/p&gt;

&lt;p&gt;Without it, your code can accidentally treat an authentication failure, rate limit, or server error as if it were a successful transcription with missing data.&lt;/p&gt;

&lt;p&gt;Word timestamps also become useful surprisingly quickly. They let you build searchable audio, synchronize captions, highlight matching sections of a recording, or connect transcript spans back to the original media.&lt;/p&gt;

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

&lt;p&gt;Sometimes the file does not live on the transcription worker.&lt;/p&gt;

&lt;p&gt;It may already be stored in object storage behind a public or signed URL.&lt;/p&gt;

&lt;p&gt;For URL-based pre-recorded transcription, use Pulse and send a JSON payload containing the URL.&lt;/p&gt;

&lt;p&gt;The URL must be reachable by the transcription service. If you use private object storage, prefer a short-lived signed URL rather than making the object permanently public.&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=audio-to-text-api-how-to-convert-recorded-audio-into-accurate-transcripts-programmatically"&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="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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;transcribe_audio_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="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="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Transcribe hosted audio using the Pulse model.&lt;/span&gt;&lt;span class="sh"&gt;"""&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;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="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="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;payload&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="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;json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;payload&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice that the model choice changed.&lt;/p&gt;

&lt;p&gt;Pulse Pro accepts raw pre-recorded audio, while URL input belongs to the Pulse workflow. Treat model selection as part of the request contract rather than a cosmetic option.&lt;/p&gt;

&lt;h2&gt;
  
  
  Treat the response as structured data
&lt;/h2&gt;

&lt;p&gt;Do not throw away everything except the transcript string.&lt;/p&gt;

&lt;p&gt;For a response with word timestamps enabled, you may have timing and confidence information that can be valuable elsewhere in the product.&lt;/p&gt;

&lt;p&gt;A defensive parser should also assume optional fields can be absent:&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=audio-to-text-api-how-to-convert-recorded-audio-into-accurate-transcripts-programmatically"&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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;parse_transcript&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="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="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Print transcript text and available word metadata.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;full_text&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;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;transcription&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="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Transcript: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;full_text&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;for&lt;/span&gt; &lt;span class="n"&gt;word&lt;/span&gt; &lt;span class="ow"&gt;in&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;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="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;word&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;word&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="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;word&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;start&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="n"&gt;word&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;end&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;confidence&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;word&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;confidence&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;start_text&lt;/span&gt; &lt;span class="o"&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="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;s&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;float&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;?&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_text&lt;/span&gt; &lt;span class="o"&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="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;s&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;float&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;?&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;confidence_text&lt;/span&gt; &lt;span class="o"&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="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;confidence&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;confidence&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;float&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;n/a&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="s"&gt;[&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;start_text&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; - &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;end_text&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;] &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="sh"&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;(confidence: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;confidence_text&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&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;language&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;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;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;unknown&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="s"&gt;Language: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;language&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Confidence values are better treated as signals than universal truth.&lt;/p&gt;

&lt;p&gt;Avoid assuming that a single threshold such as 0.7 works for every model, language, microphone, or domain. If confidence will route transcripts to human review, calibrate that threshold against your own labeled audio.&lt;/p&gt;

&lt;h2&gt;
  
  
  Add speaker diarization when “who said it” matters
&lt;/h2&gt;

&lt;p&gt;For meetings, interviews, podcasts, and customer calls, plain transcription may not be enough.&lt;/p&gt;

&lt;p&gt;You also need to know which speaker produced each segment.&lt;/p&gt;

&lt;p&gt;That is speaker diarization.&lt;/p&gt;

&lt;p&gt;With the current Pulse pre-recorded API, diarization is enabled by using the Pulse model and passing:&lt;/p&gt;

&lt;p&gt;model=pulse, language=en, and diarize=true.&lt;/p&gt;

&lt;p&gt;You can combine diarization with word timestamps when you need both timing and speaker structure.&lt;/p&gt;

&lt;p&gt;The response can contain speaker information at the word and utterance levels. A local formatter can then rebuild a readable conversation:&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=audio-to-text-api-how-to-convert-recorded-audio-into-accurate-transcripts-programmatically"&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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;format_diarized_transcript&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="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="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Convert diarized utterances into readable speaker turns.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;lines&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;utterance&lt;/span&gt; &lt;span class="ow"&gt;in&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;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;utterances&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;speaker&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;utterance&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;speaker&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;unknown_speaker&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;utterance&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;text&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;strip&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;utterance&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;start&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="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;continue&lt;/span&gt;

        &lt;span class="n"&gt;start_text&lt;/span&gt; &lt;span class="o"&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="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;s&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;float&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;?&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;lines&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="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;[&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;start_text&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;] &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;speaker&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;text&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;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Diarization introduces its own failure modes.&lt;/p&gt;

&lt;p&gt;Overlapping speech is particularly difficult because two voices can occupy the same time interval. Recording separate channels upstream, when your telephony or conferencing stack makes that possible, can simplify later processing.&lt;/p&gt;

&lt;p&gt;Also remember that diarization is not the same as identity recognition. A label such as speaker_0 tells you that the segment belongs to one detected speaker; it does not automatically tell you that the person is “Alice.”&lt;/p&gt;

&lt;p&gt;If multi-speaker transcription is central to your application, the &lt;a href="https://smallest.ai/blog/the-complete-speaker-diarization-api-guide-how-it-works-and-best-practices?utm_source=dev.to&amp;amp;utm_medium=vizup&amp;amp;utm_campaign=audio-to-text-api-how-to-convert-recorded-audio-into-accurate-transcripts-programmatically"&gt;speaker diarization API guide&lt;/a&gt; goes deeper into those tradeoffs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Handle long recordings deliberately
&lt;/h2&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%2Fb08og9xvakpt7nz3p4so.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%2Fb08og9xvakpt7nz3p4so.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
Long audio creates a different reliability problem.&lt;/p&gt;

&lt;p&gt;A single large request gives you a large failure domain. If the request times out late in processing, you may have to repeat substantial work.&lt;/p&gt;

&lt;p&gt;There are two useful approaches.&lt;/p&gt;

&lt;p&gt;For long Pulse Pro transcription, an asynchronous webhook workflow can avoid holding one HTTP connection open for the entire job.&lt;/p&gt;

&lt;p&gt;Application-level chunking is another option when you want smaller independent work units.&lt;/p&gt;

&lt;p&gt;A practical chunking strategy is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;split the recording into manageable segments&lt;/li&gt;
&lt;li&gt;include a small overlap between adjacent segments&lt;/li&gt;
&lt;li&gt;transcribe segments independently&lt;/li&gt;
&lt;li&gt;preserve each segment’s original time offset&lt;/li&gt;
&lt;li&gt;reconcile duplicate words created by the overlap&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here is the local splitting step:&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=audio-to-text-api-how-to-convert-recorded-audio-into-accurate-transcripts-programmatically"&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;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;pydub&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;AudioSegment&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;split_audio_with_overlap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;input_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;output_dir&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;chunk_length_ms&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;60_000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;overlap_ms&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2_000&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;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="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Split audio into overlapping WAV chunks.&lt;/span&gt;&lt;span class="sh"&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;AudioSegment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input_path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;output&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;output_dir&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mkdir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;parents&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;exist_ok&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;chunk_paths&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="n"&gt;chunk_index&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nf"&gt;len&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="n"&gt;end&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;chunk_length_ms&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;len&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="n"&gt;chunk&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;start&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

        &lt;span class="n"&gt;chunk_path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;output&lt;/span&gt; &lt;span class="o"&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;chunk_&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;chunk_index&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;04&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;.wav&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="n"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;export&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chunk_path&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="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="n"&gt;chunk_paths&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="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chunk_path&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;end&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nf"&gt;len&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="k"&gt;break&lt;/span&gt;

        &lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;max&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="n"&gt;end&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;overlap_ms&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;chunk_index&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;chunk_paths&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The overlap protects the boundary.&lt;/p&gt;

&lt;p&gt;Without it, a word beginning near the end of one chunk and finishing at the beginning of the next can be truncated. A small overlap gives both requests enough surrounding audio to decode the boundary more reliably.&lt;/p&gt;

&lt;p&gt;But overlapping chunks also create duplicate transcript content.&lt;/p&gt;

&lt;p&gt;Do not merge them by simply concatenating strings.&lt;/p&gt;

&lt;p&gt;Track each chunk’s original start time, offset the returned timestamps accordingly, and reconcile the overlapping region when building the final transcript.&lt;/p&gt;

&lt;p&gt;For especially long files, compare chunking against asynchronous transcription before automatically deciding that one large synchronous HTTP request is the right architecture.&lt;/p&gt;

&lt;h2&gt;
  
  
  Add retry logic without retrying everything
&lt;/h2&gt;

&lt;p&gt;Production networks fail.&lt;/p&gt;

&lt;p&gt;You should expect:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;connection errors&lt;/li&gt;
&lt;li&gt;timeouts&lt;/li&gt;
&lt;li&gt;rate limits&lt;/li&gt;
&lt;li&gt;temporary upstream failures&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Retries help, but only when they are selective.&lt;/p&gt;

&lt;p&gt;Retrying an invalid request five times does not make it valid. Authentication errors and malformed payloads generally need intervention rather than exponential backoff.&lt;/p&gt;

&lt;p&gt;A better pattern is to retry connection failures, timeouts, rate limiting, and transient server responses.&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=audio-to-text-api-how-to-convert-recorded-audio-into-accurate-transcripts-programmatically"&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;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;tenacity&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;retry&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;retry_if_exception&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;stop_after_attempt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;wait_exponential&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&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;TRANSIENT_STATUS_CODES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;429&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;502&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;503&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;504&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;should_retry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;BaseException&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;bool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Timeout&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;ConnectionError&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HTTPError&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;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;
        &lt;span class="nf"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
            &lt;span class="ow"&gt;and&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;status_code&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;TRANSIENT_STATUS_CODES&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;


&lt;span class="nd"&gt;@retry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;retry&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;retry_if_exception&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;should_retry&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;wait&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;wait_exponential&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;multiplier&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="nb"&gt;min&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;max&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="n"&gt;stop&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;stop_after_attempt&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="n"&gt;reraise&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;def&lt;/span&gt; &lt;span class="nf"&gt;transcribe_with_retries&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="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="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Transcribe audio and retry only transient failures.&lt;/span&gt;&lt;span class="sh"&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="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="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;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-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;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="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/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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In a larger system, retries should also be observable.&lt;/p&gt;

&lt;p&gt;Record the request ID when the API provides one, track retry counts, distinguish permanent from transient failures, and make sure repeated jobs do not create duplicate downstream records.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prevent duplicate transcription work
&lt;/h2&gt;

&lt;p&gt;Reliability and cost control often point to the same design.&lt;/p&gt;

&lt;p&gt;If the same audio file can be submitted more than once, calculate a deterministic content hash and use it as an idempotency or cache key in your own application.&lt;/p&gt;

&lt;p&gt;For example:&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=audio-to-text-api-how-to-convert-recorded-audio-into-accurate-transcripts-programmatically"&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;hashlib&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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;sha256_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="o"&gt;-&amp;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;digest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;hashlib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sha256&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;with&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="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="nb"&gt;file&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;block&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;iter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;file&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="mi"&gt;1024&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;digest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;block&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;digest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hexdigest&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Before creating a new transcription job, check whether that hash already has a completed result.&lt;/p&gt;

&lt;p&gt;Other useful controls include checking recording duration before submission, setting application-level limits, storing completed responses durably, and separating transcription workers from the request path when jobs can take significant time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Accuracy is an application-level measurement
&lt;/h2&gt;

&lt;p&gt;Word Error Rate, or WER, is useful because it forces you to quantify transcription errors.&lt;/p&gt;

&lt;p&gt;At 5% WER, a 500-word transcript corresponds to roughly 25 word-level errors.&lt;/p&gt;

&lt;p&gt;Whether that is acceptable depends entirely on what happens next.&lt;/p&gt;

&lt;p&gt;A rough meeting summary might tolerate mistakes that would be unacceptable in a workflow where transcript content automatically updates records, triggers transactions, or feeds a compliance process.&lt;/p&gt;

&lt;p&gt;The important lesson is not to choose a universal “good” WER.&lt;/p&gt;

&lt;p&gt;Test the system on your own audio.&lt;/p&gt;

&lt;p&gt;Start with recordings from the actual environment:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the microphones users really have&lt;/li&gt;
&lt;li&gt;the codecs used in production&lt;/li&gt;
&lt;li&gt;the accents and languages you expect&lt;/li&gt;
&lt;li&gt;real background noise&lt;/li&gt;
&lt;li&gt;overlapping speakers&lt;/li&gt;
&lt;li&gt;actual product names and domain terminology&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A short representative sample can expose obvious problems during prototyping, but production evaluation should grow into a larger labeled dataset.&lt;/p&gt;

&lt;p&gt;This is also why a clean demo clip is a weak benchmark. You are not deploying the benchmark. You are deploying your own acoustic environment.&lt;/p&gt;

&lt;h2&gt;
  
  
  From prototype to production
&lt;/h2&gt;

&lt;p&gt;A dependable Python transcription pipeline usually comes back to a few engineering disciplines.&lt;/p&gt;

&lt;p&gt;Normalize inconsistent input when necessary.&lt;/p&gt;

&lt;p&gt;Treat the API response as structured data rather than one transcript string.&lt;/p&gt;

&lt;p&gt;Choose the transcription model according to the actual input and features you need.&lt;/p&gt;

&lt;p&gt;Keep credentials server-side.&lt;/p&gt;

&lt;p&gt;Handle long recordings deliberately instead of assuming one synchronous request will always succeed.&lt;/p&gt;

&lt;p&gt;Retry transient failures, not permanent ones.&lt;/p&gt;

&lt;p&gt;Cache completed work when the same recording can be submitted more than once.&lt;/p&gt;

&lt;p&gt;And above all, evaluate accuracy using the audio your application will actually receive.&lt;/p&gt;

&lt;p&gt;The first successful transcription request proves that the API works.&lt;/p&gt;

&lt;p&gt;Everything around that request determines whether your application works.&lt;/p&gt;

&lt;p&gt;If you want to test the pipeline against your own recording, &lt;a href="https://app.smallest.ai/?utm_source=dev.to&amp;amp;utm_medium=vizup&amp;amp;utm_campaign=audio-to-text-api-how-to-convert-recorded-audio-into-accurate-transcripts-programmatically"&gt;start building with the Smallest AI API&lt;/a&gt;, create an API key, and run the pre-recorded Python example with representative audio from your application.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>speechrecognition</category>
      <category>machinelearning</category>
      <category>python</category>
    </item>
    <item>
      <title>How to Tune Voice Activity Detection for Low-Latency Voice Apps</title>
      <dc:creator>Smallest AI</dc:creator>
      <pubDate>Wed, 26 Aug 2026 06:19:59 +0000</pubDate>
      <link>https://dev.to/smallestai/how-to-tune-voice-activity-detection-for-low-latency-voice-apps-1e7n</link>
      <guid>https://dev.to/smallestai/how-to-tune-voice-activity-detection-for-low-latency-voice-apps-1e7n</guid>
      <description>&lt;p&gt;When a real-time voice app feels slow or keeps misunderstanding users, it is tempting to start debugging the speech recognizer, the language model, or the network.&lt;/p&gt;

&lt;p&gt;Sometimes the problem is earlier.&lt;/p&gt;

&lt;p&gt;Before ASR processes a word, voice activity detection (VAD) has already decided whether the incoming audio is worth sending downstream.&lt;/p&gt;

&lt;p&gt;A good VAD configuration makes the pipeline feel responsive. A bad one can send keyboard noise into transcription, clip the first word of an utterance, delay turn-taking, or make an agent respond to its own audio.&lt;/p&gt;

&lt;p&gt;That makes VAD less of a preprocessing checkbox and more of a load-bearing part of real-time voice architecture.&lt;/p&gt;

&lt;p&gt;The broader &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;Smallest AI&lt;/a&gt; stack covers real-time speech models and voice applications, but this article focuses specifically on the boundary before transcription: how to decide when speech begins, when it stops, and how aggressively the pipeline should react.&lt;/p&gt;

&lt;h2&gt;
  
  
  What VAD actually controls
&lt;/h2&gt;

&lt;p&gt;At its simplest, VAD repeatedly answers one question:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is this frame speech or non-speech?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Most implementations process audio in short windows, commonly around 10 to 30 milliseconds.&lt;/p&gt;

&lt;p&gt;Those frame-level decisions then control the rest of the system:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Start sending audio to ASR.&lt;/li&gt;
&lt;li&gt;Continue an active speech segment.&lt;/li&gt;
&lt;li&gt;Hold during a brief pause.&lt;/li&gt;
&lt;li&gt;Flush buffered audio.&lt;/li&gt;
&lt;li&gt;Stop the stream or begin endpointing logic.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In VoIP systems, VAD can avoid sending long stretches of silence.&lt;/p&gt;

&lt;p&gt;In transcription systems, it determines which audio reaches ASR.&lt;/p&gt;

&lt;p&gt;In conversational voice agents, VAD becomes one of the signals used to decide whether the user has started or stopped talking.&lt;/p&gt;

&lt;p&gt;The difficult part is that production audio rarely looks like a clean speech dataset.&lt;/p&gt;

&lt;p&gt;Your microphone may also capture HVAC noise, typing, traffic, music, another speaker, breathing, lip noise, or audio from the agent itself.&lt;/p&gt;

&lt;p&gt;VAD has to make its decision immediately, without knowing what the next few hundred milliseconds will contain.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frame size is part of your latency budget
&lt;/h2&gt;

&lt;p&gt;Frame size is one of the first VAD parameters worth examining.&lt;/p&gt;

&lt;p&gt;Short frames, such as 10 ms windows, let the system detect speech onset quickly. That can reduce the delay before ASR begins receiving useful audio.&lt;/p&gt;

&lt;p&gt;The trade-off is context.&lt;/p&gt;

&lt;p&gt;With less audio inside each decision window, short frames may be easier to misclassify in difficult acoustic environments.&lt;/p&gt;

&lt;p&gt;Longer frames, such as 30 ms windows, contain more information and can make classification more stable. But they also delay the first speech decision.&lt;/p&gt;

&lt;p&gt;Thirty milliseconds does not sound significant in isolation.&lt;/p&gt;

&lt;p&gt;It becomes significant when you add it to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Audio capture and buffering&lt;/li&gt;
&lt;li&gt;Network transport&lt;/li&gt;
&lt;li&gt;Speech recognition&lt;/li&gt;
&lt;li&gt;Endpoint detection&lt;/li&gt;
&lt;li&gt;LLM inference&lt;/li&gt;
&lt;li&gt;Text-to-speech generation&lt;/li&gt;
&lt;li&gt;Playback buffering&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Voice engineering guidance has traditionally treated roughly 150 ms of one-way delay as an important quality boundary for highly interactive communication, while normal human turn-taking can occur on the order of a few hundred milliseconds.&lt;/p&gt;

&lt;p&gt;In that environment, several small delays can consume a meaningful percentage of the entire interaction budget.&lt;/p&gt;

&lt;p&gt;That does &lt;strong&gt;not&lt;/strong&gt; mean every system should use 10 ms frames.&lt;/p&gt;

&lt;p&gt;A controlled call-center deployment with standardized headsets has a very different noise profile from a mobile application being used in kitchens, cars, cafés, and sidewalks.&lt;/p&gt;

&lt;p&gt;Choose frame size against the audio your application actually receives.&lt;/p&gt;

&lt;h2&gt;
  
  
  Classical VAD vs. neural VAD
&lt;/h2&gt;

&lt;p&gt;The right VAD architecture depends heavily on compute constraints and acoustic conditions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Classical VAD
&lt;/h3&gt;

&lt;p&gt;Classical detectors remain useful because they are inexpensive and predictable.&lt;/p&gt;

&lt;p&gt;The WebRTC VAD implementation, for example, uses Gaussian Mixture Models to compare speech and background-noise probabilities from audio features. Its implementation supports frame durations such as 10, 20, and 30 ms.&lt;/p&gt;

&lt;p&gt;You can inspect the &lt;a href="https://webrtc.googlesource.com/src/+/refs/heads/main/common_audio/vad/vad_core.c" rel="noopener noreferrer"&gt;WebRTC VAD implementation&lt;/a&gt; directly if you want to understand the statistical decision path.&lt;/p&gt;

&lt;p&gt;This kind of detector makes sense when you need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Low compute overhead&lt;/li&gt;
&lt;li&gt;On-device execution&lt;/li&gt;
&lt;li&gt;Predictable processing time&lt;/li&gt;
&lt;li&gt;Controlled acoustic environments&lt;/li&gt;
&lt;li&gt;No dependency on GPU inference&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Neural VAD
&lt;/h3&gt;

&lt;p&gt;Neural VAD usually uses a compact learned model rather than relying only on hand-designed spectral rules.&lt;/p&gt;

&lt;p&gt;That can make it more robust when background noise changes over time.&lt;/p&gt;

&lt;p&gt;Music, television, overlapping speakers, traffic, and other non-stationary noise can be particularly difficult for simpler statistical detectors.&lt;/p&gt;

&lt;p&gt;The trade-off is inference cost.&lt;/p&gt;

&lt;p&gt;For server-side pipelines that already have sufficient compute, that cost may be acceptable. On constrained devices or latency-sensitive edge deployments, it may not be.&lt;/p&gt;

&lt;h3&gt;
  
  
  Hybrid VAD
&lt;/h3&gt;

&lt;p&gt;You do not necessarily have to choose one detector for every frame.&lt;/p&gt;

&lt;p&gt;A production system can use a lightweight detector as the first gate and invoke a more expensive model only for uncertain or difficult segments.&lt;/p&gt;

&lt;p&gt;The objective is not architectural purity.&lt;/p&gt;

&lt;p&gt;The objective is to avoid spending expensive inference on obvious silence while still handling noisy edge cases reliably.&lt;/p&gt;

&lt;h2&gt;
  
  
  VAD and endpointing solve different problems
&lt;/h2&gt;

&lt;p&gt;A common implementation mistake is treating VAD and endpointing as interchangeable.&lt;/p&gt;

&lt;p&gt;They are not.&lt;/p&gt;

&lt;p&gt;VAD answers:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is the user producing speech right now?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Endpointing answers:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Has the user finished their turn?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Those are different questions.&lt;/p&gt;

&lt;p&gt;Consider this sentence:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"Can you book a meeting with... Sarah tomorrow?"&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A speaker may naturally pause after "with" while remembering the name.&lt;/p&gt;

&lt;p&gt;During that pause, VAD may correctly classify several frames as non-speech.&lt;/p&gt;

&lt;p&gt;That does not mean the conversational turn has ended.&lt;/p&gt;

&lt;p&gt;A basic endpointing system might use a rule such as:&lt;/p&gt;

&lt;p&gt;800 ms of silence → end of turn&lt;/p&gt;

&lt;p&gt;That is easy to implement, but it can cause interruptions when users pause to think.&lt;/p&gt;

&lt;p&gt;More sophisticated endpointing systems can combine VAD state with additional information such as partial transcription, semantic completeness, or dedicated turn-taking models.&lt;/p&gt;

&lt;p&gt;VAD remains a low-level signal.&lt;/p&gt;

&lt;p&gt;Endpointing turns that signal into a conversational decision.&lt;/p&gt;

&lt;p&gt;If you are budgeting latency across the entire conversational pipeline, the distinction matters. Smallest AI's 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=designing-voice-assistants-stt-llm-tts-tools-and-latency-budget"&gt;STT, LLM, TTS, tools, and latency budgeting&lt;/a&gt; looks at how those delays accumulate across the broader voice-agent stack.&lt;/p&gt;

&lt;h2&gt;
  
  
  False triggers are usually where production VAD hurts
&lt;/h2&gt;

&lt;p&gt;VAD errors broadly fall into two categories.&lt;/p&gt;

&lt;h3&gt;
  
  
  False positives
&lt;/h3&gt;

&lt;p&gt;A false positive happens when non-speech audio is classified as speech.&lt;/p&gt;

&lt;p&gt;That can push unnecessary audio into ASR.&lt;/p&gt;

&lt;p&gt;Sometimes the result is harmless gibberish.&lt;/p&gt;

&lt;p&gt;A more dangerous failure happens when the recognizer produces plausible text from noise. Downstream components may then treat something that never happened as a real user utterance.&lt;/p&gt;

&lt;p&gt;For a conversational agent, a cough, television voice, keyboard sound, or door slam should not become an actionable request.&lt;/p&gt;

&lt;h3&gt;
  
  
  False negatives
&lt;/h3&gt;

&lt;p&gt;False negatives happen when actual speech is classified as non-speech.&lt;/p&gt;

&lt;p&gt;The most noticeable version is a clipped speech onset.&lt;/p&gt;

&lt;p&gt;If VAD opens the gate too late, the downstream recognizer may receive:&lt;/p&gt;

&lt;p&gt;...eed to change my booking&lt;/p&gt;

&lt;p&gt;instead of:&lt;/p&gt;

&lt;p&gt;I need to change my booking&lt;/p&gt;

&lt;p&gt;Users notice this immediately.&lt;/p&gt;

&lt;p&gt;They start repeating words, speaking unnaturally, or assuming the application is not listening.&lt;/p&gt;

&lt;p&gt;In production traffic, several inputs frequently cause trouble:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Non-stationary noise:&lt;/strong&gt; music, television, traffic, and overlapping speakers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Breath and lip sounds:&lt;/strong&gt; these can resemble speech-like acoustic events.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hesitation sounds:&lt;/strong&gt; "um," "uh," and other short, quiet speech can disappear when onset thresholds are too conservative.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Playback leakage:&lt;/strong&gt; an agent's own TTS may reach the microphone and trigger the detector.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Threshold-edge audio:&lt;/strong&gt; frames hovering near the activation threshold can cause rapid speech/non-speech switching.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The solution is not simply "increase the threshold."&lt;/p&gt;

&lt;p&gt;Every adjustment changes which class of errors you are accepting.&lt;/p&gt;

&lt;h2&gt;
  
  
  Practical VAD tuning in a real pipeline
&lt;/h2&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%2F706w6saqx2x1yeny2wl5.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%2F706w6saqx2x1yeny2wl5.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There is no universal production threshold.&lt;/p&gt;

&lt;p&gt;Tune against the failure distribution of your application.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;Problem&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Likely cause&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;What to adjust&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;First word gets clipped&lt;/td&gt;
&lt;td&gt;Activation threshold too high or no leading buffer&lt;/td&gt;
&lt;td&gt;Lower the threshold and add pre-roll&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Agent responds too late&lt;/td&gt;
&lt;td&gt;Trailing padding or endpoint window is too long&lt;/td&gt;
&lt;td&gt;Reduce the trailing-silence window&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Background noise triggers ASR&lt;/td&gt;
&lt;td&gt;Threshold too low or weak upstream cleanup&lt;/td&gt;
&lt;td&gt;Increase the threshold or improve noise suppression&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;VAD rapidly flips states&lt;/td&gt;
&lt;td&gt;Signal is hovering near the boundary&lt;/td&gt;
&lt;td&gt;Add hysteresis smoothing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Agent detects its own TTS&lt;/td&gt;
&lt;td&gt;Playback is leaking into the microphone&lt;/td&gt;
&lt;td&gt;Add echo cancellation and playback-specific VAD behavior&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Log the decisions, not just the transcripts
&lt;/h3&gt;

&lt;p&gt;If possible, capture VAD state alongside a representative sample of audio.&lt;/p&gt;

&lt;p&gt;You want to know:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Which frames triggered speech?&lt;/li&gt;
&lt;li&gt;What did the audio actually contain?&lt;/li&gt;
&lt;li&gt;How often did speech onset get clipped?&lt;/li&gt;
&lt;li&gt;Which noise types produced false positives?&lt;/li&gt;
&lt;li&gt;How long did the system wait before closing an utterance?&lt;/li&gt;
&lt;li&gt;Were failures concentrated on specific devices or environments?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Synthetic noise tests are useful for regression.&lt;/p&gt;

&lt;p&gt;They are not a substitute for observing the acoustic environments your users actually create.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tune activation threshold and padding together
&lt;/h3&gt;

&lt;p&gt;Two settings tend to dominate day-to-day tuning:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Activation threshold&lt;/strong&gt; controls how confident the detector must be before a frame becomes speech.&lt;/p&gt;

&lt;p&gt;Lowering it improves sensitivity but usually increases false positives.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Padding&lt;/strong&gt; controls how much audio remains part of the active segment around speech boundaries.&lt;/p&gt;

&lt;p&gt;On the trailing edge, a practical conversational starting point is often around &lt;strong&gt;200–300 ms&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That is long enough to absorb many natural micro-pauses without leaving the pipeline open indefinitely.&lt;/p&gt;

&lt;p&gt;It is a baseline, not a universal default.&lt;/p&gt;

&lt;p&gt;Your application still needs measurement.&lt;/p&gt;

&lt;p&gt;On the leading edge, sensitivity often deserves extra weight because a small pre-roll buffer is cheaper than losing the beginning of a user's sentence.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why hysteresis matters
&lt;/h2&gt;

&lt;p&gt;Suppose your detector produces confidence values close to a threshold:&lt;/p&gt;

&lt;p&gt;0.48, 0.52, 0.49, 0.53, 0.47&lt;/p&gt;

&lt;p&gt;With a hard threshold at 0.50, the VAD state may repeatedly switch:&lt;/p&gt;

&lt;p&gt;off → on → off → on → off&lt;/p&gt;

&lt;p&gt;That state chatter is difficult for downstream components.&lt;/p&gt;

&lt;p&gt;Hysteresis introduces stability.&lt;/p&gt;

&lt;p&gt;Instead of using exactly the same transition rule in both directions, require sustained evidence before changing states.&lt;/p&gt;

&lt;p&gt;For example, entering speech may require several qualifying frames, while leaving speech may require a longer sequence below the deactivation boundary.&lt;/p&gt;

&lt;p&gt;The exact values depend on your detector, but the principle is broadly useful:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A frame-level classifier does not have to become a frame-level state transition.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Barge-in changes the operating conditions
&lt;/h2&gt;

&lt;p&gt;Voice agents introduce a problem that pure transcription systems often avoid.&lt;/p&gt;

&lt;p&gt;The system may be speaking while the user starts talking.&lt;/p&gt;

&lt;p&gt;Now the microphone contains both:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The user's interruption&lt;/li&gt;
&lt;li&gt;The agent's synthesized voice&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is the barge-in problem.&lt;/p&gt;

&lt;p&gt;Acoustic echo cancellation is one of the first defenses because it reduces playback leakage before VAD evaluates the microphone signal.&lt;/p&gt;

&lt;p&gt;But echo cancellation alone does not eliminate the tuning problem.&lt;/p&gt;

&lt;p&gt;Your acoustic conditions during playback are different from your acoustic conditions during silence.&lt;/p&gt;

&lt;p&gt;That means the same VAD sensitivity may not be optimal in both states.&lt;/p&gt;

&lt;p&gt;If the playback-mode threshold is too high, real interruptions get missed.&lt;/p&gt;

&lt;p&gt;If it is too low, the system detects its own TTS and interrupts itself.&lt;/p&gt;

&lt;p&gt;Treat playback and non-playback as distinct operating modes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Multi-microphone systems can improve the input before VAD
&lt;/h2&gt;

&lt;p&gt;When multiple microphone channels are available, spatial processing can improve what the detector sees.&lt;/p&gt;

&lt;p&gt;Beamforming attempts to emphasize sound arriving from a target direction while suppressing other sources.&lt;/p&gt;

&lt;p&gt;That improves signal-to-noise ratio before classification.&lt;/p&gt;

&lt;p&gt;A cleaner input can reduce both false positives and false negatives without changing the VAD model itself.&lt;/p&gt;

&lt;p&gt;This illustrates a broader point:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Not every VAD problem should be solved inside VAD.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Sometimes the correct fix is upstream.&lt;/p&gt;

&lt;p&gt;Noise suppression, acoustic echo cancellation, beamforming, microphone placement, gain control, and device-specific audio processing can all change the detector's error rate.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where VAD belongs in the voice stack
&lt;/h2&gt;

&lt;p&gt;A simplified real-time speech pipeline might look like this:&lt;/p&gt;

&lt;p&gt;microphone → echo cancellation → noise suppression → VAD → ASR → endpointing/application logic&lt;/p&gt;

&lt;p&gt;The exact order depends on the system, but placement matters.&lt;/p&gt;

&lt;p&gt;Running noise suppression before VAD gives the detector a cleaner signal.&lt;/p&gt;

&lt;p&gt;Running VAD directly on raw audio means its threshold must tolerate every acoustic artifact that reaches the microphone.&lt;/p&gt;

&lt;p&gt;The same rule applies downstream.&lt;/p&gt;

&lt;p&gt;If VAD discards a frame containing speech, ASR cannot reconstruct audio it never received.&lt;/p&gt;

&lt;p&gt;If VAD sends noise into ASR, the recognizer has to decide what to do with audio that should have been filtered earlier.&lt;/p&gt;

&lt;p&gt;For a hosted downstream STT layer, &lt;a href="https://smallest.ai/speech-to-text?utm_source=dev.to&amp;amp;utm_medium=vizup&amp;amp;utm_campaign=designing-voice-assistants-stt-llm-tts-tools-and-latency-budget"&gt;Pulse speech-to-text&lt;/a&gt; is Smallest AI's product for real-time and recorded transcription, including live-audio and voice-agent workloads.&lt;/p&gt;

&lt;p&gt;That does not make the STT model a replacement for your VAD design.&lt;/p&gt;

&lt;p&gt;It makes the VAD boundary easier to reason about: your recognizer can only process the audio your preprocessing layer decides to pass downstream.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing VAD against a real speech pipeline
&lt;/h2&gt;

&lt;p&gt;A useful VAD evaluation should not stop at frame-level accuracy.&lt;/p&gt;

&lt;p&gt;Measure what happens to the rest of the application.&lt;/p&gt;

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

&lt;ol&gt;
&lt;li&gt;Run representative audio through your VAD configuration.&lt;/li&gt;
&lt;li&gt;Preserve the accepted audio exactly as the recognizer would receive it.&lt;/li&gt;
&lt;li&gt;Send that audio through your STT layer.&lt;/li&gt;
&lt;li&gt;Compare transcripts across threshold and padding configurations.&lt;/li&gt;
&lt;li&gt;Measure speech-onset clipping.&lt;/li&gt;
&lt;li&gt;Measure false ASR activations caused by noise.&lt;/li&gt;
&lt;li&gt;Measure how VAD and trailing padding affect end-to-end response time.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This tells you something a standalone VAD score cannot:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;whether the detector's mistakes actually damage the product.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Developers who want to test the downstream transcription side can use the &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; as the speech layer in this type of evaluation.&lt;/p&gt;

&lt;p&gt;The VAD itself can remain in your client, media server, or preprocessing service. The API then gives you a consistent downstream component against which you can evaluate how different gating decisions affect transcription.&lt;/p&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/?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 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 Authorization 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 &amp;lt;SMALLEST_API_KEY value&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Keep the key on your server.&lt;/p&gt;

&lt;p&gt;Do not expose it in browser JavaScript, client-side React code, mobile application code, public repositories, screenshots, query parameters, or client-side logs.&lt;/p&gt;

&lt;p&gt;For production deployments, store it in your hosting environment's server-side secrets manager rather than committing credentials to configuration files.&lt;/p&gt;

&lt;p&gt;This article intentionally does not invent an API request specifically for VAD because the VAD architecture described here is an upstream pipeline concern rather than a Smallest AI-specific VAD endpoint.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to measure before shipping
&lt;/h2&gt;

&lt;p&gt;If VAD is going into production, evaluate it as a system component rather than a classifier in isolation.&lt;/p&gt;

&lt;p&gt;Useful measurements include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Speech-start detection delay&lt;/li&gt;
&lt;li&gt;Speech-end detection delay&lt;/li&gt;
&lt;li&gt;False-positive rate by noise category&lt;/li&gt;
&lt;li&gt;False-negative rate at utterance onset&lt;/li&gt;
&lt;li&gt;Percentage of utterances with clipped first words&lt;/li&gt;
&lt;li&gt;Number of unnecessary ASR activations&lt;/li&gt;
&lt;li&gt;Endpointing delay after actual user completion&lt;/li&gt;
&lt;li&gt;Barge-in success during TTS playback&lt;/li&gt;
&lt;li&gt;Behavior across microphones, devices, and environments&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The correct configuration depends on the cost of each failure.&lt;/p&gt;

&lt;p&gt;A dictation application may tolerate a slightly slower onset if it reduces false activations.&lt;/p&gt;

&lt;p&gt;A voice agent may prefer a more sensitive leading edge because clipped words damage conversational flow immediately.&lt;/p&gt;

&lt;p&gt;Production tuning is about choosing those trade-offs deliberately.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;VAD is an upstream latency and quality control point, not just a silence detector.&lt;/li&gt;
&lt;li&gt;Frame size affects how quickly speech can be detected and how much acoustic context the classifier receives.&lt;/li&gt;
&lt;li&gt;False positives waste downstream work; false negatives can remove speech permanently.&lt;/li&gt;
&lt;li&gt;VAD and endpointing solve different problems.&lt;/li&gt;
&lt;li&gt;Trailing padding directly affects how quickly a conversational system can respond.&lt;/li&gt;
&lt;li&gt;Around 200–300 ms of trailing padding is a reasonable starting point for many conversational systems, but it should be validated against real traffic.&lt;/li&gt;
&lt;li&gt;Hysteresis can stabilize frame-level decisions without replacing the underlying detector.&lt;/li&gt;
&lt;li&gt;Barge-in requires echo handling and often different VAD behavior during playback.&lt;/li&gt;
&lt;li&gt;Beamforming and noise suppression can improve VAD performance before you touch the detector itself.&lt;/li&gt;
&lt;li&gt;The right threshold is the one that minimizes the errors that matter most to your application.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Voice activity detection is easy to describe because the output looks binary.&lt;/p&gt;

&lt;p&gt;Production behavior is not.&lt;/p&gt;

&lt;p&gt;Every threshold, frame size, pre-roll buffer, silence window, and state-transition rule changes what the rest of your voice pipeline receives and when it receives it.&lt;/p&gt;

&lt;p&gt;That is why VAD tuning should be evaluated alongside ASR and endpointing rather than as an isolated preprocessing benchmark.&lt;/p&gt;

&lt;p&gt;Measure with real audio. Log the boundaries. Inspect false triggers. Watch for clipped onsets. Test again while TTS is playing.&lt;/p&gt;

&lt;p&gt;Then optimize the failure mode that actually damages your application.&lt;/p&gt;

&lt;p&gt;If you want to evaluate how those VAD decisions affect downstream transcription, &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;create an API key and test the pipeline with your own audio using Smallest AI&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>voiceai</category>
      <category>speechrecognition</category>
      <category>ai</category>
      <category>machinelearning</category>
    </item>
    <item>
      <title>Building an AI Dubbing Pipeline That Survives Production: STT Translation TTS</title>
      <dc:creator>Smallest AI</dc:creator>
      <pubDate>Wed, 26 Aug 2026 05:42:43 +0000</pubDate>
      <link>https://dev.to/smallestai/building-an-ai-dubbing-pipeline-that-survives-production-stt-translation-tts-4jfh</link>
      <guid>https://dev.to/smallestai/building-an-ai-dubbing-pipeline-that-survives-production-stt-translation-tts-4jfh</guid>
      <description>&lt;p&gt;&lt;strong&gt;AI dubbing looks simple on a whiteboard:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;speech → text → translation → speech&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That description is technically correct. It is also where most of the engineering details disappear.&lt;/p&gt;

&lt;p&gt;Once you move beyond a single-speaker demo, every stage starts depending on metadata produced by the stage before it. A transcription error becomes a translation error. A missing speaker label assigns the wrong synthetic voice. A translated sentence that runs 30% longer than the source pushes the next line out of sync.&lt;/p&gt;

&lt;p&gt;The result is a pipeline where failures rarely stay local.&lt;/p&gt;

&lt;p&gt;For developers building their own stack, the useful mental model is not “connect three APIs.” It preserves &lt;strong&gt;enough information between those APIs that the final audio still matches the original performance&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Platforms such as YouTube and Prime Video have already expanded automated or AI-assisted dubbing workflows, but building a reliable version yourself still means solving several production problems around transcription, translation, timing, voice synthesis, and review.&lt;/p&gt;

&lt;p&gt;This article walks through that architecture from ingestion to final audio assembly.&lt;/p&gt;

&lt;h2&gt;
  
  
  What an AI dubbing pipeline actually does
&lt;/h2&gt;

&lt;p&gt;AI dubbing replaces speech in one language with synthesized speech in another.&lt;/p&gt;

&lt;p&gt;At the center are three stages:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Speech-to-Text (STT)&lt;/strong&gt; converts the source audio into structured text.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Translation&lt;/strong&gt; converts that text into the target language.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Text-to-Speech (TTS)&lt;/strong&gt; renders the translation back into audio.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;But production dubbing asks those stages to preserve more than words.&lt;/p&gt;

&lt;p&gt;You also need to carry:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;speaker identity&lt;/li&gt;
&lt;li&gt;word and segment timestamps&lt;/li&gt;
&lt;li&gt;emotional intent&lt;/li&gt;
&lt;li&gt;speaking style&lt;/li&gt;
&lt;li&gt;terminology&lt;/li&gt;
&lt;li&gt;target duration&lt;/li&gt;
&lt;li&gt;confidence and QA status&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Without that metadata, you can generate translated speech but not necessarily a usable dub.&lt;/p&gt;

&lt;p&gt;This is why a production pipeline usually looks more like:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;audio preprocessing → STT → diarization → translation → timing validation → human QA → TTS → alignment → audio post-processing&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you’re exploring speech infrastructure for this kind of workflow, &lt;a href="https://smallest.ai/?utm_source=dev.to&amp;amp;utm_medium=vizup&amp;amp;utm_campaign=ai-dubbing-pipelines-localizing-video-audio-with-translation-timing-and-tts"&gt;Smallest AI&lt;/a&gt; exposes the speech components independently, which is useful when you want to control these stages yourself rather than treat dubbing as one opaque operation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stage 1: Speech-to-Text sets the quality ceiling
&lt;/h2&gt;

&lt;p&gt;A dubbing pipeline can rarely recover cleanly from a bad transcript.&lt;/p&gt;

&lt;p&gt;Suppose an STT model mishears a company name or technical term. The translation system receives the incorrect word, translates it fluently, and the TTS model says the mistake naturally.&lt;/p&gt;

&lt;p&gt;By the time somebody hears the final audio, the original transcription error has been polished by two more models.&lt;/p&gt;

&lt;p&gt;That makes STT quality an upstream constraint on everything that follows.&lt;/p&gt;

&lt;p&gt;For dubbing, plain transcript text is not enough. A useful STT result should ideally include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;word-level timestamps&lt;/li&gt;
&lt;li&gt;speaker labels&lt;/li&gt;
&lt;li&gt;sentence or utterance boundaries&lt;/li&gt;
&lt;li&gt;confidence information&lt;/li&gt;
&lt;li&gt;punctuation&lt;/li&gt;
&lt;li&gt;domain-specific vocabulary handling&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Why word timestamps matter
&lt;/h3&gt;

&lt;p&gt;Imagine the source contains this line:&lt;/p&gt;

&lt;p&gt;“Hello, welcome to the new headquarters.”&lt;/p&gt;

&lt;p&gt;The sentence begins at 0.52 seconds and ends at 3.10 seconds.&lt;/p&gt;

&lt;p&gt;That gives the downstream system roughly &lt;strong&gt;2.58 seconds&lt;/strong&gt; for the translated version.&lt;/p&gt;

&lt;p&gt;Without timestamps, the translation and TTS layers have no reliable timing window to target.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why diarization matters
&lt;/h3&gt;

&lt;p&gt;Multi-speaker content creates another problem.&lt;/p&gt;

&lt;p&gt;Interviews, podcasts, panel discussions, films, and training videos all require the system to know &lt;strong&gt;who said what&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;If the STT layer outputs one continuous transcript, the synthesis layer cannot reliably determine which voice should speak each translated segment.&lt;/p&gt;

&lt;p&gt;For multi-speaker implementations, treat &lt;a href="https://smallest.ai/blog/the-complete-speaker-diarization-api-guide-how-it-works-and-best-practices?utm_source=dev.to&amp;amp;utm_medium=vizup&amp;amp;utm_campaign=ai-dubbing-pipelines-localizing-video-audio-with-translation-timing-and-tts"&gt;speaker diarization&lt;/a&gt; as part of the transcription architecture, not an optional post-processing feature.&lt;/p&gt;

&lt;p&gt;Smallest AI’s &lt;a href="https://smallest.ai/speech-to-text?utm_source=dev.to&amp;amp;utm_medium=vizup&amp;amp;utm_campaign=ai-dubbing-pipelines-localizing-video-audio-with-translation-timing-and-tts"&gt;Pulse speech-to-text&lt;/a&gt; supports streaming and pre-recorded transcription, including features such as speaker diarization and word timestamps. Whatever STT system you choose, benchmark it using the actual audio your application will process.&lt;/p&gt;

&lt;p&gt;Podcast audio, accented speech, overlapping speakers, film dialogue, and noisy field recordings can behave very differently.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stage 2: Translation has to fit speech, not a document
&lt;/h2&gt;

&lt;p&gt;Translation APIs make converting a sentence from one language to another relatively straightforward.&lt;/p&gt;

&lt;p&gt;Dubbing introduces a different requirement:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The translated sentence has to be speakable inside approximately the same time window as the original.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That changes how the translation layer should be designed.&lt;/p&gt;

&lt;p&gt;A translated sentence can preserve meaning perfectly and still fail the dubbing workflow because it takes too long to say.&lt;/p&gt;

&lt;p&gt;Research into professional dubbing also shows that naturalness, translation quality, timing, and preservation of speech characteristics interact in more complicated ways than simply forcing equal character counts. The large-scale study &lt;a href="https://aclanthology.org/2023.tacl-1.25/" rel="noopener noreferrer"&gt;Dubbing in Practice&lt;/a&gt; is a useful reference for understanding those tradeoffs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Translate segments, not entire transcripts
&lt;/h3&gt;

&lt;p&gt;If the STT result already contains timed segments, preserve them.&lt;/p&gt;

&lt;p&gt;Instead of sending a complete 20-minute transcript through translation and trying to reconstruct alignment afterward, translate individual utterances or tightly grouped segments.&lt;/p&gt;

&lt;p&gt;For example, your internal pipeline might normalize STT output into a structure like this: (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=ai-dubbing-pipelines-localizing-video-audio-with-translation-timing-and-tts"&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 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;"segments"&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;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"speaker"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"A"&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.52&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.10&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, welcome to the new headquarters."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"confidence"&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.98&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;This is an internal pipeline representation, not a provider-specific API request.&lt;/p&gt;

&lt;p&gt;Keeping the segment boundaries intact gives every later stage a stable ID, speaker, and timing window.&lt;/p&gt;

&lt;h3&gt;
  
  
  Add a length-validation layer
&lt;/h3&gt;

&lt;p&gt;After translation, compare the target-language output with the available source duration.&lt;/p&gt;

&lt;p&gt;A simple first-pass implementation can estimate spoken duration using historical speaking-rate data or a language-specific character/token heuristic.&lt;/p&gt;

&lt;p&gt;Then flag segments that exceed your tolerance.&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;source_duration = 2.58s
estimated_translation_duration = 3.21s
difference = +24.4%
status = REVIEW
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The exact threshold should come from testing your content rather than being treated as universal. A system might initially flag anything more than 10–15% outside its target window, then tune that rule based on actual listening results.&lt;/p&gt;

&lt;p&gt;The important point is that timing problems should be detected &lt;strong&gt;before TTS generation&lt;/strong&gt;, not after you have rendered hundreds of unusable clips.&lt;/p&gt;

&lt;h3&gt;
  
  
  Preserve register and context
&lt;/h3&gt;

&lt;p&gt;Dubbing also needs more than literal translation.&lt;/p&gt;

&lt;p&gt;A production translation layer should preserve:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;formality&lt;/li&gt;
&lt;li&gt;slang&lt;/li&gt;
&lt;li&gt;technical terminology&lt;/li&gt;
&lt;li&gt;character relationships&lt;/li&gt;
&lt;li&gt;idioms&lt;/li&gt;
&lt;li&gt;proper nouns&lt;/li&gt;
&lt;li&gt;brand terminology&lt;/li&gt;
&lt;li&gt;emotional intent&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A casual speaker should not suddenly sound formal simply because the translation model selected a grammatically valid but stylistically inappropriate phrase.&lt;/p&gt;

&lt;p&gt;For technical, educational, or branded content, terminology enforcement is especially important. A glossary or controlled vocabulary can prevent the translation system from rewriting names and domain-specific terms differently from one segment to the next.&lt;/p&gt;

&lt;h3&gt;
  
  
  Human QA still matters
&lt;/h3&gt;

&lt;p&gt;Treat machine translation as draft dialogue.&lt;/p&gt;

&lt;p&gt;Certain outputs should be routed for review automatically, especially when they contain:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;low-confidence source transcription&lt;/li&gt;
&lt;li&gt;idioms&lt;/li&gt;
&lt;li&gt;ambiguous names&lt;/li&gt;
&lt;li&gt;unusually large length differences&lt;/li&gt;
&lt;li&gt;culturally specific references&lt;/li&gt;
&lt;li&gt;terminology that must remain consistent&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The goal is not to manually review every generated word forever. It is to make the pipeline capable of recognizing when automation has lower confidence.&lt;/p&gt;

&lt;p&gt;For a broader look at these localization tradeoffs, Smallest AI’s guide to &lt;a href="https://smallest.ai/blog/ai-dubbing-pipelines-localizing-video-audio-with-translation-timing-and-tts?utm_source=dev.to&amp;amp;utm_medium=vizup&amp;amp;utm_campaign=ai-dubbing-pipelines-localizing-video-audio-with-translation-timing-and-tts"&gt;AI dubbing pipelines for translation, timing, and TTS&lt;/a&gt; covers the same problem from a localization perspective.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stage 3: TTS has to solve voice and timing together
&lt;/h2&gt;

&lt;p&gt;Once the translated dialogue has passed validation, TTS turns it back into speech.&lt;/p&gt;

&lt;p&gt;Generating audio is not the difficult part.&lt;/p&gt;

&lt;p&gt;Generating audio that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;sounds like the intended speaker&lt;/li&gt;
&lt;li&gt;preserves emotional intent&lt;/li&gt;
&lt;li&gt;fits the source timing window&lt;/li&gt;
&lt;li&gt;stays consistent across hundreds of segments&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;is much harder.&lt;/p&gt;

&lt;h3&gt;
  
  
  Voice cloning changes speaker consistency
&lt;/h3&gt;

&lt;p&gt;A basic dubbing system can assign a preset voice to each speaker.&lt;/p&gt;

&lt;p&gt;A more advanced pipeline can create a voice representation from the source speaker and reuse it across translated segments.&lt;/p&gt;

&lt;p&gt;This matters because speaker identity is part of the original content.&lt;/p&gt;

&lt;p&gt;If someone appears throughout a 30-minute video, the translated version should not sound like three different people because different chunks were synthesized independently.&lt;/p&gt;

&lt;p&gt;Current &lt;a href="https://smallest.ai/text-to-speech?utm_source=dev.to&amp;amp;utm_medium=vizup&amp;amp;utm_campaign=ai-dubbing-pipelines-localizing-video-audio-with-translation-timing-and-tts"&gt;Lightning text-to-speech&lt;/a&gt; models from Smallest AI support voice cloning as part of the TTS workflow.&lt;/p&gt;

&lt;p&gt;For dubbing, voice quality should still be evaluated language by language. A voice that works well with one target language may not preserve the same accent, rhythm, or pronunciation behavior in another.&lt;/p&gt;

&lt;h3&gt;
  
  
  Timing is not just “increase the speed”
&lt;/h3&gt;

&lt;p&gt;Suppose the original line lasts 2.58 seconds but the translated speech naturally takes 3.1 seconds.&lt;/p&gt;

&lt;p&gt;You have several possible interventions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;shorten the translation&lt;/li&gt;
&lt;li&gt;increase speaking rate slightly&lt;/li&gt;
&lt;li&gt;modify pauses&lt;/li&gt;
&lt;li&gt;regenerate the translation with stricter length constraints&lt;/li&gt;
&lt;li&gt;allow small timeline drift&lt;/li&gt;
&lt;li&gt;correct the remaining difference in post-production&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of these approaches works for every line.&lt;/p&gt;

&lt;p&gt;If you aggressively speed up every long translation, the dub starts sounding rushed. If you rewrite every sentence until its character count matches, meaning and naturalness can suffer.&lt;/p&gt;

&lt;p&gt;Production systems usually combine translation constraints, synthesis controls, and post-processing.&lt;/p&gt;

&lt;h3&gt;
  
  
  Emotional fidelity is another constraint
&lt;/h3&gt;

&lt;p&gt;A speaker who is excited, sarcastic, uncertain, or angry carries information that is not contained in the transcript alone.&lt;/p&gt;

&lt;p&gt;TTS can generate the correct sentence while still changing the perceived performance.&lt;/p&gt;

&lt;p&gt;That is why dubbing evaluation needs listening tests rather than text-only checks.&lt;/p&gt;

&lt;p&gt;The pipeline needs to ask two different questions:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Did the system say the right thing?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;and&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Did it sound appropriate for the original scene?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Those are not the same test.&lt;/p&gt;

&lt;h2&gt;
  
  
  A production architecture for AI dubbing
&lt;/h2&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%2Foj9s56rmt5gxx5bjrfbi.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%2Foj9s56rmt5gxx5bjrfbi.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The three core models become much easier to reason about when the surrounding pipeline is explicit.&lt;/p&gt;

&lt;p&gt;A practical batch architecture can look like this:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Ingest the source
&lt;/h3&gt;

&lt;p&gt;Accept the source video or audio and create an immutable reference asset.&lt;/p&gt;

&lt;p&gt;Keep the original timeline available throughout processing.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Preprocess audio
&lt;/h3&gt;

&lt;p&gt;Depending on the source, preprocessing might include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;extracting the dialogue track&lt;/li&gt;
&lt;li&gt;normalizing levels&lt;/li&gt;
&lt;li&gt;reducing noise&lt;/li&gt;
&lt;li&gt;detecting silence&lt;/li&gt;
&lt;li&gt;splitting extremely long files into manageable units&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Be careful with operations that change timing.&lt;/p&gt;

&lt;p&gt;If you remove silence before transcription, for example, STT timestamps may no longer map directly to the original video.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Run STT with timestamps and diarization
&lt;/h3&gt;

&lt;p&gt;Generate structured transcript data containing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;segment ID&lt;/li&gt;
&lt;li&gt;speaker ID&lt;/li&gt;
&lt;li&gt;start timestamp&lt;/li&gt;
&lt;li&gt;end timestamp&lt;/li&gt;
&lt;li&gt;text&lt;/li&gt;
&lt;li&gt;confidence&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Store this as structured data rather than flattening it into a text document.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Translate each segment
&lt;/h3&gt;

&lt;p&gt;Translate with enough surrounding context to preserve meaning while retaining the original segment IDs.&lt;/p&gt;

&lt;p&gt;Do not lose the mapping between source and translated dialogue.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Validate duration
&lt;/h3&gt;

&lt;p&gt;Estimate whether the translated line can fit the original timing window.&lt;/p&gt;

&lt;p&gt;Send problematic segments into a retry or review path.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Review risky segments
&lt;/h3&gt;

&lt;p&gt;A review interface should allow somebody to inspect:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;source audio&lt;/li&gt;
&lt;li&gt;source transcript&lt;/li&gt;
&lt;li&gt;translation&lt;/li&gt;
&lt;li&gt;timing window&lt;/li&gt;
&lt;li&gt;confidence&lt;/li&gt;
&lt;li&gt;speaker&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This checkpoint is far cheaper than discovering translation mistakes after synthesis and final mixing.&lt;/p&gt;

&lt;h3&gt;
  
  
  7. Generate target speech
&lt;/h3&gt;

&lt;p&gt;Route each approved translated segment to the correct speaker voice.&lt;/p&gt;

&lt;p&gt;Your own internal synthesis queue might contain data such as: (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=ai-dubbing-pipelines-localizing-video-audio-with-translation-timing-and-tts"&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 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;"segment_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"speaker_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"A"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"translated_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;"Hola, bienvenido a la nueva sede."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"target_duration_seconds"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;2.58&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"voice_profile"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"speaker-A"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"delivery"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"friendly"&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;Again, this is an example of &lt;strong&gt;your application’s internal data model&lt;/strong&gt;, not a Smallest AI API request schema.&lt;/p&gt;

&lt;p&gt;The actual request body should follow whichever TTS provider’s current API documentation you are using.&lt;/p&gt;

&lt;h3&gt;
  
  
  8. Reassemble the timeline
&lt;/h3&gt;

&lt;p&gt;Place generated segments back at their corresponding source timestamps.&lt;/p&gt;

&lt;p&gt;Then restore or mix:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;music&lt;/li&gt;
&lt;li&gt;room tone&lt;/li&gt;
&lt;li&gt;environmental sound&lt;/li&gt;
&lt;li&gt;sound effects&lt;/li&gt;
&lt;li&gt;non-dialogue audio&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  9. Normalize and export
&lt;/h3&gt;

&lt;p&gt;Run final loudness and quality checks before producing the deliverable audio or remuxing it into the video.&lt;/p&gt;

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

&lt;p&gt;If you prototype the speech stages using the &lt;a href="https://app.smallest.ai/?utm_source=dev.to&amp;amp;utm_medium=vizup&amp;amp;utm_campaign=ai-dubbing-pipelines-localizing-video-audio-with-translation-timing-and-tts"&gt;Smallest AI API&lt;/a&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/?utm_source=dev.to&amp;amp;utm_medium=vizup&amp;amp;utm_campaign=ai-dubbing-pipelines-localizing-video-audio-with-translation-timing-and-tts"&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;Authenticated server-side requests should pass the value through the Authorization 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 &amp;lt;SMALLEST_API_KEY value&amp;gt;
&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, query parameters, or client-side logs.&lt;/p&gt;

&lt;p&gt;For production deployments, store credentials in your cloud or infrastructure provider’s server-side secrets-management system rather than committing them to source control.&lt;/p&gt;

&lt;p&gt;The exact STT and TTS endpoints, model names, and request fields can change over time, so use the current API documentation rather than copying an old request schema into a new production integration.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where dubbing pipelines actually break
&lt;/h2&gt;

&lt;p&gt;Calling three APIs sequentially is not usually the difficult part.&lt;/p&gt;

&lt;p&gt;Production failures tend to happen in the state you carry between calls.&lt;/p&gt;

&lt;h2&gt;
  
  
  Timestamp drift
&lt;/h2&gt;

&lt;p&gt;Audio preprocessing can modify the timeline used by transcription.&lt;/p&gt;

&lt;p&gt;Suppose you remove a two-second silence before sending a clip to STT. Every timestamp after that edit is now offset relative to the original video.&lt;/p&gt;

&lt;p&gt;You need either:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a mapping between processed and original timestamps, or&lt;/li&gt;
&lt;li&gt;a preprocessing strategy that preserves the original timeline&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Otherwise the translated speech can be correct and still appear at the wrong moment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Speaker IDs changing between chunks
&lt;/h2&gt;

&lt;p&gt;Diarization systems often label speakers relative to the audio chunk being processed.&lt;/p&gt;

&lt;p&gt;That means:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;chunk 1 → Speaker A = Alice
chunk 2 → Speaker A = Bob
chunk 3 → Speaker B = Alice
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If your TTS routing blindly trusts those labels, Alice’s voice clone can suddenly start reading Bob’s lines.&lt;/p&gt;

&lt;p&gt;For long or chunked media, add a speaker-reconciliation step before voice assignment. This can involve comparing speaker representations across chunks and mapping local diarization labels to stable global speaker IDs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Translation quality varying by language pair
&lt;/h2&gt;

&lt;p&gt;A pipeline validated on English → Spanish should not automatically be considered validated for English → Arabic, Thai, Hindi, Japanese, or another target language.&lt;/p&gt;

&lt;p&gt;Sentence structure, spoken duration, pronunciation behavior, translation-resource availability, and cultural adaptation requirements vary.&lt;/p&gt;

&lt;p&gt;Evaluate the &lt;strong&gt;entire pipeline&lt;/strong&gt; for each language pair you intend to support.&lt;/p&gt;

&lt;p&gt;That means measuring more than translation accuracy.&lt;/p&gt;

&lt;p&gt;Also evaluate:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;timing fit&lt;/li&gt;
&lt;li&gt;pronunciation&lt;/li&gt;
&lt;li&gt;voice consistency&lt;/li&gt;
&lt;li&gt;prosody&lt;/li&gt;
&lt;li&gt;speaker identity&lt;/li&gt;
&lt;li&gt;cultural appropriateness&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For multilingual production specifically, this guide to &lt;a href="https://smallest.ai/blog/multilingual-voice-dubbing-for-product-videos-how-to-localize-audio-without-re-recording?utm_source=dev.to&amp;amp;utm_medium=vizup&amp;amp;utm_campaign=ai-dubbing-pipelines-localizing-video-audio-with-translation-timing-and-tts"&gt;localizing product videos without re-recording&lt;/a&gt; goes deeper into the localization workflow.&lt;/p&gt;

&lt;h2&gt;
  
  
  Voice inconsistency across segments
&lt;/h2&gt;

&lt;p&gt;When a long recording is synthesized as hundreds of independent requests, subtle changes in pacing or delivery can accumulate.&lt;/p&gt;

&lt;p&gt;This can make a single speaker sound different between scenes even when the same voice profile is being used.&lt;/p&gt;

&lt;p&gt;Evaluate consistency across the complete program, not just isolated samples.&lt;/p&gt;

&lt;p&gt;If one voice needs to remain recognizable across a large content library, &lt;a href="https://smallest.ai/blog/voice-cloning-for-brand-consistency-how-teams-can-scale-a-single-voice-across-products-and-channels?utm_source=dev.to&amp;amp;utm_medium=vizup&amp;amp;utm_campaign=ai-dubbing-pipelines-localizing-video-audio-with-translation-timing-and-tts"&gt;voice cloning and brand consistency&lt;/a&gt; becomes an architectural concern rather than simply a model feature.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lip-sync adds another system
&lt;/h2&gt;

&lt;p&gt;Audio alignment and visual lip-sync are related, but they are not the same problem.&lt;/p&gt;

&lt;p&gt;Basic dubbing can align translated speech to approximately the same time window while leaving the original video untouched.&lt;/p&gt;

&lt;p&gt;True visual dubbing goes further by modifying facial motion to match the new phonemes.&lt;/p&gt;

&lt;p&gt;That requires an additional video-generation or facial-animation layer.&lt;/p&gt;

&lt;p&gt;For example, NVIDIA’s &lt;a href="https://docs.nvidia.com/nim/maxine/audio2face-2d/latest/overview.html" rel="noopener noreferrer"&gt;Audio2Face-2D documentation&lt;/a&gt; describes a system that uses audio to generate facial motion and synchronize mouth movement.&lt;/p&gt;

&lt;p&gt;Whether you need this depends heavily on the source material.&lt;/p&gt;

&lt;p&gt;Lip-sync may be less important for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;narrated screen recordings&lt;/li&gt;
&lt;li&gt;animated explainers&lt;/li&gt;
&lt;li&gt;podcasts converted to video&lt;/li&gt;
&lt;li&gt;slides with voice-over&lt;/li&gt;
&lt;li&gt;videos where the speaker’s face is rarely visible&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It becomes much more noticeable when a face occupies most of the frame.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real-time dubbing is a different architecture
&lt;/h2&gt;

&lt;p&gt;Batch dubbing gives the system a major advantage: complete context.&lt;/p&gt;

&lt;p&gt;The transcription engine can process finished sentences. The translation model can see the entire utterance. The TTS system knows how much audio it needs to generate.&lt;/p&gt;

&lt;p&gt;Real-time dubbing removes that luxury.&lt;/p&gt;

&lt;p&gt;A live pipeline may need to perform:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;streaming STT → incremental translation → streaming TTS&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;before the speaker has even finished the complete thought.&lt;/p&gt;

&lt;p&gt;That creates new problems:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;partial transcripts can be revised&lt;/li&gt;
&lt;li&gt;sentence meaning can change at the end&lt;/li&gt;
&lt;li&gt;translation may require words that have not arrived yet&lt;/li&gt;
&lt;li&gt;synthesis needs to start before the final segment is available&lt;/li&gt;
&lt;li&gt;latency accumulates across each stage&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For that architecture, &lt;a href="https://smallest.ai/blog/streaming-tts-explained-for-developers-ux-latency-and-cost-guide?utm_source=dev.to&amp;amp;utm_medium=vizup&amp;amp;utm_campaign=ai-dubbing-pipelines-localizing-video-audio-with-translation-timing-and-tts"&gt;streaming TTS&lt;/a&gt; changes how audio should be buffered and scheduled.&lt;/p&gt;

&lt;p&gt;Smallest AI also exposes &lt;a href="https://smallest.ai/speech-to-speech?utm_source=dev.to&amp;amp;utm_medium=vizup&amp;amp;utm_campaign=ai-dubbing-pipelines-localizing-video-audio-with-translation-timing-and-tts"&gt;Hydra speech-to-speech&lt;/a&gt; for low-latency, full-duplex speech applications.&lt;/p&gt;

&lt;p&gt;However, speech-to-speech and an inspectable dubbing pipeline solve different problems.&lt;/p&gt;

&lt;p&gt;If your workflow requires explicit translated text for QA, analytics, terminology control, moderation, or editing, keeping STT, translation, and TTS as explicit stages gives you much more control over the intermediate state.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build QA into the pipeline instead of adding it later
&lt;/h2&gt;

&lt;p&gt;A useful production design has checkpoints at three places.&lt;/p&gt;

&lt;h3&gt;
  
  
  After transcription
&lt;/h3&gt;

&lt;p&gt;Review or automatically flag:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;low-confidence words&lt;/li&gt;
&lt;li&gt;unclear names&lt;/li&gt;
&lt;li&gt;speaker changes&lt;/li&gt;
&lt;li&gt;overlapping speech&lt;/li&gt;
&lt;li&gt;domain vocabulary&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  After translation
&lt;/h3&gt;

&lt;p&gt;Review:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;meaning&lt;/li&gt;
&lt;li&gt;terminology&lt;/li&gt;
&lt;li&gt;register&lt;/li&gt;
&lt;li&gt;cultural adaptation&lt;/li&gt;
&lt;li&gt;timing fit&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  After synthesis
&lt;/h3&gt;

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

&lt;ul&gt;
&lt;li&gt;clipped speech&lt;/li&gt;
&lt;li&gt;unnatural pacing&lt;/li&gt;
&lt;li&gt;incorrect pronunciation&lt;/li&gt;
&lt;li&gt;voice inconsistency&lt;/li&gt;
&lt;li&gt;missing emotion&lt;/li&gt;
&lt;li&gt;timeline drift&lt;/li&gt;
&lt;li&gt;audio-level differences&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At scale, you do not necessarily need humans to listen to every second.&lt;/p&gt;

&lt;p&gt;Confidence thresholds and automated checks can reduce the review set.&lt;/p&gt;

&lt;p&gt;But eliminating QA completely usually just moves the cost downstream, where mistakes are more expensive to repair.&lt;/p&gt;

&lt;h2&gt;
  
  
  The pipeline is really about preserving state
&lt;/h2&gt;

&lt;p&gt;An AI dubbing stack has three obvious models:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;STT → translation → TTS&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;But those models are not what make the system reliable.&lt;/p&gt;

&lt;p&gt;The useful architecture is the information that survives between them:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;speaker identity → timestamps → translation context → timing constraints → voice identity → QA status&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Lose any of those and the pipeline becomes harder to control.&lt;/p&gt;

&lt;p&gt;Keep them structured, and each stage becomes independently testable.&lt;/p&gt;

&lt;p&gt;For a first prototype, keep the scope deliberately small:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;one source language&lt;/li&gt;
&lt;li&gt;one target language&lt;/li&gt;
&lt;li&gt;short clips&lt;/li&gt;
&lt;li&gt;one or two speakers&lt;/li&gt;
&lt;li&gt;explicit transcript review&lt;/li&gt;
&lt;li&gt;explicit translation review&lt;/li&gt;
&lt;li&gt;deterministic segment IDs&lt;/li&gt;
&lt;li&gt;final listening QA&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once that works, expand into longer files, additional speakers, more language pairs, streaming, or visual lip-sync.&lt;/p&gt;

&lt;p&gt;The fastest path to a useful dubbing system is not adding more models. It is making the seams between the existing models observable.&lt;/p&gt;

&lt;p&gt;If you want to prototype the speech side with your own audio, create an API key and &lt;a href="https://app.smallest.ai/?utm_source=dev.to&amp;amp;utm_medium=vizup&amp;amp;utm_campaign=ai-dubbing-pipelines-localizing-video-audio-with-translation-timing-and-tts"&gt;start building with the Smallest AI developer platform&lt;/a&gt;, then validate the complete STT → translation → TTS path against the language pairs and content your application will actually process.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>machinelearning</category>
      <category>texttospeech</category>
      <category>speechrecognition</category>
    </item>
    <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>
