360 lines
8.6 KiB
Markdown
360 lines
8.6 KiB
Markdown
|
|
# API 与 Rust Runtime 内部通信协议
|
|||
|
|
|
|||
|
|
## 1. 概述
|
|||
|
|
|
|||
|
|
本文档定义主 API 与 Rust Heavy Runtime 之间的内部 HTTP 通信协议。
|
|||
|
|
|
|||
|
|
通信方向:
|
|||
|
|
- Runtime → API:拉取 Job、提交结果、提交日志
|
|||
|
|
- API → Runtime:健康检查(可选)
|
|||
|
|
|
|||
|
|
## 2. 鉴权
|
|||
|
|
|
|||
|
|
所有 `/internal/runtime/*` 接口使用 `InternalAuthGuard`。
|
|||
|
|
|
|||
|
|
**请求头**:
|
|||
|
|
```
|
|||
|
|
x-internal-api-key: <RUNTIME_SERVICE_TOKEN>
|
|||
|
|
x-runtime-instance-id: runtime-001
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
- `x-internal-api-key`:与 API 环境变量 `INTERNAL_API_KEY` 一致
|
|||
|
|
- `x-runtime-instance-id`:Runtime 实例标识,记录到日志
|
|||
|
|
|
|||
|
|
**安全约束**:
|
|||
|
|
- 普通用户 JWT 不可访问 internal 接口
|
|||
|
|
- service token 不可访问普通用户 API
|
|||
|
|
- Runtime 不可通过 internal 接口访问非当前 job 所需数据
|
|||
|
|
|
|||
|
|
## 3. 错误响应格式
|
|||
|
|
|
|||
|
|
所有 internal 接口失败时返回:
|
|||
|
|
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"statusCode": 400,
|
|||
|
|
"errorCode": "INVALID_SNAPSHOT",
|
|||
|
|
"message": "Snapshot has expired for this job",
|
|||
|
|
"timestamp": "2026-06-11T10:00:00.000Z"
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 错误码
|
|||
|
|
|
|||
|
|
| 错误码 | HTTP | 说明 | retryable |
|
|||
|
|
|--------|------|------|-----------|
|
|||
|
|
| `JOB_NOT_FOUND` | 404 | Job 不存在 | false |
|
|||
|
|
| `JOB_ALREADY_LOCKED` | 409 | 已被其他 Runtime 锁定 | true |
|
|||
|
|
| `SNAPSHOT_EXPIRED` | 410 | 快照已过期 | true |
|
|||
|
|
| `SNAPSHOT_NOT_FOUND` | 404 | 快照不存在 | false |
|
|||
|
|
| `CREDENTIAL_NOT_FOUND` | 404 | 凭证不存在 | false |
|
|||
|
|
| `CREDENTIAL_INVALID` | 422 | 凭证无效 | false |
|
|||
|
|
| `RESULT_ALREADY_EXISTS` | 409 | 重复提交 | false |
|
|||
|
|
| `RESULT_SCHEMA_UNSUPPORTED` | 422 | schema 版本不支持 | false |
|
|||
|
|
| `RUNTIME_VERSION_INCOMPATIBLE` | 422 | Runtime 版本不兼容 | false |
|
|||
|
|
| `INTERNAL_ERROR` | 500 | 内部错误 | true |
|
|||
|
|
|
|||
|
|
## 4. 接口详情
|
|||
|
|
|
|||
|
|
### 4.1 Poll Jobs
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
POST /internal/runtime/jobs/poll
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Runtime 拉取待执行 job。API 根据 Runtime 的 `supportedJobTypes` 和 `capabilities` 过滤兼容的 job。
|
|||
|
|
|
|||
|
|
**请求**:
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"runtimeInstanceId": "runtime-001",
|
|||
|
|
"supportedJobTypes": ["learning_state_analysis", "quiz_generation"],
|
|||
|
|
"limit": 5,
|
|||
|
|
"capabilities": {
|
|||
|
|
"supportedSnapshotVersions": ["ai_snapshot_v1"],
|
|||
|
|
"supportedOutputSchemaVersions": ["analysis_output_v1", "quiz_output_v1"]
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**响应 200**:
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"jobs": [
|
|||
|
|
{
|
|||
|
|
"id": "job-abc123",
|
|||
|
|
"jobType": "learning_state_analysis",
|
|||
|
|
"targetType": "material",
|
|||
|
|
"targetId": "mat-xyz",
|
|||
|
|
"priority": 0,
|
|||
|
|
"snapshotId": "snap-001",
|
|||
|
|
"promptVersion": "learning_state_v1",
|
|||
|
|
"outputSchemaVersion": "analysis_output_v1"
|
|||
|
|
}
|
|||
|
|
]
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 4.2 Lock Job
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
POST /internal/runtime/jobs/{jobId}/lock
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Runtime 锁定一个 job,获取执行权。
|
|||
|
|
|
|||
|
|
**请求**:
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"runtimeInstanceId": "runtime-001"
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**响应 200**:
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"jobId": "job-abc123",
|
|||
|
|
"status": "locked",
|
|||
|
|
"lockUntil": 1700000000123
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 4.3 Heartbeat
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
POST /internal/runtime/jobs/{jobId}/heartbeat
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Runtime 延长 lock 有效期。
|
|||
|
|
|
|||
|
|
**请求**:
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"runtimeInstanceId": "runtime-001"
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**响应 204**:空 body,仅延长 `lockUntil`。
|
|||
|
|
|
|||
|
|
### 4.4 Get Snapshot
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
GET /internal/runtime/jobs/{jobId}/snapshot
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Runtime 获取 job 关联的 LearningAnalysisSnapshot。
|
|||
|
|
|
|||
|
|
**响应 200**:
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"jobId": "job-abc123",
|
|||
|
|
"snapshotId": "snap-001",
|
|||
|
|
"snapshotVersion": "ai_snapshot_v1",
|
|||
|
|
"privacyScope": { "allowDocumentContent": true },
|
|||
|
|
"userProfile": { "learningGoal": "exam", "currentLevel": "intermediate" },
|
|||
|
|
"aiSettings": { "allowAiAnalysis": true },
|
|||
|
|
"learningBehaviorSummary": { "totalActiveSeconds": 3600 },
|
|||
|
|
"materialProgressSummary": { "progress": 0.6 },
|
|||
|
|
"behaviorSignals": { "engagementSignal": "high" },
|
|||
|
|
"scoreSignals": { "masteryRiskScore": 0.3 },
|
|||
|
|
"constraints": { "dailyAvailableMinutes": 60 },
|
|||
|
|
"allowedModelFields": ["learningGoal", "currentLevel"]
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**错误**:
|
|||
|
|
- `404 SNAPSHOT_NOT_FOUND` — 快照不存在
|
|||
|
|
- `410 SNAPSHOT_EXPIRED` — 快照已过期,Runtime 应提交 retryable fail
|
|||
|
|
|
|||
|
|
### 4.5 Resolve Credential
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
POST /internal/runtime/model-credentials/resolve
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Runtime 获取模型调用凭证。platform_key 模式返回平台 key;user_deepseek_key 模式解密用户 key 后返回。
|
|||
|
|
|
|||
|
|
**请求**:
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"jobId": "job-abc123",
|
|||
|
|
"apiKeyMode": "user_deepseek_key",
|
|||
|
|
"credentialId": "cred-001",
|
|||
|
|
"provider": "deepseek"
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**响应 200**:
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"provider": "deepseek",
|
|||
|
|
"model": "deepseek-chat",
|
|||
|
|
"baseUrl": "https://api.deepseek.com/v1",
|
|||
|
|
"apiKey": "sk-xxxx",
|
|||
|
|
"apiKeyMode": "user_deepseek_key"
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**安全要求**:
|
|||
|
|
- 明文 `apiKey` 只在响应中短暂出现,不写日志
|
|||
|
|
- `apiKey` 不返回给 iOS / Admin
|
|||
|
|
- 用户 key 必须属于 `job.userId`
|
|||
|
|
- platform key 由 Runtime 环境变量优先使用,API 可选返回
|
|||
|
|
|
|||
|
|
**错误**:
|
|||
|
|
- `404 CREDENTIAL_NOT_FOUND`
|
|||
|
|
- `422 CREDENTIAL_INVALID`
|
|||
|
|
|
|||
|
|
### 4.6 Submit Result
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
POST /internal/runtime/jobs/{jobId}/result
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Runtime 提交执行成功的结果。
|
|||
|
|
|
|||
|
|
**请求**:
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"runtimeInstanceId": "runtime-001",
|
|||
|
|
"schemaVersion": "analysis_output_v1",
|
|||
|
|
"status": "succeeded",
|
|||
|
|
"rawOutput": { "learningState": "in_progress", "confidence": 0.85 },
|
|||
|
|
"validatedOutput": { "learningState": "in_progress", "riskLevel": "low" },
|
|||
|
|
"validationErrors": [],
|
|||
|
|
"usage": {
|
|||
|
|
"inputTokens": 1200,
|
|||
|
|
"outputTokens": 450,
|
|||
|
|
"totalTokens": 1650,
|
|||
|
|
"latencyMs": 3200,
|
|||
|
|
"costEstimate": 3
|
|||
|
|
},
|
|||
|
|
"attemptNo": 0,
|
|||
|
|
"outputHash": "sha256-abc123"
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**幂等规则**:
|
|||
|
|
- `resultIdempotencyKey = jobId + attemptNo + outputHash`
|
|||
|
|
- 相同 key 重复提交返回 200(幂等)
|
|||
|
|
- 已有 succeeded result 且 outputHash 不同返回 409 `RESULT_ALREADY_EXISTS`
|
|||
|
|
|
|||
|
|
**响应 201**:created
|
|||
|
|
|
|||
|
|
**错误**:
|
|||
|
|
- `409 RESULT_ALREADY_EXISTS`
|
|||
|
|
- `422 RESULT_SCHEMA_UNSUPPORTED`
|
|||
|
|
|
|||
|
|
### 4.7 Submit Failure
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
POST /internal/runtime/jobs/{jobId}/fail
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Runtime 提交执行失败的原因。
|
|||
|
|
|
|||
|
|
**请求**:
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"runtimeInstanceId": "runtime-001",
|
|||
|
|
"errorCode": "MODEL_TIMEOUT",
|
|||
|
|
"errorMessage": "DeepSeek request timed out after 30s",
|
|||
|
|
"retryable": true,
|
|||
|
|
"rawError": "connection timeout"
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**处理规则**:
|
|||
|
|
- `retryable=true` 且 `retryCount < maxRetryCount`:job 回到 `pending`
|
|||
|
|
- `retryable=false` 或达到 maxRetryCount:job 变为 `failed`
|
|||
|
|
- `rawError` 中不得包含 apiKey
|
|||
|
|
|
|||
|
|
**响应 200**:acknowledged
|
|||
|
|
|
|||
|
|
### 4.8 Submit Invocation Logs
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
POST /internal/runtime/invocation-logs
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Runtime 提交模型调用日志(批量)。
|
|||
|
|
|
|||
|
|
**请求**:
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"logs": [
|
|||
|
|
{
|
|||
|
|
"jobId": "job-abc123",
|
|||
|
|
"provider": "deepseek",
|
|||
|
|
"model": "deepseek-chat",
|
|||
|
|
"apiKeyMode": "user_deepseek_key",
|
|||
|
|
"credentialId": "cred-001",
|
|||
|
|
"promptName": "learning_state_analysis",
|
|||
|
|
"promptVersion": "learning_state_v1",
|
|||
|
|
"outputSchemaVersion": "analysis_output_v1",
|
|||
|
|
"inputTokens": 1200,
|
|||
|
|
"outputTokens": 450,
|
|||
|
|
"totalTokens": 1650,
|
|||
|
|
"latencyMs": 3200,
|
|||
|
|
"costEstimate": 3,
|
|||
|
|
"success": true,
|
|||
|
|
"retryCount": 0,
|
|||
|
|
"runtimeInstanceId": "runtime-001",
|
|||
|
|
"traceId": "trace-xyz",
|
|||
|
|
"correlationId": "corr-abc"
|
|||
|
|
}
|
|||
|
|
]
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**约束**:
|
|||
|
|
- 不允许 `apiKey` 字段
|
|||
|
|
- 失败调用也要提交日志
|
|||
|
|
- 日志提交失败不导致主任务崩溃
|
|||
|
|
|
|||
|
|
**响应 201**:created
|
|||
|
|
|
|||
|
|
### 4.9 Health(可选)
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
GET /internal/runtime/health
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
API 查询 Runtime 健康状态。此接口由 Runtime 暴露(非 API 暴露)。
|
|||
|
|
|
|||
|
|
**响应 200**:
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"runtimeInstanceId": "runtime-001",
|
|||
|
|
"status": "ok",
|
|||
|
|
"version": "0.1.0",
|
|||
|
|
"startedAt": 1700000000000,
|
|||
|
|
"lastJobAt": 1700000000123,
|
|||
|
|
"activeJobs": 2
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 5. 接口总览
|
|||
|
|
|
|||
|
|
| 方法 | 路径 | 调用方 | 鉴权 |
|
|||
|
|
|------|------|--------|------|
|
|||
|
|
| POST | `/internal/runtime/jobs/poll` | Runtime | InternalAuthGuard |
|
|||
|
|
| POST | `/internal/runtime/jobs/{jobId}/lock` | Runtime | InternalAuthGuard |
|
|||
|
|
| POST | `/internal/runtime/jobs/{jobId}/heartbeat` | Runtime | InternalAuthGuard |
|
|||
|
|
| GET | `/internal/runtime/jobs/{jobId}/snapshot` | Runtime | InternalAuthGuard |
|
|||
|
|
| POST | `/internal/runtime/model-credentials/resolve` | Runtime | InternalAuthGuard |
|
|||
|
|
| POST | `/internal/runtime/jobs/{jobId}/result` | Runtime | InternalAuthGuard |
|
|||
|
|
| POST | `/internal/runtime/jobs/{jobId}/fail` | Runtime | InternalAuthGuard |
|
|||
|
|
| POST | `/internal/runtime/invocation-logs` | Runtime | InternalAuthGuard |
|
|||
|
|
| GET | `/internal/runtime/health` | API | —(检查外部 Runtime) |
|
|||
|
|
|
|||
|
|
## 6. 验收清单
|
|||
|
|
|
|||
|
|
- [x] 所有 internal 接口有 DTO 定义(`runtime-internal.dto.ts`)
|
|||
|
|
- [x] 所有 internal 接口有鉴权设计(复用 InternalAuthGuard)
|
|||
|
|
- [x] 所有失败返回包含 errorCode / message
|
|||
|
|
- [x] Runtime result 支持结构化 payload(validatedOutput)
|
|||
|
|
- [x] Runtime failure 支持 retryable 标记
|
|||
|
|
- [x] Credential resolve 接口明确不记录明文 key
|
|||
|
|
- [x] 接口命名、字段命名与 Runtime 项目可直接对齐
|