api-server/docs/ai-runtime-architecture.md
wangdl 804c414901
All checks were successful
Deploy API Server / build-and-deploy (push) Successful in 42s
feat: AI Runtime 总体架构文档 (API-AI-000)
定义 API / Rust Heavy Runtime / document runtime / iOS / Admin 职责边界、
Docker 内部网络部署、Job 异步任务流、用户 Key 与平台 Key 管理、失败重试与
熔断、结果落库边界、RAG 共存策略与后续扩展预留。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-11 20:33:22 +08:00

296 lines
9.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# AI Runtime 总体架构
## 1. 概述
M-API-AI-RUNTIME 里程碑新增 Rust Heavy Runtime 作为内部重任务执行器,完成 DeepSeek 调用、学习状态分析、题目/卡片候选生成。主 API 保持业务权威层地位不变。
### 架构图
```
iOS / Admin / Web
主 APINestJS
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 安全规则
- encryptedApiKeyAES-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标记 cancelRequestedRuntime 下次 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 的实现