<?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: Mart Schweiger</title>
    <description>The latest articles on DEV Community by Mart Schweiger (@martschweiger).</description>
    <link>https://dev.to/martschweiger</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%2F3802221%2Fcdb4c7a2-d4f4-444d-908e-30d6ea3bd1a7.png</url>
      <title>DEV Community: Mart Schweiger</title>
      <link>https://dev.to/martschweiger</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/martschweiger"/>
    <language>en</language>
    <item>
      <title>Agent Context Carryover for Voice Agents on LiveKit</title>
      <dc:creator>Mart Schweiger</dc:creator>
      <pubDate>Wed, 12 Aug 2026 15:00:48 +0000</pubDate>
      <link>https://dev.to/martschweiger/agent-context-carryover-for-voice-agents-on-livekit-2fko</link>
      <guid>https://dev.to/martschweiger/agent-context-carryover-for-voice-agents-on-livekit-2fko</guid>
      <description>&lt;p&gt;If you've built a voice agent, you know the failure mode. The agent asks "What's your email?" and the caller says "j-dot-oconnor at gmail" — and the transcript comes back "j dot o connor at gmail dot com," or worse. Short replies, spelled-out entities, proper nouns, menu items, street names: these are exactly the moments where a caller expects the agent to just get it, and exactly where streaming speech-to-text tends to slip.&lt;/p&gt;

&lt;p&gt;The usual fix is to feed the model hints — key terms, custom vocabulary, a running list of nouns you expect to hear. It works, but it's plumbing you have to build and maintain. With &lt;strong&gt;Agent Context Carryover&lt;/strong&gt; on &lt;a href="https://www.assemblyai.com/blog/universal-3-5-pro-realtime" rel="noopener noreferrer"&gt;Universal-3.5 Pro Realtime&lt;/a&gt;, running on &lt;a href="https://www.assemblyai.com/blog/build-voice-agent-livekit" rel="noopener noreferrer"&gt;LiveKit&lt;/a&gt;, you skip the plumbing entirely. Flip one setting and the model transcribes each user turn while aware of what your agent just said. In this guide we'll build a working &lt;a href="https://www.assemblyai.com/blog/ai-voice-agents" rel="noopener noreferrer"&gt;voice agent&lt;/a&gt; that puts it to the test on some deliberately hard-to-transcribe input.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;What Agent Context Carryover actually does&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Context carryover lets the speech-to-text model stay aware of the conversation while it transcribes new input. Universal-3.5 Pro Realtime preserves both sides of the dialogue:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Agent context&lt;/strong&gt; — your assistant's most recent spoken reply.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;User context&lt;/strong&gt; — prior finalized user turns from the session.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That bidirectional awareness is what moves the needle on accuracy, especially for short answers and spelled-out entities. When your agent asks "What's your email address?", the model can anticipate the shape of the answer and format "&lt;a href="mailto:user@assemblyai.com"&gt;user@assemblyai.com&lt;/a&gt;" correctly instead of producing "user at assemblyai dot com."&lt;/p&gt;

&lt;p&gt;Here's the part that matters for how you build: on LiveKit, the AssemblyAI plugin feeds that agent context in &lt;em&gt;automatically&lt;/em&gt;. There's no extra wiring to capture the agent's last message and shuttle it back to the transcriber. You enable one parameter and the plugin does the rest — no key-term lists to predefine, no context relay to maintain. As of this release, AssemblyAI is the only speech-to-text provider offering this kind of drag-and-drop context carryover.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;What we'll build&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;We'll stand up a LiveKit voice agent from the official Python starter, wire in AssemblyAI for speech-to-text, enable Agent Context Carryover, and then stress-test it as a restaurant takeout line — a fictional spot called Le Périgord Fusion whose menu is a minefield of proper nouns: escargots à la bourguignonne, khachapuri adjaruli, bánh xèo, gnocchi alla sorrentina, mille-feuille, kouign-amann, yuzu kombucha. If the model can order all of that without a single custom vocabulary entry, it can handle your product names too.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Prerequisites&lt;/strong&gt;
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Python 3.10+ and &lt;a href="https://docs.astral.sh/uv/" rel="noopener noreferrer"&gt;uv&lt;/a&gt; installed&lt;/li&gt;
&lt;li&gt;A &lt;a href="https://www.assemblyai.com/dashboard/signup" rel="noopener noreferrer"&gt;free AssemblyAI API key&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;A LiveKit Cloud project (for LIVEKIT_URL, API key, and secret)&lt;/li&gt;
&lt;li&gt;livekit-agents 1.6.6 or newer (needed for automatic agent-context forwarding)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Step 1 — Spin up the LiveKit Python starter&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;LiveKit publishes a starter project that scaffolds a complete Python agent — a voice pipeline on LiveKit Inference (Gemma for the LLM, Cartesia for TTS), the LiveKit turn detector, background noise cancellation, and an eval suite. The quickest way to create one is with the LiveKit CLI:&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;lk&lt;/span&gt; &lt;span class="n"&gt;cloud&lt;/span&gt; &lt;span class="n"&gt;auth&lt;/span&gt;
&lt;span class="n"&gt;lk&lt;/span&gt; &lt;span class="n"&gt;agent&lt;/span&gt; &lt;span class="n"&gt;init&lt;/span&gt; &lt;span class="n"&gt;my&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;agent&lt;/span&gt; &lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;template&lt;/span&gt; &lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;starter&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;python&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The CLI clones the template and writes your LiveKit credentials into .env.local. Prefer to do it by hand? Clone the repo, install dependencies with uv, and copy the env template yourself:&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;git&lt;/span&gt; &lt;span class="n"&gt;clone&lt;/span&gt; &lt;span class="n"&gt;https&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="n"&gt;github&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;com&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;livekit&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;examples&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;starter&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;python&lt;/span&gt;
&lt;span class="n"&gt;cd&lt;/span&gt; &lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;starter&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;python&lt;/span&gt;
&lt;span class="n"&gt;uv&lt;/span&gt; &lt;span class="n"&gt;sync&lt;/span&gt;
&lt;span class="n"&gt;cp&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;example&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;local&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  &lt;strong&gt;Step 2 — Install the AssemblyAI plugin for LiveKit&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Add the AssemblyAI plugin to the project:&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="nx"&gt;uv&lt;/span&gt; &lt;span class="nx"&gt;add&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;livekit-agents[assemblyai]&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gives you the assemblyai plugin namespace you'll use to configure speech-to-text in the agent.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Step 3 — Configure Universal-3.5 Pro and enable context carryover&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Open the agent entrypoint at src/agent.py. Out of the box, the starter builds its AgentSession with Deepgram as the speech-to-text provider through LiveKit Inference:&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="err"&gt;#&lt;/span&gt; &lt;span class="nx"&gt;Before&lt;/span&gt; &lt;span class="err"&gt;—&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;starter&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;s default STT line
session = AgentSession(
    stt=inference.STT(model="deepgram/nova-3", language="multi"),
    # ... llm, tts, turn detection
)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Swap that one line for the AssemblyAI plugin and point it at the universal-3-5-pro model. Add the import at the top of the file — everything else in the session (LLM, TTS, turn detection) stays exactly as it was:&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;from&lt;/span&gt; &lt;span class="nx"&gt;livekit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;plugins&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;assemblyai&lt;/span&gt;

&lt;span class="err"&gt;#&lt;/span&gt; &lt;span class="nx"&gt;After&lt;/span&gt; &lt;span class="err"&gt;—&lt;/span&gt; &lt;span class="nx"&gt;AssemblyAI&lt;/span&gt; &lt;span class="nx"&gt;STT&lt;/span&gt; &lt;span class="kd"&gt;with&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt; &lt;span class="nx"&gt;carryover&lt;/span&gt;
&lt;span class="nx"&gt;session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;AgentSession&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;stt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;assemblyai&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;STT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;universal-3-5-pro&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="err"&gt;#&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="nx"&gt;llm&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="nx"&gt;turn&lt;/span&gt; &lt;span class="nx"&gt;detection&lt;/span&gt; &lt;span class="nx"&gt;unchanged&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The agent_context and previous_context_n_turns parameters below apply only to universal-3-5-pro. That's the whole integration. On livekit-agents 1.6.6 and newer, AgentSession automatically forwards each assistant reply into the model's agent_context — the carryover happens without another line of code. (If a reply runs past the 1,750-character limit, the plugin truncates it silently and keeps the trailing content, which is usually the question you care about.)&lt;/p&gt;

&lt;p&gt;Want the model primed before the caller says anything? Seed it with your agent's opening line:&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="nx"&gt;stt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;assemblyai&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;STT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;universal-3-5-pro&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;agent_context&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Hello, how can I help you today?&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Automatic forwarding takes over after the first real assistant reply.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Step 4 — Add your API key and run&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Drop your AssemblyAI key into .env.local alongside your LiveKit credentials:&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="nx"&gt;ASSEMBLYAI_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;your_api_key_here&lt;/span&gt;
&lt;span class="nx"&gt;LIVEKIT_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;your_livekit_url&lt;/span&gt;
&lt;span class="nx"&gt;LIVEKIT_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;your_livekit_api_key&lt;/span&gt;
&lt;span class="nx"&gt;LIVEKIT_API_SECRET&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;your_livekit_api_secret&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then start the agent in dev mode:&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="nx"&gt;uv&lt;/span&gt; &lt;span class="nx"&gt;run&lt;/span&gt; &lt;span class="nx"&gt;python&lt;/span&gt; &lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;py&lt;/span&gt; &lt;span class="nx"&gt;dev&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Connect to your agent, say hello, and confirm it responds. Now the fun part.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Putting it to the test&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;With the agent live as our Le Périgord Fusion takeout line, we ordered straight down the menu — appetizer, drink, dessert — using the hardest names on it. The exchange went like this:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Agent:&lt;/strong&gt; Welcome to Le Périgord Fusion. Would you like to place an order, or may I read through our menu for you?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Caller:&lt;/strong&gt; Yeah, what do you have for appetizers?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Agent:&lt;/strong&gt; We have escargots à la bourguignonne for $14.50, khachapuri adjaruli for $13, bánh xèo for $11.75, and gnocchi alla sorrentina for $12.25…&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Caller:&lt;/strong&gt; Let's do escargots à la bourguignonne… and a yuzu kombucha… and a kouign-amann.&lt;/p&gt;

&lt;p&gt;Every item — French, Georgian, Vietnamese, Italian — came back transcribed correctly, and the order summary matched exactly: one escargots à la bourguignonne, one yuzu kombucha, one kouign-amann, subtotal $29.25. No custom vocabulary. No key-term list. The only thing we turned on was Agent Context Carryover, and because the model could see what the agent had just recited, it knew which unusual words to expect when the caller echoed them back.&lt;/p&gt;

&lt;p&gt;The build actually splits the call across two agents — an order-taker that runs the menu, and a checkout host it hands off to for delivery and payment. That handoff is where the next hard case showed up: the caller read out a full delivery address, "1214 Madison Avenue, New York, 10012," and a payment method, and the checkout host read both back verbatim in its confirmation. Street addresses and spoken numbers are exactly the kind of input that trips up streaming transcription, and here the model's awareness of what the agent had just asked ("Please provide your full delivery address") is what kept the digits and the street name intact.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Tuning it: the parameters worth knowing&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Automatic forwarding covers most builds, but two parameters give you finer control when you need it.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Parameter&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;agent_context&lt;/td&gt;
&lt;td&gt;string&lt;/td&gt;
&lt;td&gt;Your agent's most recent spoken reply, up to 1,750 characters. Set at construction or updated mid-stream via update_options.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;previous_context_n_turns&lt;/td&gt;
&lt;td&gt;integer&lt;/td&gt;
&lt;td&gt;How many prior conversation turns are carried forward automatically. Range 0–100; server default is 5. Connect-time only — it can't be changed mid-session.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A couple of rules to keep in mind: an explicit agent_context over 1,750 characters raises a ValueError (only &lt;em&gt;automatic&lt;/em&gt; forwarding truncates silently), and previous_context_n_turns is fixed at connection time.&lt;/p&gt;

&lt;p&gt;If you're on a version older than 1.6.6, or you want to manage the agent context yourself, disable automatic forwarding and push updates on each assistant turn:&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;from&lt;/span&gt; &lt;span class="nx"&gt;livekit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;agents&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;AgentSession&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ConversationItemAddedEvent&lt;/span&gt;
&lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="nx"&gt;livekit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;plugins&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;assemblyai&lt;/span&gt;

&lt;span class="nx"&gt;AGENT_CONTEXT_MAX_CHARS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1750&lt;/span&gt;

&lt;span class="nx"&gt;session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;AgentSession&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;stt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;assemblyai&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;STT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;universal-3-5-pro&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nx"&gt;stt_context_options&lt;/span&gt;&lt;span class="o"&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;forward_chat_context&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;False&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="nd"&gt;session&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;conversation_item_added&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nx"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_on_conversation_item_added&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ev&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ConversationItemAddedEvent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nx"&gt;ev&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="o"&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="nx"&gt;or&lt;/span&gt; &lt;span class="nx"&gt;ev&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;role&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;assistant&lt;/span&gt;&lt;span class="dl"&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;agent_stt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stt&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nx"&gt;not&lt;/span&gt; &lt;span class="nf"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;agent_stt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;assemblyai&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;STT&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;spoken&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ev&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text_content&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nx"&gt;not&lt;/span&gt; &lt;span class="nx"&gt;spoken&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;agent_stt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update_options&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;agent_context&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;spoken&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;AGENT_CONTEXT_MAX_CHARS&lt;/span&gt;&lt;span class="p"&gt;:])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  &lt;strong&gt;Why this matters for production voice agents&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Transcription accuracy is the foundation every downstream step in a voice agent stands on — intent detection, tool calls, order summaries, CRM writes. A misheard proper noun early in a call cascades into the wrong action later. Agent Context Carryover attacks that at the source, and it does it without the maintenance burden of key-term prompting or a hand-rolled context relay. You get the accuracy gain on proper nouns and key terms, and you get it by flipping a switch.&lt;/p&gt;

&lt;p&gt;LiveKit sees the same thing from the infrastructure side:&lt;/p&gt;

&lt;p&gt;"We're excited to make AssemblyAI's Universal-3.5 Pro available on LiveKit Inference. What really stands out is their pace of innovation with Context Carryover — it intelligently applies conversation context to improve transcription accuracy in a way most speech models don't, removing the need for users to predefine key terms."&lt;/p&gt;

&lt;p&gt;— David Zhao, Co-founder at LiveKit&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Try it on your own agent&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;If you're already building on LiveKit, Agent Context Carryover is available today — turn it on and hear the difference on your hardest inputs. Follow the four steps above, and point it at the proper nouns your callers actually say. For the full parameter reference, see the &lt;a href="https://www.assemblyai.com/docs/voice-agents/livekit-u3-rt-pro" rel="noopener noreferrer"&gt;Context Carryover docs&lt;/a&gt;, and if you're new to this stack, start with our guide to &lt;a href="https://www.assemblyai.com/blog/build-voice-agent-livekit" rel="noopener noreferrer"&gt;building a voice agent with LiveKit&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Frequently asked questions&lt;/strong&gt;
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;What is Agent Context Carryover?&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Agent Context Carryover is a feature of AssemblyAI's Universal-3.5 Pro Realtime model that lets the speech-to-text model transcribe each user turn while aware of the conversation so far — specifically the agent's most recent reply and prior finalized user turns. That context improves accuracy on short replies, proper nouns, and spelled-out entities like emails.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;How is it different from key-term prompting or custom vocabulary?&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Key-term prompting requires you to predefine the words you expect and maintain that list. Context carryover works from the live conversation instead, so on LiveKit it needs no predefined terms and no extra plumbing — the plugin forwards the agent's context automatically.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Do I need to write code to forward the agent's context on LiveKit?&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;No. On livekit-agents 1.6.6 and newer, AgentSession forwards each assistant reply into the model's agent_context automatically. You only write forwarding code if you're on an older version or want to manage the context manually.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Which model supports it?&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;The agent_context and previous_context_n_turns parameters apply only to universal-3-5-pro (Universal-3.5 Pro Realtime).&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Is there a limit on how much context I can pass?&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Yes. agent_context accepts up to 1,750 characters. Setting an explicit value over that limit raises a ValueError; automatic forwarding instead truncates silently, keeping the trailing content. previous_context_n_turns ranges from 0 to 100 (default 5) and is set at connection time only.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Does it work with speech-to-text providers other than AssemblyAI?&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;As of this release, AssemblyAI is the only speech-to-text provider offering drag-and-drop context carryover on LiveKit.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>voiceassistant</category>
      <category>api</category>
      <category>webdev</category>
    </item>
    <item>
      <title>OpenAI Realtime API Alternatives in 2026 (How to Migrate)</title>
      <dc:creator>Mart Schweiger</dc:creator>
      <pubDate>Wed, 12 Aug 2026 15:00:41 +0000</pubDate>
      <link>https://dev.to/martschweiger/openai-realtime-api-alternatives-in-2026-how-to-migrate-2cl9</link>
      <guid>https://dev.to/martschweiger/openai-realtime-api-alternatives-in-2026-how-to-migrate-2cl9</guid>
      <description>&lt;p&gt;OpenAI's Realtime API made "talk to your app" easy to prototype: one model, speech in, speech out, low latency. If you're building voice agents in 2026, you've probably tried it.&lt;/p&gt;

&lt;p&gt;Prototyping and shipping are different problems, though. Once real users start calling, the single-model architecture that demos well starts working against you — on cost, on transcription accuracy, and on the details of managing a real conversation. That's what sends teams looking for an OpenAI Realtime API alternative that holds up in production.&lt;/p&gt;

&lt;p&gt;This post does two things. First, a clear-eyed rundown of the alternatives in 2026 — Gemini Live, ElevenLabs, Deepgram, a couple of open-source options, and &lt;a href="https://www.assemblyai.com/products/voice-agent-api" rel="noopener noreferrer"&gt;AssemblyAI's Voice Agent API&lt;/a&gt;. Then a concrete migration walkthrough, because a list isn't much use if nobody tells you how to actually switch.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why teams look for an OpenAI Realtime alternative
&lt;/h2&gt;

&lt;p&gt;Let's be specific about what pushes people to look.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cost you can't predict.&lt;/strong&gt; OpenAI Realtime is token-priced. The current flagship, gpt-realtime-2.1, runs roughly $32 per million audio input tokens and $64 per million audio output tokens. At the base rate a call lands around $0.05 per minute — fine on a spreadsheet. But conversation context gets re-processed each turn, so on longer calls real bills commonly run 2x to 5x that base rate. Prompt caching helps, but now you're maintaining caching logic just to keep a voice bot affordable. There's a cheaper gpt-realtime-2.1-mini at about $0.016 per minute, but you trade accuracy for the discount. The through-line: your cost per call climbs with call length, and you can't quote a customer a number without a footnote.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Speech accuracy on real audio.&lt;/strong&gt; This is the architectural crux. OpenAI Realtime is one multimodal model that also does voice, which means transcription isn't a dedicated step — it's one job the model does among many. In AssemblyAI's own side-by-side testing, OpenAI Realtime hallucinated on messy input, inventing words that were never said. On a support line or a drive-through, that's not a rounding error — that's a wrong order.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Barge-in and turn quality.&lt;/strong&gt; Interruption handling is what separates a conversation from a walkie-talkie. In that same testing, OpenAI Realtime's barge-in logic triggered on background noise — a cough was enough to make it stop talking. An agent that yields the floor to a sneeze is an agent users don't come back to.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Thirty-plus event types.&lt;/strong&gt; The Realtime API exposes 30+ event types you subscribe to and emit — session updates, input buffer commits, response deltas, and so on. Every one is a thing to handle, a thing to get wrong, and a thing to keep working as the API evolves.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lock-in.&lt;/strong&gt; One model doing everything means you can't swap the transcription layer, the reasoning layer, or the voice independently. When one piece underperforms, you don't have a knob — you have a migration.&lt;/p&gt;

&lt;h2&gt;
  
  
  The best OpenAI Realtime API alternatives in 2026
&lt;/h2&gt;

&lt;p&gt;The right pick depends on what you're optimizing for. Short, factual rundowns, then a table.&lt;/p&gt;

&lt;h3&gt;
  
  
  AssemblyAI Voice Agent API
&lt;/h3&gt;

