Maak kennis met Eleven v4Maak kennis met Eleven v4, ons meest expressieve model tot nu toe.

Naar content

Realtime Speech to Text onder 200 ms: een architectuurgids

Gepubliceerd
Laatst bijgewerkt

LuisterenLuister naar dit artikel

Realtime Speech to Text (STT) transcribeert audio actief terwijl iemand spreekt en geeft de woorden binnen enkele honderden milliseconden als tekst terug. Maar STT-latentie laag houden is evenzeer een architectuurprobleem als een modelprobleem. Engineers moeten rekening houden met het transport, chunking, end-pointing en het opnamepad, die elk latentie toevoegen. Inefficiëntie in een van deze onderdelen kan je budget van 200 ms overschrijden.

Deze gids biedt een praktisch systeem waarmee je realtime Speech to Text-pipelines kunt bouwen vanaf de transportlaag. We richten ons op Scribe v2 Realtime, dat gedeeltelijke transcripties produceert met ongeveer 150 ms modellatentie, meer dan 90 talen ondersteunt, PCM-audio (8 kHz-48 kHz) en mu-law accepteert, en Voice Activity Detection plus handmatige commitbesturing voor het finaliseren van segmenten biedt.

We bekijken hoe de audio de server bereikt, hoe hypotheses uitgroeien tot vastgelegde tekst, wat in-streamfuncties kosten en hoe je audio correct opneemt en doorstuurt.

Samenvatting

  • Voor het maken van realtime Speech to Text-systemen is architecturale verfijning nodig, zodat de latentie in de hele pipeline laag blijft.
  • WebSocket is voor de meeste pipelines de juiste standaard, al biedt WebRTC diverse voordelen en is het complexer.
  • Voice Activity Detection verzorgt handsfree segmentatie, terwijl handmatige commit je applicaties de mogelijkheid geeft dit te overschrijven wanneer ze weten dat de beurt voorbij is.
  • Partials zijn voorlopig en finals zijn vastgelegd. Je moet ze daarom verschillend weergeven. 
  • Kleine PCM-chunks van ongeveer 100 ms minimaliseren de latentie tot de eerste partial.

WebSocket versus WebRTC voor realtime Speech to Text

Voordat transcriptie begint, moet audio van de bron naar de herkenner reizen. Het kanaal dat je kiest, bepaalt de ondergrens voor de latentie van alles wat volgt. Er zijn twee haalbare opties om audio bij de transcriptielaag te krijgen.

WebSocket is een langlevend, geordend, betrouwbaar, bidirectioneel kanaal via TCP. Je opent een verbinding, verstuurt binaire audioframes en ontvangt transcriptevents. Het is eenvoudig aan zowel client- als serverzijde, gaat door bedrijfsproxies en firewalls die HTTPS al toestaan, en wordt door elke browser- en serverruntime ondersteund. 

De beperking van WebSocket is dat het op TCP draait. Als een pakket verloren gaat, verzendt TCP het opnieuw en houdt het latere gegevens tegen totdat het gat is opgevuld. Bij goede netwerkomstandigheden merk je dit niet. Bij pakketverlies veroorzaakt het head-of-line blocking: een korte onderbreking waarbij audio zich ophoopt en vervolgens in een burst arriveert.

WebRTC is gemaakt voor realtime media. Het verzendt media via UDP (met SRTP), dus een verloren pakket houdt de stream niet op; de pipeline gaat door. Het bevat een jitterbuffer die variatie in de aankomsttijd van pakketten opvangt, onderhandelt NAT-traversal met ICE/STUN/TURN zodat peers achter routers verbinding kunnen maken, en heeft eigen mechanismen voor audio-opname en -codering. 

Je hebt doorgaans TURN-servers nodig voor clients die niet rechtstreeks verbinding kunnen maken, en aan de serverzijde moet je een mediastream beëindigen in plaats van een bytestream lezen.

Hier zie je in één oogopslag de afweging:

WebSocket
Transport
TCP (reliable, ordered)
Behavior under packet loss
Head-of-line blocking, bursty recovery
Jitter handling
Your responsibility
NAT traversal
Not needed (client-initiated)
Browser support
Universal, trivial
Server complexity
Low
WebRTC
Transport
UDP/SRTP (real-time, loss-tolerant)
Behavior under packet loss
Graceful degradation
Jitter handling
Built-in jitter buffer
NAT traversal
Requires ICE/STUN/TURN
Browser support
Universal, but more API surface
Server complexity
High (media server or SFU)

