1. 系统概述
1.1 背景
直播过程中需要持续与观众互动以活跃气氛,但主播无法兼顾所有评论。本系统作为直播间自动互动机器人,监听抖音直播间评论,自动生成语音回复并从服务端声卡播报(直播系统采集声卡输出),实现"评论区有人说话就回应、没人说话也不冷场"。
1.2 功能目标
| # | 功能 | 说明 |
|---|---|---|
| 1 | LLM 自动回复评论 | 调用 LLM 生成口语化回复,通过 TTS 语音播报(不是文字回复) |
| 2 | 欢迎词播报 | 检测"XXX来了 / XXX进入直播间"类消息,语音播报"欢迎 XXX"(优先级低,优先回复高价值互动) |
| 3 | 随机话题播报 | 没有回复和欢迎词时,LLM 围绕直播主题生成话题播报,防止冷场 |
| 4 | 话题延续性 | 话题与最近播报内容/用户评论关联,且不重复 |
| 5 | 前端管理页面 | #live-interact 独立页面:设置直播主题、开关各功能、查看播报日志 |
1.3 关键决策
| 决策项 | 选择 | 理由 |
|---|---|---|
| TTS 播放方式 | 服务端播放(pygame / ffplay) | 直播系统采集声卡输出,服务端播放最稳定,无需对接直播软件 |
| 直播主题 | 用户手动设置 | 在互动页面填写,LLM 参考主题生成话题 |
| 话题来源 | LLM 动态生成 | 结合主题与最近播报内容,保证延续性与不重复 |
| LLM 调用 | 复用 frameflow.llm.client |
JSON 配置 + 热加载,多 Provider 支持,互动场景优先免费 provider |
| 前端结构 | 独立新页面 | 与直播管理页(#live)分离,入口挂在该页 |
2. 整体架构
2.1 架构图
┌─────────────────────────────────────────────────────────────────┐
│ 抖音直播间(浏览器页面) │
└──────────────────────────────┬──────────────────────────────────┘
│ Playwright 抓取
┌──────────────────────────────▼──────────────────────────────────┐
│ live_monitor(评论监控) MonitorManager │
│ 评论去重 → 入库 live_comment → 回调通知 │
└──────────────────────────────┬──────────────────────────────────┘
│ on_comment 回调(非阻塞入队)
┌──────────────────────────────▼──────────────────────────────────┐
│ InteractionManager(每房间一个实例) │
│ │
│ ┌──────────────┐ ┌────────────────┐ ┌───────────────────┐ │
│ │ 评论队列 │──▶│ 决策线程 _run │──▶│ 播报任务队列(有界10)│ │
│ │ queue.Queue │ │ 0.3s 节流轮询 │ │ queue.Queue │ │
│ └──────────────┘ └───────┬────────┘ └─────────┬─────────┘ │
│ │ 提交 LLM 任务 │ 串行消费 │
│ ┌───────▼────────┐ ┌───────▼─────────┐ │
│ │ LLM 线程池 │ │ 播放线程 │ │
│ │ (2 workers) │ │ _play_worker │ │
│ │ 回复 / 话题 │ │ TTS生成→播放→日志│ │
│ └───────┬────────┘ └───────┬─────────┘ │
└─────────────────────────────┼───────────────────────┼────────────┘
│ │
┌───────────────▼─────────┐ ┌─────────▼──────────┐
│ frameflow.llm.client │ │ EdgeTTSService │
│ (config/llm_config.json) │ │ → output/live_tts │
│ agness 优先,mimo 兜底 │ │ AudioPlayer │
└─────────────────────────┘ │ pygame / ffplay │
└─────────┬──────────┘
│ 声卡输出
直播系统采集 → 观众
2.2 处理优先级
1. 欢迎词(无需 LLM,即时入播报队列,成本低、延迟小)
2. LLM 评论回复(高价值互动;同一时刻仅允许一个 LLM 任务在跑)
3. 随机话题(兜底:决策线程空闲且距上次话题 ≥ topic_interval 时才触发)
2.3 代码结构
| 文件 | 职责 |
|---|---|
service/live_interaction_service.py |
核心服务:互动管理器、决策循环、LLM 调用、欢迎词检测、违禁词过滤 |
service/audio_player.py |
服务端音频播放器(pygame 优先,ffplay 兜底),全局单例 audio_player |
api/live_interact.py |
互动管理 API,前缀 /api/live-interact |
db/models.py |
LiveInteraction(互动配置)、LiveBroadcastLog(播报日志) |
web/views/live-interact.html/.js |
前端互动管理页面(#live-interact 路由) |
frameflow/llm/client.py |
底层 LLM 客户端(多 Provider,JSON 配置热加载) |
frameflow/service/tts/text2speech_edge.py |
Edge-TTS 语音合成服务 |
3. 数据库设计
3.1 live_interaction(互动配置,每房间一条)
| 字段 | 类型 | 说明 |
|---|---|---|
| room_id | Text, 索引 | 关联 live_room.id(抖音房间号) |
| topic | Text | 直播主题(用户手动设置,供 LLM 参考) |
| is_active | Boolean | 是否正在互动(预留) |
| reply_enabled | Boolean, 默认 true | 是否启用评论回复 |
| welcome_enabled | Boolean, 默认 true | 是否启用欢迎词 |
| topic_enabled | Boolean, 默认 true | 是否启用随机话题 |
| topic_interval | Integer, 默认 60 | 随机话题最小间隔(秒) |
| voice | String(32), 默认 "xiaoxiao" | TTS 语音角色 |
3.2 live_broadcast_log(播报日志)
| 字段 | 类型 | 说明 |
|---|---|---|
| room_id | Text, 索引 | 房间 ID |
| broadcast_type | String(16) | reply / welcome / topic |
| content | Text | 播报文本 |
| triggered_by_comment | Text | 触发的原始评论(欢迎词/回复场景) |
| triggered_by_user | Text | 触发用户昵称 |
| llm_raw_reply | Text | LLM 原始返回(调试用) |
| llm_provider | String(32) | 实际使用的 LLM provider(成本核算依据) |
| llm_model | String(64) | 实际使用的模型名(成本核算依据) |
| created_at | DateTime | 播报时间 |
播报日志同时承担话题去重(LLM 提示词携带最近播报)与成本审计(每条记录实际 provider/model)两个职责。
3.3 依赖表
live_room:直播间基础信息(URL、主播昵称、状态等)。live_comment:评论流水(message_id唯一索引去重;message_type区分 chat/system/bottom)。
4. 核心模块设计
4.1 InteractionManager(房间互动管理器)
每个房间一个实例,包含 3 类线程 + 2 个队列:
| 组件 | 说明 |
|---|---|
评论队列 queue.Queue |
监控线程回调 on_comment 投递,决策线程消费(非阻塞) |
决策线程 _run |
0.3 秒节流轮询:① 检查 LLM 后台任务完成→入播报队列;② 消费评论队列做决策;③ 空闲且话题到期→提交话题生成 |
LLM 线程池 _LLM_POOL |
ThreadPoolExecutor(max_workers=2);同一时刻每房间仅 1 个 LLM 任务(_llm_future 占位),生成期间不阻塞决策线程 |
| 播报任务队列 | 有界队列(容量 10),满时丢弃最旧任务(信息轰炸保护) |
播放线程 _play_worker |
串行消费播报队列:TTS 生成(网络)→ 声卡播放(等播完)→ 写播报日志,保证语音不重叠 |
启动流程:
1. 清空评论队列与播报队列(防止上次会话残留);
2. 初始化评论水位线:取 live_comment 当前最大 ID,历史评论不重播;
3. 向 MonitorManager 注册评论回调(注册/注销使用同一回调引用);
4. 启动播放线程与决策线程(均为 daemon 线程)。
停止流程对称:注销回调 → 置停止事件 → join 线程(5s 超时)→ 清空队列 → audio_player.stop() 停止正在播放的语音。
4.2 评论决策(_handle_comment)
评论到达
├─ 欢迎词检测(welcome_enabled 时)
│ ├─ 形态1:抖音进入提示 bottom 消息
│ │ (content ∈ {来了, 来了来了, 来啦},username 字段即进入者昵称)
│ ├─ 形态2:弹幕自报家门 "XXX来了/进入直播间/来啦/加入直播间"
│ │ (先 _strip_decorations 剥离尾部 [表情]/emoji/标点,再正则 fullmatch;
│ │ 系统消息"欢迎 XX 进入直播间"自动剥掉"欢迎"前缀)
│ ├─ 过滤:违禁词昵称 / 含 ≥5 位连续数字的昵称 → 不播报
│ ├─ 冷却:距上次欢迎 < 5 秒 → 跳过
│ └─ 通过 → format_welcome_text 生成"欢迎 XX"
│ (尾缀 1~4 位数字保留播报;纯数字昵称改为"用户NNNN")→ 入播报队列
│
└─ LLM 回复(reply_enabled 时),需同时满足:
├─ msg_type == "chat"(普通聊天,非系统/bottom 消息)
├─ 非低价值评论(filter_reason 为空)
├─ 评论不含违禁词
├─ 无进行中的 LLM 任务(_llm_future is None)
├─ 距上次回复 ≥ 10 秒(REPLY_COOLDOWN)
└─ 长度 > 2 且不以 "/" 开头(排除指令类文本)
4.3 LLM 调用(_call_llm)
- Provider 策略:优先
agness(免费),失败自动回退mimo;两个都失败返回空文本(本轮放弃播报)。 - 调用链:
asyncio.run(chat_completion(...))→frameflow.llm.client,Provider 定义见config/llm_config.json(JSON 配置 + 文件 mtime 热加载,改配置无需重启)。 - 返回:
(文本, "provider/model"),后者随播报日志落库,用于成本核算。
两类提示词(均为 system + user 两段式):
| 场景 | 要点 | temperature |
|---|---|---|
回复 _llm_reply |
携带直播主题 + 最近播报(最多 3 条);语气亲切自然、符合直播场景;只返回回复内容;字数 100 字以内(在提示词中控制) | 0.7 |
话题 _llm_topic |
携带直播主题 + 最近播报(最多 3 条);与最近话题有关联但不重复;尽量延续上一次播报自然延伸;以陈述句为主(疑问句 ≤ 1/5);避免"评论区"一词;100 字以内 | 0.9 |
话题延续性与不重复的实现:决策线程维护 deque(maxlen=5) 的最近播报列表,每次生成话题/回复时把最近内容注入提示词。
4.4 播报队列与 TTL
播报任务带 created_at 时间戳,播放线程取出时检查 TTL:
| 参数 | 值 | 说明 |
|---|---|---|
TTS_QUEUE_SIZE |
10 | 队列上限,满时丢最旧 |
WELCOME_TTL |
10 秒 | 进入提示过时即无意义(人进来 10 秒后才欢迎很尴尬) |
REPLY_TTL |
60 秒 | 回复/话题过期丢弃 |
WELCOME_COOLDOWN |
5 秒 | 欢迎词最小间隔 |
REPLY_COOLDOWN |
10 秒 | 回复最小间隔 |
另外:播报进行中(_task_busy)到达的"来了"类进入提示直接丢弃,避免欢迎词堆积轰炸。
4.5 TTS 与音频播放
- 合成:
EdgeTTSService(微软 Edge TTS),输出目录output/live_tts/,文件名live_{room_id}_{time_ns}.mp3(纳秒时间戳防冲突)。 - 播放:
AudioPlayer单例,优先pygame.mixer,不可用回退ffplay -nodisp -autoexit;内部维护播放队列 + 锁,多线程调用安全、自动排队。 - 串行保证:播放线程在
audio_player.play()(异步播放)后轮询is_playing()等待播完,确保多条语音不重叠。 - 磁盘清理:决策线程每 ~30 秒清理一次超过 1 小时的历史音频,防止磁盘膨胀。
4.6 内容安全过滤
| 规则 | 作用范围 |
|---|---|
BANNED_WORDS 违禁词表(脏话/色情等中英文关键词,子串匹配,不区分大小写) |
昵称命中 → 不播欢迎词;评论命中 → 不触发 LLM 回复 |
| 昵称含 ≥5 位连续数字 | 判定为机器/广告号,不播欢迎词 |
| 昵称尾缀 1~4 位数字 | 保留数字播报(如"欢迎 阿明1234");纯数字昵称播"用户NNNN" |
5. API 接口
前缀:/api/live-interact(注册于 src/mooclawer/douyin/api/__init__.py)
| 接口 | 方法 | 说明 |
|---|---|---|
/{room_id} |
GET | 获取互动配置(不存在时自动创建默认配置),返回体含 is_running |
/{room_id} |
PUT | 更新配置(topic / reply_enabled / welcome_enabled / topic_enabled / topic_interval / voice,可选字段部分更新) |
/{room_id}/start |
POST | 启动互动;若该房间评论监控未开启会自动启动监控;已运行返回 400 |
/{room_id}/stop |
POST | 停止互动;未运行返回 400 |
/{room_id}/logs |
GET | 播报日志分页查询(page / page_size / broadcast_type 过滤,按时间倒序) |
/{room_id}/test |
POST | TTS 试播(自定义文本 + 语音角色),用于播放链路验证 |
6. 前端页面
- 路由:
http://localhost:8002/#live-interact(SPA 按需加载live-interact.html+live-interact.js) - 入口:
#live直播管理页增加【直播互动】入口 - 页面布局:
┌─────────────────────────────────────────────┐
│ 直播互动管理 [启动互动] │
├─────────────────────────────────────────────┤
│ 直播间: [下拉选择] │
│ 直播主题: [输入框 - 供LLM参考] │
│ │
│ ┌─ 互动设置 ─────────────────────────────┐ │
│ │ ☑ 评论回复 语音: [下拉] │ │
│ │ ☑ 欢迎词 话题间隔: [60]秒 │ │
│ │ ☑ 随机话题 │ │
│ └────────────────────────────────────────┘ │
│ │
│ ── 最近播报 ────────────────────────────── │
│ [时间] [类型] [内容] │
│ 14:30 回复 "谢谢支持,欢迎常来" │
│ 14:28 话题 "今天我们聊聊..." │
│ 14:25 欢迎 "欢迎小明进入直播间" │
└─────────────────────────────────────────────┘
7. 运行与验证
7.1 启动与依赖
- 直播互动依赖 mooclawer 服务(端口 8002):评论监控(Playwright 驱动浏览器读取弹幕)+ 本互动服务。
- LLM 依赖:
config/llm_config.json中对应 provider 的api_key_env环境变量(agness →AGNESS_KEY,mimo →MIMO_API_KEY)。
7.2 验证清单
- 后端语法检查:
python -m py_compile各新建/修改的.py文件。 - 数据库:
live_interaction/live_broadcast_log表自动创建。 - API 冒烟:
GET /api/live-interact/{room_id}能返回默认配置。 - TTS 播放:
POST /api/live-interact/{room_id}/test确认声卡出声。 - 前端:
http://localhost:8002/#live-interact页面加载正常。 - 端到端:选直播间 → 填主题 → 启动互动 → 直播间发评论 → 观察语音回复。
7.3 观测与成本核算
- 每条播报在
live_broadcast_log记录实际llm_provider/llm_model,按模型单价聚合即可核算互动场景的 LLM 成本。 - 运行日志(loguru)关键行:
[Interaction]前缀:互动启动/停止、回复、随机话题、丢弃的过期播报等;[LLM]前缀:底层 client 每次调用输出 provider、model、prompt_tokens、completion_tokens、total_tokens(用量日志,用于成本评估)。
8. 已知限制与后续优化
| 项 | 现状 | 可能的优化 |
|---|---|---|
| LLM 并发 | 每房间同一时刻仅 1 个 LLM 任务,回复有 10 秒冷却 | 高热度直播间可提升并发或缩短冷却 |
| 回复字数 | 需求为 ≤30 字,当前提示词约束为 100 字以内 | 如需严格 30 字,调整 _llm_reply 提示词并加输出长度校验重试 |
| 违禁词 | 静态关键词表 | 可接入 LLM 审核或平台敏感词库 |
| TTS | Edge-TTS(免费但依赖网络) | 可切换小米/火山等付费 TTS 提升音质,配置化切换 |
| 播报日志 | 记录 provider/model,未记录 token 数 | 可将 LLM usage 落库,实现自动成本统计报表 |
| 话题去重 | 靠提示词注入最近 5 条播报 | 可加相似度校验(如编辑距离/向量)硬保证不重复 |
以上就是本文的全部内容
如果觉得有帮助,欢迎 点赞 · 在看 · 转发,让更多朋友看到 👇
— END —