full-hydrogen-48173
03/03/2026, 5:10 PMtall-airline-16756
03/03/2026, 5:10 PMtall-airline-16756
03/03/2026, 5:10 PMMEDIUM setting favors not cutting people off mid-sentence, so interruption detection can feel sluggish.
• Even after an interruption is detected, queued or currently playing TTS segments often complete because audio is streamed/queued in chunks and isn’t auto‑truncated.
• There’s no automatic immediate barge‑in cancellation by default; stopping playback requires an explicit interrupt call from the application.
Practical changes you can make (start here)
1. Increase turn‑detection sensitivity
◦ Set turn_detection="HIGH" so the model reacts faster to user speech (more responsive to interruptions). This may increase the chance of cutting off slower speakers, so test with your users.
1. Explicitly cancel playback on interruption
◦ When your code detects user speech while the agent is speaking, call interrupt() on the current SpeechHandle or the session to stop playback immediately:
▪︎ Use handle.interrupt() to stop a single speech handle, or
▪︎ Use session.interrupt() to stop all current speech.
◦ This prevents the agent from finishing the queued TTS segment and lets you move to the next response without delay.
1. Combine detection and cancellation for best UX
◦ Use turn_detection="HIGH" for faster detection and implement interrupt() so you both detect interruptions quickly and stop playback immediately.
◦ Optionally use allow_interruptions=False selectively for messages you don’t want interrupted (e.g., critical instructions).
Tradeoffs to consider
• Higher sensitivity can cause premature cutoffs for slow or hesitant speakers.
• Immediate cancellation gives faster response time but can make the audio experience feel abruptly truncated if not handled smoothly (consider giving a short fade or confirmation sound when you interrupt).
If you want, I can:
• Suggest a small code example tailored to your stack showing detection + immediate interrupt logic.
• Help you pick test values for turn_detection and a simple test plan to measure UX improvements.
Which would you prefer — a code sample or a testing plan first?
Sources: Amazon Nova Sonic integration guide | LiveKit Documentation | Turns overview | LiveKit Documentationfull-hydrogen-48173
03/03/2026, 5:12 PMtall-airline-16756
03/03/2026, 5:12 PMinterrupt() immediately and handle the incoming audio.
# 1) Create session with higher turn detection sensitivity
session = AgentSession(
llm=aws.realtime.RealtimeModel.with_nova_sonic_2(
turn_detection="HIGH" # more responsive to interruptions
),
)
# 2) Start TTS and keep handle so it can be interrupted
speech_handle = session.say("Hello, I will explain how to use the new feature...")
# 3) Example: an event listener that triggers when user speech starts
def on_user_speech_start(event):
# Event fired when VAD/ASR detects user starting to speak
# Immediately interrupt the current speech playback
try:
speech_handle.interrupt() # stops current TTS right away
except Exception:
# Fallback: interrupt the whole session if handle isn't available
session.interrupt()
# Optionally: capture user audio or route to ASR for transcription
user_audio = event.audio_segment
transcript = asr.recognize(user_audio)
# Continue conversation with user's intent
session.respond_to_transcript(transcript)
# 4) Hook the event listener into your audio pipeline / VAD
audio_input.on("speech_start", on_user_speech_start)
Notes and best practices
• Use turn_detection="HIGH" for faster detection; test to balance false positives.
• Always keep a reference to the active SpeechHandle so you can call interrupt() quickly.
• If your platform buffers large TTS chunks, consider reducing TTS chunk size or buffering to lower playback latency.
• Provide a slight UX polish when interrupting (e.g., a short click or soft fade) so abrupt cutoffs feel intentional.
Quick test plan (simple, iterative)
1. Baseline measurement:
◦ Measure current time from user speech start to the agent’s response change (before making changes).
1. Implement turn_detection="HIGH" + `interrupt()`:
◦ Run the same test cases (fast interruption, slow speech, overlapping speech).
◦ Measure the latency improvement.
1. Evaluate audio artifacts:
◦ Listen for abrupt audio cuts. If jarring, add a short fade-out or a 50–150ms gap before starting the agent response.
1. Tune sensitivity:
◦ If you see many false interruptions, lower sensitivity to MEDIUM or add a minimal confirmation (e.g., require 150ms continuous user speech).
1. User testing:
◦ Run with a small group and collect qualitative feedback on perceived responsiveness and naturalness.
If you want, I can:
• Provide a concrete, runnable snippet for your exact SDK/language (Node, browser JS, or other) — tell me which environment you’re using.
• Suggest specific values for minimal user speech duration (e.g., 100–200ms) to reduce false positives based on your expected user behavior.
Which environment should I prepare the concrete sample for? (Node, Browser JS, or Python)full-hydrogen-48173
03/03/2026, 5:12 PMtall-airline-16756
03/03/2026, 5:12 PMagent.py example that demonstrates:
• creating a session with turn_detection="HIGH",
• starting TTS with a SpeechHandle you can interrupt,
• listening for user speech start events (VAD/ASR),
• immediately interrupting playback and routing the user audio to ASR for processing,
• simple smoothing (short fade) to avoid abrupt audio artifacts.
This is a runnable, template-style file you can adapt to your environment. Replace placeholders (API clients, keys, and wiring) with your real audio/ASR/transport code.
agent.py
"""
agent.py
Template example showing Nova Sonic 2 barge-in handling:
- Uses turn_detection="HIGH"
- Keeps a SpeechHandle to call .interrupt() when user speech starts
- Routes user audio to ASR after interruption and responds
Adapt the placeholders (AudioInput, AudioOutput, ASRClient, LiveKit/AgentSession) to your stack.
"""
import threading
import time
# --- Placeholder SDK imports (replace with your actual SDKs) ---
# from livekit_agent import AgentSession, SpeechHandle # example
# from asr_client import ASRClient
# from audio_pipeline import AudioInput, AudioOutput
# For this template we'll mock the minimal interfaces used below:
class SpeechHandle:
def __init__(self, text):
self.text = text
self._interrupted = False
def interrupt(self):
# Immediately stop playback in a real implementation
self._interrupted = True
print("[SpeechHandle] interrupted")
def is_interrupted(self):
return self._interrupted
class AgentSession:
def __init__(self, turn_detection="MEDIUM"):
self.turn_detection = turn_detection
self._current_handle = None
print(f"[AgentSession] created with turn_detection={turn_detection}")
def say(self, text):
# Start TTS playback and return a handle
handle = SpeechHandle(text)
self._current_handle = handle
# Simulate asynchronous playback on a separate thread
def playback():
print(f"[TTS] start playing: {text}")
# simulate chunked playback, check for interrupt between chunks
for i in range(10):
if handle.is_interrupted():
print("[TTS] playback stopped mid-message")
return
time.sleep(0.05) # simulate short chunk playback
print("[TTS] playback finished")
t = threading.Thread(target=playback, daemon=True)
t.start()
return handle
def interrupt(self):
# Stop all playback
if self._current_handle:
self._current_handle.interrupt()
def respond_to_transcript(self, transcript):
# Generate a response to the transcript. Replace with real LLM/agent call.
response = f"Understood: {transcript}"
print(f"[Agent] responding: {response}")
self.say(response)
class ASRClient:
def recognize(self, audio_segment):
# Replace with real ASR call
# For template, return a mocked transcript
return "mocked user transcript"
# --- End placeholders ---
# Simple event emitter to simulate VAD/speech-start events
class AudioInputMock:
def __init__(self):
self._listeners = []
def on_speech_start(self, callback):
self._listeners.append(callback)
def simulate_user_starts_speaking(self, audio_segment=None):
event = {"audio_segment": audio_segment}
for cb in self._listeners:
try:
cb(event)
except Exception as e:
print(f"[AudioInputMock] listener error: {e}")
# Realistic interruption manager
class BargeInManager:
def __init__(self, session: AgentSession, asr: ASRClient):
self.session = session
self.asr = asr
self.current_handle = None
self.lock = threading.Lock()
def speak(self, text):
with self.lock:
self.current_handle = self.session.say(text)
def on_user_speech_start(self, event):
# Called when VAD detects user started speaking.
# Immediately interrupt current TTS and hand off to ASR.
with self.lock:
print("[BargeInManager] user started speaking -> interrupting TTS")
try:
if self.current_handle:
self.current_handle.interrupt()
else:
# fallback: interrupt whole session
self.session.interrupt()
except Exception as e:
print(f"[BargeInManager] interrupt call failed: {e}")
self.session.interrupt()
# Optional small smoothing delay to avoid jarring audio artifacts
time.sleep(0.04) # 40ms fade/gap — tune as needed
# Route audio to ASR and continue flow
audio = event.get("audio_segment")
transcript = self.asr.recognize(audio)
print(f"[ASR] transcript: {transcript}")
# Let the session/agent respond to the transcript
self.session.respond_to_transcript(transcript)
# Clear current handle after handing off
with self.lock:
self.current_handle = None
def main():
# Create session with HIGH turn detection for faster barge-in responsiveness
session = AgentSession(turn_detection="HIGH")
asr = ASRClient()
barge_manager = BargeInManager(session, asr)
# Hook into your real audio input's speech-start event
audio_input = AudioInputMock()
audio_input.on_speech_start(barge_manager.on_user_speech_start)
# Start by having the agent speak something longer that could be interrupted
barge_manager.speak("Hello — I will explain the new onboarding steps in a few parts. If you interrupt, I'll stop and listen.")
# Simulate the user interrupting after 0.25 seconds
time.sleep(0.25)
audio_input.simulate_user_starts_speaking(audio_segment=b"<audio-bytes>")
# Let threads run briefly to show flow in this mock
time.sleep(1.0)
if __name__ == "__main__":
main()
How to adapt this template to your real stack
• Replace the placeholder classes with your actual SDK objects:
◦ Create AgentSession using your Nova Sonic 2 realtime constructor including turn_detection="HIGH".
◦ Use the real SpeechHandle returned by session.say(...) and call its .interrupt() method.
◦ Hook the on_user_speech_start to your real VAD/ASR speech-start event (browser, mobile, or server-side).
◦ Replace ASRClient.recognize() with your ASR provider call and feed the audio captured at interruption time.
Tuning suggestions
• Short smoothing gap: 30–80 ms often reduces perceived abruptness without adding latency.
• If you see frequent false interruptions, require a minimal continuous user speech duration (e.g., 100–200 ms) before calling interrupt.
• If your TTS buffers large chunks, try reducing chunk size on the TTS side or stream smaller buffers so interruption points are more frequent.
If you want, I can:
• Convert this into a concrete example using your specific LiveKit/SDK import lines and real ASR client (tell me which libraries you use).
• Add a small fade-out implementation for audio output if your output path supports it.
Which real SDKs/libraries should I adapt this to? (LiveKit Python client, specific ASR provider, and your audio I/O library)full-hydrogen-48173
03/03/2026, 5:14 PMtall-airline-16756
03/03/2026, 5:16 PMagent.py you can drop into your project. It uses LiveKit’s Python agents framework with the AWS Nova Sonic 2 realtime plugin, preloads a VAD (Silero) for responsiveness, configures turn_detection for better barge‑in, and demonstrates interruption handling. Replace the placeholders and pip install the listed packages.
Notes:
• This example expects the LiveKit agents framework and LiveKit AWS realtime plugin as shown in the docs.
• Set AWS and LiveKit environment variables in a .env file (example shown below).
• Tune turn_detection (HIGH / MEDIUM / LOW) and minor timing values to match your UX needs.
.env (example)
AWS_ACCESS_KEY_ID=your_aws_access_key_id
AWS_SECRET_ACCESS_KEY=your_aws_secret_access_key
AWS_DEFAULT_REGION=us-east-1
LIVEKIT_URL=your_livekit_url
LIVEKIT_API_KEY=your_livekit_api_key
LIVEKIT_API_SECRET=your_livekit_api_secret
Install dependencies
pip install "livekit-plugins-aws[realtime]" "livekit-agents[silero]" python-dotenv
agent.py
"""
agent.py
Complete example showing:
- Nova Sonic 2.0 via LiveKit AWS realtime plugin
- turn_detection tuned for responsive barge-in
- prewarming Silero VAD
- interruption handling (interrupting current TTS playback)
Adapt placeholders and imports as needed for your environment.
"""
import logging
from dotenv import load_dotenv
from typing import Optional
# LiveKit agents & plugins
from livekit.agents import Agent, AgentSession, AgentServer, JobContext, JobProcess, cli
from livekit.plugins import aws, silero
load_dotenv()
# Configure logging
logging.basicConfig(level=<http://logging.INFO|logging.INFO>)
logger = logging.getLogger("nova-sonic-agent")
# Create the agent server instance
server = AgentServer()
# Prewarm VAD once per process for faster interruption response
def prewarm(proc: JobProcess):
"""
Load Silero VAD model into proc.userdata for reuse across sessions.
This avoids loading on each connection and reduces latency.
"""
proc.userdata["vad"] = silero.VAD.load()
<http://logger.info|logger.info>("Silero VAD prewarmed for process")
server.setup_fnc = prewarm
class NovaSonicAgent(Agent):
def __init__(self) -> None:
super().__init__(
instructions="""
You are a friendly voice assistant using Nova Sonic 2.0. Speak naturally,
be concise, and allow users to interrupt. If interrupted, stop speaking
and handle the user's input promptly.
""".strip()
)
async def on_enter(self):
# Agent speaks first when the session starts
await self.session.generate_reply(
instructions="Hello — I am your assistant. How can I help you today?"
)
@server.rtc_session(agent_name="nova-sonic-agent")
async def entrypoint(ctx: JobContext):
"""
RTC session entrypoint. Configures Nova Sonic 2.0 and starts AgentSession.
"""
ctx.log_context_fields = {"room": ctx.room.name}
<http://logger.info|logger.info>("Starting Nova Sonic 2.0 session for room=%s", ctx.room.name)
# Configure the Nova Sonic 2 realtime model:
# - voice: pick a voice available in your account/region (e.g., "tiffany")
# - turn_detection: HIGH/MEDIUM/LOW (HIGH -> more responsive, more false interrupts)
# - modalities="mixed" enables both audio and programmatic text input
llm = aws.realtime.RealtimeModel.with_nova_sonic_2(
voice="tiffany",
modalities="mixed",
turn_detection="MEDIUM", # tune to HIGH for faster detection if needed
generate_reply_timeout=15.0,
)
# Build AgentSession with prewarmed VAD for better barge-in coordination
session = AgentSession(
llm=llm,
vad=ctx.proc.userdata.get("vad"), # Silero VAD prewarmed in server.setup_fnc
# Optional tuning parameters (if available in your SDK):
# min_interruption_duration=0.15,
# allow_interruptions=True,
)
# Start the session with our agent and connect to the LiveKit room
agent = NovaSonicAgent()
await session.start(agent=agent, room=ctx.room)
await ctx.connect()
# The session will now handle incoming audio, interruption events, and replies.
# If you need programmatic interruption control (stop currently playing TTS),
# you can call session.interrupt() or use the SpeechHandle returned from say().
#
# Example (conceptual):
# handle = session.say("This message can be interrupted")
# ...on user speech detected: handle.interrupt() or session.interrupt()
if __name__ == "__main__":
cli.run_app(server)
Quick integration notes and tuning tips
• To make interruptions feel immediate:
◦ Set turn_detection="HIGH" to detect user speech faster (may increase false positives).
◦ Ensure you keep a reference to the SpeechHandle returned by session.say() if you want to call handle.interrupt() explicitly.
◦ If Nova Sonic completes queued chunks before an interrupt takes hold, call session.interrupt() to stop all playback.
• Add a small smoothing gap (30–80 ms) between stopping playback and beginning the agent response to avoid audible clipping.
• If you see too many accidental interruptions, require a minimum continuous user speech time (e.g., 100–200 ms) before calling interrupt().
If you want, I can:
• Provide a concrete snippet that shows capturing the SpeechHandle and calling handle.interrupt() inside a VAD callback.
• Convert this sample into a runnable local console example that simulates user speech events so you can test barge-in behaviour without a full LiveKit setup.
Which would you prefer: a runnable local simulation or the explicit interrupt-within-VAD callback example using the same SDK?
Sources: Amazon Nova Sonic integration guide | LiveKit Documentation | VAD and turn detection configuration guide | LiveKit | Simple Call Answering Agent | LiveKit Documentation | Turns overview | LiveKit Documentation | AWS AI and LiveKit | LiveKit Documentation