Voor de meeste use cases is WebSocket de juiste optie. Gebruik het wanneer je clients een redelijke verbinding hebben en je het opnamepad beheert: server-naar-serverpipelines, desktopapps, browserapps op breedband en de meeste contactcenterbackends waarbij audio al op een andere manier bij je server aankomt. 

Kies WebRTC wanneer je rechtstreeks opneemt vanaf consumentenapparaten op onbetrouwbare mobiele netwerken, wanneer je al een WebRTC-stack gebruikt voor tweerichtingsaudio (bijvoorbeeld een spraakagent die ook terugspreekt), of wanneer realtime gedrag met weinig verlies belangrijker is dan eenvoudige implementatie.

In de rest van deze gids gebruiken we WebSocket-transport voor de verbinding met de herkenner, omdat daarmee de bewegende onderdelen zichtbaar blijven en het voor de meeste teams het juiste startpunt is. Niets hiervan is specifiek voor WebSocket, dus je kunt later een WebRTC-mediaverbinding ervoor plaatsen, de audio op de server naar PCM decoderen en dezelfde chunks naar de pipeline doorsturen.

Partials en definitieve transcripties: tussentijdse resultaten uitgelegd

Een realtime herkenner wacht niet op een volledige zin voordat hij resultaten geeft. In plaats daarvan geeft hij een doorlopende stroom van gissingen af die scherper worden naarmate er meer audio binnenkomt, en legt hij ze vervolgens vast. Het verschil tussen deze twee statussen begrijpen, maakt het verschil tussen een transcript dat levendig voelt en een transcript dat kapot voelt. 

Een partial-hypothese (tussentijds) is de beste inschatting van het model op basis van de tot nu toe ontvangen audio. Partials zijn bewust instabiel. Naarmate er meer audio binnenkomt, herziet het model eerdere woorden: "Ik wil" kan veranderen in "Ik wil twee tickets" zodra latere context de dubbelzinnigheid oplost. Ze komen snel binnen (dit is wat het latentiegetal van ~150 ms beschrijft) en zijn bedoeld om te worden overschreven.

Een final-hypothese is een vastgelegd segment dat niet meer verandert. Zodra een segment is gefinaliseerd, gaat de herkenner verder en beschrijven volgende hypotheses latere audio. Finals zijn wat je bewaart, naar een LLM stuurt of als transcript opslaat.

Het onderscheid tussen partials en finals bepaalt drie dingen die je verkeerd doet als je ze door elkaar haalt:

  • Gebruikerservaring: Door partials te tonen voelt een transcript live: de gebruiker ziet woorden verschijnen terwijl die spreekt, wat bevestigt dat de microfoon werkt en het systeem luistert.
  • End-pointing: Partials geven je een continu signaal van spraakactiviteit. Gecombineerd met VAD kun je ermee bepalen wanneer de spreker daadwerkelijk is gestopt.
  • Timing downstream: In een spraakagentpipeline zijn de stappen: audio in, vervolgens Speech to Text, daarna een LLM, vervolgens Text to Speech, en dan audio uit. Je kunt op basis van partials al speculatief werk starten en dit bij finals bevestigen. Zo verlaag je de ervaren reactietijd, ten koste van het af en toe weggooien van speculatief werk.

Geef partials en finals verschillend weer. Een eenvoudig, effectief patroon is om één veranderlijke "huidige regel" te koppelen aan de nieuwste partial en die bij een final toe te voegen aan een alleen-aanvulbaar transcript:

type TranscriptState = {
  committed: string[]; // finalized segments, never rewritten
  current: string;     // latest partial, overwritten on each update
};

const onPartial = (s: TranscriptState, text: string): TranscriptState =>
  ({ ...s, current: text });

const onFinal = (s: TranscriptState, text: string): TranscriptState =>
  ({ committed: [...s.committed, text], current: "" });

Geef vastgelegde tekst visueel weer als definitief en de huidige tekst lichter of cursief, zodat de gebruiker begrijpt dat die nog kan veranderen.

End-pointing en voice activity detection (VAD)

