L05 将对话原型服务化:任务、审批、事件流
适用章节:M05–M06。本实验允许多种正确实现;验收不变量固定,不强制特定框架。
工程事故
用户点了两次提交,产生两个Run;等待批准的请求挂着HTTP连接半小时;刷新后浏览器重放事件,把同一条结果追加三次。你需要把任务寿命与一次连接的寿命分开。
最终交付
持久任务模型、审批记录、API接口实现或完整契约测试、SSE重连设计;一份Outbox故障说明。参考内核不提供完整HTTP服务器。
已提供材料
specs/API_CONTRACT.md、reference/agentlab/durability.py、reference/agentlab/business.py
先独立写出预期行为再看参考解。所有商店、订单、试次与价格单位都是教学构造;不连接真实资金系统。
接口与不可破坏的约束
遵循API_CONTRACT:创建任务返回202与run_id;请求幂等键绑定租户、用户和规范化请求体。模型生成的“确认”不能代替人类批准。审批绑定金额、对象版本、operation_id与有效期。
事件流按(run_id,seq)去重。连接中断不取消任务;取消是单独的经过授权操作。审批与任务终态都保存在服务端,而不是仅放localStorage。
实施步骤
- 实现Run表、Event表、Outbox表。在一个事务内创建Run+初始事件+待投递消息。
- 提供create/get/claim三个内核操作,验证相同请求幂等与不同请求冲突。
- 画出QUEUED、RUNNING、WAITING_APPROVAL等合法转移。等待状态不持有执行lease。
- 实现审批提案与确认入口;同时测试旧摘要、过期和已消费提案。
- 实现事件游标:重连从after_seq继续,客户端用主键去重;过旧游标返回resync_required并提供快照。
- 接上HTTP层时先用契约测试,再做一个最小Console。不要让UI直接调用数据库或持有Provider密钥。
验收矩阵
| 故障/动作 | 期望 |
|---|---|
| 同key同请求POST两次 | 同run_id,仅一条初始Outbox |
| 同key不同请求 | 409语义冲突 |
| 事务提交后未投递进程退出 | Outbox仍可恢复投递 |
| 投递成功但未mark_sent | 允许重复投递,消费者必须去重 |
| after_seq=1重连 | 只回seq>1;客户端仍处理重复网络帧 |
| 等待审批时claim | NOT_RUNNABLE |
| 其他租户读run/events | NOT_FOUND |
如何运行已有参考测试
在包根目录运行:
python -m unittest discover -s reference/tests -v
本实验主要参考测试类为DurabilityTests。已有测试默认验证reference/;测试通过不代表你的starter/实现通过。接入自己的实现时,将被测模块显式替换/注入,保持测试和领域契约不变,不要修改断言来迁就实现。
评分
事务/幂等25;审批25;事件与重连20;权限15;服务边界与测试15。
出现跨租户泄露、未经批准的退款或伪造结果时,不以其他分数抵消。允许记录未完成的选修功能,但必须说明范围。
进阶挑战
为等待状态添加经过认证的resume事务:校验当前version和审批→QUEUED→写新Outbox,三者原子提交。并发两次批准只能唤醒一次。
提交前自查
保存代码版本、运行命令、输入数据版本、输出日志、失败案例与设计决策。解释至少一个被你否决的方案。没有运行的测试标记“未运行”,不写“预计通过”。参考解见对应答案。