LiveKit Agents LemonSlice 插件实战:为实时语音 AI Agent 接入虚拟形象与外部视频会议
2026/9/15 7:35:37 网站建设 项目流程

LiveKit Agents LemonSlice 插件实战:为实时语音 AI Agent 接入虚拟形象与外部视频会议

【免费下载链接】agentsA framework for building realtime voice AI agents 🤖🎙️📹项目地址: https://gitcode.com/GitHub_Trending/agen/agents

导读

本文围绕开源仓库中livekit-plugins/livekit-plugins-lemonslice插件的 README.md 展开,完整讲解如何在 LiveKit Agents 框架中为实时语音 Agent 接入 LemonSlice 虚拟形象(Virtual Avatar),并通过源码级分析说明其会话启动、API 鉴权、数据流音频传输与 Zoom / Google Meet / Microsoft Teams / Webex 外部会议接入的底层原理。读完本文,你将掌握该插件的安装、前置条件、核心参数语义、与AgentSession的接线方式,以及外部会议集成模式下的音频与聊天中继机制。


一、插件概览:LemonSlice 是什么

LemonSlice 是一个提供虚拟形象(Virtual Avatar)能力的在线服务,能够让 AI Agent 以"人脸形象"出现在实时音视频房间中。livekit-plugins-lemonslice是 LiveKit Agents 生态中用于对接该服务的官方插件,其职责可以概括为:

  • 通过 LemonSlice 云端 API 创建"Agent 会话",把 LiveKit 房间信息(URL、Token、Session ID)交给 LemonSlice,由其托管虚拟形象的渲染与推流;
  • 在 LiveKit 房间中以一个独立的 Participant 身份加入(默认 identity 为lemonslice-avatar-agent),发布视频轨道;
  • 将 Agent 的 TTS 音频通过 LiveKit DataStream 以"代发布"(publish on behalf)的方式路由给该虚拟形象,实现"虚拟形象开口说话";
  • 可选地支持把虚拟形象"派入"外部视频会议(Zoom、Google Meet、Microsoft Teams、Webex),并中继会议音频与聊天消息回 Agent。

从仓库结构看,插件源码位于 livekit/plugins/lemonslice,包含:

文件职责
avatar.pyAvatarSession会话类,负责启动会话、加入/离开会议、生成房间选项
api.pyLemonSlice REST API 客户端,含重试逻辑与图片上传
meeting/audio.py会议音频 WebSocket 中继、PCM 反序列化、降混与重采样
meeting/chat.py会议聊天消息中继到AgentSession
meeting/codec.py会议聊天消息的 JSON 线格式解析
meeting/room.pyJoinMeetingResult结果类型
init.py插件入口,导出AvatarSessionLemonSliceException并注册插件

二、安装与前置条件

2.1 安装插件

根据 README,插件通过 pip 安装:

pip install livekit-plugins-lemonslice

从 pyproject.toml 可以看到该插件声明的运行时依赖为livekit-agents>=1.8.0pillow>=10.3.0,并要求 Python>=3.10.0pillow依赖用于支持直接以 PIL 图片作为形象上传的能力;而livekit-agents提供了AgentSessionPlugin基类、APIConnectOptions等核心运行设施。

2.2 前置条件:API Key

你需要向 LemonSlice 申请一个 API Key。插件支持两种提供方式:

  1. 环境变量(推荐):设置LEMONSLICE_API_KEY
  2. 代码参数:在AvatarSession/LemonSliceAPI构造时通过api_key=显式传入

在 api.py 中,客户端会优先使用显式传入的api_key,否则回退读取LEMONSLICE_API_KEY环境变量;两者都缺失时直接抛出LemonSliceException("LEMONSLICE_API_KEY must be set")。此外,插件内置了默认的 API 端点https://lemonslice.com/api/liveai/sessions,可通过api_url参数覆盖。

除了 LemonSlice 自身的 API Key,使用该插件还需要LiveKit 服务器凭据。在AvatarSession.start()中,插件要求提供livekit_urllivekit_api_keylivekit_api_secret,三者均可通过参数传入或从环境变量LIVEKIT_URLLIVEKIT_API_KEYLIVEKIT_API_SECRET读取,缺失时同样抛出LemonSliceException(见 avatar.py)。

小结:运行时需要的环境变量至少包括LEMONSLICE_API_KEY,以及 LiveKit 相关的LIVEKIT_URLLIVEKIT_API_KEYLIVEKIT_API_SECRET


三、核心类AvatarSession:参数与语义

插件对外最重要的类是AvatarSession,它继承自 LiveKit Agents 的 AvatarSession 抽象基类(livekit.agents.voice.avatar.AvatarSession)。基类定义了avatar_identity抽象属性、start()wait_for_join()aclose()等生命周期方法;AvatarSession继承后实现了provider == "lemonslice"

