| title | LLM Metrics | ||||
|---|---|---|---|---|---|
| category | metrics | ||||
| tags |
|
||||
| difficulty | beginner | ||||
| description | Shows how to use the LLM metrics to log metrics to the console for all of the different LLM models. | ||||
| demonstrates |
|
This example shows how to capture token and latency metrics emitted by the LLM pipeline and print them as a Rich table whenever the agent responds. It's a quick way to see prompt/response token counts and time-to-first-token during a live call.
- Add a
.envin this directory with your LiveKit and OpenAI credentials:LIVEKIT_URL=your_livekit_url LIVEKIT_API_KEY=your_api_key LIVEKIT_API_SECRET=your_api_secret OPENAI_API_KEY=your_openai_key - Install dependencies:
pip install python-dotenv rich "livekit-agents[silero]"
Set up dotenv, a logger, and a Rich console for the metrics table.
import logging
import asyncio
from dotenv import load_dotenv
from livekit.agents import JobContext, JobProcess, Agent, AgentSession, inference, AgentServer, cli
from livekit.agents.metrics import LLMMetrics
from livekit.plugins import silero
from rich.console import Console
from rich.table import Table
from rich import box
from datetime import datetime
load_dotenv()
logger = logging.getLogger("metrics-llm")
logger.setLevel(logging.INFO)
console = Console()
server = AgentServer()Preload the VAD model once per process to reduce connection latency.
def prewarm(proc: JobProcess):
proc.userdata["vad"] = silero.VAD.load()
server.setup_fnc = prewarmKeep the agent lightweight with just instructions. In on_enter, attach an on("metrics_collected") listener to the session's LLM so every response triggers your metrics handler.
class LLMMetricsAgent(Agent):
def __init__(self) -> None:
super().__init__(
instructions="""
You are a helpful agent.
"""
)
async def on_enter(self):
def sync_wrapper(metrics: LLMMetrics):
asyncio.create_task(self.on_metrics_collected(metrics))
self.session.llm.on("metrics_collected", sync_wrapper)
self.session.generate_reply()When metrics arrive, format them into a table with timestamps, TTFT, durations, and token counts.
async def on_metrics_collected(self, metrics: LLMMetrics) -> None:
table = Table(
title="[bold blue]LLM 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("Request ID", str(metrics.request_id))
table.add_row("Timestamp", timestamp)
table.add_row("Duration", f"[white]{metrics.duration:.4f}[/white]s")
table.add_row("Time to First Token", f"[white]{metrics.ttft:.4f}[/white]s")
table.add_row("Cancelled", "✓" if metrics.cancelled else "✗")
table.add_row("Completion Tokens", str(metrics.completion_tokens))
table.add_row("Prompt Tokens", str(metrics.prompt_tokens))
table.add_row("Total Tokens", str(metrics.total_tokens))
table.add_row("Tokens/Second", f"{metrics.tokens_per_second:.2f}")
console.print("\n")
console.print(table)
console.print("\n")Configure the AgentSession with STT, LLM, TTS, and prewarmed VAD. The LLM's metrics events will be captured by the listener attached in on_enter.
@server.rtc_session()
async def entrypoint(ctx: JobContext):
ctx.log_context_fields = {"room": ctx.room.name}
session = AgentSession(
stt=inference.STT(model="deepgram/nova-3-general"),
llm=inference.LLM(model="openai/gpt-4.1-mini"),
tts=inference.TTS(model="cartesia/sonic-3", voice="9626c31c-bec5-4cca-baa8-f8ba9e84c8bc"),
vad=ctx.proc.userdata["vad"],
preemptive_generation=True,
)
agent = LLMMetricsAgent()
await session.start(agent=agent, room=ctx.room)
await ctx.connect()Start the agent server with the CLI.
if __name__ == "__main__":
cli.run_app(server)python metrics_llm.py console- The agent runs with standard STT/LLM/TTS and Silero VAD.
- The LLM emits
metrics_collectedafter each generation. - A wrapper in
on_enterscheduleson_metrics_collectedso you can await inside it. - Rich renders the metrics in a readable table showing latency and token stats.
import logging
import asyncio
from dotenv import load_dotenv
from livekit.agents import JobContext, JobProcess, Agent, AgentSession, inference, AgentServer, cli
from livekit.agents.metrics import LLMMetrics
from livekit.plugins import silero
from rich.console import Console
from rich.table import Table
from rich import box
from datetime import datetime
load_dotenv()
logger = logging.getLogger("metrics-llm")
logger.setLevel(logging.INFO)
console = Console()
class LLMMetricsAgent(Agent):
def __init__(self) -> None:
super().__init__(
instructions="""
You are a helpful agent.
"""
)
async def on_enter(self):
def sync_wrapper(metrics: LLMMetrics):
asyncio.create_task(self.on_metrics_collected(metrics))
self.session.llm.on("metrics_collected", sync_wrapper)
self.session.generate_reply()
async def on_metrics_collected(self, metrics: LLMMetrics) -> None:
table = Table(
title="[bold blue]LLM 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("Request ID", str(metrics.request_id))
table.add_row("Timestamp", timestamp)
table.add_row("Duration", f"[white]{metrics.duration:.4f}[/white]s")
table.add_row("Time to First Token", f"[white]{metrics.ttft:.4f}[/white]s")
table.add_row("Cancelled", "✓" if metrics.cancelled else "✗")
table.add_row("Completion Tokens", str(metrics.completion_tokens))
table.add_row("Prompt Tokens", str(metrics.prompt_tokens))
table.add_row("Total Tokens", str(metrics.total_tokens))
table.add_row("Tokens/Second", f"{metrics.tokens_per_second:.2f}")
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()
async def entrypoint(ctx: JobContext):
ctx.log_context_fields = {"room": ctx.room.name}
session = AgentSession(
stt=inference.STT(model="deepgram/nova-3-general"),
llm=inference.LLM(model="openai/gpt-4.1-mini"),
tts=inference.TTS(model="cartesia/sonic-3", voice="9626c31c-bec5-4cca-baa8-f8ba9e84c8bc"),
vad=ctx.proc.userdata["vad"],
preemptive_generation=True,
)
agent = LLMMetricsAgent()
await session.start(agent=agent, room=ctx.room)
await ctx.connect()
if __name__ == "__main__":
cli.run_app(server)