This guide covers both the Python SDK (
tuner-livekit-sdk) and the Node.js SDK (@usetuner.ai/livekit-sdk). Use the language switcher below to view the guide for your stack.Video Tutorial
Prerequisites
- Tuner Active Account
- Configured Agent with provider “Custom API” in Tuner
- A LiveKit Agents project, either:
- Python running LiveKit Agents v1.4 or later, or
- Node.js 18+ running
@livekit/agentsv1.4 or later with@livekit/rtc-nodev0.13 or later.
Overview
The Tuner LiveKit SDK automatically captures session data from your LiveKit agent and sends it to Tuner when the call ends — no manual API calls required. It ships in two flavours that share the same wire format and feature set: a Python package and a Node.js package.1
Install the SDK
Add the package to your project.
2
Set Your Credentials
Configure your Tuner API key, workspace ID, and agent remote ID.
3
Add the Plugin
Drop two lines into your entrypoint and you’re done.
- Python
- Node.js
Step 1: Install the SDK
livekit-agents >= 1.4, aiohttp >= 3.9Step 2: Set Your Credentials
You can configure credentials via environment variables or pass them directly in code.- Environment Variables
- Inline in Code
Step 3: Add the Plugin
ImportTunerPlugin and add it right after creating your AgentSession — before calling session.start():SIP correlation for simulation
To run Call Simulation against this agent, pass the inbound call’s SIPCall-ID as sip_correlation_id. For why Tuner needs it and how the value can change name in transit, see Simulation SIP setup.This applies to inbound simulation, where Tuner dials your agent. For outbound simulation, pass the dialed SIP URI as
recipient instead — see Outbound Simulation.sip.callIDFull attribute.Requires
tuner-livekit-sdk >= 0.1.5.Helper: extract the SIP correlation ID
Add this helper to your entrypoint module. It scans the room’s remote participants for the SIP participant and returns itssip.callIDFull attribute.None and TunerPlugin falls back to its normal behaviour.Wire it into your entrypoint
The SIP participant only becomes visible after the agent joins the room. Callctx.connect() first, then read the correlation ID, then pass it to TunerPlugin:Disconnection Reasons
Tuner captures why each call ended. User disconnects and errors are tracked automatically. To record agent-initiated hangups, passreason="agent_hangup" when ending the call programmatically:Full tool call example
Full tool call example
drain=True lets any in-flight audio finish before the session closes. Omit it for an immediate cutoff.LangChain and LangGraph observability
Using LangGraph or LangChain for your logic layer? The plugin can capture node transitions, tool calls, and timing alongside session data. See LangChain / LangGraph.Configuration Options
Call Type
Call Type
The plugin auto-detects the call type (
phone_call for SIP participants, web_call otherwise). Override it explicitly when needed:Recipient
Recipient
The phone number or SIP URI of the callee. Optional — pass it when you want to record who the call was directed to:
Recording URL
Recording URL
Tuner requires a
recording_url for every call. Provide a resolver function that returns the URL. If you don’t provide one, the plugin submits "pending" as a placeholder.Cost Calculation
Cost Calculation
Provide a callable that receives a
UsageSummary and returns the call cost in USD cents. Implement the method that matches your pricing plan; the snippet below is illustrative.Extra Metadata
Extra Metadata
Attach arbitrary key-value data to every call record:
Retry and Timeout
Retry and Timeout
Agent Version Tracking
Agent Version Tracking
Track which version of your agent handled each call — useful when you update a prompt, swap a model, or change your pipeline:Tuner reads it automatically. Bump the number on every deployment.Override in code (takes priority over the env var):
Disable the Plugin
Disable the Plugin
Useful for local development or test environments:
Full Example
Troubleshooting
Calls don't appear in Tuner
Calls don't appear in Tuner
- Verify that
TUNER_AGENT_IDis the Agent Remote ID from Agent Settings → Agent Connection (not your agent’s display name). - Confirm
TUNER_WORKSPACE_IDis correct. - Check your application logs for any error messages from the plugin.
Recording URL shows as 'pending'
Recording URL shows as 'pending'
- You haven’t provided a
recording_url_resolver. Add one that returns the actual recording URL for each call. - If using LiveKit Egress, ensure the recording has finished processing before the resolver is called.
OpenTelemetry traces
LiveKit Agents already emits OpenTelemetry spans for every session. Point its exporter at Tuner and those spans appear as a span tree on the matching call, under a Traces tab. See OpenTelemetry Traces for how to read one.This is optional and separate from the SDK integration above. Trace export is configured through LiveKit’s own telemetry module, in Python.
1
Point your exporter at Tuner
TUNER_API_KEY you set in Step 2. It identifies your workspace, so nothing else needs configuring.If you already send traces to your own observability backend, leave that variable alone and fan out with an OpenTelemetry Collector instead of switching endpoints.2
Use the HTTP exporter
Tuner accepts OTLP over HTTP with protobuf encoding, which is the default for the HTTP exporter. gRPC is not supported.
3
Tag the trace with your call id
Tuner matches a trace to a call on a You only need the attribute on one span in the trace. Tuner applies it to the whole trace, including spans that arrived before the tagged one.
tuner.call_id span attribute. Its value has to be the same call id the plugin submits for this session, character for character.metadata is applied to every span in the session, so setting it once on the tracer provider is enough.4
Check that it worked
Open the call in Tuner. A Traces tab appears on the call details page once spans have been received and matched. Correlation completes when the call itself is ingested, so the tab can take a moment to appear after a session ends.
Span reference
The span names come from LiveKit Agents, so they are the same ones you would see in any other OpenTelemetry backend.
See LiveKit’s telemetry documentation for the full set, and OpenTelemetry Traces for how to read the tree in Tuner.
Traces troubleshooting
The Traces tab never appears
The Traces tab never appears
In order of likelihood:
tuner.call_iddoes not match the call id submitted for the session. They have to be identical strings. This is the most common cause by a wide margin.- The attribute is missing entirely. Nothing in the trace carries it, so there is nothing for Tuner to match on.
- The gRPC exporter is still in use. Requests are rejected with
415. Check your agent logs for exporter errors. - The API key is wrong or revoked. Requests are rejected with
401or403.
Traces also stopped reaching my own backend
Traces also stopped reaching my own backend
You probably repointed
OTEL_EXPORTER_OTLP_ENDPOINT rather than the traces-specific variable. Restore it and use an OpenTelemetry Collector to send spans to both destinations.service.name reads unknown_service
service.name reads unknown_service
Your tracer provider was created without a service name resource. Traces still arrive and still correlate, but they are harder to tell apart when several agents report into the same workspace.
Webhooks
Tuner can notify your systems when it has something to report on a call — see Webhooks.What’s Next?
Configuring Your Agent
Set up call outcomes, user intents, and evals.
Custom Integration
Learn about the underlying API if you need more control.
Classifying Calls
Define how Tuner categorizes your calls.
Real-Time Alerts
Get notified when issues are detected.