Weten wat er is gezegd, is maar de helft van het werk. Een herkenner moet ook weten wanneer een gedachte is afgelopen. Die beslissing bepaalt wanneer je een segment finaliseert en, bij een agent, wanneer het systeem begint te reageren. 

End-pointing is de beslissing dat een uiting is afgelopen. Te vroeg finaliseren onderbreekt gebruikers midden in een zin. Te laat finaliseren laat een agent stil na een gebruiker duidelijk klaar is.

Scribe v2 Realtime biedt twee aanvullende mechanismen:

  • Voice Activity Detection segmenteert audio op basis van stilte: De herkenner detecteert wanneer spraak overgaat in aanhoudende stilte en gebruikt die grens om een segment automatisch te finaliseren. VAD is de juiste standaard voor conversatie-interfaces, omdat het zich aanpast aan een natuurlijk spreekritme zonder dat je de timing handmatig bijhoudt.
  • Handmatige commitbesturing: Met handmatige commitbesturing bepaalt je applicatie wanneer het huidige segment wordt gefinaliseerd, onafhankelijk van stilte. Je stuurt een commit-signaal, de herkenner sluit het huidige segment en geeft een final af. Dit is het juiste hulpmiddel wanneer je applicatie al weet dat de beurt voorbij is: wanneer een push-to-talkknop wordt losgelaten, bij een actie "verzenden" of volgens een extern beurtnamebeleid.

De twee werken goed samen. Een typische spraakagent gebruikt VAD voor handsfree bediening en biedt handmatige commit als overschrijving, zodat een gebruiker die pauzeert om na te denken niet wordt onderbroken, terwijl een gebruiker die op een knop tikt direct een grens krijgt.

De stiltedrempel is een echte afweging zonder universeel correcte waarde:

  • Een korte time-out na spraak (bijvoorbeeld finaliseren na ~200-400 ms stilte) laat het systeem responsief voelen. Maar gebruikers die van nature tussen zinsdelen pauzeren, worden ook onderbroken, waardoor één gedachte in meerdere segmenten wordt gesplitst en bij een agent een voortijdige reactie wordt getriggerd.
  • Een lange time-out (bijvoorbeeld ~800-1200 ms) verdraagt natuurlijke pauzes en houdt uitingen intact, maar veroorzaakt een merkbare vertraging voordat het systeem reageert.

Hiervoor bestaat geen globale constante; stem de drempel af op de interactie:

  • Dicteren en notities maken verdragen langere pauzes, omdat gebruikers midden in een zin nadenken. Kies eerder voor langere time-outs en leun op VAD.
  • Opdracht- en besturingsagents en transactionele agents profiteren van kortere time-outs plus handmatige commit, omdat beurten kort en duidelijk zijn.
  • Meertalige sprekers of sprekers die geen moedertaalsprekers zijn, pauzeren vaker. Reken daarom op meer stilte voordat je finaliseert.

Met deze tips kun je een effectief end-pointingsysteem bouwen en dichter bij realtime Speech to Text komen.

In-streamfuncties: taaldetectie en sprekerdiarisatie

Streamingherkenning kan meer dan woorden produceren. Elk extra signaal dat je opvraagt, heeft echter invloed op latentie en stabiliteit. De vuistregel is om alleen in te schakelen wat de live ervaring nodig heeft en de rest uit te stellen tot een batchverwerking.

Automatische taalherkenning laat Scribe v2 Realtime de gesproken taal identificeren binnen de meer dan 90 ondersteunde talen, zonder dat je die vooraf hoeft op te geven. Het model heeft daarvoor kort audio nodig om met vertrouwen een keuze te maken, waardoor de eerste partials van een stream minder stabiel kunnen zijn terwijl de taal wordt vastgesteld. Als je de taal al kent, neemt het opgeven ervan die dubbelzinnigheid weg en levert het meestal stabielere vroege partials op.

Sprekerdiarisatie wijst spraak toe aan afzonderlijke sprekers en identificeert wie wat zei. In batchtranscriptie is dit relatief eenvoudig omdat het model het hele bestand ziet. Bij streaming is het moeilijker: de herkenner moet een sprekerlabel toewijzen op basis van alleen de tot dan toe ontvangen audio, en een label voor vroege audio moet mogelijk worden herzien wanneer meer van de stem van die spreker hoorbaar is. Behandel speakerlabels bij streaming zoals partiale tekst: voorlopig totdat het segment is gefinaliseerd.

