This example shows you how to log voice-activity-detection (VAD) metrics during a call. Each time the Silero VAD processes speech, it emits idle time and inference timing data that you render with Rich.
This recipe uses the per-plugin metrics_collected event on the VAD instance. This per-component surface is not deprecated. A separate session-level metrics_collected event (session.on("metrics_collected", ...)) is deprecated. For session-scoped cost and usage tracking, see Session usage.
Prerequisites
- Add a
.envin this directory with your LiveKit credentials:LIVEKIT_URL=your_livekit_urlLIVEKIT_API_KEY=your_api_keyLIVEKIT_API_SECRET=your_api_secret - Install dependencies:pip install rich "livekit-agents[silero]" python-dotenv
Load environment, logging, and define an AgentServer
Set up dotenv, logging, a Rich console for the VAD reports, and initialize the AgentServer.
import loggingimport asynciofrom dotenv import load_dotenvfrom livekit.agents import JobContext, JobProcess, AgentServer, cli, Agent, AgentSession, inferencefrom livekit.agents.metrics import VADMetricsfrom livekit.plugins import silerofrom rich.console import Consolefrom rich.table import Tablefrom rich import boxfrom datetime import datetimeload_dotenv()logger = logging.getLogger("metrics-vad")logger.setLevel(logging.INFO)console = Console()server = AgentServer()
Define a lightweight agent and VAD metrics display function
Keep the Agent class minimal with just instructions. Define an async function to display VAD metrics as a Rich table.
class VADMetricsAgent(Agent):def __init__(self) -> None:super().__init__(instructions="You are a helpful agent.")async def display_vad_metrics(metrics: VADMetrics):table = Table(title="[bold blue]VAD Metrics Report[/bold blue]",box=box.ROUNDED,highlight=True,show_header=True,header_style="bold cyan")table.add_column("Metric", style="bold green")table.add_column("Value", style="yellow")timestamp = datetime.fromtimestamp(metrics.timestamp).strftime('%Y-%m-%d %H:%M:%S')table.add_row("Type", str(metrics.type))table.add_row("Label", str(metrics.label))table.add_row("Timestamp", timestamp)table.add_row("Idle Time", f"[white]{metrics.idle_time:.4f}[/white]s")table.add_row("Inference Duration Total", f"[white]{metrics.inference_duration_total:.4f}[/white]s")table.add_row("Inference Count", str(metrics.inference_count))console.print("\n")console.print(table)console.print("\n")
Prewarm VAD for faster connections
Preload the VAD model once per process. This runs before any sessions start and stores the VAD instance in proc.userdata so it can be reused, cutting down on connection latency.
def prewarm(proc: JobProcess):proc.userdata["vad"] = silero.VAD.load()server.setup_fnc = prewarm
Define the rtc session with VAD metrics hook
Create an rtc session entrypoint that retrieves the prewarmed VAD, hooks into its metrics_collected event, and starts the agent session with STT/LLM/TTS configuration.
@server.rtc_session(agent_name="my-agent")async def entrypoint(ctx: JobContext):ctx.log_context_fields = {"room": ctx.room.name}vad_instance = ctx.proc.userdata["vad"]def on_vad_metrics(metrics: VADMetrics):asyncio.create_task(display_vad_metrics(metrics))vad_instance.on("metrics_collected", on_vad_metrics)session = AgentSession(stt=inference.STT(model="deepgram/nova-3-general"),llm=inference.LLM(model="openai/gpt-5-mini"),tts=inference.TTS(model="cartesia/sonic-3", voice="9626c31c-bec5-4cca-baa8-f8ba9e84c8bc"),vad=vad_instance,preemptive_generation=True,)await session.start(agent=VADMetricsAgent(), room=ctx.room)await ctx.connect()
Run the server
The cli.run_app() function starts the agent server. It manages the worker lifecycle, connects to LiveKit, and processes incoming jobs.
if __name__ == "__main__":cli.run_app(server)
Run it
python metrics_vad.py console
How it works
- The VAD model is prewarmed once per process for faster connections.
- When the rtc session starts, the
metrics_collectedevent handler is attached to the VAD. - Silero VAD detects speech and emits metrics events with idle time, inference duration, and count.
- A background task formats and prints the metrics as a Rich table.
- Because the handler is async, it does not block ongoing audio processing.
Full example
import loggingimport asynciofrom dotenv import load_dotenvfrom livekit.agents import JobContext, JobProcess, AgentServer, cli, Agent, AgentSession, inferencefrom livekit.agents.metrics import VADMetricsfrom livekit.plugins import silerofrom rich.console import Consolefrom rich.table import Tablefrom rich import boxfrom datetime import datetimeload_dotenv()logger = logging.getLogger("metrics-vad")logger.setLevel(logging.INFO)console = Console()class VADMetricsAgent(Agent):def __init__(self) -> None:super().__init__(instructions="You are a helpful agent.")async def display_vad_metrics(metrics: VADMetrics):table = Table(title="[bold blue]VAD Metrics Report[/bold blue]",box=box.ROUNDED,highlight=True,show_header=True,header_style="bold cyan")table.add_column("Metric", style="bold green")table.add_column("Value", style="yellow")timestamp = datetime.fromtimestamp(metrics.timestamp).strftime('%Y-%m-%d %H:%M:%S')table.add_row("Type", str(metrics.type))table.add_row("Label", str(metrics.label))table.add_row("Timestamp", timestamp)table.add_row("Idle Time", f"[white]{metrics.idle_time:.4f}[/white]s")table.add_row("Inference Duration Total", f"[white]{metrics.inference_duration_total:.4f}[/white]s")table.add_row("Inference Count", str(metrics.inference_count))console.print("\n")console.print(table)console.print("\n")server = AgentServer()def prewarm(proc: JobProcess):proc.userdata["vad"] = silero.VAD.load()server.setup_fnc = prewarm@server.rtc_session(agent_name="my-agent")async def entrypoint(ctx: JobContext):ctx.log_context_fields = {"room": ctx.room.name}vad_instance = ctx.proc.userdata["vad"]def on_vad_metrics(metrics: VADMetrics):asyncio.create_task(display_vad_metrics(metrics))vad_instance.on("metrics_collected", on_vad_metrics)session = AgentSession(stt=inference.STT(model="deepgram/nova-3-general"),llm=inference.LLM(model="openai/gpt-5-mini"),tts=inference.TTS(model="cartesia/sonic-3", voice="9626c31c-bec5-4cca-baa8-f8ba9e84c8bc"),vad=vad_instance,preemptive_generation=True,)await session.start(agent=VADMetricsAgent(), room=ctx.room)await ctx.connect()if __name__ == "__main__":cli.run_app(server)