transforms

Built-in text transformers for TTS voice formatting.

Transformers are async callables with signature:

async def transform(text: str, aggregation_type: str) -> str

They are registered with a TTS service via text_transforms:

tts = CartesiaTTSService(
    text_transforms=[("*", strip_markdown), ("*", expand_currency)],
)

Or use VoiceFormatter for a single configurable bundle:

tts = CartesiaTTSService(
    text_transforms=[("*", VoiceFormatter(expand_currency=True))],
)
async pipecat.utils.text.transforms.normalize_acronyms(text: str, aggregation_type: str | AggregationType) str[source]

Insert spaces between letters of uppercase acronyms.

This transformer is alphanumeric-preserving: the same letters are kept, only spaces are added between them, so the WordCompletionTracker requires no segment-map overhead.

Parameters:
  • text – Input text possibly containing acronyms like API or HTTP.

  • aggregation_type – Aggregation type of the text frame (unused).

Returns:

Text with acronyms letter-spaced (e.g. "API""A P I").

Example:

result = await normalize_acronyms("Use the API or HTTP endpoint", "*")
# "Use the A P I or H T T P endpoint"
async pipecat.utils.text.transforms.expand_currency(text: str, aggregation_type: str | AggregationType) str[source]

Expand currency amounts to their spoken form.

Parameters:
  • text – Input text possibly containing currency expressions.

  • aggregation_type – Aggregation type of the text frame (unused).

Returns:

Text with currency amounts replaced by spoken equivalents.

Example:

result = await expand_currency("Your balance is $42.50", "*")
# "Your balance is forty-two dollars and fifty cents"
async pipecat.utils.text.transforms.normalize_dates(text: str, aggregation_type: str | AggregationType) str[source]

Expand date expressions to their spoken form.

Handles ISO format (YYYY-MM-DD) and US format (MM/DD/YYYY or MM-DD-YYYY).

Parameters:
  • text – Input text possibly containing date expressions.

  • aggregation_type – Aggregation type of the text frame (unused).

Returns:

Text with date expressions replaced by spoken equivalents.

Example:

result = await normalize_dates("Meeting on 2023-05-10", "*")
# "Meeting on May 10th, two thousand and twenty three"
async pipecat.utils.text.transforms.email_to_speech(text: str, aggregation_type: str | AggregationType) str[source]

Transform email addresses into their spoken form.

Parameters:
  • text – Input text possibly containing email addresses.

  • aggregation_type – Aggregation type of the text frame (unused).

Returns:

Text with email addresses replaced by spoken equivalents.

Example:

result = await email_to_speech("Contact user@example.com today", "*")
# "Contact user at example dot com today"
pipecat.utils.text.transforms.expand_numbers(digit_cutoff: int | None = 2025) Callable[[str, str | AggregationType], object][source]

Return a transform that expands numbers to their spoken form.

When digit_cutoff is set, numbers above it are read digit-by-digit (e.g. "2026""2 0 2 6"). Numbers at or below the cutoff are expanded as quantities (e.g. "42""forty two"). Pass None to expand all numbers as words regardless of magnitude.

Parameters:

digit_cutoff – Numbers larger than this value are read digit-by-digit. None disables the cutoff so every number is expanded as a word.

Returns:

An async transform callable compatible with text_transforms.

Example:

transform = expand_numbers(digit_cutoff=2025)
result = await transform("Room 42 has 1234 seats and opens in 2026", "*")
# "Room forty two has one thousand two hundred thirty four seats and opens in 2 0 2 6"
async pipecat.utils.text.transforms.expand_percentages(text: str, aggregation_type: str | AggregationType) str[source]

Expand percentage expressions to their spoken form.

Parameters:
  • text – Input text possibly containing percentage expressions.

  • aggregation_type – Aggregation type of the text frame (unused).

Returns:

Text with percentages replaced by spoken equivalents.

Example:

result = await expand_percentages("50% off today", "*")
# "fifty percent off today"
async pipecat.utils.text.transforms.expand_phone_numbers(text: str, aggregation_type: str | AggregationType) str[source]

Space out phone number digits so TTS reads them individually.

This transformer is alphanumeric-preserving: digits are kept, only separators change to spaces, so the WordCompletionTracker requires no segment-map overhead.

Parameters:
  • text – Input text possibly containing phone numbers.

  • aggregation_type – Aggregation type of the text frame (unused).

Returns:

Text with phone numbers replaced by space-separated digit sequences.

Example:

result = await expand_phone_numbers("Call 123-456-7890 now", "*")
# "Call 1 2 3 4 5 6 7 8 9 0 now"
pipecat.utils.text.transforms.replace_text(replacements: list[tuple[str, str]]) Callable[[str, str | AggregationType], object][source]