&lt;p&gt;Instead of one model doing everything, the &lt;a href="https://www.assemblyai.com/blog/introducing-our-voice-agent-api" rel="noopener noreferrer"&gt;Voice Agent API&lt;/a&gt; wires together dedicated models for each step — speech-to-text, LLM, and text-to-speech — behind a single WebSocket and a single bill. Pricing is flat at $4.50/hr all-in, so cost doesn't drift with call length. End-to-end latency is around 1 second, it covers 6 languages (EN/ES/FR/DE/IT/PT), and concurrency is unlimited. It's built on Universal-3.5 Pro Realtime, which is the accuracy story below. &lt;strong&gt;Best for:&lt;/strong&gt; production voice agents where transcription accuracy and predictable cost matter most.&lt;/p&gt;

&lt;h3&gt;
  
  
  Google Gemini Live
&lt;/h3&gt;

&lt;p&gt;Gemini Live is Google's real-time multimodal offering — native audio, video input, and tight integration if you're already in Google's ecosystem. Architecturally it's the same shape as OpenAI Realtime: one large multimodal model, usage-based pricing, and the same "transcription is a side effect" tradeoff, so it inherits the same accuracy and cost-predictability questions. &lt;strong&gt;Best for:&lt;/strong&gt; teams already standardized on Google Cloud who need audio plus vision in one model.&lt;/p&gt;

&lt;h3&gt;
  
  
  ElevenLabs Conversational AI
&lt;/h3&gt;

&lt;p&gt;ElevenLabs is known for its text-to-speech, and its Conversational AI product leans on that voice-output quality. On the input side — the part that decides whether your agent hears the caller correctly — its Scribe v2 model lands at 9.76% WER on Pipecat's benchmark, behind the category leaders, and concurrency is capped (around 30), which becomes a constraint at scale. &lt;strong&gt;Best for:&lt;/strong&gt; applications where expressive TTS output is the top priority and input accuracy is secondary. (More in our &lt;a href="https://www.assemblyai.com/blog/assemblyai-voice-agent-api-vs-elevenlabs-conversational-ai" rel="noopener noreferrer"&gt;AssemblyAI vs. ElevenLabs comparison&lt;/a&gt;.)&lt;/p&gt;

&lt;h3&gt;
  
  
  Deepgram Voice Agent
&lt;/h3&gt;

&lt;p&gt;Deepgram offers a single-API Voice Agent product in the same shape as AssemblyAI's. The differentiator is accuracy on real agent audio: its newer Flux model comes in at 15.58% WER on the Pipecat benchmark, well behind the leaders, and the gap widens on entity error — getting names, numbers, and addresses right — where Flux reaches 50.50%. &lt;strong&gt;Best for:&lt;/strong&gt; teams already invested in Deepgram's stack. (Full breakdown in our &lt;a href="https://www.assemblyai.com/blog/assemblyai-vs-deepgram-best-voice-agent-api" rel="noopener noreferrer"&gt;AssemblyAI vs. Deepgram comparison&lt;/a&gt;.)&lt;/p&gt;

&lt;h3&gt;
  
  
  Open-source (Moshi, Qwen-Omni)
&lt;/h3&gt;

&lt;p&gt;If you want to own the whole stack and self-host, the open-source options are worth a look. Kyutai's Moshi is a full-duplex speech model you can run yourself; Alibaba's Qwen-Omni is a multimodal model you can wire into orchestration frameworks like Pipecat or LiveKit. You avoid a per-minute fee, but you take on the hosting, scaling, reliability, and accuracy-tuning yourself. &lt;strong&gt;Best for:&lt;/strong&gt; teams with ML infrastructure that need on-prem or full customization and can staff the maintenance.&lt;/p&gt;

&lt;p&gt;Here's how they stack up:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Alternative&lt;/th&gt;
&lt;th&gt;Pricing model&lt;/th&gt;
&lt;th&gt;Speech accuracy (Pipecat WER)&lt;/th&gt;
&lt;th&gt;Developer experience&lt;/th&gt;
&lt;th&gt;Concurrency&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;OpenAI Realtime&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Token-based, variable (~$0.05/min base, often 2x–5x)&lt;/td&gt;
&lt;td&gt;Prone to hallucination on messy audio&lt;/td&gt;
&lt;td&gt;30+ event types&lt;/td&gt;
&lt;td&gt;Rate-limited&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;AssemblyAI&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Flat $4.50/hr all-in&lt;/td&gt;
&lt;td&gt;6.99%&lt;/td&gt;
&lt;td&gt;One WebSocket + JSON&lt;/td&gt;
&lt;td&gt;Unlimited&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Gemini Live&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Token-based, variable&lt;/td&gt;
&lt;td&gt;One-model tradeoff&lt;/td&gt;
&lt;td&gt;Multimodal API&lt;/td&gt;
&lt;td&gt;Rate-limited&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;ElevenLabs&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Usage-based&lt;/td&gt;
&lt;td&gt;9.76% (Scribe v2)&lt;/td&gt;
&lt;td&gt;Single API&lt;/td&gt;
&lt;td&gt;Plan-tiered&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Deepgram&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Usage-based&lt;/td&gt;
&lt;td&gt;15.58% (Flux)&lt;/td&gt;
&lt;td&gt;Single API&lt;/td&gt;
&lt;td&gt;Plan-tiered&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Open-source&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Self-hosted (infra cost)&lt;/td&gt;
&lt;td&gt;Varies by model&lt;/td&gt;
&lt;td&gt;DIY orchestration&lt;/td&gt;
&lt;td&gt;Your hardware&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Why AssemblyAI is the closest drop-in for production
&lt;/h2&gt;

&lt;p&gt;The wedge is simple. OpenAI Realtime is one multimodal model that also does voice. The &lt;a href="https://www.assemblyai.com/products/voice-agent-api" rel="noopener noreferrer"&gt;Voice Agent API&lt;/a&gt; is dedicated models per step, assembled for conversation quality. That difference shows up in four places that matter when real calls are coming in.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Accuracy that holds on real audio.&lt;/strong&gt; On Pipecat's open STT benchmark — lower is better — Universal-3.5 Pro Realtime posts 6.99% WER. For context: Google Chirp3 sits at 9.04%, ElevenLabs Scribe v2 at 9.76%, and Deepgram Flux at 15.58%. The gap widens on entities — the names, order numbers, and addresses your business actually runs on. Universal-3.5 Pro Realtime's entity error rate is 15.31% versus Deepgram's 50.50%. When a caller says "my confirmation is A-as-in-apple 4 7 2," that's the number that decides whether the call works. More on the model in the &lt;a href="https://www.assemblyai.com/blog/universal-3-5-pro-realtime" rel="noopener noreferrer"&gt;Universal-3.5 Pro Realtime launch post&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Context that raises accuracy further.&lt;/strong&gt; You can feed the agent live context — product names, customer vocabulary, whatever your domain needs. Across 20,000 files, using agent_context cut WER by 10.2%. That's a tuning knob a single-model architecture doesn't expose.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;One WebSocket instead of 30+ events.&lt;/strong&gt; No SDK required, standard JSON over a WebSocket. You're not subscribing to a taxonomy of event types — you send audio and handle responses. That's a big reason the Voice Agent API &lt;a href="https://www.assemblyai.com/blog/why-assemblyais-voice-agent-api-is-designed-for-coding-agents" rel="noopener noreferrer"&gt;plays well with coding agents&lt;/a&gt; like Claude Code, which can scaffold an integration without wrestling a sprawling event model.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Predictable everything.&lt;/strong&gt; Flat $4.50/hr all-in — STT, LLM, and TTS through one bill — so a 2-minute call and a 20-minute call cost what you'd expect. Around 1-second end-to-end latency. Unlimited concurrency, so a traffic spike doesn't mean a support ticket. And you can change configuration mid-conversation instead of tearing down and rebuilding a session. For where these differences bite, we wrote up &lt;a href="https://www.assemblyai.com/blog/where-voice-agent-stacks-start-showing-their-limits" rel="noopener noreferrer"&gt;where voice agent stacks start showing their limits&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Want to hear it before you read another word of code? &lt;a href="https://www.assemblyai.com/products/voice-agents" rel="noopener noreferrer"&gt;&lt;strong&gt;Talk to a Live Agent&lt;/strong&gt;&lt;/a&gt; — it's the fastest way to judge latency and interruption handling for yourself.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to migrate from OpenAI Realtime to AssemblyAI
&lt;/h2&gt;

&lt;p&gt;The good news: if you built on OpenAI Realtime, you already have the hard parts — audio capture, a client transport, and turn logic. Migration is mostly re-pointing them at a simpler surface.&lt;/p&gt;

&lt;h3&gt;
  
  
  Map the concepts
&lt;/h3&gt;

&lt;p&gt;Here's how the mental model translates:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;OpenAI Realtime&lt;/th&gt;
&lt;th&gt;AssemblyAI Voice Agent API&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;session.update with dozens of fields&lt;/td&gt;
&lt;td&gt;Create an agent once (name, prompt, greeting, voice), then connect&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;30+ typed events to emit and subscribe to&lt;/td&gt;
&lt;td&gt;One WebSocket; send audio, receive messages&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Instructions passed per session&lt;/td&gt;
&lt;td&gt;system_prompt set on the agent, adjustable live mid-conversation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Manual VAD / turn-detection config&lt;/td&gt;
&lt;td&gt;Turn detection handled server-side&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tool/function-calling via event schema&lt;/td&gt;
&lt;td&gt;Tool-calling wired to your agent, invoked in the conversation flow&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Audio buffers you commit and clear&lt;/td&gt;
&lt;td&gt;Stream raw audio frames over the socket&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The shift in one sentence: instead of configuring a session with a wall of parameters and events, you define an agent as a durable object, then open a realtime connection to it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Create the agent
&lt;/h3&gt;

&lt;p&gt;Define it once with a single API call:&lt;/p&gt;

&lt;p&gt;curl -X POST &lt;a href="https://agents.assemblyai.com/v1/agents" rel="noopener noreferrer"&gt;https://agents.assemblyai.com/v1/agents&lt;/a&gt; \&lt;br&gt;
-H "Authorization: $ASSEMBLYAI_API_KEY" \&lt;br&gt;
-H "Content-Type: application/json" \&lt;br&gt;
-d '{&lt;br&gt;
"name": "Quickstart Assistant",&lt;br&gt;
"system_prompt": "You are a friendly assistant having a casual voice conversation. Keep replies short and natural.",&lt;br&gt;
"greeting": "Hey there, what can I help with?",&lt;br&gt;
"voice": { "voice_id": "alba" }&lt;br&gt;
}'&lt;/p&gt;

&lt;p&gt;That returns an agent_id. To go live, you connect to it over a realtime WebSocket by referencing that agent_id — and the same agent works unchanged from a server, a browser, or a phone. Define once, connect anywhere. For a fuller build, walk through &lt;a href="https://www.assemblyai.com/blog/how-to-build-with-voice-agent-api" rel="noopener noreferrer"&gt;how to build with the Voice Agent API&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  The audio gotcha worth knowing before you ship
&lt;/h3&gt;

&lt;p&gt;This one trips people up. For best accuracy, feed the model raw audio. If you're capturing in a browser, disable noiseSuppression and autoGainControl in your getUserMedia constraints — but keep echoCancellation on. Aggressive client-side processing is tuned to make audio sound nice to humans, and in doing so it smears the very signal a transcription model relies on. You want the microphone's honest output going over the wire, not a cleaned-up version that's lost detail. Turning that processing off is often the single biggest accuracy win in a migration.&lt;/p&gt;

&lt;h3&gt;
  
  
  Sessions and resumption
&lt;/h3&gt;

&lt;p&gt;You don't rebuild an agent to change its behavior. Because configuration is live, you can adjust the system prompt or swap context mid-conversation without tearing down the connection — handy for escalating tone, injecting a looked-up account detail, or handing off between skills. And since the agent is a persistent object rather than an ephemeral session, reconnecting a dropped call is a matter of connecting to the same agent_id again, not reconstructing state from scratch.&lt;/p&gt;

&lt;p&gt;That's the whole migration. Point your existing audio pipeline at one WebSocket, define your agent once, fix your browser audio constraints, and you're running.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;You don't have to take the benchmark numbers on faith.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.assemblyai.com/products/voice-agents" rel="noopener noreferrer"&gt;&lt;strong&gt;Talk to a Live Agent&lt;/strong&gt;&lt;/a&gt; and judge latency and interruption handling with your own voice.&lt;/li&gt;
&lt;li&gt;Read the &lt;a href="https://www.assemblyai.com/docs" rel="noopener noreferrer"&gt;docs&lt;/a&gt; to see the full agent and WebSocket reference.&lt;/li&gt;
&lt;li&gt;Check &lt;a href="https://www.assemblyai.com/pricing" rel="noopener noreferrer"&gt;pricing&lt;/a&gt; to confirm the flat $4.50/hr math against your current OpenAI bill.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Frequently asked questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What is the best alternative to the OpenAI Realtime API?
&lt;/h3&gt;

&lt;p&gt;It depends on what you're optimizing for, but for production voice agents where transcription accuracy and predictable cost matter most, AssemblyAI's Voice Agent API is the closest drop-in. It uses dedicated models per step behind one WebSocket, posts 6.99% WER on Pipecat's benchmark, and bills at a flat $4.50/hr. Gemini Live fits teams deep in Google Cloud, and ElevenLabs fits cases where TTS output quality outweighs input accuracy.&lt;/p&gt;

&lt;h3&gt;
  
  
  How much does the OpenAI Realtime API cost?
&lt;/h3&gt;

&lt;p&gt;It's token-priced and variable. The flagship gpt-realtime-2.1 runs about $32 per million audio input tokens and $64 per million output tokens — roughly $0.05 per minute at the base rate. Because conversation context gets re-processed each turn, real bills commonly run 2x to 5x higher on longer calls. There's a cheaper gpt-realtime-2.1-mini at around $0.016 per minute. The catch is predictability: your cost per call climbs with call length.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is there a cheaper alternative to OpenAI Realtime?
&lt;/h3&gt;

&lt;p&gt;"Cheaper" is less useful than "predictable." AssemblyAI's Voice Agent API is a flat $4.50/hr all-in — STT, LLM, and TTS on one bill — so cost doesn't drift with call length the way token-based pricing does. Open-source options like Moshi or Qwen-Omni have no per-minute fee, but you take on the hosting, scaling, and accuracy-tuning costs yourself.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do I migrate from OpenAI Realtime to AssemblyAI?
&lt;/h3&gt;

&lt;p&gt;Reuse your existing audio capture and transport. Create an agent once with a single POST (name, system prompt, greeting, voice), then connect to the returned agent_id over a realtime WebSocket. Replace the 30+ event-type handling with a single send-audio/receive-message loop. One migration tip: if you capture audio in the browser, disable noiseSuppression and autoGainControl while keeping echoCancellation on, since aggressive client-side processing hurts transcription accuracy.&lt;/p&gt;

&lt;h3&gt;
  
  
  What's the difference between OpenAI Realtime and a cascaded voice pipeline?
&lt;/h3&gt;

&lt;p&gt;OpenAI Realtime is one multimodal model that handles speech-to-text, reasoning, and speech output all at once — but transcription is just one job among many, which is where hallucination on messy audio creeps in. A cascaded (dedicated-models-per-step) pipeline like AssemblyAI's routes each stage to a purpose-built model, so you get category-leading transcription accuracy, the ability to feed in live context, and the option to tune each layer independently — all still behind one WebSocket.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>voiceassistant</category>
      <category>api</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Best Voice Agent API (2026): Accuracy, Cost &amp; Options</title>
      <dc:creator>Mart Schweiger</dc:creator>
      <pubDate>Wed, 12 Aug 2026 15:00:15 +0000</pubDate>
      <link>https://dev.to/martschweiger/best-voice-agent-api-2026-accuracy-cost-options-10o7</link>
      <guid>https://dev.to/martschweiger/best-voice-agent-api-2026-accuracy-cost-options-10o7</guid>
      <description>&lt;p&gt;Search "best voice agent API" and you'll get a beauty contest. Everyone's the fastest, everyone's the easiest, everyone has the slickest dashboard. It's noise. And it points you at the wrong question.&lt;/p&gt;

&lt;p&gt;Here's the question that actually decides whether your voice agent works in production: does it hear your user correctly? Because a voice agent is a pipeline — speech-to-text feeds the LLM, the LLM decides what to say, and text-to-speech says it. If the first step gets the words wrong, everything downstream is confidently answering a question the user never asked. The model can be brilliant. The voice can be gorgeous. Doesn't matter. Garbage in, garbage out — at the speed of conversation.&lt;/p&gt;

&lt;p&gt;So the best voice agent API isn't the flashiest one. It's the one built on the most accurate foundation. Let's talk about how to actually evaluate that, and where the real options land.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;What "best voice agent API" actually means&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;When most people picture a voice agent, they picture the LLM — the "brain." That's the fun part. But the brain only ever sees text. It never hears your customer. It reads a transcript that some speech-to-text model produced a few hundred milliseconds ago, and it acts on that transcript as if it were ground truth.&lt;/p&gt;

&lt;p&gt;Think about what that means when someone says their name is "Nguyen," reads back a 16-digit account number, or talks through a drive-thru with road noise in the background. If the STT mishears "Nguyen" as "when," the agent doesn't know it misheard. It just runs with the wrong input. The user has to repeat themselves, the conversation stalls, and trust evaporates.&lt;/p&gt;

&lt;p&gt;That's why we frame the whole category around accuracy. The &lt;a href="https://www.assemblyai.com/blog/ai-voice-agents" rel="noopener noreferrer"&gt;foundation of any voice agent&lt;/a&gt; is how well it hears — because that's the one error every other component inherits and none of them can fix. Pick your API on that basis and most of the "which platform is prettiest" debate stops mattering.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;The criteria that decide production quality&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;A demo that works once in a quiet room tells you almost nothing. Here's what separates a voice agent that ships from one that gets pulled after week one:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Speech accuracy on real audio.&lt;/strong&gt; Not clean, scripted benchmark clips — messy conversational audio with names, account numbers, accents, cross-talk, and background noise. This is the tiebreaker, and we'll come back to it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Turn detection.&lt;/strong&gt; Knowing when the user has actually finished speaking versus just pausing mid-thought. Get this wrong and your agent either interrupts constantly or sits there dead-air awkward.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Interruption and barge-in handling.&lt;/strong&gt; Real conversations aren't turn-based. People cut in. A production agent has to stop talking the instant the user does and pick up the new thread.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Latency.&lt;/strong&gt; End-to-end, mouth to ear. Above roughly a second and a half, the conversation starts feeling like a bad phone connection and people talk over the agent.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Billing model.&lt;/strong&gt; One unified bill, or three vendors you're metering and reconciling separately? This is a cost-predictability question as much as an accounting one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Concurrency and scale.&lt;/strong&gt; Can you run 500 calls at once during a Monday-morning spike, or are you hitting a concurrency cap and rate limits?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Developer experience.&lt;/strong&gt; Standard protocols and clear docs, or an SDK maze and a proprietary conversation-design tool you have to bend your app around?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Weight these however your use case demands. But notice that accuracy sits underneath most of them — bad transcripts make turn detection harder, force retries that add latency, and generate the "sorry, can you repeat that?" loops that wreck the experience.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;API vs platform: which are you actually buying?&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;This is the fork in the road, and it's worth being honest about.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No-code platforms&lt;/strong&gt; — Vapi, Retell, Synthflow — give you an opinionated way to design a conversation. Drag-and-drop flows, hosted orchestration, a dashboard for non-engineers. If you're a small team that needs a working phone agent by Friday and your logic is fairly standard, that's a genuinely good fit. No shame in it. You're trading control for speed, and sometimes speed is exactly what you need.&lt;/p&gt;

&lt;p&gt;But here's where it gets interesting. Those same platforms bundle a speech-to-text vendor you often can't choose, wrap your logic in their conversation model, and constrain what you can customize when your requirements get weird — and in production, requirements always get weird. You inherit their accuracy, their latency budget, and their abstractions. When &lt;a href="https://www.assemblyai.com/blog/where-voice-agent-stacks-start-showing-their-limits" rel="noopener noreferrer"&gt;voice agent stacks start showing their limits&lt;/a&gt;, it's usually because the thing you most need to tune is the thing the platform hid from you.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A voice agent API&lt;/strong&gt; gives you the pipeline and the control. You own the logic, the prompts, the integration, and — critically — the foundation your agent hears through. That's the path if you're building something differentiated, if accuracy is load-bearing, or if you just don't want a vendor's opinions baked into your product.&lt;/p&gt;

