龍騰 AI 非選批閱平台 開發者文件中心
提交與批閱

提交與作答 Submission & Response

提交必須包含的作答範圍、冪等鍵、批次提交,以及提交的生命週期

提交(Submission)是一位學生對一份考試中,所有題目的作答(Response)的完整集合。一份提交中,必須包含該考試的所有應回答題目的對應作答,必須一次建立。提交與作答建立後不可修改也不可刪除,並且會在建立後立即排程,每筆作答同時進行非同步批閱。

一份提交的請求內容如下。responses 中每一筆對應考試裡的一道應回答題目,answer 的形態依題型而定(填充題為 fill_in,其餘為 text):

{
  "exam_id": "exam_kQXzTR",
  "student_ref": "student_0042",
  "roster_id": "ros_7hK2Qn",
  "custom_id": "midterm-0042",
  "responses": [
    {
      "question_ref": "q_mN4pXe:1",
      "answer": {
        "type": "fill_in",
        "entries": [
          {
            "blank_id": "mockBlank1leeu4igarik",
            "doc": {
              "type": "doc",
              "content": [
                { "type": "paragraph", "content": [{ "type": "text", "text": "類囊體膜" }] }
              ]
            }
          }
        ]
      }
    },
    {
      "question_ref": "q_7g2NkP:2",
      "answer": {
        "type": "text",
        "doc": {
          "type": "doc",
          "content": [
            { "type": "paragraph", "content": [{ "type": "text", "text": "光反應在類囊體膜產生 ATP 與 NADPH。" }] }
          ]
        }
      }
    }
  ]
}

建立提交的請求另需帶 Idempotency-Key 標頭,詳見冪等鍵

提交的內容

一份提交中,其 responses 欄位必須完整包含該考試中,所有應回答的題目。每道應回答題目均應存在恰好一筆作答,同一道應答題目不得存在重複的作答。

應回答題目將包含:

  • 一般題目:即題目本身。
  • 題組:應回答題目為其末端子題。因此建立提交時,其 question_ref 應指向該些末端子題的 ref(包含 _sub_ 區段的版本化參照,例如 q_sci_sub_mockSubQ1gk3vphrn2ecx_sub_mockSubQ2wd8j5tramx6f:1)。題組本身不是可作答對象,因此對其建立作答將會被視為無效參照。

應回答題目的數量,可查看讀取考試端點的 gradeable_count 參數。請注意 gradeable_countquestion_count 參數不同,後者計算的是最上層的題目總數,因此題組將僅計為一題,兩者用途不同。

學生未作答的題目,仍需要送出一筆空白作答,不可省略。其寫法請參考作答與題幹的內容格式

若送出了在考試中不存在的題目參照,或存在重複的參照,或遺漏應回答題目,都會使整個請求返回 400 ERR_VALIDATION_FAILED 錯誤,且回應的 details 會指出存在問題的參照。

學生識別

student_refroster_id 提供學生識別碼與學生名冊的參照。兩個欄位都是選填,且都不影響批閱。

student_ref 是客戶端系統中的學生識別碼,必要時將可引用此參照作為學生重複作答的查詢基礎。roster_id 指定一份學生名冊,可於未來提供 IDP 服務時,作為學生手寫姓名辨識名冊對照使用。

冪等鍵

使用 POST /submissions 建立提交時,必須在請求標頭中傳送自訂字串的 Idempotency-Key 冪等鍵,以確保重複的相同請求,得以被正確識別及去重。

冪等鍵的有效時間為 24 小時。在有效時間內,對於同鍵、同內容的請求,將重放相同的回應,不會建立第二筆提交;但若為同鍵、不同內容的請求,則會得到 409 ERR_DUPLICATE_TXID 錯誤。

ERR_DUPLICATE_TXID 錯誤不應視為請求成功。 若該請求意圖確實是新的操作,應產生新的冪等鍵;若該請求意圖是重送某個請求,則表示客戶端的兩次嘗試之間可能存在問題, 導致 payload 有所異動,請勿將 409 錯誤視為「已處理完成」而略過。