Timing op woordniveau en entiteitscontext volgen dezelfde logica. Hoe meer metadata per token je opvraagt, hoe meer zowel het model als de verbinding moet verwerken. Voor de meeste realtime UI's heb je alleen de tekst en segmentgrenzen live nodig; de gedetailleerde metadata kun je uitstellen tot een batchverwerking na de call met Scribe v2.

Audioformaten voor streaming: PCM en mu-law

Transport- en herkenningslogica krijgen de meeste aandacht, maar verrassend veel bugs in de praktijk ontstaan één laag lager: bij het coderen en chunken van audio. Het juiste formaat en de juiste chunkgrootte kiezen, is de voordeligste manier om Speech to Text-latentie te verlagen.

PCM (lineair, 16-bits signed, little-endian) is het formaat dat je gebruikt wanneer je de opname beheert. Hogere sample rates bevatten meer akoestisch detail: 16 kHz is de standaardondergrens voor spraakherkenning en meestal voldoende; 8 kHz is telefoonkwaliteit en verliest inhoud met hoge frequenties. Gebruik de sample rate die bij je bron past. Het heeft geen zin om telefonieaudio van 8 kHz op te schalen naar 48 kHz, omdat die informatie niet kan worden hersteld.

Mu-law op 8 kHz is het telefonieformaat. Als je gesprekken van een provider zoals Twilio binnenhaalt, komt audio binnen als mu-law op 8 kHz en kun je deze beter in dat formaat doorsturen dan twee keer transcoderen. Het bronformaat behouden voorkomt resamplingartefacten en een onnodige conversiestap.

De chunkgrootte is de knop die de ervaren latentie het meest direct beïnvloedt. Je verstuurt audio in chunks en de herkenner produceert partials zodra chunks binnenkomen. Kleinere chunks betekenen vaker updates en minder latentie tot de eerste partial; grotere chunks betekenen minder berichten en iets meer context per inferentie. Een praktisch bereik is 20-250 ms audio per chunk. Als concreet voorbeeld: bij mono 16-bits PCM op 16 kHz is één seconde audio 32.000 bytes, dus een chunk van 100 ms is ongeveer 3.200 bytes.

Microfooninvoer opnemen in de browser

In de browser is de juiste tool de Web Audio API met een AudioWorklet. De worklet draait op de audiothread voor rendering, ontvangt audio in kleine frames en ondervindt geen haperingen op de main thread zoals de oudere ScriptProcessorNode. De taak is om de oorspronkelijke float-samples van de browser om te zetten naar 16-bits PCM en ze aan de main thread te geven, die ze via de WebSocket doorstuurt.

De kern van de workletprocessor is de conversie van float naar PCM:

// pcm-worklet.ts - registered via audioContext.audioWorklet.addModule()
class PCMWorklet extends AudioWorkletProcessor {
  process(inputs: Float32Array[][]) {
    const channel = inputs[0]?.[0]; // mono; Float32, range [-1, 1]
    if (!channel) return true;
    const pcm = new Int16Array(channel.length);
    for (let i = 0; i < channel.length; i++) {
      const s = Math.max(-1, Math.min(1, channel[i]));
      pcm[i] = s < 0 ? s * 0x8000 : s * 0x7fff;
    }
    // Transfer the buffer to the main thread without copying.
    this.port.postMessage(pcm.buffer, [pcm.buffer]);
    return true;
  }
}
registerProcessor("pcm-worklet", PCMWorklet);

De pipeline in code

De pipeline bestaat uit drie onderdelen: een browserclient die de microfoon opneemt en PCM naar je server streamt, een Node-server die audio doorstuurt naar Scribe v2 Realtime en transcripties terugstuurt, en een scriptbare client die PCM streamt vanuit een bestand of telefoniebrug.

De server stuurt door in plaats van de herkenner rechtstreeks aan de browser bloot te stellen, om één belangrijke reden: je ElevenLabs API-sleutel is geheim en mag nooit in code aan clientzijde staan. De server bewaart de sleutel. Als de browser toch rechtstreeks met de herkenner moet communiceren, maak dan server-side een token voor eenmalig gebruik met een korte levensduur en geef dat aan de client in plaats van de API-sleutel.

Browserclient

