A lightweight, explainable semantic TTS normalizer for Python apps and voice-agent pipelines.
Project description
Utterwise
Utterwise is a lightweight semantic text normalizer for Python voice assistants, TTS apps, tutoring tools, and small developer projects.
It exists because compact TTS models often read useful assistant text badly: versions, dates, money, temperatures, URLs, formulas, and ambiguous numbers can sound strange unless they are normalized first.
from utterwise import normalize
normalize("Call 911 if it reaches 25°C on 03/04/2026.")
# Output:
# "Call nine one one if it reaches twenty five degrees Celsius on third of April twenty twenty six"
Install
pip install utterwise
Math and LaTeX parser support is optional:
pip install "utterwise[math]"
For local development with uv:
.venv\Scripts\uv.exe sync --extra dev --extra math
Quick Start
from utterwise import explain, explain_pretty, normalize, normalize_ssml
normalize("Python 3.12 costs $12.50 on Jan 3, 2026")
normalize_ssml("hello@example.com")
explain("Flight 911 departs at 6")
print(explain_pretty("Call 911 immediately"))
Examples
| Input | Output |
|---|---|
Born in 1998 |
Born in nineteen ninety eight |
The value is 1998 |
The value is one thousand nine hundred ninety eight |
Call 911 immediately |
Call nine one one immediately |
Flight 911 departs at 6 |
Flight nine eleven departs at six |
Python 3.12 |
Python three point twelve |
25°C |
twenty five degrees Celsius |
$12.50 |
twelve dollars and fifty cents |
03/04/2026 |
third of April twenty twenty six |
https://openai.com |
open ai dot com |
x^2 |
x squared |
\frac{a+b}{c} |
a plus b over c |
Why Not Just Regex?
Regex can replace symbols, but it cannot reliably decide what something means. Utterwise keeps candidates and uses context before verbalizing.
Call 911 immediately -> nine one one
Flight 911 departs at 6 -> nine eleven
911 divided by 3 is 303 -> nine hundred eleven
That distinction is the point of the project: small, deterministic, explainable normalization before text reaches a speech model.
Supported Features
| Feature | Status | Example |
|---|---|---|
| Numbers and years | Supported | 2024, 42 |
| Contextual ambiguity | Supported | 911, 1998 |
| URLs and emails | Supported | https://openai.com, hello@example.com |
| Versions | Supported | Python 3.12, v3.12 |
| Phones | Supported | 9876543210, +1-800-555-0100 |
| Acronyms | Supported | NASA, HTTP |
| Percentages | Supported | 12.5% |
| Temperatures | Supported | 25°C, 98.6°F |
| Currency | Supported | $12.50, €45, £9.99 |
| Dates | Supported | Jan 3, 2026, 2026-04-03, 03/04/2026 |
| Math and LaTeX | Optional extra | x^2, \sqrt{x+1} |
| SSML | Minimal | <speak>...</speak> |
Slash dates use day/month/year by default.
Explain Mode
from utterwise import explain
explain("Call 911 immediately")
Returns a dictionary with the normalized output, detection flags, token spans, candidates, winner, rule chain, signals, confidence, and metadata.
For humans, use:
from utterwise import explain_pretty
print(explain_pretty("Call 911 immediately"))
Example shape:
raw type spoken rule conf
----------- ----- ------------- ----------------------- ----
Call WORD Call identity 1.00
911 PHONE nine one one verb_call_before_number 0.94
immediately WORD immediately identity 1.00
Runtime Configuration
Use NormalizeConfig or convenience keyword flags to disable optional semantic
normalizers at runtime.
from utterwise import NormalizeConfig, normalize
normalize("x^2", enable_math=False)
config = NormalizeConfig(enable_currency=False, enable_dates=False)
normalize("$12.50 on 03/04/2026", config=config)
CLI
utterwise "Call 911 immediately"
utterwise --ssml "hello@example.com"
utterwise --explain "Flight 911 departs at 6"
utterwise --pretty 'Python 3.12 costs $12.50'
Limitations
- Utterwise is deterministic and rule-based; confidence scores are rule confidence, not statistical probabilities.
- Date parsing uses day/month/year for slash dates.
- SSML output is currently minimal.
- Chemistry normalization, rich policy-specific SSML, and locale-aware date formatting are planned for later.
Development
Run tests with:
.venv\Scripts\uv.exe run --extra dev pytest
.venv\Scripts\python.exe -m pytest
Run the interactive development menu:
.venv\Scripts\python.exe tests\menu.py
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file utterwise-0.1.0.tar.gz.
File metadata
- Download URL: utterwise-0.1.0.tar.gz
- Upload date:
- Size: 25.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.16 {"installer":{"name":"uv","version":"0.11.16","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4d051d8fd4dca3ffaec968cc7fad01f18b5bdf023fb119d6efbb0d74b4abf945
|
|
| MD5 |
64e1f5f13ba710730ce4b369850fded8
|
|
| BLAKE2b-256 |
4d49b3b2109064cc92bc856e2768b522b63424e9a022a0f7e858196d3450484d
|
File details
Details for the file utterwise-0.1.0-py3-none-any.whl.
File metadata
- Download URL: utterwise-0.1.0-py3-none-any.whl
- Upload date:
- Size: 30.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.16 {"installer":{"name":"uv","version":"0.11.16","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
abd3c61d3e16700dfbc281556fa818dad4b34931247f7217639e57f26880d156
|
|
| MD5 |
731d84d3bad589b6f5010b6c9a5ab6d7
|
|
| BLAKE2b-256 |
87ef85bd058af6e97ca04914282135dbc14e61f753cf5f5318addeea00d81fc5
|