Skip to main content
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:

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.

Step 1: Install the SDK

Requirements: Python ≥ 3.10, 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.
Use the Agent Remote ID from Agent Settings > Agent Connection, not your agent’s display name. It is a UUID (for example fa4da74c-...). Copy your value from the Tuner dashboard.

Step 3: Add the plugin

Import TunerPlugin and add it right after creating your AgentSession, before calling session.start():
The plugin listens to session events and submits call data to Tuner when the session ends.

Enable simulation

Required for Call Simulation. Skip it otherwise.

Inbound

Tuner calls your agent over SIP.
  1. Add your agent’s SIP URI in Agent Settings > Simulation Setup. Get it from your SIP provider, then see Set up inbound simulation.
  2. Run your agent. There is nothing to add in code.
If a carrier such as Twilio or Telnyx sits between Tuner and LiveKit, also follow Carrier in front of LiveKit.
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.
  1. Configure the trigger endpoint in Agent Settings > Simulation Setup. See Set up outbound simulation.
  2. Pass the SIP URI your agent dialed as recipient, exactly as Tuner sent it.
Requires tuner-livekit-sdk 0.1.7 or later.
If the plugin starts before your agent knows the SIP URI, report it later with attach_sip. This needs tuner-livekit-sdk 0.1.12 or @usetuner.ai/livekit-sdk 0.5.6.

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.
Requires 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 incoming call.initiated event. Telnyx puts Tuner’s header in the event’s custom_headers.
See SIP providers for the SIP URI and credentials. Add two calls where your webhook already sends the call to LiveKit: 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.
You can call 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.
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.
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.

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.
  1. Open Call Logs and look for the call. If it’s missing, check that the plugin is sending calls (see Troubleshooting).
  2. If it’s there, check your tuner-livekit-sdk version against the Inbound table. Behind a carrier, also check the carrier webhook and the trunk’s include_headers setting. See Fix an Unreported run.

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, pass reason="agent_hangup" when ending the call programmatically:
Without this, agent hangups are indistinguishable from dropped calls in your analytics.
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

The plugin auto-detects the call type (phone_call for SIP participants, web_call otherwise). Override it explicitly when needed:
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.
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.
Attach arbitrary key-value data to every call record:
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):
With the traces extra installed, the plugin forwards LiveKit’s OpenTelemetry spans to Tuner. forward_traces defaults to True. To turn it off:
See OpenTelemetry traces.
Useful for local development or test environments:

Full example

The SIP Call-ID is captured automatically, so the example doesn’t pass 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

The extra adds the OpenTelemetry SDK and the OTLP HTTP exporter. Without it, trace forwarding is skipped with a debug log, and the rest of the plugin works as usual.
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

  1. The traces extra isn’t installed. The plugin skips forwarding and logs a debug message. Install tuner-livekit-sdk[traces].
  2. The SDK is older than 0.1.10. Upgrade tuner-livekit-sdk.
  3. forward_traces=False is set. Remove it or set it to True.
  4. The API key is wrong or revoked. Tuner rejects the spans with 401 or 403.

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

  • Verify that TUNER_AGENT_ID is the Agent Remote ID from Agent Settings > Agent Connection (not your agent’s display name).
  • Confirm TUNER_WORKSPACE_ID is correct.
  • Check your application logs for any error messages from the plugin.
  • Ensure TUNER_API_KEY starts with tr_api_ and is valid.
  • Confirm the API key belongs to the correct workspace.
  • Inbound: check that the caller joins the room as a SIP participant with a sip.callIDFull attribute, and check your tuner-livekit-sdk version 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’s include_headers is SIP_X_HEADERS or SIP_ALL_HEADERS.
  • Outbound: confirm you pass recipient with the SIP URI exactly as Tuner sent it.
  • See Fix an Unreported run.
  • You haven’t provided a recording URL resolver (recording_url_resolver in Python, recordingUrlResolver in 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 as http:// or localhost. 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.