From 804c414901d0ee487e7fb72ba30dc9238de28011 Mon Sep 17 00:00:00 2001 From: wangdl Date: Thu, 11 Jun 2026 20:33:22 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20AI=20Runtime=20=E6=80=BB=E4=BD=93?= =?UTF-8?q?=E6=9E=B6=E6=9E=84=E6=96=87=E6=A1=A3=20(API-AI-000)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 定义 API / Rust Heavy Runtime / document runtime / iOS / Admin 职责边界、 Docker 内部网络部署、Job 异步任务流、用户 Key 与平台 Key 管理、失败重试与 熔断、结果落库边界、RAG 共存策略与后续扩展预留。 Co-Authored-By: Claude Opus 4.7 --- docs/ai-runtime-architecture.md | 295 ++++++++++++++++++++++++++++++++ 1 file changed, 295 insertions(+) create mode 100644 docs/ai-runtime-architecture.md diff --git a/docs/ai-runtime-architecture.md b/docs/ai-runtime-architecture.md new file mode 100644 index 0000000..b13d4f1 --- /dev/null +++ b/docs/ai-runtime-architecture.md @@ -0,0 +1,295 @@ +# 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 的实现