## 主要变更 ### 文档重构(减少 1199 行,-23%) - 01-架构设计.md: 503→369 行 (-27%),删除 DDL/配置示例,精简鉴权/存储描述 - 02-接口文档.md: 1313→570 行 (-57%),删除 Go 接口/Orchestrator 实现/配置管理 - 07-成本控制.md: 65→59 行 (-9%),代码块替换为文件引用 ### 文档编号规范化 - 08-功能创意.md → 删除(内容整合到 README.md "功能扩展方向") - 10-Eino框架与编排设计.md → 08-Eino框架与编排设计.md - 情景切换.md → 09-情景切换.md - 12-鉴权体系.md → 10-鉴权体系.md - 13-令牌桶限流.md → 11-令牌桶限流.md ### 交叉引用更新 - 01-架构设计.md: 更新对鉴权体系/令牌桶限流的引用为新编号 - README.md: 更新文档索引表、推荐阅读顺序、新增功能扩展方向 ### 删除过时文档 - 09-技术名词解释.md(内容已整合到 03-技术选型.md) - 10-Eino重构方案.md(历史记录,已完成) - 11-Eino框架技术文档.md(已合并到 08) - 情景切换功能完整文档.md(已规范化为 09) ## 重构原则 - 架构文档聚焦系统结构,移除实现细节 - 接口文档保留纯契约,删除内部实现 - 编号连续(01-11),语义清晰 - 通过交叉引用连接相关文档,避免重复
370 lines
13 KiB
Markdown
370 lines
13 KiB
Markdown
# 架构设计
|
||
|
||
## 项目概述
|
||
|
||
CamTalk 是一款**多模态实时 AI 视觉对话助手**。用户通过摄像头和麦克风与 AI 交互,AI 理解视觉场景和语音输入后,以文字和语音形式给出自然回应。
|
||
|
||
核心挑战在于三个维度之间的张力:
|
||
|
||
| 维度 | 关键问题 |
|
||
|------|---------|
|
||
| 视觉理解 | 如何准确理解摄像头画面中的人物、物体、场景? |
|
||
| 语音交互 | 如何让对话像真人交流一样自然、低延迟? |
|
||
| 成本控制 | 实时视频流 + LLM 推理,如何避免账单爆炸? |
|
||
|
||
## 系统架构
|
||
|
||
三层架构:**前端做轻量预处理,后端做智能编排,云端 AI 服务按需调用**。
|
||
|
||
```mermaid
|
||
graph TB
|
||
subgraph Browser["浏览器客户端"]
|
||
UI["UI 渲染层<br/>React 18 + TypeScript"]
|
||
Edge["边缘预处理层<br/>VAD / 关键帧检测"]
|
||
Media["媒体采集层<br/>Camera / Microphone"]
|
||
end
|
||
|
||
subgraph Gateway["Go 网关"]
|
||
WS["WebSocket Handler<br/>连接管理 / 消息分发"]
|
||
Session["Session Manager<br/>会话状态 / 对话历史"]
|
||
Orch["AI Orchestrator<br/>Eino Graph 声明式编排"]
|
||
Auth["Auth 模块<br/>JWT / bcrypt"]
|
||
REST["REST API<br/>健康检查 / 对话管理"]
|
||
Store["Store 层<br/>Repository 接口"]
|
||
end
|
||
|
||
subgraph AI["云端 AI 服务"]
|
||
STT["STT<br/>Deepgram / MiMo ASR"]
|
||
LLM["LLM<br/>GPT-4o / 通义千问"]
|
||
TTS["TTS<br/>OpenAI TTS / MiMo TTS"]
|
||
end
|
||
|
||
subgraph Storage["存储层"]
|
||
Mem["Memory<br/>进程内缓存"]
|
||
Redis["Redis<br/>会话状态"]
|
||
PG["PostgreSQL<br/>持久化存储"]
|
||
end
|
||
|
||
Media --> Edge
|
||
Edge -->|"query (image+audio)"| WS
|
||
UI <-->|"WebSocket"| WS
|
||
WS --> Session
|
||
WS --> Orch
|
||
Orch --> STT
|
||
Orch --> LLM
|
||
Orch --> TTS
|
||
Session --> Store
|
||
Store --> Mem
|
||
Store --> Redis
|
||
Store --> PG
|
||
REST --> Session
|
||
WS --> Auth
|
||
```
|
||
|
||
> 为什么单独加一层 Go 网关,而不是让前端直连 AI API?1)API Key 安全性;2)统一的速率限制和成本管控;3)多模型路由逻辑集中在一处便于维护。
|
||
|
||
## 核心交互流程
|
||
|
||
一次完整的"用户提问 → AI 回答"流程:
|
||
|
||
```mermaid
|
||
sequenceDiagram
|
||
participant B as 浏览器
|
||
participant G as Go 网关(Eino Graph)
|
||
participant S as STT
|
||
participant L as LLM(ChatModel)
|
||
participant T as TTS
|
||
|
||
B->>B: VAD 检测到语音结束
|
||
B->>G: query {image, audio}
|
||
Note over G: EinoOrchestrator 启动 Graph.Stream()
|
||
|
||
G->>S: STT Lambda:音频 → 文本
|
||
S-->>G: 识别文本
|
||
G-->>B: stt_result {text}
|
||
|
||
G->>G: History Lambda:组装提示词 + 历史 + 多模态消息
|
||
|
||
G->>L: ChatModel Node:流式推理
|
||
loop LLM 流式输出(Callback OnEndWithStreamOutput)
|
||
L-->>G: token delta
|
||
G-->>B: llm_chunk {delta}
|
||
end
|
||
|
||
G->>G: Msg2Str + Splitter Lambda:句子切分
|
||
G->>T: TTS Lambda:逐句合成
|
||
T-->>G: 音频 chunk
|
||
G-->>B: tts_audio {audio}
|
||
|
||
G->>G: Done Lambda:发送完成通知
|
||
G-->>B: llm_done {full_text, tokens}
|
||
G-->>B: tts_audio {final: true}
|
||
```
|
||
|
||
**关键优化**:Eino Graph 以 Stream 模式运行,ChatModel 的 token 流通过 Callback 的 `OnEndWithStreamOutput` 实时推送到客户端(`llm_chunk`),同时 Splitter 节点将 token 流切分为句子,TTS 节点逐句合成并推送音频。LLM 文本流和 TTS 音频流**并行推送**,用户感知延迟大幅降低。
|
||
|
||
## 技术栈
|
||
|
||
### 前端
|
||
|
||
| 技术 | 选型 | 选择理由 |
|
||
|------|------|---------|
|
||
| 框架 | React 18 + TypeScript | 组件化开发,类型安全,生态成熟 |
|
||
| 构建 | Vite | 开发热更新快,构建产物小 |
|
||
| 实时通信 | WebSocket(原生 API) + 自封装连接管理 | 浏览器原生支持,封装心跳/重连/消息分发 |
|
||
| 语音检测 | @ricky0123/vad-web | 基于 WebRTC VAD,纯前端零延迟 |
|
||
| 媒体采集 | MediaDevices API | 浏览器原生摄像头/麦克风访问 |
|
||
|
||
### 后端
|
||
|
||
| 技术 | 选型 | 选择理由 |
|
||
|------|------|---------|
|
||
| 语言 | Go | 高并发 goroutine 模型,适合长连接管理 |
|
||
| HTTP 框架 | Gin | 高性能 HTTP 路由,中间件生态成熟 |
|
||
| WebSocket | gorilla/websocket | Go 生态最成熟的 WebSocket 库 |
|
||
| 会话存储 | Memory / Redis / PostgreSQL 三级存储 | 进程内存零依赖,Redis 支持多实例,PG 持久化。TieredManager 自动降级 |
|
||
| AI 编排 | CloudWeGo Eino Graph | 声明式 DAG 编排,Stream 模式,Callback AOP |
|
||
| 持久化存储 | PostgreSQL | 对话历史、用户数据、会话元数据 |
|
||
| 配置管理 | Viper + godotenv | 支持 YAML + .env + 环境变量覆盖 |
|
||
| 日志 | Zap | 高性能结构化日志 |
|
||
|
||
### AI 服务
|
||
|
||
| 能力 | 默认方案 | 备选方案 |
|
||
|------|---------|---------|
|
||
| 多模态 LLM | DashScope qwen3-vl-plus | GPT-4o 等 OpenAI 兼容模型 |
|
||
| 语音识别 STT | MiMo ASR(小米) | Deepgram |
|
||
| 语音合成 TTS | MiMo TTS(小米) | OpenAI TTS |
|
||
|
||
> Go 网关的 AI 服务层统一封装不同服务商的调用接口,通过配置切换 provider。LLM 通过 Eino 框架的 `eino-ext/components/model/openai` 组件接入,支持任何 OpenAI 兼容接口。
|
||
|
||
## 后端模块
|
||
|
||
```mermaid
|
||
graph LR
|
||
subgraph Entry["入口层"]
|
||
Main["main.go<br/>依赖注入 / 启动"]
|
||
end
|
||
|
||
subgraph Transport["传输层"]
|
||
WSH["WebSocket Handler<br/>连接管理 / 认证"]
|
||
APH["REST API Handlers<br/>Auth / Conversation / Health"]
|
||
end
|
||
|
||
subgraph Business["业务层"]
|
||
SM["Session Manager<br/>会话生命周期"]
|
||
ORCH["EinoOrchestrator<br/>Eino Graph 编排"]
|
||
AS["Auth Service<br/>注册/登录/刷新/登出"]
|
||
end
|
||
|
||
subgraph Eino_Layer["Eino 编排层"]
|
||
PG["PipelineGraph<br/>7 节点 DAG"]
|
||
CB["Callback Handler<br/>LLM token 推送"]
|
||
ST["PipelineState<br/>跨节点状态"]
|
||
end
|
||
|
||
subgraph AI_Layer["AI 服务层"]
|
||
STT_S["STT Service<br/>MiMo / Deepgram"]
|
||
LLM_S["ChatModel<br/>eino-ext OpenAI 兼容"]
|
||
TTS_S["TTS Service<br/>MiMo / OpenAI"]
|
||
end
|
||
|
||
subgraph Data["数据层"]
|
||
UR["UserRepository"]
|
||
MR["MessageRepository"]
|
||
SR["SessionRepository"]
|
||
end
|
||
|
||
Main --> WSH
|
||
Main --> APH
|
||
Main --> SM
|
||
Main --> ORCH
|
||
Main --> AS
|
||
|
||
WSH --> SM
|
||
WSH --> ORCH
|
||
APH --> SM
|
||
APH --> AS
|
||
ORCH --> PG
|
||
PG --> CB
|
||
PG --> ST
|
||
PG --> STT_S
|
||
PG --> LLM_S
|
||
PG --> TTS_S
|
||
SM --> MR
|
||
SM --> SR
|
||
AS --> UR
|
||
```
|
||
|
||
| 模块 | 职责 |
|
||
|------|------|
|
||
| WebSocket Handler | 管理客户端连接生命周期,JWT 认证,conversation_id 恢复,单播消息推送 |
|
||
| Session Manager | 维护用户会话状态、对话历史,三级存储架构,30 分钟 TTL |
|
||
| Eino 编排层 | 基于 CloudWeGo Eino Graph 的声明式 AI 编排,7 节点 DAG 流水线,Stream 模式调用 |
|
||
| AI Orchestrator | EinoOrchestrator 适配器,包装 Eino Graph 实现 Orchestrator 接口 |
|
||
| AI Service Layer | AI 服务抽象层,多 provider 支持(Deepgram/MiMo/OpenAI 等) |
|
||
| Auth | 用户认证与授权,JWT 双 token 轮转,bcrypt 密码哈希 |
|
||
| Store | 持久化存储层,Repository 接口与实现(内存 + PostgreSQL) |
|
||
| REST API | 健康检查、认证、对话管理端点 |
|
||
| Logger | Zap 结构化日志 |
|
||
| Models | 数据模型定义 |
|
||
| Migrations | 数据库版本化迁移 |
|
||
| Model Router | 根据请求类型选择 AI 模型(待实现) |
|
||
| Rate Limiter | 令牌桶限流,详见 [11-令牌桶限流.md](./11-令牌桶限流.md) |
|
||
|
||
## 前端组件
|
||
|
||
| 组件 | 职责 |
|
||
|------|------|
|
||
| LandingPage | 未登录时的着陆页,内嵌 LoginModal 登录/注册弹窗 |
|
||
| CameraManager | 摄像头流采集 |
|
||
| MicManager | 麦克风音频采集 |
|
||
| EdgeProcessor | VAD + 关键帧检测 |
|
||
| WebSocketManager | WebSocket 连接生命周期管理 |
|
||
| ChatPanel | 消息展示、流式回复、文本输入、场景选择 |
|
||
| VideoPreview | 摄像头画面预览 |
|
||
| SessionSidebar | 左侧对话列表(搜索、重命名、删除、时间分组) |
|
||
| ConfigPanel | 右侧配置面板(主题、TTS 开关、detail level、语言、场景、登出) |
|
||
| Toast | 轻量通知提示 |
|
||
|
||
核心 Hook:`useVisionSession()` 封装完整的视觉对话会话(摄像头、VAD、WebSocket、消息状态、认证、场景模式)。`useSessionList()` 通过 REST API 管理对话列表 CRUD。
|
||
|
||
### 前端会话状态模型(三态)
|
||
|
||
前端 UI 存在三个会话状态,由 `isConnected` 和 `isCameraOn` 联合决定:
|
||
|
||
```
|
||
┌──────────┐ startSession() ┌──────────┐
|
||
│ initial │ ──────────────────→ │ video │
|
||
│ 初始态 │ │ 视频通话 │
|
||
└──────────┘ └──────────┘
|
||
↑ │
|
||
│ stopSession() stopVideo()
|
||
│ │
|
||
│ ▼
|
||
│ ┌──────────┐
|
||
└──────────────────────── │ textOnly │
|
||
│ 文字对话 │
|
||
└──────────┘
|
||
│
|
||
startSession()
|
||
│
|
||
▼
|
||
┌──────────┐
|
||
│ video │
|
||
└──────────┘
|
||
```
|
||
|
||
| 状态 | 条件 | WebSocket | 摄像头 | 消息 | 文字输入 |
|
||
|------|------|-----------|--------|------|---------|
|
||
| `initial` | `!isConnected && messages.length === 0` | 断开 | 关闭 | 空 | 可用(自动连接) |
|
||
| `video` | `isConnected && isCameraOn` | 连接 | 开启 | 有 | 可用 |
|
||
| `textOnly` | `isConnected && !isCameraOn` | 连接 | 关闭 | 保留 | 可用 |
|
||
|
||
- **`stopVideo()`**:停止摄像头/麦克风/VAD,保持 WebSocket 连接和消息历史,用户可继续文字对话
|
||
- **`stopSession()`**:完全断开 WebSocket、清空消息、重置状态,回到初始态
|
||
|
||
## 数据库设计
|
||
|
||
### ER 关系
|
||
|
||
```mermaid
|
||
erDiagram
|
||
users ||--o{ sessions : "1:N"
|
||
users ||--o{ refresh_tokens : "1:N"
|
||
sessions ||--o{ messages : "1:N"
|
||
|
||
users {
|
||
uuid id PK
|
||
varchar username UK
|
||
varchar password_hash
|
||
timestamptz created_at
|
||
timestamptz updated_at
|
||
}
|
||
|
||
sessions {
|
||
uuid id PK
|
||
uuid user_id FK
|
||
varchar title
|
||
jsonb config
|
||
timestamptz created_at
|
||
timestamptz updated_at
|
||
}
|
||
|
||
messages {
|
||
bigserial id PK
|
||
uuid session_id FK
|
||
varchar role
|
||
text content
|
||
integer tokens_used
|
||
timestamptz created_at
|
||
}
|
||
|
||
refresh_tokens {
|
||
bigserial id PK
|
||
uuid user_id FK
|
||
varchar token_hash UK
|
||
timestamptz expires_at
|
||
timestamptz created_at
|
||
}
|
||
```
|
||
|
||
系统采用关系型数据库存储持久化数据,包括用户账户、对话会话、消息记录和刷新令牌。数据库表定义详见 `backend/migrations/` 目录下的 SQL 迁移文件。
|
||
|
||
### 存储策略
|
||
|
||
系统采用**三级存储架构**(TieredManager)实现会话状态管理,平衡性能与可靠性:
|
||
|
||
- **L1 Memory**:进程内缓存,提供微秒级读写性能
|
||
- **L2 Redis**:分布式缓存层,支持多实例部署,提供毫秒级访问
|
||
- **L3 PostgreSQL**:持久化存储层,确保数据可靠性
|
||
|
||
会话数据按 TTL(默认 30 分钟)在三级存储间流转,支持 Redis 故障时自动降级到 Memory + PostgreSQL 模式。配置灵活,可根据部署规模选择单级(Memory)、双级(Memory + PostgreSQL)或完整三级存储方案。
|
||
|
||
## 认证设计
|
||
|
||
系统采用 **JWT 双 token 轮转认证机制**,结合 bcrypt 密码哈希和 Refresh Token Rotation 安全策略。
|
||
|
||
核心机制包括:双 token 轮转(access_token 15 分钟 + refresh_token 7 天)、密码安全(bcrypt cost=10)、token 安全(SHA256 哈希存储、复用检测)、WebSocket 连接认证(基于 access_token 的 HTTP Upgrade 校验)等。认证流程、安全机制、配置要求等详细设计见 [10-鉴权体系.md](./10-鉴权体系.md)。
|
||
|
||
## 部署架构
|
||
|
||
系统采用分层部署架构,支持单实例和多实例水平扩展:
|
||
|
||
```mermaid
|
||
graph TB
|
||
User["用户浏览器"] --> Nginx
|
||
|
||
subgraph Nginx["Nginx 反向代理"]
|
||
Static["/ → 前端静态资源"]
|
||
API["/api/* → Go Gateway"]
|
||
WS_Proxy["/ws → Go Gateway"]
|
||
end
|
||
|
||
subgraph Gateway_Pool["Go Gateway 实例"]
|
||
G1["Gateway-1"]
|
||
G2["Gateway-2"]
|
||
GN["Gateway-N"]
|
||
end
|
||
|
||
Nginx --> G1
|
||
Nginx --> G2
|
||
Nginx --> GN
|
||
|
||
G1 --> Redis
|
||
G2 --> Redis
|
||
GN --> Redis
|
||
|
||
G1 --> PG_DB["PostgreSQL"]
|
||
G2 --> PG_DB
|
||
GN --> PG_DB
|
||
|
||
G1 --> AI_Services["AI Services(外部 API)"]
|
||
G2 --> AI_Services
|
||
GN --> AI_Services
|
||
```
|
||
|
||
**跨域策略**:Nginx 将前端(`/`)、REST API(`/api/*`)、WebSocket(`/ws`)统一反代到同一域名,浏览器无跨域问题。
|
||
|
||
**开发环境**:前端 Vite :5173 通过 `server.proxy` 转发 `/ws` 和 `/api` 到后端 :8080,无需硬编码端口。
|