17 KiB
17 KiB
BigWo 智能语音对话系统 — 系统架构文档
版本:1.0 | 更新日期:2026-03-09
1. 系统概述
BigWo 是一个企业级智能客服对话系统,支持语音通话和文字对话两种交互模式,可无缝切换且保持上下文连续。
核心能力
| 能力 | 说明 |
|---|---|
| 实时语音对话 | 火山引擎 RTC + S2S 端到端语音大模型,混合编排模式 |
| 知识库问答 | Function Calling → 方舟私域知识库 → 本地知识库 |
| 文字对话 | Coze v3 Chat API,支持 SSE 流式输出 |
| 语音↔文字切换 | 同一 sessionId 贯穿,MySQL 持久化完整历史 |
| 工具调用 | 知识库检索、天气、订单、时间、计算 |
技术栈
- 前端:React 18 + Vite 5 + TailwindCSS 4 + Lucide Icons
- 后端:Node.js + Express 4 (port 3012)
- 语音:@volcengine/rtc SDK + S2S 端到端 + 方舟 LLM (API v2024-12-01)
- 文字:Coze v3 Chat API(流式 SSE)
- 知识库:方舟 Chat Completions API + knowledge_base metadata
- 数据库:MySQL 8 (mysql2/promise)
- 部署:PM2 (cluster) + Nginx 反向代理 + 宝塔面板
2. 系统架构图
┌─────────────────────────────────────────────────────────┐
│ 浏览器客户端 │
│ ┌────────────┐ ┌───────────┐ ┌──────────────────┐ │
│ │ VoicePanel │ │ ChatPanel │ │ SettingsPanel │ │
│ └─────┬──────┘ └─────┬─────┘ └──────────────────┘ │
│ │ │ │
│ ┌─────▼──────┐ ┌─────▼─────┐ ┌────────────────┐ │
│ │useVoiceChat│ │ chatApi │ │ voiceApi │ │
│ │ (Hook) │ │ (HTTP/SSE)│ │ (HTTP 封装) │ │
│ └─────┬──────┘ └─────┬─────┘ └────────────────┘ │
│ │ │ │
│ ┌─────▼──────┐ │ │
│ │ rtcService │ │ │
│ │(WebRTC SDK)│ │ │
│ └─────┬──────┘ │ │
└────────┼───────────────┼────────────────────────────────┘
│ WebRTC │ HTTPS
│ 音频流 │ REST/SSE
▼ ▼
┌────────────────┐ ┌─────────────────────────────────────┐
│ 火山引擎 RTC │ │ Express 后端 (port 3012) │
│ 云端服务 │ │ │
│ ┌────────────┐ │ │ routes/voice.js — 语音全生命周期 │
│ │S2S 端到端 │ │ │ routes/chat.js — 文字对话 │
│ │语音大模型 │ │ │ routes/session.js — 会话管理 │
│ ├────────────┤ │ │ │
│ │方舟 LLM │ │ │ services/volcengine.js — OpenAPI │
│ │(工具决策) │ │ │ services/toolExecutor.js — 工具 │
│ └─────┬──────┘ │ │ services/cozeChatService.js — Coze │
│ │FC 回调 │ │ services/arkChatService.js — 方舟 │
│ │(HTTP) │ │ config/voiceChatConfig.js — 配置 │
│ ▼ │ │ db/index.js — MySQL │
│ fc_callback ──►│ │ │
│ ◄── Update ────│ └─────────────────────────────────────┘
└────────────────┘ │
▼
┌─────────────────┐ ┌──────────────┐
│ MySQL 8 │ │ 方舟知识库 │
│ sessions 表 │ │ (远程 API) │
│ messages 表 │ └──────────────┘
└─────────────────┘
3. 目录结构
test2/
├── client/ # 前端(React + Vite)
│ ├── src/
│ │ ├── App.jsx # 主应用,语音/文字模式切换
│ │ ├── components/
│ │ │ ├── VoicePanel.jsx # 语音通话界面
│ │ │ ├── ChatPanel.jsx # 文字对话界面(SSE 流式)
│ │ │ ├── SettingsPanel.jsx # 语音参数设置面板
│ │ │ └── SubtitleDisplay.jsx# 实时字幕展示
│ │ ├── hooks/
│ │ │ └── useVoiceChat.js # 语音通话核心 Hook
│ │ └── services/
│ │ ├── rtcService.js # RTC SDK 封装(WebRTC)
│ │ ├── voiceApi.js # 语音 HTTP 请求
│ │ └── chatApi.js # 文字 HTTP/SSE 请求
│ └── vite.config.js
├── server/ # 后端(Express)
│ ├── app.js # 入口 + FC 回调 raw body 解析
│ ├── routes/
│ │ ├── voice.js # 语音全生命周期 + FC 回调(核心)
│ │ ├── chat.js # 文字对话(Coze)
│ │ └── session.js # 会话历史 & 模式切换
│ ├── services/
│ │ ├── volcengine.js # 火山引擎 OpenAPI 签名调用
│ │ ├── toolExecutor.js # 工具执行器
│ │ ├── arkChatService.js # 方舟 LLM(文字场景备选)
│ │ └── cozeChatService.js # Coze Chat API(文字主服务)
│ ├── config/
│ │ ├── voiceChatConfig.js # StartVoiceChat 配置构建器
│ │ └── tools.js # FC 工具定义(5 个工具)
│ ├── db/index.js # MySQL CRUD
│ ├── lib/token.js # RTC Token 生成
│ └── .env # 环境变量
└── ecosystem.config.js # PM2 部署配置
4. 语音通话模块
4.1 混合编排模式(OutputMode=1)
S2S 端到端模型处理普通闲聊(低延迟 ~300-800ms),方舟 LLM 同时决策是否需要调用工具。两者并行运行。
4.2 会话生命周期
POST /prepare → 创建房间 + 生成 RTC Token + 分配 TaskId
↓
客户端 joinRoom() → 用户进入 RTC 房间、开启麦克风
↓
POST /start → 构建配置 → StartVoiceChat API → AI Bot 进房
↓
实时语音对话(S2S 直接回复 + LLM 工具决策)
↓
POST /stop → StopVoiceChat API → 返回字幕 → 可切换文字模式
4.3 语音 API 端点(voice.js)
| 端点 | 方法 | 说明 |
|---|---|---|
/api/voice/config |
GET | 获取模型、音色列表 |
/api/voice/prepare |
POST | 创建房间、生成 Token |
/api/voice/start |
POST | 启动 AI 语音对话 |
/api/voice/stop |
POST | 停止对话、返回字幕 |
/api/voice/fc_callback |
POST | FC 回调(RTC 服务端→服务端) |
/api/voice/subtitle |
POST | 客户端转发确认字幕 |
/api/voice/room_message |
POST | 客户端转发 RTC 房间消息 |
4.4 内存数据映射
voice.js 维护以下 Map 用于会话状态关联:
| Map 名称 | Key | Value | 用途 |
|---|---|---|---|
activeSessions |
sessionId | 完整会话对象 | 会话生命周期管理 |
roomToBotUserId |
roomId | botUserId | FC 回调→UpdateVoiceChat |
roomToHumanUserId |
roomId | userId | 日志追踪 |
roomToSessionId |
roomId | sessionId | DB 写入关联 |
roomToTaskId |
roomId | taskId | UpdateVoiceChat 必须用此 TaskId |
latestUserSpeech |
roomId | {text, timestamp} | FC 参数解析兜底 |
toolCallBuffers |
TaskID | buffer 对象 | FC chunk 收集 |
5. Function Calling 回调处理(核心)
5.1 数据流
RTC 服务 (LLM 触发 tool_call)
│ HTTP POST(无 Content-Type,body 为 JSON)
▼
app.js 手动读取 raw body → JSON.parse → 分配 _seq 序列号
│
▼
voice.js fc_callback 路由
│
├─ FormatA: Type="tool_calls" → OpenAI 格式数组,流式 chunk
├─ FormatB: Type="information" → RTC 原生格式
└─ FormatC: 会话状态回调 → 记录日志
│
▼(FormatA 为主)
chunk 缓冲收集(toolCallBuffers Map,1s 超时触发)
│
▼ 1s 后
参数解析尝试链:
① JSON.parse(拼接 chunks)
② latestUserSpeech(ASR 用户语音兜底)
③ extractReadableText(从 chunks 提取中文字符)
│
▼
发送 interrupt 打断 S2S 直接回复
│ UpdateVoiceChat({ Command: "interrupt", TaskId: sessionTaskId })
▼
执行工具(toolExecutor.js)
│ search_knowledge: 方舟 KB(30s) → 本地 KB
▼
回传结果
│ UpdateVoiceChat({
│ Command: "function",
│ TaskId: sessionTaskId, ← 必须是 StartVoiceChat 的 TaskId
│ Message: JSON.stringify({ ToolCallID, Content })
│ })
▼
AI 用知识库内容语音回复
5.2 关键设计决策
| 问题 | 解决方案 |
|---|---|
| FC 回调无 Content-Type | app.js 在 express.json() 之前手动读取 raw body |
| Chunk 乱序且不完整 | 1s 定时器收集全部 chunks 后拼接 |
| JSON.parse 失败 | 用 ASR 用户语音文本作为查询参数(方案B) |
| S2S 直接回复覆盖 FC 结果 | 先发 interrupt 打断,再发 function 结果 |
| TaskId 不匹配 | roomToTaskId 存储 StartVoiceChat 的 TaskId |
| HTTP 响应超时 | 立即返回 200,异步执行工具 |
5.3 用户语音文本获取(方案B)
FC 回调的 arguments 经常乱序无法解析,因此需要从其他途径获取用户的原始问题:
客户端 RTC SDK
│ onSubtitleMessageReceived / onRoomBinaryMessageReceived
▼
useVoiceChat.js
│ 转发 definite=true 的用户字幕
▼
POST /api/voice/subtitle → latestUserSpeech.set(roomId, text)
POST /api/voice/room_message → 解析二进制消息中的字幕数据
6. 文字对话模块
6.1 架构
通过 Coze v3 Chat API 实现,Coze Bot 内置知识库插件。
用户输入 → POST /api/chat/send-stream
↓
cozeChatService.chatStream()
│ 首次对话注入语音历史作为上下文
│ Coze 自动管理 conversation_id
↓
SSE 流式返回 → 前端逐字展示
6.2 文字 API 端点(chat.js)
| 端点 | 方法 | 说明 |
|---|---|---|
/api/chat/start |
POST | 创建会话,注入语音历史上下文 |
/api/chat/send |
POST | 非流式发送 |
/api/chat/send-stream |
POST | SSE 流式发送 |
/api/chat/history/:id |
GET | 获取会话状态 |
/api/chat/:id |
DELETE | 删除会话 |
7. 会话管理与模式切换
7.1 统一 sessionId
同一个 sessionId 贯穿语音和文字模式,所有消息持久化到 MySQL messages 表。
7.2 消息来源标记
| source 值 | 说明 |
|---|---|
voice_asr |
语音 ASR 识别的用户文本 |
voice_bot |
AI 语音回复字幕 |
voice_tool |
语音场景工具调用结果 |
chat_user |
文字对话用户输入 |
chat_bot |
文字对话 AI 回复 |
7.3 模式切换 API(session.js)
| 端点 | 方法 | 说明 |
|---|---|---|
/api/session/:id/history |
GET | 获取完整历史(支持 llm/full 格式) |
/api/session/:id/switch |
POST | 切换模式,返回上下文历史 |
7.4 数据库表
sessions:id(PK), user_id, mode(voice/chat), created_at, updated_at
messages:id(AI PK), session_id, role(user/assistant/tool/system), content, source, tool_name, created_at
8. 客户端组件
8.1 组件树
App.jsx # 模式切换 + 全局设置
├── VoicePanel.jsx # 语音通话 UI(开始/结束/静音/时长)
│ └── SubtitleDisplay # 实时字幕(definite/interim 区分)
├── ChatPanel.jsx # 文字对话 UI(消息列表 + SSE 流式显示)
└── SettingsPanel.jsx # 设置面板(模型/音色/系统角色/VAD)
8.2 useVoiceChat Hook
管理语音通话完整生命周期:
- start(options):prepare → joinRoom → startVoiceChat
- stop():leaveRoom → stopVoiceChat → 返回字幕
- toggleMute():静音/取消静音
- 状态:isActive, isMuted, isConnecting, subtitles, duration, error
8.3 rtcService.js
封装 @volcengine/rtc SDK:
- init(appId):创建引擎、注册事件监听
- joinRoom():入房 + 开始音频采集 + 启用字幕
- 事件监听:字幕(onSubtitleMessageReceived)、房间消息(binary/text)、诊断(音量/流)
- 方案B 消息转发:所有房间消息 → onRoomMessage 回调 → useVoiceChat → 后端
9. 环境变量
必需
| 变量 | 说明 |
|---|---|
VOLC_RTC_APP_ID |
RTC 应用 ID |
VOLC_RTC_APP_KEY |
RTC 应用密钥(生成 Token) |
VOLC_ACCESS_KEY_ID |
火山引擎 AK(API 签名) |
VOLC_SECRET_ACCESS_KEY |
火山引擎 SK |
VOLC_S2S_APP_ID |
S2S 端到端语音 AppID |
VOLC_S2S_TOKEN |
S2S Token |
VOLC_ARK_ENDPOINT_ID |
方舟 LLM 推理接入点 ID |
可选
| 变量 | 说明 |
|---|---|
COZE_API_TOKEN |
Coze 智能体 Token(文字对话) |
COZE_BOT_ID |
Coze Bot ID |
VOLC_ARK_KNOWLEDGE_BASE_IDS |
方舟私域知识库数据集 ID(逗号分隔) |
VOLC_ARK_API_KEY |
方舟 API Key |
VOLC_WEBSEARCH_API_KEY |
联网搜索 Key |
FC_SERVER_URL |
FC 回调地址 |
FC_SIGNATURE |
FC 回调签名 |
MYSQL_HOST/PORT/USER/PASSWORD/DATABASE |
MySQL 配置 |
10. 部署架构
互联网用户
│
▼ HTTPS (443)
┌──────────────┐
│ Nginx │ ← 宝塔面板管理
│ (反向代理) │
│ SSL 终止 │
└──────┬───────┘
│ http://localhost:3012
▼
┌──────────────┐
│ PM2 │ ← ecosystem.config.js
│ bigwo-server│
│ (Node.js) │
└──────┬───────┘
│
├── MySQL 8 (localhost:3306)
├── 火山引擎 RTC API (rtc.volcengineapi.com)
├── 方舟 LLM API (ark.cn-beijing.volces.com)
└── Coze API (api.coze.cn)
PM2 配置
- 进程名:
bigwo-server - 工作目录:
/www/wwwroot/demo.tensorgrove.com.cn/server - 日志路径:
/var/log/bigwo/server-out.log、server-error.log - 内存限制:512M 自动重启
11. 工具定义(tools.js)
系统定义了 5 个 Function Calling 工具:
| 工具 | 参数 | 说明 |
|---|---|---|
search_knowledge |
query: string | 核心工具,强制优先调用 |
query_weather |
city: string | 天气查询(Mock) |
query_order |
order_id: string | 订单查询(Mock) |
get_current_time |
无 | 当前时间 |
calculate |
expression: string | 数学计算 |
search_knowledge 查询链
方舟私域知识库 (30s 超时)
│ POST https://ark.cn-beijing.volces.com/api/v3/chat/completions
│ metadata.knowledge_base: { dataset_ids, top_k: 3, threshold: 0.5 }
│
│ 失败 ↓
▼
本地知识库 (即时,关键词匹配)
│ 覆盖:退货、退款、配送、保修、会员
12. 安全与限制
- API 签名:所有火山引擎 API 调用使用 AK/SK HMAC 签名
- FC 回调签名:ServerSignature 校验(当前信任模式)
- 计算工具防注入:仅允许
0-9 + - * / ( ) . %字符 - CORS:已开启(
cors()中间件) - Body 限制:1MB
- 会话过期:文字对话 30 分钟自动清理
- 环境变量:敏感信息存于
.env,启动时校验
13. 已知问题与优化方向
- FC 响应延迟:用户提问到 AI 用知识库回答约需 12-15s(LLM 决策 ~8s + KB 查询 ~5s),期间有静默
- Chunk 乱序:RTC FC 回调的 tool_call arguments 被拆成单字符 chunk 且乱序,只能靠 ASR 文本兜底
- S2S 与 LLM 并行冲突:S2S 会先给出直接回复,需 interrupt 打断后再发 FC 结果
- Mock 工具:天气和订单工具目前为 Mock 数据,可接入真实 API
- 知识库冷启动:方舟 KB 首次查询较慢(~10s),后续查询 ~3-5s