docs: 文档与代码一致性检查与修复

- 修复心跳 Bug:应用层 ping 不更新 lastPong,60 秒后连接会被错误断开
- 03-接口文档:audio/mpeg→audio/mp3、STTConfig/TTSConfig 补充 Model 字段、
  APP_ENV 环境变量名修正、配置搜索路径补充、.env 加载说明、Vite proxy 说明修正
- 02-系统架构:补充 ConfigPanel/Toast 组件、Model Router/Rate Limiter 标注规划中、
  补充 Gin 框架、MVP 存储改为 Memory、AI 服务 provider 更新、Orchestrator 伪代码对齐
- 04-技术选型:新增 AI 服务栈选型章节(STT/LLM/TTS)、PostgreSQL 标注规划中
- 06-语音交互:VAD 参数名修正、STT 改为一次性识别描述、音频编码格式补充
- 07-视觉理解:关键帧检测代码改为 TypeScript、分辨率修正、阈值逻辑统一
- 08-成本控制:变量名修正、未实现功能标注规划中、对话历史裁剪策略补充
- CLAUDE.md:同步更新技术栈、模块结构、存储策略描述
This commit is contained in:
hhs
2026-06-14 08:52:36 +08:00
parent a6ce6d9c4f
commit 6e0c67e1cb
8 changed files with 171 additions and 170 deletions

View File

