# 技术选型 ## 概述 本文档记录项目中各项技术的**选型过程、替代方案对比和决策理由**。技术选型没有"绝对正确",只有"更适合"。 **定位**:本文档记录各项技术的选型过程和决策理由。 ``` 技术选型 ├── AI 编排框架 │ └── CloudWeGo Eino Graph(声明式 DAG 编排,替代手写 goroutine 管道) ├── AI 服务栈 │ ├── STT: MiMo ASR(默认) / Deepgram │ ├── LLM: DashScope qwen3-vl-plus(默认) / GPT-4o 等 OpenAI 兼容模型 │ └── TTS: MiMo TTS(默认) / OpenAI TTS ├── 持久化层 │ ├── 数据库: PostgreSQL(pgx/v5,手写 SQL) │ ├── 迁移: 嵌入式 SQL 文件,自动执行 │ └── 存储模式: 三级存储 TieredManager(L1 Memory → L2 Redis → L3 PostgreSQL) ├── 认证与用户系统 │ ├── 认证方案: JWT (HS256), access 15min + refresh 7day │ ├── JWT 库: golang-jwt/jwt/v5 │ ├── 密码哈希: bcrypt │ ├── 数据库驱动: pgx/v5(手写 SQL,不用 ORM) │ └── 前端 Token 存储: localStorage └── 前端边缘处理层 ├── 关键帧检测: Canvas 像素比较(160x120 降采样) ├── 语音检测: @ricky0123/vad-web └── 媒体采集: MediaDevices API ``` --- ## 一、AI 编排框架选型 ### 候选方案对比 | 框架 | 语言 | 特点 | CamTalk 适用性 | |------|------|------|---------------| | **CloudWeGo Eino** | Go | Go 原生、类型安全、流式原生、Graph DAG 编排 | ✅ 完美匹配 | | LangChain Go | Go | 生态丰富但较重,抽象层多 | ❌ 过度抽象 | | 自研编排 | Go | 完全可控 | ❌ 维护成本高 | ### 选择 Eino 的理由 | 维度 | 手写 goroutine(旧方案) | Eino Graph(新方案) | |------|------------------------|---------------------| | 编排方式 | 手动 `go func()` + `sync.WaitGroup` | 声明式 DAG,类型安全 | | 流式处理 | 自定义 `chan` 传递 | `StreamReader` + `Pipe`,自动转换 | | 错误处理 | 各节点独立处理,不一致 | Graph 级别统一错误传播 | | 回调/AOP | 日志散落各处 | `callbacks.Handler` 统一注入 | | 配置灵活性 | Pipeline 创建时固定 | 每请求 `Option` 动态注入 | | 可测试性 | 需启动 goroutine | `Graph.Invoke()` 直接测试 | | 扩展性 | 修改 Pipeline 代码 | 添加节点 + 边,无侵入 | ### 核心依赖 ``` github.com/cloudwego/eino v0.9.9 # 核心框架 github.com/cloudwego/eino-ext/components/model/openai v0.1.13 # OpenAI 兼容 ChatModel ``` **核心理由**: 1. Go 原生,泛型支持,编译时类型检查 2. 原生流式处理(`StreamReader`),适合 LLM token 级推送 3. Graph 支持分支、并行、循环,满足当前和未来需求 4. Callback 机制实现 AOP(日志、指标、消息推送) 5. eino-ext 提供 OpenAI ChatModel 实现,直接对接 DashScope > 详细的 Eino 框架使用文档见 [11-Eino框架技术文档](11-Eino框架技术文档.md),重构方案见 [10-Eino重构方案](10-Eino重构方案.md),实施记录见 [12-Eino重构实施记录](12-Eino重构实施记录.md)。 --- ## 二、AI 服务栈选型 ### STT(语音识别) | 方案 | 延迟 | 成本 | 特点 | |------|------|------|------| | **MiMo ASR**(默认) | ~1s | 按量计费 | 国产替代,兼容 OpenAI chat/completions 格式,HTTP 非流式 | | **Deepgram** | <500ms | 按分钟计费 | 流式识别,延迟极低,WebSocket 接口 | | Whisper API | 1-3s | 按分钟计费 | 准确率高,支持多语言 | | FunASR | <500ms | 自部署免费 | 阿里开源,中文优化 | 当前默认使用 MiMo ASR(mimo-v2.5-asr),可通过 `ai.stt.provider` 配置切换到 Deepgram。 ### LLM(多模态大模型) | 方案 | 成本 | 特点 | |------|------|------| | **DashScope qwen3-vl-plus**(默认) | 按量计费 | 阿里云,通过 OpenAI 兼容接口调用,视觉理解能力强 | | GPT-4o | $2.5/1M tokens | OpenAI,API 成熟,流式推理 | | Claude Sonnet | $3/1M tokens | Anthropic,长上下文能力强 | LLM 通过 Eino 框架的 `eino-ext/components/model/openai` ChatModel 组件接入,支持任何 OpenAI 兼容接口。配置 `ai.llm.provider`、`ai.llm.model`、`ai.llm.endpoint` 即可切换。 ### TTS(语音合成) | 方案 | 成本 | 特点 | |------|------|------| | **MiMo TTS**(默认) | 按量计费 | 国产替代,通过配置切换,模型 mimo-v2.5-tts | | OpenAI TTS | $15/1M 字符 | 音质自然,支持流式,默认模型 tts-1,语音 alloy | 当前默认使用 MiMo TTS(mimo-v2.5-tts),可通过 `ai.tts.provider` 配置切换到 OpenAI TTS。 --- ## 二、持久化层选型 ### 数据特征分析 | 数据 | 结构特征 | 读写模式 | 数据量级 | |------|---------|---------|---------| | 对话消息 | 强结构化 | 写多读少,按会话聚合读取 | 中(每用户日均 ~100 条) | | 会话元信息 | 强结构化 | 写少读少 | 低 | | 对话上下文 | 半结构化 JSON | 高频读写,TTL 过期 | 低(仅当前窗口) | | 用量统计 | 强结构化 | 写多,定期聚合读 | 低(日粒度汇总后很小) | | 用户偏好 | 强结构化 KV | 写极少读少 | 极低 | | 关键帧图像 | 非结构化二进制 | 写少,按需读 | 大(单张 100KB~1MB) | 核心数据(对话、会话、统计)都是**强结构化**的,关系型数据库天然适配。"对话上下文"是半结构化 JSON,需要数据库对 JSON 有良好支持。 ### 候选方案对比 | 维度 | PostgreSQL | MySQL | SQLite | MongoDB | TiDB | |------|-----------|-------|--------|---------|------| | 数据模型 | 关系型 + JSONB | 关系型 | 关系型(嵌入式) | 文档型(BSON) | 关系型(分布式) | | JSON 支持 | JSONB 原生索引 | JSON 类型,索引弱 | 无原生支持 | 天生擅长 | 兼容 MySQL JSON | | 关联查询 | 强 | 强 | 强 | 弱(需 $lookup) | 强 | | 聚合统计 | 窗口函数/CTE | 基础聚合 | 基础聚合 | 聚合管道 | 强 | | 并发能力 | 高(MVCC) | 中 | 低(单写锁) | 高 | 极高(分布式) | | Go 生态 | pgx / GORM | go-sql-driver | go-sqlite3 | mongo-go-driver | 兼容 MySQL 驱动 | ### 淘汰理由 **SQLite** — 写锁是全局的,并发写入会频繁锁等待。多个 Go Gateway 实例无法共享同一 SQLite 文件。适合单机桌面应用,不适合 Web 服务。 **MySQL** — JSON 类型索引能力弱,无法对 JSON 内部字段高效查询。缺少 `gen_random_uuid()` 等原生函数。如果团队只熟悉 MySQL,MVP 阶段完全可用,后续复杂查询会比 PostgreSQL 麻烦。 **MongoDB** — `messages` 需按 `session_id` 关联 `sessions`,MongoDB 中要用 `$lookup`,写法复杂且性能不如 SQL JOIN。用量统计的"按天聚合"用 SQL 一句话搞定,MongoDB 聚合管道代码量多 3-5 倍。 **TiDB** — 部署复杂(至少 3 PD + 3 TiKV + 2 TiDB),单机 PostgreSQL 完全够用,过度设计。 ### 选择 PostgreSQL 的理由 | 项目需求 | PostgreSQL 匹配点 | |---------|------------------| | 强结构化数据 | 原生关系型,SQL 标准完备 | | JSON 半结构化 | JSONB 支持索引、路径查询、部分更新 | | messages ↔ sessions 关联 | 完整的 FK 约束 + JOIN | | 用量按天/周/月聚合 | 窗口函数、CTE、`DATE_TRUNC` | | Go 后端对接 | pgx 驱动性能优秀,GORM/Ent 支持成熟 | | 未来全文搜索 | 内置 `tsvector`,无需额外引入 ES | ### Go 集成示例 ```go import "github.com/jackc/pgx/v5/pgxpool" pool, _ := pgxpool.New(ctx, "postgres://user:pass@localhost:5432/vision_ai") func SaveMessage(ctx context.Context, pool *pgxpool.Pool, msg *Message) error { _, err := pool.Exec(ctx, `INSERT INTO messages (session_id, role, content, image_url, tokens_used) VALUES ($1, $2, $3, $4, $5)`, msg.SessionID, msg.Role, msg.Content, msg.ImageURL, msg.TokensUsed, ) return err } func GetWeeklyUsage(ctx context.Context, pool *pgxpool.Pool, userID string) ([]UsageRow, error) { rows, _ := pool.Query(ctx, `SELECT date, llm_tokens, estimated_cost FROM usage_daily WHERE user_id = $1 AND date >= CURRENT_DATE - INTERVAL '7 days' ORDER BY date`, userID) defer rows.Close() // ... scan rows } ``` JSONB 包容查询: ```sql SELECT id, content, created_at FROM messages WHERE role = 'user' AND content @> '{"text": "花"}' ORDER BY created_at DESC LIMIT 20; ``` ### 冷热分离架构(三级存储) ``` Go Gateway (TieredManager) ├── L1: Memory(进程内缓存,微秒级) ├── L2: Redis(分布式缓存,毫秒级) └── L3: PostgreSQL(持久化存储,冷数据) 读取路径:L1 → L2 → L3,逐级回源,命中后向上回填 写入路径:L1 → L2(同步) → L3(异步) ``` `TieredManager` 自动管理三级存储,后台 goroutine 每 30 秒 ping Redis 健康状态,Redis 故障时自动降级为 L1+L3 模式。 ### 决策流程 ``` 需要持久化? ├── 否 → 继续用 Redis └── 是 → 数据强结构化? ├── 否, 高度嵌套 → 考虑 MongoDB └── 是 → 数据量级? ├── < 100GB, 单机可扛 → PostgreSQL ├── 海量, 需水平扩展 → TiDB / CockroachDB └── 极小, 单文件即可 → SQLite ``` --- ## 二、前端边缘处理层选型 ### 总览 | 能力 | 当前选型 | 选择理由 | |------|---------|---------| | 边缘推理 | ONNX Runtime Web | 通用推理引擎,模型无关,WASM 加速 | | 语音检测 | @ricky0123/vad-web | 包装原生 WebRTC VAD,零延迟,体积极小 | | 媒体采集 | MediaDevices API | 浏览器原生接口,无中间层,零依赖 | ### 边缘推理:ONNX Runtime Web | 方案 | 特点 | 适用场景 | |------|------|---------| | **ONNX Runtime Web** | 通用推理引擎,支持任意 ONNX 模型,WASM 加速 | 自定义模型 pipeline | | TensorFlow.js | Google 生态,WebGL/WebGPU 加速 | 模型本身就是 TF 格式 | | MediaPipe | 开箱即用 CV 任务 | 只需常见 CV 任务,不需自定义模型 | | Transformers.js | HuggingFace 生态 | 快速集成预训练模型 | 项目需要同时跑 VAD 和关键帧检测两种自定义模型。ONNX 是跨框架通用格式,核心优势是**模型无关**。 ### 语音检测:@ricky0123/vad-web | 方案 | 特点 | 适用场景 | |------|------|---------| | **@ricky0123/vad-web** | 基于 WebRTC VAD,~100KB 含 WASM,纯前端零延迟 | "有没有人说话"二分类 | | Web Audio API + 能量检测 | AnalyserNode 计算 RMS | 极简但不抗噪 | | Silero VAD (ONNX) | 神经网络级 VAD | 嘈杂环境需更精准 | | Picovoice Porcupine | 商业级唤醒词引擎 | 需要唤醒词功能 | vad-web 是"够用且最轻"的平衡点——直接包装浏览器原生 WebRTC VAD 算法。 ### 媒体采集:MediaDevices API `navigator.mediaDevices.getUserMedia()` 是所有浏览器音视频采集的**唯一标准入口**。所有上层封装库底层都是调这个 API。项目需要原始 MediaStream,用封装库反而要多一层解包。 ### 选型共同逻辑 三个技术选择的共同决策模式——**选择最薄的抽象层**: | 技术 | "最薄"体现在 | |------|------------| | ONNX Runtime Web | 不绑定特定框架,模型格式通用 | | @ricky0123/vad-web | 包装原生 WebRTC VAD,没有多余的模型加载 | | MediaDevices API | 直接用浏览器原生接口,不加封装层 | 与"前端做轻量预处理"原则一致:前端层只需采集和判断"有没有值得发给后端的数据"。 --- ## 四、认证与用户系统选型 ### 总览 | 能力 | 选型 | 选择理由 | |------|------|---------| | 认证方案 | JWT (HS256) | 无状态,分布式友好,实现简单 | | JWT 库 | golang-jwt/jwt/v5 | 社区主流,v5 活跃维护 | | 密码哈希 | bcrypt | Go 标准库直接可用,安全性足够 | | 数据库驱动 | pgx/v5 | Go 生态性能最优的 PostgreSQL 驱动 | | 数据库迁移 | 手写 SQL | MVP 阶段足够,后续可引入 golang-migrate | ### 认证方案:JWT | 方案 | 特点 | 适用场景 | |------|------|---------| | **JWT (HS256)** | 无状态 token,服务端不存 session,水平扩展友好 | 分布式部署、前后端分离 | | Session + Cookie | 有状态,服务端存 session(通常 Redis) | 传统 Web 应用、需要服务端控制会话 | | OAuth2 | 第三方登录授权 | 需要接入微信/GitHub 等第三方登录 | 选择 JWT 的核心理由:项目架构是前后端分离 + WebSocket 长连接,JWT 无需服务端维护 session 状态,天然适配。HS256 对称签名足以满足安全需求,实现比 RS256 简单。 Token 策略采用 **access (15min) + refresh (7day) 双 token**:access_token 短生命周期降低泄露风险,refresh_token 支持无感续期。 ### JWT 库:golang-jwt/jwt/v5 | 方案 | 状态 | 特点 | |------|------|------| | **golang-jwt/jwt/v5** | 活跃维护 | dgrijalva/jwt-go 的官方继任,社区主流 | | dgrijalva/jwt-go | 已停维护 | 原始库,不再更新 | | lestrrat-go/jwx | 活跃 | 功能更全(JWE/JWS),但项目只需签名,过度引入 | v5 是 Go 生态中 JWT 的事实标准,API 简洁,文档完善。 ### 密码哈希:bcrypt | 方案 | 特点 | 选择理由 | |------|------|---------| | **bcrypt** | 自适应 cost factor,抗暴力破解 | Go 标准库 `golang.org/x/crypto/bcrypt` 直接可用 | | argon2 | 2015 年密码哈希竞赛冠军,抗 GPU/ASIC | 安全性更高,但 Go 生态库不如 bcrypt 成熟 | | scrypt | 内存硬哈希 | 参数调优复杂,bcrypt 已足够 | bcrypt 的 `cost` 参数可随硬件升级调大,当前默认 cost=10 足够安全。 ### 数据库驱动:pgx/v5 | 方案 | 特点 | 适用场景 | |------|------|---------| | **pgx/v5** | 原生 PostgreSQL 协议实现,连接池 pgxpool,性能最优 | 需要高性能、直接写 SQL | | GORM | 全功能 ORM,自动迁移、关联预加载 | 快速开发、不想写 SQL | | Ent | Facebook 出品,类型安全的 ORM | 大型项目、强类型需求 | | database/sql + lib/pq | 标准接口,但 lib/pq 已停维护 | 简单场景 | 项目规模不大(4 张表),手写 SQL 更可控,避免 ORM 的抽象泄漏和性能黑盒。pgx 原生支持 `pgxpool` 连接池,无需额外引入。 ### 数据库迁移:手写 SQL | 方案 | 特点 | 适用场景 | |------|------|---------| | **手写 SQL** | 零依赖,完全可控 | 表少(<10 张)、团队小 | | golang-migrate | CLI + 库双模式,支持版本回滚 | 表多、需要严格版本管理 | | Atlas | 声明式迁移,HCL 定义 schema | 大型项目、多环境管理 | MVP 阶段 4 张表,手写 `schema.sql` 即可。后续表结构复杂后可引入 golang-migrate。 ### 前端 Token 存储 | 方案 | 特点 | 选择理由 | |------|------|---------| | **localStorage** | 持久化存储,刷新不丢失,JS 可直接读写 | 简单直接,SPA 应用标准做法 | | httpOnly Cookie | 防 XSS 读取,但需防 CSRF | 传统 Web 应用,需额外 CSRF 防护 | | sessionStorage | 仅当前标签页有效 | 关闭标签页需重新登录,体验差 | JWT 存 localStorage,配合请求拦截器统一附加 `Authorization: Bearer ` header。refresh_token 同样存 localStorage,401 时自动触发刷新流程。