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

12 KiB
Raw Blame History

tags, create time
tags create time
AI
架构设计
技术栈
端云协同
WebSocket
微服务
2026-06-12 14:32

项目架构与技术栈

概述

本文档设计 AI 视觉对话助手的分层架构技术选型。核心设计原则:前端做轻量预处理,后端做智能编排,云端 AI 服务按需调用——在保证交互体验的同时控制成本。

正文

整体架构

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 回答"流程:

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 网关的核心模块:

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 后端核心代码结构:

// 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
}

前端架构设计

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 设计:

// 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 };
}

部署架构

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 Upgradeip_hash 或 sticky session确保同一用户的请求始终路由到同一个 Gateway 实例。

存储与持久化策略

当前架构使用 Redis 做会话存储,但 Redis 是内存数据库,默认不做持久化——服务重启数据即丢。是否需要持久化,取决于业务阶段:

分阶段策略

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 表设计要点

-- 对话会话表
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替换成本也很低。

更新后的存储层架构

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互不干扰。

关联笔记