This example shows you how to log voice-activity-detection (VAD) metrics during a call. Each time the 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
.env.localin 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" 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, AgentServer, cli, Agent, AgentSession, inferencefrom livekit.agents.metrics import VADMetricsfrom rich.console import Consolefrom rich.table import Tablefrom rich import boxfrom datetime import datetimeload_dotenv(".env.local")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")
Define the rtc session with VAD metrics hook
Create an rtc session entrypoint that builds an inference.VAD instance, hooks into its metrics_collected event, and starts the agent session with STT/LLM/TTS configuration. Passing the instance to AgentSession as vad replaces the bundled VAD so the same object emits the metrics you display.
@server.rtc_session(agent_name="my-agent")async def entrypoint(ctx: JobContext):ctx.log_context_fields = {"room": ctx.room.name}vad_instance = inference.VAD(model="silero")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="google/gemma-4-31b-it"),tts=inference.TTS(model="inworld/inworld-tts-2", voice="Ashley"),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
lk agent console metrics_vad.py
How it works
- When the rtc session starts, the
metrics_collectedevent handler is attached to the VAD. - The 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, AgentServer, cli, Agent, AgentSession, inferencefrom livekit.agents.metrics import VADMetricsfrom rich.console import Consolefrom rich.table import Tablefrom rich import boxfrom datetime import datetimeload_dotenv(".env.local")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()@server.rtc_session(agent_name="my-agent")async def entrypoint(ctx: JobContext):ctx.log_context_fields = {"room": ctx.room.name}vad_instance = inference.VAD(model="silero")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="google/gemma-4-31b-it"),tts=inference.TTS(model="inworld/inworld-tts-2", voice="Ashley"),vad=vad_instance,preemptive_generation=True,)await session.start(agent=VADMetricsAgent(), room=ctx.room)await ctx.connect()if __name__ == "__main__":cli.run_app(server)