&lt;p&gt;The reframe: don't ask "which platform wins." Ask "am I buying a control surface or a black box?" — and then, whichever you pick, ask what it's hearing your users through.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;How the main options compare&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Let's be objective about the field. Every option here is a real, capable product. They just optimize for different things.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AssemblyAI Voice Agent API.&lt;/strong&gt; One WebSocket handles the full STT → LLM → TTS pipeline, built on Universal-3.5 Pro Realtime for speech accuracy. Flat $4.50/hour all-in, one bill. Around one second end-to-end latency, unlimited concurrency, no rate limits, and standard JSON over WebSocket with no SDK required. You can update the system prompt, tools, and settings mid-conversation without reconnecting. It's &lt;a href="https://www.assemblyai.com/products/voice-agent-api" rel="noopener noreferrer"&gt;invisible infrastructure&lt;/a&gt; — the foundation your agent stands on, not another dashboard to learn.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;OpenAI Realtime.&lt;/strong&gt; A &amp;nbsp;multimodal model that also does voice. But that's the framing to keep in mind: it's a model that speaks, not a pipeline engineered end-to-end for conversation quality. It's token-priced, so your cost varies with usage in ways that are hard to forecast, and you're assembling the production concerns — turn-taking, barge-in, scale — yourself.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Deepgram.&lt;/strong&gt; Ships a unified voice agent API. . The differentiator comes down to accuracy on real agent audio, which is where the benchmark numbers below get pointed. We put the full breakdown in our &lt;a href="https://www.assemblyai.com/blog/assemblyai-vs-deepgram-best-voice-agent-api" rel="noopener noreferrer"&gt;AssemblyAI vs Deepgram comparison&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ElevenLabs.&lt;/strong&gt; If lifelike TTS is your top priority, they're have a good option. The tradeoffs are concurrency (capped around 30, which matters at scale) and, on the input side, transcription accuracy. More in our &lt;a href="https://www.assemblyai.com/blog/assemblyai-voice-agent-api-vs-elevenlabs-conversational-ai" rel="noopener noreferrer"&gt;Voice Agent API vs ElevenLabs Conversational AI writeup&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Vapi and Retell.&lt;/strong&gt; The leading no-code platforms. Fast to a working agent, great for standard flows and non-engineering teams. You trade control and vendor choice for that speed, per the section above.&lt;/p&gt;

&lt;p&gt;One more thing worth naming: &lt;strong&gt;LiveKit and Pipecat&lt;/strong&gt; are open-source pipeline frameworks, not competitors. We ship drop-in plugins for both. If you're already building on them, you can slot AssemblyAI in as the STT layer without rearchitecting anything.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Option&lt;/th&gt;
&lt;th&gt;Foundation&lt;/th&gt;
&lt;th&gt;Pricing&lt;/th&gt;
&lt;th&gt;Concurrency&lt;/th&gt;
&lt;th&gt;Best for&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;AssemblyAI Voice Agent API&lt;/td&gt;
&lt;td&gt;Universal-3.5 Pro Realtime&lt;/td&gt;
&lt;td&gt;Flat $4.50/hr all-in&lt;/td&gt;
&lt;td&gt;Unlimited&lt;/td&gt;
&lt;td&gt;Accuracy-critical production agents&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OpenAI Realtime&lt;/td&gt;
&lt;td&gt;Multimodal model&lt;/td&gt;
&lt;td&gt;Token-priced (variable)&lt;/td&gt;
&lt;td&gt;Model-dependent&lt;/td&gt;
&lt;td&gt;Multimodal prototyping&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Deepgram&lt;/td&gt;
&lt;td&gt;Own STT&lt;/td&gt;
&lt;td&gt;Usage-based&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;Developer-controlled pipelines&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ElevenLabs&lt;/td&gt;
&lt;td&gt;Own STT + best-in-class TTS&lt;/td&gt;
&lt;td&gt;Usage-based&lt;/td&gt;
&lt;td&gt;~30&lt;/td&gt;
&lt;td&gt;Voice-output-first agents&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Vapi / Retell&lt;/td&gt;
&lt;td&gt;Bundled STT vendor&lt;/td&gt;
&lt;td&gt;Platform + passthrough&lt;/td&gt;
&lt;td&gt;Platform-dependent&lt;/td&gt;
&lt;td&gt;No-code, standard flows&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Why speech accuracy is the tiebreaker&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Here's the part everyone skips, and it's the part that decides production quality.&lt;/p&gt;

&lt;p&gt;Pipecat runs an open STT benchmark built from real agent conversations — the messy, name-heavy, number-heavy audio your agents actually field, not scripted clean speech. Lower is better. On word error rate, Universal-3.5 Pro Realtime comes in at &lt;strong&gt;6.99%&lt;/strong&gt;, against Deepgram Flux at 15.58%, ElevenLabs Scribe v2 at 9.76%, and Google Chirp3 at 9.04%.&lt;/p&gt;

&lt;p&gt;Sit with that gap for a second. Roughly half the word errors of the next-closest option on realistic agent audio. Every one of those avoided errors is a "can you repeat that?" that never happens.&lt;/p&gt;

&lt;p&gt;Now the number that matters even more for agents: &lt;strong&gt;entity error rate&lt;/strong&gt; — how often the model botches the things that carry the meaning, like names, account numbers, and confirmation codes. Universal-3.5 Pro Realtime lands at &lt;strong&gt;15.31%&lt;/strong&gt;. Deepgram Flux: 50.50%. When someone reads back a credit card number or spells their last name, that's the metric deciding whether your agent got it right or is about to charge the wrong account.&lt;/p&gt;

&lt;p&gt;And there's a lever most stacks don't have. Pass the agent's current question to the transcription model as context — we call it agent_context — and word error rate drops another &lt;strong&gt;10.2%&lt;/strong&gt;, measured across 20,000 voice agent audio files. The intuition is simple: if the agent just asked "what's your ZIP code?", the model should expect five digits, not a sentence. Context makes it hear better. You can see the full methodology on our &lt;a href="https://www.assemblyai.com/benchmarks" rel="noopener noreferrer"&gt;benchmarks page&lt;/a&gt; and the model details in the &lt;a href="https://www.assemblyai.com/blog/universal-3-5-pro-realtime" rel="noopener noreferrer"&gt;Universal-3.5 Pro Realtime announcement&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This is the whole argument in one place. When two APIs both promise low latency and a nice developer experience, accuracy on real audio is what's left to actually choose on.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;What you'll pay&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Cost in this category is sneaky, because most options make you assemble it yourself.&lt;/p&gt;

&lt;p&gt;Wire up a voice agent from separate STT, LLM, and TTS providers and you're metering three usage-based bills, each priced differently — per-minute here, per-token there, per-character somewhere else — and each with its own concurrency terms and overage behavior. Your cost per conversation becomes a function of how chatty the call was, how long the LLM's replies ran, and which tier you tripped into that month. Forecasting it is a spreadsheet exercise, and it drifts.&lt;/p&gt;

&lt;p&gt;AssemblyAI's Voice Agent API is a flat &lt;strong&gt;$4.50 per hour, all-in&lt;/strong&gt; — STT, LLM, and TTS on one bill. An hour of conversation costs $4.50 whether the user rambled or kept it tight. That predictability is the point: you can price your own product against it without modeling three vendors' token math. Full numbers are on the &lt;a href="https://www.assemblyai.com/pricing" rel="noopener noreferrer"&gt;pricing page&lt;/a&gt;, and new accounts get &lt;strong&gt;$50 in free credits&lt;/strong&gt; to start.&lt;/p&gt;

&lt;p&gt;Token-priced options like OpenAI Realtime can look cheap in a demo and then scale in ways that surprise you, because your bill tracks token volume rather than wall-clock conversation time. Neither model is wrong — but know which one you're signing up for before you're at 10,000 calls a day.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Getting started&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;The developer experience is deliberately boring, in the best way. One WebSocket connection, standard JSON, no SDK to install and no proprietary framework to learn. If you can open a socket, you can build on it — which is also &lt;a href="https://www.assemblyai.com/blog/why-assemblyais-voice-agent-api-is-designed-for-coding-agents" rel="noopener noreferrer"&gt;why it works natively with coding agents&lt;/a&gt; like Claude Code.&lt;/p&gt;

&lt;p&gt;Here's a whole agent:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://agents.assemblyai.com/v1/agents &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: &lt;/span&gt;&lt;span class="nv"&gt;$ASSEMBLYAI_API_KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
    "name": "Quickstart Assistant",
    "system_prompt": "You are a friendly assistant having a casual voice conversation. Keep replies short and natural.",
    "greeting": "Hey there, what can I help with?",
    "voice": { "voice_id": "alba" }
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. Name it, prompt it, give it a greeting and a voice, and you've got a running agent on a foundation that hears your users right. Want to go deeper on tools, live configuration, and production patterns? The &lt;a href="https://www.assemblyai.com/blog/how-to-build-with-voice-agent-api" rel="noopener noreferrer"&gt;build guide&lt;/a&gt; and the &lt;a href="https://www.assemblyai.com/blog/introducing-our-voice-agent-api" rel="noopener noreferrer"&gt;launch announcement&lt;/a&gt; walk through the rest.&lt;/p&gt;

&lt;p&gt;The best voice agent API is the one that gets the words right the first time, every time, at scale, on one bill. Everything else is a preference. Accuracy is the foundation.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Frequently asked questions&lt;/strong&gt;
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;What is the best voice agent API?&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;The best voice agent API is the one that hears your users most accurately, because speech-to-text is the foundation the entire agent stands on — if the transcript is wrong, the LLM responds to the wrong thing. On Pipecat's open benchmark of real agent conversations, AssemblyAI's Voice Agent API, built on Universal-3.5 Pro Realtime, posts a 6.99% word error rate, roughly half the next-closest option, which is why we'd point accuracy-critical builders there. The "best" for you also depends on your priorities across turn-taking, latency, cost, and concurrency.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;What's the difference between a voice agent API and a voice agent platform?&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;A platform (like Vapi or Retell) gives you an opinionated, often no-code way to design conversations, trading developer control for speed. An API gives you the pipeline and full control over logic, prompts, and the STT foundation your agent hears through. Platforms fit standard flows and non-engineering teams; APIs fit differentiated products where accuracy and customization are load-bearing.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;How much does a voice agent API cost?&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;**** It depends on the pricing model. Assembling STT, LLM, and TTS from separate vendors means three usage-based bills that vary with conversation length and token volume. AssemblyAI's Voice Agent API is a flat $4.50 per hour, all-in on one bill, so cost per conversation is predictable regardless of how the call goes. Token-priced options like OpenAI Realtime vary with usage and can be harder to forecast at scale.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Which voice agent API is most accurate?&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;**** On Pipecat's open STT benchmark of real agent conversations, Universal-3.5 Pro Realtime leads with a 6.99% word error rate versus Deepgram Flux at 15.58%, ElevenLabs Scribe v2 at 9.76%, and Google Chirp3 at 9.04%. It's even further ahead on entity error rate — the names, account numbers, and codes that carry meaning — at 15.31% versus Deepgram Flux's 50.50%. Passing the agent's question as context cuts word error rate another 10.2%.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Do I need separate STT, LLM, and TTS providers?&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;**** No. You can wire up three vendors yourself for maximum control over each layer, but that means orchestrating three systems and reconciling three bills. A unified voice agent API handles the full STT → LLM → TTS pipeline over a single connection, on one bill, which removes the orchestration and cost-forecasting overhead for most teams.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Can I change the agent's behavior mid-conversation?&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Yes. AssemblyAI's Voice Agent API supports live configuration — you can update the system prompt, tools, and settings mid-conversation without reconnecting, so the agent can adapt as the call moves between steps without dropping the session.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>voiceassistant</category>
      <category>api</category>
      <category>webdev</category>
    </item>
    <item>
      <title>The Hard Cases in Speaker Diarization: Overlap &amp; Noise</title>
      <dc:creator>Mart Schweiger</dc:creator>
      <pubDate>Wed, 12 Aug 2026 15:00:09 +0000</pubDate>
      <link>https://dev.to/martschweiger/the-hard-cases-in-speaker-diarization-overlap-noise-1d5g</link>
      <guid>https://dev.to/martschweiger/the-hard-cases-in-speaker-diarization-overlap-noise-1d5g</guid>
      <description>&lt;p&gt;Speaker diarization looks solved right up until it isn't. Point a decent model at a clean two-person phone call and it'll nail who said what, turn after turn, with almost no effort. That's the demo everyone shows.&lt;/p&gt;

&lt;p&gt;Now drop the same model into a dinner party. Four people, a cross-talk moment, somebody laughing mid-sentence, a "yeah, exactly" fired off while the host is still finishing a thought. The clean-call model falls apart—and it falls apart in specific, nameable ways.&lt;/p&gt;

&lt;p&gt;This post is that list. Not "diarization is hard, be careful," but the actual taxonomy: the exact cases that break systems, why each one breaks them, and—the part almost nobody writes down—how you'd even know it's failing. Because here's the uncomfortable part: the metric most people quote barely notices some of the worst failures.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Why diarization is still hard&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Diarization is two jobs stapled together. First, figure out how many distinct speakers exist. Second, draw the boundaries that say person A talked from here to here, then person B took over. Do both perfectly and you get a clean speaker-labeled transcript. Miss on either and the errors compound.&lt;/p&gt;

&lt;p&gt;On a clean call, both jobs are easy. Two clearly different voices, one talking at a time, generous pauses between turns—the acoustic signal practically labels itself. If you want the full mechanics of how this works under the hood, we wrote a &lt;a href="https://www.assemblyai.com/blog/what-is-speaker-diarization-and-how-does-it-work" rel="noopener noreferrer"&gt;complete explainer on speaker diarization&lt;/a&gt; that walks through the pipeline.&lt;/p&gt;

&lt;p&gt;Real conversations don't cooperate. People interrupt. They react. They talk over each other. They mumble from across a room. And the moment reality gets messy, the failures sort into a handful of recurring categories. Let's name them.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Overlapped speech&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Two people talking at once is the single biggest source of diarization error. Full stop.&lt;/p&gt;

&lt;p&gt;Here's why it's so brutal. Most diarization systems are built on an assumption baked deep into their architecture—that at any given instant, exactly one person is speaking. That assumption makes the math tractable. It also makes overlap a blind spot, because when two voices land in the same slice of audio, the system has to pick one. The other speaker's words just vanish.&lt;/p&gt;

&lt;p&gt;Some systems handle overlap by masking it—detecting the overlapped region and excluding it from analysis so it doesn't corrupt the clustering. That keeps the speaker labels cleaner, sure. But think about what you just did: you threw away the audio where two people were talking. Every word in that region is gone from the transcript. For a meeting-summarization use case or a call-center QA workflow, those are often the words that matter most—the interruption, the objection, the correction.&lt;/p&gt;

&lt;p&gt;So what does good look like? A model that treats overlap as a first-class event, not noise to be filtered. It should attribute overlapping speech to the right speakers and keep both sets of words, instead of picking a winner and dropping the loser. That's a real bar, and most legacy pipelines don't clear it.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Short turns and back-channels&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;"Yep." "No." "Right." A quick laugh. A "wait, what?" thrown in while someone else holds the floor.&lt;/p&gt;

&lt;p&gt;These are back-channels—the tiny verbal reactions that keep a conversation moving—and they wreck diarization in a sneaky way. The system credits them to whoever was already speaking. Person A is mid-sentence, Person B says "exactly," and the transcript hands that "exactly" to Person A. The turn was too short and too embedded to trigger a speaker switch, so it gets swallowed.&lt;/p&gt;

&lt;p&gt;Now here's the part that should genuinely change how you evaluate diarization. &lt;strong&gt;You might never see this failure in your metrics.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The standard metric, DER (diarization error rate), weights errors by seconds of audio. A back-channel is maybe 300 milliseconds. Misattribute it and DER shrugs—it barely moves. So you can have a system that systematically misfiles every short reaction in your data and still posts a DER that looks great on a slide.&lt;/p&gt;

&lt;p&gt;We ran the numbers on this and the gap is almost comical. In our own testing, a catastrophic attribution output—words credited to the wrong people all over the place—scored just &lt;strong&gt;15.1% DER&lt;/strong&gt;. Sounds fine, right? The same output scored &lt;strong&gt;30.7% cpWER&lt;/strong&gt;. Twice as bad by the metric that actually tracks whether each person's words landed in the right place.&lt;/p&gt;

&lt;p&gt;It cuts the other way too. A flawless transcript—every word right, every word attributed correctly—scored &lt;strong&gt;51.5% DER&lt;/strong&gt; but &lt;strong&gt;0.0% cpWER&lt;/strong&gt;. DER called a perfect result a failure. If you're optimizing for DER, you're optimizing for the wrong thing, and short turns are exactly where that bites. We broke this whole comparison down in &lt;a href="https://www.assemblyai.com/blog/der-vs-cpwer" rel="noopener noreferrer"&gt;DER vs cpWER&lt;/a&gt; if you want the full teardown.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Background noise and far-field audio&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Not every conversation happens through a headset an inch from someone's mouth. A lot of the most valuable audio is captured in the worst possible conditions.&lt;/p&gt;

&lt;p&gt;Think about a drive-thru: engine noise, wind, a speaker box, someone in the back seat. Or a self-service kiosk in a busy lobby. Or a conference room where the mic is in the center of the table and half the speakers are eight feet away. Or a phone on speaker sitting on a desk. This is far-field audio, and it stacks two problems at once.&lt;/p&gt;

&lt;p&gt;First, the target voices are quieter and smeared by room reverberation, which blurs the acoustic fingerprint the system uses to tell speakers apart. Second, background noise and background speech—a TV, a nearby conversation, ambient chatter—give the model extra "voices" to trip over. It can invent speakers that don't exist or fail to separate the ones that do.&lt;/p&gt;

&lt;p&gt;The fix isn't just a better microphone you don't control. It's a model that can suppress background speech and noise before it corrupts the speaker labels—tuned for whether the audio is near-field or far-field, because those are genuinely different acoustic problems. A single-mic room recording and a multichannel setup call for different strategies too; if you've got separate audio channels per speaker, &lt;a href="https://www.assemblyai.com/blog/multichannel-speaker-diarization" rel="noopener noreferrer"&gt;multichannel diarization&lt;/a&gt; sidesteps a lot of this by design.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Getting the speaker count wrong&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Every diarization output has a hidden claim baked into it: this conversation had N speakers. Get N wrong and everything downstream is wrong too.&lt;/p&gt;

&lt;p&gt;There are two ways to blow it. &lt;strong&gt;Merging&lt;/strong&gt; is when the system collapses two people into one—two similar-sounding colleagues become "Speaker A," and now half of one person's words are filed under the other. &lt;strong&gt;Splitting&lt;/strong&gt; is the opposite: one person's voice shifts—they get excited, they lower their voice, the line quality changes—and the system decides that's a new person, so "Speaker A" and "Speaker C" are actually the same human.&lt;/p&gt;

&lt;p&gt;Similar-sounding voices make merging worse. Two men with comparable pitch and cadence, or two people with the same regional accent, sit close together in the model's internal space, and the clustering can't pull them apart.&lt;/p&gt;

&lt;p&gt;This is why you need a roster metric—speaker count error—that asks a blunt question: did the system get the number of people right? It's separate from word-level accuracy, and it catches structural failures that a per-word score can smear across the whole transcript. Merges and splits are cheap to make and expensive to ignore.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;How to tell if your diarization is actually failing&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;By now the theme should be clear. The failures that hurt most—short turns, misattribution, cross-talk—are the ones DER is worst at seeing. So if you only track DER, you're flying blind on your hardest cases.&lt;/p&gt;

&lt;p&gt;Here's the evaluation stack that actually works, in two numbers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;cpWER (concatenated minimum-permutation word error rate).&lt;/strong&gt; For each person in the conversation, what fraction of their words did the system get wrong—misrecognized, dropped, or credited to someone else? You concatenate everything each speaker said, find the speaker-mapping that gives the best possible match, and score the errors. Because it's grounded in words, not seconds, a misattributed "exactly" costs exactly as much as it should. cpWER is the number that punishes the failures your users will actually notice.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Speaker count error.&lt;/strong&gt; The roster metric from the last section—did we get the number of people right? Track it alongside cpWER so structural merges and splits can't hide.&lt;/p&gt;

&lt;p&gt;That's the honest scorecard: cpWER for word-level attribution, speaker count error for the roster. If you want to run this yourself, we published a companion walkthrough on &lt;a href="https://www.assemblyai.com/blog/der-vs-cpwer" rel="noopener noreferrer"&gt;measuring cpWER in Python&lt;/a&gt; so you can score your own audio instead of trusting a vendor's slide.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;How modern models handle the hard cases&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Naming the failures is one thing. Building around them is another. Here's how we approach it.&lt;/p&gt;

