Skip to main content
This guide covers the Python SDK. JavaScript support is coming soon.

Prerequisites

  • Tuner Active Account
  • Configured Agent with provider “Custom API” in Tuner
  • Python project running LiveKit Agents v1.4 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.
1

Install the SDK

Add the package to your project.
2

Set Your Credentials

Configure your Tuner API key, workspace ID, and agent ID.
3

Add the Plugin

Drop two lines into your entrypoint and you’re done.
Estimated time: 2 minutes from start to finish

Step 1: Install the SDK

pip install tuner-livekit-sdk
Requirements: Python ≥ 3.10, livekit-agents >= 1.4, aiohttp >= 3.9

Step 2: Set Your Credentials

You can configure credentials via environment variables or pass them directly in code.
export TUNER_API_KEY="tr_api_..."
export TUNER_WORKSPACE_ID="123"
export TUNER_AGENT_ID="my-agent"
VariableRequiredDescription
TUNER_API_KEYBearer token (starts with tr_api_)
TUNER_WORKSPACE_IDYour Tuner workspace ID
TUNER_AGENT_IDAgent identifier from Agent Settings
TUNER_BASE_URLAPI base URL (default: https://api.usetuner.ai)
The Agent ID must match the identifier configured in Tuner. Find it under Agent Settings > Agent Connection > Agent ID.

Step 3: Add the Plugin

Import TunerPlugin and add it right after creating your AgentSession — before calling session.start():
from tuner import TunerPlugin

async def entrypoint(ctx: JobContext):
    session = AgentSession(...)
    TunerPlugin(session, ctx)          # wires itself automatically
    await session.start(...)
That’s it. The plugin listens to session events and submits call data to Tuner when the session ends.

Configuration Options

The plugin auto-detects the call type (phone_call for SIP participants, web_call otherwise). Override it explicitly when needed:
TunerPlugin(session, ctx, call_type="phone_call")
TunerPlugin(session, ctx, call_type="web_call")
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.
# Static / pre-known URL
async def my_resolver(room_name: str, job_id: str) -> str:
    return f"https://cdn.example.com/recordings/{job_id}.ogg"

TunerPlugin(session, ctx, recording_url_resolver=my_resolver)
# LiveKit Egress → S3
async def egress_resolver(room_name: str, job_id: str) -> str:
    url = await my_egress_db.get_recording_url(room_name)
    return url or "pending"

TunerPlugin(session, ctx, recording_url_resolver=egress_resolver)
Provide a callable that receives a UsageSummary and returns the call cost in USD Cents. Implement the method that match your pricing plan. the below implementation is just for example.
def calculate_cost(usage) -> float:
    llm_cost  = usage.llm_prompt_tokens     * 0.000_003
    llm_cost += usage.llm_completion_tokens * 0.000_015
    tts_cost  = usage.tts_characters_count  * 0.000_030
    stt_cost  = usage.stt_audio_duration    * 0.000_006
    return llm_cost + tts_cost + stt_cost

TunerPlugin(session, ctx, cost_calculator=calculate_cost)
Attach arbitrary key-value data to every call record:
TunerPlugin(
    session, ctx,
    extra_metadata={
        "env": "production",
        "region": "us-east-1",
        "deployment": "v2.3.1",
    },
)
TunerPlugin(
    session, ctx,
    timeout_seconds=15.0,   # per-request timeout (default: 30.0)
    max_retries=5,          # retries on 5xx / 429 / network errors (default: 3)
)
Useful for local development or test environments:
import os

TunerPlugin(
    session, ctx,
    enabled=os.getenv("ENV") == "production",
)

Full Example

import os
from livekit.agents import JobContext, AgentSession
from tuner import TunerPlugin


def calculate_cost(usage) -> float:
    return (
        usage.llm_prompt_tokens     * 0.000_003
        + usage.llm_completion_tokens * 0.000_015
        + usage.tts_characters_count  * 0.000_030
    )


async def get_recording_url(room_name: str, job_id: str) -> str:
    return await my_storage.get_url(job_id) or "pending"


async def entrypoint(ctx: JobContext):
    session = AgentSession(...)

    TunerPlugin(
        session, ctx,
        api_key=os.environ["TUNER_API_KEY"],
        workspace_id=int(os.environ["TUNER_WORKSPACE_ID"]),
        agent_id="customer-support-v3",
        call_type="phone_call",
        recording_url_resolver=get_recording_url,
        cost_calculator=calculate_cost,
        extra_metadata={"env": "prod", "region": "us-east-1"},
        timeout_seconds=20.0,
        max_retries=3,
        enabled=True,
    )

    await session.start(...)

Troubleshooting

  • Verify that TUNER_AGENT_ID matches the Agent ID configured in Tuner under Agent Settings > Agent Connection.
  • 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.
  • 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.

What’s Next?

Configuring Your Agent

Set up call outcomes, user intents, and behavior checks.

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.