De client opent een WebSocket naar je server, neemt de microfoon op met de worklet hierboven en stuurt elk PCM-frame door zodra het wordt geproduceerd. Binnenkomende events (door de server al genormaliseerd naar { type, text }) bepalen de eerdere partial/final-status:

// client.ts - runs in the browser. ws is an open WebSocket to your server.
const audioContext = new AudioContext({ sampleRate: 16000 });
await audioContext.audioWorklet.addModule("pcm-worklet.js");

const mediaStream = await navigator.mediaDevices.getUserMedia({
  audio: { channelCount: 1, echoCancellation: true, noiseSuppression: true },
});

const source = audioContext.createMediaStreamSource(mediaStream);
const worklet = new AudioWorkletNode(audioContext, "pcm-worklet");

// Forward each PCM frame to the server the moment it is produced.
worklet.port.onmessage = (e: MessageEvent<ArrayBuffer>) => {
  if (ws.readyState === WebSocket.OPEN) ws.send(e.data);
};
source.connect(worklet);

// Manual commit: tell the server to finalize the current segment.
const commit = () => ws.send(JSON.stringify({ type: "commit" }));

Serverrelay

De server opent één herkennerverbinding per client, bewaart de API-sleutel op de server, stuurt binair PCM rechtstreeks door en normaliseert herkennerevents naar de stabiele vorm { type, text } die de client gebruikt:

// server.ts - Node, using the `ws` library. ELEVENLABS_API_KEY and the
// recognizer URL come from the environment; see the Speech to Text reference
// for the exact path and query parameters.
import { WebSocketServer, WebSocket } from "ws";

new WebSocketServer({ port: 8080 }).on("connection", (client) => {
  // The API key stays on the server, never on the wire to the browser.
  const recognizer = new WebSocket(process.env.RECOGNIZER_WSS_URL!, {
    headers: { "xi-api-key": process.env.ELEVENLABS_API_KEY! },
  });

  // Browser -> recognizer: forward binary PCM, translate control messages.
  client.on("message", (data, isBinary) => {
    if (recognizer.readyState !== WebSocket.OPEN) return;
    if (isBinary) recognizer.send(data); // raw PCM bytes
    else if (JSON.parse(data.toString()).type === "commit")
      recognizer.send(sendCommit());
  });

  // Recognizer -> browser: normalize events into a stable shape.
  recognizer.on("message", (raw) => {
    const event = parseRecognizerEvent(raw.toString());
    if (event && client.readyState === WebSocket.OPEN)
      client.send(JSON.stringify(event));
  });

  // ... open handshake, queueing pre-open audio, and teardown on close/error
});

Alles wat endpointspecifiek is, is beperkt tot de twee adapterfuncties hieronder. Vervang de veldnamen door de exacte namen uit de Speech to Text-referentie; de rest van de pipeline verandert niet:

// The single place that knows the recognizer's wire format.
const sendCommit = (): string => JSON.stringify({ type: "commit" });

type NormalizedEvent =
  | { type: "partial"; text: string }
  | { type: "final"; text: string }
  | { type: "vad"; speaking: boolean };

function parseRecognizerEvent(raw: string): NormalizedEvent | null {
  const msg = JSON.parse(raw);
  if (msg.is_final === true || msg.type === "final")
    return { type: "final", text: msg.text ?? "" };
  if (msg.type === "vad") return { type: "vad", speaking: !!msg.speaking };
  if (typeof msg.text === "string")
    return { type: "partial", text: msg.text };
  return null;
}

Scriptbare backendclient

Voor backendpipelines en voor de benchmark hieronder werkt dezelfde herkennerverbinding zonder browser: lees PCM uit elke bron, verstuur het volgens het realtime chunkritme en lees events terug. De API-sleutel en URL komen net als op de server uit de omgeving.

// stream-stt.ts - pace ~100ms chunks at real time, then commit the tail.
const SAMPLE_RATE = 16000, CHUNK_MS = 100;
const CHUNK_BYTES = (SAMPLE_RATE * 2 * CHUNK_MS) / 1000; // 3200 bytes
const ws = new WebSocket(process.env.RECOGNIZER_WSS_URL!, {
  headers: { "xi-api-key": process.env.ELEVENLABS_API_KEY! },
});

