feat: 添加realtime_dialog和realtime_dialog_external_rag_test项目,更新test2项目

This commit is contained in:
User
2026-03-13 13:06:46 +08:00
parent 9dab61345c
commit 5521b673f5
215 changed files with 7626 additions and 1876 deletions

View File

@@ -1,6 +1,6 @@
# BigWo 智能语音对话系统 — 系统架构文档
> 版本:1.0 | 更新日期2026-03-09
> 版本:2.0 | 更新日期2026-03-13
---
@@ -12,7 +12,7 @@ BigWo 是一个**企业级智能客服对话系统**,支持**语音通话**和
| 能力 | 说明 |
|------|------|
| 实时语音对话 | 火山引擎 RTC + S2S 端到端语音大模型,混合编排模式 |
| 实时语音对话 | Native WebSocket + S2S 端到端语音大模型,混合编排模式 |
| 知识库问答 | Function Calling → 方舟私域知识库 → 本地知识库 |
| 文字对话 | Coze v3 Chat API支持 SSE 流式输出 |
| 语音↔文字切换 | 同一 sessionId 贯穿MySQL 持久化完整历史 |
@@ -22,7 +22,7 @@ BigWo 是一个**企业级智能客服对话系统**,支持**语音通话**和
- **前端**React 18 + Vite 5 + TailwindCSS 4 + Lucide Icons
- **后端**Node.js + Express 4 (port 3012)
- **语音**@volcengine/rtc SDK + S2S 端到端 + 方舟 LLM (API v2024-12-01)
- **语音**Native WebSocket (`wss://openspeech.bytedance.com/api/v3/realtime/dialogue`) + S2S 端到端 + 方舟 LLM
- **文字**Coze v3 Chat API流式 SSE
- **知识库**:方舟 Chat Completions API + knowledge_base metadata
- **数据库**MySQL 8 (mysql2/promise)
@@ -33,47 +33,50 @@ BigWo 是一个**企业级智能客服对话系统**,支持**语音通话**和
## 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 表 │ └──────────────
└─────────────────┘
┌───────────────────────────────────────────────────────────
│ 浏览器客户端
│ ┌────────────┐ ┌───────────┐ ┌────────────────────┐ │
│ │ VoicePanel │ │ ChatPanel │ │SessionHistoryPanel │ │
│ └─────┬──────┘ └─────┬─────┘ └────────────────────┘ │
│ │ │
│ ┌─────▼──────────┐ ┌─────────┐ ┌────────────────┐ │
│ │useNativeVoice │ chatApi │ │ voiceApi │ │
│ │ Chat (Hook) │(HTTP/SSE)│ │ (HTTP 封装) │ │
│ └─────┬──────────┘ └─────────┘ └────────────────┘ │
│ │
│ ┌─────▼──────────────┐ │
│ │nativeVoiceService │ │
│ │ (WebSocket+Audio) │ │
│ └─────┬──────────────┘ │
└────────┼────────────────┼─────────────────────────────────┘
│ WebSocket │ HTTPS
PCM 音频流 │ REST/SSE
┌──────────────────────────────────────────────────┐
Express 后端 (port 3001)
/ws/realtime-dialog — Native 语音网关 (核心)
│ routes/voice.js — 语音配置、直连会话
routes/chat.js — 文字对话 (Coze SSE)
routes/session.js — 会话列表、历史、删除
services/nativeVoiceGateway.js — WebSocket 网关
services/realtimeDialogProtocol.js — 二进制协议
services/realtimeDialogRouting.js — 意图路由
services/toolExecutor.js — 工具执行器
services/arkChatService.js — 方舟 LLM
services/cozeChatService.js — Coze Chat
db/index.js — MySQL CRUD │
└──────┬───────────────┬────────────────────────────┘
│ WebSocket
┌───────────────┐ ┌─────────────────┐ ┌──────────────
│ 火山 Realtime │ MySQL 8 │ 方舟知识库 │
│ Dialog 服务 │ │ sessions 表 │ │ (远程 API) │
│ (S2S + LLM) │ │ messages 表 │ └──────────────┘
└───────────────┘ └─────────────────┘
```
---
@@ -84,157 +87,113 @@ BigWo 是一个**企业级智能客服对话系统**,支持**语音通话**和
test2/
├── client/ # 前端React + Vite
│ ├── src/
│ │ ├── App.jsx # 主应用,语音/文字模式切换
│ │ ├── App.jsx # 主应用,模式切换 + 会话管理
│ │ ├── components/
│ │ │ ├── VoicePanel.jsx # 语音通话界面
│ │ │ ├── ChatPanel.jsx # 文字对话界面SSE 流式)
│ │ │ ├── SessionHistoryPanel.jsx # 会话历史侧边栏
│ │ │ ├── SettingsPanel.jsx # 语音参数设置面板
│ │ │ └── SubtitleDisplay.jsx# 实时字幕展示
│ │ ├── hooks/
│ │ │ └── useVoiceChat.js # 语音通话核心 Hook
│ │ │ └── useNativeVoiceChat.js # Native WebSocket 语音 Hook
│ │ └── services/
│ │ ├── rtcService.js # RTC SDK 封装WebRTC
│ │ ├── voiceApi.js # 语音 HTTP 请求
│ │ ├── nativeVoiceService.js # WebSocket 语音服务(音频采集/播放
│ │ ├── voiceApi.js # 语音/会话 HTTP 请求
│ │ └── chatApi.js # 文字 HTTP/SSE 请求
│ └── vite.config.js
├── server/ # 后端Express
│ ├── app.js # 入口 + FC 回调 raw body 解析
│ ├── app.js # 入口,启动 HTTP + WebSocket 服务
│ ├── routes/
│ │ ├── voice.js # 语音全生命周期 + FC 回调(核心)
│ │ ├── chat.js # 文字对话Coze
│ │ └── session.js # 会话历史 & 模式切换
│ │ ├── voice.js # 语音配置、直连会话、知识库查询
│ │ ├── chat.js # 文字对话Coze SSE 流式
│ │ └── session.js # 会话列表、历史、删除、模式切换
│ ├── services/
│ │ ├── volcengine.js # 火山引擎 OpenAPI 签名调用
│ │ ├── nativeVoiceGateway.js # WebSocket 语音网关(核心)
│ │ ├── realtimeDialogProtocol.js # 二进制协议编解码
│ │ ├── realtimeDialogRouting.js # 意图路由 + 工具调度
│ │ ├── toolExecutor.js # 工具执行器
│ │ ├── arkChatService.js # 方舟 LLM(文字场景备选)
│ │ ├── 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. 语音通话模块Native WebSocket 方案)
### 4.1 混合编排模式OutputMode=1
### 4.1 混合编排模式
S2S 端到端模型处理普通闲聊(低延迟 ~300-800ms),方舟 LLM 同时决策是否需要调用工具。两者并行运行。
S2S 端到端模型处理普通闲聊(低延迟),方舟 LLM 同时决策是否需要调用工具。两者并行运行。
### 4.2 会话生命周期
```
POST /prepare → 创建房间 + 生成 RTC Token + 分配 TaskId
客户端 WebSocket 连接 → /ws/realtime-dialog
客户端 joinRoom() → 用户进入 RTC 房间、开启麦克风
nativeVoiceGateway 创建会话 → 连接上游 Realtime Dialog 服务
POST /start → 构建配置 → StartVoiceChat API → AI Bot 进房
客户端采集麦克风 PCM → 发送音频帧 → 上游 ASR + S2S
实时语音对话S2S 直接回复 + LLM 工具决策)
上游返回字幕/音频 → realtimeDialogRouting 意图路由
POST /stop → StopVoiceChat API → 返回字幕 → 可切换文字模式
工具调用或闲聊回复 → ChatTTSText 注入语音流 → 客户端播放
客户端断开 WebSocket → 会话结束 → 可切换文字模式
```
### 4.3 语音 API 端点voice.js
### 4.3 语音相关端点
| 端点 | 方法 | 说明 |
| 端点 | 类型 | 说明 |
|------|------|------|
| `/ws/realtime-dialog` | WebSocket | **核心**Native 语音网关 |
| `/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 房间消息 |
| `/api/voice/direct/session` | POST | 创建直连会话 |
| `/api/voice/direct/message` | POST | 添加消息 |
| `/api/voice/direct/query` | POST | 知识库直接查询 |
| `/api/voice/stop` | POST | 停止会话 |
### 4.4 内存数据映射
### 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 收集 |
| 文件 | 职责 |
|------|------|
| `nativeVoiceGateway.js` | WebSocket 网关,管理客户端↔上游连接、音频流转发、消息持久化 |
| `realtimeDialogProtocol.js` | 二进制协议编解码(消息类型、标志位、序列化/反序列化) |
| `realtimeDialogRouting.js` | 意图路由(规则+LLM 双层决策)、工具调度、语音播报 |
---
## 5. Function Calling 回调处理(核心)
## 5. 语音意图路由与工具调用
### 5.1 数据流
```
RTC 服务 (LLM 触发 tool_call)
HTTP POST无 Content-Typebody 为 JSON
用户语音 ASR 识别文本
上游 Realtime Dialog 服务返回
app.js 手动读取 raw body → JSON.parse → 分配 _seq 序列号
nativeVoiceGateway 接收字幕 → 持久化用户语音到 DB
realtimeDialogRouting.resolveReply()
├─ 规则路由:时间/天气/订单/计算 → 直接工具调用
├─ search_knowledge → 方舟知识库 API
└─ chat → 方舟 LLM 闲聊回复
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 提取中文字符)
工具结果/LLM回复 → ChatTTSText 注入上游语音流
发送 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 用知识库内容语音回复
AI 语音播报 + 持久化到 DB
```
### 5.2 关键设计决策
### 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 → 解析二进制消息中的字幕数据
```
| 意图识别 | 规则路由 + LLM 路由双层决策 |
| S2S 与工具冲突 | ChatTTSText 注入工具结果覆盖 S2S 直连回复 |
| 音频编解码 | realtimeDialogProtocol 自定义二进制协议 |
| 语音文本分段 | 按标点切分,估算播报时长,分段 TTS |
---
@@ -282,12 +241,14 @@ SSE 流式返回 → 前端逐字展示
| `chat_user` | 文字对话用户输入 |
| `chat_bot` | 文字对话 AI 回复 |
### 7.3 模式切换 APIsession.js
### 7.3 会话管理 APIsession.js
| 端点 | 方法 | 说明 |
|------|------|------|
| `/api/session/list` | GET | 获取会话列表(带最后消息预览) |
| `/api/session/:id/history` | GET | 获取完整历史(支持 llm/full 格式) |
| `/api/session/:id/switch` | POST | 切换模式,返回上下文历史 |
| `/api/session/:id` | DELETE | 删除会话及其消息 |
### 7.4 数据库表
@@ -302,30 +263,32 @@ SSE 流式返回 → 前端逐字展示
### 8.1 组件树
```
App.jsx # 模式切换 + 全局设置
App.jsx # 模式切换 + 全局设置 + 会话管理
├── VoicePanel.jsx # 语音通话 UI开始/结束/静音/时长)
│ └── SubtitleDisplay # 实时字幕definite/interim 区分)
├── ChatPanel.jsx # 文字对话 UI消息列表 + SSE 流式显示)
├── SessionHistoryPanel.jsx # 会话历史侧边栏(新建/切换/删除会话)
└── SettingsPanel.jsx # 设置面板(模型/音色/系统角色/VAD
```
### 8.2 useVoiceChat Hook
### 8.2 useNativeVoiceChat Hook
管理语音通话完整生命周期:
管理 Native WebSocket 语音通话完整生命周期:
- **start(options)**prepare → joinRoom → startVoiceChat
- **stop()**leaveRoom → stopVoiceChat → 返回字幕
- **start(options)**连接 WebSocket → 采集麦克风 PCM → 发送音频帧
- **stop()**断开 WebSocket → 返回字幕和 sessionId
- **toggleMute()**:静音/取消静音
- **状态**isActive, isMuted, isConnecting, subtitles, duration, error
### 8.3 rtcService.js
### 8.3 nativeVoiceService.js
封装 @volcengine/rtc SDK
封装 Native WebSocket 语音连接
- **init(appId)**:创建引擎、注册事件监听
- **joinRoom()**:入房 + 开始音频采集 + 启用字幕
- **事件监听**:字幕(onSubtitleMessageReceived)、房间消息(binary/text)、诊断(音量/流)
- **方案B 消息转发**:所有房间消息 → onRoomMessage 回调 → useVoiceChat → 后端
- **connect(options)**:建立 WebSocket 连接初始化音频采集getUserMedia + Web Audio API 降采样)
- **disconnect()**:关闭连接、停止音频
- **setMuted(muted)**:控制麦克风
- **事件回调**onSubtitle字幕、onConnectionStateChange连接状态、onError错误
- **音频播放**:接收 PCM 音频帧,通过 Web Audio API 播放
---
@@ -335,10 +298,6 @@ App.jsx # 模式切换 + 全局设置
| 变量 | 说明 |
|------|------|
| `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 |
@@ -352,8 +311,8 @@ App.jsx # 模式切换 + 全局设置
| `VOLC_ARK_KNOWLEDGE_BASE_IDS` | 方舟私域知识库数据集 ID逗号分隔 |
| `VOLC_ARK_API_KEY` | 方舟 API Key |
| `VOLC_WEBSEARCH_API_KEY` | 联网搜索 Key |
| `FC_SERVER_URL` | FC 回调地址 |
| `FC_SIGNATURE` | FC 回调签名 |
| `VOLC_S2S_SPEAKER_ID` | 自定义音色 ID |
| `ENABLE_NATIVE_VOICE_GATEWAY` | 语音网关开关(默认开启,设 false 关闭) |
| `MYSQL_HOST/PORT/USER/PASSWORD/DATABASE` | MySQL 配置 |
---
@@ -363,13 +322,13 @@ App.jsx # 模式切换 + 全局设置
```
互联网用户
▼ HTTPS (443)
▼ HTTPS (443) + WSS
┌──────────────┐
│ Nginx │ ← 宝塔面板管理
│ (反向代理) │
│ SSL 终止 │
└──────┬───────┘
│ http://localhost:3012
│ http://localhost:3001
┌──────────────┐
│ PM2 │ ← ecosystem.config.js
@@ -378,7 +337,7 @@ App.jsx # 模式切换 + 全局设置
└──────┬───────┘
├── MySQL 8 (localhost:3306)
├── 火山引擎 RTC API (rtc.volcengineapi.com)
├── 火山 Realtime Dialog (wss://openspeech.bytedance.com)
├── 方舟 LLM API (ark.cn-beijing.volces.com)
└── Coze API (api.coze.cn)
```
@@ -433,8 +392,8 @@ App.jsx # 模式切换 + 全局设置
## 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
1. **天气和订单工具为 Mock**queryWeather 和 queryOrder 使用硬编码数据,可接入真实 API
2. **本地知识库简陋**searchLocalKnowledge 仅 5 条硬编码记录,需接入真实知识库
3. **方舟 LLM Mock 模式**:未配置 VOLC_ARK_ENDPOINT_ID 时返回硬编码回复
4. **文字对话依赖 Coze**Coze 未配置时文字对话模式不可用,可考虑 fallback 到 arkChatService
5. **知识库冷启动**:方舟 KB 首次查询较慢(~10s后续查询 ~3-5s