&lt;p&gt;The biggest architectural shift is refusing to treat transcription and diarization as separate steps. &lt;a href="https://www.assemblyai.com/blog/universal-3-5-pro-async" rel="noopener noreferrer"&gt;Universal-3.5 Pro&lt;/a&gt; produces the transcript and the speaker labels &lt;strong&gt;jointly&lt;/strong&gt;—one model, one pass—instead of transcribing first and then bolting speaker labels on afterward. That joint modeling is exactly what lets it catch the short turns, rapid back-and-forth, and overlapped speech that break two-stage pipelines. And it's optimized for cpWER, not DER, because that's the metric that reflects real quality.&lt;/p&gt;

&lt;p&gt;The numbers back it up. Universal-3.5 Pro averages &lt;strong&gt;30.17 cpWER&lt;/strong&gt; against Deepgram Nova-3 English at 37.92, ElevenLabs Scribe v2 at 35.26, and Gladia at 36.87. It runs async at &lt;strong&gt;$0.21/hour&lt;/strong&gt;. Getting started is about as light as it gets:&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;assemblyai&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;aai&lt;/span&gt;

&lt;span class="n"&gt;aai&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;settings&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;YOUR_API_KEY&amp;gt;&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;aai&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;TranscriptionConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;speaker_labels&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;transcript&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;aai&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Transcriber&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;transcribe&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="n"&gt;config&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;transcript&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;utterances&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;Speaker &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;utterance&lt;/span&gt;&lt;span class="p"&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;utterance&lt;/span&gt;&lt;span class="p"&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Real-time is where diarization gets genuinely thorny, because you can't cluster speakers you haven't heard yet. When someone new talks 30 seconds in, a naive streaming system either guesses early and gets it wrong or stalls waiting for certainty.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.assemblyai.com/blog/universal-3-5-pro-realtime" rel="noopener noreferrer"&gt;Universal-3.5 Pro Realtime&lt;/a&gt; handles this with streaming diarization plus revision. It labels speakers live as the audio comes in, then—once it has the full picture—re-clusters and sends a single correction within about &lt;strong&gt;half a second&lt;/strong&gt; of the stream ending. You get low-latency labels during the call and a cleaned-up roster the moment it's over, handling up to &lt;strong&gt;10 speakers&lt;/strong&gt;. We wrote up the mechanics in &lt;a href="https://www.assemblyai.com/blog/streaming-speaker-diarization" rel="noopener noreferrer"&gt;streaming speaker diarization&lt;/a&gt; and the &lt;a href="https://www.assemblyai.com/blog/streaming-diarization-major-upgrade" rel="noopener noreferrer"&gt;major streaming upgrade&lt;/a&gt; that made this possible.&lt;/p&gt;

&lt;p&gt;For the far-field and noise problems, voice_focus with near_field and far_field settings suppresses background speech and noise before it can poison the speaker labels—the difference between a drive-thru transcript that's usable and one that's full of phantom speakers.&lt;/p&gt;

&lt;p&gt;Want to see it on your own audio? Poke at it in the &lt;a href="https://www.assemblyai.com/playground" rel="noopener noreferrer"&gt;playground&lt;/a&gt;, read the &lt;a href="https://www.assemblyai.com/features/speaker-diarization" rel="noopener noreferrer"&gt;speaker diarization feature page&lt;/a&gt; for the full spec, or just wire it into your stack and run your worst files through it.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Frequently asked questions&lt;/strong&gt;
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;What are the limitations of speaker diarization?&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;The main limitations of speaker diarization are overlapping speech, short turns and back-channels, background noise and far-field audio, speaker-count errors, and similar-sounding voices. Overlapping speech is the single biggest source of error, because most systems assume one person talks at a time and drop words when two voices collide. Short reactions like "yep" or "no" get credited to the wrong speaker, and noisy or far-field audio blurs the acoustic differences the system relies on. Modern joint transcription-and-diarization models built and measured for cpWER handle these cases far better than legacy two-stage pipelines.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;What is overlapping speech?&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Overlapping speech is when two or more people talk at the same time, so their voices land in the same slice of audio. It's the #1 source of diarization error because most systems are built to attribute each moment to a single speaker, which forces them to pick one voice and lose the other's words. Systems that mask overlap keep cleaner speaker labels but delete the transcribed words in the overlapped region—often the interruptions and corrections that matter most.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;How does background noise affect speaker diarization?&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Background noise degrades diarization by masking the acoustic features a system uses to tell speakers apart and by adding stray sounds it can mistake for extra speakers. Far-field audio—kiosks, drive-thrus, conference rooms, speakerphones—makes it worse, since distance and reverberation blur each voice's fingerprint. Noise suppression tuned for near-field versus far-field capture, like AssemblyAI's voice_focus setting, strips out background speech and noise before it corrupts the speaker labels.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Why do diarization systems get the number of speakers wrong?&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Diarization systems miscount speakers in two ways: merging and splitting. Merging collapses two similar-sounding people into one speaker, while splitting treats one person's changing voice—excitement, volume shifts, line-quality changes—as a brand-new speaker. Similar-sounding voices make merging especially common. Tracking speaker count error as a dedicated roster metric is the only reliable way to catch these structural mistakes.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;How do you measure speaker diarization accuracy?&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Measure diarization accuracy with cpWER (concatenated minimum-permutation word error rate) plus speaker count error, not DER alone. cpWER asks, for each person, what fraction of their words the system got wrong—misrecognized, dropped, or credited to someone else—so it catches short-turn misattribution that DER's seconds-based weighting misses. In our testing a catastrophic attribution output scored just 15.1% DER but 30.7% cpWER, while a flawless transcript scored 51.5% DER yet 0.0% cpWER—proof that DER can grade a perfect result as a failure and a broken one as fine.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>machinelearning</category>
      <category>speechtotext</category>
      <category>python</category>
    </item>
    <item>
      <title>Does Whisper Do Speaker Diarization? How to Add It</title>
      <dc:creator>Mart Schweiger</dc:creator>
      <pubDate>Wed, 12 Aug 2026 14:59:43 +0000</pubDate>
      <link>https://dev.to/martschweiger/does-whisper-do-speaker-diarization-how-to-add-it-2b90</link>
      <guid>https://dev.to/martschweiger/does-whisper-do-speaker-diarization-how-to-add-it-2b90</guid>
      <description>&lt;h2&gt;
  
  
  &lt;strong&gt;Does Whisper do speaker diarization?&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;No. Whisper is OpenAI's automatic speech recognition (ASR) model — it turns audio into text, and that's the whole job. It doesn't tell you who spoke. Hand it a two-person interview and you'll get an accurate transcript with zero speaker labels, no "Speaker A" and "Speaker B," nothing that separates one voice from another.&lt;/p&gt;

&lt;p&gt;So if you want &lt;a href="https://www.assemblyai.com/blog/what-is-speaker-diarization-and-how-does-it-work" rel="noopener noreferrer"&gt;speaker diarization&lt;/a&gt; — the "who spoke when" layer on top of the words — you have to bolt it on yourself. Most people reach for pyannote.audio (or WhisperX, which wraps a similar idea). That works. But it's a pipeline you assemble and own, and the seams show up exactly where diarization is hardest: short turns, rapid back-and-forth, and overlapping speech.&lt;/p&gt;

&lt;p&gt;Let's build the DIY version first, then measure it honestly.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;How people add diarization to Whisper&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;The standard recipe has three stages. Transcribe with Whisper to get words and timestamps. Run a separate diarization model — pyannote — to get speaker segments. Then align the two: for each word, figure out which speaker segment it belongs to.&lt;/p&gt;

&lt;p&gt;Here's the shape of it in Python. This is illustrative — the point is the structure, not a production-ready alignment engine:&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="c1"&gt;# pip install openai-whisper pyannote.audio torch
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;whisper&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pyannote.audio&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Pipeline&lt;/span&gt;

&lt;span class="c1"&gt;# 1. Transcribe with Whisper (word-level timestamps)
&lt;/span&gt;&lt;span class="n"&gt;asr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;whisper&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load_model&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;large-v3&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="n"&gt;asr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;transcribe&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="n"&gt;word_timestamps&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="c1"&gt;# 2. Diarize with pyannote (needs a free Hugging Face access token)
&lt;/span&gt;&lt;span class="n"&gt;diarizer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Pipeline&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_pretrained&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pyannote/speaker-diarization-3.1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;use_auth_token&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;HF_TOKEN&amp;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="n"&gt;diarization&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;diarizer&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="c1"&gt;# 3. Align: for each Whisper word, find the diarization segment its
#    midpoint falls in, and attach that speaker label. (You write this glue.)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Looks clean. Three steps, a few dozen lines. But here's where it gets interesting — every one of those steps hides real friction.&lt;/p&gt;

&lt;p&gt;The pyannote models are gated. You need a free Hugging Face account, you have to accept the model's user conditions, and you generate an access token to pass into from_pretrained. Miss that and the download fails. It's not hard, but it's a step, and it's a credential you now manage.&lt;/p&gt;

&lt;p&gt;You'll want a GPU. Whisper's large-v3 and pyannote both run painfully slow on CPU. For anything beyond a short clip, you're provisioning and maintaining a GPU — locally or in the cloud — plus the CUDA and PyTorch versions that keep them happy.&lt;/p&gt;

&lt;p&gt;There's voice-activity detection in the mix too. pyannote leans on VAD to decide where speech is before it decides who's speaking, and tuning that on noisy or far-field audio is its own rabbit hole.&lt;/p&gt;

&lt;p&gt;And then there's the alignment glue — step 3, the innocent-looking comment. That's your code. You're mapping Whisper's word timestamps onto pyannote's speaker segments, deciding what to do when a word straddles a boundary, what to do when the diarizer and the ASR disagree about where speech even is. The midpoint heuristic in the snippet is the naive version. It works until it doesn't, and when it doesn't, you own the bug.&lt;/p&gt;

&lt;p&gt;None of this is a knock on Whisper or pyannote. They're solid open-source tools, and for an offline project or a weekend build, this pipeline is genuinely fine. The question is what happens to accuracy when the conversation gets messy — and how you'd even know.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Where the cracks show: measuring it with cpWER&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Most diarization tutorials measure quality with DER — diarization error rate. DER asks, roughly, what fraction of audio time got assigned to the wrong speaker. It's the classic academic metric, and it has a blind spot big enough to drive a truck through: it barely notices short turns, and it can wave through catastrophic attribution errors.&lt;/p&gt;

&lt;p&gt;The metric that actually reflects what your users experience is cpWER — concatenated minimum-permutation word error rate. Here's the plain-English version: for each person in the conversation, what fraction of their words did the system get wrong? Wrong meaning misrecognized, dropped, or credited to the wrong speaker. You concatenate everything each speaker actually said, compare it against what the system attributed to them (under the best speaker-to-speaker matching), and count the damage. It folds transcription errors and attribution errors into one honest number.&lt;/p&gt;

&lt;p&gt;Why does the choice of metric matter so much? Because DER and cpWER can tell completely different stories about the same output. In AssemblyAI's testing, a catastrophic attribution failure — a case where speakers got badly swapped — scored a tidy 15.1% DER while landing at 30.7% cpWER. The DER made it look like a B-plus. The cpWER told the truth: nearly a third of the words were attributed to the wrong person. If you're reading a transcript, that's the difference between "mostly right" and "who said that?"&lt;/p&gt;

&lt;p&gt;Now think about where the Whisper + pyannote pipeline is most likely to break.&lt;/p&gt;

&lt;p&gt;Short turns. When someone jumps in with a quick "Right," "No, wait," or "Exactly," that turn might be half a second long. The midpoint-alignment heuristic has almost no signal to work with, and the interjection frequently gets swallowed into the neighboring speaker's block. DER shrugs — it's a sliver of audio. cpWER counts every one of those words as misattributed.&lt;/p&gt;

&lt;p&gt;Overlap. Two people talking at once is the reality of real meetings, debates, and calls. A pipeline that runs VAD, then diarization, then alignment tends to collapse overlapped speech into a single speaker — one voice wins, the other's words vanish or get reassigned. Those dropped and misattributed words hit cpWER directly.&lt;/p&gt;

&lt;p&gt;Speaker merging. On long recordings, a diarizer can decide two people are one, folding one speaker's turns into another. DER, again, can under-penalize this depending on the timing. cpWER makes the merge obvious, because a whole speaker's worth of words landed under the wrong label.&lt;/p&gt;

&lt;p&gt;The uncomfortable takeaway: you can stand up the Whisper + pyannote pipeline, run DER, see a respectable number, and ship something that mangles exactly the moments people care about most. If you want to measure this properly on your own audio, AssemblyAI's companion walkthrough on measuring cpWER in Python shows how, and the &lt;a href="https://www.assemblyai.com/blog/der-vs-cpwer" rel="noopener noreferrer"&gt;DER vs. cpWER breakdown&lt;/a&gt; explains why the metric you pick changes the verdict.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;The managed alternative: diarization built into the model&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Here's the other way to do this. Instead of stitching an ASR model to a diarization model and writing the glue between them, you call one API that produces the transcript and the speaker labels together.&lt;/p&gt;

&lt;p&gt;AssemblyAI's Universal-3.5 Pro is the async flagship, and it's built for exactly this. It generates the transcript and speaker labels jointly, and it's optimized for cpWER — the honest metric — not just DER. Because diarization is produced alongside the words rather than aligned after the fact, it holds up on the hard cases: short turns, rapid back-and-forth, and overlapped speech.&lt;/p&gt;

&lt;p&gt;Here's the whole thing in Python:&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;assemblyai&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;aai&lt;/span&gt;

&lt;span class="n"&gt;aai&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;settings&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;YOUR_API_KEY&amp;gt;&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="n"&gt;audio_file&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://assembly.ai/wildfires.mp3&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;  &lt;span class="c1"&gt;# or "./meeting.wav"
&lt;/span&gt;
&lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;aai&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;TranscriptionConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;speaker_labels&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;transcript&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;aai&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Transcriber&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;transcribe&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="n"&gt;config&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;transcript&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;utterances&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;Speaker &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;utterance&lt;/span&gt;&lt;span class="p"&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;utterance&lt;/span&gt;&lt;span class="p"&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's pip install assemblyai and a single speaker_labels=True flag. No Hugging Face token. No gated model downloads. No alignment heuristic to write and debug. No GPU to provision, patch, or keep alive. The utterances come back already labeled by speaker, in order, ready to print or store.&lt;/p&gt;

&lt;p&gt;On price, Universal-3.5 Pro runs $0.21/hr, and standard diarization is an additional $0.02/hr — a rounding error against the cost of running your own GPU box and maintaining the pipeline around it. On accuracy, the numbers hold up where it counts: Universal-3.5 Pro averages 30.17 cpWER, compared with 37.92 for Deepgram Nova-3 English, 35.26 for ElevenLabs Scribe v2, and 36.87 for Gladia. Lower is better, and the gap is largest exactly on the short-turn and overlap cases that a DIY pipeline fumbles. The full &lt;a href="https://www.assemblyai.com/blog/universal-3-5-pro-async" rel="noopener noreferrer"&gt;Universal-3.5 Pro async release&lt;/a&gt; has the details.&lt;/p&gt;

&lt;p&gt;This isn't magic — it's just diarization treated as a first-class part of the model instead of a post-processing step you assemble. And it means the messy moments in real conversations get attributed correctly instead of collapsed.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Whisper + pyannote vs. Universal-3.5 Pro: when to use which&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Both are legitimate choices. Here's the honest comparison.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Factor&lt;/th&gt;
&lt;th&gt;Whisper + pyannote&lt;/th&gt;
&lt;th&gt;Universal-3.5 Pro (async)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Setup&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Multiple libraries, gated models, HF token&lt;/td&gt;
&lt;td&gt;pip install assemblyai, one API call&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Diarization&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Separate model, aligned after transcription&lt;/td&gt;
&lt;td&gt;Produced jointly with the transcript&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Short turns&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Often swallowed by neighboring speaker&lt;/td&gt;
&lt;td&gt;Captured&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Overlapping speech&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Frequently collapsed to one speaker&lt;/td&gt;
&lt;td&gt;Handled&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Optimized for&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;DER (typical tutorials)&lt;/td&gt;
&lt;td&gt;cpWER — the honest metric&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Avg cpWER&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Varies; you tune and measure it yourself&lt;/td&gt;
&lt;td&gt;30.17&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Infra&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;GPU strongly recommended, you manage it&lt;/td&gt;
&lt;td&gt;None — fully managed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Alignment code&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;You write and maintain it&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Cost&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;"Free" software + your GPU + your time&lt;/td&gt;
&lt;td&gt;$0.21/hr + $0.02/hr diarization&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Offline / air-gapped&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No (API)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Support&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Community / GitHub issues&lt;/td&gt;
&lt;td&gt;Docs, playground, support&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;So when should you reach for each?&lt;/p&gt;

&lt;p&gt;Go with Whisper + pyannote if you need everything to run offline or air-gapped, if you're doing research or a hobby build where the pipeline itself is the point, if you have a GPU sitting idle, and if cost pressure means you'd rather spend engineering time than dollars. It's a fine tool for that.&lt;/p&gt;

&lt;p&gt;Go with Universal-3.5 Pro if accuracy on real conversations matters — short turns, cross-talk, overlap — if you don't want to own a GPU and an alignment script, if you'd rather ship in an afternoon than tune VAD for a week, and if you want a number you can trust because it's measured with cpWER. For most production speech-to-text work, that's the trade that pays off.&lt;/p&gt;

