Skip to content

feat(expressive): expose expressive mode and add expressive agent example - #2245

Closed
rosetta-livekit-bot[bot] wants to merge 1 commit into
auditory-wedging-staidlyfrom
fracking-vulva-salute
Closed

feat(expressive): expose expressive mode and add expressive agent example#2245
rosetta-livekit-bot[bot] wants to merge 1 commit into
auditory-wedging-staidlyfrom
fracking-vulva-salute

Conversation

@rosetta-livekit-bot

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

Copy link
Copy Markdown
Contributor

Summary

  • expose ExpressiveOptions from the public voice API
  • normalize provider expression labels into stable moods and publish both on lk.expression
  • expand Fish Audio tones, emotions, sounds, aliases, and steering guidance while preserving bracket prose in transcripts
  • attach expression metadata when delta transcription streams open
  • adapt the expressive friend example with runtime voice selection, metadata fallback, participant attributes, adaptive interruption, and the source persona

Ports livekit/agents#6698 from exact aggregate commit 404a3b70.

Validation

  • pnpm build passed for all 37 workspace packages
  • pnpm lint passed for all 37 workspace packages with existing warnings
  • pnpm format:check passed
  • pnpm --filter @livekit/agents typecheck passed
  • focused expressive/core tests passed: 97/97
  • compiled expressive example startup passed: node ./examples/dist/expressive_agent.js --help
  • full agents suite: 1,308 passed, 2 skipped; 11 existing AMD timing/classification tests failed
  • full examples suite: 94 passed, 2 skipped; 3 existing nondeterministic LLM/handoff assertions failed, with 2 existing survey harness unhandled errors
  • repository-wide suite additionally encountered missing CEREBRAS_API_KEY, unavailable/corrupt Silero assets, and a Hugging Face network timeout
  • API Extractor is blocked by existing unsupported export * as declarations; the repository-wide command also reaches an existing plugin with no api-extractor.json
  • cue-cli is installed, but runtime validation could not dispatch because no LiveKit credentials or .env are available in this workspace
Source diff coverage
  1. .github/workflows/deploy-examples.yml - not applicable: target has no Jukebox or per-example Python deployment workflow.
  2. .github/workflows/evals.yml - not applicable: source only follows the Python drive-thru to drive_thru rename; target has no counterpart.
  3. examples/README.md - not applicable: target has no examples catalog counterpart.
  4. examples/drive_thru/.dockerignore - not applicable: rename-only and target has no per-example Docker context.
  5. examples/drive_thru/Dockerfile - not applicable: rename-only and target has no per-example Dockerfile convention.
  6. examples/drive_thru/README.md - adapted to examples/src/drive-thru/README.md, retaining target naming and TypeScript setup.
  7. examples/drive_thru/agent.py - adapted to examples/src/drive-thru/drivethru_agent.ts; the target retains its idiomatic directory and provider APIs.
  8. examples/drive_thru/bg_noise.mp3 - not applicable: rename-only asset not used by the target implementation.
  9. examples/drive_thru/database.py - adapted to examples/src/drive-thru/database.ts.
  10. examples/drive_thru/order.py - adapted to examples/src/drive-thru/order.ts.
  11. examples/drive_thru/pyproject.toml - not applicable: target examples share examples/package.json.
  12. examples/drive_thru/test_agent.py - adapted to examples/src/drive-thru/test_agent.test.ts.
  13. examples/expressive_agent/.dockerignore - not applicable: target examples are not independent deployment units.
  14. examples/expressive_agent/Dockerfile - not applicable: Python and uv deployment infrastructure has no target counterpart.
  15. examples/expressive_agent/README.md - adapted to examples/src/expressive_agent/README.md with JS commands, dispatch name, and pipeline terminology.
  16. examples/expressive_agent/agent.py - adapted to examples/src/expressive_agent.ts, translating the friend pipeline, AssemblyAI STT, Gemma LLM, adaptive interruption, runtime voices, and attributes to JS APIs while retaining explicit cue-cli dispatch.
  17. examples/expressive_agent/prompt.md - adapted to examples/src/expressive_agent/prompt.md, preserving persona, output, venting, and guardrail rules.
  18. examples/expressive_agent/protocol.py - adapted to examples/src/expressive_agent/protocol.ts with equivalent metadata parsing, malformed fallback, four-voice table, expressive toggle, and participant attributes.
  19. examples/expressive_agent/pyproject.toml - adapted through the existing shared examples/package.json; no dependency change was required.
  20. examples/playground.yaml - not applicable: Python Jukebox deployment metadata and hosted agent IDs have no target infrastructure counterpart.
  21. livekit-agents/livekit/agents/__init__.py - adapted to agents/src/index.ts and agents/src/voice/index.ts; root wildcard export plus the added voice export expose ExpressiveOptions, while the other option types were already public.
  22. livekit-agents/livekit/agents/tts/_mood.py - adapted to agents/src/tts/_mood.ts with equivalent mood type, priority, word-start weighting, tie-breaking, and calm fallback.
  23. livekit-agents/livekit/agents/tts/_mood_data.py - adapted to agents/src/tts/_mood_data.ts with the complete weighted source table.
  24. livekit-agents/livekit/agents/tts/_provider_format.py - adapted to agents/src/tts/_provider_format.ts, preserving target document-order abstractions while porting mood payloads, Fish vocabulary and tones, provider guidance, aliases, xAI chunking, and bracket-prose preservation.
  25. livekit-agents/livekit/agents/voice/__init__.py - adapted to agents/src/voice/index.ts by exporting ExpressiveOptions.
  26. livekit-agents/livekit/agents/voice/agent_activity.py - ported in agents/src/voice/agent_activity.ts; the target base already resolves and gates public expressive settings.
  27. livekit-agents/livekit/agents/voice/agent_session.py - ported in agents/src/voice/agent_session.ts; the target base already exposes expressive settings and defaults.
  28. livekit-agents/livekit/agents/voice/room_io/_output.py - adapted to agents/src/voice/room_io/_output.ts; normalized lk.expression is attached to the opening delta header because rtc-node close() cannot carry attributes.
  29. pyproject.toml - not applicable: Python workspace membership only; target uses pnpm.
  30. tests/test_expressive_toggle.py - adapted to agents/src/voice/agent_session.test.ts with equivalent default, boolean, and object assertions; broader expressive tests already existed on the target base.
  31. tests/test_mood.py - adapted to agents/src/tts/mood.test.ts with equivalent table completeness, priority, weighted match, word-start, fallback, and payload tests.
  32. tests/test_tokenizer_xml_markup.py - adapted across agents/src/tokenize/xml_markup.test.ts, agents/src/tts/expr_markup.test.ts, and agents/src/tts/markup_utils.test.ts for Fish expansion, tones, aliases, normalized payloads, provider prompts, and bracket prose.
  33. uv.lock - not applicable: Python lock and workspace registration only; no target dependency changed.