冪等鍵沒有格式的限制,任何能作為唯一性識別的字串皆可使用。但我們建議,比起使用常見的 UUID,使用結構化的字串可以更便於理解及除錯排查。例如:

sub-{student_ref}-{exam_id}-{yyyymmdd}

送出後的回應

正常的提交請求回應應為 201,且內容中會顯示狀態為 pending,並將內嵌所有收到的作答。例如:

請求回應範例

{
  "data": {
    "id": "sub_9Xw2Lp",
    "exam_id": "exam_kQXzTR",
    "student_ref": "student_0042",
    "custom_id": "midterm-0042",
    "source": "direct",
    "status": "pending",
    "response_count": 2,
    "responses": [
      {
        "id": "resp_2Fa8Qz",
        "question_ref": "q_mN4pXe:1",
        "status": "pending",
        "eta": 1,
        "…": "…"
      },
      {
        "id": "resp_5Kc3Yv",
        "question_ref": "q_7g2NkP:2",
        "status": "pending",
        "eta": 3,
        "…": "…"
      }
    ],
    "eta": 3,
    "created_at": "2026-03-02T09:30:00Z"
  }
}

回應中,eta 欄位為該份考卷提交(或該筆題目作答)的預估批閱剩餘分鐘數。該數值由當下系統負載、題目類型等資訊即時估算,且欄位只在批閱未完成時出現。該時間僅供估算參考,並非批閱完成的時間保證,也不盡然將會隨時間絕對遞減。

其中,整個考卷提交層級的 etadata.eta)為未完成作答中(data.responses[].eta)的最大值。

提交的生命週期與錯誤處理

Loading diagram...

一份提交,將在其下的每一筆作答都到達終態時(completedfailed)標記其狀態為 completed

個別題目作答批閱失敗時,考卷提交並不會標示為失敗。 舉例來說,若一筆作答批閱逾時,提交仍會 completed,但提交的 summary.failed 將為 1。因此判斷是否有作答批閱任務出錯時,應查看 summary.failed,而非提交的 status

有個別題目作答批閱失敗時,整份考卷提交的 summary.total_score 將為 null ,表示「總分尚不可用」,不應使用已經評分的部分作答先行加總計算。請於 Studio 將失敗的作答重新排程進行重試。

若整份考卷提交遇到整體性的失敗,例如批閱從未派送成功,或提交本身的紀錄無法解析出可批閱的內容時,其提交狀態將轉換為 failed。在此狀況下,請重新建立一份新的提交。

讀取批閱進度

請使用 GET /submissions/{id} 以讀取 responses 的個別進度或狀態。每筆個別題目作答的 response 均會各自帶有獨立的 statuseta,批閱完成者另帶最新的批閱結果 evaluation

當所有題目均達到終態,且整份提交的狀態為 completed 時,將整份提交讀取時另外會有 summary 的統計資料物件,以及 completed_at 的完成時間戳。

請注意 GET /submissions 為列表端點,回傳物件將僅有摘要,不含 responses

正式環境中,請訂閱 submission.completed Webhook 而非使用輪詢。 若確需輪詢,亦請採用指數退避,避免使用固定的短間隔。

且批閱時間依題型而異,例如申論題或作文所需時間將遠長於填充題。應參考 eta 提供的時間設定指數退避的週期。

批次提交

若需要大量建立提交時,可以使用 POST /submissions/batch 端點。

請求本體為一個 submissions 陣列,每一筆的內容與單筆建立相同,另各自帶有自己的 idempotency_key