&lt;p&gt;The thing is, most teams reach for Whisper + pyannote because it looks free and turns out to be expensive — in GPU time, in glue code, and in the accuracy you quietly lose on the hardest turns. Priced against that, a managed API optimized for the honest metric is usually the cheaper answer.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Next steps&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;If you're building diarization into a product, the fastest way to compare is to run the same audio through both. Grab a messy clip — an interview with interruptions, a meeting with cross-talk — and see which one attributes the short turns correctly.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Try it live in the &lt;a href="https://www.assemblyai.com/playground" rel="noopener noreferrer"&gt;playground&lt;/a&gt; — paste a file, get labeled speakers, no code.&lt;/li&gt;
&lt;li&gt;Read the &lt;a href="https://www.assemblyai.com/docs" rel="noopener noreferrer"&gt;speaker diarization docs&lt;/a&gt; for the async API details.&lt;/li&gt;
&lt;li&gt;See the &lt;a href="https://www.assemblyai.com/features/speaker-diarization" rel="noopener noreferrer"&gt;speaker diarization feature overview&lt;/a&gt; for how it fits into a full pipeline.&lt;/li&gt;
&lt;li&gt;Compare options in our roundup of the &lt;a href="https://www.assemblyai.com/blog/top-speaker-diarization-libraries-and-apis" rel="noopener noreferrer"&gt;top speaker diarization libraries and APIs&lt;/a&gt; and the &lt;a href="https://www.assemblyai.com/blog/the-top-free-speech-to-text-apis-and-open-source-engines" rel="noopener noreferrer"&gt;top free speech-to-text APIs and open-source engines&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Frequently asked questions&lt;/strong&gt;
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Does Whisper support speaker diarization?&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;&amp;nbsp;No. Whisper is an ASR model — it transcribes speech to text but doesn't identify who's speaking. To get speaker labels you have to pair it with a separate diarization model like pyannote.audio, or use a managed API that does both jointly.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;How do you add speaker diarization to Whisper?&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;&amp;nbsp;The common approach is a three-stage pipeline: transcribe with Whisper for word-level timestamps, run pyannote.audio (or WhisperX) to get speaker segments, then align each word to a speaker turn with your own glue code. pyannote's models are gated, so you'll need a free Hugging Face token, and a GPU is strongly recommended.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Is Whisper + pyannote free?&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;The software is open source, so there's no license fee. But "free" is misleading — you'll typically need a GPU to run it at any real speed, plus engineering time to build and maintain the alignment step, manage credentials, and tune voice-activity detection. The dollars move from a per-hour bill to your infrastructure and your team's hours.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;How accurate is Whisper speaker diarization?&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Whisper itself is a strong transcriber; the diarization accuracy depends on pyannote and your alignment code. Measured with cpWER — the metric that counts misattributed words — DIY pipelines tend to struggle on short turns, rapid back-and-forth, and overlapping speech, precisely the cases DER can hide. Always measure with cpWER, not just DER.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;What's the difference between DER and cpWER?&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;DER (diarization error rate) measures the fraction of audio time attributed to the wrong speaker, and it under-penalizes short turns and attribution swaps. cpWER (concatenated minimum-permutation word error rate) measures, per speaker, the fraction of their words that were misrecognized, dropped, or credited to someone else. In one AssemblyAI test, a bad attribution output scored just 15.1% DER but 30.7% cpWER — same output, very different story.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;What's a simpler alternative to Whisper + pyannote?&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;&amp;nbsp;AssemblyAI's Universal-3.5 Pro async model produces the transcript and speaker labels together from a single API call — no Hugging Face token, no alignment script, no GPU to manage. It's optimized for cpWER, averages 30.17 cpWER, and costs $0.21/hr plus $0.02/hr for standard diarization.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>machinelearning</category>
      <category>speechtotext</category>
      <category>python</category>
    </item>
    <item>
      <title>How to Measure Diarization Accuracy with cpWER in Python</title>
      <dc:creator>Mart Schweiger</dc:creator>
      <pubDate>Wed, 12 Aug 2026 14:59:37 +0000</pubDate>
      <link>https://dev.to/martschweiger/how-to-measure-diarization-accuracy-with-cpwer-in-python-21oh</link>
      <guid>https://dev.to/martschweiger/how-to-measure-diarization-accuracy-with-cpwer-in-python-21oh</guid>
      <description>&lt;p&gt;&lt;em&gt;A hands-on runbook: compute cpWER in Python to see how good your speaker diarization really is — then score a Universal-3.5 Pro transcript end to end.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Here's a scorer you can paste into a notebook right now:&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;numpy&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;jiwer&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;scipy.optimize&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;linear_sum_assignment&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_error_count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hyp&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;ref&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ref&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="ow"&gt;or&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;empty&amp;gt;&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;hyp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;hyp&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="ow"&gt;or&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;empty&amp;gt;&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;out&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;jiwer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;process_words&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hyp&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;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;substitutions&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;deletions&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;insertions&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;cpwer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reference&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hypothesis&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
    reference, hypothesis: dict mapping speaker label -&amp;gt; that speaker&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s full text.
    Returns (cpWER, best_mapping). cpWER is invariant to how speakers are named.
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;ref_speakers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reference&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="n"&gt;hyp_speakers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hypothesis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="n"&gt;n&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="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ref_speakers&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;hyp_speakers&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;ref_padded&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ref_speakers&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="bp"&gt;None&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;n&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;ref_speakers&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;hyp_padded&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;hyp_speakers&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="bp"&gt;None&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;n&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;hyp_speakers&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="n"&gt;cost&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;zeros&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;dtype&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;int&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;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ref_padded&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;j&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hyp_padded&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;cost&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;_error_count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reference&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="n"&gt;r&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;hypothesis&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="n"&gt;h&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;rows&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cols&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;linear_sum_assignment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cost&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;total_errors&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cost&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cols&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;total_ref_words&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sum&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;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&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;t&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;reference&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;values&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="n"&gt;mapping&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;ref_padded&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="n"&gt;hyp_padded&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;j&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;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;zip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cols&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;total_errors&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="n"&gt;total_ref_words&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;mapping&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's the whole thing. By the end of this post you'll know exactly what it computes, why it beats the metric most people reach for, and how to point it at a real AssemblyAI transcript. Let's dig in.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Why "who spoke when" needs its own metric&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Plain word error rate is great at one job: did the system get the words right? Take a two-person interview, transcribe it perfectly, and standard WER can read 0% even if the model credited every one of the host's sentences to the guest. WER never looks at the speaker labels, so speaker mistakes are invisible to it. That's a problem the second your product shows "Speaker A" and "Speaker B" to a user.&lt;/p&gt;

&lt;p&gt;Our research team wrote the full argument for why cpWER is the honest way to measure &lt;a href="https://www.assemblyai.com/blog/what-is-speaker-diarization-and-how-does-it-work" rel="noopener noreferrer"&gt;speaker diarization&lt;/a&gt; and why DER quietly lies — read &lt;a href="https://www.assemblyai.com/blog/der-vs-cpwer" rel="noopener noreferrer"&gt;the DER-vs-cpWER pillar&lt;/a&gt; if you want the deep theory. This post is the other half: the code. So rather than send you off to read a definition, I'll restate it here and then we'll build it.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;What cpWER actually measures&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;cpWER stands for concatenated minimum-permutation word error rate. Three moving parts, and the name tells you all of them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Concatenated.&lt;/strong&gt; For each speaker, glue all their words into one stream — one blob of text per person, in both the reference and the hypothesis.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Minimum-permutation.&lt;/strong&gt; Your system's "Speaker A" isn't guaranteed to be the reference's "Speaker A." So try every way of matching hypothesis speakers to reference speakers and keep the pairing that produces the fewest total word errors.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Word error rate.&lt;/strong&gt; Under that best pairing, compute a normal WER across everything.&lt;/p&gt;

&lt;p&gt;In plain English: for each person in the conversation, what fraction of their words did the system get wrong — misrecognized, dropped, or handed to the wrong speaker? One number, and it only goes up when something a human would actually notice goes wrong.&lt;/p&gt;

&lt;p&gt;Now contrast that with DER, the diarization error rate. DER scores seconds of audio, and it scores things a user never sees: silent pauses, segment padding, non-speech annotations. The gap between the two is not subtle. In our testing, a flawless transcript scored &lt;strong&gt;51.5% DER but 0.0% cpWER&lt;/strong&gt; — DER screaming failure at a perfect result. Flip it around and a catastrophic speaker-attribution output — words shoved onto the wrong people — scored just &lt;strong&gt;15.1% DER but 30.7% cpWER&lt;/strong&gt;. DER shrugged at output that was genuinely broken. If you're evaluating &lt;a href="https://www.assemblyai.com/blog/how-to-evaluate-speech-recognition-models" rel="noopener noreferrer"&gt;speech recognition models&lt;/a&gt;, that's the exact kind of inversion you can't afford.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Setting up&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;One install line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;jiwer scipy numpy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;jiwer does the word-level alignment, scipy gives you the optimal assignment solver, and numpy holds the cost matrix. That's it — no GPU, no models, no audio processing.&lt;/p&gt;

&lt;p&gt;What you actually need to run an evaluation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A reference transcript with speaker labels&lt;/strong&gt; — your ground truth, shaped as {speaker: text}.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A system hypothesis&lt;/strong&gt; in the same shape — whatever your diarizer produced.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And here's an underrated part of the pitch. To annotate for cpWER, a human just needs to write down who said what — a speaker-labeled transcript. To annotate for DER, someone has to mark frame-level speech boundaries down to the fraction of a second. That transcript-only labeling runs roughly &lt;strong&gt;10x cheaper&lt;/strong&gt; than DER's frame-level labels, which matters a lot when you're building an eval set by hand.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Computing cpWER step by step&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Look back at the scorer. It does three things in order.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Per-speaker concatenation.&lt;/strong&gt; The reference and hypothesis are dicts of {speaker: text}, so the concatenation is already done by the time text lands in the dict — one string per speaker.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The assignment step.&lt;/strong&gt; This is the clever bit. We build an n x n cost matrix where cost[i, j] is the number of word errors you'd get if reference speaker i were matched to hypothesis speaker j. Then linear_sum_assignment — the Hungarian algorithm — finds the pairing that minimizes the total across the diagonal. Padding with None handles the case where the two sides have a different speaker count (the model found three speakers, the truth has two). That's the "minimum-permutation" in the name, done efficiently instead of by brute force.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scoring with jiwer.&lt;/strong&gt; Each cell's cost comes from jiwer.process_words, summing substitutions, deletions, and insertions. Total errors under the best mapping, divided by total reference words, gives you cpWER.&lt;/p&gt;

&lt;p&gt;Now the payoff — a worked example that proves the metric doesn't care what you name your speakers:&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;reference&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;A&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;hey did you finish the report&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;B&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;yeah i sent it this morning&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="c1"&gt;# Same words, correct attribution — but the labels are swapped.
&lt;/span&gt;&lt;span class="n"&gt;hypothesis&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;spk_1&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;yeah i sent it this morning&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;spk_2&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;hey did you finish the report&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;score&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mapping&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;cpwer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reference&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hypothesis&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;cpWER: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&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;%&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;# cpWER: 0.0%
&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;mapping&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;                         &lt;span class="c1"&gt;# {'A': 'spk_2', 'B': 'spk_1'}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Words correct, labels flipped, and the score is &lt;strong&gt;0.0%&lt;/strong&gt;. The assignment step lines A up with spk_2 and B with spk_1, then finds zero word errors. That's label-invariance — exactly what you want, because "Speaker A" is an arbitrary tag, not a fact about the world.&lt;/p&gt;

&lt;p&gt;Break the attribution instead of the naming and the score moves. Merge both people into one speaker and cpWER jumps toward the high 80s; introduce a single misrecognized word and you'll see it tick up by one word's worth of error. The metric tracks the mistakes a listener would actually catch — nothing more, nothing less. If you've ever felt burned by how &lt;a href="https://www.assemblyai.com/blog/word-error-rate-is-broken" rel="noopener noreferrer"&gt;word error rate hides real failures&lt;/a&gt;, this is the diarization-aware fix.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Scoring a real transcript with AssemblyAI&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Now let's feed it something real. &lt;a href="https://www.assemblyai.com/features/speaker-diarization" rel="noopener noreferrer"&gt;AssemblyAI's speaker diarization&lt;/a&gt; runs on &lt;a href="https://www.assemblyai.com/blog/universal-3-5-pro-async" rel="noopener noreferrer"&gt;Universal-3.5 Pro&lt;/a&gt;, our async flagship at $0.21/hr, which produces the transcript and the speaker labels jointly and is optimized for cpWER — not DER. It's tuned to catch short turns, rapid back-and-forth, and overlapped speech, which is where diarizers usually fall apart. (Standard async diarization adds $0.02/hr.)&lt;/p&gt;

&lt;p&gt;Install the SDK and transcribe with speaker labels on:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;assemblyai
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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;assemblyai&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;aai&lt;/span&gt;

&lt;span class="n"&gt;aai&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;settings&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;YOUR_API_KEY&amp;gt;&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="n"&gt;audio_file&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://assembly.ai/wildfires.mp3&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;  &lt;span class="c1"&gt;# or a local path like "./meeting.wav"
&lt;/span&gt;
&lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;aai&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;TranscriptionConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;speaker_labels&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;transcript&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;aai&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Transcriber&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;transcribe&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="n"&gt;config&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;transcript&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;utterances&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;Speaker &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;utterance&lt;/span&gt;&lt;span class="p"&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;utterance&lt;/span&gt;&lt;span class="p"&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The full parameter list lives in the &lt;a href="https://www.assemblyai.com/docs" rel="noopener noreferrer"&gt;AssemblyAI docs&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The transcript comes back as a list of utterances, each with a speaker tag and text. To score it, fold those utterances into the {speaker: text} shape our scorer expects — concatenate every utterance per speaker:&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;collections&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;defaultdict&lt;/span&gt;

&lt;span class="n"&gt;hypothesis&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;defaultdict&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="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;transcript&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;utterances&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;hypothesis&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;utterance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;speaker&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+=&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="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="n"&gt;text&lt;/span&gt;

&lt;span class="c1"&gt;# `reference` is your ground-truth transcript in the same {speaker: text} shape
&lt;/span&gt;&lt;span class="n"&gt;score&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mapping&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;cpwer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reference&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hypothesis&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;cpWER: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&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;%&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;That's the entire loop: transcribe, group by speaker, score against your ground truth. The mapping tells you which model speaker the scorer matched to each reference speaker, which is handy when you want to eyeball a specific person's errors.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Reading your score: what's good and the traps&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;So you've got a number. Now what?&lt;/p&gt;

&lt;p&gt;For context, Universal-3.5 Pro posts an &lt;strong&gt;average cpWER of 30.17&lt;/strong&gt;. Where do competitors land on the same measure? Deepgram Nova-3 English sits at &lt;strong&gt;37.92&lt;/strong&gt;, ElevenLabs Scribe v2 at &lt;strong&gt;35.26&lt;/strong&gt;, and Gladia at &lt;strong&gt;36.87&lt;/strong&gt;. Lower is better, and the spread between providers is real — this is the kind of gap you can only see once you're measuring the thing users actually experience.&lt;/p&gt;

&lt;p&gt;A few things to keep in mind as you interpret your own runs:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Short turns and overlap dominate the error.&lt;/strong&gt; A one-word "yeah" dropped in the middle of someone else's sentence is brutal for any diarizer, and those moments contribute a disproportionate share of cpWER. If your score is high, listen to the rapid back-and-forth sections first — that's usually where it's coming from.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Formatting choices swing DER but not cpWER.&lt;/strong&gt; This is the trap that sends people down rabbit holes. In our testing, changing a single segmentation setting — max_gap — swung DER from &lt;strong&gt;47.8% to 14.5%&lt;/strong&gt; on &lt;em&gt;identical model output&lt;/em&gt;. Toggling whether laughter was included in the reference swung DER from &lt;strong&gt;23.4% to 1.1%&lt;/strong&gt;. Same words out of the model, wildly different DER, purely from annotation and segmentation knobs. cpWER doesn't budge for any of that, because it scores words, not seconds. When your metric moves and the transcript didn't, that's DER noise — not a real quality change.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Compare like with like.&lt;/strong&gt; cpWER is text-normalization sensitive (casing, punctuation, numbers). Keep your normalization identical across reference and hypothesis, and identical across the providers you're comparing, or you'll be measuring your preprocessing instead of the model.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Next steps&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;You've now got a self-contained, tested cpWER scorer and a way to point it at production diarization output. From here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Build a small labeled eval set from your own audio — remember, transcript-only annotation is about 10x cheaper than DER labeling, so this is more achievable than it sounds.&lt;/li&gt;
&lt;li&gt;Run the same set through multiple providers and rank them on cpWER, not DER or plain WER. If you're surveying options, our roundup of &lt;a href="https://www.assemblyai.com/blog/top-speaker-diarization-libraries-and-apis" rel="noopener noreferrer"&gt;speaker diarization libraries and APIs&lt;/a&gt; is a good starting map.&lt;/li&gt;
&lt;li&gt;Kick the tires without writing any setup code in the &lt;a href="https://www.assemblyai.com/playground" rel="noopener noreferrer"&gt;AssemblyAI playground&lt;/a&gt;, then wire the SDK into your pipeline.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When you're ready to score a real transcript, run the two code blocks above end to end. Measure the thing your users actually see — then go make it better.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Frequently asked questions&lt;/strong&gt;
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;What is cpWER?&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;cpWER is concatenated minimum-permutation word error rate. It concatenates each speaker's words into one stream, finds the reference-to-hypothesis speaker mapping that minimizes total word error, and reports the word error rate under that best mapping. In plain terms: for each person, what fraction of their words did the system get wrong — misrecognized, dropped, or credited to the wrong speaker.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;What's the difference between cpWER and DER?&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;cpWER scores words a listener actually reads; DER scores seconds of audio, including things users never see like silent pauses, segment padding, and non-speech annotations. The two can disagree dramatically — a flawless transcript scored 51.5% DER but 0.0% cpWER in our testing, while a badly mis-attributed one scored just 15.1% DER but 30.7% cpWER. cpWER is also cheaper to annotate, since it needs only a speaker-labeled transcript.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;How do you measure speaker diarization accuracy in Python?&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Represent your reference and hypothesis as {speaker: text} dicts, then use jiwer for word-level alignment and scipy's linear_sum_assignment to find the best speaker mapping. The cpwer() function in this post does exactly that — install with pip install jiwer scipy numpy and pass in your two dicts.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Does AssemblyAI support speaker diarization?&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Yes. Set speaker_labels=True in your TranscriptionConfig and Universal-3.5 Pro returns per-utterance speaker labels alongside the transcript. It's optimized for cpWER and built to handle short turns, rapid back-and-forth, and overlapped speech. Standard async diarization adds $0.02/hr.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Why is diarization error rate (DER) misleading?&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Because DER moves when the words don't. In our testing, changing one segmentation setting swung DER from 47.8% to 14.5% on identical model output, and toggling whether laughter was in the reference swung it from 23.4% to 1.1%. Those are annotation and formatting artifacts, not quality changes. cpWER ignores them because it scores words instead of seconds.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;How much labeled data do I need to compute cpWER?&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Just a speaker-labeled transcript of your audio — who said what, in order. You don't need frame-level speech boundaries, which is why cpWER annotation runs roughly 10x cheaper than DER annotation. Start with a handful of representative clips and grow the set as you compare providers.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>python</category>
      <category>machinelearning</category>
      <category>speechtotext</category>
    </item>
    <item>
      <title>AssemblyAI vs NVIDIA Parakeet &amp; Canary for Production</title>
      <dc:creator>Mart Schweiger</dc:creator>
      <pubDate>Wed, 05 Aug 2026 12:54:23 +0000</pubDate>
      <link>https://dev.to/martschweiger/assemblyai-vs-nvidia-parakeet-canary-for-production-gd6</link>
      <guid>https://dev.to/martschweiger/assemblyai-vs-nvidia-parakeet-canary-for-production-gd6</guid>
      <description>&lt;p&gt;NVIDIA's speech models are having a moment, and it's earned. Parakeet TDT and Canary post excellent numbers on standard ASR benchmarks, they're fast, and if your stack already lives on NVIDIA GPUs and NIM, running them feels like the path of least resistance. Teams evaluating clinical and conversational transcription are right to put them on the shortlist.&lt;/p&gt;

&lt;p&gt;So this isn't a takedown. It's the comparison you actually need before you build a product on one of them — because benchmark accuracy and production accuracy are different things, and in regulated, entity-heavy domains like healthcare, the difference is the whole ballgame.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Quick comparison&lt;/strong&gt;
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;NVIDIA Parakeet / Canary&lt;/th&gt;
&lt;th&gt;AssemblyAI (Universal-3.5 Pro)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Delivery&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Open weights you host (NIM / your GPUs)&lt;/td&gt;
&lt;td&gt;Managed API, or self-hosted in your VPC&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Cost model&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;GPU hours + ops + eval pipeline&lt;/td&gt;
&lt;td&gt;$0.21/hr, per second, no minimums&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Medical tuning&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Build it yourself&lt;/td&gt;
&lt;td&gt;Medical Mode (medical-v1), +$0.15/hr&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Entity accuracy&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Strong on clean benchmarks&lt;/td&gt;
&lt;td&gt;Best-in-market on drugs, numbers, names&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Diarization&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Separate system&lt;/td&gt;
&lt;td&gt;Joint transcript + speaker, cpWER-optimized&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Compliance&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Your responsibility&lt;/td&gt;
&lt;td&gt;BAA available without a sales call, SOC 2, EU residency&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Support&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Community / your team&lt;/td&gt;
&lt;td&gt;Forward-deployed engineers&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Where NVIDIA is genuinely strong&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Give the models their due. On clean, single-speaker English benchmarks, Parakeet and Canary are among the best open models available, and their inference speed on NVIDIA hardware is hard to beat. If you're doing high-volume offline batch on infrastructure you already own and operate, that combination is real leverage.&lt;/p&gt;

&lt;p&gt;The question is what happens when the audio stops being clean and the domain starts being regulated.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Real-world clinical audio isn't a benchmark&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;A clinician dictating over a noisy ward, a patient with an accent describing symptoms, two voices overlapping across an exam room — this is the audio a medical scribe actually hears, and it's nothing like a read-speech leaderboard. Two things decide whether a transcript is safe to use downstream: getting the &lt;em&gt;entities&lt;/em&gt; right and getting the &lt;em&gt;speakers&lt;/em&gt; right.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Medical entity accuracy.&lt;/strong&gt; Drug names, dosages, conditions, and procedures are exactly the tokens where a single error is clinically meaningful. AssemblyAI's &lt;a href="https://www.assemblyai.com/solutions/medical" rel="noopener noreferrer"&gt;Medical Mode&lt;/a&gt; — turned on with a single "domain": "medical-v1" parameter, no model switch — reduces the missed entity rate on drugs, conditions, and procedures by roughly 20%. And because Universal-3.5 Pro takes contextual prompting, feeding a patient's prior-visit note cut missed medical terms by 31% in our internal testing, even when the note came from an earlier visit. Getting that behavior out of a raw Parakeet checkpoint is a research project you'd own.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Diarization.&lt;/strong&gt; Attributing each utterance to the right speaker is its own hard problem. Universal-3.5 Pro produces the transcript and speaker boundaries &lt;a href="https://www.assemblyai.com/blog/universal-3-5-pro-async" rel="noopener noreferrer"&gt;jointly&lt;/a&gt; and leads our published cpWER benchmarks across meetings, telephony, and conversational audio. Bolting a separate diarizer onto an NVIDIA checkpoint gives you the brittle timestamp-alignment approach that falls apart on the interruptions clinical audio is full of.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Compliance is a feature, not a footnote&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;For healthcare teams, the model is half the decision. The other half is whether you can legally put protected health information through it.&lt;/p&gt;

