All checks were successful
Deploy API Server / build-and-deploy (push) Successful in 45s
- runtime-internal.service: resolveSnapshot 自动重建、persistResult 5种jobType持久化、validateOutput 校验、convertQuizCandidates/convertFlashcardCandidates 候选转换、notifyJobComplete 通知、JOB_CANCELLED处理、heartbeat 双阶段更新+取消检测 - user-ai.service: createAnalysisJob 11步流程、cancelJob、publishQuiz/publishFlashcard、getAnalysis/listAnalyses等 - user-ai.controller: 20+ 用户API端点 - 新增服务: SnapshotBuilderService、PriorityRulesService、SnapshotCleanupService、JobReaperService - 新增模块: admin-learning (CRUD管理) - Prisma schema: cancelRequestedAt/cancelledAt/sourceBlockIds 字段、expiresAt 索引 - 文档: ai-runtime-user-api.md、Issue 记录 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
323 lines
7.7 KiB
Markdown
323 lines
7.7 KiB
Markdown
# AI Runtime 用户 API 接入文档
|
|
|
|
## 概述
|
|
|
|
本文档描述 AI Runtime 对外暴露的用户面 REST API。所有端点均需 Bearer Token 认证(`Authorization: Bearer <token>`),用户只能操作自己的资源。
|
|
|
|
Base URL: `/ai`
|
|
|
|
---
|
|
|
|
## 1. Job 管理
|
|
|
|
### 1.1 创建分析 Job
|
|
|
|
```
|
|
POST /ai/jobs
|
|
```
|
|
|
|
**Request Body:**
|
|
```json
|
|
{
|
|
"jobType": "learning_state_analysis | weak_point_analysis | next_action_planning | quiz_generation | flashcard_generation",
|
|
"targetType": "user | material | knowledge_base",
|
|
"targetId": "string",
|
|
"idempotencyKey": "string (optional)",
|
|
"apiKeyMode": "platform_key | user_deepseek_key (optional, default from settings)",
|
|
"credentialId": "string (optional, required for user_deepseek_key)",
|
|
"questionCount": 5,
|
|
"difficultyLevel": "easy | medium | hard",
|
|
"questionTypes": ["choice", "judge"],
|
|
"cardCount": 5,
|
|
"knowledgePointIds": ["kp1", "kp2"]
|
|
}
|
|
```
|
|
|
|
**Response 201:**
|
|
```json
|
|
{ "jobId": "clx...", "status": "pending", "createdAt": "2026-...", "planId": "clx... (quiz/flashcard only)" }
|
|
```
|
|
|
|
**Error Codes:** `AI_ANALYSIS_DISABLED`, `INVALID_JOB_TYPE`, `INVALID_TARGET_TYPE`, `CREDENTIAL_REQUIRED`, `CREDENTIAL_NOT_FOUND`
|
|
|
|
### 1.2 查询 Job 列表
|
|
|
|
```
|
|
GET /ai/jobs?status=pending&take=20
|
|
```
|
|
|
|
**Response 200:**
|
|
```json
|
|
[
|
|
{ "id": "clx...", "jobType": "learning_state_analysis", "targetType": "material", "targetId": "m1",
|
|
"status": "pending", "priority": 50, "errorCode": null, "cancelRequestedAt": null,
|
|
"startedAt": null, "finishedAt": null, "createdAt": "2026-..." }
|
|
]
|
|
```
|
|
|
|
### 1.3 查询单个 Job
|
|
|
|
```
|
|
GET /ai/jobs/:jobId
|
|
```
|
|
|
|
**Response 200:**
|
|
```json
|
|
{
|
|
"id": "clx...", "jobType": "...", "targetType": "...", "targetId": "...",
|
|
"status": "succeeded", "priority": 50, "snapshotId": "snap-...",
|
|
"attemptNo": 0, "retryCount": 0, "maxRetryCount": 3,
|
|
"errorCode": null, "errorMessage": null,
|
|
"cancelRequestedAt": null, "cancelledAt": null,
|
|
"startedAt": "2026-...", "finishedAt": "2026-...",
|
|
"createdAt": "2026-...", "updatedAt": "2026-..."
|
|
}
|
|
```
|
|
|
|
### 1.4 取消 Job
|
|
|
|
```
|
|
POST /ai/jobs/:jobId/cancel
|
|
```
|
|
|
|
**Response 200:** `{ "jobId": "clx...", "status": "cancelled | cancel_requested" }`
|
|
|
|
**Error Codes:** `JOB_NOT_FOUND`, `JOB_CANNOT_CANCEL`
|
|
|
|
---
|
|
|
|
## 2. 分析结果查询
|
|
|
|
### 2.1 查询分析列表
|
|
|
|
```
|
|
GET /ai/analyses?targetType=material&targetId=m1&take=20
|
|
```
|
|
|
|
**Response 200:**
|
|
```json
|
|
[
|
|
{ "id": "clx...", "targetType": "material", "targetId": "m1",
|
|
"learningState": "mastered", "riskLevel": "low", "confidence": 0.85,
|
|
"summary": "...", "createdAt": "2026-..." }
|
|
]
|
|
```
|
|
|
|
### 2.2 查询单个分析
|
|
|
|
```
|
|
GET /ai/analyses/:id
|
|
```
|
|
|
|
**Response 200:**
|
|
```json
|
|
{
|
|
"id": "clx...", "userId": "...", "jobId": "...", "snapshotId": "...",
|
|
"targetType": "material", "targetId": "m1",
|
|
"learningState": "mastered", "summary": "...", "riskLevel": "low",
|
|
"confidence": 0.85, "evidence": ["fact1", "fact2"],
|
|
"nextActionIds": ["..."],
|
|
"promptVersion": "learning_state_v1", "schemaVersion": "analysis_output_v1",
|
|
"createdAt": "2026-...", "updatedAt": "2026-..."
|
|
}
|
|
```
|
|
|
|
### 2.3 触发重新分析
|
|
|
|
```
|
|
POST /ai/reanalyze
|
|
```
|
|
|
|
**Request Body:** `{ "targetType": "material", "targetId": "m1" }`
|
|
|
|
**Response 201:** `{ "jobId": "clx...", "status": "pending", "createdAt": "2026-..." }`
|
|
|
|
---
|
|
|
|
## 3. 建议 / 弱项查询
|
|
|
|
### 3.1 查询建议列表
|
|
|
|
```
|
|
GET /ai/recommendations?targetType=material&targetId=m1&status=active&take=20
|
|
```
|
|
|
|
**Response 200:**
|
|
```json
|
|
[
|
|
{ "id": "clx...", "actionType": "review", "targetType": "material", "targetId": "m1",
|
|
"title": "...", "reason": "...", "priority": 10, "estimatedMinutes": 15,
|
|
"deviceSuitability": "phone", "status": "active", "createdAt": "2026-..." }
|
|
]
|
|
```
|
|
|
|
### 3.2 查询弱项列表
|
|
|
|
```
|
|
GET /ai/weak-points?targetType=material&targetId=m1&status=active&take=20
|
|
```
|
|
|
|
**Response 200:**
|
|
```json
|
|
[
|
|
{ "id": "clx...", "knowledgePointId": "kp1", "title": "Grammar: Tense",
|
|
"reason": "...", "confidence": 0.9, "evidence": ["..."],
|
|
"status": "active", "targetType": "material", "targetId": "m1", "createdAt": "2026-..." }
|
|
]
|
|
```
|
|
|
|
---
|
|
|
|
## 4. 题目查询
|
|
|
|
### 4.1 查询题目列表
|
|
|
|
```
|
|
GET /ai/quizzes?knowledgeBaseId=kb1&status=active&take=20
|
|
```
|
|
|
|
**Response 200:**
|
|
```json
|
|
[
|
|
{ "id": "clx...", "knowledgeBaseId": "kb1", "title": "...", "questionCount": 10,
|
|
"sourceType": "ai", "status": "active", "createdAt": "2026-..." }
|
|
]
|
|
```
|
|
|
|
### 4.2 查询单个 Quiz
|
|
|
|
```
|
|
GET /ai/quizzes/:quizId
|
|
```
|
|
|
|
**Response 200:**
|
|
```json
|
|
{
|
|
"id": "clx...", "knowledgeBaseId": "kb1", "title": "...", "description": "...",
|
|
"questionCount": 10, "sourceType": "ai", "sourceId": "job-...",
|
|
"status": "active", "createdAt": "2026-...", "updatedAt": "2026-..."
|
|
}
|
|
```
|
|
|
|
### 4.3 查询 Quiz 题目详情
|
|
|
|
```
|
|
GET /ai/quizzes/:quizId/questions
|
|
```
|
|
|
|
**Response 200:**
|
|
```json
|
|
[
|
|
{ "id": "clx...", "type": "choice", "stem": "...", "options": ["A", "B", "C", "D"],
|
|
"answer": "A", "explanation": "...", "sourceBlockIds": ["..."], "orderIndex": 0 }
|
|
]
|
|
```
|
|
|
|
### 4.4 发布 Quiz (draft → active)
|
|
|
|
```
|
|
POST /ai/quizzes/:quizId/publish
|
|
```
|
|
|
|
**Response 200:** `{ "quizId": "clx...", "status": "active" }`
|
|
|
|
**Error Codes:** `QUIZ_NOT_FOUND`, `QUIZ_NOT_READY`
|
|
|
|
---
|
|
|
|
## 5. 卡片查询
|
|
|
|
### 5.1 查询卡片列表
|
|
|
|
```
|
|
GET /ai/flashcards?knowledgePointId=kp1&status=active&take=20
|
|
```
|
|
|
|
**Response 200:**
|
|
```json
|
|
[
|
|
{ "id": "clx...", "front": "...", "back": "...", "hint": "...",
|
|
"difficultyLevel": "medium", "knowledgePointId": "kp1",
|
|
"sourceType": "ai", "status": "active", "createdAt": "2026-..." }
|
|
]
|
|
```
|
|
|
|
### 5.2 查询单个卡片
|
|
|
|
```
|
|
GET /ai/flashcards/:cardId
|
|
```
|
|
|
|
**Response 200:**
|
|
```json
|
|
{
|
|
"id": "clx...", "front": "...", "back": "...", "hint": "...",
|
|
"difficultyLevel": "medium", "knowledgePointId": "kp1",
|
|
"sourceBlockIds": ["..."],
|
|
"sourceType": "ai", "sourceId": "job-...", "generatedByJobId": "job-...",
|
|
"status": "active", "createdAt": "2026-...", "updatedAt": "2026-..."
|
|
}
|
|
```
|
|
|
|
### 5.3 发布卡片 (draft → active)
|
|
|
|
```
|
|
POST /ai/flashcards/:cardId/publish
|
|
```
|
|
|
|
**Response 200:** `{ "cardId": "clx...", "status": "active" }`
|
|
|
|
**Error Codes:** `FLASHCARD_NOT_FOUND`, `FLASHCARD_NOT_DRAFT`
|
|
|
|
---
|
|
|
|
## 6. 反馈
|
|
|
|
### 6.1 通用反馈
|
|
|
|
```
|
|
POST /ai/feedback
|
|
```
|
|
|
|
**Request Body:** `{ "category": "bug|feature|ux|other", "content": "...", "email": "optional", "deviceInfo": {} }`
|
|
|
|
**Response 201:** `{ "id": "clx...", "status": "open", "createdAt": "2026-..." }`
|
|
|
|
### 6.2 AI 产出物反馈
|
|
|
|
```
|
|
POST /ai/artifacts/:type/:id/feedback
|
|
```
|
|
|
|
**Path Params:** `type` = `analysis | quiz | flashcard`
|
|
|
|
**Request Body:** `{ "feedbackType": "correct|incorrect|helpful|not_helpful|...", "reason": "optional" }`
|
|
|
|
**Response 201:** `{ "id": "clx...", "feedbackType": "correct", "createdAt": "2026-..." }`
|
|
|
|
**Error Codes:** `ARTIFACT_NOT_FOUND`
|
|
|
|
---
|
|
|
|
## 错误码汇总
|
|
|
|
| 错误码 | HTTP Status | 说明 |
|
|
|--------|------------|------|
|
|
| `AI_ANALYSIS_DISABLED` | 400 | 用户关闭了 AI 分析 |
|
|
| `INVALID_JOB_TYPE` | 400 | 不支持的 jobType |
|
|
| `INVALID_TARGET_TYPE` | 400 | targetType 不匹配 jobType 要求 |
|
|
| `CREDENTIAL_REQUIRED` | 400 | user_deepseek_key 模式需提供 credentialId |
|
|
| `CREDENTIAL_NOT_FOUND` | 404 | 凭证不存在或未激活 |
|
|
| `JOB_NOT_FOUND` | 404 | Job 不存在或不属于用户 |
|
|
| `JOB_CANNOT_CANCEL` | 400 | Job 已处于终态,无法取消 |
|
|
| `QUIZ_NOT_FOUND` | 404 | Quiz 不存在或不属于用户 |
|
|
| `QUIZ_NOT_READY` | 400 | Quiz 非 ready 状态,无法发布 |
|
|
| `FLASHCARD_NOT_FOUND` | 404 | 卡片不存在或不属于用户 |
|
|
| `FLASHCARD_NOT_DRAFT` | 400 | 卡片非 draft 状态,无法发布 |
|
|
| `ANALYSIS_NOT_FOUND` | 404 | 分析结果不存在 |
|
|
| `ARTIFACT_NOT_FOUND` | 404 | 产出物不存在或不属于用户 |
|
|
|
|
## 认证
|
|
|
|
所有端点使用 `Authorization: Bearer <JWT>` 认证,用户身份从 JWT payload 提取。内部端点(`/internal/runtime/*`)使用 Service Token 认证,不对外暴露。
|