3.1 构造参数一览

参数类型说明
agent_idstrLemonSlice 平台上的 Agent(形象)ID,用于在会话中启用某个已配置好的形象
agent_image_urlstr直接使用一个图片 URL 作为形象
agent_imagePIL.Image.Image直接传入 PIL 图片对象,插件会将其编码为 PNG 并以 multipart 上传
agent_promptstr提示词,用于"潜移默化地"影响形象在回复时的动作与表情
agent_idle_promptstr提示词,用于影响形象在空闲时的动作与表情
idle_timeoutint空闲超时(秒)
api_urlstr覆盖默认的 LemonSlice API 地址
api_keystrLemonSlice API Key,缺省读LEMONSLICE_API_KEY
avatar_participant_identitystr形象以什么 Participant Identity 加入 LiveKit 房间,默认lemonslice-avatar-agent
avatar_participant_namestr形象在房间中的显示名,默认lemonslice-avatar-agent
conn_optionsAPIConnectOptions对 LemonSlice API 的连接选项(超时、最大重试次数等),默认DEFAULT_API_CONNECT_OPTIONS
额外**kwargsdict其余关键字会作为extra_payload原样合并进 API 请求体

3.2 形象来源的三选一约束

agent_idagent_image_urlagent_image三者是互斥的。在 api.py 中,start_agent_session会统计三个参数中"被显式提供"的数量:

  • 为 0:抛出LemonSliceException("Missing one of agent_id, agent_image_url or agent_image")
  • 大于 1:抛出LemonSliceException("Only one of agent_id, agent_image_url or agent_image can be provided")

因此构造AvatarSession时,形象来源必须且只能指定一种。

3.3 底层 API 请求体结构

当不携带图片时,start_agent_session会向 LemonSlice API 发送如下 JSON(见 api.py):

{ "transport_type": "livekit", "properties": { "livekit_url": "<LiveKit 服务器 URL>", "livekit_token": "<为形象签发的 LiveKit JWT>", "livekit_session_id": "<房间 SID>" }, "agent_id": "<可选>", "agent_image_url": "<可选>", "agent_prompt": "<可选>", "agent_idle_prompt": "<可选>", "idle_timeout": 60 }

若提供了agent_image(PIL 图片),则改用aiohttp.FormData进行 multipart 上传:payload字段携带上面的 JSON,image字段携带 PNG 编码后的图片字节,文件名为image.pngcontent_typeimage/png(见 api.py 与_encode_image函数)。

请求携带X-API-Key请求头进行鉴权。底层_post方法内置了基于APIConnectOptions指数退避重试:总超时 60 秒、建连超时取conn_options.timeout;对可重试的错误按_interval_for_retry(i)间隔重试,最多重试conn_options.max_retry次;对非可重试的APIStatusError直接抛出(api.py)。


四、将 AvatarSession 接入 AgentSession

4.1 标准接入流程

综合源码与 LiveKit Agents 的 Avatar 使用模式,推荐接入流程如下:

import asyncio from livekit.agents import AgentSession, Agent, RoomInputOptions from livekit.agents.llm import LLM from livekit.plugins.lemonslice import AvatarSession from livekit.plugins.openai import LLM as OpenAILLM async def main(): # 1. 创建 LemonSlice 虚拟形象会话 avatar = AvatarSession( agent_id="<你的 LemonSlice agent_id>", agent_prompt="表现得热情、专业,回复时配合自然的点头动作", agent_idle_prompt="空闲时保持自然呼吸感", idle_timeout=120, ) # 2. 创建 Agent 会话,并把 avatar 挂载进去 session = AgentSession( llm=OpenAILLM(model="gpt-4o-mini"), avatar=avatar, ) # 3. 获取 avatar 推荐的房间 I/O 选项并启动 room_options = avatar.room_options() await session.start( room=room, room_input_options=RoomInputOptions(), room_output_options=room_options, ) # 4. 等待形象真正加入房间并发布视频轨道 await avatar.wait_for_join() asyncio.run(main())

说明:以上示例中的 LLM、房间创建等环节与 LiveKit Agents 常规用法一致;AgentSession的构造支持avatar参数(传入实现了AvatarSession抽象基类的实例)。wait_for_join()来自基类实现,默认最多等待 30 秒,形象未按时加入会抛asyncio.TimeoutError(见 livekit-agents 的 _types.py)。

4.2start()内部到底做了什么