// Send: walk the PCM buffer in 100ms chunks, sleeping between to mimic a
// live source. For audio that already arrives in real time, drop the sleep.
async function sendAudio(pcm: Buffer) {
  for (let off = 0; off < pcm.length; off += CHUNK_BYTES) {
    ws.send(pcm.subarray(off, off + CHUNK_BYTES));
    await new Promise((r) => setTimeout(r, CHUNK_MS));
  }
  ws.send(JSON.stringify({ type: "commit" })); // finalize the trailing segment
}

// Receive: print partials in place, append finals.
ws.on("message", (raw) => {
  const e = parseRecognizerEvent(raw.toString());
  if (e?.type === "final") console.log(`[final]   ${e.text}`);
  else if (e?.type === "partial") process.stdout.write(`[partial] ${e.text}\r`);
});

Speech to Text-latentie en word error rate benchmarken

Latentie en word error rate variëren beide met de spreker, de taal, de akoestische omstandigheden, de audiolengte, je netwerkpad naar de dichtstbijzijnde regio van elke provider en de huidige belasting van elke service. 

Een resultaat gemeten op een laptop in de ene stad is niet generaliseerbaar naar je productieomgeving in een andere stad. Voer de harness uit vanuit infrastructuur die op productie lijkt, met audio die op je werkelijke invoer lijkt, en rapporteer bereiken en verdelingen in plaats van losse getallen.

De enige latentie- en nauwkeurigheidswaarden die ertoe doen, zijn de waarden die je meet op je eigen audio vanuit infrastructuur die op productie lijkt. Hier volgt een gids voor het benchmarken van Speech to Text-latentie.

Wat je moet meten voor Speech to Text-latentie

Hieronder staan de belangrijkste meetwaarden die je moet meten bij het benchmarken van realtime Speech to Text-latentie:

  • Tijd tot eerste partial: Van het versturen van de eerste audiochunk tot het ontvangen van de eerste niet-lege partial.
  • Vertraging van partial tot final: Van de laatste audiochunk van een uiting tot de final-hypothese.
  • Word error rate (WER): WER van het gefinaliseerde transcript ten opzichte van een menselijke referentie, identiek berekend voor alle systemen.
  • Stabiliteitsveranderingen: Hoeveel partials worden herschreven voordat ze worden gefinaliseerd. Deze meetwaarde geeft een indicatie van hoeveel de live UI zichtbaar verandert.

Controles

Voer verschillende controles in je experiment in om onbetrouwbare gegevens te vermijden en alles consistent te houden.

Dit zijn de belangrijkste controles voor het benchmarken van Speech to Text-latentie:

  • Identieke audio: Gebruik voor elk systeem dezelfde bestanden, sample rate en codering.
  • Identiek ritme: Stream elk systeem met hetzelfde realtime chunkritme (bijvoorbeeld chunks van 100 ms).
  • Herhaal en rapporteer verdelingen: Voer elk bestand gedurende de dag vaak uit; rapporteer de mediaan en de staart (p50/p95).
  • Identieke referenties en scoring: Normaliseer tekst op dezelfde manier (hoofdletters, interpunctie, getallen) voordat je WER berekent.
  • Vermeld regio en netwerk: Geef aan waar de harness draaide en wat het pad naar elke provider was.

Door al deze elementen gelijk te houden, krijg je nauwkeurigere meetwaarden.

Skelet voor de harness

De meetkern gebruikt een provideradapter en registreert tijd tot eerste partial, finalisatievertraging en veranderingen in partials:

// benchmark.ts - measurement core; one StreamFn adapter per provider.
type StreamFn = (
  audioPath: string,
  onEvent: (kind: "partial" | "final", text: string) => void,
  result: RunResult
) => Promise<void>; // adapter sets result.lastChunkSentAt on the final chunk

interface RunResult {
  firstPartialMs?: number;
  finalLagMs?: number;
  hypothesis: string;
  partialEdits: number;
  lastChunkSentAt: number;
  startedAt: number;
}

async function measure(streamFn: StreamFn, audioPath: string): Promise<RunResult> {
  const result: RunResult = {
    hypothesis: "", partialEdits: 0, lastChunkSentAt: 0,
    startedAt: performance.now(),
  };
  let prevPartial = "";

  await streamFn(audioPath, (kind, text) => {
    const now = performance.now();
    if (kind === "partial") {
      if (text && result.firstPartialMs === undefined)
        result.firstPartialMs = now - result.startedAt;
      if (text !== prevPartial) { result.partialEdits++; prevPartial = text; }
    } else { // final
      result.hypothesis = result.hypothesis ? `${result.hypothesis} ${text}` : text;
      if (result.lastChunkSentAt)
        result.finalLagMs = now - result.lastChunkSentAt;
    }
  }, result);

  return result;
}

