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.py | AvatarSession会话类,负责启动会话、加入/离开会议、生成房间选项 |
| api.py | LemonSlice REST API 客户端,含重试逻辑与图片上传 |
| meeting/audio.py | 会议音频 WebSocket 中继、PCM 反序列化、降混与重采样 |
| meeting/chat.py | 会议聊天消息中继到AgentSession |
| meeting/codec.py | 会议聊天消息的 JSON 线格式解析 |
| meeting/room.py | JoinMeetingResult结果类型 |
| init.py | 插件入口,导出AvatarSession、LemonSliceException并注册插件 |
二、安装与前置条件
2.1 安装插件
根据 README,插件通过 pip 安装:
pip install livekit-plugins-lemonslice从 pyproject.toml 可以看到该插件声明的运行时依赖为livekit-agents>=1.8.0与pillow>=10.3.0,并要求 Python>=3.10.0。pillow依赖用于支持直接以 PIL 图片作为形象上传的能力;而livekit-agents提供了AgentSession、Plugin基类、APIConnectOptions等核心运行设施。
2.2 前置条件:API Key
你需要向 LemonSlice 申请一个 API Key。插件支持两种提供方式:
- 环境变量(推荐):设置
LEMONSLICE_API_KEY - 代码参数:在
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_url、livekit_api_key、livekit_api_secret,三者均可通过参数传入或从环境变量LIVEKIT_URL、LIVEKIT_API_KEY、LIVEKIT_API_SECRET读取,缺失时同样抛出LemonSliceException(见 avatar.py)。
小结:运行时需要的环境变量至少包括
LEMONSLICE_API_KEY,以及 LiveKit 相关的LIVEKIT_URL、LIVEKIT_API_KEY、LIVEKIT_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_id | str | LemonSlice 平台上的 Agent(形象)ID,用于在会话中启用某个已配置好的形象 |
agent_image_url | str | 直接使用一个图片 URL 作为形象 |
agent_image | PIL.Image.Image | 直接传入 PIL 图片对象,插件会将其编码为 PNG 并以 multipart 上传 |
agent_prompt | str | 提示词,用于"潜移默化地"影响形象在回复时的动作与表情 |
agent_idle_prompt | str | 提示词,用于影响形象在空闲时的动作与表情 |
idle_timeout | int | 空闲超时(秒) |
api_url | str | 覆盖默认的 LemonSlice API 地址 |
api_key | str | LemonSlice API Key,缺省读LEMONSLICE_API_KEY |
avatar_participant_identity | str | 形象以什么 Participant Identity 加入 LiveKit 房间,默认lemonslice-avatar-agent |
avatar_participant_name | str | 形象在房间中的显示名,默认lemonslice-avatar-agent |
conn_options | APIConnectOptions | 对 LemonSlice API 的连接选项(超时、最大重试次数等),默认DEFAULT_API_CONNECT_OPTIONS |
额外**kwargs | dict | 其余关键字会作为extra_payload原样合并进 API 请求体 |
3.2 形象来源的三选一约束
agent_id、agent_image_url、agent_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.png、content_type为image/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):
- 读取 LiveKit 凭据:参数优先,其次环境变量;
- 签发形象 Token:用
api.AccessToken以 identity/name 为lemonslice-avatar-agent(可自定义)、kind="agent"、授予room_join权限签发 JWT,并通过with_attributes({ATTRIBUTE_PUBLISH_ON_BEHALF: <本地 Agent identity>})声明"该形象可代理本地 Agent 发布媒体"; - 热切换音频输出:调用
agent_session.output.replace_audio_tail(...)把音频输出替换为DataStreamAudioOutput,目标为形象 identity,采样率 16000Hz,等待视频轨道出现后再缓冲、等待播放开始; - 调用 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保证热替换后TranscriptSynchronizer与RecorderAudioOutput链路不中断,中间间隙由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) # 外部会议中的机器人 IDJoinMeetingResult定义在 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_join与can_subscribe,但禁止发布(can_publish=False、can_publish_data=False),即只允许订阅形象在会议中的媒体、不允许向 LiveKit 房间发布数据(avatar.py)。该 Token 随join_meeting请求提交给 LemonSlice,用于后续订阅形象在会议场景下的音视频。
5.3 会议音频中继
加入会议后,插件会:
- 创建一个
MeetingAudioInput(继承livekit.agents.voice.io.AudioInput),并把它挂到agent_session.input.audio上——这样会议混音音频会直接进入 Agent 的 STT,而不再从 LiveKit 房间取音频输入; - 启动后台任务
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、会议中继连接状态、首帧/首条聊天到达等调试信息。
七、常见问题与最佳实践
- 形象不出现/没有视频:确认
wait_for_join()被调用且未超时;检查agent_id或图片参数是否合法,以及 LiveKit 凭据是否能正常签发 Token。Token 必须带ATTRIBUTE_PUBLISH_ON_BEHALF属性,否则音频输出无法正确路由到形象。 - 会议模式下没有声音进 STT:确认在
AgentSession.start()时使用了avatar.room_options()的返回值;会议音频是直接喂给 STT 的,若仍从 LiveKit 房间取音频会重复/丢失。 - 重连日志刷屏:中继 WebSocket 断线时会按 1s→30s 指数退避自动重连,属正常行为;若长期连不上,检查
websocket_url与网络环境。 - 资源泄漏:会话结束务必调用
await avatar.aclose(),它会依次离开会议、取消中继任务、移除房间参与者。 - 形象来源三选一:
agent_id、agent_image_url、agent_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),仅供参考