ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

LiveKit Agents Expressive Agent 示例深度解析:用 expressive=True 让语音 Agent 拥有情绪化表达

LiveKit Agents Expressive Agent 示例深度解析:用 expressive=True 让语音 Agent 拥有情绪化表达 LiveKit Agents Expressive Agent 示例深度解析用 expressiveTrue 让语音 Agent 拥有情绪化表达【免费下载链接】agentsA framework for building realtime voice AI agents ️项目地址: https://gitcode.com/GitHub_Trending/agen/agents导读本篇文章围绕仓库中examples/expressive_agent示例展开讲解如何在 LiveKit Agents 框架中通过AgentSession上的单个expressiveTrue标志让语音 Agent 不再只是朗读文字而是具备情绪、节奏与拟声词等表达力好消息会兴奋坏消息会低落。文章会完整剖析该示例的架构分工agent.py/prompt.md/protocol.py、前后端 dispatch 契约、四种 TTS 语音的选择方式并深入框架源码揭示 expressive mode 的 markup 注入与剥离机制。读完你将掌握 expressive mode 的开启方式、可配置项、适用前提并能基于该示例快速搭建自己的朋友式情绪化语音 Agent。Expressive Mode 是什么一个标志开启的表达层按示例文档的定义Expressive mode 是AgentSession上的单一开关expressiveTrue见 examples/expressive_agent/README.md。启用后框架会自动完成两件事把 TTS 提供商的 markup 指南注入 LLM 的 prompt——模型学会在回复里输出行内的表达标签emotion 情绪、pacing 语速、non-verbal sounds 拟声词让 TTS 渲染这些标签、而对话转写文本中永不显示它们——用户听到的是情绪饱满的语音看到的转写却是干净的文字。这就是 expressive mode 与普通 TTS 的核心区别说什么内容由 LLM 决定怎么说表达由 markup 标签驱动两者在 prompt 层面被刻意分开管理。示例的prompt.md里就明确写着Expressive Mode injects the delivery guide separately, so this prompt covers only who you are and what you say. Tone and pacing rules dont belong here, but word choice does.见 examples/expressive_agent/prompt.md 第 4-6 行——persona 只管说什么expressive 只管怎么发音二者互不重复。框架侧的关键证据在框架源码中这个注入由update_expressive_instructions完成它会在 chat context 里插入或替换一条 ID 固定的系统消息lk.expressive.instructions见 livekit-agents/livekit/agents/voice/generation.pyEXPRESSIVE_INSTRUCTIONS_MESSAGE_ID lk.expressive.instructions # value must not change def update_expressive_instructions(chat_ctx: ChatContext, *, text: str) - None: Insert or replace the expressive markup-guide system message. def remove_expressive_instructions(chat_ctx: ChatContext) - None: Remove the expressive markup-guide message added by update_expressive_instructions, if present.默认注入的模板在 livekit-agents/livekit/agents/voice/agent_session.py 中定义DEFAULT_EXPRESSIVE_OPTIONS: ExpressiveOptions ExpressiveOptions( tts_instructions_templateInstructions( You can control how you speak using the following formatting tags. Use them when appropriate to make your speech more expressive and natural:\n\n {tts.markup.llm_instructions} ), speech_steeringDEFAULT_SPEECH_STEERING_OPTIONS, )其中的{tts.markup.llm_instructions}占位符在渲染时由当前 TTS 的Markup.llm_instructions()填充——也就是每个 TTS 插件声明自己会说哪种 markup 方言框架负责把它教给 LLM。示例架构三个文件各司其职示例目录结构如下examples/expressive_agent/ ├── Dockerfile # 与 examples/ 下其他示例共用的容器镜像 ├── README.md # 本文依据的文档 ├── agent.py # 组合根会话组装 服务入口 ├── prompt.md # 仅存放 persona说什么 ├── protocol.py # 前后端完整契约dispatch 元数据 回显属性 语音表 └── pyproject.toml # 依赖声明README 明确给出了三文件分工见 examples/expressive_agent/README.mdagent.py是组合根composition root负责 AgentSession 的组装与服务器入口prompt.md只放 persona只控制what说什么表达层how怎么发音交给 expressive mode两者绝不互相重复protocol.py是整个前端契约dispatch 元数据的形状、回显给前端的属性、以及这些元数据值所指向的语音表。agent.py会话组装细节核心入口在 examples/expressive_agent/agent.py值得逐段拆解class Friend(Agent): def __init__(self) - None: super().__init__(instructionsINSTRUCTIONS) async def on_enter(self) - None: await self.session.generate_reply(instructionsGREETING)Friend是无任务、无工具的自由对话 Agenton_enter时用一段像给熟人接电话一样的开场白主动发起第一句GREETING常量见 examples/expressive_agent/agent.py。会话组装examples/expressive_agent/agent.pysession AgentSession( sttinference.STT(assemblyai/universal-3-5-pro, languageen), llminference.LLM(google/gemma-4-31b-it), ttsinference.TTS(config.voice.model, voiceconfig.voice.voice), turn_handlingTurnHandlingOptions( turn_detectioninference.TurnDetector(versionv1), interruption{mode: adaptive}, preemptive_generation{enabled: True}, ), expressiveconfig.expressive, ) await session.start(agentFriend(), roomctx.room) await ctx.connect() await ctx.room.local_participant.set_attributes(config.attributes())可以看出整条管线全部走LiveKit InferenceSTT 用 AssemblyAI Universal-3.5 Pro英文、LLM 用 Google Gemma 4 31B、TTS 由前端 dispatch 的语音名动态决定、轮转检测用 LiveKit turn detectorv1与 README 描述一致见 examples/expressive_agent/README.mdturn_handling启用了 adaptive 打断模式与预生成preemptive generation让朋友式对话更自然expressiveconfig.expressive直接把解析后的 dispatch 配置传给会话会话建立后通过set_attributes把配置回显为参与者属性供前端展示。本地运行两步启动按 READMEexamples/expressive_agent/README.md先在仓库根目录或环境变量提供 LiveKit Cloud 凭证放在../.env然后uv sync --all-extras --dev # 在仓库根目录执行 uv run agent.py consoleuv run agent.py console启动本地 console 会话适合快速试听效果uv run agent.py dev把 Agent 连接到 LiveKit Cloud供真实前端接入会话。依赖声明在 examples/expressive_agent/pyproject.tomldependencies [ livekit-agents1.6, python-dotenv1.0.0, ]值得注意的细节pyproject.toml中设置了package false并刻意不引用 workspace 的[tool.uv.sources]其注释说明——示例是脚本集合而非可安装分发包保持零 workspace 引用才能让该目录脱离仓库独立解析依赖。若需容器化部署examples/expressive_agent/Dockerfile 与其他示例共用构建上下文中 pyproject.toml 必须能独立解析镜像基于 Python 3.13 slim uv并在构建期执行python -m livekit.agents download-files预下载模型权重如 silero VAD、turn-detector 等避免上线后冷启动卡顿最终以python agent.py start启动。前后端契约dispatch 元数据与回显属性README 强调Agent 读取自身的 dispatch metadata前端在连接时即可选择管线无需重新部署见 examples/expressive_agent/README.md。契约形状由 examples/expressive_agent/protocol.py 顶部的 docstring 一锤定音{ expressive: true, tts: fishaudio }expressive布尔值默认true开关 expressive modetts从protocol.py的语音表选择一个声音fishaudio、inworld、cartesia、xai。会话建立后两个值会作为参与者属性回显给前端expressive、tts_provider、tts_label前端据此展示当前激活的管线。protocol.py 实现要点SessionRequest是 pydantic 模型examples/expressive_agent/protocol.pyclass SessionRequest(BaseModel, extraignore): The dispatch metadata, as sent. Unknown fields are ignored so an older agent still starts against a newer frontend. expressive: bool True tts: str | None None classmethod def parse(cls, metadata: str | None) - SessionRequest: Read a dispatch metadata blob. Anything malformed falls back to defaults, because a demo that starts with the wrong voice beats one that fails to start.两个设计很实用extraignore未知字段被静默忽略旧 Agent 也能在更新的前端下正常启动parse容错metadata 为空或 JSON 解析失败ValidationError时回退到默认值并打 warning——用错声音启动的 demo 胜过起不来的 demo。语音表examples/expressive_agent/protocol.py定义了四种可选项键providermodelvoice显示标签fishaudiofishaudiofishaudio/s2.1-pro51b44863613e405a896f7f4294c6e6d0Fish Audio S2.1 Pro (Marley)inworldinworldinworld/inworld-tts-2AshleyInworld TTS 2 (Ashley)cartesiacartesiacartesia/sonic-39626c31c-bec5-4cca-baa8-f8ba9e84c8bcCartesia Sonic 3 (Jacqueline)xaixaixai/tts-1evexAI TTS 1 (Eve)resolve()把请求中的tts键解析为具体的Voice未知键回落到默认fishaudio随后SessionConfig.attributes()examples/expressive_agent/protocol.py生成回显属性注意协议中属性必须是字符串因此布尔值被序列化为true/falsedef attributes(self) - dict[str, str]: return { expressive: true if self.expressive else false, tts_provider: self.voice.provider, tts_label: self.voice.label, }关于 xAI 的一个特殊之处README 特别提醒见 examples/expressive_agent/README.mdxAI 通过韵律prosody与声音标签引导表达但它没有独立的 expression 标签因此不会发布lk.expression。它的语音依然富有表现力只是前端想展示情绪指示器时会没有数据可读。这个行为在protocol.py的语音表中得到印证——xAI 条目与其他三家的字段结构完全一致差异只来自 provider 的 markup 方言能力。深入框架expressive mode 的底层实现ExpressiveOptions不止一个布尔值框架层面expressive参数除了bool还接受ExpressiveOptions字典见 livekit-agents/livekit/agents/voice/agent_session.pyclass ExpressiveOptions(TypedDict, totalFalse): All keys are optional; common shapes: - {speech_steering: {...}} — steer delivery and non-verbal sounds on top of the provider-agnostic default instructions. - {tts_instructions_template: ...} — a fully custom prompt. - {tts_instructions_append: ...} — your own rules appended to the template. speech_steering: SpeechSteeringOptions tts_instructions_template: Instructions | str tts_instructions_append: str其中SpeechSteeringOptions提供三档表达微调livekit-agents/livekit/agents/voice/agent_session.pydisfluencies默认True是否允许填充词um / uh设为False可退出nonverbal_sounds允许 TTS 发出哪些非语言声音True保留全套词汇False全部禁用也可传NonverbalOptions字典按类别开关paceslow|normal|fast。resolve_expressive_optionslivekit-agents/livekit/agents/voice/agent_session.py负责把用户配置解析成面向具体 provider 的最终指令先基于默认模板把speech_steering渲染成 provider 专属的 delivery 指南追加进去再应用显式的tts_instructions_template覆盖最后追加tts_instructions_append用户自由规则永远最后生效、优先级最高。未设置的 steering 字段回退到默认值。值得注意Agent上也存在expressive属性且Agent 上的值会覆盖 Session 上的值见 livekit-agents/livekit/agents/voice/agent.py 与 livekit-agents/livekit/agents/voice/agent_session.py 的注释。TTS.Markup插件如何声明表达力框架在 livekit-agents/livekit/agents/tts/tts.py 定义了TTS.Markup内部类作为 expressive 管线的能力声明点_provider_key()返回表示不支持 markup不注入指令、不做归一化/转换插件覆盖它即声明了自己会说哪种方言其他 markup 方法都会经由这个 key 委托到共享表info暴露该语音的MarkupInfo含nonverbals非语言声音矩阵llm_instructions()返回描述可用 markup 标签的 LLM 指令文本框架在 expressive 模式下把它注入系统 prompt也就是上面{tts.markup.llm_instructions}的取值来源。Markup 的完整数据流在 livekit-agents/livekit/agents/inference/tts.py 可以看到合成的关键链路会话开始时框架快照 expressive 状态然后分句器sentence_tokenizer(provider, expressive...)以 provider 的 markup 方言切句文本先经markup.normalize归一化token 再经markup.convert转换后送交 TTS。相关的文本处理函数集中在 livekit-agents/livekit/agents/tts/_provider_format.pynormalize_markup(provider, text)把 LLM 产出的标签归一化为 provider 的原生语法convert_markup(provider, text)归一化后进一步转换为该 provider 可消费的标记split_all_markup/strip_all_markup/strip_expr_markup把标签从文本中剥离——转写 sink 是 provider 无关地统一剥离 markup 的见 livekit-agents/livekit/agents/tts/tts.py 注释这保证了用户看到的转写始终干净steering_instructions(provider, steering)把SpeechSteeringOptions渲染成 delivery 指南只有真正改变默认值的字段才产生输出被禁用的声音不会出现在广告词表中sentence_tokenizer(provider, *, expressive)返回 provider 专属的分句器此外_MAX_INPUT_LEN表livekit-agents/livekit/agents/tts/_provider_format.py按 provider 设定了每次合成的字符上限如 inworld 900、cartesia 400expressive 模式下同时充当句子批量分组的上限。关闭 expressive 时的历史清理框架还处理了一个隐蔽的边界当某一轮以 expressive off 运行时显式关闭或当前 TTS 无 markup 方言_strip_assistant_markup会把历史 assistant 消息中的 markup 全部剥离见 livekit-agents/livekit/agents/voice/generation.py——否则历史里残留的标签会 few-shot 诱导 LLM 继续输出无人转换、无人剥离的 markup。而且一旦某一轮以 off 运行之前轮次的 markup 就永久清除了即使之后重新开启 expressive也因为指令已重新注入而保持一致性。适用前提哪些 TTS 支持 expressiveREADME 明确examples/expressive_agent/README.mdexpressive mode 要求livekit.agents.inference.TTS模型声明了自己的 markup 方言。Fish Audio、Inworld TTS 2、Cartesia Sonic 3、xAI 均满足条件而没有方言的 provider 会正常合成、该标志保持惰性inert——不会报错只是表达标签不生效。这与框架侧TTS.Markup._provider_key()默认返回不支持 markup的实现完全对应。对比实验开与关感受表达层的价值README 把对比本身称为这个 demo 的意义所在见 examples/expressive_agent/README.md分别以expressiveTrue与expressiveFalse各跑一次对两边说同样的话——文字内容几乎一样但听感天差地别开启时模型按 markup 指南输出情绪标签、语速标记与拟声词TTS 渲染出兴奋、低落、停顿等听感关闭时同一段文字以平铺直叙的方式合成表达标签不生成也不渲染。具体操作上你可以在连接时通过 dispatch metadata 传入{expressive: false, tts: fishaudio}对应protocol.py中expressive字段默认true、可显式置false也可以直接改agent.py中传给AgentSession的expressiveconfig.expressive的值。由于回显属性expressive会实时反映配置前端可以在界面上直接看到当前会话处于哪种模式。小结examples/expressive_agent演示了 LiveKit Agents 中 expressive mode 的完整落地路径一个布尔标志 一个前端可选的语音表 一个只管说什么的 persona prompt就能得到一个情绪随对话起伏、转写却始终干净的自由语音 Agent。理解这套机制的钥匙在于三层契约会话层AgentSession(expressive...)是唯一入口可扩展为ExpressiveOptions精细控制表达插件层TTS.Markup._provider_key()决定 provider 是否具备 markup 方言llm_instructions把方言教给 LLM管线层update/remove_expressive_instructions维护 prompt 注入normalize/convert/strip系列函数保证标签进音频、不进转写。如果你要在此基础上继续深入可以依次阅读 examples/expressive_agent/agent.py、examples/expressive_agent/protocol.py、livekit-agents/livekit/agents/voice/agent_session.py、livekit-agents/livekit/agents/tts/_provider_format.py 这几处核心实现即可从会用进阶到能改。【免费下载链接】agentsA framework for building realtime voice AI agents ️项目地址: https://gitcode.com/GitHub_Trending/agen/agents创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表