虚拟主播直播Agent并不是把大模型接进直播间就能直接使用。直播场景的实时性、角色一致性和多模态输出要求比普通聊天机器人严格得多。一个看似简单的弹幕互动,需要经过弹幕接入、意图判断、回复生成、语音合成、动作触发和直播间状态更新等多个环节。任何一个环节出现数百毫秒以上的额外延迟,观众就会感到出戏。设计直播Agent时,首要任务是把这些环节拆成可独立扩展的模块,再用统一事件循环串起来。

一、总体架构:事件循环与模块边界
将系统分为感知层、决策层、执行层、状态层。感知层对接直播平台开放接口,实时接收弹幕、礼物、点赞和进出直播间事件。这些原始数据格式不一致,需要标准化。决策层维护角色设定、直播脚本和上下文记忆,根据事件重要性决定是否回应、回应内容。执行层负责TTS合成、数字人动作、表情切换和直播间控件操作。状态层则保存当前话题、观众偏好、已回复弹幕摘要等,供后续决策复用。模块之间通过内部事件总线通信,避免同步调用阻塞直播节奏。
事件循环是Agent的心脏。以下是一个精简的异步主循环,演示如何从优先级队列中取出事件、调用决策函数并执行动作。实际项目中还需要加入超时控制、并发限制和错误捕获。
import asyncio
from queue import PriorityQueue
class LiveAgent:
def __init__(self):
self.event_queue = PriorityQueue()
self.running = False
async def consume_events(self):
while self.running:
if not self.event_queue.empty():
priority, event = self.event_queue.get()
response = await self.decide(event)
await self.execute(response)
await asyncio.sleep(0.05)
事件循环不能只处理弹幕,还要处理系统信号。例如TTS播放结束、动作完成、模型返回慢、直播间异常断开,这些都需要进入同一个队列。可以通过不同事件类型和优先级来区分。业务事件优先级通常高于心跳事件,但安全事件必须最高。模块边界清晰后,任何一层都可以替换实现,例如把大模型从A厂商切换到B厂商不会影响动作系统。
二、弹幕理解与响应优先级
高频弹幕是直播互动的主要来源,但弹幕质量参差不齐。短文本中存在大量错别字、缩写、颜文字和梗。逐条回复会迅速耗尽模型调用额度,也容易让Agent陷入无意义刷屏。因此,先做意图分类比直接生成回复更重要。可以将弹幕意图归为提问、点歌、动作请求、夸赞、负面节奏和闲聊。规则匹配处理高频固定指令,例如换个动作、唱首歌等;大模型处理开放型问题和需要角色发挥的内容。
展示弹幕事件数据结构。优先级由意图、用户等级、是否礼物等多个维度加权得到,冷却时间和黑名单规则在结构之外统一处理。这个结构需要保持轻量,方便在高并发下序列化和传输。
from dataclasses import dataclass
@dataclass
class DanmakuEvent:
user_id: str
text: str
intent: str
is_gift: bool
priority: int
timestamp: float
def compute_priority(event: DanmakuEvent) -> int:
score = 1
if event.is_gift:
score += 10
if event.intent == "question":
score += 5
if event.intent == "negative":
score -= 3
return max(0, score)
调度策略需要避免被节奏裹挟。给每类意图设置冷却时间,例如点歌类30秒内不再重复回应,动作类15秒。对负面节奏类弹幕,默认忽略或转移到安全回应,只有高等级粉丝的合理质疑才进入模型回复。还可以使用短时窗口计数,如果同一关键词在5秒内出现超过阈值,说明可能被刷屏,应触发降级策略而不是继续生成回复。这样既保留互动感,又避免Agent被弹幕带偏。
三、语音合成与动作同步控制
虚拟主播的呈现效果很大程度取决于声音和动作是否同步。TTS服务返回的音频通常不会自带动作事件,如果先等语音播放完再执行动作,就会显得僵硬。解决方案是在合成文本中插入时间标记。SSML 中的 <mark> 标签可以在音频时间线上标记点位,TTS服务播放到该位置时回调前端触发动作。例如打招呼时可以写一段带标记的合成文本。
下面是SSML示例。使用<mark>标记挥手动作,播放器捕获到wave标记后调用角色挥手动画。
<speak version="1.0" xmlns="http://www.w3.org/2001/10/synthesis"> 大家好呀<mark name="wave"/>今天来聊聊新玩法。 喜欢的话<mark name="heart"/>记得点个关注。 </speak>
如果TTS服务不支持SSML标记,可以在生成回复时返回动作时间线。后端根据每段文本估算时间偏移,前端使用音频播放进度来触发。更稳定的做法是让动作系统独立于音频,只要时间戳误差控制在200毫秒以内,观众很难察觉。口型同步可以借助音频能量或音素信息驱动,不必逐帧精确。动作库最好设计成可插拔,根据直播间主题替换动作资源。
四、状态记忆、上下文压缩与异常兜底
长时间直播会产生大量上下文。把完整弹幕历史塞给大模型既昂贵又容易超出上下文窗口。需要引入压缩记忆。可以把历史对话整理成角色印象、当前话题、观众关注点、待回应问题等结构化摘要。每过一段时间刷新,旧内容只保留摘要和关键用户信息。这样既能维持长期人设,又不会让请求体无限膨胀。
上下文压缩可以设计成两阶段。第一阶段用规则提取高价值弹幕和已执行动作,第二阶段用模型生成精简摘要。存储可用Redis或SQLite,但直播中必须异步写入,避免阻塞主循环。如果模型返回超时,Agent要立即使用预置兜底语句,同时记录告警。以下是一个简单的超时与兜底示例。
import asyncio
async def call_model_with_timeout(prompt: str, timeout: float = 3.0):
try:
result = await asyncio.wait_for(
model_client.generate(prompt),
timeout=timeout
)
return result
except asyncio.TimeoutError:
return "这个问题我需要想一想,稍后再聊。"
except Exception as exc:
logger.error("model call failed: %s", exc)
return "信号不太好,我们继续刚才的话题。"
异常兜底不能只考虑模型超时。直播平台断线、TTS合成失败、动作服务无响应都会影响直播。需要统一健康检查,每个执行模块定时上报状态。主循环根据健康状态降级:TTS失败时可以先发送文字弹幕回复,动作失败时继续用语音,模型频繁失败时切换到规则模式。人工运营人员也需要一个介入接口,在必要时接管直播间,避免安全事故。
最后,安全性设计要前置。所有弹幕内容进入模型前必须经过敏感词过滤,模型输出也要经过审核。涉及政治、色情、暴力等内容直接丢弃并记录。角色设定中应写清禁止行为,避免被诱导说出不符合平台规则的内容。良好的日志和回放机制还能帮助复盘直播事故,持续优化Agent策略。