i have a voice bot which uses livekit so no if the...
# ask-ai
q
i have a voice bot which uses livekit so no if the user goes silent for more than 3 seconds i want the bot to speak again asking the user if he still there on the call or whatever so how do i make it happen
✅ 1
t
Thanks for reaching out, Krutika. I'm an AI assistant and I'm here to help. Give me a moment while I research this topic.
Thanks — I understand you want the bot to detect when a user goes silent for more than 3 seconds and then have the bot speak (e.g., “Are you still there?”). Below is a simple, reliable approach you can adapt to your voice bot running with LiveKit. Key idea • Track the timestamp of the last time the user spoke. • Run a small background monitor that checks for silence and triggers the bot’s TTS when silence exceeds 3 seconds. • Cancel/cleanup the monitor when the session ends. Minimal example (pseudo/Python-like) you can adapt to your agent framework 1. Set a silence threshold and track last-spoken time. 2. Update last-spoken time on a “user started speaking” event. 3. Run an async monitor that checks the elapsed time and calls your TTS method when > 3s.
Copy code
SILENCE_THRESHOLD = 3  # seconds
user_last_spoke_time = time.time()

# Called by your framework when the user starts speaking
def on_user_started_speaking():
    global user_last_spoke_time
    user_last_spoke_time = time.time()

# Background monitor - run this as an async task for the session
async def silence_monitor(agent):
    while True:
        await asyncio.sleep(0.5)  # check frequently but not constantly
        elapsed = time.time() - user_last_spoke_time
        if elapsed > SILENCE_THRESHOLD:
            await agent.say("Are you still there?")  # or your TTS call
            # decide whether to:
            # - break (ask once), or
            # - reset last_spoke_time and continue monitoring, or
            # - retry prompting N times then hang up
            break
Practical notes and options • Event to use: Use whatever event your integration exposes for “user started speaking” (VAD/voice-activity events). If you receive more detailed events (e.g., start/stop), update the timestamp on start; if only audio level/meters are available, update when level crosses a threshold. • Poll interval: 0.5–1 second is usually good. Too frequent wastes CPU; too rare delays the prompt. • Debounce/false positives: Only prompt when the user was recently active (e.g., last_spoke_time was within the last minute) to avoid prompting inactive/left users immediately. • Prompt strategy: ◦ Ask once then wait for response. ◦ Ask up to N times with increasing delays. ◦ If no response after prompts, end call or mark session inactive. • Cleanup: Ensure the monitor task is cancelled when the session ends to avoid leaks. • Integrate with other signals: You can combine this with connection-state or silence-detection confidence if your audio pipeline provides it. If you want, tell me: • What language/framework your agent runs in (Node, Python, etc.) • How you currently detect user speech (VAD event, audio level, or nothing) With that I can give a concrete code sample that matches your stack and show how to wire the monitor into your session lifecycle. Sources: Agents Framework | livekit/livekit_composite | DeepWiki | How to detect and handle silence during calls | examples/frontdesk | livekit/livekit_composite | DeepWiki | examples/voice_agents | livekit/livekit_composite | DeepWiki, with interactive elements
q
1. Language/Framework: Python. We are using the official
livekit-agents
library. 2. How we detect user speech: We are currently detecting speech purely through LiveKit's built-in VAD events. Specifically, we hook into
@session.on("user_speech_committed")
to know when the user stops speaking,
@session.on("agent_started_speaking")
to know when the bot begins audio playback, and
@session.on("agent_speech_committed")
to know when the bot finishes. Can you provide a minimal, non-blocking Python
async
polling implementation that integrates directly with these specific
@session.on
events?
t
Thanks — this is clear and helpful. Below is a minimal, non-blocking async Python implementation that integrates with the
livekit-agents
session events you listed (
user_speech_committed
,
agent_started_speaking
,
agent_speech_committed
). It tracks silence, runs a background monitor using
asyncio
tasks, cancels/cleans up properly, and calls the agent TTS via
session.say()
after 3 seconds of silence. Important behaviors implemented: • Reset the silence timer when the user finishes speaking (
user_speech_committed
). • Cancel the timer while the agent is speaking (
agent_started_speaking
) to avoid overlapping prompts. • Restart the timer when the agent finishes speaking (
agent_speech_committed
). • Non-blocking: uses
asyncio.create_task()
and fine-grained sleeps. • Safe cleanup: cancels the background timeout task when the session is shutting down. Code (adapt into your agent init/session setup):
Copy code
import asyncio
from typing import Optional
# from livekit.agents import AgentSession  # use your actual import

