Skip to main content

Memcat

A text-to-speech cache for Pipecat voice bots. When your agent says a line it has said before (or part of one), Memcat replays the stored audio instead of paying the provider to synthesize it again, and the audio starts in milliseconds.

Measured
Speech served from cache, scripted support calls (Harper Valley Bank, 1,446 calls) 54.0% of characters, 59.0% of responses
Same, typed support chats (ABCD, 10,042 chats) 32.4% of characters, 38.5% of responses
First audio of a sentence that opens with a cached clause, recorded ElevenLabs traffic 2–13 ms with Memcat vs 346 ms without (median, HTTP and WebSocket)
What a listener hears, recorded ElevenLabs traffic, cached and live clauses mixed the same words in the same order: 0.3% word error vs 0.0% without Memcat, by speech recognition
First audio on a hit, local Kokoro voice 1.0 ms from memory, 10 ms from Redis (vs 745 ms live)
Added time on a miss none measurable: 488 ms vs 495 ms without Memcat (median, within run-to-run noise)
Storage per cached minute 464 KB at 8 kHz (mu-law), 236 KB at 24 kHz (Opus)
Cache keys containing a digit (order numbers, prices, dates, phone numbers) 0 on all corpora
Redis killed mid-call every turn spoken, no error reaches the pipeline

Hit rates come from replaying public dialogue corpora through Memcat's own chunker, privacy rules and admission policy (bench/hitrate.py); no TTS was called. Net of what filling the cache costs, savings are within 2 points of these (54.0% and 31.9%). LLM agents whose every sentence carries an order number or a price cache less: 6–17% of characters on τ²-bench traces, where those clauses stay live. The ElevenLabs rows replay traffic recorded from the ElevenLabs API (Flash v2.5) through Pipecat's ElevenLabs services (bench/elevenlabs_replay.py). The recordings are not in the repository; anyone with an ElevenLabs key can make their own and rerun it.

Install

uv add memcat                      # or: pip install memcat
uv add "memcat[redis,opus]"        # shared cache across bot processes, compact audio

Python 3.11–3.13 and pipecat-ai 1.10–1.13. Extras: redis (RedisStore), opus (Opus storage), s3 (S3Store), all.

Use

from pipecat.services.cartesia.tts import CartesiaTTSService
from memcat import cached, MemcatConfig, RedisStore

CachedCartesia = cached(CartesiaTTSService)   # once, at import time

tts = CachedCartesia(
    api_key=CARTESIA_API_KEY,
    voice_id=VOICE_ID,
    memcat=MemcatConfig(store=RedisStore(url=REDIS_URL), namespace="acme-support"),
)

Use tts exactly as you used the unwrapped service. Nothing else in the pipeline changes. await tts.memcat_stats() reports hits, characters saved and dollars saved.

No API keys at hand? From a clone, uv run examples/kokoro_local.py speaks three lines twice with a local voice; the second time they come from the cache.

How it works

  1. Clauses, not just sentences. Each sentence Pipecat hands to the TTS is split at commas, semicolons, colons and dashes. "Thanks for waiting, your order 48213 has shipped, and it should arrive soon." is three clauses; the first and last are reusable, the middle one is spoken live.
  2. Privacy first. A clause with any digit, a number in words, an email, a URL, money, a date, a time or a spelled-out code is never cached. A clause is cached only after it has been heard in 3 distinct calls, so a line said in one call never enters the cache.
  3. One lookup per sentence. An in-process index answers misses with no network call; hits come from memory or one Redis MGET.
  4. Exact order on every provider. On HTTP services cached and live audio interleave inside one generator. On WebSocket services Memcat keeps Pipecat's audio context and gives each run of live clauses its own provider stream, releasing segments strictly in order, so cached clauses play before, between and after live ones. Transcripts and word timestamps stay correct.
  5. Joins matched to live speech. Clips are stored tight and Memcat sets the pause at each join the way live speech does (short after "Sure,", longer after a full clause), matches loudness, and fades edges. Against live synthesis of the same sentences, pauses differ by 45 ms at the median and loudness by at most 0.62 LU (bench/abx.py).
  6. Fill in the background. A clause admitted from a live run that synthesized it alone is stored as is, for free; others are rendered once by a background copy of the same service. Nothing on the call ever waits for the cache.

If the store, a codec or Memcat itself fails, the sentence goes to the provider as if Memcat were not installed: Memcat can lose savings, but the call goes on.

Providers

