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
- An active Tuner account
- A Tuner agent with provider LiveKit
- A Tuner API key, your workspace ID and the agent’s Agent Remote ID. See API keys.
- 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 captures session data from your LiveKit agent and sends it to Tuner when the call ends. You don’t make any API calls yourself. It ships as a Python package and a Node.js package that share the same wire format.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
Add two lines to your entrypoint.
4
Enable simulation
Required only if you use Call Simulation. See Enable simulation.
- Python
- Node.js
Step 1: Install the SDK
livekit-agents >= 1.4, aiohttp >= 3.9. The package also installs tuner-langchain >= 0.1.1 as a dependency.To forward OpenTelemetry traces as well, install the traces extra instead. See OpenTelemetry traces.Step 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():Enable simulation
Required for Call Simulation. Skip it otherwise.Inbound
Tuner calls your agent over SIP.- Add your agent’s SIP URI in Agent Settings > Simulation Setup. Get it from your SIP provider, then see Set up inbound simulation.
- Run your agent. There is nothing to add in code.
How Tuner links the call
How Tuner links the call
Tuner puts two IDs on each simulated call: the SIP Call-ID and an
X-Correlation-Id header. The plugin reads both from the SIP participant and sends them with the call report. Tuner links the call on either one. When Tuner calls LiveKit directly, the Call-ID links it. Behind a carrier, the Call-ID changes and the header links it.See How simulated calls are linked.Older Python SDK versions
Older Python SDK versions
To upgrade, install 0.1.11 or later. No code changes are needed.
Outbound
Your agent calls a Simulated Caller at a SIP URI that Tuner sends to your trigger endpoint.- Configure the trigger endpoint in Agent Settings > Simulation Setup. See Set up outbound simulation.
- Pass the SIP URI your agent dialed as
recipient, exactly as Tuner sent it.
- Python
- Node.js
Requires
tuner-livekit-sdk 0.1.7 or later.attach_sip. This needs tuner-livekit-sdk 0.1.12 or @usetuner.ai/livekit-sdk 0.5.6.
- Python
- Node.js
Carrier in front of LiveKit
Use this if a carrier such as Twilio or Telnyx sits between Tuner and LiveKit. Skip it if Tuner calls LiveKit directly.Prerequisite: your LiveKit inbound trunk must pass SIP headers to the agent. Set the trunk’s
include_headers option to SIP_X_HEADERS or SIP_ALL_HEADERS. The default, SIP_NO_HEADERS, drops them. As an alternative, map the header explicitly with headers_to_attributes, using {"X-Correlation-Id": "sip.h.x-correlation-id"}.To check, run lk sip inbound list --json. The setting appears only in the JSON output.tuner-livekit-sdk 0.1.12 or later (Python) or @usetuner.ai/livekit-sdk 0.5.6 or later (Node.js).
Set up the carrier so Tuner’s call reaches your webhook:
- Twilio: Tuner dials a Twilio SIP Domain with a Credential List. Set the domain’s Voice URL to your webhook. Use the domain for inbound only, because a domain has one Voice URL. Twilio passes Tuner’s header to the webhook as
SipHeader_X-Correlation-Id. - Telnyx: Tuner dials the SIP subdomain of a Call Control application,
sip:<user>@<subdomain>.sip.telnyx.com. Set the subdomain to receive calls from anyone, and set the application’s webhook to your server. Transfer the call on the incomingcall.initiatedevent. Telnyx puts Tuner’s header in the event’scustom_headers.
extract_sip reads Tuner’s data from the carrier’s webhook, and forward_to_sip builds the leg into LiveKit with Tuner’s header on it. Your server keeps its routing, signature checks and call control.
- Twilio
- Telnyx
- Python
- Node.js
forward_to_sip on every call. Without Tuner’s header, it returns your address unchanged. A malformed header is dropped with a warning on the tuner.telephony logger, and the call still connects. The SDK doesn’t check the carrier’s webhook signature, so keep your own check.
Why a carrier needs this
Why a carrier needs this
The carrier opens a new SIP leg into LiveKit with its own Call-ID, and doesn’t copy Tuner’s
X-Correlation-Id header onto that leg. forward_to_sip forwards the header, and the trunk setting lets it reach your agent. The plugin then sends it to Tuner, which links the call. For the general rule, see How simulated calls are linked.The SDK knows where each carrier puts Tuner’s data (Twilio’s SipHeader_ fields, Telnyx’s custom_headers), so the code is the same for every carrier. Only the provider name changes.How the header travels
How the header travels
If the call doesn't link
If the call doesn't link
The call still connects and your agent still talks. In the run it shows Unreported. The header can be lost in two places:
The header also can’t link the call if another worker with the same agent name takes the job and reports to a different Tuner agent. Give each agent’s worker its own name.
Support another carrier
Support another carrier
Write a provider with a
name, an extract method that reads the webhook payload into a SipInfo, and a forward_to_sip method that returns a Forward. Register it once. Then pass its name as provider.- Python
- Node.js
Check simulation works
Start a simulation run. See Run a simulation. When the call ends, check its result in the run. If it shows Unreported, Tuner never received a matching call from your agent.- Open Call Logs and look for the call. If it’s missing, check that the plugin is sending calls (see Troubleshooting).
- If it’s there, check your
tuner-livekit-sdkversion against the Inbound table. Behind a carrier, also check the carrier webhook and the trunk’sinclude_headerssetting. See Fix an Unreported run.
- Python
- Node.js
Per-turn timing
Each user and agent turn in the transcript carries these metadata fields. The values come from LiveKit’s own per-turn metrics; the SDK converts them to milliseconds and does not recompute them.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
If you use 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: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.The URL must be a public https:// link. Tuner stores a placeholder or any other value as sent, but treats the call as having no recording: it has no playback in call details and can’t be added to a Dataset.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, for example after you update a prompt, swap a model, or change your pipeline:The plugin reads
AGENT_VERSION automatically. Bump the number on every deployment.Override in code (takes priority over the env var):Trace forwarding
Trace forwarding
With the See OpenTelemetry traces.
traces extra installed, the plugin forwards LiveKit’s OpenTelemetry spans to Tuner. forward_traces defaults to True. To turn it off:Disable the plugin
Disable the plugin
Useful for local development or test environments:
Full example
sip_call_id.OpenTelemetry traces
LiveKit Agents emits OpenTelemetry spans for every session. The Python SDK forwards them to Tuner, and they appear as a span tree on the call’s Traces tab. See Traces for how to read one. The Node.js SDK does not forward traces.Requires
tuner-livekit-sdk 0.1.10 or later.1
Install the traces extra
2
Keep forward_traces on
forward_traces defaults to True, so TunerPlugin(session, ctx) is enough. The plugin sends spans to Tuner with the API key and base URL you already configured, and tags every span with the call’s ID. You don’t need to create a TracerProvider, choose an exporter, or set any OTEL_* environment variable.If you already configured a TracerProvider to export traces elsewhere, the plugin adds Tuner as an extra destination on it. Your existing backend keeps receiving the same spans.To stop forwarding traces, pass forward_traces=False.3
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. Matching 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 Traces for how to read the tree in Tuner.
Traces troubleshooting
The Traces tab never appears
The Traces tab never appears
- The
tracesextra isn’t installed. The plugin skips forwarding and logs a debug message. Installtuner-livekit-sdk[traces]. - The SDK is older than 0.1.10. Upgrade
tuner-livekit-sdk. forward_traces=Falseis set. Remove it or set it toTrue.- The API key is wrong or revoked. Tuner rejects the spans with
401or403.
Webhooks
Tuner can notify your systems when it has something to report on a call. See Webhooks.Before you finish
- A test call appears in Call Logs.
- Your API key, workspace ID and Agent Remote ID come from API keys.
- If you use Call Simulation: Enable simulation is done for your agent’s call direction, and one simulated call shows a result other than Unreported.
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.
Simulated calls show as Unreported
Simulated calls show as Unreported
- Inbound: check that the caller joins the room as a SIP participant with a
sip.callIDFullattribute, and check yourtuner-livekit-sdkversion against the Inbound table. - Inbound, with a carrier in front of LiveKit: check that your webhook calls
forward_to_sip(see Carrier in front of LiveKit) and that the inbound trunk’sinclude_headersisSIP_X_HEADERSorSIP_ALL_HEADERS. - Outbound: confirm you pass
recipientwith the SIP URI exactly as Tuner sent it. - See Fix an Unreported run.
A call has no recording
A call has no recording
- You haven’t provided a recording URL resolver (
recording_url_resolverin Python,recordingUrlResolverin Node.js), so the plugin sent"pending". Add one that returns the actual recording URL for each call. - The resolver returned a link that isn’t a public
https://URL, such ashttp://orlocalhost. Tuner treats those as no recording. - If using LiveKit Egress, ensure the recording has finished processing before the resolver is called.
Next steps
Configure your agent
Set up call outcomes, user intents, and evals.
Custom integration
Learn about the underlying API if you need more control.
Classify calls
Define how Tuner categorizes your calls.
Alerts
Get notified when issues are detected.