late-glass-71191
12/19/2025, 9:38 PMtall-airline-16756
12/19/2025, 9:38 PMtall-airline-16756
12/19/2025, 9:38 PMroomList grant (CLI example shown below).
b. Call the REST endpoints to list sessions or fetch a session by id.
• Examples:
◦ Create token (CLI):
lk token create \
--api-key $LIVEKIT_API_KEY \
--api-secret $LIVEKIT_SECRET_KEY \
--list \
--valid-for 24h
• List sessions:
curl -H "Authorization: Bearer $TOKEN" \
"<https://cloud-api.livekit.io/api/project/$PROJECT_ID/sessions?start=2024-01-12&end=2024-01-13&limit=100>"
• Get session details:
curl -H "Authorization: Bearer $TOKEN" \
"<https://cloud-api.livekit.io/api/project/$PROJECT_ID/sessions/$SESSION_ID>"
• Notes: Date range is supported (start, end), pagination via limit and page. The Analytics API is available to LiveKit Cloud customers on Scale plan or higher.
2) SDK / Agent event metrics (real-time, fine-grained)
• What it gives: real-time pipeline metrics emitted by the Agents SDKs (STT, LLM, TTS, VAD, EOUs). Example metric types:
◦ STT: ttfb, duration, audio_duration
◦ LLM: ttft, completion_tokens, prompt_tokens
◦ TTS: ttfb, duration, characters_count
• How to use: register event listeners in your agent/session code to receive metrics_collected (or similar) events and log or export the metrics.
• Example (pseudocode):
def on_metrics_collected(metrics: LLMMetrics):
print(f"TTFT: {metrics.ttft}s, Tokens: {metrics.total_tokens}")
session.llm.on("metrics_collected", on_metrics_collected)
Which should I help you with first?
• If you want historical/session-level metrics from Cloud, I can show full request/response examples and required token scopes for the Analytics API.
• If you need real-time agent metrics, tell me which agent metric type (STT, LLM, TTS, VAD) and which SDK/language you’re using so I can provide a concrete listener example. Sources: Analytics API | LiveKit docs | STT Metrics | LiveKit docs | LLM Metrics | LiveKit docs | VAD Metrics | LiveKit docs | TTS Metrics | LiveKit docslate-glass-71191
12/19/2025, 9:42 PMtall-airline-16756
12/19/2025, 9:43 PMiss = API Key, sub = identity, and a video grant (e.g., roomJoin: true) if joining rooms. Use the server SDK to avoid mistakes.
Quick checks
1. Are you using an official LiveKit server SDK for your backend language? (recommended)
2. Is the error thrown during token generation (server) or when the client connects?
3. Do you pass the raw API secret as the HMAC key (no extra encoding)?
4. Are you calling the SDK method that produces a JWT (e.g., ToJWT() / .toJwt()), not manually signing?
If you’re using Go, example that works:
at := auth.NewAccessToken(apiKey, apiSecret)
grant := &auth.VideoGrant{
RoomJoin: true,
Room: "my-room",
CanPublish: true,
}
at.SetVideoGrant(grant).
SetIdentity("admin-user").
SetValidFor(time.Minute * 10)
token, err := at.ToJWT()
if err != nil { log.Fatal(err) }
If you’re using Node:
const at = new AccessToken(apiKey, apiSecret, { identity: 'admin-user', ttl: '10m' });
at.addGrant({ roomJoin: true, room: 'my-room' });
const token = await at.toJwt();
Please share (one or two items):
• The code snippet you use to generate the token (server-side).
• Your backend language/runtime.
• Whether you copied the API secret exactly and whether you altered it (base64 decode, etc.).
• Full server error log if available.
With that, I’ll pinpoint the exact cryptographic misstep and provide the minimal fix.
Sources: Authentication | LiveKit docs | server-sdk-go | livekit/livekit_composite | DeepWiki | Server-side | livekit/livekit_composite | DeepWiki | server-sdk-kotlin | livekit/livekit_composite | DeepWiki | server-sdk-ruby | livekit/livekit_composite | DeepWikilate-glass-71191
12/20/2025, 9:07 PMAgent ${agentId} entry point triggered);
await ctx.connect();
console.log('Connected to LiveKit room');
// Wait for a participant to join
const participant = await ctx.waitForParticipant();
console.log(Participant joined: ${participant.identity});
const usageCollector = new metrics.UsageCollector();
const agent = new voice.Agent({
instructions: '{{INSTRUCTIONS}}',
tools: {
lookupWeather,
getCurrentTime,
},
});
// Preloaded VAD
const vad = ctx.proc.userData.vad as silero.VAD;
const infomaniakSTT = new InfomaniakSTT({
apiToken: process.env.INFOMANIAK_API_TOKEN!,
productId: process.env.INFOMANIAK_PRODUCT_ID!,
pollingInterval: 500,
maxPollingAttempts: 60,
});
// This is required for non streaming STT like Infomaniak or OpenAI Whisper
const streamAdapter = new stt.StreamAdapter(infomaniakSTT, vad);
const session = new voice.AgentSession({
stt: streamAdapter,
llm: new inference.LLM({
model: '{{LLM_MODEL}}',
}),
tts: new inference.TTS({
model: '{{TTS_MODEL}}',
voice: '{{TTS_VOICE}}',
}),
vad: vad,
});
session.on(voice.AgentSessionEventTypes.MetricsCollected, async (ev) => {
// Log metrics to console
metrics.logMetrics(ev.metrics);
// Collect usage for summary
usageCollector.collect(ev.metrics);
// Save to database for analytics
const metricsData = {
type: ev.metrics.type,
timestamp: new Date().toISOString(),
data: ev.metrics,
};
await saveMetricsToDatabase(agentId, metricsData);
// Log specific metric types
if (ev.metrics.type === 'llm_metrics') {
console.log([LLM Metrics] Tokens: ${ev.metrics.completionTokens}, TTFT: ${ev.metrics.ttftMs}ms, Duration: ${ev.metrics.durationMs}ms);
} else if (ev.metrics.type === 'tts_metrics') {
console.log([TTS Metrics] Audio Duration: ${ev.metrics.audioDurationMs}ms, TTFB: ${ev.metrics.ttfbMs}ms);
} else if (ev.metrics.type === 'stt_metrics') {
console.log([STT Metrics] Audio Duration: ${ev.metrics.audioDurationMs}ms, Duration: ${ev.metrics.durationMs}ms);
} else if (ev.metrics.type === 'eou_metrics') {
console.log([EOU Metrics] EOU Delay: ${ev.metrics.endOfUtteranceDelayMs}ms);
}
});
console.log('AgentSession created with Infomaniak STT wrapped in StreamAdapter');
console.log('VAD will buffer audio and trigger transcription on speech end');
await session.start({
agent,
room: ctx.room,
});
console.log('AgentSession started');
await session.generateReply({
instructions: '{{GREETING_INSTRUCTIONS}}',
});
console.log('Initial greeting sent');
},
});
cli.runApp(
new WorkerOptions({
agent: fileURLToPath(import.meta.url),
agentName: '{{AGENT_NAME}}', // NOTE --> Make sure agent has a name to avoid problems during dispatch
}),
);
is this code correct ?tall-airline-16756
12/20/2025, 9:07 PMproc.userdata (lowercase userdata) when prewarming and reading VAD.
• Async event handler safety: don’t leave an async callback unhandled on .on(...). Wrap async work in a fire-and-forget async function with try/catch to avoid unhandled rejections.
• Prisma JSON field: ensure your Prisma schema defines metricsData as Json (or serialize before insert).
• Error handling: wrap session.start() (and other awaited calls) in try/catch.
• Optional: add timeout or handling for ctx.waitForParticipant() so the agent doesn’t hang indefinitely if no participant joins.
Minimal concrete fixes (high level)
• Change:
◦ proc.userData.vad = await silero.VAD.load();
◦ to:
▪︎ proc.userdata.vad = await silero.VAD.load();
◦ And read it with const vad = ctx.proc.userdata.vad as silero.VAD;
• Change event listener from async (ev) => { ... } to:
session.on(voice.AgentSessionEventTypes.MetricsCollected, (ev) => {
void (async () => {
try {
// log, collect, and save metrics
} catch (err) {
console.error('Error processing metrics event:', err);
}
})();
});
• Ensure Prisma model:
model agentMetrics {
id String @id @default(cuid())
agent_id String
metricsType String
metricsData Json
timestamp DateTime @default(now())
}
• Wrap session start in try/catch:
try {
await session.start({ agent, room: ctx.room, record: true });
} catch (err) {
console.error('Failed to start session:', err);
}
Other recommendations
• Serialize/flatten metrics if your DB or downstream consumers expect specific fields (e.g., include requestId/speechId if present).
• Batch DB inserts at high volume to reduce load.
• Consider enabling record: true for observability and using tracing IDs for correlation.
If you want, I can:
• Provide a corrected full file with the fixes applied.
• Convert the metrics save to a batched writer for higher throughput.
Which would you like next?
Sources: Agents (Node.js) | livekit/livekit_composite | DeepWiki | TTS Metrics | LiveKit docs | Streamline Troubleshooting with Agent Observability | https://blog.livekit.io/streamline-troubleshooting-with-agent-observability/#/portal/signup| | Agents (Python) | livekit/livekit_composite | DeepWikilate-glass-71191
12/20/2025, 9:24 PMtall-airline-16756
12/20/2025, 9:24 PMsession.on(voice.AgentSessionEventTypes.MetricsCollected, (ev) => {
console.log('MetricsCollected event fired:', ev.metrics.type);
void (async () => { ... })();
});
• Do you see that log when exercising the playground?
2) Verify saveMetricsToDatabase is invoked and not erroring
• Add logging before/after the DB call and inside catch:
console.log('Saving metrics to DB', metricsData);
await prisma.agentMetrics.create(...);
console.log('Saved metrics to DB');
• Check your application logs for those messages or any "Failed to save metrics:" errors.
3) Check Prisma / DB connectivity and schema
• Confirm DATABASE_URL is set in the environment the worker uses.
• Ensure Prisma client is initialized in the same process and not closed prematurely.
• Confirm your Prisma schema has metricsData typed as Json and table/columns exist:
◦ model includes agent_id String, metricsType String, metricsData Json, timestamp DateTime.
4) Ensure event handler async work isn’t being dropped
• Use the fire-and-forget pattern with try/catch (we recommended this). If you used async directly on the handler, exceptions could be lost. Confirm the handler wraps async logic in a try/catch.
5) Confirm metrics are actually emitted by the playground run
• Some playground scenarios may not trigger metrics (e.g., no LLM/TT S/STT activity). Log the full ev.metrics to confirm contents:
console.log('Metrics payload:', JSON.stringify(ev.metrics));
6) Check for batching/transaction issues or silent DB rollbacks
• If you use transactions or a job worker that exits quickly, ensure the process stays alive until the DB write completes.
• If using connection pooling, verify pool limits and errors in logs.
7) Review worker environment differences
• Does the playground run the same code and environment variables as your local/production worker? The worker running in the playground may not have DATABASE_URL or Prisma migrations applied.
If you can, please paste:
• The console output showing whether the MetricsCollected log appears.
• The small logs you added around the DB call (saving / saved / errors).
• Your Prisma model for agentMetrics (the schema snippet).
• Confirmation of where the agent process runs (local vs Playground hosted) and the value/source of DATABASE_URL (no secrets — just whether it’s set).
Give me those details and I’ll point to the exact fix. If you prefer, I can provide a minimal patched handler you can drop in to verify end-to-end saving. Which do you want next?late-glass-71191
12/20/2025, 9:27 PMtall-airline-16756
12/20/2025, 9:27 PMsession.on(voice.AgentSessionEventTypes.MetricsCollected, (ev) => {
console.log('METRICS_EVENT fired:', ev.metrics.type);
void (async () => {
try {
console.log('METRICS_PAYLOAD:', JSON.stringify(ev.metrics));
const metricsData = { type: ev.metrics.type, timestamp: new Date().toISOString(), data: ev.metrics };
console.log('SAVING_METRICS:', metricsData);
await saveMetricsToDatabase(agentId, metricsData);
console.log('SAVED_METRICS');
} catch (err) {
console.error('METRICS_SAVE_ERROR:', err);
}
})();
});
3) Ensure PM2 preserves logs across restarts
• Start with ecosystem file or flags:
◦ pm2 start ecosystem.config.js
◦ or pm2 start app.js --name my-agent --output ./logs/out.log --error ./logs/err.log
4) Confirm Prisma writes and DB connectivity
• Add these checks at startup and before write:
console.log('DATABASE_URL present?', Boolean(process.env.DATABASE_URL));
try {
await prisma.$connect();
console.log('Prisma connected');
} catch (e) {
console.error('Prisma connect error:', e);
}
• Ensure your process has correct DATABASE_URL and migrations applied.
5) Keep process alive until write completes
• If your worker may exit, ensure PM2 doesn’t restart mid-write. Logs above will show if writes complete.
6) If still nothing appears, capture these artifacts and paste them:
• Output of pm2 logs around a test run (lines that include METRICS_EVENT, SAVING_METRICS, SAVED_METRICS, METRICS_SAVE_ERROR).
• The prisma model snippet for agentMetrics from schema.prisma.
• Confirmation that DATABASE_URL used by PM2 matches the DB you inspect.
If you want, I can provide a one-file small repro agent that logs and writes a test metric so you can verify PM2 + Prisma end-to-end. Would you like that?