Files
bigwo/test2/ARCHITECTURE.md
2026-03-12 12:47:56 +08:00

441 lines
17 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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-Typebody 为 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 Map1s 超时触发)
▼ 1s 后
参数解析尝试链:
① JSON.parse(拼接 chunks)
② latestUserSpeechASR 用户语音兜底)
③ 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 模式切换 APIsession.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` | 火山引擎 AKAPI 签名) |
| `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-15sLLM 决策 ~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