Files
CamTalk/docs/01-架构设计.md

16 KiB
Raw Blame History

架构设计

项目概述

CamTalk 是一款多模态实时 AI 视觉对话助手。用户通过摄像头和麦克风与 AI 交互AI 理解视觉场景和语音输入后,以文字和语音形式给出自然回应。

核心挑战在于三个维度之间的张力:

维度 关键问题
视觉理解 如何准确理解摄像头画面中的人物、物体、场景?
语音交互 如何让对话像真人交流一样自然、低延迟?
成本控制 实时视频流 + LLM 推理,如何避免账单爆炸?

系统架构

三层架构:前端做轻量预处理,后端做智能编排,云端 AI 服务按需调用

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 API1API Key 安全性2统一的速率限制和成本管控3多模型路由逻辑集中在一处便于维护。

核心交互流程

一次完整的"用户提问 → AI 回答"流程:

sequenceDiagram
    participant B as 浏览器
    participant G as Go 网关Eino Graph
    participant S as STT
    participant L as LLMChatModel
    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 兼容接口。

后端模块

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 维护用户会话状态、对话历史。Memory默认/ Redis可切换30 分钟 TTLWrite-Through 到 PG
Eino 编排层 基于 CloudWeGo Eino Graph 的声明式 AI 编排。7 节点 DAGSTT→History→ChatModel→Msg2Str→Splitter→TTS→DoneStream 模式调用Callback 实现 LLM token 实时推送
AI Orchestrator EinoOrchestrator 适配器,包装 Eino Graph 实现 Orchestrator 接口。context 取消 + 超时控制
AI Service Layer AI 服务抽象层,多 provider 支持Deepgram/MiMo/OpenAI 等)
Auth 用户认证与授权。JWT (HS256) 双 token 轮转bcrypt 密码哈希Gin 中间件
Store 持久化存储层。UserRepository / MessageRepository / SessionRepository内存 + PostgreSQL 双实现
REST API 健康检查、认证、对话管理端点
Logger Zap 结构化日志
Models 数据模型定义
Migrations 数据库版本化迁移,嵌入式 SQL 文件自动执行
Model Router 根据请求类型选择 AI 模型(待实现)
Rate Limiter 令牌桶限流(待实现)

前端组件

组件 职责
AuthPage 登录/注册表单
CameraManager 摄像头流采集
MicManager 麦克风音频采集
EdgeProcessor VAD + 关键帧检测Canvas 像素比较)
WebSocketManager WS 连接生命周期管理
ChatPanel 消息展示、流式回复、文本输入、场景选择
VideoPreview 摄像头画面预览
SessionSidebar 左侧抽屉式对话列表(搜索、重命名、删除)
ConfigPanel 右侧抽屉式配置面板主题、TTS 开关、detail level、语言、场景、账户
Toast 轻量通知提示3 秒自动消失)

核心 HookuseVisionSession() 封装一次完整的视觉对话会话摄像头、VAD、WebSocket、消息状态、认证、场景模式

前端会话状态模型(三态)

前端 UI 存在三个会话状态,由 isConnectedisCameraOn 联合决定:

┌──────────┐    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 关系

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
    }

表结构

-- 用户表
CREATE TABLE users (
    id            UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    username      VARCHAR(64) NOT NULL UNIQUE,
    password_hash VARCHAR(256) NOT NULL,
    created_at    TIMESTAMPTZ DEFAULT now(),
    updated_at    TIMESTAMPTZ DEFAULT now()
);

-- 会话表
CREATE TABLE sessions (
    id         UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    user_id    UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
    title      VARCHAR(128) DEFAULT '新对话',
    config     JSONB DEFAULT '{}',
    created_at TIMESTAMPTZ DEFAULT now(),
    updated_at TIMESTAMPTZ DEFAULT now()
);

-- 消息表
CREATE TABLE messages (
    id          BIGSERIAL PRIMARY KEY,
    session_id  UUID NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
    role        VARCHAR(16) NOT NULL,
    content     TEXT NOT NULL,
    tokens_used INTEGER DEFAULT 0,
    created_at  TIMESTAMPTZ DEFAULT now()
);

-- 刷新令牌表
CREATE TABLE refresh_tokens (
    id          BIGSERIAL PRIMARY KEY,
    user_id     UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
    token_hash  VARCHAR(256) NOT NULL UNIQUE,
    expires_at  TIMESTAMPTZ NOT NULL,
    created_at  TIMESTAMPTZ DEFAULT now()
);

存储策略

场景 存储方案 说明
默认 Memory进程内 零依赖快速启动。MemoryManager 支持 Write-Through 到 PG
持久化 Memory + PostgreSQL 通过 storage.persistence.enabled: true 启用MemoryManager 注入 PG Repository
多实例 Redis独立 通过配置切换到 RedisManager适合多实例部署
三级存储 TieredManager L1 Memory → L2 Redis → L3 PostgreSQL自动降级

三级存储架构TieredManager

TieredManager
├── L1: Memory进程内缓存微秒级读写
├── L2: Redis分布式缓存毫秒级读写
└── L3: PostgreSQL持久化存储冷数据
  • 读取路径L1 → L2 → L3逐级回源命中后向上回填
  • 写入路径L1 → L2同步 → L3异步
  • 健康检查:后台 goroutine 每 30 秒 ping Redis故障时自动降级为 L1+L3 模式
  • 冷热分离L1/L2 存"热数据"当前对话上下文L3 存"冷数据"(历史记录)

认证设计

sequenceDiagram
    participant C as 客户端
    participant G as Go 网关
    participant DB as PostgreSQL

    Note over C,DB: 注册流程
    C->>G: POST /api/auth/register {username, password}
    G->>G: bcrypt hash 密码
    G->>DB: INSERT users
    G->>G: 生成 access_token + refresh_token
    G->>DB: 存 SHA256(refresh_token)
    G-->>C: {user, access_token, refresh_token}

    Note over C,DB: 登录流程
    C->>G: POST /api/auth/login {username, password}
    G->>DB: 查 users by username
    G->>G: bcrypt.CompareHashAndPassword
    G->>G: 生成 token pair
    G->>DB: 存 SHA256(refresh_token)
    G-->>C: {user, access_token, refresh_token}

    Note over C,DB: Token 刷新(轮转)
    C->>G: POST /api/auth/refresh {refresh_token}
    G->>G: 校验签名和过期
    G->>DB: 验证 hash 存在
    G->>DB: 撤销旧 refresh_token
    G->>G: 生成新 token pair
    G->>DB: 存新 refresh_token hash
    G-->>C: {access_token, refresh_token}

Token 策略access_token 15 分钟有效refresh_token 7 天有效。refresh 时旧 token 失效(轮转),防止重放攻击。

WebSocket 认证:连接地址 ws://host/ws?token=<access_token>&conversation_id=<uuid>。HTTP Upgrade 前校验 token失败返回 401。

部署架构

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无需硬编码端口。