文件与异步任务

上传、名单绑定、收卷预览、确认提交、试卷解析与任务轮询。

更新于 10月28日

改卷和试卷解析都从上传文件开始,以异步任务结束。验收时 Claude Code 与 Codex 各自经 CLI 和 Skill,按下面的流程改了一份答卷、解析了一份试卷。哪些操作已开放,以 API 参考为准。

1. 上传文件

用 files:write 和 Idempotency-Key 申请上传。工作区取自授权。

curl -X POST "$ONEAI_ORIGIN/api/public/v1/files/upload-intents" -H "Authorization: Bearer $ONEAI_TOKEN" \
  -H "Idempotency-Key: $(uuidgen)" -H "Content-Type: application/json" \
  -d '{"filename":"sheet.pdf","mime":"application/pdf","bytes":184213,"sha256":"<hex>","purpose":"grade-sheet"}'

响应含 file(带 id 与 state)和 upload:一个 PUT 地址及要带的请求头。地址 10 分钟内有效,只接受声明的字节数、类型与 SHA-256;上传时不带任何 OneAI 令牌。单个文件最多 8 MiB(8,388,608 字节),bytes 更大时这一步就被拒绝。grade-sheet 收 PDF 与图片,scheme-source 只收图片,ingestion-source 收全部支持的类型。然后调用 POST /files/{id}/complete(另带 Idempotency-Key)。OneAI 核对大小、类型、摘要与文件签名后,文件才变为 ready。字节还没到时返回 409 public.file.not_uploaded,可以重试;不一致时文件记为 failed 并给出稳定原因。没有 files:write 的令牌在这一切之前就被 403 external.scope.denied 拒绝,验收时实测过。

2. 预览收卷

每份答卷都绑定 GET /classes/{id}/members(需 roster:read)返回的名单 memberId,不按文件名猜学生。

{ "sheets": [ { "fileId": "<ready file id>", "memberId": "<roster member id>", "submissionKey": "s-01" } ], "mode": "append" }

提交到 POST /grade-sessions/{id}/submission-previews(需 grades:submit)。返回逐份冲突和 sessionRevision。没有冲突时给出 previewId,10 分钟内有效且只能用一次;有冲突时 previewId 为 null,不能提交。

3. 提交并跟踪任务

教师确认后,带 Idempotency-Key 向 POST /grade-sessions/{id}/submissions 提交 {"previewId": "…", "expectedRevision": "<sessionRevision>"}。返回 202 与 {"jobId": "…", "status": "queued", "requestId": "…"}。名单、细则或答卷变化后,要重新预览。轮询 GET /jobs/{jobId},支持 ETag 与 If-None-Match,并遵守 Retry-After。状态取 queued、running、succeeded、failed、partially_succeeded、needs_action、cancelling 或 cancelled;失败项给出 submissionKey 与稳定原因。只有发起任务的教师和工作区能看到它,其他人得到 404 public.not_found;验收时用一个不存在的 id 实测过。POST /jobs/{jobId}/cancel 只停止尚未开始的答卷;已开始的照常跑完并计费。CLI 把这些步骤封装为 oneai files upload、oneai grades submit --preview、oneai grades submit --preview-id … --yes 和 oneai jobs wait。

4. 解析试卷

每个文件用 ingestion-source 上传,再带 Idempotency-Key 把各份试卷提交到 POST /ingestions(需 ingestions:write):

{ "documents": [ { "fileIds": ["<ready file id>"] } ], "title": "2025 年一卷" }

每一项是一份试卷:一个 PDF 或 Word 文件单独一份,或者同一份试卷的照片按页序放在一起;合计最多 20 个文件。返回 202 与 jobId。解析按页扣积分,转换出页数后才计费。任务结束后,resultRefs 给出摄入批次。GET /ingestions/{id}(需 ingestions:read)返回状态(processing、complete、partial 或 failed)、各文件进度和按状态的候选计数。GET /ingestions/{id}/candidates 分页列出草稿题:正文、标签、flags、boundaryReview 与 revision。候选的状态有 ready、flagged(例如 referenceMismatch)、failed、published、discarded 等。

候选在教师发布前一直是草稿。修改(PATCH /ingestion-candidates/{id},需 ingestions:review)和发布(POST /ingestions/{id}/publish,需 ingestions:publish)都要写明教师看过的 revision。CLI 默认登录不申请这两个 scope,所以通常由教师在 OneAI 的题库 → 导入里校对并入库。页面图片和切题框也只在那里能看到。

一个文件只能用于一次解析或一份答卷;再用会得到 409 public.ingestion.file_unavailable 或 file.already_used 冲突。CLI 按文件名和内容派生上传键,同一个文件再传一次,拿到的是第一次那份已用过的上传。CLI 把解析封装为 oneai ingestions create、oneai jobs wait、oneai ingestions get 和 oneai ingestions candidates。

原卷与学生信息

原卷需要 originals:read,学生实名需要 students:pii。两者都不默认包含,并且仍受上级对象权限与学校政策约束。

文件与异步任务 · 开发者文档