Metadata-Version: 2.5
Name: livekit-plugins-tts-cache-router
Version: 0.2.1
Summary: LiveKit Agents plugin for tts-cache-router, a caching TTS router for Sarvam, Raya, ElevenLabs, OpenAI, Deepgram, xAI and Soniox
Project-URL: Homepage, https://ttscacherouter.dev
Project-URL: Documentation, https://docs.ttscacherouter.dev/livekit
Project-URL: Source, https://github.com/talqing/tts-cache-router
Author: Oggnai Technologies Private Limited
License-Expression: Apache-2.0
License-File: LICENSE
Keywords: ai,audio,cache,deepgram,elevenlabs,livekit,openai,raya,realtime,router,sarvam,soniox,tts,voice,xai
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.10
Classifier: Topic :: Multimedia :: Sound/Audio
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Requires-Python: >=3.10.0
Requires-Dist: livekit-agents>=1.8.5
Description-Content-Type: text/markdown

# livekit-plugins-tts-cache-router

[LiveKit Agents](https://github.com/livekit/agents) plugin for
[tts-cache-router](https://ttscacherouter.dev): Sarvam, Raya, ElevenLabs, OpenAI, Deepgram, xAI
and Soniox text-to-speech,
with every sentence your agent has said before served from a per-account cache.

Same output whether you call the provider directly or through the router: each sentence reaches
the provider verbatim with your exact settings, and a cache hit replays a real provider take for
that exact request.

Full documentation: [docs.ttscacherouter.dev/livekit](https://docs.ttscacherouter.dev/livekit).

## Install

```bash
pip install livekit-plugins-tts-cache-router
```

## Usage

```python
from livekit.plugins import tts_cache_router as tcr

tts = tcr.TTS(
    provider=tcr.Sarvam(model="bulbul:v3", speaker="shubh", language="hi-IN"),
)
# or
tts = tcr.TTS(
    provider=tcr.Raya(
        model="standard", voice_id="d6a002d0-230c-49b1-a137-b8a7d564b1ae", language="hi"
    ),
)
# or
tts = tcr.TTS(
    provider=tcr.ElevenLabs(voice_id="JBFqnCBsd6RMkjVDRZzb", model="eleven_flash_v2_5"),
)
# or
tts = tcr.TTS(
    provider=tcr.OpenAI(model="gpt-4o-mini-tts", voice="ash", instructions="Speak warmly."),
)
# or
tts = tcr.TTS(
    provider=tcr.Deepgram(model="aura-2", voice="thalia-en"),
)
# or
tts = tcr.TTS(
    provider=tcr.XAI(voice="ara", language="auto"),
)
# or
tts = tcr.TTS(
    provider=tcr.Soniox(voice="Adrian", language="en"),
)

session = AgentSession(tts=tts, ...)
```

Before the first request, store your Sarvam, Raya, ElevenLabs, OpenAI, Deepgram, xAI or Soniox API
key in the dashboard at
[ttscacherouter.dev](https://ttscacherouter.dev/dashboard/provider-keys). The router keeps it
encrypted and uses it only to call that provider for your requests.

Set `TTS_CACHE_ROUTER_API_KEY` to your router API key, from the same dashboard (or pass
`api_key=`).

### `TTS(...)`

| Argument | Default | |
|---|---|---|
| `provider` | required, or `model` | `Sarvam(...)`, `Raya(...)`, `ElevenLabs(...)`, `OpenAI(...)`, `Deepgram(...)`, `XAI(...)` or `Soniox(...)` |
| `model`, `voice`, `language`, `speed`, `options` | | instead of `provider`: see [From a model string](#from-a-model-string) |
| `api_key` | `$TTS_CACHE_ROUTER_API_KEY` | router API key |
| `base_url` | `wss://api.ttscacherouter.dev` | set to your own router when self-hosting |
| `sample_rate` | `24000` | 8000, 16000, 22050 or 24000; ElevenLabs also 32000, 44100 or 48000; OpenAI 24000 only; Deepgram 8000, 16000, 24000, 32000, 44100 or 48000; xAI 8000, 16000, 22050, 24000, 44100 or 48000; Soniox 8000, 16000, 24000, 44100 or 48000 |

How long cached audio is kept is your org's retention setting, in the dashboard.

### From a model string

Instead of `provider`, give `model` as `"<provider>/<model>"`, with the voice and settings in the
router's own names (those of `/v1/audio/speech` and `GET /v1/models`):

```python
tts = tcr.TTS(
    model="elevenlabs/eleven_flash_v2_5",
    voice="JBFqnCBsd6RMkjVDRZzb",
    language="en",
    options={"voice_settings": {"stability": 0.5, "similarity_boost": 0.75}},
)
```

`voice` is required (`GET /v1/voices` lists them, your account's own included); `language` and
`speed` where the provider takes them; `options` holds the provider's other settings. The router
checks them when a reply starts: a wrong one fails that reply with `APIStatusError`
(`bad_request`). `update_options(model=..., voice=..., options=...)` changes them.

### `Sarvam(...)`

`model` (`"bulbul:v3"` or `"bulbul:v4-flash"`), `speaker`, `language` (e.g. `"hi-IN"`),
`pace` (0.5–2.0, default 1.0), `temperature` (0.01–1.0, default 0.6), `dict_id`, and for
`bulbul:v4-flash` only: `pitch` (-0.5–0.5), `loudness` (0.1–2.5), `enable_preprocessing`;
`min_buffer_size` (30–200) and `max_chunk_length` (50–500; by default each sentence is
synthesized whole).

### `Raya(...)`

`model` (`"standard"` or `"m1"`), `voice_id`, `language` (e.g. `"hi"`), `speed` (0.5–1.5,
default 1.0).

### `ElevenLabs(...)`

The settings of ElevenLabs' own LiveKit plugin: `voice_id`, `model` (default
`"eleven_turbo_v2_5"`; `eleven_v3` and `eleven_v4` models go through ElevenLabs' text-to-dialogue
API), `language` (optional; `"en-US"` or `"english"` is sent as `"en"`, as ElevenLabs' plugin
sends it), `voice_settings` (`VoiceSettings(stability, similarity_boost,
style=None, speed=None, use_speaker_boost=None)`; the voice's stored settings when omitted),
`apply_text_normalization` (`"auto"`, `"on"` or `"off"`), `apply_language_text_normalization`,
`enable_ssml_parsing`, `enable_logging`, `pronunciation_dictionary_locators`
(`PronunciationDictionaryLocator(pronunciation_dictionary_id, version_id)`, up to 3),
`sync_alignment` (default `True`: word-level timed transcripts) and `preferred_alignment`
(`"normalized"` or `"original"`).

### `OpenAI(...)`

The settings of OpenAI's own LiveKit plugin: `model` (default `"gpt-4o-mini-tts"`; also
`"gpt-4o-mini-tts-2025-12-15"`, `"tts-1"` and `"tts-1-hd"`), `voice` (default `"ash"`), `speed`
(0.25–4.0, default 1.0) and `instructions` (how to speak; `gpt-4o-mini-tts` follows them, `tts-1`
and `tts-1-hd` ignore them). OpenAI speaks the language of the text, at 24000 Hz only.

### `Deepgram(...)`

The settings of LiveKit's Deepgram plugin (`TTS` for Aura, `TTSv2` for Flux): `model` (`"aura-2"`,
the default, `"aura-1"` or `"flux"`), `voice` (default `"andromeda-en"`; Deepgram's model id is the
two joined, so `model="aura-2", voice="thalia-en"` is `aura-2-thalia-en`), `speed` (0.7–1.5,
`aura-2` only; default `None`, Deepgram's own) and `mip_opt_out` (default `False`). With Aura, the
first sentence of a reply after a pause of about five seconds or more takes about 0.7–1.2 s longer
to start than with LiveKit's Deepgram plugin; sentences from the cache, and Flux, are unaffected.

### `XAI(...)`

The settings of LiveKit's xAI plugin: `voice` (default `"ara"`, or a custom voice id), `language`
(default `"auto"`: xAI detects it; or a code such as `"en"`, `"hi"`, `"pt-BR"`), `speed` (0.7–1.5;
default `None`, xAI's own), `optimize_streaming_latency` (`0`, `1` or `2`; default `None`) and
`text_normalization` (default `None`, xAI's own: off). Each sentence is synthesized on its own.

### `Soniox(...)`

The settings of LiveKit's Soniox plugin: `language` (required, e.g. `"en"`; Soniox reads the text
through this language's phonetics, so set it to the language of the reply), `model`
(`"tts-rt-v2"`), `voice` (default `"Adrian"`, or a cloned voice's id) and `speed` (0.7–1.3, default
1.0), plus Soniox's `reduce_silence` (default `None`, Soniox's own: off). Each sentence is
synthesized on its own, so the pauses between sentences are a little shorter (about 0.2 s) than
in one Soniox stream (about 0.5 s).

### Changing settings mid-session

```python
tts.update_options(speaker="priya", pace=1.1)  # the current provider's settings, by name
tts.update_options(provider=tcr.Raya(...))  # or another provider
```

Replies that start afterwards use the new settings.

### Connections

When an `AgentSession` starts, it calls `tts.prewarm()`: the plugin connects to the router, and
the router connects to your provider with the current settings at once, so the first reply
doesn't wait for either. Idle connections stay open between replies, for up to about 30 minutes.

### Interruptions

When the agent is interrupted, the plugin cancels the router segment and keeps the connection open,
so the next reply starts on a warm connection.

### Metrics

`TTSMetrics.characters_count` counts only characters the provider synthesized (and billed) for
the request; sentences served from the cache count zero. `provider` and `model` report the
underlying provider, so usage dashboards attribute cost correctly.

Once a reply is all sent, the plugin also emits `cache_stats`, a `CacheStats` with the reply's
`request_id`, `segment_id` (when streamed), `sentences`, `cached_sentences`, `characters` and
`billed_characters` (and `cached_characters`, the difference):

```python
tts.on("cache_stats", lambda stats: print(stats.cached_sentences, "of", stats.sentences, "cached"))
```