Provider WebSocket HTTP
Cartesia cached clauses anywhere in a sentence cached clauses anywhere
ElevenLabs cached clauses anywhere cached clauses anywhere, previous_text kept
Deepgram Aura cached clauses anywhere, within Deepgram's 20-flush/minute limit cached clauses anywhere
Sarvam cached clauses before the first live one cached clauses anywhere
Any other Pipecat TTS service cached clauses before the first live one (if it pushes its own start frames) cached clauses anywhere (if it returns audio inline)

Every listed provider is tested with its Pipecat service class against a local emulator of the provider's wire protocol (tests/emulators/). ElevenLabs is also tested against recordings of its API, replayed with their original timing (tests/test_replay_elevenlabs.py). To record your own, see CONTRIBUTING.md.

Configure

MemcatConfig(
    store=RedisStore("redis://..."),   # default: one MemoryStore per process; RedisStore, S3Store
    namespace="acme-support",          # tenant prefix on every key
    admit_after_calls=3,               # distinct calls before a clause is cached
    warm=["Thank you for calling ABC Shop, how can I help you today?"],  # cached at start
    block_patterns=[r"\bACME-\w+"],    # extra text that must never be cached
    ttl_s=30 * 24 * 3600,              # refreshed when served
    codec="auto",                      # mu-law at 8 kHz, Opus when installed, else PCM16
    enabled=True,                      # kill switch: False means a pure pass-through
)

All fields are described in docs/configuration.md.

Operate

memcat warm  --config memcat.toml --from calls.jsonl --top 500   # render your most repeated clauses
memcat stats --config memcat.toml                                # entries, minutes, codecs
memcat purge --config memcat.toml --text "..."                   # erase a text everywhere
memcat export --config memcat.toml --to backup/ ; memcat import --config memcat.toml --from backup/
memcat serve --config memcat.toml --port 8080                    # TTS endpoint for Vapi and other platforms

memcat serve puts the cache in front of hosted platforms that call a custom TTS URL (Vapi's custom voice): it answers with raw 16-bit PCM, streamed, and counts admission per platform call. It listens on 127.0.0.1 unless you set a secret (or pass --insecure-no-auth behind your own authentication).

Every command except serve and warm --dry-run needs a Redis or S3 [store]. purge --text erases the text and each of its clauses in every voice, setting and sample rate; purge --all asks for confirmation (or --yes).

See docs/operations.md for memcat.toml, metrics and OpenTelemetry.

Benchmarks

bench/ reproduces every measured number above: hitrate.py (ABCD, Harper Valley Bank, τ²-bench), elevenlabs_replay.py (recorded ElevenLabs traffic), latency.py (Kokoro + Redis), storage.py, hotpath.py (run nightly, which fails at 3x its baseline), soak.py (100 concurrent calls) and abx.py, which builds a 30-sentence listening test and measures each join against live speech. The Redis row is tests/test_store_outage.py. Results live in bench/results/.

License

BSD 2-Clause, the same as Pipecat. Keep one cache (or one namespace) per customer so audio is never shared across customers, and check your provider's terms for anything else.

Metadata

Release files for memcat 1.0.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 memcat 1.0.0
File Size Uploaded Provenance Actions
memcat-1.0.0.tar.gz 100.7 kB Without provenance Details for memcat-1.0.0.tar.gz

Built distribution (wheel)

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

Total release size: 214.8 kB

Release files / memcat-1.0.0.tar.gz

Download URL memcat-1.0.0.tar.gz
Size 100.7 kB
Tags Source
SHA-256 checksum
How to use checksums
a2ec085bbb0a138b8ef4a046dc4e5dcf205d48ef6db7e956f247807450dd0196
BLAKE2b-256 checksum
How to use checksums
4b26fba7d91e0254d5aa0b4afbdc912ce80d3d57344ad2790b02ac039e4cf4d0
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.13.3 {"installer":{"name":"uv","version":"0.13.3","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / memcat-1.0.0-py3-none-any.whl

Download URL memcat-1.0.0-py3-none-any.whl
Size 114.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
f008a86568c9ca9239d0c42294e8f27f12237bac208afc951f62474f092f655f
BLAKE2b-256 checksum
How to use checksums
6effb5f68c1b8c4312ca3ab262105233c1eeba57a23b6de2262412505c7aae65
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.13.3 {"installer":{"name":"uv","version":"0.13.3","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Security for memcat 1.0.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.

Release history Release notifications | RSS feed

This release

1.0.0 This release

2 release files No provenance

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