{
  "submissions": [
    {
      "idempotency_key": "midterm-0042-v1",
      "exam_id": "exam_kQXzTR",
      "student_ref": "student_0042",
      "responses": [
        {
          "question_ref": "q_mN4pXe:1",
          "answer": {
            "type": "fill_in",
            "entries": [
              {
                "blank_id": "mockBlank1leeu4igarik",
                "doc": {
                  "type": "doc",
                  "content": [
                    { "type": "paragraph", "content": [{ "type": "text", "text": "類囊體膜" }] }
                  ]
                }
              }
            ]
          }
        },
        {
          "question_ref": "q_7g2NkP:2",
          "answer": {
            "type": "text",
            "doc": {
              "type": "doc",
              "content": [
                { "type": "paragraph", "content": [{ "type": "text", "text": "光反應在類囊體膜產生 ATP 與 NADPH。" }] }
              ]
            }
          }
        }
      ]
    },
    {
      "idempotency_key": "midterm-0043-v1",
      "exam_id": "exam_kQXzTR",
      "student_ref": "student_0043",
      "responses": ["…"]
    }
  ]
}

每則批次提交請求最多包含 100 筆提交,若超過將會得到 400 ERR_BATCH_TOO_LARGE 錯誤。

使用批次提交時,大部分的請求與回應均與單筆建立相同,但有兩處不同:

  1. 冪等鍵改置於個別提交中:批次提交的冪等鍵應置於每筆提交的 idempotency_key 欄位而非使用整個請求的 Idempotency-Key 標頭。當重送時,批次中每一筆提交的回放行為,則將與單筆建立時相同。
  2. 提交可以部分成功:就算批次中的提交可能有項目驗證或建立失敗,但批次請求本身仍將回應 200。驗證成功的提交項目將照常建立並排入批閱,因個別提交內容錯誤而導致拒絕的情況(例如:格式錯誤的作答、過長的作答文字、缺少應作答的題目)則將標示於請求回應中:

請求回應範例

{
  "data": {
    "batch_id": "batch_7Td3Mf",
    "total": 2,
    "succeeded": 1,
    "failed": 1,
    "results": [
      {
        "index": 0,
        "idempotency_key": "midterm-0042-v1",
        "status": "created",
        "submission_id": "sub_9Xw2Lp"
      },
      {
        "index": 1,
        "idempotency_key": "midterm-0043-v1",
        "status": "failed",
        "error": {
          "code": "ERR_VALIDATION_FAILED",
          "message": "responses do not cover every exam item: missing 'q_7g2NkP:2'"
        }
      }
    ]
  }
}

其中,results 維持請求的順序,index 為該項目在請求陣列中的位置。

在批次提交中,回應 200 不代表全部成功。請務必逐筆檢查 results[].status

整批遭到拒絕的情況僅可能有:

  1. 提交超過 100 筆
  2. 請求超過大小上限
  3. 請求結構本身無法逐筆處理(submissions 缺少或不是陣列,或某筆缺少 idempotency_key)。

可重試的批次迴圈

以下的程式範例,為一個簡單的自動重試函式,將批次建立提交,並自動重試直至全部成功建立(或重試次數用盡)。

async function submitBatch(items: SubmissionInput[]) {
  const pending = new Map(items.map((i) => [i.idempotency_key, i]));

  for (let attempt = 0; attempt < 3 && pending.size > 0; attempt++) {
    const res = await fetch(`${API}/submissions/batch`, {
      method: 'POST',
      headers: {
        Authorization: `Bearer ${KEY}`,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({ submissions: [...pending.values()] }),
    });
    const { data } = await res.json();

    for (const r of data.results) {
      if (r.status === 'created') {
        pending.delete(r.idempotency_key);
      } else if (!isRetryable(r.error.code)) {
        // 不可重試的錯誤:記錄下來交由人工處理
        report(r);
        pending.delete(r.idempotency_key);
      }
    }
    if (pending.size > 0) await sleep(2 ** attempt * 1000);
  }
  return [...pending.values()]; // 重試次數用盡仍未成功的項目
}

由於冪等鍵是逐筆的,整批重送對已成功的項目是安全的,它們會被重播而不會重複建立。

相關

本頁內容