echospeaks
Sentence-level TTS audio cache for pipecat
voice pipelines. Audio lives in echo-server (the pod-shared cache service;
it keeps its own L1/L2 internally) - there is no client-side store. Caching is
enabled by passing an api_key in cache_config; without it everything
passes through untouched and the pipeline behaves fully stock. To get a key,
visit echospeaks.ai and contact us.
Layout
echospeaks/
defaults.py SDK-wide defaults
common/ shared dataclasses + logger
cache/ cache keys + the echo-server client
pipecat_tts/ the pipecat integration (read.py, write.py, plan.py)
How it works
Two pipeline processors around the host's completely stock TTS service:
- Read -
EchoTTSCacheRead(echospeaks.pipecat_tts.read): placed before the tts. Aggregates LLM tokens into sentences (the TTS receives pre-aggregated sentences, so its own aggregator never engages) and asks echo-server per sentence (0.5s bounded) - a hit is swallowed (the TTS never sees it); a miss flows through and is registered in the shared turn plan for capture. - Write -
EchoTTSCacheWrite(echospeaks.pipecat_tts.write): placed right after the tts. Slices each miss sentence's audio out of the turn's shared provider context by word timestamps and fire-and-forgets a POST to echo-server (audio base64-encoded inside the v1 JSON envelope; decoded on fetch), and injects cached hit audio at the exact sentence boundary so sentences always play in LLM order. Interruptions discard partial buffers, so truncated audio is never stored.
Standalone utterances (TTSSpeakFrame) are cached too. Fixed prompts
the host pushes directly (never through the LLM) are cached as one entry
over the full utterance. A miss forwards the original frame and captures
its audio; a hit swallows it and injects the cached audio with the same
frame sequence a live utterance produces. Fixed prompts repeat on every
call, making them the highest-value entries in the cache.
echo-server behavior is strictly fail-open: the lookup is bounded by a 0.5s timeout and every error (down, slow, 401, 5xx) degrades to a plain miss.
Install
pip install echospeaks
pipecat-ai comes from your own app (the pipeline the processors go into);
pip install "echospeaks[pipecat]" pulls it in for a standalone install.
Usage
The TTS service stays completely stock: the cache is two pipeline processors around it. The read side aggregates LLM tokens into sentences (so the TTS receives pre-aggregated sentences and its own aggregator never engages), swallows cache hits, and forwards misses; the paired writer captures each miss sentence's audio for storing and injects cached hit audio at the exact sentence boundary, so sentences always play in LLM order for any hit/miss pattern.
from echospeaks.pipecat_tts import EchoTTSCacheRead, EchoTTSCacheWrite
EchoTTSCacheRead.configure({
"api_key": ECHO_SERVER_API_KEY,
"cache_key": {
"provider": "elevenlabs", # mandatory
"voice_id": VOICE_ID, # mandatory
"model_id": "eleven_turbo_v2_5", # mandatory
"speed": 1.1, # any other key: folded into the key
},
"tag": "my-campaign",
"region": "asia-south1", # nearest echo-server: "asia-south1" (default) or "us-west1"
"debug": False,
})
pipeline = Pipeline([
...,
llm,
EchoTTSCacheRead(), # picks up the configure()d config
tts, # your TTS service, fully stock
EchoTTSCacheWrite(), # pairs itself with the read upstream at StartFrame
transport.output(),
...,
])
EchoTTSCacheRead.current() returns this call's instance, whose stats
property exposes the raw counters for the host's own logs/metrics:
s = EchoTTSCacheRead.current().stats
# {"hits": 6, "misses": 3, "hit_chars": 214, "miss_chars": 101,
# "total_chars": 315, "total_sentences": 9, "hit_rate": 0.679}
log.info(f"cache hits={s['hits']} misses={s['misses']} hit_rate={s['hit_rate']:.0%}")
hit_rate is character-weighted (characters are what synthesis costs), 0..1.
Regions. There is one echo-server, with its own cache, per region. Set
region to the one nearest the host running the pipeline (asia-south1 by
default, us-west1 for the US). The same API key works in every region; each
region's cache is independent.
Without an api_key (or the keying inputs) everything passes through
untouched. Segmentation and in-order injection use word timestamps;
ElevenLabs and Cartesia both provide them. Without word timestamps the writer
degrades gracefully: single-sentence turns still store, mid-turn hits inject
at turn end.
TTSSpeakFrames need no extra wiring - they pass through the same read
processor and are cached whole. One trade-off to know: a speak utterance
waits for its lookup before synthesis starts, so its time-to-first-audio
shifts by the lookup round trip (~60-250ms, bounded at 0.5s fail-open) on a
miss - and skips synthesis entirely on a hit.
cache_config reference
One dict holds every host tunable. Missing or None keys fall back to the
defaults in echospeaks/defaults.py; unknown keys are warned about and
ignored (never a crash).
| Key | Default | What it does |
|---|---|---|
api_key |
(unset) | echo-server API key; setting it enables the cache. Get one at echospeaks.ai. |
cache_key.provider |
(mandatory) | TTS provider name (e.g. elevenlabs). Missing disables caching. |
cache_key.voice_id |
(mandatory) | Voice the TTS synthesizes with. Missing disables caching. |
cache_key.model_id |
(mandatory) | Model the TTS synthesizes with. Missing disables caching. |
cache_key.<other> |
(unset) | Any additional key is folded into the cache key, so differently-tuned audio never shares an entry, e.g. speed, stability, similarity_boost, style, use_speaker_boost, language, emotion, pitch. Pass the same values you gave the TTS. Adding, changing, or removing such a key changes the cache key, invalidating existing entries: they stop matching and re-store on first use. |
tag |
(unset) | Grouping label (campaign, tenant, ...) recorded per store for filtered listings (?tag=). Not part of the cache key. |
region |
asia-south1 |
Which echo-server to use: the region nearest the host running the pipeline. asia-south1 or us-west1 (see ECHO_SERVER_REGIONS in defaults.py). Keys work in every region; each region keeps its own cache. Unknown names warn and fall back to asia-south1. |
audio_chunk_ms |
30 |
Duration of each injected audio slice. Converted to bytes at each entry's own sample rate (30ms = 960B @ 16kHz mono). Smaller = finer barge-in stop, more frame overhead. |
debug |
False |
Verbose per-frame DEBUG logs (word attribution, holds, cuts). INFO HIT/MISS lines are always on. |
How the dict is sourced is the host's business - env vars, a config file, a per-tenant document; the SDK only sees the final dict.
Tests
uv sync && uv run pytest
Includes an HTTP round-trip test against an in-process fake echo-server (auth, miss, store, hit, delete, fail-open).
Metadata
Release files for echospeaks 0.6.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| echospeaks-0.6.0.tar.gz | 41.9 kB | Without provenance | Details for echospeaks-0.6.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| echospeaks-0.6.0-py3-none-any.whl | Python 3 | none | any | Details for echospeaks-0.6.0-py3-none-any.whl |
Total release size: 72.5 kB
Release files / echospeaks-0.6.0.tar.gz
| Download URL | echospeaks-0.6.0.tar.gz |
|---|---|
| Size | 41.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
536428f5e4e5788ffb0445d77eb860b54955705a378122e806c5dab6fe45c1cf
|
|
BLAKE2b-256 checksum How to use checksums |
6a2aaa75e2d7a324030542c2a6150b9b61c7cd45f1a09d3f86b1eed3149ae2de
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.11.13 {"installer":{"name":"uv","version":"0.11.13","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Release files / echospeaks-0.6.0-py3-none-any.whl
| Download URL | echospeaks-0.6.0-py3-none-any.whl |
|---|---|
| Size | 30.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
2d4717d4659f8f0d0385b3a216c5d7fdeef0520f5d9cfc5d9dfbfb643e4c49ba
|
|
BLAKE2b-256 checksum How to use checksums |
077402fcd4728961e6c70273bfe2d86b4d15cffa1ad5c0342789649c10a398f4
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.11.13 {"installer":{"name":"uv","version":"0.11.13","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Security for echospeaks 0.6.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.