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 要点
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 的共存
- 本批不迁移现有 M1 RAG。
- 现有 Chat / RAG 问答链路(AiGatewayService → DeepSeek)继续运行。
- 新 AI Runtime 只做:学习状态分析、题目/卡片候选生成。
- 两者不共享 Job 队列。
- 若 Chat 结果和 AI Analysis 结果同时存在,前端应区分展示:
- 对话回答:来自 Chat/RAG,时效性高
- 学习分析建议:来自 AI Runtime,基于 Snapshot 聚合
- 后续 RAG 迁移需要独立里程碑。
10. 后续扩展预留
| 方向 |
本批 |
后续 |
| RAG 迁移 |
❌ |
独立里程碑 |
| 批量知识库分析 |
❌ (只做 user/material 级) |
parentJob + childJob |
| 多模型供应商 |
❌ (只做 DeepSeek) |
ModelProvider trait |
| A/B 测试 |
❌ |
promptVersion 字段已预留 |
| Prompt 热更新 |
❌ (静态模板) |
PromptRegistry |
| 用户自定义 baseUrl |
❌ |
需安全审计 |
11. 验收清单