Ported from livekit/agents#6698

Original PR description

No description.

@rosetta-livekit-bot
rosetta-livekit-bot Bot requested a review from a team as a code owner August 7, 2026 22:45
@changeset-bot

changeset-bot Bot commented Aug 7, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: ec5b051

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 Major
@livekit/agents-plugin-anam Major
@livekit/agents-plugin-anthropic Major
@livekit/agents-plugin-assemblyai Major
@livekit/agents-plugin-baseten Major
@livekit/agents-plugin-bey Major
@livekit/agents-plugin-cartesia Major
@livekit/agents-plugin-cerebras Major
@livekit/agents-plugin-deepgram Major
@livekit/agents-plugin-did Major
@livekit/agents-plugin-elevenlabs Major
@livekit/agents-plugin-fishaudio Major
@livekit/agents-plugin-google Major
@livekit/agents-plugin-hedra Major
@livekit/agents-plugin-hume Major
@livekit/agents-plugin-inworld Major
@livekit/agents-plugin-lemonslice Major
@livekit/agents-plugin-liveavatar Major
@livekit/agents-plugin-livekit Major
@livekit/agents-plugin-minimax Major
@livekit/agents-plugin-mistral Major
@livekit/agents-plugin-mistralai Major
@livekit/agents-plugin-neuphonic Major
@livekit/agents-plugin-openai Major
@livekit/agents-plugin-perplexity Major
@livekit/agents-plugin-phonic Major
@livekit/agents-plugin-resemble Major
@livekit/agents-plugin-rime Major
@livekit/agents-plugin-runway Major
@livekit/agents-plugin-sarvam Major
@livekit/agents-plugin-silero Major
@livekit/agents-plugin-soniox Major
@livekit/agents-plugin-tavus Major
@livekit/agents-plugins-test Major
@livekit/agents-plugin-trugen Major
@livekit/agents-plugin-xai Major

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 2 potential issues.

View 2 additional findings in Devin Review.

Open in Devin Review

