Skip to content

Add expressive expr marker dialect#1988

Open
rosetta-livekit-bot[bot] wants to merge 1 commit into
mainfrom
guest-ravine-iguana
Open

Add expressive expr marker dialect#1988
rosetta-livekit-bot[bot] wants to merge 1 commit into
mainfrom
guest-ravine-iguana

Conversation

@rosetta-livekit-bot

@rosetta-livekit-bot rosetta-livekit-bot Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Summary

  • add shared TTS provider formatting for LiveKit <expr/> expression markers
  • gate expressive prompt injection behind expressive options on AgentSession/Agent
  • lower markers before inference, Cartesia, and Inworld TTS provider calls and strip markers from committed assistant text

Verification

  • pnpm build:agents
  • pnpm exec eslint "agents/src/tts/provider_format.ts" "agents/src/inference/tts.ts" "agents/src/voice/agent.ts" "agents/src/voice/agent_activity.ts" "agents/src/voice/agent_session.ts" "agents/src/voice/index.ts" "agents/src/voice/turn_config/utils.ts" "plugins/cartesia/src/tts.ts" "plugins/inworld/src/tts.ts" (warnings only from existing rules/patterns)
  • local node smoke check for Cartesia/Inworld/xAI conversion and transcript stripping
  • cue-cli voice-mode runtime check with temporary agent expr-marker-js, asserting assistant framework event did not contain raw <expr/> markers

Notes

  • pnpm --filter @livekit/agents-plugin-cartesia --filter @livekit/agents-plugin-inworld build built JS bundles; Inworld completed fully, Cartesia declaration generation failed on existing missing test-only workspace deps (@livekit/agents-plugin-silero, @livekit/agents-plugins-test, @livekit/agents-plugin-openai).
  • pnpm --filter @livekit/agents api:check failed before API analysis because the current API Extractor does not support the existing export * as ... syntax in agents/dist/index.d.ts.

Ported from livekit/agents#6347

Original PR description

Replace the provider-native markup dialects with a single marker tag — the only dialect the LLM is ever taught, everywhere (llm_instructions() and the expressive preset bodies alike):

  <expr type="expression" label="say playfully"/>
  <expr type="break" label="500ms"/>
  <expr type="sound" label="laugh"/>
  <expr type="prosody" label="whisper">wrapped words</expr>
  <expr type="spell">A7X9</expr>

The marker syntax is shared, but each provider gets its own instruction block advertising only the types and label vocabularies it actually supports:

  • Cartesia: expression with its discrete emotion vocabulary (NOT free-form), break, self-closing prosody point controls (slow/fast/soft/loud -> coarse speed/volume ratios), and a wrapping spell type; no sounds
  • Inworld: free-form expression descriptions, its own sound list (laugh/sigh/breathe/clear throat/cough/yawn), break; no prosody
  • xAI: its own sound cues (chuckle/tsk/tongue-click/...), break, and wrapping prosody from its native tag list; no free-form expression

The provider-native instruction blocks are deleted; the six preset bodies are rewritten in expr. The native tag tables remain solely as tolerance — a hallucinated native tag is still stripped from the transcript and converted for the TTS instead of leaking.

convert_markup lowers expr to each provider's native syntax before synthesis and drops types a provider doesn't support — the words always survive, the marker never leaks. normalize_markup closes unclosed self-closing markers, and the transcript strippers remove expr in a dedicated pre-pass so the type/label pair surfaces as an ExpressiveTag.

@rosetta-livekit-bot rosetta-livekit-bot Bot requested a review from a team as a code owner July 7, 2026 21:29
@changeset-bot

changeset-bot Bot commented Jul 7, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 650417e

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 36 packages
Name Type
@livekit/agents Patch
@livekit/agents-plugin-cartesia Patch
@livekit/agents-plugin-inworld Patch
@livekit/agents-plugin-anam Patch
@livekit/agents-plugin-anthropic Patch
@livekit/agents-plugin-assemblyai Patch
@livekit/agents-plugin-baseten Patch
@livekit/agents-plugin-bey Patch
@livekit/agents-plugin-cerebras Patch
@livekit/agents-plugin-deepgram Patch
@livekit/agents-plugin-did Patch
@livekit/agents-plugin-elevenlabs Patch
@livekit/agents-plugin-fishaudio Patch
@livekit/agents-plugin-google Patch
@livekit/agents-plugin-hedra Patch
@livekit/agents-plugin-hume Patch
@livekit/agents-plugin-lemonslice Patch
@livekit/agents-plugin-liveavatar Patch
@livekit/agents-plugin-livekit Patch
@livekit/agents-plugin-minimax Patch
@livekit/agents-plugin-mistral Patch
@livekit/agents-plugin-mistralai Patch
@livekit/agents-plugin-neuphonic Patch
@livekit/agents-plugin-openai Patch
@livekit/agents-plugin-perplexity Patch
@livekit/agents-plugin-phonic Patch
@livekit/agents-plugin-resemble Patch
@livekit/agents-plugin-rime Patch
@livekit/agents-plugin-runway Patch
@livekit/agents-plugin-sarvam Patch
@livekit/agents-plugin-silero Patch
@livekit/agents-plugin-soniox Patch
@livekit/agents-plugin-tavus Patch
@livekit/agents-plugins-test Patch
@livekit/agents-plugin-trugen Patch
@livekit/agents-plugin-xai Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 potential issue.

Open in Devin Review

(_match, _tag, _attrs, inner: string | undefined) => inner ?? '',
);
clean = clean.replace(new RegExp(`<\\/(?:${tagPattern})\\s*>`, 'g'), '');
return clean.replace(/\[[^\]]+\]/g, '');

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔍 stripAllMarkup removes all square-bracket content, which may strip legitimate text

The stripAllMarkup function at agents/src/tts/provider_format.ts:238 ends with .replace(/\[[^\]]+\]/g, '') which strips ALL [...] content from the text. This is used to clean assistant messages before storing them in chat context (agent_activity.ts:2652, agent_activity.ts:3102, agent_activity.ts:3148). If the LLM produces legitimate square-bracket content (e.g. [1] citations, [link text](url) markdown, or literal brackets), that content will be silently removed from the conversation history. This only affects users who enable the expressive option, since stripExpressiveMarkup gates on expressiveInstructions returning a truthy value. The risk is low since markdown filtering is typically already applied, but worth being aware of.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants