Skip to main content
Uses tuner-core plus one observer per role in your pipeline. Not on Python? See Direct API.
This guide shows the Tuner lines you add to a voice agent you already have. The examples assume Deepgram for STT, LangGraph for the LLM layer, and Cartesia for TTS. Swap or skip any role you don’t use. Your audio loops, agent logic, and synthesis code stay as they are. With the observers attached, Tuner receives user and agent turns with timing, per-turn STT/LLM/TTS and end-to-end latency, tool calls and results in the same timeline, word-accurate interruption capture, and usage plus cost, without code of your own.

Prerequisites

  • An active Tuner account
  • A Tuner agent with provider Custom API
  • A Tuner API key, your workspace ID and the agent’s Agent Remote ID. See API keys.
  • Python 3.10 to 3.13 and a custom voice stack you control

The mental model

1

Create one TunerSession per call

tuner-core coordinates the call. The session holds your call’s identity (call_id, workspace, and agent mapping) and coordinates the observers.
2

Attach one observer per role

STT, LLM, and TTS each have their own package. Attach only the roles your stack has; each is independent.
3

flush() at call end

The session assembles what the observers captured into a single timeline and sends it to Tuner via the Create Call API. One network call, at the end.

Step 1: Install

tuner-core provides TunerSession and TunerConfig. Provider SDKs sit behind extras so you only pull in what you use. Other combinations are in Mix and match below.

Step 2: Set your credentials

TunerConfig.from_env() reads these from the environment. Keyword arguments you pass to it take priority.

Step 3: Create a session per call

Create one TunerSession at the start of each call. The model names are recorded as call metadata. Keep call_id stable across retries so ingestion stays idempotent.

Extra metadata

Attach arbitrary key-value data to every call, such as environment, region, deployment version, or tenant:
Metadata can also be used as an eval input, which is how you evaluate agents whose instructions change per call. See Evaluate agents with per-call instructions. The keys ai_models and usage_token are reserved and ignored in extra_metadata.

Cost calculation

Pass a cost_calculator callable. At flush, it receives a CallUsage and returns the call cost in USD cents. The rates below are illustrative.
CallUsage fields: llm_prompt_tokens, llm_completion_tokens, llm_total_tokens, tts_characters, stt_audio_seconds. If the callable raises, the call is sent without a cost.

Disconnection reason

Set session.disconnection_reason before flush() to record why the call ended. Use a DisconnectReason value or your own string:

Step 4: Attach observers

STT: Deepgram

Send audio to Deepgram as you normally would. The adapter only listens on the connection’s events. Your LiveOptions must set utterance_end_ms and vad_events=True, or you’ll see calls with no user turns. Speechmatics, custom providers, and what each adapter captures: Speech-to-Text.

LLM: LangGraph

Node transitions, per-node LLM latency, and tool calls land in the timeline. Plain chains and privacy controls: LangChain / LangGraph.

TTS: Cartesia

Word-accurate interruption capture, the SSE pattern for HTTP-only stacks, and what your app owns for barge-in: Text-to-Speech.

Step 5: Flush at call end

flush() is the only network call the libraries make. Put it in a finally block so the call reaches Tuner even when the connection drops or your loop raises:
Open Call Logs in Tuner to see the processed result.

Enable simulation

Required if you’ll use Call Simulation. Skip it otherwise. Tuner links each simulated call to its run through a value you send back: sip_call_id for inbound agents and recipient for outbound agents (how simulated calls are linked). Do the steps for your agent’s call direction.

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. Pass the SIP Call-ID of the call Tuner placed as sip_call_id. Read it from the inbound call in your SIP stack; it may arrive under a different name.
When you send sip_call_id, Tuner uses it in place of call_id to detect duplicates.

Outbound

Your agent calls a Simulated Caller at a SIP URI that Tuner sends to your trigger endpoint. Tuner links the call through recipient, and tuner-core can’t send recipient yet.
  1. Configure the trigger endpoint in Agent Settings > Simulation Setup. See Set up outbound simulation.
  2. Send outbound calls with the Create Call API instead of session.flush(), with the dialed SIP URI in recipient. See Direct API: Enable simulation.

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. See Fix an Unreported run.

Mix and match

Missing an adapter for one of your providers? Extend the base adapter for that role. See Custom providers on the Speech-to-Text and Text-to-Speech pages.

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 TUNER_AGENT_ID is the Agent Remote ID from Agent Settings > Agent Connection.
  • Set TUNER_DEBUG=true to log the full payload on flush.
  • Confirm TUNER_WORKSPACE_ID is correct.
  • Confirm session.flush() ran. Put it in a finally block so exceptions don’t skip it.
  • Ensure TUNER_API_KEY starts with tr_api_ and is valid.
  • Confirm the API key belongs to the correct workspace.
Role-specific problems (missing user turns, interrupted turns recording in full, latency fields absent) are on the Speech-to-Text and Text-to-Speech pages.

Next steps

Speech-to-Text

Deepgram and Speechmatics adapters, options, custom providers.

Text-to-Speech

Cartesia over WebSocket or SSE, word-level interruption capture.

LangChain / LangGraph

Node transitions, tool calls, and LLM latency in your timeline.

Review your first call

What Tuner does with the data once it lands.