Skip to main content

Speakable

Text normalization for voice agents. Turn LLM output into text a TTS engine speaks correctly.

Your LLM returns this:

**Invoice #42**: $1,234.56 due 2026-11-11. Call Dr. Rao at (555) 123-4567
before 3:30 PM. Save 15%! 🎉

Your TTS engine says "asterisk asterisk invoice hash forty two asterisk asterisk colon dollar one comma two three four point five six..."

Speakable returns this:

Invoice number forty-two: one thousand two hundred thirty-four dollars and
fifty-six cents due November eleventh, twenty twenty-six. Call Doctor Rao at
five five five one two three four five six seven before three thirty PM.
Save fifteen percent!

Pure Python, zero dependencies in the core, ~1.5 ms for a typical utterance.

What it handles

Input Output
**bold**, # headings, `code`, [link](url) stripped to plain text
$1,234.56 / ₹500 / £20 / €99 one thousand two hundred thirty-four dollars and fifty-six cents
2026-11-11, 11/11/2026, Nov 11, 2026 November eleventh, twenty twenty-six
3:30 PM, 9:05 a.m., 14:00 three thirty PM, nine oh five AM, two o'clock PM
(555) 123-4567, +1 555-123-4567 digit-by-digit, the way a human reads a number aloud
15%, 2.5% fifteen percent, two point five percent
1st, 21st, 3rd first, twenty-first, third
5kg, 10mi, 3hrs five kilograms, ten miles, three hours
Dr., Inc., vs., etc. Doctor, Incorporated, versus, et cetera
john@example.com, https://acme.io/help john at example dot com, acme dot io
🎉 and friends removed
API (opt-in) A P I, skipping acronyms TTS already says right
10-20, 1000-2000 ten to twenty (ranges, not phone numbers)
-45 negative forty-five
192.168.1.1, v1.2.3 one hundred ninety-two dot ..., one point two point three
**unclosed bold unpaired markers dropped, not spoken
3/4, 5/16 three quarters, five over sixteen

Library use

from speakable import normalize

normalize("Pay $20 by 2026-01-05.")
# 'Pay twenty dollars by January fifth, twenty twenty-six.'

normalize("The API is up", spell_acronyms=True)
# 'The A P I is up'

normalize(text, spell_acronyms=False, strip_emoji=True) is the whole API. It is idempotent — running it twice changes nothing.

Use it in a LiveKit agent

The one line that matters, right before synthesis:

from speakable import normalize

async def on_llm_response(text: str):
    await tts.say(normalize(text))

HTTP API

pip install -r requirements.txt
uvicorn app:app --reload          # docs at http://127.0.0.1:8000/docs
curl -X POST http://127.0.0.1:8000/normalize \
  -H 'Content-Type: application/json' \
  -d '{"text": "Pay $20 by 2026-01-05."}'
{
  "text": "Pay twenty dollars by January fifth, twenty twenty-six.",
  "original": "Pay $20 by 2026-01-05.",
  "chars_in": 22,
  "chars_out": 54,
  "took_ms": 0.9
}

POST /normalize/batch takes {"texts": [...]} for up to 100 strings at once. GET /health for liveness.

Set RAPIDAPI_PROXY_SECRET to reject any traffic that didn't come through the marketplace proxy. MAX_CHARS and MAX_BATCH bound request size.

Docker

docker build -t speakable .
docker run -p 8000:8000 speakable

Tests

python tests/test_normalize.py     # 28 cases, no pytest required

Why this exists

Every voice agent hits this. The LLM is trained on written English and emits markdown, digits and abbreviations; TTS engines are built for prose. The gap between them is where callers hear "asterisk" out loud. Most teams end up with a pile of ad-hoc regexes that handle their own demo and break on the next one.

This is that pile, written once, tested, and ordered correctly — the ordering is most of the difficulty. Dates have to run before phone numbers, because 2026-11-11 is also digits-and-hyphens. Emails have to run before anything touches a dot. Currency has to run before plain integers.

Not handled (yet)

Non-English locales, Indian digit grouping (lakh/crore), roman numerals, and mathematical expressions. PRs welcome.

License

MIT.

Metadata

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

Built distribution (wheel)

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

Total release size: 22.5 kB

Release files / speakable-1.0.0.tar.gz

Download URL speakable-1.0.0.tar.gz
Size 12.5 kB
Tags Source
SHA-256 checksum
How to use checksums
30a921f7792bd4768dd2f612f1dda3132554dd7f32e0ac163dee19b264037c06
BLAKE2b-256 checksum
How to use checksums
7634a60c11ea54632b55daaddb6c087777242b95040d4c9de088472098112ae2
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.1

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

Download URL speakable-1.0.0-py3-none-any.whl
Size 9.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
f88dfedc63f85b67ca7275373f5ae1fdbebad0e704c1d722dbbfa49c1d0e03b3
BLAKE2b-256 checksum
How to use checksums
80315de64c67bca1ec8dca95428a9b47775fcdd80cd89fa0c1a5ee5d4be96d16
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.1

Security for speakable 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