&lt;p&gt;AssemblyAI enables covered entities and their business associates subject to HIPAA to use the services to process protected health information. AssemblyAI is considered a business associate under HIPAA, and we offer a Business Associate Addendum (BAA) — required under HIPAA — that you can sign in minutes, without a sales call. Add SOC 2, EU data residency at the same price, and &lt;a href="https://www.assemblyai.com/deployments/self-hosted" rel="noopener noreferrer"&gt;self-hosted deployment in your own VPC&lt;/a&gt;, and the compliance path is a signature, not a quarter of legal review. Self-hosting an open model means building and documenting all of that yourself.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;When NVIDIA is the right call&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;If you already run a mature NVIDIA GPU and NIM stack, have an ML platform team that wants to own the model, and your workload is mostly offline batch where you can keep utilization high, Parakeet and Canary are a strong, cost-effective choice. Same if you have a hard requirement to keep everything on infrastructure you fully control and the team to operate it — though note a managed platform can meet strict data-isolation needs through self-hosted deployment too.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Verdict&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;NVIDIA's models are excellent engines. A production medical transcription product needs more than an engine — it needs entity accuracy on messy clinical audio, reliable speaker attribution, and a compliance path you can actually sign. Universal-3.5 Pro with Medical Mode delivers those out of the box; self-hosting Parakeet means building each of them and owning them forever.&lt;/p&gt;

&lt;p&gt;The right way to decide is on your audio: turn on Medical Mode and run it against a batch of your real recordings — the noisy, accented, terminology-dense ones.&lt;/p&gt;

&lt;h3&gt;
  
  
  Frequently asked questions
&lt;/h3&gt;

&lt;h4&gt;
  
  
  What's the difference between NVIDIA Parakeet and Canary?
&lt;/h4&gt;

&lt;p&gt;Parakeet and Canary are both open speech models from NVIDIA built on the NeMo framework, sharing a FastConformer encoder but using different decoders. Parakeet (for example, Parakeet-TDT-0.6B) is optimized for blazing-fast, high-throughput transcription at low latency. Canary (for example, Canary-1B-v2, ~1B parameters) is the larger multitask model, tuned for accuracy and multilingual translation across 25 European languages. In short: reach for Parakeet for speed, Canary for multilingual and translation accuracy.&lt;/p&gt;

&lt;h4&gt;
  
  
  Is AssemblyAI or NVIDIA Parakeet better for medical transcription?
&lt;/h4&gt;

&lt;p&gt;For clinical audio, the deciding factors are medical entity accuracy, reliable speaker attribution, and a compliance path — not clean-benchmark WER. Parakeet and Canary are excellent engines on clean, read speech, but medical tuning, diarization, and HIPAA documentation are things you'd build and own yourself. AssemblyAI's Universal-3.5 Pro with Medical Mode (one &lt;code&gt;"domain": "medical-v1"&lt;/code&gt; parameter) reduces the missed entity rate on drugs, conditions, and procedures by roughly 20%, produces diarization jointly with the transcript, and comes with a signable BAA. Test both on your own noisy, terminology-dense recordings.&lt;/p&gt;

&lt;h4&gt;
  
  
  Do NVIDIA Parakeet and Canary have an API?
&lt;/h4&gt;

&lt;p&gt;Parakeet and Canary are released as open weights — via Hugging Face and NVIDIA NeMo/NIM — that you host and serve yourself, rather than a fully managed transcription API with an SLA. You can wrap them in your own endpoint or run them through NVIDIA NIM, but you own the GPUs, autoscaling, and uptime. A managed API like AssemblyAI bills per second of audio and runs that infrastructure for you.&lt;/p&gt;

&lt;h4&gt;
  
  
  Can NVIDIA Parakeet do real-time streaming transcription?
&lt;/h4&gt;

&lt;p&gt;Parakeet is built for low-latency, high-throughput inference and NeMo offers streaming-capable variants, but production streaming — chunking, partial hypotheses, endpointing, and turn detection — is something you assemble and operate yourself. If you need live transcription for captions, dictation, or voice agents out of the box, a streaming-native API saves that work. AssemblyAI's Universal-3.5 Pro Realtime ships it at roughly 300ms end-of-turn.&lt;/p&gt;

&lt;h4&gt;
  
  
  Can I use NVIDIA Parakeet or Canary for HIPAA or PHI workloads?
&lt;/h4&gt;

&lt;p&gt;NVIDIA's open models don't come with a Business Associate Addendum (BAA), so if you self-host them to process protected health information, the HIPAA safeguards, documentation, and vendor BAAs are your responsibility. AssemblyAI is considered a business associate under HIPAA and offers a standard BAA you can sign in minutes without a sales call, alongside SOC 2, EU data residency, and self-hosted VPC deployment. That turns the compliance path into a signature rather than a build-and-document project.&lt;/p&gt;

&lt;h4&gt;
  
  
  When should you self-host NVIDIA Parakeet instead of using a speech-to-text API?
&lt;/h4&gt;

&lt;p&gt;Self-host Parakeet or Canary when you already run a mature NVIDIA GPU and NIM stack, your workload is mostly offline batch where you can keep utilization high, and you have an ML platform team that wants to own and fine-tune the model. Those cases make the economics work. For production features that need medical tuning, diarization, streaming, or a compliance path you can sign, a managed API is usually the better trade.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>machinelearning</category>
      <category>speechtotext</category>
      <category>api</category>
    </item>
    <item>
      <title>Self-Host STT on Baseten, Modal, Fireworks — or an API?</title>
      <dc:creator>Mart Schweiger</dc:creator>
      <pubDate>Wed, 05 Aug 2026 12:54:16 +0000</pubDate>
      <link>https://dev.to/martschweiger/self-host-stt-on-baseten-modal-fireworks-or-an-api-303k</link>
      <guid>https://dev.to/martschweiger/self-host-stt-on-baseten-modal-fireworks-or-an-api-303k</guid>
      <description>&lt;p&gt;Here's a decision more teams are facing: an open speech model like Whisper or Qwen3-ASR is free, and platforms like Baseten, Modal, and Fireworks make it genuinely easy to serve one. A few hours of work and you've got a transcription endpoint. So why pay a managed API per hour when you can host the model yourself for the cost of some GPU time?&lt;/p&gt;

&lt;p&gt;It's a reasonable question — and the answer isn't "self-hosting is bad." These are capable platforms. The answer is that you're not comparing a model to a model. You're comparing &lt;em&gt;renting GPUs to run a checkpoint yourself&lt;/em&gt; against &lt;em&gt;calling an API where someone else owns the whole stack&lt;/em&gt;. Once you frame it that way, the comparison is about total cost of ownership, not sticker price.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;What you're actually comparing&lt;/strong&gt;
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Self-host on Baseten / Modal / Fireworks&lt;/th&gt;
&lt;th&gt;AssemblyAI&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Headline cost&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;GPU time (looks cheap)&lt;/td&gt;
&lt;td&gt;$0.15–$0.21/hr async, per second&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;You also pay for&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Idle GPUs, autoscaling eng, on-call, evals&lt;/td&gt;
&lt;td&gt;Nothing — it's in the rate&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Production features&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Build diarization/streaming/entities yourself&lt;/td&gt;
&lt;td&gt;Included&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Concurrency&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;You provision and scale it&lt;/td&gt;
&lt;td&gt;Unlimited, no rate limits&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Reliability&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Your pager&lt;/td&gt;
&lt;td&gt;SOC 2, SLA, forward-deployed engineers&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;The three platforms, briefly&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;They're not identical, and the differences matter for the cost math:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Baseten&lt;/strong&gt; gives you dedicated, autoscaled model deployments — great control, but you're paying for provisioned GPU capacity and tuning the autoscaling yourself.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Modal&lt;/strong&gt; is serverless GPU with pay-per-execution. That fixes idle cost when traffic is quiet, but hands you a cold-start problem: scale to zero and the next request waits for a container and a multi-gigabyte model to load. Keep instances warm to avoid it and you're paying for idle again.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fireworks&lt;/strong&gt; offers fast hosted inference for open models on a per-token or per-second basis — convenient, but you're still assembling the production speech stack around it.&lt;/p&gt;

&lt;p&gt;Different pricing shapes, same conclusion: the platform runs the &lt;em&gt;checkpoint&lt;/em&gt;. Everything that turns a checkpoint into a transcription product is still yours.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Hidden cost #1: the GPU you pay for whether or not it's working&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Open weights are free; GPUs are not, and utilization is the number nobody puts in the demo. Transcription traffic is spiky. A GPU idling at 15% still bills at 100%. Serverless softens idle but adds cold starts; dedicated capacity avoids cold starts but bills for idle. Autoscaling a GPU fleet against bursty voice traffic is a real, ongoing engineering project.&lt;/p&gt;

&lt;p&gt;A managed API bills per &lt;em&gt;second of audio&lt;/em&gt; — nothing for the gaps — because absorbing that utilization math is the vendor's problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Hidden cost #2: everything the checkpoint doesn't do&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Hosting solves "how do I run the model." It does nothing for what the model doesn't produce: &lt;a href="https://www.assemblyai.com/blog/universal-3-5-pro-async" rel="noopener noreferrer"&gt;speaker diarization&lt;/a&gt; that survives cross-talk, entity accuracy on numbers and names, &lt;a href="https://www.assemblyai.com/products/streaming-speech-to-text" rel="noopener noreferrer"&gt;real-time streaming&lt;/a&gt; with endpointing, code-switching, PII redaction, formatting. Every one is a feature you build and maintain on top of the endpoint — or one that's already in the API price.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Hidden cost #3: reliability becomes your job&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;The demo runs one request on one GPU. Production is concurrency at volume, retries, latency under load, zero-downtime model upgrades, failover, monitoring, uptime. Self-hosting means all of it is yours and someone carries the pager. AssemblyAI runs &lt;a href="https://www.assemblyai.com/benchmarks" rel="noopener noreferrer"&gt;hundreds of millions of inference calls a month&lt;/a&gt; with unlimited concurrency and no rate limits, plus forward-deployed engineers who embed with your team — because reliability at scale is the product.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;When self-hosting on these platforms makes sense&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;If you're running massive offline batch where latency is irrelevant and you can pin GPUs at high utilization, the economics genuinely can favor self-hosting. Same if you have a hard requirement to keep audio on infrastructure you fully control, or an ML platform team that wants to own the model and has the cycles. If that's you, Baseten, Modal, and Fireworks are good tools — and note AssemblyAI also offers &lt;a href="https://www.assemblyai.com/deployments/self-hosted" rel="noopener noreferrer"&gt;self-hosted deployment in your own VPC&lt;/a&gt; if control is the driver but you'd rather not rebuild the stack.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Verdict&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;The mistake isn't self-hosting. It's comparing the model's price to the API's price instead of comparing the &lt;em&gt;fully loaded&lt;/em&gt; cost of running your own speech stack — GPUs, idle, engineers, evals, on-call — against a per-second bill where someone else owns all of it. Run that math honestly at your real volume and the answer flips more often than the afternoon-demo instinct suggests.&lt;/p&gt;

&lt;h3&gt;
  
  
  Frequently asked questions
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Is self-hosting an open speech model cheaper than a managed API?
&lt;/h4&gt;

&lt;p&gt;Not as often as the sticker price suggests. Open weights are free, but self-hosting means renting GPUs that bill whether or not audio is flowing, plus the engineering time to autoscale them and the on-call to keep them up. A managed API like AssemblyAI bills per second of audio ($0.15–$0.21/hr async, no minimums) and absorbs that utilization math. The honest comparison is your fully loaded cost — GPUs, idle, engineers, evals, on-call — versus a per-second bill, and at real production volume the answer flips more often than a quick demo suggests.&lt;/p&gt;

&lt;h4&gt;
  
  
  When does self-hosting speech-to-text actually become cheaper?
&lt;/h4&gt;

&lt;p&gt;Self-hosting tends to win when you run large, steady, offline batch workloads where you can keep GPUs pinned at high utilization and latency doesn't matter. In that scenario the fixed GPU cost is spread across near-full usage, which is where owning the hardware pays off. It usually loses on spiky or real-time traffic, where GPUs sit idle between requests but still bill — and that math ignores the engineering and on-call cost of running the stack. Model it at your real volume and utilization, not on a single-GPU demo.&lt;/p&gt;

&lt;h4&gt;
  
  
  What's the difference between Baseten, Modal, and Fireworks for hosting a speech model?
&lt;/h4&gt;

&lt;p&gt;All three make it easy to serve an open model, but their pricing shapes differ. Baseten gives you dedicated, autoscaled deployments — strong control, but you pay for provisioned GPU capacity and tune the autoscaling yourself. Modal is serverless GPU with pay-per-execution, which fixes idle cost but introduces cold starts when it scales to zero. Fireworks offers fast hosted inference on a per-token or per-second basis. In every case the platform runs the checkpoint; the production speech stack around it is still yours to build.&lt;/p&gt;

&lt;h4&gt;
  
  
  What is the cold-start problem with serverless GPU transcription?
&lt;/h4&gt;

&lt;p&gt;A cold start is the delay when a serverless GPU platform has scaled to zero and your next request has to spin up a container and load a multi-gigabyte model before it can transcribe. It's the tradeoff for not paying for idle capacity — you skip the idle bill but add latency to the first request. Keeping instances warm removes the cold start but puts you back to paying for idle. A managed API absorbs this by running the fleet for you and billing only per second of audio.&lt;/p&gt;

&lt;h4&gt;
  
  
  What does a managed speech-to-text API price include that a raw checkpoint doesn't?
&lt;/h4&gt;

&lt;p&gt;A managed API price covers the whole system around the model, not just inference. That includes speaker diarization, entity accuracy on numbers and names, real-time streaming with endpointing, code-switching, PII redaction, and formatting — plus the infrastructure: unlimited concurrency, autoscaling, uptime, and on-call. A raw checkpoint gives you a transcript; every one of those production features is something you build and maintain yourself, or one that's already in the per-second rate.&lt;/p&gt;

&lt;h4&gt;
  
  
  What's the best alternative to hosting Whisper yourself?
&lt;/h4&gt;

&lt;p&gt;The best alternative to self-hosting Whisper is a managed speech-to-text API that runs the infrastructure and ships the production features for you. AssemblyAI, for example, bills per second of audio ($0.15–$0.21/hr async) with diarization, streaming, and entity accuracy included, unlimited concurrency, and a SOC 2 / SLA-backed platform — and offers self-hosted VPC deployment if data control was the reason you were considering hosting in the first place. Run it against your real traffic before committing engineering time to either path.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>machinelearning</category>
      <category>devops</category>
      <category>api</category>
    </item>
    <item>
      <title>AssemblyAI vs Qwen3-ASR: Which Speech-to-Text to Ship?</title>
      <dc:creator>Mart Schweiger</dc:creator>
      <pubDate>Wed, 05 Aug 2026 12:53:51 +0000</pubDate>
      <link>https://dev.to/martschweiger/assemblyai-vs-qwen3-asr-which-speech-to-text-to-ship-4p37</link>
      <guid>https://dev.to/martschweiger/assemblyai-vs-qwen3-asr-which-speech-to-text-to-ship-4p37</guid>
      <description>&lt;p&gt;Qwen's audio models are some of the most interesting work coming out of open research. Qwen3-ASR is capable, multilingual, and if your team already builds on the Qwen ecosystem, reaching for it is a natural move. Strong model, real momentum — worth taking seriously.&lt;/p&gt;

&lt;p&gt;And then worth pressure-testing against what production actually demands. Because a capable open checkpoint and a production speech platform solve overlapping but different problems, and the gap shows up fastest on the two things Qwen wasn't primarily built to own: everything around the transcript, and everything around running it.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Quick comparison&lt;/strong&gt;
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Qwen3-ASR&lt;/th&gt;
&lt;th&gt;AssemblyAI (Universal-3.5 Pro)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Delivery&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Open weights you host&lt;/td&gt;
&lt;td&gt;Managed API, or self-hosted in your VPC&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Cost&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;GPU time + ops&lt;/td&gt;
&lt;td&gt;$0.21/hr, per second, no minimums&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Streaming&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Build it yourself&lt;/td&gt;
&lt;td&gt;Native, ~300ms end-of-turn&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Diarization&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Not production-ready&lt;/td&gt;
&lt;td&gt;Joint transcript + speaker, cpWER-optimized&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Code-switching&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Varies by config&lt;/td&gt;
&lt;td&gt;Native across 18 languages, no config&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Entity accuracy&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Raw output&lt;/td&gt;
&lt;td&gt;Best-in-market on numbers, names, addresses&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Support / SLA&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Community&lt;/td&gt;
&lt;td&gt;Forward-deployed engineers, SOC 2, BAA, EU residency&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Multilingual is the right battleground — so let's use it&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Qwen's appeal is heavily multilingual, so that's the fair place to test it. And multilingual transcription has a specific hard mode: code-switching, where a speaker changes language mid-sentence — Hinglish, Spanglish, and the like. It's one of the harshest tests in speech-to-text, and the spread between models is enormous.&lt;/p&gt;

&lt;p&gt;Universal-3.5 Pro handles &lt;a href="https://www.assemblyai.com/blog/universal-3-5-pro-async" rel="noopener noreferrer"&gt;native code-switching across 18 languages&lt;/a&gt; with no configuration and no separate pass — the model transcribes each word in the language it was actually spoken. On our published code-switching benchmark it posts 7.69% average normalized WER, well ahead of other models on the same audio. That's the capability to beat, and building it out of a raw checkpoint is not a config flag.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;The transcript is the start, not the finish&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Qwen3-ASR gives you words. A production feature needs the layers around them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Diarization&lt;/strong&gt; that survives interruptions and overlap — produced jointly with the transcript, not stitched on from a second system. &lt;strong&gt;Entity accuracy&lt;/strong&gt; on the tokens that break products when they're wrong: account numbers, emails, addresses, names. &lt;strong&gt;Streaming&lt;/strong&gt; for anything live — &lt;a href="https://www.assemblyai.com/products/streaming-speech-to-text" rel="noopener noreferrer"&gt;Universal-3.5 Pro Realtime&lt;/a&gt; ships the chunking, partials, and endpointing you'd otherwise build from scratch, since most open checkpoints are async-first. Redaction and formatting on top of that.&lt;/p&gt;

&lt;p&gt;You can assemble all of it around Qwen. The question is whether that assembly is your product or a distraction from it.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;And it still has to run&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Free weights come with a GPU bill you pay whether audio is flowing or not, autoscaling against spiky traffic, and an on-call rotation. A managed API bills per second and runs &lt;a href="https://www.assemblyai.com/benchmarks" rel="noopener noreferrer"&gt;hundreds of millions of inference calls a month&lt;/a&gt; with unlimited concurrency — the full economics are in the anchor post, "the real cost of self-hosting open-source speech-to-text."&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;When Qwen3-ASR is the right choice&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;If you're already deep in the Qwen ecosystem, doing research or offline batch where you control the audio and can keep GPUs busy, or you have a platform team that wants to own and fine-tune the model, Qwen3-ASR is a solid, flexible choice. Those are legitimate reasons to self-host.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Verdict&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Qwen3-ASR is a strong open model, especially for multilingual and research-flavored work. A production voice product needs code-switching that just works, diarization that holds up, reliable entities, streaming, and an SLA — and Universal-3.5 Pro delivers those without a GPU fleet or a stack to maintain. If you're shipping a product rather than studying a model, that's usually the trade worth making.&lt;/p&gt;

&lt;p&gt;Settle it on your own audio: run Universal-3.5 Pro against your hardest multilingual recordings next to Qwen3-ASR.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frequently asked questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What is Qwen3-ASR?
&lt;/h3&gt;