Word error rate is een standaard-Levenshtein-afstand op tokenniveau voor genormaliseerde tekst. Zet zowel de referentie als de hypothese om naar kleine letters en verwijder interpunctie op identieke wijze voordat je deze berekent, anders meet je je normalisator in plaats van het model. Plaats deze meting in een loop die elk bestand ongeveer 10 keer per provider uitvoert en de mediane tijd tot eerste partial en mediane WER (p50/p95) rapporteert, omdat één sample vooral wordt beïnvloed door netwerkvariatie.

Om dit te laten draaien, lever je twee dingen aan. Schrijf eerst één StreamFn-adapter per systeem. De scriptbare client hierboven is er al een; adapters voor de andere systemen volgen hetzelfde contract (audioPath, onEvent, result) en stellen result.lastChunkSentAt in wanneer de laatste audiochunk wordt verstuurd. Laad vervolgens je audiobestanden en referenties en roep de metingen hiervoor aan. Voer dit uit vanaf een machine die je deployment vertegenwoordigt, met audio die je gebruikers vertegenwoordigt, en je hebt een vergelijking die je kunt reproduceren.

Samenvatting: realtime Speech to Text realiseren 

In dit artikel hebben we veel architecturale aanpassingen behandeld waarmee je je systeem iteratief kunt verbeteren en dichter bij realtime Speech to Text komt.

Een realtime STT-systeem in productie komt neer op een aantal beslissingen:

  • Transport: Kies WebSocket voor eenvoud en gecontroleerde netwerken, en WebRTC wanneer je bestand moet zijn tegen verlies en opneemt vanaf consumentenapparaten.
  • Partials en finals: Behandel partials als voorlopig en finals als vastgelegd, en geef ze verschillend weer zodat gebruikers de live tekst vertrouwen.
  • End-pointing: Gebruik VAD voor handsfree segmentatie, handmatige commit als overschrijving en stem de stiltedrempel af op de interactie in plaats van op een vaste constante.
  • In-streamfuncties: Schakel in-streamfuncties alleen in waar de live ervaring ze nodig heeft en stel de rest uit tot een batchverwerking met Scribe v2.
  • Audioformaat: Neem op in kleine PCM-frames, verstuur chunks van ~100 ms en gebruik voor telefonie het bronformaat.
  • Benchmarking: Stel de knoppen voor nauwkeurigheid versus latentie empirisch in op basis van je eigen audio en doelmeetwaarde.
  • API-beveiliging: Bewaar je API-sleutel op de server, of maak tokens voor eenmalig gebruik voor rechtstreekse clientverbindingen.

Als je wilt zien hoe je latentie in een spraakagent optimaliseert, hebben we daar ook een gids over geschreven.

Bouw realtime Speech to Text-systemen met Scribe v2 Realtime

Scribe v2 Realtime produceert partials met ongeveer 150 ms modellatentie. Of je gebruikers die waarde ervaren of iets hogere latentie, hangt af van de architectuur eromheen, en dat is het deel dat je beheert. Met de strategieën in dit artikel bouw je een betere pipelinearchitectuur die latentie verlaagt en de klantervaring verbetert. 

Bekijk voor meer informatie het overzicht van Speech to Text-mogelijkheden, lees onze modelreferentie voor de volledige lijst met functies en talen, en bezoek de realtime productpagina's: realtime Speech to Text API en realtime Speech to Text.

Wanneer je klaar bent om te bouwen, maak je gratis een ElevenLabs-account en stream je vandaag nog je eerste transcript.

Veelgestelde vragen over realtime Speech to Text-latentie

Geschreven door

Tadas Petra is Developer Relations Engineer en helpt ontwikkelaars bouwen met de productreeks van ElevenLabs. Hij is ook de oprichter van Hungrimind, een onderwijsplatform voor ontwikkelaars. Eerder was hij Lead Developer Advocate bij Agora en docent bij Zero To Mastery.

Vergelijkbare artikelen

Creëer met AI-audio van de hoogste kwaliteit