Return a transform that applies a list of find-and-replace rules.

Each rule is a (pattern, replacement) tuple. Patterns are treated as regular expressions; use re.escape(pattern) for literal string matching.

Rules are applied in the order provided. Whether the resulting transform is alphanumeric-preserving depends on the replacements supplied.

Patterns are compiled at construction time so invalid regex patterns raise re.error immediately rather than during live TTS processing.

Parameters:

replacements – Ordered list of (regex_pattern, replacement_string) pairs.

Returns:

An async transform callable compatible with text_transforms.

Example:

transform = replace_text([
    (r"\bDr\.", "Doctor"),
    (r"\bSt\.", "Street"),
    (r"\bvs\b", "versus"),
])
tts = CartesiaTTSService(text_transforms=[("*", transform)])
async pipecat.utils.text.transforms.strip_markdown(text: str, aggregation_type: str | AggregationType) str[source]

Remove Markdown formatting symbols that have no spoken equivalent.

Strips bold/italic markers, backtick code spans, fenced code blocks, ATX headers, and blockquote markers. Does not modify link or image syntax since those may carry meaningful text (the link label is preserved verbatim).

This transformer is alphanumeric-preserving: the normalized alnum sequence of the output is identical to the input, so the WordCompletionTracker requires no segment-map overhead.

Parameters:
  • text – Input text, potentially containing Markdown formatting.

  • aggregation_type – Aggregation type of the text frame (unused).

Returns:

Text with Markdown formatting symbols removed.

Example:

result = await strip_markdown("**Hello** and _world_", "*")
# "Hello and world"
async pipecat.utils.text.transforms.expand_units(text: str, aggregation_type: str | AggregationType) str[source]

Expand unit abbreviations to their full spoken form.

Parameters:
  • text – Input text possibly containing unit expressions.

  • aggregation_type – Aggregation type of the text frame (unused).

Returns:

Text with unit abbreviations replaced by spoken equivalents.

Example:

result = await expand_units("Run 5km at 100kph", "*")
# "Run 5 kilometers at 100 kilometers per hour"
class pipecat.utils.text.transforms.VoiceFormatter(*, strip_markdown: bool = True, expand_phone_numbers: bool = True, normalize_acronyms: bool = True, expand_currency: bool = True, expand_numbers: bool = False, number_digit_cutoff: int | None = None, expand_percentages: bool = True, expand_units: bool = True, email_to_speech: bool = True, normalize_dates: bool = True, custom_replacements: list[tuple[str, str]] | None = None)[source]

Bases: object

Configurable bundle that applies a pipeline of voice-formatting transforms.

Each option enables or disables one transform. Transforms are applied in a deliberate order: structural cleanup first, language expansions second, user replacements last.

Example:

formatter = VoiceFormatter(
    strip_markdown=True,
    expand_currency=True,
    number_digit_cutoff=2025,
    custom_replacements=[(r"\bDr\.", "Doctor")],
)
tts = CartesiaTTSService(
    text_transforms=[("*", formatter)],
)
__init__(*, strip_markdown: bool = True, expand_phone_numbers: bool = True, normalize_acronyms: bool = True, expand_currency: bool = True, expand_numbers: bool = False, number_digit_cutoff: int | None = None, expand_percentages: bool = True, expand_units: bool = True, email_to_speech: bool = True, normalize_dates: bool = True, custom_replacements: list[tuple[str, str]] | None = None)[source]

Initialize the voice formatter.

Parameters:
  • strip_markdown – Strip Markdown formatting symbols (bold, italic, headers, code spans). Enabled by default.

  • expand_phone_numbers – Space out phone number digits for individual pronunciation. Enabled by default.

  • normalize_acronyms – Space out uppercase acronyms (e.g. "API""A P I"). Enabled by default.

  • expand_currency – Expand currency amounts to spoken form (e.g. "$42.50""forty two dollars and fifty cents"). Requires num2words.

  • expand_numbers – Expand numeric digits to spoken words. Disabled by default since it can affect numbers that are better read as digits. Requires num2words.

  • number_digit_cutoff – Numbers above this value are read digit-by-digit instead of as a quantity. Defaults to None (expand all numbers as words). Only used when expand_numbers=True.

  • expand_percentages – Expand percentage expressions (e.g. "50%""fifty percent"). Requires num2words.

  • expand_units – Expand unit abbreviations (e.g. "5km""5 kilometers"). Enabled by default.

  • email_to_speech – Transform email addresses to spoken form. Enabled by default.

  • normalize_dates – Expand date expressions to spoken form. Requires num2words.

  • custom_replacements – List of (regex_pattern, replacement) pairs applied after all other transforms.

Submodules