&lt;p&gt;Qwen3-ASR is a family of open-source automatic speech recognition models from Alibaba's Qwen team, released in early 2026. It includes two all-in-one models (Qwen3-ASR-1.7B and Qwen3-ASR-0.6B) built on the Qwen3-Omni foundation, with language identification and transcription across 52 languages and dialects. The weights are freely available on Hugging Face, and Alibaba also offers a hosted version (Qwen3-ASR-Flash) through its API.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is Qwen3-ASR free, and what does it cost to run in production?
&lt;/h3&gt;

&lt;p&gt;The Qwen3-ASR weights are free to download, but running them in production is not. Self-hosting means paying for GPU time — billed whether or not audio is flowing — plus the engineering to add streaming, diarization, and formatting and the on-call time to keep it up. A managed API like &lt;a href="https://www.assemblyai.com/pricing" rel="noopener noreferrer"&gt;AssemblyAI&lt;/a&gt; instead charges $0.21 per hour of audio, billed per second with no minimums, so you only pay for what you process. Alibaba's hosted Qwen3-ASR-Flash API is a third option, priced separately from the open weights.&lt;/p&gt;

&lt;h3&gt;
  
  
  How does Qwen3-ASR compare to AssemblyAI for production speech-to-text?
&lt;/h3&gt;

&lt;p&gt;Qwen3-ASR is a capable open checkpoint, while AssemblyAI is a managed speech platform — they solve overlapping but different problems. Qwen3-ASR gives you the transcript and the freedom to host and fine-tune it yourself; AssemblyAI's &lt;a href="https://www.assemblyai.com/blog/universal-3-5-pro-async" rel="noopener noreferrer"&gt;Universal-3.5 Pro&lt;/a&gt; adds the production layers around the transcript (native code-switching, joint speaker diarization, entity accuracy, streaming, and redaction) plus an SLA, SOC 2, a BAA, and per-second pricing. If you're shipping a product rather than studying a model, the managed path usually removes more work. Benchmark both on your own audio before deciding.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does Qwen3-ASR support real-time streaming and speaker diarization?
&lt;/h3&gt;

&lt;p&gt;Qwen3-ASR is primarily an offline, async-first model, so real-time streaming and production diarization aren't turnkey — you'd build the chunking, partial results, endpointing, and speaker separation yourself. AssemblyAI ships these natively: &lt;a href="https://www.assemblyai.com/products/streaming-speech-to-text" rel="noopener noreferrer"&gt;Universal-3.5 Pro Realtime&lt;/a&gt; streams partial transcripts with roughly 300 ms end-of-turn detection, and diarization is produced jointly with the transcript and optimized for cpWER. For live or conversational audio, that difference is usually where self-hosting gets expensive.&lt;/p&gt;

&lt;h3&gt;
  
  
  How good is Qwen3-ASR at multilingual and code-switched audio?
&lt;/h3&gt;

&lt;p&gt;Qwen3-ASR covers 52 languages and dialects with built-in language identification, which makes it attractive for multilingual work, though code-switching behavior depends on configuration. Code-switching — where a speaker changes language mid-sentence, like Hinglish or Spanglish — is one of the hardest tests in speech-to-text. AssemblyAI's Universal-3.5 Pro handles &lt;a href="https://www.assemblyai.com/blog/multilingual-speech-to-text-api-universal-3-pro" rel="noopener noreferrer"&gt;native code-switching across 18 languages&lt;/a&gt; with no configuration and posts 7.69% average normalized word error rate on its published code-switching benchmark. Test both on your own multilingual recordings, since results vary widely by audio.&lt;/p&gt;

&lt;h3&gt;
  
  
  When should you use Qwen3-ASR instead of a managed speech-to-text API?
&lt;/h3&gt;

&lt;p&gt;Qwen3-ASR is the right choice when you're already invested in the Qwen ecosystem, doing research or offline batch work where you control the audio and can keep GPUs busy, or you have a platform team that wants to own and fine-tune the model. Those are legitimate reasons to self-host. For real-time or customer-facing products that need reliable code-switching, diarization, entities, streaming, and an SLA without maintaining a GPU fleet, a managed API like AssemblyAI is usually the lower-cost, faster path to production.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>machinelearning</category>
      <category>speechtotext</category>
      <category>api</category>
    </item>
    <item>
      <title>AssemblyAI vs Whisper Large-v3: Which STT to Ship?</title>
      <dc:creator>Mart Schweiger</dc:creator>
      <pubDate>Wed, 05 Aug 2026 12:53:44 +0000</pubDate>
      <link>https://dev.to/martschweiger/assemblyai-vs-whisper-large-v3-which-stt-to-ship-hmh</link>
      <guid>https://dev.to/martschweiger/assemblyai-vs-whisper-large-v3-which-stt-to-ship-hmh</guid>
      <description>&lt;p&gt;Whisper is the model that made open-source speech-to-text a real option. It's free, it's genuinely good on clean English, and it's the first thing most teams reach for when they add transcription to a product. So let's be fair to it before we compare — Whisper Large-v3 is a strong baseline, and if you're transcribing tidy studio audio in one language, it might be all you need.&lt;/p&gt;

&lt;p&gt;But "add transcription to a product" and "run transcription in production" are different jobs. The gap between them is where the Whisper-vs-managed-API decision actually gets made, and it's rarely about the clean-audio benchmark everyone quotes.&lt;/p&gt;

&lt;p&gt;Here's the honest breakdown.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Quick comparison&lt;/strong&gt;
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Whisper Large-v3&lt;/th&gt;
&lt;th&gt;AssemblyAI (Universal-3.5 Pro)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Cost&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;"Free" weights + your GPU bill&lt;/td&gt;
&lt;td&gt;$0.21/hr async, per second, no minimums&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Hosting&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;You run it&lt;/td&gt;
&lt;td&gt;Fully managed API (or self-hosted in your VPC)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Streaming&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Async only — build it yourself&lt;/td&gt;
&lt;td&gt;Native streaming, ~300ms end-of-turn&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Diarization&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Not built in&lt;/td&gt;
&lt;td&gt;Joint transcript + speaker model, cpWER-optimized&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Entity formatting&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Raw text&lt;/td&gt;
&lt;td&gt;Best-in-market on numbers, names, emails, addresses&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Hallucination&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Known to invent text on silence/noise&lt;/td&gt;
&lt;td&gt;~30% lower than Whisper in our testing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Code-switching&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Limited&lt;/td&gt;
&lt;td&gt;Native across 18 languages, no config&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Support / SLA&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Community&lt;/td&gt;
&lt;td&gt;Forward-deployed engineers, SOC 2, BAA, EU residency&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Accuracy is fine on clean audio — and that's the trap&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Whisper's headline accuracy comes from clean, read speech. Your users don't produce clean, read speech. They're on phones in cars, in call centers with cross-talk, using product names and jargon no model trained on.&lt;/p&gt;

&lt;p&gt;The failure mode that costs you most there is hallucination — Whisper's tendency to generate fluent, confident text during silence or background noise. It's not a cosmetic bug. A transcript that's wrong in a &lt;em&gt;plausible&lt;/em&gt; way sails straight past every downstream check, into your analytics, your summaries, your agent's next turn. In AssemblyAI's own testing, Universal-3 Pro's hallucination rate runs about 30% lower than Whisper's, and Universal-3.5 Pro is built specifically for the messy, real-world audio where Whisper slips. You can see the &lt;a href="https://www.assemblyai.com/blog/how-accurate-speech-to-text" rel="noopener noreferrer"&gt;full accuracy breakdown here&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;The transcript is where Whisper stops — and where your work begins&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;This is the part the "it's basically the same model" argument skips. Whisper gives you a transcript. A production voice feature needs more than a transcript.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Speaker labels.&lt;/strong&gt; Whisper doesn't do diarization. Bolt on a separate system and you're aligning timestamps between two models that don't know about each other — brittle on exactly the audio that matters, like interruptions and overlapping speech. Universal-3.5 Pro produces the transcript and the speaker boundaries &lt;a href="https://www.assemblyai.com/blog/universal-3-5-pro-async" rel="noopener noreferrer"&gt;jointly&lt;/a&gt;, scored on cpWER rather than the looser metrics that flatter demos.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Entity accuracy.&lt;/strong&gt; When a caller reads out a credit card, a confirmation code, or a medication name, one wrong character makes the transcript worthless. This is precisely where raw checkpoints struggle and where our models are tuned to win.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Streaming.&lt;/strong&gt; Whisper Large-v3 is async. Building anything live — a voice agent, real-time captions, in-call intelligence — means building the whole streaming layer yourself: chunking, partials, endpointing. &lt;a href="https://www.assemblyai.com/products/streaming-speech-to-text" rel="noopener noreferrer"&gt;Universal-3.5 Pro Realtime&lt;/a&gt; ships it.&lt;/p&gt;

&lt;p&gt;You can build all of this on top of Whisper. The real question is whether reimplementing diarization and a streaming stack is a better use of your engineers than shipping your product.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;And someone has to run it&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Free weights, metered GPUs. Serving Whisper at production latency means renting accelerators that bill whether or not audio is flowing, autoscaling them against bursty traffic, and carrying the pager when it falls over at 2am. A managed API bills per second of audio and absorbs the concurrency, uptime, and version-upgrade problems — we run &lt;a href="https://www.assemblyai.com/benchmarks" rel="noopener noreferrer"&gt;hundreds of millions of inference calls a month&lt;/a&gt; with unlimited concurrency and no rate limits. We cover the full economics in the anchor post, "the real cost of self-hosting open-source speech-to-text."&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;When Whisper Large-v3 is the right choice&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Credit where it's due. Reach for Whisper if you're prototyping and want full model control, running large offline batch jobs where latency doesn't matter and you can keep GPUs pinned at high utilization (which fixes the cost math), or you specifically need to fine-tune the weights on your own data and have the team to do it. Those are real, defensible cases.&lt;/p&gt;

&lt;p&gt;And if you like Whisper's coverage but not the ops, note we also offer &lt;a href="https://www.assemblyai.com/pricing" rel="noopener noreferrer"&gt;Whisper-Streaming as a managed endpoint&lt;/a&gt; at $0.30/hr — Whisper's reach without the GPU fleet.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Verdict&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Whisper Large-v3 is a great place to start and a hard place to scale. The moment you need speaker labels, reliable entities, streaming, or an SLA, you're either building a platform around a free checkpoint or paying per second for one that already exists. For most teams shipping a real product, the second math wins.&lt;/p&gt;

&lt;p&gt;The fastest way to settle it is on your own audio: run Universal-3.5 Pro against your worst recordings — the noisy, accented, jargon-heavy ones — and compare the transcripts side by side with Whisper's. That's the benchmark that decides it.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Frequently asked questions&lt;/strong&gt;
&lt;/h3&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;Is AssemblyAI or Whisper Large-v3 more accurate for transcription?&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;On clean, read English audio, Whisper Large-v3 and a managed model like AssemblyAI's Universal-3.5 Pro are both strong. The difference shows up on real-world audio — phone calls, cross-talk, accents, and industry jargon — where Whisper is prone to hallucinating fluent but wrong text during silence or noise. In AssemblyAI's own testing, Universal-3 Pro's hallucination rate runs about 30% lower than Whisper's, and Universal-3.5 Pro is built specifically for that messy audio. The reliable way to decide is to run both on a batch of your own worst recordings.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;Why does Whisper hallucinate, and how do you prevent it?&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;Whisper hallucinates because it's trained to always produce fluent text, so during silence or background noise it can invent words that were never spoken. This matters because a plausibly-wrong transcript passes every downstream check and corrupts your summaries, analytics, and an agent's next turn. You can reduce it with voice-activity detection and careful chunking, or use a model tuned for noisy, conversational audio — Universal-3.5 Pro posts roughly 30% lower hallucination than Whisper in our testing.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;When should you use Whisper Large-v3 instead of a speech-to-text API?&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;Use Whisper Large-v3 when you're prototyping and want full model control, running large offline batch jobs where latency is irrelevant and you can keep GPUs at high utilization, or fine-tuning the weights on your own data with a team that can maintain it. Those are real, defensible cases. For production voice features that need streaming, diarization, reliable entity formatting, or an SLA, a managed API is usually the better trade.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;Is it cheaper to self-host Whisper or use a speech-to-text API?&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;Whisper's weights are free, but self-hosting is not — you rent GPUs that bill whether or not audio is flowing, autoscale them against bursty traffic, and carry the pager when they fail at 2am. A managed API like AssemblyAI bills per second of audio ($0.21/hr async, no minimums) and absorbs the utilization, concurrency, and uptime problems. The honest comparison isn't $0 vs. $0.21/hr — it's your fully loaded engineering cost versus a per-second bill someone else owns.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;Does Whisper Large-v3 support real-time streaming and speaker diarization?&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;No — Whisper Large-v3 is async-only and has no built-in speaker diarization. To run it live you build the streaming layer yourself (chunking, partial hypotheses, endpointing), and to label speakers you bolt on a separate diarization model and align timestamps between two systems that don't know about each other. AssemblyAI's Universal-3.5 Pro Realtime ships native streaming at ~300ms end-of-turn and produces the transcript and speaker boundaries jointly,&lt;/p&gt;

</description>
      <category>ai</category>
      <category>machinelearning</category>
      <category>speechtotext</category>
      <category>opensource</category>
    </item>
    <item>
      <title>The Real Cost of Self-Hosting Open Source Speech-to-Text</title>
      <dc:creator>Mart Schweiger</dc:creator>
      <pubDate>Wed, 05 Aug 2026 12:53:18 +0000</pubDate>
      <link>https://dev.to/martschweiger/the-real-cost-of-self-hosting-open-source-speech-to-text-21p6</link>
      <guid>https://dev.to/martschweiger/the-real-cost-of-self-hosting-open-source-speech-to-text-21p6</guid>
      <description>&lt;p&gt;Open-source speech-to-text got good. That part isn't marketing spin, and pretending otherwise is how you lose an engineer's trust in the first paragraph.&lt;/p&gt;

&lt;p&gt;Whisper Large-v3 transcribes clean English about as well as anything you can buy. Qwen3-ASR and NVIDIA's Parakeet and Canary models are genuinely strong. Mistral shipped Voxtral. And the checkpoints are free — pip install, download the weights, point it at an audio file, done. Spin one up on Baseten, Modal, or Fireworks and you've got a working transcription endpoint in an afternoon. For a growing number of teams, that afternoon ends with a reasonable question: why are we paying an API for this?&lt;/p&gt;

&lt;p&gt;It's a fair question. Here's the more useful one: what does it actually cost to run that endpoint in production for the next two years?&lt;/p&gt;

&lt;p&gt;Because the sticker price of an open model is zero, and the total cost of ownership is not. The gap between those two numbers is where most self-hosting decisions quietly go wrong.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;What "free" actually buys you&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;A model checkpoint is one component of a transcription product. It is not the product.&lt;/p&gt;

&lt;p&gt;When you call a managed speech-to-text API, the price covers the model &lt;em&gt;plus&lt;/em&gt; the parts you'd otherwise have to build, operate, and keep alive yourself: the inference infrastructure, the accuracy work on messy real-world audio, speaker diarization, entity formatting, streaming, language handling, and the on-call rotation for when any of it breaks at 2am. Download a raw checkpoint and you own every one of those. The weights are free. The system around them is the job.&lt;/p&gt;

&lt;p&gt;So the honest comparison isn't "$0 vs. $0.21 an hour." It's "your engineering org vs. $0.21 an hour." Let's break down what lands on your side of that trade.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Factor&lt;/th&gt;
&lt;th&gt;Self-hosted open source (Whisper, Parakeet, Voxtral, Qwen3-ASR)&lt;/th&gt;
&lt;th&gt;Managed API (AssemblyAI)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Upfront model cost&lt;/td&gt;
&lt;td&gt;$0 — open weights, no license fee&lt;/td&gt;
&lt;td&gt;$0 to start — free credits and playground, then pay-as-you-go&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;What you actually pay for&lt;/td&gt;
&lt;td&gt;GPU hours (billed even when idle), plus engineering and DevOps time&lt;/td&gt;
&lt;td&gt;Only the audio you process, billed per second&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Typical run cost&lt;/td&gt;
&lt;td&gt;~$2–4+/hr per on-demand A100/H100-class GPU, regardless of utilization&lt;/td&gt;
&lt;td&gt;$0.15–$0.21/hr async; $0.15–$0.45/hr streaming&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Idle / low utilization&lt;/td&gt;
&lt;td&gt;You pay full price for idle GPUs — 15% utilized still bills 100%&lt;/td&gt;
&lt;td&gt;No idle cost — you only pay for audio transcribed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Accuracy on real-world audio&lt;/td&gt;
&lt;td&gt;Varies; higher hallucination and WER on noisy, accented, code-switched speech (e.g., Voxtral Mini ~18% WER code-switched)&lt;/td&gt;
&lt;td&gt;Tuned for production audio; Universal-3.5 Pro &amp;lt;8% WER on code-switched speech, and Universal-3 Pro runs ~30% fewer hallucinations than Whisper&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Speaker diarization&lt;/td&gt;
&lt;td&gt;Build or integrate a separate system&lt;/td&gt;
&lt;td&gt;Built in — transcript and speaker turns produced jointly&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Real-time streaming&lt;/td&gt;
&lt;td&gt;Build your own low-latency serving stack&lt;/td&gt;
&lt;td&gt;Native streaming API with ~300 ms partial transcripts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Entity accuracy &amp;amp; PII redaction&lt;/td&gt;
&lt;td&gt;Not included — build and maintain separately&lt;/td&gt;
&lt;td&gt;Included: entity detection, PII redaction, and formatting&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Scaling &amp;amp; concurrency&lt;/td&gt;
&lt;td&gt;You manage autoscaling, queuing, and retries&lt;/td&gt;
&lt;td&gt;Unlimited concurrency, no rate limits&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reliability &amp;amp; maintenance&lt;/td&gt;
&lt;td&gt;Your team owns uptime, monitoring, model upgrades, and on-call&lt;/td&gt;
&lt;td&gt;Managed uptime with 24/7 support&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Data control &amp;amp; compliance&lt;/td&gt;
&lt;td&gt;Full control; runs entirely in your own VPC or on-prem&lt;/td&gt;
&lt;td&gt;Voice AI Cloud, EU data residency, self-hosted deployment, and a BAA available&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Time to production&lt;/td&gt;
&lt;td&gt;Weeks to months of setup and tuning&lt;/td&gt;
&lt;td&gt;Minutes — sign up and get an API key&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Best fit&lt;/td&gt;
&lt;td&gt;High-utilization offline batch, research/prototyping, strict data isolation&lt;/td&gt;
&lt;td&gt;Real-time and customer-facing apps needing accuracy, features, and scale without ops burden&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Hidden cost #1: the GPU bill nobody quotes you&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Open weights don't come with a price tag, but GPUs do. To serve transcription at production quality and latency you're renting accelerators — and you pay for them whether or not audio is flowing through them.&lt;/p&gt;

&lt;p&gt;This is the detail that surprises teams. Utilization is rarely high. Traffic is spiky, models sit warm waiting for requests, and a GPU idling at 15% still bills at 100%. Serverless platforms like Modal soften the idle problem but hand you a cold-start problem instead: scale to zero and your next request waits for a container and a multi-gigabyte model to load. Keep instances warm to avoid that and you're back to paying for idle. Autoscaling a GPU fleet against bursty voice traffic is a real, ongoing engineering project, not a checkbox.&lt;/p&gt;

&lt;p&gt;A managed API bills per second of audio — &lt;a href="https://www.assemblyai.com/pricing" rel="noopener noreferrer"&gt;AssemblyAI's pricing&lt;/a&gt; runs $0.15–$0.21/hr for pre-recorded transcription with no minimums and no idle time — because absorbing that utilization math is the vendor's problem, not yours.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Hidden cost #2: everything the checkpoint doesn't do&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Here's where the "it's basically the same model" argument falls apart. Raw ASR checkpoints give you a transcript. Production voice products need a lot more than a transcript.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Speaker diarization.&lt;/strong&gt; Who said what. Most open checkpoints don't do it at all, and the ones that gesture at it don't hold up on the audio that matters — short back-and-forth turns, interruptions, overlapping speech. Getting diarization right is its own research problem; &lt;a href="https://www.assemblyai.com/blog/universal-3-5-pro-async" rel="noopener noreferrer"&gt;AssemblyAI's latest models&lt;/a&gt; produce the transcript and the speaker boundaries jointly, optimized for concatenated word error rate rather than the looser metrics that make demos look fine and production look broken.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Entity accuracy.&lt;/strong&gt; In the real world people say credit card numbers, email addresses, medication names, and account IDs — the exact tokens where a single wrong character makes the transcript worthless. A base model transcribes the words; it doesn't reliably format an alphanumeric confirmation code spelled out over a phone line.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Streaming.&lt;/strong&gt; Most open checkpoints are async-only. If you're building anything live — a voice agent, real-time captions, in-call intelligence — you get to build the streaming layer yourself: chunking, partial hypotheses, endpointing, the works. &lt;a href="https://www.assemblyai.com/products/streaming-speech-to-text" rel="noopener noreferrer"&gt;Real-time transcription&lt;/a&gt; is a different engineering problem than batch, not a config flag.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Code-switching, redaction, formatting.&lt;/strong&gt; Multilingual speakers switch languages mid-sentence. Recordings need PII redacted. Numbers, dates, and punctuation need to render like a human wrote them. Every one of these is a feature you either buy once or build and maintain forever.&lt;/p&gt;

