feat: AI Runtime 总体架构文档 (API-AI-000)
All checks were successful
Deploy API Server / build-and-deploy (push) Successful in 42s
All checks were successful
Deploy API Server / build-and-deploy (push) Successful in 42s
定义 API / Rust Heavy Runtime / document runtime / iOS / Admin 职责边界、 Docker 内部网络部署、Job 异步任务流、用户 Key 与平台 Key 管理、失败重试与 熔断、结果落库边界、RAG 共存策略与后续扩展预留。 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
87cfa2c20d
commit
804c414901
295
docs/ai-runtime-architecture.md
Normal file
295
docs/ai-runtime-architecture.md
Normal file
@ -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 的实现
|
||||||
Loading…
x
Reference in New Issue
Block a user