What to expect
Before optimizing or comparing providers, measure your own deployment
with a fixed model, voice, region, and endpoint.
The three factors
End-to-end latency decomposes into three parts; each has different levers.- Inference — the model itself. You don’t tune this directly; you avoid paying the model prefill more often than necessary (see chunking — every client-side flush forces a fresh model prefill).
- Processing — what happens to your text before inference. Language
auto-detection adds work when
languageis unset. Output resampling also adds processing when you request a non-native sample rate. - Network — your RTT to the API, paid once per message exchange and several times during a connection handshake. Pick the closest region, and pre-connect so the handshake never lands in a user-visible request.
Levers
Pre-connect at startup
The single biggest fix. Without it, your first request pays the full WebSocket handshake; with it, the handshake happens at application startup where nobody is waiting.client.connect() (JavaScript
and Java) and Python’s async KugelAudio.create() warm the pooled
single-request WebSocket used by stream; a streamingSession owns a separate
socket, so call that session’s connect() before the user interaction.
Connections are reusable across turns — see
Turn lifecycle. Python’s
synchronous generate() / stream() wrappers create and close a connection
on their own event-loop thread, so use stream_async() or
streaming_session_sync() when connection reuse matters.
Set the language explicitly
Whenlanguage is unset and normalization is on, the server auto-detects the
language. If you know the language, say so:
Let the server chunk; flush once per turn
Client-side per-sentence flushing forces a fresh model prefill per segment — the most common self-inflicted latency bug. Send tokens as they arrive, flush exactly once at the end of the turn. Full guidance: Chunking & per-segment latency and Turn lifecycle.Avoid extra model prefills
Let the server group incoming text at natural sentence boundaries, and avoid forcing extra model prefills with client-side flushes. See Chunking & per-segment latency.Pick the right region and sample rate
Use the region closest to your servers. Keep the native24000 Hz sample rate when you can; other rates require resampling and do not
make inference faster.
Measuring TTFA correctly
Time-to-first-audio is the metric that matters for voice agents. Measure it correctly or you’ll chase the wrong bottleneck.Pre-connect, then measure
Including the handshake in a TTFA measurement makes every other change look smaller than it is. Pre-connect first, start the clock after the connection is open:What to measure
Report p50 and p95 over a repeated set of warm requests, not a single measurement. Keep the text, voice, model, language, endpoint, and region fixed.Reference benchmark
The Java SDK ships a complete TTFA bench you can run against any endpoint (cloud or self-hosted):packages/public/java-sdk/benchmark/src/main/java/com/kugelaudio/bench/TTFABench.java
measures:
- Cold TTFA (first request, includes handshake) vs pooled TTFA (subsequent requests, connection reused) — quantifies what pre-connecting saves on your network.
- TTFA across chunking strategies (full-text, sentence, ≥20-char, clause, word) — the cost of small flushes.
- RTF on long-form text.
Common reporting mistakes
- Including the handshake in TTFA. Cold-start cost that has nothing to do with the model. Pre-connect first.
- Measuring only against
localhost. That omits the network path your production users experience. - Single-shot timings. Cold caches, GC pauses, JIT, and scheduler jitter can dominate one request. Compare distributions of repeated warm requests.
- Mixing inference TTFA and end-to-end TTFA. Decide which one you’re reporting and label it. Comparing one to the other across vendors is how people end up with wrong “we’re slower than X” conclusions.
Next steps
Chunking & per-segment latency
Why per-sentence flushing increases TTFA and how server chunking works
Turn lifecycle
How turns start and end, session reuse, and the idle auto-flush