"""Tests for the LiveKit expression marker (expr) dialect.
The LLM emits a single marker tag — ```` (self-closing
for expression/break/sound, wrapping for prosody/spell) — and the framework lowers it
to each provider's native markup before synthesis while stripping it from transcripts.
The syntax is shared, but the kinds and label vocabularies are per provider: each
provider's instruction block advertises only what that provider supports.
"""
from __future__ import annotations
import pytest
from livekit.agents.llm import ChatContext, ChatMessage
from livekit.agents.tts._provider_format import (
TranscriptMarkupStripper,
convert_markup,
expression_attribute,
llm_instructions,
normalize_markup,
split_all_markup,
split_markup,
strip_expr_markup,
)
pytestmark = pytest.mark.unit
# Inworld-flavored turn: free-form expression + sound + break
JOKE = (
' Why did the burger go to the gym? '
' Because it wanted better buns! '
''
)
# ---------------------------------------------------------------------------
# convert_markup: expr -> xAI (sounds, breaks, wrapping prosody; no expression)
# ---------------------------------------------------------------------------
def test_convert_expr_xai() -> None:
text = (
'So I walked in and there it was! '
' '
'It was a secret the whole time.'
)
assert convert_markup("xai", text) == (
"So I walked in and [pause] there it was! [laugh] "
"It was a secret the whole time."
)
def test_convert_expr_xai_break_durations() -> None:
assert convert_markup("xai", '') == "[pause]"
assert convert_markup("xai", '') == "[long-pause]"
def test_convert_expr_xai_sound_alias() -> None:
# tolerance: an Inworld-style "breathe" label maps to xAI's native [breath] cue
assert convert_markup("xai", '') == "[breath]"
def test_convert_expr_xai_prosody_multiword_label() -> None:
# multi-word labels normalize to xAI's hyphenated tag names
text = 'no way'
assert convert_markup("xai", text) == "no way"
def test_convert_expr_xai_prosody_unknown_label_unwraps() -> None:
text = 'ahoy there'
assert convert_markup("xai", text) == "ahoy there"
def test_convert_expr_xai_drops_expression() -> None:
# xAI has no free-form delivery descriptions; a hallucinated expression marker is
# dropped from the audio path (it still surfaces in transcript tags)
text = ' Hello!'
assert convert_markup("xai", text) == " Hello!"
# ---------------------------------------------------------------------------
# convert_markup: expr -> Inworld (free-form expression, its sound list, breaks)
# ---------------------------------------------------------------------------
def test_convert_expr_inworld() -> None:
# expression/sound lower to Inworld's bracket syntax; stays native SSML
assert convert_markup("inworld", JOKE) == (
"[say playfully] Why did the burger go to the gym? "
' Because it wanted better buns! [laugh]'
)
def test_convert_expr_inworld_stray_prosody_becomes_expression_hint() -> None:
text = 'keep it secret'
assert convert_markup("inworld", text) == "[whisper]keep it secret"
# ---------------------------------------------------------------------------
# convert_markup: expr -> Cartesia (discrete emotions, breaks, spell; no sounds)
# ---------------------------------------------------------------------------
def test_convert_expr_cartesia() -> None:
text = (
' We won! '
' Unbelievable.'
)
# expression -> , break stays, sound is dropped (no Cartesia support)
assert convert_markup("cartesia", text) == (
' We won! Unbelievable.'
)
def test_convert_expr_cartesia_spell() -> None:
text = 'Your code is A7X9.'
assert convert_markup("cartesia", text) == "Your code is A7X9."
def test_convert_expr_spell_unwraps_elsewhere() -> None:
# spell is Cartesia-only; other providers keep the characters, drop the marker
text = 'Your code is A7X9.'
assert convert_markup("xai", text) == "Your code is A7X9."
assert convert_markup("inworld", text) == "Your code is A7X9."
def test_convert_expr_cartesia_prosody_point_controls() -> None:
# Cartesia prosody labels lower to its native speed/volume ratio tags
assert convert_markup("cartesia", ' One moment.') == (
' One moment.'
)
assert convert_markup("cartesia", ' We won!') == (
' We won!'
)
# wrapping form applies the control before the span
assert convert_markup("cartesia", 'bad news') == (
'bad news'
)
def test_convert_expr_cartesia_prosody_unknown_label_unwraps() -> None:
text = 'keep it secret'
assert convert_markup("cartesia", text) == "keep it secret"
def test_convert_stray_expr_never_reaches_tts() -> None:
# an unpaired prosody open/close (e.g. split across stream chunks) is dropped,
# keeping the words
assert convert_markup("xai", 'hello there') == "hello there"
assert convert_markup("xai", "hello there") == "hello there"
# ---------------------------------------------------------------------------
# transcript stripping (per-provider + provider-agnostic)
# ---------------------------------------------------------------------------
@pytest.mark.parametrize("provider", ["xai", "inworld", "cartesia"])
def test_split_markup_strips_expr(provider: str) -> None:
clean, tags = split_markup(provider, JOKE)
assert clean.strip() == "Why did the burger go to the gym? Because it wanted better buns!"
assert tags == [
{"type": "expression", "value": "say playfully"},
{"type": "break", "value": "500ms"},
{"type": "sound", "value": "laugh"},
]
def test_split_markup_wrapping_keeps_inner_text() -> None:
text = (
'She said keep it secret — '
'code A7X9.'
)
clean, tags = split_markup("xai", text)
assert clean == "She said keep it secret — code A7X9."
assert tags == [
{"type": "prosody", "value": "whisper"},
{"type": "spell", "value": ""},
]
def test_split_all_markup_mixed_expr_and_native() -> None:
text = ' Hello! [sigh]'
clean, tags = split_all_markup(text)
assert clean.strip() == "Hello!"
assert {"type": "expression", "value": "say playfully"} in tags
assert {"type": "sound", "value": "laugh"} in tags
assert {"type": "", "value": "sigh"} in tags
def test_expr_regex_does_not_match_native_expression_tag() -> None:
# " there.'
def test_transcript_stripper_streaming_chunks() -> None:
stripper = TranscriptMarkupStripper()
out = ""
# split mid-tag so the partial " Hello',
' wor',
"ld!",
]:
out += stripper.push(chunk)
out += stripper.flush()
assert out == " Hello world!"
assert stripper.tags[0] == {"type": "expression", "value": "say playfully"}
assert {"type": "prosody", "value": "whisper"} in stripper.tags
def test_expression_attribute_from_expr() -> None:
_, tags = split_markup("inworld", JOKE)
attr = expression_attribute(tags)
assert attr is not None
assert '"say playfully"' in next(iter(attr.values()))
# ---------------------------------------------------------------------------
# normalize_markup: fix unclosed self-closing expr markers
# ---------------------------------------------------------------------------
@pytest.mark.parametrize("provider", ["xai", "inworld", "cartesia"])
def test_normalize_closes_unclosed_expr(provider: str) -> None:
text = ' Hello'
assert normalize_markup(provider, text) == ' Hello'
def test_normalize_leaves_wrapping_and_closed_tags_alone() -> None:
text = (
'hi '
'A7X9'
)
assert normalize_markup("xai", text) == text
# ---------------------------------------------------------------------------
# llm instructions: shared syntax, per-provider kinds and vocabularies
# ---------------------------------------------------------------------------
@pytest.mark.parametrize("provider", ["xai", "inworld", "cartesia"])
def test_llm_instructions_use_expr_syntax(provider: str) -> None:
instructions = llm_instructions(provider)
assert instructions is not None
assert "' in instructions
assert "NOT free-form" in instructions
assert '' in instructions
# coarse self-closing prosody point controls
assert '' in instructions
# no non-verbal sounds
assert 'type="sound"' not in instructions
def test_llm_instructions_inworld_kinds() -> None:
instructions = llm_instructions("inworld")
assert instructions is not None
# free-form delivery descriptions + Inworld's own sound list
assert '' in instructions
assert "free-form" in instructions
assert "clear throat" in instructions
# no wrapping prosody, no spell
assert 'type="prosody"' not in instructions
assert 'type="spell"' not in instructions
def test_llm_instructions_xai_kinds() -> None:
instructions = llm_instructions("xai")
assert instructions is not None
# xAI's own sound cues + wrapping prosody vocabulary
assert "tongue-click" in instructions
assert '' in instructions
assert "sing-song" in instructions
# no free-form delivery descriptions, no spell
assert 'type="expression"' not in instructions
assert 'type="spell"' not in instructions
def test_llm_instructions_none_for_unknown_provider() -> None:
assert llm_instructions("") is None
assert llm_instructions("openai") is None
# ---------------------------------------------------------------------------
# strip_expr_markup + ChatMessage.text_content / raw_text_content
# ---------------------------------------------------------------------------
# assistant text mixing expr markers with content that must survive an expr-only strip:
# provider-native tags, bracket spans, markdown links, and stray angle brackets
MIXED = (
' Press [Enter] to see bold, '
'read [the docs](https://docs.livekit.io), then 1 < 2. '
'keep it secret'
)
MIXED_CLEAN = (
" Press [Enter] to see bold, "
'read [the docs](https://docs.livekit.io), then 1 < 2. '
"keep it secret"
)
def test_strip_expr_markup_only_touches_expr() -> None:
assert strip_expr_markup(MIXED) == MIXED_CLEAN
def test_strip_expr_markup_noop_without_expr() -> None:
text = 'plain text with [brackets] and '
assert strip_expr_markup(text) == text
def test_assistant_text_content_strips_expr_only() -> None:
msg = ChatMessage(role="assistant", content=[MIXED])
assert msg.text_content == MIXED_CLEAN
assert msg.raw_text_content == MIXED
@pytest.mark.parametrize("role", ["user", "system", "developer"])
def test_non_assistant_text_content_stays_raw(role: str) -> None:
# only assistant messages carry expressive markup; other roles are never stripped
msg = ChatMessage(role=role, content=[JOKE])
assert msg.text_content == JOKE
assert msg.raw_text_content == JOKE
def test_text_content_none_without_text() -> None:
msg = ChatMessage(role="assistant", content=[])
assert msg.text_content is None
assert msg.raw_text_content is None
def test_to_dict_strip_markup_is_expr_only_and_assistant_only() -> None:
chat_ctx = ChatContext.empty()
chat_ctx.add_message(role="user", content=[MIXED])
chat_ctx.add_message(role="assistant", content=[MIXED])
items = chat_ctx.to_dict(strip_markup=True)["items"]
assert items[0]["content"] == [MIXED] # user content untouched
assert items[1]["content"] == [MIXED_CLEAN] # assistant loses only expr tags
# default keeps the raw content for persistence
items = chat_ctx.to_dict()["items"]
assert items[1]["content"] == [MIXED]