This guide uses Tuner’s observability libraries for custom voice stacks:
tuner-core, tuner-stt-observer, tuner-tts-observer, and tuner-langchain.Prerequisites
- Tuner Active Account
- Configured Agent with provider “Custom API” in Tuner
- Python ≥ 3.10 and a custom voice stack you control
The mental model
Every Tuner library follows the same three-step lifecycle:Create one TunerSession per call
The session coordinates everything and holds your call’s identity —
call_id, workspace, and agent mapping.Attach observers
Each observer hooks into one component of your stack — STT, TTS, or your LLM framework — using that component’s native event system.
flush() at call end
The session assembles everything the observers captured into a single structured timeline and sends it to Tuner via the Create Call API. One network call, at the end.
Step 1: Install the libraries
Full stack from this guide:tuner-core provides TunerSession and TunerConfig. Provider SDKs are behind extras so you only pull in what you use.
Step 2: Set Your Credentials
TunerConfig.from_env() reads your credentials from environment variables:
| Variable | Required | Description |
|---|---|---|
TUNER_API_KEY | ✅ | Bearer token (starts with tr_api_) |
TUNER_WORKSPACE_ID | ✅ | Your Tuner workspace ID |
TUNER_AGENT_ID | ✅ | Agent Remote ID from Agent Settings → Agent Connection (UUID — not your agent’s display name) |
Step 3: Create a session per call
At the start of each call, create oneTunerSession. The model names you pass are used for cost calculation. call_id is your unique identifier for the call — keep it stable across retries so ingestion stays idempotent.
Step 4: Attach observers
Attach only the ones that match your stack — each is independent.STT — Deepgram
Attach the adapter to the Deepgram connection you already have. It’s a pure observer — it captures complete user utterances, turn timing, STT latency, and STT usage; it never touches your audio.LLM — LangGraph
Wrap your graph and attach it. Node transitions, per-node LLM latency, and tool calls and results all land in the call timeline. Invoke the wrapped graph exactly as you would the original.TTS — Cartesia
Wrap your synthesis stream withtrack_ws(). Tuner observes synthesis — it never owns or drives it. When the user barges in and you stop streaming, Tuner records only the words actually spoken, not the full intended response.
Word-accurate interruption capture requires
add_timestamps=True on your Cartesia context. An SSE pattern is available for HTTP-only stacks — see Text-to-Speech.track_ws() only records an interruption once your app reports one — building working barge-in also needs a detection signal, mark_interrupted()/wait_for_interruption(), and client-side playback stopping. See 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 agent loop raises:
Mix and match
| Your stack | Install |
|---|---|
| Deepgram + LangGraph + Cartesia | tuner-core "tuner-stt-observer[deepgram]" "tuner-tts-observer[cartesia]" tuner-langchain |
| Speechmatics + Cartesia | tuner-core "tuner-stt-observer[speechmatics]" "tuner-tts-observer[cartesia]" |
| STT observability only | tuner-core "tuner-stt-observer[deepgram]" |
| LLM tracing only | tuner-core tuner-langchain |
| LiveKit or Pipecat | Use the LiveKit / Pipecat SDKs instead |
session.flush() or by your own request.
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. - Confirm
session.flush()actually ran — put it in afinallyblock so exceptions don’t skip it.
Calls have no user turns
Calls have no user turns
- Your
LiveOptionsmust setutterance_end_ms(e.g."1000") andvad_events=True— without them Deepgram never emitsUtteranceEndand the utterance buffer never flushes. - Confirm caller audio is routed through
stt.send_audio(), not sent to the Deepgram connection directly.
What’s Next?
Speech-to-Text
Deepgram and Speechmatics adapters and 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 call timeline.
Review your first call
See what Tuner does with the data once it lands.