# 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. 已知问题与优化方向 1. **FC 响应延迟**:用户提问到 AI 用知识库回答约需 12-15s(LLM 决策 ~8s + KB 查询 ~5s),期间有静默 2. **Chunk 乱序**:RTC FC 回调的 tool_call arguments 被拆成单字符 chunk 且乱序,只能靠 ASR 文本兜底 3. **S2S 与 LLM 并行冲突**:S2S 会先给出直接回复,需 interrupt 打断后再发 FC 结果 4. **Mock 工具**:天气和订单工具目前为 Mock 数据,可接入真实 API 5. **知识库冷启动**:方舟 KB 首次查询较慢(~10s),后续查询 ~3-5s