SILENCE_THRESHOLD = 3.0  # seconds

async def attach_silence_prompt_monitor(session):
    """
    Attach silence-detection prompt logic to an AgentSession.
    Call this after the session is created/started.
    """
    timeout_task: Optional[asyncio.Task] = None
    shutdown = False

    def _cancel_timeout():
        nonlocal timeout_task
        if timeout_task and not timeout_task.done():
            timeout_task.cancel()
        timeout_task = None

    def _schedule_timeout():
        nonlocal timeout_task
        _cancel_timeout()
        timeout_task = asyncio.create_task(_silent_timeout())

    async def _silent_timeout():
        try:
            await asyncio.sleep(SILENCE_THRESHOLD)
            # Only prompt if agent is not currently speaking.
            # Adjust checks according to your session state model if different.
            # Example assumes session has properties or state you can inspect.
            # If there's no state, rely on events (agent_started_speaking cancels the task).
            await session.say("Are you still there?", allow_interruptions=True)
        except asyncio.CancelledError:
            # expected when user/agent speech interrupts the timeout
            return

    # Event: user finished speaking -> start/reset silence timer
    @session.on("user_speech_committed")
    def _on_user_speech_committed(*_args, **_kwargs):
        # user just finished speaking, start the timeout
        _schedule_timeout()

    # Event: agent starts speaking -> cancel any pending user-silence prompt
    @session.on("agent_started_speaking")
    def _on_agent_started_speaking(*_args, **_kwargs):
        _cancel_timeout()

    # Event: agent finished speaking -> start/reset silence timer (listening again)
    @session.on("agent_speech_committed")
    def _on_agent_speech_committed(*_args, **_kwargs):
        _schedule_timeout()

    # Optional: if your session supports shutdown callbacks, ensure cleanup
    def _on_shutdown():
        nonlocal shutdown
        shutdown = True
        _cancel_timeout()

    # If AgentSession has a lifecycle hook for shutdown, register it here.
    # Example: session.add_shutdown_callback(_on_shutdown)
    if hasattr(session, "add_shutdown_callback"):
        session.add_shutdown_callback(_on_shutdown)

    # Start initial timer so we begin monitoring immediately (optional)
    _schedule_timeout()
Notes and small adjustments you may want • allow_interruptions: I used
allow_interruptions=True
in
session.say()
so incoming user speech can interrupt the prompt. Remove or adjust if you want the prompt to run to completion. • Checking agent state: If you have an easy way to inspect whether the agent is speaking or in a listening state, you can add a guard before calling
session.say()
(but the event-driven cancel already prevents overlaps). • Multiple prompts / retries: To ask more than once, replace the
break
-style behavior with a retry counter in
_silent_timeout()
and re-schedule after prompts as needed. • Debounce for long inactive sessions: If you only want prompts for recently active users, keep a timestamp of recent activity and check before scheduling the timeout. • Poll interval: This implementation uses a single sleep for the threshold and is efficient; no tight loop required. If you want, I can: • Convert this into a complete runnable snippet using your exact session creation/startup code, or • Add retry logic (ask N times with increasing delays), or • Show how to integrate with any session state fields your code already uses. Which next step would you like? Sources: Turns overview | LiveKit Documentation | Agent session | LiveKit Documentation, with interactive elements