调用AvatarSession.start()时,插件按顺序完成以下关键动作(avatar.py):

  1. 读取 LiveKit 凭据:参数优先,其次环境变量;
  2. 签发形象 Token:用api.AccessToken以 identity/name 为lemonslice-avatar-agent(可自定义)、kind="agent"、授予room_join权限签发 JWT,并通过with_attributes({ATTRIBUTE_PUBLISH_ON_BEHALF: <本地 Agent identity>})声明"该形象可代理本地 Agent 发布媒体";
  3. 热切换音频输出:调用agent_session.output.replace_audio_tail(...)把音频输出替换为DataStreamAudioOutput,目标为形象 identity,采样率 16000Hz,等待视频轨道出现后再缓冲、等待播放开始;
  4. 调用 LemonSlice API 创建会话:把形象来源、提示词、LiveKit 连接信息与 Token 一并提交,拿到session_id并返回。

其中第 2 步的ATTRIBUTE_PUBLISH_ON_BEHALF是 LiveKit Agents 房间 I/O 层的既有机制——在 room_io/_output.py 与 room_io.py 中,_output会读取参与者的该属性来决定媒体发布的"实际归属",从而让形象发布的音视频被路由到正确的本地 Agent 会话。这也是"虚拟形象替你开口说话"的实现基石。

第 3 步的DataStreamAudioOutput定义于 livekit-agents 的 _datastream_io.py,其核心机制是:通过 LiveKit 房间的 DataStream 字节流把 TTS 音频推送给远端形象工作进程,并注册 RPC 回调来感知"播放完成/播放开始",实现播放进度的闭环同步。replace_audio_tail保证热替换后TranscriptSynchronizerRecorderAudioOutput链路不中断,中间间隙由wait_remote_track的缓冲兜底。


五、外部视频会议集成(join_meeting)

该插件一个突出的能力是:把虚拟形象派入 Zoom、Google Meet、Microsoft Teams、Webex 等外部视频会议。调用顺序要求严格:先start()创建 LemonSlice 会话,再join_meeting(),之后才启动AgentSession

5.1 join_meeting 参数与返回值

result = await avatar.join_meeting( "https://meet.google.com/xxx-yyy-zzz", # 外部会议 URL bot_name="LemonSlice Avatar", # 可选:机器人在会议中的显示名 listen_to_meeting_chat=True, # 可选:是否把会议聊天中继给 Agent ) print(result.websocket_url) # 会议音频/聊天中继的 WebSocket 地址 print(result.meeting_bot_id) # 外部会议中的机器人 ID

JoinMeetingResult定义在 meeting/room.py,包含websocket_url(混合会议音频与聊天的 WebSocket 地址)与meeting_bot_id(机器人实例标识)两个字段。

5.2 调用前置校验与广播 Token

join_meeting()有两个前置约束(avatar.py):

  • 必须先调用过start()(否则抛LemonSliceException("call start() before join_meeting()"));
  • 不能重复加入(已存在meeting_bot_id时抛"already joined a meeting; call leave_meeting() first")。

此外,插件会使用缓存的 LiveKit 凭据签发一个广播 Token_mint_broadcast_token):该 Token 以 identity 为lemonslice-meeting-broadcast、TTL 4 小时,授权room_joincan_subscribe,但禁止发布can_publish=Falsecan_publish_data=False),即只允许订阅形象在会议中的媒体、不允许向 LiveKit 房间发布数据(avatar.py)。该 Token 随join_meeting请求提交给 LemonSlice,用于后续订阅形象在会议场景下的音视频。

5.3 会议音频中继

加入会议后,插件会:

  1. 创建一个MeetingAudioInput(继承livekit.agents.voice.io.AudioInput),并把它挂到agent_session.input.audio上——这样会议混音音频会直接进入 Agent 的 STT,而不再从 LiveKit 房间取音频输入;
  2. 启动后台任务stream_meeting_relay,连接 LemonSlice 返回的 WebSocket 中继。

MeetingAudioInput的音频流水线(meeting/audio.py)依次执行:

  • 反序列化:每条二进制帧以<I(采样率,4 字节小端无符号)+B(声道数,1 字节)的头部 + PCM16 数据组成(_deserialize_frame);
  • 降混:多声道通过 numpy 取均值降为单声道,与 RoomIO 的 STT 输入保持行为一致(_downmix_to_mono);
  • 重采样:与目标采样率不一致时,通过rtc.AudioResampler实时重采样到 16000Hz(_resample);
  • 入队:帧进入有界队列(默认容量 100,满时丢弃最旧帧),STT 侧通过__anext__消费。

中继 WebSocket 连接自带20 秒心跳指数退避重连(初始 1 秒、上限 30 秒,每次失败翻倍),直到收到stop事件(meeting/audio.py)。

5.4 会议聊天中继

