When we built AI grading, one question took a long time to settle: does a score produced by the AI actually count? If it counts, who answers for it when it is wrong? If it does not, what exactly is the AI for?
The conclusion we landed on: the AI writes drafts, and the teacher makes the call. And this boundary is not written into product documentation — it is written into the data model.
Drafts go into extension columns, business state stays untouched
The learning-record table has explicit business-state columns (submitted / reviewed, and so on) and a score column. AI output never goes into those columns; it goes into attributes, a JSONB extension column.
01// 草稿挂在扩展属性上,业务状态列不动02record.attributes["ai_feedback"] = {03 "score": 82,04 "comment": "第二问的推导跳了一步,建议补上边界条件…",05 "model": "glm-4",06 "generated_at": "2026-06-25T10:12:03Z",07 "draft": true08}0910// 终审仍走教师既有的 feedback 接口11POST /tv1/teacher/homeworks/{hid}/feedbackWhole-class grading must tolerate partial failure
A class has dozens of submissions; the whole batch must not roll back because the seventh one timed out. So the batch endpoint processes work in chunks: a single failure does not interrupt the rest of the batch, errors are reported per item, and the next run retries automatically — records that already have a draft are skipped, so tokens are never burned twice.
- Only records in the submitted state are processed; final-reviewed ones never get a new draft
- Records that already have a draft are skipped automatically, avoiding duplicate model calls
- A single failure reports an error for that item only, leaving the rest of the batch untouched
- Work is processed in chunks with a cap of 100 per batch, so one request cannot overwhelm the model service
Graceful degradation when unconfigured
When no LLM endpoint is configured, the AI grading endpoint returns 501 and every other feature is completely unaffected. This design lets customers who want to try AI and customers who want nothing to do with it run the same build.
We push the parts AI speeds up as far as they will go — but the question of who answers for the result is not something we hand to the model.