i am getting trancript on sdk after llm generation...
# ask-ai
e
i am getting trancript on sdk after llm generation even though its just and interruption
t
Thanks for reaching out, Guess. I'm an AI assistant and I'm here to help. Give me a moment while I research this topic.
Thanks — I’m following up about your message: you’re seeing a transcript emitted to the SDK after the LLM started TTS but the user interrupted it. That’s expected behavior: transcripts are created from the text sent into TTS and any partial text produced before an interruption is forwarded by the transcription_node. You can handle this in three ways: • Detect interrupted speech at runtime: listen for
speech_created
and check the
SpeechHandle
state to see if the speech was canceled/aborted, then ignore or mark its transcript accordingly. • Use lifecycle hooks: use
on_user_turn_completed
to distinguish completed turns from canceled ones and suppress transcripts for canceled turns. • Filter in the pipeline: add logic in your
transcription_node
to suppress or flag transcripts that originate from interrupted speech (for example, drop transcripts when the associated SpeechHandle has an interrupted/canceled flag). Which SDK/language are you using and can you share the event payload you’re receiving for the interrupted speech? I’ll give exact code snippets for detecting and filtering those transcripts. Sources: Text and transcriptions | LiveKit docs | examples/voice_agents | livekit/livekit_composite | DeepWiki | Pipeline nodes and hooks | LiveKit docs | Agent speech and audio | LiveKit docs
e
so tranxcription from bnot and user must be on speaking biut i am getting bot trancript before itself , but human it streams
t
Thanks — I’m responding to your note that the bot’s transcript appears before the bot actually speaks, while the human transcription streams in real time. I can help narrow this down, but I need a few details: 1. Which SDK and language/version are you using (e.g., Web JS SDK vX, Android, iOS)? 2. Are you using LiveKit Agents with built-in TTS and the
transcription_node
, or a custom pipeline? 3. Share example event payloads or logs you see when the bot transcript appears early (the
speech_created
,
speech_started
,
speech_stopped
, or transcription events and any
SpeechHandle
fields). 4. Do you see a separate event for TTS start/stop, or only the transcript event? 5. Is the bot transcript marked as partial or final when it arrives? With those I’ll provide exact code snippets and where to filter (client-side listener or in
transcription_node
) to ensure bot transcripts are only forwarded once speech actually starts or to suppress partial transcripts from interrupted/canceled TTS.
e
useEffect(() => { if (orchestrationType && !transcriptManagerRef.current) { transcriptManagerRef.current = new TranscriptManager( orchestrationType, { onFinalMessage: (message) => { setTranscripts(prev => { const lastMessage = prev.length > 0 ? prev[prev.length - 1] : null; // Check for exact duplicate if (lastMessage && lastMessage.text.trim() === message.text.trim() && lastMessage.speaker === message.speaker && lastMessage.isAI === message.isAI) { return prev; // Skip duplicate } // If last message was human AND new message is human, concatenate if (!message.isAI && lastMessage && !lastMessage.isAI) { const updatedTranscripts = [...prev]; const lastIndex = updatedTranscripts.length - 1; const lastText = updatedTranscripts[lastIndex].text.trim(); const newText = message.text.trim(); // Simple concatenation - just append with space updatedTranscripts[lastIndex] = { ...updatedTranscripts[lastIndex], text:
${lastText} ${newText}
, }; return updatedTranscripts; } // Create new message block (AI message or first human message after AI) const newTranscripts = [...prev, message]; // Auto-show transcript when first message arrives if (!transcriptShownRef.current && prev.length === 0 && !isMobile()) { transcriptShownRef.current = true; setShowTranscript(true); } return newTranscripts; }); }, onInterimUpdate: (text, speaker) => { setCurrentTranscript(text); setCurrentSpeaker(text ? speaker : null); }, onSpeakerChange: (speaker) => { setCurrentSpeaker(speaker); }, } ); } return () => { // Cleanup on unmount or orchestration type change if (transcriptManagerRef.current) { transcriptManagerRef.current.cleanup(); transcriptManagerRef.current = null; } }; }, [orchestrationType]);
t
Thanks — I reviewed the React snippet you shared. You’re handling final and interim messages and deduplicating/concatenating nicely. To stop the bot’s transcript appearing before it actually speaks, try one of these focused changes (pick the one that fits your pipeline): 1) Prefer speech start/handle state before showing AI final transcript • If your events include
speech_started
or a
SpeechHandle
state (started vs created), only push the AI final transcript when you see speech start. In your
onFinalMessage
guard, require a flag like
message.speechStarted === true
(or check the
SpeechHandle
for
started
) before adding an AI message. 2) Suppress transcripts from created-but-cancelled TTS • If you get transcripts from text that was sent to TTS but later interrupted, ignore messages where
message.speechCancelled === true
or where
SpeechHandle
indicates aborted. Add an early-return in `onFinalMessage`:
Copy code
if (message.isAI && (message.speechCancelled || !message.speechStarted)) return;
3) Delay display until speech actually begins (UI-friendly) • If events for start/stop exist, buffer the AI transcript and only commit when
speech_started
arrives. Example approach: ◦ onFinalMessage: if isAI, store in
pendingAI
. ◦ onSpeechStarted: if
pendingAI
exists for that speaker, commit it and clear
pendingAI
. ◦ onSpeechStopped/cancel: clear
pendingAI
. 4) Use interim streaming for humans, final-only for bots • Your
onInterimUpdate
is already updating live text for humans. For bots, ignore interim transcripts entirely and only display final text after speech start. Quick code hint to integrate into your existing handler:
Copy code
// inside onFinalMessage
if (message.isAI) {
  // require explicit speechStarted flag, and skip duplicates as you already do
  if (!message.speechStarted) {
    // optionally store pending message keyed by speaker id
    pendingAIRef.current[message.speaker] = message;
    return;
  }
  // commit as before
}
Next step: can you share one example event payload for the bot that arrives early (fields on
message
, plus any
SpeechHandle
fields) and tell me which SDK/version you’re using? I’ll give the exact snippet to plug into your TranscriptManager.
e
SDK version 2 above
t
Thanks — responding to your note that you’re on SDK v2+ and need bot transcripts suppressed until speech actually starts. Two quick clarifying questions so I can give exact code to plug into your TranscriptManager: 1. Which bot events do you receive in your client for TTS lifecycle? Do you see any of these:
speech_created
,
speech_started
,
speech_stopped
(or fields on the message like
speechStarted
,
speechCancelled
, or a
SpeechHandle
)? 2. Please paste one example payload for the premature bot transcript event you’re getting (the full
message
object you receive). If you confirm you receive
speech_started
, I’ll give a minimal SDK v2+ patch to: • buffer AI final messages until
speech_started
for that speaker, then commit; and • drop buffered AI messages if `speech_stopped`/cancel arrives before start.
e
how can i know
thats peech started comes
t
You're on the right track — since
speech_started
does arrive, here's a concise plan and exact things to do so you can suppress the bot transcript until the TTS actually starts. 1) Quickly log incoming events to confirm fields • Add a debug logger where you receive transcript/agent events:
Copy code
console.log('agent-event', { eventType: event.type, payload: event });
• Look for
speech_created
,
speech_started
,
speech_stopped
or fields like
speechStarted
,
speechCancelled
, or a
SpeechHandle
id. 2) Minimal approach: buffer AI final messages until
speech_started
• Maintain a pending map keyed by
speaker
or `speechHandleId`:
Copy code
const pendingAI = new Map();
• Update your TranscriptManager handlers: onFinalMessage:
Copy code
if (message.isAI) {
  // If speech not started yet, buffer
  if (!message.speechStarted) {
    pendingAI.set(message.speechHandleId || message.speaker, message);
    return;
  }
  // commit immediately if speechStarted
  commitMessage(message);
  return;
}
commitMessage(message); // human messages as before
onSpeechStarted (handler triggered when speech_started event arrives):
Copy code
const key = event.speechHandleId || event.speaker;
const pending = pendingAI.get(key);
if (pending) {
  commitMessage(pending);
  pendingAI.delete(key);
}
onSpeechStopped / onSpeechCancelled:
Copy code
const key = event.speechHandleId || event.speaker;
if (pendingAI.has(key)) pendingAI.delete(key); // drop buffered AI if cancelled
3) commitMessage helper (reuse your existing dedupe/concat logic) • Use the same logic you already have in
onFinalMessage
to add the message to state (dedupe, concat with last human, etc.). That keeps behavior consistent. 4) If your events only contain a generic
event.type === 'speech_started'
without IDs • Use
speaker
as the key (assuming one concurrent TTS per speaker). • If multiple concurrent TTS from same speaker are possible, prefer
speechHandleId
(or whatever unique id the event includes). If you want, paste one logged event object from your console (the
speech_created
or
speech_started
payload) and I’ll return the precise 6–10 line patch you can drop into your TranscriptManager.