Skip to main content

TTS observability for Tuner — Cartesia adapter with barge-in and latency tracking.

Project description

tuner-tts-observer

TTS observability for Tuner. Wraps your TTS provider's synthesis stream to capture agent transcript and latency — Tuner observes synthesis, it does not own or drive it. Removing the context managers leaves your synthesis code completely unaffected.

Supported adapters

Provider Adapter Status
Cartesia CartesiaAdapter ✅ Supported
ElevenLabs SOON
OpenAI TTS SOON

Need a provider that isn't listed? Extend BaseTTSAdapter — see Custom providers below.

Installation

pip install tuner-core tuner-tts-observer

WebSocket support (recommended) requires the websockets extra:

pip install "tuner-tts-observer[cartesia]"
# equivalent to: pip install "cartesia[websockets]>=1.0"

Usage

WebSocket (recommended)

WebSocket is the production-standard pattern for real-time voice agents. It supports word-level interruption detection — when the user interrupts the agent, Tuner records only the words actually spoken, not the full intended response.

import os
from cartesia import AsyncCartesia
from tuner_core import TunerConfig, TunerSession
from tuner_tts_observer import CartesiaAdapter

cartesia_client = AsyncCartesia(api_key=os.environ["CARTESIA_API_KEY"])

session = TunerSession(config=TunerConfig.from_env(), call_id=call_id)
adapter = session.attach(CartesiaAdapter())

# Open one WebSocket connection per call — reused across turns
async with cartesia_client.tts.websocket_connect() as tts_ws:

    # Per turn:
    ctx = await tts_ws.context(
        model_id="sonic-2",
        voice={"id": "your-voice-id", "mode": "id"},
        output_format={"container": "raw", "encoding": "pcm_s16le", "sample_rate": 16000},
        add_timestamps=True,  # required for word-level interruption detection
    )
    await ctx.push(agent_text)
    await ctx.no_more_inputs()

    async with adapter.track_ws(agent_text) as tracked:
        async for chunk in tracked(ctx.receive()):
            if barge_in_event.is_set():
                break  # interrupted — Tuner records only what was spoken
            if chunk.audio:
                await websocket.send_bytes(chunk.audio)

await session.flush()

SSE (legacy)

SSE is the simpler pattern, available for HTTP-only stacks or existing integrations. Interruption detection is supported but spoken text cannot be cut accurately.

import os
from cartesia import Cartesia
from tuner_core import TunerConfig, TunerSession
from tuner_tts_observer import CartesiaAdapter

cartesia_client = Cartesia(api_key=os.environ["CARTESIA_API_KEY"])

session = TunerSession(config=TunerConfig.from_env(), call_id=call_id)
adapter = session.attach(CartesiaAdapter())

with adapter.track(agent_text) as tracked:
    for chunk in tracked(cartesia_client.tts.sse(
        model_id="sonic-2",
        transcript=agent_text,
        voice={"id": "your-voice-id", "mode": "id"},
        output_format={"container": "raw", "encoding": "pcm_s16le", "sample_rate": 16000},
    )):
        if chunk.audio:
            await websocket.send_bytes(chunk.audio)

await session.flush()

Custom providers

For providers other than Cartesia, extend BaseTTSAdapter from tuner_tts_observer — its docstring documents the full contract (timestamping, _record_agent_turn() / _record_tts_usage(), mark_interrupted()) with a worked example.

What gets captured automatically

Signal SSE WebSocket
Agent transcript text ✓ full text ✓ spoken words only on interruption
Turn start timestamp ✓ (first audio chunk)
Turn duration ✓ (acoustic length)
TTS TTFB
E2e latency
LLM latency
interrupted: true on barge-in
Word-accurate spoken text cut

Development

uv sync
make test

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

tuner_tts_observer-0.1.0.tar.gz (16.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

tuner_tts_observer-0.1.0-py3-none-any.whl (12.8 kB view details)

Uploaded Python 3

File details

Details for the file tuner_tts_observer-0.1.0.tar.gz.

File metadata

  • Download URL: tuner_tts_observer-0.1.0.tar.gz
  • Upload date:
  • Size: 16.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.7

File hashes

Hashes for tuner_tts_observer-0.1.0.tar.gz
Algorithm Hash digest
SHA256 d21799475913aae39e74336bb233f67c51e85f02a205e32030bb3850da285277
MD5 cb1b516c3b1d95e984c89d9c0075ef89
BLAKE2b-256 d0ee00b3908c9f4c434be56d8b72f45b6b19aed2af16267ce813a760b6f5b1ec

See more details on using hashes here.

File details

Details for the file tuner_tts_observer-0.1.0-py3-none-any.whl.

File metadata

File hashes

Hashes for tuner_tts_observer-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 260b616f7dd4b4678fa2bc837b354159bf50cc8661a81727eacedda11d6e87c6
MD5 0f956041c7336e7886872a1b097271f6
BLAKE2b-256 46c897fd65754592e598732db1ddfbb5b349975f162c4756dc1f3a5d6875a737

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page