Metadata-Version: 2.5
Name: livekit-plugins-tts-cache-router
Version: 0.10.0
Summary: LiveKit Agents plugin for tts-cache-router (https://ttscacherouter.dev), a caching TTS router for Maya Open-Source, Sarvam, Raya, ElevenLabs, OpenAI, Deepgram, xAI, Soniox, Smallest AI, Cartesia, Gemini and 60db
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: 60db,ai,audio,cache,cartesia,deepgram,elevenlabs,gemini,livekit,maya,openai,raya,realtime,router,sarvam,smallestai,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): Maya Open-Source (hosted by the router, no key
needed), Sarvam, Raya, ElevenLabs, OpenAI, Deepgram, xAI, Soniox, Smallest AI, Cartesia, Gemini and
60db 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). For
LLMs and coding agents: [llms.txt](https://docs.ttscacherouter.dev/llms.txt), and this page as
Markdown at [livekit.md](https://docs.ttscacherouter.dev/livekit.md).

## Install

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

## Usage

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

tts = tcr.TTS(model="maya/maya-2-native", voice="Ahana", language="hi")
# or
tts = tcr.TTS(model="sarvam/bulbul:v3", voice="shubh", language="hi-IN")
# or
tts = tcr.TTS(model="raya/standard", voice="d6a002d0-230c-49b1-a137-b8a7d564b1ae", language="hi")
# or
tts = tcr.TTS(
    model="elevenlabs/eleven_flash_v2_5",
    voice="JBFqnCBsd6RMkjVDRZzb",
    options={"voice_settings": {"stability": 0.5, "similarity_boost": 0.75}},
)
# or
tts = tcr.TTS(model="openai/gpt-4o-mini-tts", voice="ash", options={"instructions": "Speak warmly."})
# or
tts = tcr.TTS(model="deepgram/aura-2", voice="thalia-en")  # Deepgram's aura-2-thalia-en
# or
tts = tcr.TTS(model="xai/xai-tts", voice="ara", language="auto")
# or
tts = tcr.TTS(model="soniox/tts-rt-v2", voice="Adrian", language="en")
# or
tts = tcr.TTS(model="smallestai/lightning_v3.1_pro", voice="meher", language="en")
# or
tts = tcr.TTS(model="cartesia/sonic-3", voice="f786b574-daa5-4673-aa0c-cbe3e8534c02", language="en")
# or
tts = tcr.TTS(model="gemini/gemini-3.8-flash-tts", voice="Kore", options={"instructions": "Warm and upbeat"})
# or
tts = tcr.TTS(model="60db/60db-fast-v01", voice="fbb75ed2-975a-40c7-9e06-38e30524a9a1")

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

Maya Open-Source runs on the router's own GPUs and needs no provider key. For every other
provider, store your Sarvam, Raya, ElevenLabs, OpenAI, Deepgram, xAI, Soniox, Smallest AI, Cartesia,
Gemini or 60db API key in the dashboard at
[ttscacherouter.dev](https://ttscacherouter.dev/dashboard/provider-keys) before the first request.
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(...)`

The settings are in the router's own names (those of `/v1/audio/speech` and `GET /v1/models`), the
same for every provider:

| Argument | Default | |
|---|---|---|
| `model` | required | `"<provider>/<model>"`: `"maya/maya-2-native"`, `"sarvam/bulbul:v3"`, `"raya/standard"`, `"elevenlabs/eleven_flash_v2_5"`, `"openai/gpt-4o-mini-tts"`, `"deepgram/aura-2"`, `"xai/xai-tts"`, `"soniox/tts-rt-v2"`, `"smallestai/lightning_v3.1_pro"`, `"cartesia/sonic-3"`, `"gemini/gemini-3.8-flash-tts"`, `"60db/60db-fast-v01"`, … |
| `voice` | required | the provider's voice id (`GET /v1/voices` lists them, your account's own included); `None` for a Gemini multi-speaker voice, whose `options["speakers"]` names the voices |
| `language` | `None` | the provider's own language code, where it takes one: `"hi"` for Maya Open-Source and Raya, `"hi-IN"` for Sarvam, `"en"` for the rest |
| `speed` | `None` | the speaking rate, where the provider takes one |
| `options` | `None` | the provider's other settings, e.g. `{"temperature": 0.6}` for Sarvam, `{"voice_settings": {...}}` for ElevenLabs, `{"instructions": "..."}` for OpenAI |
| `split` | `"sentences"`, or your org's setting | `"sentences"`: each sentence of a reply is synthesized and cached on its own, and the first plays as soon as the LLM has written it. `"none"`: the whole reply is one request and one cache entry, synthesized once all its text has arrived, so its audio starts only after the LLM has finished; for fixed lines, such as a greeting spoken with `session.say()`. Left out, your org's setting for the provider in the dashboard decides (`"sentences"` unless you changed it) |
| `api_key` | `$TTS_CACHE_ROUTER_API_KEY` | router API key |
| `base_url` | the provider's region (below) | the router for every provider; your own router when self-hosting (locally `ws://localhost:8090`) |
| `sample_rate` | `24000` | one the provider streams; 24000 works with every provider |

Every provider's models, voices, languages, sample rates and options are in
[Models and options](https://docs.ttscacherouter.dev/reference/models). The router checks the
settings: when the session starts, the plugin connects and a wrong setting is logged as an error,
and a reply with a wrong one fails with `APIStatusError` (`bad_request`, not retryable).

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

### Regions

The hosted router runs in two regions, and each keeps its own cache. With no `base_url`, the
plugin connects to the region of the model's provider:

| Providers | Region | `base_url` |
|---|---|---|
| Maya Open-Source, Sarvam, Raya, 60db | India (in Singapore for now) | `wss://api.in.ttscacherouter.dev` |
| ElevenLabs, OpenAI, Deepgram, xAI, Soniox, Smallest AI, Cartesia, Gemini | US (San Francisco) | `wss://api.us.ttscacherouter.dev` |

`base_url` sends every provider to that host instead. An agent far from its provider's region may
get faster cache hits by setting `base_url` to the region near it; its cache there starts empty,
and the provider is then called from that region.

### Changing settings mid-session

```python
tts.update_options(voice="priya", speed=1.1)
tts.update_options(model="cartesia/sonic-3", voice="f786b574-daa5-4673-aa0c-cbe3e8534c02", language="en")
```

Replies that start afterwards use the new settings. `options` replaces the options as a whole. A
`model` of another provider starts from no `language`, `speed` or `options` unless they are given
too. Without a `base_url`, a provider in another region connects there from the next reply.

### Provider notes

- **Maya Open-Source** (`model="maya/maya-2-native"`) is Maya Research's open-weights Maya 2 Native,
  hosted by the router: no provider key. Its 20 voices are named (`voice="Ahana"`, `"Anirban"`,
  …; `GET /v1/voices?provider=maya` lists each voice's languages). `language` is optional and takes
  bare codes (`"as"`, `"bn"`, `"en"`, `"gu"`, `"hi"`, `"kn"`, `"ml"`, `"mr"`, `"or"`, `"pa"`,
  `"ta"`, `"te"`); left out, Maya picks it from each sentence's script. 24000 Hz only, and no
  `speed`. `options`: `normalize` (numbers, dates and the like spoken as words; on by default),
  `temperature`, `top_k`, `top_p`, `repetition_penalty` and `seed`. Both regions serve it, but its
  GPU is in India, so a miss through the US region takes about 200 ms longer to start. Maya
  Open-Source is billed to your router credit: what it synthesizes, and what the cache serves at the
  cache's price, as for every provider.
- **ElevenLabs:** `eleven_v3` and `eleven_v4` models go through ElevenLabs' text-to-dialogue API.
  Transcripts are word by word (turn this off with `options={"sync_alignment": False}`).
- **OpenAI** speaks the language of the text, at 24000 Hz only, and takes no `language`.
- **Deepgram:** the model is the family and the voice the rest of Deepgram's model id
  (`model="deepgram/aura-2", voice="thalia-en"` is `aura-2-thalia-en`). 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.
- **Soniox** needs `language`: it reads the text through that language's phonetics.
- **xAI, Soniox, Smallest AI and Cartesia:** each sentence is synthesized on its own, so the pauses
  between sentences are a little shorter than in one provider stream, and a sentence doesn't carry
  the prosody of the one before it.
- **Cartesia:** transcripts are word by word (turn this off with
  `options={"word_timestamps": False}`).
- **Gemini** speaks the language of the text and takes no `language` or `speed`: ask for a pace or a
  tone in `options={"instructions": ...}`. The 2.5 and 3.1 models get LiveKit's Gemini plugin's
  default instructions when you give none, and send a sentence's audio only once it is complete
  (2.5: about 4 s to first audio). Two speakers: `voice=None, options={"speakers": {"Joe": "Kore",
  "Jane": "Puck"}, "speaker": "Jane"}` on the 3.8 models; switch with
  `update_options(options={..., "speaker": "Joe"})`.
- **60db** speaks the language of the text and takes no `language`. The `model` is the tier of the
  voice (`60db-fast-v01` or `60db-quality-v01`; `GET /v1/voices` lists each tier's). Set the pace
  with `options={"wpm": 150}`; `stability` and `similarity` are options too.

### 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,
and are replaced at once when the router restarts.

### 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 and model, 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"))
```

An interrupted reply emits both too (`TTSMetrics.cancelled` set), counting the sentences the router
sent in full before the interruption. This needs a router that reports them (the hosted router
does); with an older one, the plugin logs an error and reports nothing for the interrupted reply.
