Files
vocero-client/debug-mcp-audio-stutter.md
T
2026-08-26 17:42:00 -04:00

3.8 KiB

Debug Session: mcp-audio-stutter

  • Status: [OPEN]
  • Issue: Piccole interruzioni audio quando una risposta MCP lunga viene eseguita o reiniettata nella conversazione live.

Reproduction Steps

  1. Avvia la UI realtime.
  2. Esegui una richiesta che attiva un tool MCP con output grande.
  3. Ascolta la risposta audio dell'assistente.
  4. Verifica se compaiono micro-interruzioni durante o subito dopo l'iniezione del risultato MCP.

Hypotheses & Verification

ID Hypothesis Likelihood Effort Evidence
A Inviare al modello il payload MCP quasi completo degrada la fluidità della risposta audio anche senza bloccare il main thread. High Low Confirmed
B Esiste una race: il fallback MCP decide il turno prima che response.created arrivi, quindi la prima response speculativa parte comunque e viene tagliata. High Medium Confirmed
C La chat/UI fa render pesanti quando arriva il risultato MCP grande. Medium Medium Rejected
D I log/debug del payload MCP grande aggravano il problema sul main thread. Medium Low Rejected
E L'attacco audio di ogni frase soffre di jitter iniziale perché il playback parte senza prebuffer sul primo chunk disponibile. High Medium Confirmed

Log Evidence

  • execRemoteMcpTool riceve un result JSON di 5738 byte e produce un output testo di 3262 caratteri senza tempi anomali.
  • built live prompt from MCP output mostra promptLength ~3400 e buildMs: 0.
  • rendered tool result row e rendered MCP tool result in chat mostrano renderMs: 0-1.
  • sending conversation.item.create payload mostra payloadLength ~3790 e stringifyMs: 0.
  • Quindi serializzazione, render UI e stringify non spiegano i micro-tagli.
  • Nel run post-fix sendUserText mostra responseActive: false, openResponses: 0, createInFlight: false: non è un classico barge-in su response già attiva.
  • Il sintomo residuo si manifesta solo sulla prima frase speculativa; questo è coerente con una race prima di response.created.
  • Il browser può decidere il fallback MCP prima che il client abbia un responseId da silenziare: per questo ora viene marcata anche la next response come sopprimibile.
  • L'ultimo sintomo riferito dall'utente cambia ancora: le micro-interruzioni si sentono sulle prime parole di ogni frase, non solo su quella speculativa.
  • Questo è coerente con il worklet audio-playback che parte subito sul primo chunk ricevuto, senza accumulare buffer iniziale.

Minimal Fix

  • Accodata la sendUserText() del payload MCP quando esiste una response attiva.
  • Flush del testo differito solo dopo response.done, prima del response.create già in coda.
  • Riassunto/troncamento del payload MCP prima di reiniettarlo nella conversazione live.
  • Ridotto il delay del fallback MCP a 150ms per match forti e senza argomenti obbligatori.
  • Soppressione della next speculative response se il fallback MCP vince il turno prima che response.created sia arrivato.
  • Introdotto un piccolo prebuffer di avvio (90ms) nel worklet audio-playback prima di iniziare ogni frase.
  • Obiettivo: evitare sia il barge-in quando presente, sia il carico eccessivo sul modello con JSON troppo grande.
  • Obiettivo aggiuntivo: ridurre la finestra in cui il modello può iniziare una frase speculativa prima che parta il tool MCP.
  • Obiettivo finale: evitare il clipping/jitter sulle prime parole di ogni frase.

Verification Conclusion

  • Pre-fix: il browser non mostrava alcun collo di bottiglia locale; il prompt inviato al modello restava però molto grande.
  • Post-fix: il testo MCP viene accodato se c'è una risposta attiva, il modello riceve solo un summary compatto, il fallback parte quasi subito, la next response speculativa viene silenziata se necessario e il playback aspetta un buffer iniziale prima di iniziare ogni frase.