listen_to_meeting_chat=True时,MeetingChatRelay会把会议聊天"翻译"成 Agent 的用户输入:

  • WebSocket 的 TEXT 帧经 codec.py 的deserialize_chat解析,线格式为{"type": "chat", "sender": "...", "text": "...", "to": "..."}
  • 过滤掉机器人自己发送的消息(按bot_name或默认LemonSlice Avatar忽略);
  • 格式化为[sender]: text文本(format_chat_user_input),放入容量 100 的队列;
  • 后台 drain 任务依次:等待会话离开initializing状态 →await session.interrupt()打断当前语音 →session.generate_reply(user_input=...)触发回复(meeting/chat.py)。

5.5 room_options:会议模式的 I/O 切换

AvatarSession.room_options()会根据当前是否处于会议模式返回不同的RoomOptions(avatar.py):

  • 会议模式(已join_meeting):RoomOptions(audio_input=False, audio_output=False),即关闭 LiveKit 房间侧的音频输入输出,会议音频走MeetingAudioInput、聊天走MeetingChatRelay
  • 普通模式:原样透传调用方传入的RoomOptions

5.6 离开会议与资源释放

leave_meeting()会:调用 LemonSlice API 的/leave-meeting端点移除机器人 → 置位stop事件并取消中继任务 → 关闭聊天中继。aclose()则先leave_meeting()再调用基类清理(avatar.py)。基类的aclose()还会通过 LiveKit API 把形象参与者的身份从房间中移除(见 livekit-agents 的 _types.py)。


六、错误处理与可观测性

6.1 异常体系

插件统一以LemonSliceException(继承自Exception,定义于 api.py)表达配置/前置条件类错误,例如:

  • 缺少LEMONSLICE_API_KEY
  • 缺少livekit_url/livekit_api_key/livekit_api_secret
  • 形象来源参数缺失或重复;
  • 未先start()就调用join_meeting()
  • 重复加入会议。

对 API 调用失败,_post会抛出 LiveKit Agents 标准异常APIStatusError(服务端返回非 2xx)或APIConnectionError(重试耗尽)。

6.2 日志

插件使用独立的 loggerlivekit.plugins.lemonslice(见 log.py),可通过标准 logging 配置开启 DEBUG 级别查看会话 ID、会议中继连接状态、首帧/首条聊天到达等调试信息。


七、常见问题与最佳实践

  1. 形象不出现/没有视频:确认wait_for_join()被调用且未超时;检查agent_id或图片参数是否合法,以及 LiveKit 凭据是否能正常签发 Token。Token 必须带ATTRIBUTE_PUBLISH_ON_BEHALF属性,否则音频输出无法正确路由到形象。
  2. 会议模式下没有声音进 STT:确认在AgentSession.start()时使用了avatar.room_options()的返回值;会议音频是直接喂给 STT 的,若仍从 LiveKit 房间取音频会重复/丢失。
  3. 重连日志刷屏:中继 WebSocket 断线时会按 1s→30s 指数退避自动重连,属正常行为;若长期连不上,检查websocket_url与网络环境。
  4. 资源泄漏:会话结束务必调用await avatar.aclose(),它会依次离开会议、取消中继任务、移除房间参与者。
  5. 形象来源三选一agent_idagent_image_urlagent_image只能传一个,传多会抛异常。

八、小结

livekit-plugins-lemonslice是 LiveKit Agents 中把 LemonSlice 虚拟形象能力接入实时语音 Agent 的完整实现:通过一个AvatarSession类即可完成"创建形象会话 → 热切换 DataStream 音频输出 → 形象以独立参与者身份发布音视频",并在此基础上延伸出"虚拟形象参加 Zoom/Meet/Teams/Webex 会议"的高级能力,会议侧的音频经 WebSocket 中继重采样后直达 STT、聊天经中继格式化为 Agent 用户输入,形成闭环。

如需深入阅读实现细节,推荐从以下文件开始:

  • 插件 README:livekit-plugins/livekit-plugins-lemonslice/README.md
  • 会话实现:livekit/plugins/lemonslice/avatar.py
  • API 客户端:livekit/plugins/lemonslice/api.py
  • 会议音频中继:livekit/plugins/lemonslice/meeting/audio.py
  • 会议聊天中继:livekit/plugins/lemonslice/meeting/chat.py
  • Avatar 抽象基类:livekit-agents/livekit/agents/voice/avatar/_types.py
  • DataStream 音频输出:livekit-agents/livekit/agents/voice/avatar/_datastream_io.py
  • 插件依赖声明:pyproject.toml

【免费下载链接】agentsA framework for building realtime voice AI agents 🤖🎙️📹项目地址: https://gitcode.com/GitHub_Trending/agen/agents

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询