互动题库的协议设计:Schema、答案隔离与幂等提交¶
把一道题显示在网页上很简单;把它做成可重试、可提示、可语音播报、不会提前泄露答案的互动教学系统,就需要一份稳定协议。
woke_tutor 的 choice-interaction-v1 目前只支持单选和多选。我刻意冻结这个范围,用完整协议跑通“内容 → 判分 → 反馈 → 语音 → 会话记录”,再考虑扩展题型。
内部题目与公开题目不是同一个对象¶
服务端内部题目包含完整内容:
{
"content": {},
"presentation": {},
"response_spec": {},
"grading_spec": {},
"runtime_policy_id": "practice_v1",
"narration": {}
}
浏览器初次加载时只得到题干、去除 is_correct 的选项、展示方式和当前可播放片段 ID。以下字段不能发送:
grading_spec与正确选项;- 答案、解析和错误模式;
- 未解锁提示;
- 答案公布与解析的朗读文本;
- 可猜测的受限音频直链。
如果只是前端“暂时不显示”答案,用户仍然可以从网络响应或源代码中找到它。安全边界必须在服务器序列化之前建立。
Schema 负责形状,业务校验负责语义¶
以单选题为例,JSON Schema 可以限制选项数组、字段类型和最少数量,但“恰好一个正确选项”通常由业务校验表达更清楚:
correct = [option for option in options if option.is_correct]
if question.type == "single_choice" and len(correct) != 1:
raise ValidationError("单选题必须恰好有一个正确选项")
多选题则要求至少两个正确项。除此之外,还要检查选项 key 唯一、题干公式闭合、提示层级完整,以及每个错误选项是否有对应的错误路径。
这形成两层防线:Schema 阻止结构漂移,业务校验阻止结构合法但无法教学的内容进入发布包。
不在浏览器判分¶
浏览器提交:
{
"contract_version": "choice-interaction-v1",
"session_id": "session_001",
"qid": "q_practice_t00001",
"attempt_no": 1,
"selected_keys": ["B"],
"client_elapsed_ms": 8432
}
后端依次验证:会话是否存在、当前题是否匹配、尝试序号是否有效、选项 key 是否属于该题、单选数量是否正确。判分结果再由题库策略决定公开哪些内容。
practice_v1 可以即时返回对错和一层提示;assessment_v1 则返回 deferred,在会话结束前不公布答案。运行时不能解析自然语言提示来决定状态,只能依据冻结策略。
幂等是学习记录的一部分¶
用户双击提交、浏览器超时后重试、代理重复请求,都可能让同一次作答到达服务端多次。如果每次都新增记录,尝试次数和得分会被错误放大。
协议采用:
- 相同序号、相同选择:返回第一次结果;
- 相同序号、不同选择:返回
409 idempotency_conflict; - 下一个有效提交必须使用递增序号。
这比简单地在按钮点击后加 disabled 更可靠,因为网络层仍然可能重复,而服务端才掌握最终学习记录。
反馈状态机¶
选择题不只有“对 / 错”。多选题只选中部分正确项且没有错误项时,partial 可以触发不同反馈;诊断和测评场景需要 deferred。
stateDiagram-v2
READY --> NARRATING
NARRATING --> AWAITING_ANSWER
AWAITING_ANSWER --> EVALUATING
EVALUATING --> COMPLETE: correct / deferred
EVALUATING --> HINT_OR_RETRY: partial / incorrect
HINT_OR_RETRY --> AWAITING_ANSWER: retry
HINT_OR_RETRY --> COMPLETE: attempts exhausted
音频播放另有独立状态机。用户选择选项时可以继续播放,提交、切题或关闭声音时必须立即清空旧队列,避免上一题的讲解串到下一题。
受限音频也需要授权¶
答案文本没有发给浏览器,不代表答案音频天然安全。如果静态目录包含 answer_reveal.mp3,用户仍可猜 URL 访问。
项目使用 session_id + qid + segment_id 请求音频,服务端根据当前会话状态判断片段是否解锁。未解锁时返回 403。浏览器只知道当前可用片段 ID,不知道内部文件路径。
TTS API Key 同样只存在后端环境变量中,浏览器从不直连供应商。服务不可用时退化为文字,答题流程不受阻断。
协议版本何时升级¶
以下变化会改变消费者行为,需要新协议版本:
- 增加填空、口语或主观题;
- 改变用户答案结构;
- 改变
partial的判定语义; - 调整必需朗读片段或反馈解锁时机;
- 允许选项随机打乱;
- 增加用户语音回答。
更换音色、调整语速或增加内部日志字段则可以保持 V1。区分破坏性和兼容性变化,能避免生成器、后端和前端在不知情的情况下使用不同协议。
小结¶
一份可用的互动题库协议至少要同时定义:内容结构、公开结构、提交格式、判分语义、教学策略、幂等规则、语音授权和版本演进。只有这样,AI 生成的题目才不再是一段孤立 JSON,而是可以安全进入运行时的产品数据。