La latenza P99 è il tempo di risposta battuto dal 99% delle richieste. Se il P99 di un’API è 400 millisecondi, 99 richieste su cento tornano in 400 ms o meno e una impiega di più. Il P95 è la stessa idea al 95%: una richiesta su venti è più lenta. Entrambi descrivono la coda della distribuzione, le richieste lente che una media appiattisce.
La coda conta più di quanto suggeriscano le sue dimensioni. Chi fa una sola richiesta raramente se ne accorge. Chi parla con un agente vocale per venti turni probabilmente sì. Questa guida spiega come funzionano i percentili, come calcolarli, gli errori che li rendono ingannevoli e cosa significano per l’IA vocale in tempo reale.
P50, P95 e P99 in breve
| Percentile | Cosa indica | Richieste più lente |
|---|---|---|
| P50 | La mediana, una richiesta tipica | 1 su 2 |
| P90 | L’inizio della coda lenta | 1 su 10 |
| P95 | L’obiettivo abituale di dashboard e SLO | 1 su 20 |
| P99 | Rallentamenti rari: pause, nuovi tentativi, code | 1 su 100 |
| P99.9 | Casi isolati visibili solo con grandi volumi | 1 su 1.000 |
I percentili alti servono a qualcosa solo con più campioni. Un P99 calcolato su 100 richieste si basa su una sola richiesta. Usa almeno qualche centinaio di campioni per il P95 e qualche migliaio per il P99, e riporta il numero di campioni accanto al risultato.
Perché la media nasconde la coda
Prendi 100 richieste. 94 terminano in 200 ms e 6 impiegano due secondi. La media è 308 ms, un tempo che nessuna richiesta ha avuto. La mediana è 200 ms, che sembra ottimo. P95 e P99 sono entrambi 2.000 ms: è quello che hanno visto sei dei tuoi utenti.
Le distribuzioni reali di latenza sono sbilanciate in questo modo. La maggior parte delle richieste prende la strada veloce e una minoranza aspetta dietro un disco lento, una cache fredda, una pausa della garbage collection o una coda piena. La media mescola i due gruppi in un numero che non descrive nessuno dei due. La mediana ignora del tutto il gruppo lento. I percentili lo tengono in vista.
Come calcolare P95 e P99
Ordina le misure dalla più veloce alla più lenta e leggi il valore che si trova al 95% o al 99% dell’elenco. Con 200 campioni, il P95 è il 190° valore e il P99 il 198°. È il metodo del rango più vicino:
import math
def percentile(samples, p):
"""Nearest rank: the smallest sample with at least p% of samples at or below it."""
ordered = sorted(samples)
rank = math.ceil(p / 100 * len(ordered))
return ordered[max(rank, 1) - 1]
latencies_ms = [200] * 94 + [2000] * 6
print(percentile(latencies_ms, 50)) # 200
print(percentile(latencies_ms, 95)) # 2000
print(percentile(latencies_ms, 99)) # 2000
NumPy interpola per impostazione predefinita, quindi numpy.percentile può restituire un valore tra due campioni. Passa method="inverted_cdf" per ottenere il rango più vicino. Con pochi campioni i due metodi possono differire parecchio, un motivo in più per indicare il numero di campioni.
I sistemi di monitoraggio di solito non conservano ogni campione. Contano le richieste in intervalli di latenza e stimano il percentile dall’istogramma, come fa histogram_quantile() di Prometheus. La stima è precisa solo quanto gli intervalli intorno, quindi metti i limiti vicino alle latenze che ti interessano.
Tre errori che rendono ingannevoli i percentili
Fare la media dei percentili. Un server gestisce 900 richieste, tutte in 200 ms, e un secondo ne gestisce 100, dieci delle quali impiegano un secondo. I P95 sono 200 ms e 1.000 ms, e la loro media è 600 ms. Il vero P95 sulle 1.000 richieste è 200 ms. Lo dice anche la documentazione di Prometheus: i quantili calcolati separatamente non si possono aggregare. Unisci gli istogrammi o i campioni, poi calcola.
Sommare i percentili delle fasi. Un agente vocale esegue in sequenza riconoscimento vocale, modello linguistico e sintesi vocale. Supponi che il riconoscimento impieghi sempre 300 ms, l’LLM 500 ms ma 1.500 ms in una chiamata su dieci, e il TTS 150 ms ma 600 ms in una chiamata su dieci. Sommando i P95 delle fasi si ottengono 2.400 ms. Il vero P95 del turno completo è 1.950 ms, perché i due casi lenti capitano di rado nello stesso turno. Misura direttamente il tempo end-to-end invece di stimarlo dalle parti.
Misurare con un client che aspetta. Un generatore di carico che invia la richiesta successiva solo quando torna la precedente ne invia meno proprio quando il server è lento, quindi i periodi lenti risultano sottostimati. Gil Tene la chiama omissione coordinata, e il suo generatore di carico wrk2 esiste per evitarla inviando a ritmo fisso. Per i numeri di produzione, misura il traffico reale e non un client di prova.
Perché la coda decide come si percepisce un agente vocale
La conversazione ha tempi stretti. Una rassegna della ricerca sull’alternanza dei turni di Levinson e Torreira rileva che le pause tra un turno e l’altro sono nell’ordine dei 200 ms. In uno studio su chiamate telefoniche, Kendrick e Torreira hanno osservato che le risposte dopo pause oltre circa 700 ms erano per lo più rifiuti e altre risposte non preferite, e che anche pause oltre circa 300 ms rendevano meno probabile un’accettazione netta. Chi ascolta dà un significato alle pause, quindi per chi chiama un agente che tace per 900 ms può sembrare riluttante, non solo lento.
Ora moltiplica per i turni. Se ogni turno ha, in modo indipendente, il 5% di probabilità di superare il P95, una chiamata di 20 turni ha il 64% di probabilità di incontrarne almeno uno (1 − 0,95²⁰). Anche il P99 compare in circa il 18% delle chiamate di 20 turni. La mediana descrive un turno tipico; la coda descrive una chiamata tipica. Jeff Dean e Luiz André Barroso di Google hanno fatto la stessa osservazione per i grandi servizi in The Tail at Scale: quando un’azione dell’utente si divide in molte richieste, le risposte lente rare diventano comuni.
Cosa misurare in una pipeline vocale
Misura ciò che sente chi chiama. Per un agente vocale è il tempo tra la fine della frase di chi chiama e il primo campione udibile della risposta, registrato dal lato di chi chiama. Poi scomponilo per fase, così sai da dove viene la coda:
| Fase | Cosa cronometrare | Cause comuni della coda |
|---|---|---|
| Rilevamento del turno | Dalla fine del parlato alla chiusura del turno | Esitazioni, rumore di fondo, pause lunghe |
| Riconoscimento vocale | Dalla fine del turno alla trascrizione finale | Turni lunghi, jitter di rete |
| LLM e strumenti | Dalla trascrizione al primo token, strumenti inclusi | Strumenti lenti, prompt lunghi, code del provider |
| Sintesi vocale | Dal primo testo inviato al primo audio udibile | Nuove connessioni, prime frasi lunghe |
| Rete e telefonia | Dall’audio che lascia il server a chi chiama | Instradamento dell’operatore, buffer di jitter |
Il tempo al primo audio (TTFA) è il dato di sintesi vocale da tenere d’occhio, perché un modello in streaming inizia la riproduzione prima che la frase sia completa. Conta anche il silenzio iniziale: un blocco che inizia con 300 ms di silenzio sono 300 ms di attesa in più. Lo script qui sotto cronometra i primi byte, quindi controlla a parte il silenzio iniziale del tuo audio.
Questo script misura i percentili di TTFA della sintesi vocale Sonic di Cartesia. Pianifica 205 richieste a mezzo secondo di distanza, scarta le prime cinque e cronometra ciascuna dall’orario previsto ai primi byte audio. Misurare dall’orario previsto, e non dall’invio, fa comparire nei numeri una risposta lenta che ritarda la richiesta successiva, evitando l’omissione coordinata descritta sopra:
import math
import os
import time
from cartesia import Cartesia
def percentile(samples, p):
"""Nearest rank: the smallest sample with at least p% of samples at or below it."""
ordered = sorted(samples)
rank = math.ceil(p / 100 * len(ordered))
return ordered[max(rank, 1) - 1]
# max_retries=0: a retried request would hide a failure inside a slow sample.
client = Cartesia(api_key=os.environ["CARTESIA_API_KEY"], max_retries=0)
interval = 0.5 # seconds between scheduled requests
ttfa_ms = []
failures = 0
first = time.perf_counter() + interval
for i in range(205):
# Each request has a fixed start time. If a slow response makes us late,
# the wait counts against latency instead of quietly thinning the sample.
scheduled = first + i * interval
time.sleep(max(0.0, scheduled - time.perf_counter()))
got_audio = False
try:
with client.tts.with_streaming_response.generate(
model_id="sonic-3.6",
transcript="Your order shipped this morning and should arrive on Thursday.",
voice="6ccbfb76-1fc6-48f7-b71d-91ac6298247b",
output_format={"container": "raw", "encoding": "pcm_s16le", "sample_rate": 16000},
language="en",
) as response:
for chunk in response.iter_bytes():
if chunk:
got_audio = True
if i >= 5: # the first requests pay for connection setup
ttfa_ms.append((time.perf_counter() - scheduled) * 1000)
break
except Exception:
pass
if not got_audio:
failures += 1
print(f"{len(ttfa_ms)} timed requests, {failures} failed or returned no audio")
if ttfa_ms:
for p in (50, 90, 95, 99):
print(f"p{p}: {percentile(ttfa_ms, p):.0f} ms")
print(f"max: {max(ttfa_ms):.0f} ms")
Installa l’SDK con pip install cartesia e imposta CARTESIA_API_KEY dalla dashboard. Eseguilo dalla regione in cui girano i tuoi server, nell’ora di picco del traffico, perché un portatile sul Wi-Fi di casa misura il Wi-Fi. L’SDK ripete per impostazione predefinita due volte le richieste fallite, il che fonderebbe un errore e il suo nuovo tentativo in un unico campione lento; per questo lo script disattiva i nuovi tentativi e conta a parte gli errori, comprese le risposte senza audio. Non toglierli dal report: una richiesta che non ha mai risposto è la peggiore latenza possibile. Per un agente dal vivo, cronometra il percorso WebSocket che usi davvero, perché mantiene una connessione aperta tra un turno e l’altro.
Cosa mostrano i benchmark pubblici
Coval esegue ogni 30 minuti benchmark indipendenti di sintesi vocale, riconoscimento vocale e voce a voce su API pubbliche, e pubblica mediane, percentili e numero di campioni. La sua metodologia include il silenzio iniziale nel TTFA ed esclude l’apertura delle connessioni.
Nella sua classifica di sintesi vocale a 30 giorni, consultata il 15 ottobre 2026, la distanza tra mediana e P95 variava molto tra 33 modelli con almeno 260 campioni ciascuno. Per il modello tipico, il P95 era 1,38 volte la mediana. Agli estremi, il P95 di un modello era 1,1 volte la sua mediana e quello di un altro più di sette volte: 624 ms nel caso tipico, 4.751 ms per una richiesta su venti.
La lezione è quella dell’inizio di questa guida: due modelli con mediane simili possono dare chiamate molto diverse. Confronta i P95, con il numero di campioni, misurati come li vivono i tuoi utenti.
Ridurre la latenza di coda
- Tieni aperte le connessioni. Ogni nuova richiesta HTTPS ripaga l’apertura di TCP e TLS, e la documentazione di Cartesia osserva che questo costo è spesso dello stesso ordine del tempo al primo byte audio. Un WebSocket aperto per tutta la chiamata lo paga una volta sola.
- Metti in cache ciò che si ripete. Saluti, messaggi di attesa e congedi non vanno generati di nuovo a ogni chiamata. Salvali in cache come audio grezzo e inseriscili nello stream dal vivo.
- Inizia a parlare prima. Invia i token dell’LLM alla sintesi vocale man mano che arrivano invece di aspettare un paragrafo intero.
- Dai una scadenza agli strumenti. Una chiamata a uno strumento che impiega due secondi aggiunge due secondi al turno, ogni volta. Imposta un timeout e fai dire all’agente cosa sta facendo mentre aspetta.
- Duplica le richieste lente. The Tail at Scale descrive l’invio di una richiesta di riserva quando la prima supera il P95 abituale, usando quella che risponde per prima. Aspettare il P95 limita il carico extra a circa il 5%. Fallo solo con richieste che si possono inviare due volte senza rischi, come rigenerare lo stesso audio, mai con uno strumento che scrive dati.
- Esegui i modelli vicino alla chiamata. Se la tua coda dipende dalla distanza di rete o da un’infrastruttura condivisa, il deployment on-premise mette i modelli accanto alla tua telefonia.
Se preferisci non assemblare e regolare la pipeline da solo, Cartesia Managed Agents esegue rilevamento del turno, riconoscimento vocale Ink, LLM e Sonic su un numero di telefono, e puoi crearne uno nel playground e chiamarlo. Se la tua pipeline è lenta, la soluzione di solito sta nella fase indicata dalle tue misure, e lo script qui sopra è il punto di partenza.