"""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]