Skip to main content

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).

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's run_tts asks 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, Cartesia flush_audio with continue: 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
    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.
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).

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.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for echospeaks 0.2.0
File Size Uploaded Provenance Actions
echospeaks-0.2.0.tar.gz 19.1 kB Without provenance Details for echospeaks-0.2.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for echospeaks 0.2.0
File Interpreter ABI Platform Actions
echospeaks-0.2.0-py3-none-any.whl Python 3 none any Details for echospeaks-0.2.0-py3-none-any.whl

Total release size: 41.5 kB

Release files / echospeaks-0.2.0.tar.gz

Download URL echospeaks-0.2.0.tar.gz
Size 19.1 kB
Tags Source
SHA-256 checksum
How to use checksums
c21a8584f214722c339d34c3802abfda3a4a6d43235e3459b225d0c259e5edfd
BLAKE2b-256 checksum
How to use checksums
f17b0150b6222cb6dbcd3b4b3ce7a30eb7210675e66ea09fd8438cf8e96cbed1
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.9.5

Release files / echospeaks-0.2.0-py3-none-any.whl

Download URL echospeaks-0.2.0-py3-none-any.whl
Size 22.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
c2af0bc326a5331d2ed96bdc2c6b4985c219265103e79b03b0c976897f585001
BLAKE2b-256 checksum
How to use checksums
626a5df01fbb76305a1f72d1e487952b3cc282491543104db8d468aa6a5e1276
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.9.5

Security for echospeaks 0.2.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.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page