@@ -9,7 +9,7 @@
| 层级 | 职责 | 关键约束 |
|------|------|---------|
| **客户端(浏览器)** | 媒体采集、边缘预处理、UI 渲染 | 浏览器资源有限,模型需轻量 |
| **Go 网关** | 会话管理、模型路由、AI 服务编排 | 高并发、低延迟、状态管理 |
| **Go 网关** | 会话管理、AI 服务编排、流式管道 | 高并发、低延迟、状态管理 |
| **AI 服务** | LLM 推理、语音识别、语音合成 | 按量计费,需控制调用频率 |
> 为什么要单独加一层 Go 网关,而不是让前端直连 AI API1API Key 安全性2统一的速率限制和成本管控3多模型路由逻辑集中在一处便于维护。
@@ -22,7 +22,7 @@
|------|------|---------|
| 框架 | React 18 + TypeScript | 组件化开发,类型安全,生态成熟 |
| 构建 | Vite | 开发热更新快,构建产物小 |
| 实时通信 | WebSocket原生 API | 浏览器原生支持,无需额外依赖 |
| 实时通信 | WebSocket原生 API + 自封装连接管理 | 浏览器原生支持,封装心跳/重连/消息分发 |
| 边缘推理 | ONNX Runtime Web | 浏览器端跑轻量模型VAD、关键帧检测 |
| 语音检测 | @ricky0123/vad-web | 基于 WebRTC VAD纯前端零延迟 |
| 媒体采集 | MediaDevices API | 浏览器原生摄像头/麦克风访问 |
@@ -32,22 +32,22 @@
| 技术 | 选型 | 选择理由 |
|------|------|---------|
| 语言 | Go | 高并发 goroutine 模型,适合长连接管理 |
| HTTP 框架 | Gin | 高性能 HTTP 路由,中间件生态成熟 |
| WebSocket | gorilla/websocket | Go 生态最成熟的 WebSocket 库 |
| 会话存储 | Redis | 高速 KV 存储,适合会话状态和上下文缓存 |
| 持久化存储 | PostgreSQL | 对话历史、用量统计、用户偏好MVP 阶段可选 |
| 配置管理 | Viper | 支持 YAML + 环境变量覆盖,详见 `03-接口文档.md` 第六章 |
| 会话存储 | Redis(规划中) / MemoryMVP 默认) | 高速 KV 存储MVP 阶段使用进程内存,可通过配置切换到 Redis |
| 持久化存储 | PostgreSQL(规划中) | 对话历史、用量统计、用户偏好MVP 阶段未实现 |
| 配置管理 | Viper + godotenv | 支持 YAML + .env + 环境变量覆盖,详见 `03-接口文档.md` 第六章 |
| 日志 | Zap | 高性能结构化日志 |
### AI 服务
| 能力 | 主选方案 | 备选方案 | 选型考量 |
|------|---------|---------|---------|
| 多模态 LLM | GPT-4o | Claude Sonnet | 视觉理解能力强API 成熟 |
| 语音识别 STT | Deepgram | FunASR 自部署 | 流式识别延迟低(<500ms |
| 语音合成 TTS | OpenAI TTS | Edge TTS免费 | 音质自然,支持流式 |
| 轻量分类 | GPT-4o-mini | Haiku | 模型路由时的复杂度判断 |
| 多模态 LLM | GPT-4o(默认) | 通义千问等 OpenAI 兼容模型 | 通过 OpenAI 兼容接口,可灵活切换 |
| 语音识别 STT | Deepgram(默认) | MiMo ASR小米 | 支持多 provider 切换 |
| 语音合成 TTS | OpenAI TTS(默认) | MiMo TTS小米 | 支持多 provider 切换 |
> 不必绑定单一厂商。Go 网关的模型路由层统一封装不同 AI 服务的调用接口,按场景动态切换
> 不必绑定单一厂商。Go 网关的 AI 服务层统一封装不同服务的调用接口,通过配置切换 provider
## 核心交互流程
@@ -78,52 +78,35 @@ Browser Go Gateway STT LLM TTS
| 模块 | 职责 | 关键实现 |
|------|------|---------|
| WebSocket Hub | 管理所有客户端连接,广播/定向推送 | goroutine per connection |
| Session Manager | 维护用户会话状态、对话历史 | Redis Hash + List30 分钟 TTL详见 `03-接口文档.md` 第五章) |
| Model Router | 根据请求类型选择 AI 模型 | 规则引擎 + 成本阈值 |
| AI Orchestrator | 编排多路 AI 调用(并行/串行) | context 取消 + 超时控制 |
| Rate Limiter | 防止单用户过度消耗 API 额度 | 令牌桶算法 |
| WebSocket Handler | 管理客户端连接生命周期,单播消息推送 | goroutine per connection |
| Session Manager | 维护用户会话状态、对话历史 | MemoryMVP 默认)/ Redis可切换30 分钟 TTL详见 `03-接口文档.md` 第五章) |
| AI Orchestrator | 编排 STT→LLM→TTS 流式并行管道 | context 取消 + 超时控制 + 句子切分 |
| AI Service Layer | AI 服务抽象层STT/LLM/TTS | 多 provider 支持Deepgram/MiMo/OpenAI 等) |
| REST API | 健康检查、会话管理端点 | Gin 路由 |
| Error Handler | 统一错误码定义与发送 | 错误码枚举 |
| Logger | 日志初始化封装 | Zap 结构化日志 |
| Models | 数据模型定义 | WebSocket 消息、会话、配置等 |
| Model Router | 根据请求类型选择 AI 模型(规划中) | 规则引擎 + 成本阈值 |
| Rate Limiter | 防止单用户过度消耗 API 额度(规划中) | 令牌桶算法 |
AI Orchestrator 核心代码(句子级流式并行
AI Orchestrator 核心接口(`internal/orchestrator/orchestrator.go`
```go
func (o *Orchestrator) ProcessQuery(ctx context.Context, client MessageSender, req *QueryRequest) {
ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
defer cancel()
// Step 1: STT — 识别用户语音(串行)
text, err := o.stt.Recognize(ctx, req.Audio, STTOptions{...})
if err != nil {
client.SendError(req.RequestID, "STT_ERROR", err.Error())
return
}
client.SendSTTResult(req.RequestID, text, true)
// Step 2: LLM 流式输出 + 句子切分(并行)
llmStream, _ := o.llm.ChatStream(ctx, LLMRequest{Image: req.Image, Text: text, ...})
sentenceCh := make(chan string, 4)
go func() {
defer close(sentenceCh)
var buf strings.Builder
for chunk := range llmStream {
client.SendLLMChunk(req.RequestID, chunk.Delta) // 逐 token 推送文字
buf.WriteString(chunk.Delta)
if isSentenceEnd(chunk.Delta) { // 按 。!?\n 切分
sentenceCh <- buf.String()
buf.Reset()
}
}
if buf.Len() > 0 { sentenceCh <- buf.String() }
}()
// Step 3: TTS 并行消费句子流
ttsStream, _ := o.tts.SynthesizeStream(ctx, sentenceCh, TTSOptions{...})
for chunk := range ttsStream {
client.SendTTSAudio(req.RequestID, chunk.Audio, chunk.IsLast)
}
// Orchestrator AI 编排器接口。
type Orchestrator interface {
ProcessQuery(ctx context.Context, sessionID string, req models.WsQuery,
history []models.Message, sender Sender) error
}
```
Pipeline 实现(`internal/orchestrator/pipeline.go`)流程:
1. Base64 解码音频/图片
2. 调用 `stt.Recognize()` → 发送 `stt_result`
3. 调用 `llm.ChatStream()` 获取流式输出goroutine 消费 token → 发送 `llm_chunk` + 句子切分
4. 另一 goroutine 从句子 channel 读取 → 调用 `tts.SynthesizeStream()` → 发送 `tts_audio`
5. 流结束 → 发送 `llm_done`
6. TTS 失败静默跳过STT/LLM 失败发送对应 error 消息
> **关键优化**LLM 文本流和 TTS 音频流**并行推送**——客户端先逐 token 展示文字,同时 TTS 逐句子合成并推送音频,用户感知延迟大幅降低。详细的 AI 服务层接口和编排策略见 `03-接口文档.md` 第三、四章。
## 前端组件
@@ -132,10 +115,12 @@ func (o *Orchestrator) ProcessQuery(ctx context.Context, client MessageSender, r
|------|------|
| CameraManager | 摄像头流采集 |
| MicManager | 麦克风音频采集 |
| EdgeProcessor | VAD + 关键帧检测(ONNX Runtime |
| EdgeProcessor | VAD + 关键帧检测(Canvas 像素比较 |
| WebSocketManager | WS 连接生命周期管理 |
| ChatPanel | 消息展示 |
| VideoPreview | 摄像头画面预览 |
| ConfigPanel | 右侧抽屉式配置面板主题、TTS 开关、detail level、语言 |
| Toast | 轻量通知提示3 秒自动消失) |
核心 Hook`useVisionSession()` 封装一次完整的视觉对话会话摄像头、VAD、WebSocket、消息状态
@@ -173,7 +158,7 @@ function useVisionSession() {
| 阶段 | 存储方案 | 持久化内容 | 理由 |
|------|---------|-----------|------|
| MVP | Redis only | 无 | 快速验证核心功能,重启丢数据可接受 |
| MVP | Memory进程内 | 无 | 快速验证核心功能,重启丢数据可接受。Redis 实现已就绪,可通过 `storage.driver` 配置切换 |
| 上线 | Redis + PostgreSQL | 对话历史、用户偏好、用量统计 | 用户需要查看历史,运营需要成本数据 |
| 规模化 | Redis + PG + 对象存储 | 图像帧、音频片段归档 | 大文件不适合存关系库 |
@@ -262,24 +247,8 @@ server {
> WebSocket 是长连接Nginx 必须配置 `Upgrade` 和 `Connection` 头。`proxy_read_timeout` 需要覆盖心跳间隔(客户端 30s ping否则 Nginx 会主动断开空闲连接。
### 开发环境Vite proxy
### 开发环境
开发时前端Vite :5173和后端Gin :8080不同端口,用 Vite 内置代理解决跨域:
开发时前端Vite :5173和后端Gin :8080不同端口。当前实现中前端 WebSocket 地址硬编码为 `ws://localhost:8080/ws`,直连后端,不经过 Vite 代理。
```typescript
// frontend/vite.config.ts
export default defineConfig({
plugins: [react()],
server: {
proxy: {
"/api": "http://localhost:8080",
"/ws": {
target: "ws://localhost:8080",
ws: true,
},
},
},
});
```
前端代码中 WebSocket 地址改为相对路径 `ws://localhost:5173/ws`Vite 自动代理到后端。部署时 Nginx 同理,前端无需区分开发/生产地址。
> 如需使用 Vite 代理解决跨域,可在 `vite.config.ts` 中添加 `server.proxy` 配置,并将前端 WebSocket 地址改为相对路径。