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. The tier
is enabled by passing an api_key in cache_config; without it the wrapped
service runs fully stock (no caching, no per-sentence contexts). To get a key,
visit echospeaks.ai and contact us.
Layout
echospeaks/
defaults.py every SDK-wide tunable default, in one place
common/ shared dataclasses (CachedAudio, AudioMeta) + logger shim
cache/ keying (blake2b-128 cache keys + text normalization) and
remote.py - the echo-server client (the one audio store)
pipecat_tts/ pipeline integration - mixin.py (read), capture.py (write),
pending.py (read→write handoff); needs pipecat-ai
How it works
Two pipeline pieces, wired around the host's existing TTS service:
- Read -
EnableTTSCache(echospeaks.pipecat_tts.mixin): drop-in replacement for instantiating the provider class. Every sentence'srun_ttsasks echo-server (0.5s bounded) - a hit replays the cached PCM + word timestamps with zero provider traffic (barge-in aware); a miss synthesizes live and marks the sentence for capture. - Write -
TTSCacheCapture(echospeaks.pipecat_tts.capture): a pass-through processor placed right after the TTS service in the pipeline. It buffers each miss-marked sentence's audio + word timings and fire-and-forgets a POST to echo-server (audio base64-encoded inside the v1 JSON envelope; decoded on fetch). Interruptions discard partial buffers, so truncated audio is never stored.
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
- a dead cache server costs money, never quality. The 4xx
invalid_*family is logged as an error instead of swallowed (it means this library sent something malformed).
Cache keys (echospeaks.cache.keying) are blake2b-128 hex (32 chars - the
audio_key format echo-server validates) over normalized text + provider +
voice + model + sample rate, so whitespace/casing never causes a false miss
and two voices can never serve each other's audio. Normalization is for key
matching only - synthesis always uses the real text.
Provider specifics handled by the wrapper:
- One provider context per sentence, closed right after its text is sent
(dialect-detected: ElevenLabs
_close_context, Cartesiaflush_audiowithcontinue: false; close is graceful - audio flushes, then the final). - A client-side gate caps simultaneous contexts (default 4 - ElevenLabs kills the socket at >5). Slots free on the context's final, interruption, or websocket reconnect.
- Cache hits take no context and no gate slot; the replayed context is completed explicitly so the sentence serializer never stalls.
Install
pip install echospeaks # core (no pipecat needed)
pip install "echospeaks[pipecat]" # with the pipecat pipeline integration
Usage
from pipecat.services.elevenlabs.tts import ElevenLabsTTSService
from echospeaks.pipecat_tts import EnableTTSCache, TTSCacheCapture
# read side: instead of ElevenLabsTTSService(...). Works the same for any
# subclass of a supported provider service.
tts = EnableTTSCache(ElevenLabsTTSService,
api_key=ELEVENLABS_API_KEY, # the TTS provider's key
cache_config={
"api_key": ECHO_SERVER_API_KEY, # echo-server key: caching on
"tag": AGENT_ID, # optional: label stores for filtered listings
},
settings=ElevenLabsTTSService.Settings(model="eleven_turbo_v2_5", voice=VOICE_ID),
)
# or Cartesia:
from pipecat.services.cartesia.tts import CartesiaTTSService
tts = EnableTTSCache(CartesiaTTSService,
api_key=CARTESIA_API_KEY,
cache_config={"api_key": ECHO_SERVER_API_KEY},
settings=CartesiaTTSService.Settings(model="sonic-3", voice=VOICE_ID),
)
# write side: right after the TTS service in the pipeline.
pipeline = Pipeline([..., tts, TTSCacheCapture(), transport.output(), ...])
The wrapped service exposes get_and_reset_turn_stats() and
log_cache_summary(call_sid=...) for call logs. Skip both pieces and the
provider runs fully stock.
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 (Authorization: Bearer). Setting it enables the cache; unset = caching disabled, service runs fully stock. Process-wide. Visit echospeaks.ai to contact us and get a key. |
max_concurrent_context |
4 |
Gate cap on simultaneous provider contexts per service. ElevenLabs hard-kills the socket at >5 (per websocket); Cartesia's limit is account-wide per plan - size this to plan_concurrency ÷ concurrent calls. |
sentence_wait_timeout_s |
3.0 |
Longest a sentence waits at a full gate before stale slots are reclaimed (a context's final normally arrives 150–300 ms after close). |
replay_chunk_bytes |
960 |
Hit-replay slice size (~30 ms @ 16 kHz mono s16le). Smaller = finer barge-in stop granularity, more frame overhead. Sample-aligned and clamped automatically. |
pending_max_entries |
512 |
Bound on the read→write capture handoff registry (process-wide; entries for contexts that never finish age out). |
tag |
(unset) | Optional grouping label (an agent id, a campaign, a tenant of your own) attached to every store. echo-server records it per entry, so listings can be filtered with GET /stats/{l1|l2}?tag=. It is NOT part of the cache key: the same sentence stays one shared entry no matter which tag stored it (the latest store's tag wins). Normalized to lowercase [a-z0-9-], max 32 chars; an unusable tag is dropped with a warning, never an error. |
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.2.3
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.2.3.tar.gz | 20.0 kB | Without provenance | Details for echospeaks-0.2.3.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| echospeaks-0.2.3-py3-none-any.whl | Python 3 | none | any | Details for echospeaks-0.2.3-py3-none-any.whl |
Total release size: 43.4 kB
Release files / echospeaks-0.2.3.tar.gz
| Download URL | echospeaks-0.2.3.tar.gz |
|---|---|
| Size | 20.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
3500275cfffa7afb32d297c5c178bf49788789e51ac8c56d1cb6e786e5a6c43b
|
|
BLAKE2b-256 checksum How to use checksums |
94bbce7f267908e35c7ea8beda6837b04bdcb9bb36be1177936f9f362d2524a9
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.9.5
|
Release files / echospeaks-0.2.3-py3-none-any.whl
| Download URL | echospeaks-0.2.3-py3-none-any.whl |
|---|---|
| Size | 23.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
cc6caf9529815b0b177e251ed28d3478da2e776392fa738013d5809db13896bd
|
|
BLAKE2b-256 checksum How to use checksums |
95677b6a0dabbc0761e4e395eb76f315a141cdd2aae8ee9bb878ae47d3b8a9ed
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.9.5
|
Security for echospeaks 0.2.3
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.