Comment thread agents/src/tts/_mood.ts
Comment on lines +54 to +63
for (const mood of MOOD_PRIORITY) {
const score = Object.entries(MOOD_KEYWORDS[mood]).reduce(
(total, [keyword, weight]) => total + (matchesWord(text, keyword) ? weight : 0),
0,
);
if (score > bestScore) {
best = mood;
bestScore = score;
}
}

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.

🟡 Delivery labels that describe curiosity are reported as empathy instead

A label whose descriptive words tie in weight is resolved by a fixed priority list (matchMood at agents/src/tts/_mood.ts:54-63) that ranks empathy above curiosity, so "gently curious, welcoming" is published as empathetic and the PR's own expectation for that label does not hold.

Impact: Frontends reading the published mood get the wrong feeling for common mixed labels, and the new mood test in this PR fails.

Score computation and tie-break mechanism

Scoring is a sum over keyword prefix matches. For the lowercased label gently curious, welcoming:

  • empathetic matches BOTH gentle (weight 1, agents/src/tts/_mood_data.ts:189) and gently (weight 1, agents/src/tts/_mood_data.ts:190) because matchesWord only requires the match to start at a word start and does not require a word end — the single word "gently" is therefore counted twice → score 2.
  • curious matches curious (weight 2) → score 2.

MOOD_PRIORITY lists empathetic (index 5) before curious (index 7) and the loop updates only on score > bestScore, so the first mood with the maximum score wins → empathetic.

agents/src/tts/mood.test.ts:17 asserts matchMood('gently curious, welcoming') === 'curious', so this case is a failing test as well as a questionable classification. Either the overlapping gentle/gently stems must be de-duplicated (keep only the gentl stem) or the priority/tie-break must match the upstream Python ordering.

Prompt for agents
matchMood in agents/src/tts/_mood.ts sums keyword weights per mood and breaks ties by the order of MOOD_PRIORITY (first mood with the strictly-highest score wins). Two problems interact: (1) MOOD_KEYWORDS.empathetic in agents/src/tts/_mood_data.ts contains both the stem 'gentle' and the longer form 'gently', and matchesWord only anchors at a word start (no word-end check), so a single occurrence of the word 'gently' contributes 2 points instead of 1; (2) MOOD_PRIORITY ranks 'empathetic' ahead of 'curious'. As a result matchMood('gently curious, welcoming') returns 'empathetic', while agents/src/tts/mood.test.ts:17 expects 'curious'. Verify against the upstream Python _mood.py/_mood_data.py whether the keyword table should contain only one stem ('gentl') and/or whether MOOD_PRIORITY ordering differs, and align the port so the shipped test passes and overlapping stems cannot double-count.
Open in Devin Review

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

Comment thread agents/src/tts/_mood.ts
Comment on lines +6 to +50
export type AgentMood =
| 'excited'
| 'happy'
| 'playful'
| 'curious'
| 'surprised'
| 'hopeful'
| 'empathetic'
| 'sad'
| 'angry'
| 'anxious'
| 'calm';

export const MOOD_PRIORITY: AgentMood[] = [
'angry',
'sad',
'anxious',
'surprised',
'playful',
'empathetic',
'excited',
'curious',
'hopeful',
'happy',
'calm',
];
export const DEFAULT_MOOD: AgentMood = 'calm';

function matchesWord(text: string, keyword: string): boolean {
let start = 0;
while (true) {
const at = text.indexOf(keyword, start);
if (at === -1) return false;
if (at === 0 || !/\p{L}/u.test(text[at - 1]!)) return true;
start = at + 1;
}
}

export function matchMood(label: string): AgentMood;
export function matchMood(label: string, fallback: AgentMood): AgentMood;
export function matchMood(label: string, fallback: null): AgentMood | null;
export function matchMood(
label: string,
fallback: AgentMood | null = DEFAULT_MOOD,
): AgentMood | null {

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.

🟡 New public helpers in the mood module ship without documentation comments

The newly added exported type, constants and function (AgentMood, MOOD_PRIORITY, DEFAULT_MOOD, matchMood at agents/src/tts/_mood.ts:6-50) carry no TSDoc, which the repository's contribution rules require for every new interface/method addition.

Impact: Generated API docs omit the mood normalization surface, and reviewers/consumers have no stated contract for the fallback behaviour.

Rule reference

CONTRIBUTING.md: "If writing new methods/interfaces/enums/classes, document them. This project uses TypeDoc for automatic API documentation generation, and every new addition has to be properly documented."

The same omission applies to SessionConfig and parseSessionConfig in examples/src/expressive_agent/protocol.ts:54-60.

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.

1 participant