api-server/docs/ai-runtime-user-api.md
wangdl c88af39673
All checks were successful
Deploy API Server / build-and-deploy (push) Successful in 45s
feat: AI Runtime 完整业务逻辑实现
- 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>
2026-06-18 11:22:03 +08:00

7.7 KiB

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:

{
  "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:

{ "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:

[
  { "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:

{
  "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:

[
  { "id": "clx...", "targetType": "material", "targetId": "m1",
    "learningState": "mastered", "riskLevel": "low", "confidence": 0.85,
    "summary": "...", "createdAt": "2026-..." }
]

2.2 查询单个分析

GET /ai/analyses/:id

Response 200:

{
  "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:

[
  { "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:

[
  { "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:

[
  { "id": "clx...", "knowledgeBaseId": "kb1", "title": "...", "questionCount": 10,
    "sourceType": "ai", "status": "active", "createdAt": "2026-..." }
]

4.2 查询单个 Quiz

GET /ai/quizzes/:quizId

Response 200:

{
  "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:

[
  { "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:

[
  { "id": "clx...", "front": "...", "back": "...", "hint": "...",
    "difficultyLevel": "medium", "knowledgePointId": "kp1",
    "sourceType": "ai", "status": "active", "createdAt": "2026-..." }
]

5.2 查询单个卡片

GET /ai/flashcards/:cardId

Response 200:

{
  "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 认证,不对外暴露。