Files
note/课题一/AI 视觉对话助手/项目架构与技术栈.md
2026-06-12 15:36:35 +08:00

394 lines
12 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.
---
tags: [AI, 架构设计, 技术栈, 端云协同, WebSocket, 微服务]
create time: 2026-06-12 14:32
---
# 项目架构与技术栈
## 概述
本文档设计 AI 视觉对话助手的**分层架构**与**技术选型**。核心设计原则:**前端做轻量预处理,后端做智能编排,云端 AI 服务按需调用**——在保证交互体验的同时控制成本。
## 正文
### 整体架构
```mermaid
graph TB
subgraph Client["浏览器客户端"]
UI["React UI"]
CAM["摄像头/麦克风"]
EDGE["边缘预处理"]
WS_C["WebSocket Client"]
end
subgraph Gateway["Go 后端网关"]
WS_S["WebSocket Server"]
SESSION["会话管理"]
ROUTER["模型路由"]
ORCH["AI 编排器"]
end
subgraph AI["云端 AI 服务"]
LLM["多模态 LLM"]
STT["语音识别"]
TTS["语音合成"]
end
CAM --> EDGE
EDGE -->|"关键帧 + 语音片段"| WS_C
WS_C <-->|"双向实时通信"| WS_S
WS_S --> SESSION
SESSION --> ROUTER
ROUTER --> ORCH
ORCH --> LLM
ORCH --> STT
ORCH --> TTS
TTS -->|"音频流"| WS_S
LLM -->|"文本流"| WS_S
```
三层各司其职:
| 层级 | 职责 | 关键约束 |
|------|------|---------|
| **客户端** | 媒体采集、边缘预处理、UI 渲染 | 浏览器资源有限,模型需轻量 |
| **Go 网关** | 会话管理、模型路由、AI 服务编排 | 高并发、低延迟、状态管理 |
| **AI 服务** | LLM 推理、语音识别、语音合成 | 按量计费,需控制调用频率 |
> [!question] 思考
> 为什么要单独加一层 Go 网关,而不是让前端直连 AI API原因有三1API Key 安全性2统一的速率限制和成本管控3多模型路由逻辑集中在一处便于维护。
### 技术栈选型
#### 前端
| 技术 | 选型 | 选择理由 |
|------|------|---------|
| 框架 | **React 18 + TypeScript** | 组件化开发,类型安全,生态成熟 |
| 构建 | **Vite** | 开发热更新快,构建产物小 |
| 实时通信 | **WebSocket (原生 API)** | 浏览器原生支持,无需额外依赖 |
| 边缘推理 | **ONNX Runtime Web** | 浏览器端跑轻量模型VAD、关键帧检测 |
| 语音检测 | **@ricky0123/vad-web** | 基于 WebRTC VAD纯前端零延迟 |
| 媒体采集 | **MediaDevices API** | 浏览器原生摄像头/麦克风访问 |
#### 后端
| 技术 | 选型 | 选择理由 |
|------|------|---------|
| 语言 | **Go** | 高并发 goroutine 模型,适合长连接管理 |
| WebSocket | **gorilla/websocket** | Go 生态最成熟的 WebSocket 库 |
| 会话存储 | **Redis** | 高速 KV 存储,适合会话状态和上下文缓存 |
| 持久化存储 | **PostgreSQL** | 对话历史、用量统计、用户偏好MVP 阶段可选) |
| 配置管理 | **Viper** | 支持多格式配置,环境变量覆盖 |
| 日志 | **Zap** | 高性能结构化日志 |
#### AI 服务(按需选型)
| 能力 | 主选方案 | 备选方案 | 选型考量 |
|------|---------|---------|---------|
| 多模态 LLM | **GPT-4o** | Claude Sonnet | 视觉理解能力强API 成熟 |
| 语音识别 STT | **Deepgram** | FunASR 自部署 | 流式识别延迟低(<500ms |
| 语音合成 TTS | **OpenAI TTS** | Edge TTS免费 | 音质自然,支持流式 |
| 轻量分类 | **GPT-4o-mini** | Haiku | 模型路由时的复杂度判断 |
> [!tip] 混合策略
> 不必绑定单一厂商。Go 网关的模型路由层可以统一封装不同 AI 服务的调用接口,按场景动态切换。比如简单识别用 GPT-4o-mini深度分析用 GPT-4oTTS 用免费的 Edge TTS 降低成本。
### 核心交互流程
一次完整的"用户提问 → AI 回答"流程:
```mermaid
sequenceDiagram
participant B as Browser
participant G as Go Gateway
participant S as STT Service
participant L as LLM Service
participant T as TTS Service
B->>B: VAD 检测到语音开始
B->>B: 捕获当前摄像头帧
B->>G: WebSocket 发送 [音频流 + 图像帧]
G->>S: 转发音频流
S-->>G: 流式返回识别文本
G->>L: 发送 [图像 + 识别文本 + 历史上下文]
L-->>G: 流式返回回答文本
G-->>B: WebSocket 推送回答文本
G->>T: 发送回答文本
T-->>G: 流式返回音频
G-->>B: WebSocket 推送音频流
B->>B: 播放音频 + 渲染文字
```
> [!info] 关键优化
> 注意 LLM 文本流和 TTS 音频流是**并行推送**的——客户端先展示文字,同时开始播放语音,用户感知延迟大幅降低。
### 后端架构设计
Go 网关的核心模块:
```mermaid
graph TD
subgraph Server["Go Gateway"]
WS["WebSocket Hub"]
SM["Session Manager"]
MR["Model Router"]
AO["AI Orchestrator"]
RL["Rate Limiter"]
CACHE["Context Cache"]
end
WS --> SM
SM --> MR
MR --> AO
SM --> RL
SM --> CACHE
```
各模块职责:
| 模块 | 职责 | 关键实现 |
|------|------|---------|
| **WebSocket Hub** | 管理所有客户端连接,广播/定向推送 | goroutine per connection |
| **Session Manager** | 维护用户会话状态、对话历史 | Redis + TTL 过期策略 |
| **Model Router** | 根据请求类型选择 AI 模型 | 规则引擎 + 成本阈值 |
| **AI Orchestrator** | 编排多路 AI 调用(并行/串行) | context 取消 + 超时控制 |
| **Rate Limiter** | 防止单用户过度消耗 API 额度 | 令牌桶算法 |
Go 后端核心代码结构:
```go
// AI 编排器:并行调用 LLM 和 TTS
func (o *Orchestrator) ProcessQuery(ctx context.Context, req *QueryRequest) (*QueryResponse, error) {
ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
defer cancel()
// 并行LLM 推理 + 准备 TTS
llmCh := make(chan string, 1)
go func() {
resp, _ := o.llm.Chat(ctx, req.Image, req.Text, req.History)
llmCh <- resp
}()
llmText := <-llmCh
// LLM 返回后,流式推送给客户端,同时启动 TTS
ttsCh := make(chan []byte, 1)
go func() {
audio, _ := o.tts.Synthesize(ctx, llmText)
ttsCh <- audio
}()
return &QueryResponse{Text: llmText, Audio: <-ttsCh}, nil
}
```
### 前端架构设计
```mermaid
graph TD
subgraph App["React App"]
MAIN["App Root"]
CAM_M["CameraManager"]
MIC_M["MicManager"]
EDGE_M["EdgeProcessor"]
WS_M["WebSocketManager"]
CHAT["ChatPanel"]
VIDEO["VideoPreview"]
end
MAIN --> CAM_M
MAIN --> MIC_M
MAIN --> WS_M
MAIN --> CHAT
MAIN --> VIDEO
CAM_M --> EDGE_M
MIC_M --> EDGE_M
EDGE_M --> WS_M
```
核心 Hook 设计:
```typescript
// useVisionSession —— 封装一次完整的视觉对话会话
function useVisionSession() {
const [messages, setMessages] = useState<Message[]>([]);
const wsRef = useWebSocket("ws://localhost:8080/ws");
// 摄像头管理
const videoRef = useRef<HTMLVideoElement>(null);
const { captureFrame } = useCamera(videoRef);
// VAD 语音检测
const { isSpeaking } = useVAD({
onSpeechEnd: async (audio) => {
const frame = captureFrame();
// 同时发送图像帧和语音片段
wsRef.current?.send(JSON.stringify({
type: "query",
image: frame.toDataURL("image/jpeg", 0.7),
audio: encodeAudio(audio)
}));
}
});
// 接收 AI 回复(文本 + 音频)
useEffect(() => {
wsRef.current?.on("message", (data) => {
const { text, audio } = JSON.parse(data);
setMessages(prev => [...prev, { role: "assistant", text }]);
if (audio) playAudio(audio);
});
}, []);
return { messages, videoRef, isSpeaking };
}
```
### 部署架构
```mermaid
graph LR
subgraph CDN["CDN"]
STATIC["静态资源"]
end
subgraph LB["负载均衡"]
NGINX["Nginx"]
end
subgraph App["应用层"]
GW1["Gateway-1"]
GW2["Gateway-2"]
end
subgraph Storage["存储层"]
REDIS["Redis"]
end
USER["用户浏览器"] --> CDN
CDN --> STATIC
USER -->|"WebSocket"| NGINX
NGINX --> GW1
NGINX --> GW2
GW1 --> REDIS
GW2 --> REDIS
GW1 -->|"API Calls"| AI["AI Services"]
GW2 -->|"API Calls"| AI
```
> [!tip] WebSocket 与负载均衡
> WebSocket 是长连接Nginx 需要配置 `proxy_set_header Upgrade` 和 `ip_hash` 或 sticky session确保同一用户的请求始终路由到同一个 Gateway 实例。
### 存储与持久化策略
当前架构使用 Redis 做会话存储,但 Redis 是**内存数据库**,默认不做持久化——服务重启数据即丢。是否需要持久化,取决于业务阶段:
#### 分阶段策略
```mermaid
graph LR
A["MVP 阶段"] -->|"Redis 内存存储"| B["快速验证"]
C["上线阶段"] -->|"Redis + PostgreSQL"| D["持久化对话与用量"]
E["规模化阶段"] -->|"Redis + PG + 对象存储"| F["完整数据体系"]
```
| 阶段 | 存储方案 | 持久化内容 | 理由 |
|------|---------|-----------|------|
| **MVP** | Redis only | 无 | 快速验证核心功能,重启丢数据可接受 |
| **上线** | Redis + **PostgreSQL** | 对话历史、用户偏好、用量统计 | 用户需要查看历史,运营需要成本数据 |
| **规模化** | Redis + PG + **对象存储** | 图像帧、音频片段归档 | 大文件不适合存关系库 |
#### 需要持久化的数据
| 数据类型 | 写入频率 | 查询模式 | 推荐存储 |
|---------|---------|---------|---------|
| 对话历史(文本) | 每轮对话 | 按用户+时间范围查询 | PostgreSQL |
| 用量统计tokens/成本) | 每次 API 调用 | 聚合统计(日/周/月) | PostgreSQL |
| 用户偏好(语言/声音) | 低频 | 按 user_id 查询 | PostgreSQL |
| 实时会话状态 | 高频读写 | 按 session_id 查询 | Redis不变 |
| 关键帧图像 | 按需 | 按对话 ID 关联 | 对象存储S3/MinIO |
#### PostgreSQL 表设计要点
```sql
-- 对话会话表
CREATE TABLE sessions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL,
created_at TIMESTAMPTZ DEFAULT now(),
updated_at TIMESTAMPTZ DEFAULT now()
);
-- 对话消息表
CREATE TABLE messages (
id BIGSERIAL PRIMARY KEY,
session_id UUID REFERENCES sessions(id),
role VARCHAR(16) NOT NULL, -- "user" | "assistant"
content TEXT NOT NULL,
image_url TEXT, -- 关联的关键帧(可选)
tokens_used INTEGER DEFAULT 0,
created_at TIMESTAMPTZ DEFAULT now()
);
-- 用量统计表(按天聚合,方便成本分析)
CREATE TABLE usage_daily (
user_id UUID NOT NULL,
date DATE NOT NULL,
llm_tokens BIGINT DEFAULT 0,
stt_seconds REAL DEFAULT 0,
tts_chars INTEGER DEFAULT 0,
estimated_cost NUMERIC(10,4) DEFAULT 0,
PRIMARY KEY (user_id, date)
);
```
> [!info] 为什么选 PostgreSQL 而不是 MySQL
> PostgreSQL 对 JSON 类型支持更好(对话上下文可直接存 JSONB且有 `gen_random_uuid()` 等原生函数,更适合这类 AI 应用场景。当然,如果团队更熟悉 MySQL替换成本也很低。
#### 更新后的存储层架构
```mermaid
graph TD
subgraph App["Go Gateway"]
SM["Session Manager"]
HM["History Manager"]
UM["Usage Monitor"]
end
subgraph Cache["热数据 - Redis"]
SESSION["会话状态"]
CTX["对话上下文窗口"]
end
subgraph DB["冷数据 - PostgreSQL"]
HISTORY["对话历史"]
USAGE["用量统计"]
PREFS["用户偏好"]
end
subgraph OSS["大文件 - 对象存储"]
IMG["关键帧图像"]
AUDIO["音频片段"]
end
SM --> SESSION
SM --> CTX
HM --> HISTORY
HM --> IMG
UM --> USAGE
SM --> PREFS
```
> [!question] 思考
> Redis 存"热数据"当前对话上下文PostgreSQL 存"冷数据"(历史记录)——这就是经典的**冷热分离**策略。实时对话走 Redis 微秒级读写,历史查询走 PostgreSQL互不干扰。
## 关联笔记
- [[视觉理解]]
- [[语音交互]]
- [[成本控制]]
- [[用户故事]]
- [[项目架构与技术栈/技术名词解释]]
- [[持久化技术选型]]