&lt;p&gt;You can build all of it. The question is whether reimplementing diarization and a streaming stack is the best use of your team versus shipping your actual product.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Hidden cost #3: accuracy on audio that isn't clean&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Benchmarks are run on clean audio. Your users are not clean audio.&lt;/p&gt;

&lt;p&gt;They're on a phone in a moving car, in a call center with three conversations bleeding together, using product names and industry jargon no model saw in training, in accents the leaderboard underweighted. This is exactly where open checkpoints tend to slip — and where one specific failure mode gets expensive: hallucination. Whisper is known to invent fluent, confident text during silence or noise. It's not a rounding error; in AssemblyAI's own testing, Universal-3 Pro's hallucination rate runs about 30% lower than Whisper's, and on &lt;a href="https://www.assemblyai.com/blog/how-accurate-speech-to-text" rel="noopener noreferrer"&gt;published head-to-head benchmarks&lt;/a&gt;, open models trail badly on the audio that resembles production — Voxtral Mini, for instance, posts nearly 18% word error rate on code-switched speech versus under 8% for a managed flagship. A transcript that's wrong in a plausible way is more dangerous than one that's obviously garbled, because nothing downstream flags it.&lt;/p&gt;

&lt;p&gt;Closing that gap yourself means fine-tuning, building an eval set from your own audio, and re-running it every time you touch the model. That's a standing capability you'd be committing to own. And &lt;a href="https://www.assemblyai.com/blog/word-error-rate-is-broken" rel="noopener noreferrer"&gt;word error rate alone won't even tell you if you're winning&lt;/a&gt; — you need to measure the errors that actually break your product.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Hidden cost #4: reliability is now your pager&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;The afternoon demo runs on one GPU serving one request. Production is a different animal.&lt;/p&gt;

&lt;p&gt;Concurrency at real volume, retries and graceful degradation, latency under load, model-version upgrades without downtime, region failover, monitoring, uptime — this is the operational surface of a speech platform, and self-hosting means it's yours. Someone carries the pager. A managed platform runs &lt;a href="https://www.assemblyai.com/benchmarks" rel="noopener noreferrer"&gt;hundreds of millions of inference calls a month&lt;/a&gt; with unlimited concurrency and no rate limits, because reliability at scale is the product it's selling. When you self-host, reliability at scale is a headcount question.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;When self-hosting is the right call&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;None of this means "never self-host." There are real cases where owning the stack is the correct decision:&lt;/p&gt;

&lt;p&gt;You're doing research or a prototype where model control matters more than production polish. You're running massive offline batch jobs where latency is irrelevant and you can keep GPUs pinned at high utilization, which fixes the economics. You have a hard data-isolation requirement that rules out any external call — though note that a managed platform can meet strict needs too, through &lt;a href="https://www.assemblyai.com/enterprise" rel="noopener noreferrer"&gt;self-hosted deployment in your own VPC&lt;/a&gt;, EU data residency, and a signable Business Associate Addendum for teams handling protected health information. Or you have an ML platform team that &lt;em&gt;wants&lt;/em&gt; to own this and has the cycles to.&lt;/p&gt;

&lt;p&gt;If one of those is you, self-hosting is defensible. If none of them is you, the "free" model is the most expensive line item on the roadmap — it just doesn't show up on the invoice.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;The number that actually matters&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;The mistake isn't choosing open source. The mistake is comparing the wrong two numbers.&lt;/p&gt;

&lt;p&gt;The comparison that matters isn't the model's price against the API's price — it's the &lt;em&gt;fully loaded&lt;/em&gt; cost of running your own speech stack, GPUs and engineers and eval pipelines and on-call included, against a per-second bill where someone else owns all of it. Run that math honestly and the answer flips more often than the afternoon-demo instinct suggests. Cheap models are real. Free ones don't exist.&lt;/p&gt;

&lt;p&gt;If you want to see where a managed model lands on your actual audio before you commit engineering time to either path, run it against a batch of your worst recordings — the noisy ones, the accented ones, the jargon-heavy ones. That's the benchmark that decides it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frequently asked questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Is open-source speech-to-text really free?
&lt;/h3&gt;

&lt;p&gt;Open-source speech-to-text models are free to download but not free to run in production. Model weights like &lt;a href="https://www.assemblyai.com/blog/the-top-free-speech-to-text-apis-and-open-source-engines" rel="noopener noreferrer"&gt;Whisper, NVIDIA Parakeet, Qwen3-ASR, and Mistral's Voxtral&lt;/a&gt; carry no license fee, but you still pay for the GPUs that serve them, the engineering time to add production features, and the on-call burden to keep them running. The real comparison isn't "$0 vs. an API bill" — it's your fully loaded infrastructure and engineering cost versus a managed per-second price.&lt;/p&gt;

&lt;h3&gt;
  
  
  How much does it cost to self-host an open-source speech-to-text model?
&lt;/h3&gt;

&lt;p&gt;Self-hosting cost is driven by GPU time, not the model. A single on-demand cloud GPU in the A100/H100 class typically runs $2–4+ per hour and bills whether or not it's actively transcribing, so a model idling at 15% utilization still costs you 100%. On top of that you add engineering time to build diarization, streaming, and formatting, plus ongoing monitoring and model upgrades. By contrast, a managed API like AssemblyAI charges &lt;a href="https://www.assemblyai.com/pricing" rel="noopener noreferrer"&gt;$0.15–$0.21 per hour of audio&lt;/a&gt; for pre-recorded transcription, billed per second with no idle cost.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is self-hosted Whisper cheaper than a speech-to-text API?
&lt;/h3&gt;

&lt;p&gt;It depends almost entirely on GPU utilization. Self-hosted Whisper can be cost-effective for large offline batch jobs that keep a GPU near 100% busy, but for spiky or low-volume workloads the idle GPU time usually makes a per-second managed API cheaper. Whisper also lacks production features and shows higher hallucination rates on real-world audio — AssemblyAI's &lt;a href="https://www.assemblyai.com/blog/introducing-universal-3-pro" rel="noopener noreferrer"&gt;Universal-3 Pro&lt;/a&gt; runs about 30% fewer hallucinations than Whisper. Benchmark both on your own audio and include engineering and infrastructure time, not just the sticker price.&lt;/p&gt;

&lt;h3&gt;
  
  
  What features do open-source speech-to-text models lack out of the box?
&lt;/h3&gt;

&lt;p&gt;Raw open-source checkpoints transcribe audio but omit most of what production apps need. Commonly missing pieces include &lt;a href="https://www.assemblyai.com/blog/streaming-speaker-diarization" rel="noopener noreferrer"&gt;speaker diarization&lt;/a&gt; (who said what), real-time streaming, PII redaction, entity accuracy for data like credit cards and medication names, code-switching across languages, and consistent formatting. Each one has to be built, integrated, and maintained yourself, whereas a managed API ships them as part of the model or a single parameter.&lt;/p&gt;

&lt;h3&gt;
  
  
  When does self-hosting open-source speech-to-text make sense?
&lt;/h3&gt;

&lt;p&gt;Self-hosting is a good fit when you have high, steady GPU utilization or strict control requirements. The clearest cases are large offline batch jobs that keep GPUs busy, research and prototyping where you need to modify the model, strict data-isolation rules, and teams that already run a dedicated ML platform. For most real-time or customer-facing products that need accuracy, features, and scale without an ops burden, a managed API is usually the lower-cost and faster path to production.&lt;/p&gt;

&lt;h3&gt;
  
  
  How much does a managed speech-to-text API cost?
&lt;/h3&gt;

&lt;p&gt;AssemblyAI's pre-recorded speech-to-text starts at $0.15–$0.21 per hour of audio, billed per second with no minimums, and &lt;a href="https://www.assemblyai.com/pricing" rel="noopener noreferrer"&gt;streaming transcription&lt;/a&gt; ranges from $0.15 to $0.45 per hour. There are no idle-GPU charges, no rate limits, and unlimited concurrency, so you pay only for the audio you actually process.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>machinelearning</category>
      <category>opensource</category>
      <category>speechtotext</category>
    </item>
    <item>
      <title>Best Voice Agent Platform for Developer Experience</title>
      <dc:creator>Mart Schweiger</dc:creator>
      <pubDate>Wed, 05 Aug 2026 12:53:11 +0000</pubDate>
      <link>https://dev.to/martschweiger/best-voice-agent-platform-for-developer-experience-3hi2</link>
      <guid>https://dev.to/martschweiger/best-voice-agent-platform-for-developer-experience-3hi2</guid>
      <description>&lt;p&gt;Every voice agent demo looks great. The gap shows up later — the afternoon you're three providers deep, staring at three dashboards, trying to work out why the agent heard "twenty" as "plenty" and confidently booked the wrong appointment.&lt;/p&gt;

&lt;p&gt;That's the real test of developer experience for voice agents. Not how fast you get a demo talking, but how fast you ship something to production and how much control you keep once it's live. On that test, "best DX" usually gets handed to whichever platform has the most building blocks. We'd argue the opposite: the best developer experience is the one that gives you the fewest moving parts to manage and the most control over the parts that matter.&lt;/p&gt;

&lt;p&gt;Let's compare the real options.&lt;/p&gt;

&lt;h2&gt;
  
  
  What "developer experience" actually means for voice agents
&lt;/h2&gt;

&lt;p&gt;Strip away the marketing and DX for a voice agent comes down to five things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Time to a working agent.&lt;/strong&gt; How long from reading the docs to a real conversation?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Simplicity of the stack.&lt;/strong&gt; How many providers, bills, and dashboards are you managing?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Control after launch.&lt;/strong&gt; Can you change behavior, tools, and settings without rebuilding?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Accuracy of what the agent hears.&lt;/strong&gt; If the transcription is wrong, everything downstream is wrong.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Freedom from lock-in.&lt;/strong&gt; Can you shape the agent to your product, or does every agent end up sounding the same?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Notice that "how many features are on the pricing page" isn't on the list. More surface area is not more DX. Often it's the opposite.&lt;/p&gt;

&lt;h2&gt;
  
  
  The two paths most teams take (and the third one)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Path one: an orchestration platform&lt;/strong&gt; like Vapi or Retell. You get a fast start and a lot of built-in scaffolding. The tradeoff shows up at the ceiling — opinionated conversation design, limited room for niche integrations, and a sameness where every agent built on the platform behaves alike. Great for getting going; frustrating when you need your agent to be genuinely yours.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Path two: do it yourself&lt;/strong&gt; — wire up a separate speech-to-text provider, an LLM, and a text-to-speech provider. Maximum control, but now you own three vendors, three invoices, three sets of logs, and all the glue in between: turn detection, interruption handling, voice activity detection. You'll spend your first weeks on plumbing instead of product.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Path three: one API&lt;/strong&gt; that runs the whole speech-to-speech pipeline — STT, LLM, and TTS — behind a single connection, while still letting you control conversation design, tools, and timing. This is where AssemblyAI's Voice Agent API sits. You own the hardest part of the stack (what the agent hears and how it responds) without owning the integration burden. It's infrastructure you build on, not a platform you build inside.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick comparison
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;AssemblyAI Voice Agent API&lt;/th&gt;
&lt;th&gt;Vapi / Retell&lt;/th&gt;
&lt;th&gt;OpenAI Realtime API&lt;/th&gt;
&lt;th&gt;DIY (STT + LLM + TTS)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Integration model&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;One WebSocket + JSON&lt;/td&gt;
&lt;td&gt;Platform SDK/config&lt;/td&gt;
&lt;td&gt;Realtime API (30+ event types)&lt;/td&gt;
&lt;td&gt;Three separate providers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;SDK required&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes (platform SDK)&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Multiple&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Time to first agent&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Same afternoon&lt;/td&gt;
&lt;td&gt;Fast&lt;/td&gt;
&lt;td&gt;Moderate&lt;/td&gt;
&lt;td&gt;Slow&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Pricing&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Flat $4.50/hr, all-in&lt;/td&gt;
&lt;td&gt;Platform fee + usage&lt;/td&gt;
&lt;td&gt;~$18/hr&lt;/td&gt;
&lt;td&gt;Three separate bills&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;STT accuracy (pooled WER)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;6.99%&lt;/td&gt;
&lt;td&gt;Varies by chosen STT&lt;/td&gt;
&lt;td&gt;Multimodal, not STT-specialized&lt;/td&gt;
&lt;td&gt;Depends on provider&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Live mid-call config&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Limited&lt;/td&gt;
&lt;td&gt;Limited&lt;/td&gt;
&lt;td&gt;You build it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Framework plugins&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;LiveKit, Pipecat&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;td&gt;You build it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Lock-in&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Low (infrastructure)&lt;/td&gt;
&lt;td&gt;Higher (platform)&lt;/td&gt;
&lt;td&gt;Model-tied&lt;/td&gt;
&lt;td&gt;None, but you own everything&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;Pooled WER is from Pipecat's open STT benchmark of real agent conversations. Comparison reflects general positioning; benchmark your own audio.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Pooled WER is from Pipecat's open STT benchmark of real agent conversations. Comparison reflects general positioning; benchmark your own audio.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  DX, criterion by criterion
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Time to a working agent
&lt;/h3&gt;

&lt;p&gt;The Voice Agent API is a standard JSON API over a WebSocket — no SDK, no framework to learn. You can read the full API reference in about 10 minutes, and most developers have a working agent the same afternoon. It works with Claude Code out of the box: copy the docs, paste, build. Connect to the WebSocket, stream audio in, get audio back, write your system prompt, and focus on your product.&lt;/p&gt;

&lt;p&gt;Compare that to standing up three providers yourself, or learning the OpenAI Realtime API's 30-plus event types before your agent says its first word.&lt;/p&gt;

&lt;h3&gt;
  
  
  Simplicity of the stack
&lt;/h3&gt;

&lt;p&gt;This is the heart of it. One WebSocket replaces three providers. One bill — measured in hours, not token math across three invoices. One set of logs instead of three dashboards to correlate when something breaks. At a flat $4.50/hr, STT, LLM, and TTS are all included. When your agent misbehaves at 11pm, you're debugging one surface, not triangulating across three.&lt;/p&gt;

&lt;h3&gt;
  
  
  Control after launch
&lt;/h3&gt;

&lt;p&gt;Shipping the demo is the easy part. The DX difference is what happens when you need to change the agent in production. With the Voice Agent API you can update the system prompt, tools, and settings mid-conversation without reconnecting. Custom functions are defined with plain JSON Schema. You control voice activity detection, turn timing, and barge-in. This is where platforms tend to hit their ceiling — you can configure what they expose, and no more.&lt;/p&gt;

&lt;h3&gt;
  
  
  Accuracy of what the agent hears
&lt;/h3&gt;

&lt;p&gt;Here's the criterion that quietly determines everything: if the STT is wrong, the LLM responds to the wrong thing. The whole pipeline is built around getting the input right. It runs on Universal-3.5 Pro Realtime, which posts a &lt;strong&gt;6.99% pooled word error rate&lt;/strong&gt; on Pipecat's open STT benchmark of real agent conversations — ahead of Deepgram Flux (15.58%), ElevenLabs Scribe v2 (9.76%), and Google Chirp3 (9.04%). It even takes the agent's own question as input via agent_context — which cut word error rate by 10.2% across a benchmark of 20,000 voice agent audio files — and keeps a rolling conversation memory so a one-word "yes" or a spelled-out account number resolves in context. Turn detection reads tonality and pacing, not just silence, so the agent knows the difference between a pause and a finished thought.&lt;/p&gt;

&lt;p&gt;Most platforms treat transcription as a commodity you plug in. We treat it as the foundation, because it is.&lt;/p&gt;

&lt;h3&gt;
  
  
  Freedom from lock-in
&lt;/h3&gt;

&lt;p&gt;The Voice Agent API is invisible infrastructure — "we're not the agent, we're the platform upon which you build." Your customers should feel like you built the whole thing from scratch. And you're not boxed in: there are drop-in plugins for LiveKit and Pipecat, the two most popular open-source voice pipeline frameworks, and sophisticated teams can hit the WebSocket directly. You're building on infrastructure, not renting space inside someone's platform.&lt;/p&gt;

&lt;h2&gt;
  
  
  When a platform is the better choice
&lt;/h2&gt;

&lt;p&gt;If you need no-code configuration so non-technical teammates can build and edit agents, a platform like Vapi or Retell will serve you better today. If you need built-in telephony and call routing out of the box with zero setup, that's a platform strength (telephony is a fast-follow on our side, not shipping yet). And if you never need to go beyond what the platform exposes, its guardrails are a feature, not a limit.&lt;/p&gt;

&lt;p&gt;The Voice Agent API is aimed at developers who want to ship fast &lt;em&gt;and&lt;/em&gt; keep control — roughly 80% of new voice builders start net-new, and they consistently value simplicity and ownership over pre-built scaffolding. In a recent week, 11 of 12 new enterprise accounts were voice-agent or conversation-intelligence builders — the pattern is real.&lt;/p&gt;

&lt;h2&gt;
  
  
  The verdict
&lt;/h2&gt;

&lt;p&gt;If DX means "how many features can I click together," a platform wins. If DX means "how fast can I ship a production voice agent, and how much control do I keep after launch," the answer is one API that owns the hardest part of the stack and gets out of your way on the rest.&lt;/p&gt;

&lt;p&gt;The fastest way to judge developer experience is to build on it. So build one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frequently asked questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;What is the best AI voice agent platform?&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;There's no single best — it depends on whether you optimize for speed-to-demo or long-term control. No-code platforms like Vapi and Retell are fastest for non-technical teams and offer built-in telephony today. For developers who want to ship fast while keeping full control over conversation design and integrations, a single API like AssemblyAI's Voice Agent API offers the best balance: one WebSocket for the full STT-LLM-TTS pipeline, no SDK, flat $4.50/hr, built on a market-leading realtime speech model.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;What's the difference between a voice agent platform and a voice agent API?&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;A platform (Vapi, Retell) is an environment you build inside — fast to start, but with ceilings on customization and a tendency toward sameness. An API (AssemblyAI's Voice Agent API) is infrastructure you build on — you keep control over conversation logic, tools, and timing while the API handles the speech pipeline. Platforms trade control for convenience; APIs trade a little setup for control.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;How much do AI voice agents cost to build?&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;AssemblyAI's Voice Agent API is a flat $4.50/hr that includes speech-to-text, the LLM, and text-to-speech in one bill, billed by the minute. Building it yourself means paying three separate providers and reconciling three invoices, and OpenAI's Realtime API runs closer to $18/hr for a comparable pipeline. Flat, all-in pricing makes voice-agent costs far easier to forecast.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Which voice agent platform is best for startups and small businesses?&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Startups building their first voice product usually value time-to-working-demo and predictable pricing over pre-built scaffolding. A single API with flat $4.50/hr pricing, no SDK, and a 10-minute API reference lets a small team ship an agent the same afternoon and keep control as they grow — without platform lock-in or a per-seat plan. Teams that need non-technical, no-code configuration may prefer a managed platform.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Do I need an SDK to build a voice agent?&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Not with AssemblyAI's Voice Agent API. It's a standard JSON API over a WebSocket — connect, stream audio in, get audio back. You can read the full API reference in about 10 minutes, and it works with Claude Code out of the box.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Vapi vs. Retell vs. AssemblyAI — which has the best developer experience?&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Vapi and Retell win on no-code configuration and built-in telephony. AssemblyAI wins on stack simplicity (one API, one bill, one set of logs), transcription accuracy (6.99% pooled WER on Pipecat's open benchmark), and control after launch (live mid-conversation config, custom tools via JSON Schema, no lock-in). Developers who want to own their product's behavior tend to prefer the API approach.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>voiceassistant</category>
      <category>api</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
