人工覆核
產生新的批閱結果版本、樂觀鎖、覆核紀錄,以及覆核觸發的事件
當 AI 產生的批閱結果需要調整時,除了在客戶端逕行修正外,請對該批閱結果版本回傳一筆覆核(revise)記錄,產生新的批閱結果版本。
送出覆核
使用 POST /v1/responses/{id}/evaluation/revise 端點送出覆核。該端點動作需要 API Key 具有 evaluations:revise scope。
每次請求中都必填 expected_version 與 reason。其餘欄位依原有批閱結果的項目而定。
| 原有批閱結果帶有的欄位 | 覆核需送出的欄位 | 覆核不得送出的欄位 |
|---|---|---|
dimensions | dimensions(必填),只列出要修改的向度 | score、blanks |
blanks | blanks(必填),只列出要修改的格子 | score、dimensions |
criteria | score.earned(必填) | dimensions、blanks、criteria |
若該題目採用分析式評分(批閱結果存在 dimensions)或為填空題時,覆核僅應送出有修正的部分,且無須送出計算後更新的總分。未送出的部分將自動沿用前一版,並且由伺服器計算更新總分。
若題目採用整體式評分(批閱結果存在 criteria)時,覆核僅應送出 score.earned 的總分。若需要補充修正方向與理由,應於 reason 中說明。
覆核者不受評分標準的級距限制(但仍受上下界限制,詳見驗證規則)。
範例:分析式評分
{
"expected_version": 1,
"dimensions": [
{
"key": "內容正確性",
"score": { "earned": 4.5 },
"feedback": "替代論證可接受,酌予提高。"
}
],
"feedback": "學生以替代論證說明暗反應,內容大致正確。",
"reason": "學生以替代論證方式作答,原評分低估其正確性。"
}原本兩個向度為 3 + 4 = 7 分,覆核後回應的 score.earned 為 4.5 + 4 = 8.5,由伺服器算出。
範例:填充題
填充題的覆核不送總分。列出要修改的格子即可,其餘的格子由伺服器沿用前一版,總分則由各格加總得出。三格的題目中只改第二格時,請求就只有一筆:
{
"expected_version": 1,
"blanks": [
{
"key": "mockBlank2r7fnq2xoahd",
"score": { "earned": 1 },
"feedback": "「囊狀膜」為可接受的同義說法,予以部分給分。"
}
],
"reason": "accepted[] 未涵蓋此同義詞,原判定過嚴"
}回應的 score.earned 為 2 + 1 + 2 = 5,由伺服器算出。若請求另外帶了 score,會得到 400 而不是以其中一個為準。
範例:整體式評分
{
"expected_version": 1,
"score": { "earned": 18 },
"feedback": "取材切題,惟結構稍鬆散。",
"reason": "計分程式判定離題並封頂,惟本文確有扣題,原判定過嚴。"
}這種結果沒有構成總分的部分可以修正,因此覆核者直接指定總分。請求中不得帶 criteria。
樂觀鎖
送出覆核時,請求應帶有 expected_version 參數,標示該次修正所依據的版本(也就是客戶端讀取到的目前最新版本)。系統建立覆核版本時,將檢核該參數是否正確等於系統中存在的最新版本。
若自讀取到寫入覆核的期間內,有另一位覆核者已先寫入新版本,導致 expected_version 不等於寫入當下系統的最新版本時,該請求會回應 409 ERR_CONFLICT 錯誤,且不建立任何版本。
{
"error": {
"code": "ERR_CONFLICT",
"message": "Evaluation has been updated since the version you based on",
"details": { "expected": 1, "current": 2 },
"request_id": "req_01j9f3k2m1"
}
}在此狀況下,請勿逕行只修改 expected_version 為最新版本便直接重送,而使其他覆核者的修改丟失。
請重新讀取最新批閱結果,將修正重新套用,並再次確認修改內容,如下範例:
async function revise(responseId: string, patch: ReviseInput) {
const latest = await getLatestEvaluation(responseId);
const res = await post(`/responses/${responseId}/evaluation/revise`, {
...patch,
expected_version: latest.version,
});
if (res.status === 409) {
// 已有較新的版本落地:將最新結果送回 UI 由覆核者重新判斷,不自動重試
throw new StaleEvaluationError(await getLatestEvaluation(responseId));
}
return res;
}驗證規則
以下驗證規則將會在建立覆核版本時執行。違反將回應 400 ERR_VALIDATION_FAILED 錯誤。
reason為必填:請清楚描述覆核的理由、項目等說明資訊。該些描述將不可變的儲存於版本紀錄中。- 分數更新後的有效值域:
score.earned不得為負,也不得高於題目滿分;逐格的earned同樣必須落在該格的[0, max_score]之內。超出範圍會得到400 ERR_VALIDATION_FAILED錯誤。 - 分析式評分或填充題需進行至少一項修正:若完全未傳入修正項目,將回應錯誤。
回應
回應為 200,內容是新的批閱結果版本,並附帶 previous_score:
{
"data": {
"id": "eval_6Vq1Zr",
"response_id": "resp_5Kc3Yv",
"version": 2,
"method": "rubric_grading",
"score": { "earned": 8.5, "max": 10 },
"dimensions": [
{
"key": "內容正確性",
"score": { "earned": 4.5, "max": 6 },
"feedback": "替代論證可接受,酌予提高。"
},
{ "key": "完整性", "score": { "earned": 4, "max": 4 } }
],
"feedback": "學生以替代論證說明暗反應,內容大致正確。",
"reason": "學生以替代論證方式作答,原評分低估其正確性。",
"graded_by": "user_3Nb7Qh",
"graded_at": "2026-03-02T14:10:00Z",
"previous_score": { "earned": 7, "max": 10 }
}
}previous_score 只出現在覆核的回應中,供確認畫面使用。它不屬於儲存的批閱結果,後續讀取不會回傳。
Webhook 事件
客戶端可以訂閱 Webhook 的 response.evaluation_revised 事件,以在建立新的覆核版本時接收主動通知。
覆核清單
使用 GET /v1/submissions/{submission_id}/evaluation-revisions 端點以讀取一份題目作答的歷次覆核請求:
{
"data": [
{
"response_id": "resp_5Kc3Yv",
"question_ref": "q_7g2NkP:2",
"evaluation_id": "eval_6Vq1Zr",
"version": 2,
"score": { "earned": 8.5, "max": 10 },
"previous_score": { "earned": 7, "max": 10 },
"delta": 1.5,
"graded_by": "user_3Nb7Qh",
"reason": "學生以替代論證方式作答,原評分低估其正確性。",
"graded_at": "2026-03-02T14:10:00Z",
"method": "rubric_grading"
}
],
"pagination": { "has_more": false }
}delta 為 score.earned − previous_score.earned,覆核調降時為負值。evaluation_id 是該覆核版本自身的 id。
本端點回傳覆核請求及事件本身的記錄,與 GET /responses/{id}/evaluation/history 回傳歷次批閱結果版本(經套用覆核後的結果)不同。
相關
- API 參考 — 批閱結果
- 取得批閱結果
- Webhook 事件 —
response.evaluation_revised的 payload