# AI Runtime 总体架构 ## 1. 概述 M-API-AI-RUNTIME 里程碑新增 Rust Heavy Runtime 作为内部重任务执行器,完成 DeepSeek 调用、学习状态分析、题目/卡片候选生成。主 API 保持业务权威层地位不变。 ### 架构图 ``` iOS / Admin / Web ↓ 主 API(NestJS) ↓ AiRuntimeJob / LearningAnalysisSnapshot ↓ Rust Heavy Runtime(内部 HTTP) ↓ DeepSeek API ↓ Rust Heavy Runtime 输出结构化结果 ↓ 主 API 二次校验 / 落库 ↓ iOS 展示 / Admin 诊断 ``` ## 2. 职责边界 ### 2.1 API(本仓库) | 负责 | 不负责 | |------|--------| | 用户权限与会员额度 | 直接调用 DeepSeek | | 学习数据读取与聚合 | Prompt 渲染 | | 用户 DeepSeek key 加密存储 | 模型输出校验 | | AI Job 创建与调度 | 任务消费 | | Snapshot 构建 | 结构化 JSON 解析 | | Runtime 内部接口提供 | 题目/卡片生成 | | Runtime 结果校验与落库 | | | iOS / Admin 对外 API | | | 平台预算与熔断 | | ### 2.2 Rust Heavy Runtime | 负责 | 不负责 | |------|--------| | Job Worker 消费 | 用户登录与会员 | | DeepSeek 调用 | 直接写业务主表 | | Prompt 渲染 | 对公网暴露接口 | | 结构化 JSON 校验 | 处理 readingTargetType | | 题目/卡片候选生成 | 用户数据查询 | | 调用日志回传 | | ### 2.3 Rust Document Runtime | 负责 | 不负责 | |------|--------| | 文档解析与阅读状态 | readingTargetType | | 阅读事件 V2 生成 | userId | | EventBuffer | 上传 API | ### 2.4 iOS | 负责 | 不负责 | |------|--------| | 阅读页 UI | 直接调 DeepSeek | | heartbeat tick 控制 | 直接调 Rust Heavy Runtime | | 补充 readingTargetType | | | 本地上传队列 + ack | | ### 2.5 Admin | 负责 | 不负责 | |------|--------| | 管理页面 | 直接调 Rust Runtime | | 成本统计与诊断 | | ## 3. Docker 部署 ``` ┌─────────────────────── Docker internal network ───────────────────────┐ │ │ │ ┌─────────┐ ┌──────────────────┐ ┌──────────────────────┐ │ │ │ MySQL │ │ api (NestJS) │ │ heavy-runtime (Rust) │ │ │ │ :3306 │ │ :3000 │ │ :8080 │ │ │ │ │ │ → 公网暴露 │ │ → 不暴露公网 │ │ │ └─────────┘ └────────┬─────────┘ └──────────┬───────────┘ │ │ │ │ │ │ ├── 调 POST /internal/runtime/* ──→ │ │ │ │ │ │ │←── Runtime 调 internal API ──┤ │ │ │ └──────────────────────────────────────────────────────────────────────┘ ``` ### docker-compose 要点 ```yaml services: api: environment: RUNTIME_INTERNAL_BASE_URL: http://heavy-runtime:8080 RUNTIME_SERVICE_TOKEN: ${RUNTIME_SERVICE_TOKEN} depends_on: - heavy-runtime networks: - zhixi-internal heavy-runtime: environment: API_INTERNAL_BASE_URL: http://api:3000 RUNTIME_SERVICE_TOKEN: ${RUNTIME_SERVICE_TOKEN} DEEPSEEK_API_KEY: ${DEEPSEEK_API_KEY} expose: - "8080" networks: - zhixi-internal networks: zhixi-internal: driver: bridge ``` 关键约束: - heavy-runtime 不映射公网端口 - 服务间通过 Docker 内部 DNS 发现(`http://heavy-runtime:8080`) - service token 通过环境变量注入 ## 4. 通信方式 ### 4.1 主任务流:Job 异步 ``` API 创建 AiRuntimeJob(status=pending) → Runtime poll /internal/runtime/jobs/poll → Runtime lock job → Runtime 获取 Snapshot + credential → Runtime 执行 DeepSeek 调用 → Runtime 提交 result 到 /internal/runtime/jobs/{jobId}/result → API 校验并落库 ``` ### 4.2 内部接口 | 接口 | 调用方 | 说明 | |------|--------|------| | `POST /internal/runtime/jobs/poll` | Runtime | 拉取 pending job | | `POST /internal/runtime/jobs/{jobId}/lock` | Runtime | 锁定 job | | `POST /internal/runtime/jobs/{jobId}/heartbeat` | Runtime | 心跳续约 | | `GET /internal/runtime/jobs/{jobId}/snapshot` | Runtime | 获取快照 | | `POST /internal/runtime/model-credentials/resolve` | Runtime | 获取模型 key | | `POST /internal/runtime/jobs/{jobId}/result` | Runtime | 提交成功结果 | | `POST /internal/runtime/jobs/{jobId}/fail` | Runtime | 提交失败 | | `POST /internal/runtime/invocation-logs` | Runtime | 提交调用日志 | | `GET /internal/runtime/health` | API | Runtime 健康检查 | ### 4.3 鉴权 - 复用已有 `InternalAuthGuard`,使用 `x-internal-api-key` header - 新增 `X-Runtime-Instance-Id` header 用于追踪 - 普通用户 JWT 不可访问 internal 接口 - service token 不可访问普通用户 API ## 5. AI Job 生命周期 ### 5.1 状态机 ``` pending ──→ locked ──→ running ──→ succeeded │ │ │ │ │ └──→ failed (retryable) ──→ pending │ │ └──→ failed (non-retryable) │ │ └──→ expired │ │ │ └──→ expired (lockUntil 超时) │ └──→ cancelled ``` ### 5.2 Job 类型 | 类型 | 说明 | 输出 | |------|------|------| | `learning_state_analysis` | 学习状态分析 | AiLearningAnalysis | | `weak_point_analysis` | 薄弱点分析 | WeakPointCandidate[] | | `next_action_planning` | 下一步建议 | NextActionRecommendation[] | | `quiz_generation` | 题目生成 | QuizQuestion[] | | `flashcard_generation` | 卡片生成 | Flashcard[] | ### 5.3 锁定与超时 ``` lockUntil = now + 60s (推荐初始值) heartbeat 延长 lockUntil lockUntil 超时后其他 Runtime 可接管 maxRetryCount = 3(默认) ``` ## 6. 用户 Key 与平台 Key ### 6.1 两种模式 | 模式 | Key 来源 | 消费方 | |------|---------|--------| | `platform_key` | 环境变量 `DEEPSEEK_API_KEY` 或 API 配置 | 平台预算 | | `user_deepseek_key` | 用户在 iOS 填写,API 加密存储 | 用户自己 | ### 6.2 Key 流转 ``` 用户填 key → API 加密落库(UserModelCredential) → Runtime resolve → API 解密返回明文 → 仅内存使用 → 不写日志 → ModelInvocationLog 只记录 credentialId,不记录 key ``` ### 6.3 安全规则 - encryptedApiKey:AES-256-GCM 加密,密钥来自环境变量 - 明文 key 不落库、不进日志、不返回前端、Admin 不可查看 - maskedKey:如 `sk-****xxxx` ## 7. 失败与重试 ### 7.1 retryable 错误 ``` MODEL_TIMEOUT, MODEL_RATE_LIMIT, NETWORK_ERROR, TEMPORARY_PROVIDER_ERROR → retryable=true, job 回到 pending, retryCount+1 ``` ### 7.2 non-retryable 错误 ``` INVALID_SNAPSHOT, INVALID_SCHEMA, INVALID_CREDENTIAL → retryable=false, job 直接 failed ``` ### 7.3 熔断 连续失败 N 次 platform_key job → 平台熔断 open → 拒绝新的 platform_key job → half_open 后试探 ### 7.4 取消 - pending job:用户/Admin 可直接取消 - running job:标记 cancelRequested,Runtime 下次 heartbeat 获知 ## 8. 结果落库边界 ### Runtime 提交 → API 校验 → 落业务表 ``` Runtime submit result → API RuntimeOutputBusinessValidator 二次校验 → 源数据归属校验 (sourceBlockIds, knowledgePointId) → 去重 (exact hash) → 写入对应业务表 (AiLearningAnalysis / QuizQuestion / Flashcard / ...) → 更新 job 状态 succeeded ``` Runtime 不直接写:AiLearningAnalysis, QuizQuestion, Flashcard, WeakPointCandidate, NextActionRecommendation, QuestionGenerationPlan, FlashcardGenerationPlan。这些只由 API 写。 ## 9. 与现有 M1 RAG/Chat 的共存 1. 本批不迁移现有 M1 RAG。 2. 现有 Chat / RAG 问答链路(AiGatewayService → DeepSeek)继续运行。 3. 新 AI Runtime 只做:学习状态分析、题目/卡片候选生成。 4. 两者不共享 Job 队列。 5. 若 Chat 结果和 AI Analysis 结果同时存在,前端应区分展示: - 对话回答:来自 Chat/RAG,时效性高 - 学习分析建议:来自 AI Runtime,基于 Snapshot 聚合 6. 后续 RAG 迁移需要独立里程碑。 ## 10. 后续扩展预留 | 方向 | 本批 | 后续 | |------|------|------| | RAG 迁移 | ❌ | 独立里程碑 | | 批量知识库分析 | ❌ (只做 user/material 级) | parentJob + childJob | | 多模型供应商 | ❌ (只做 DeepSeek) | ModelProvider trait | | A/B 测试 | ❌ | promptVersion 字段已预留 | | Prompt 热更新 | ❌ (静态模板) | PromptRegistry | | 用户自定义 baseUrl | ❌ | 需安全审计 | ## 11. 验收清单 - [ ] 文档明确 API 是业务权威层 - [ ] 文档明确 Runtime 是内部重任务执行器 - [ ] 文档明确 Runtime 不对公网暴露 - [ ] 文档明确 Docker 内部网络部署方式 - [ ] 文档明确本批不迁移 RAG - [ ] 文档明确 AI Job 异步流程 - [ ] 文档明确 Runtime 结果最终由 API 校验和落库 - [ ] 文档明确后续 iOS / Admin 只访问 API,不访问 Runtime - [ ] 文档明确后续 RAG 迁移只是预留 - [ ] 文档能指导 API-AI-001~072 的实现