复现流式、取消与重试
实践目标
用确定性测试分别复现流式分段、取消后保留上下文和 Provider 重试,学会区分三种看似相似的“响应中断”。
前置知识
- 已理解 asyncio cancellation;
- 已读 Provider retry 和 Runtime Event;
- 当前实践不需要 DeepSeek key。
需要阅读的代码
nanobot/providers/base.py::chat_stream_with_retrynanobot/providers/base.py::_run_with_retrynanobot/agent/loop.py::_cancel_active_tasksnanobot/agent/loop.py::_dispatchnanobot/agent/runner.py::runnanobot/api/server.py::handle_chat_completionstests/providers/test_provider_retry.pytests/agent/test_stop_preserves_context.pytests/test_api_stream.py
先写下预测
为三种情况分别写:
| 情况 | 会不会重试 | 会不会保留部分文本 | 会不会保存 checkpoint |
|---|---|---|---|
| Provider 在首个 delta 前 429 | |||
| stream 已输出后普通错误 | |||
用户 /stop |
实践 A:流式重试边界
运行:
Set-Location "example\nanobot\code-EN"
python -m pytest tests\providers\test_provider_retry.py `
-k "does_not_retry_after_emitting_content or retries_timeout_in_new_stream_segment" `
-vv给这些位置打断点:
chat_stream_with_retry()设置has_streamed_content;_run_with_retry()的should_retry_guard;- 新 stream segment 分支。
预期观察
- 已输出内容后,普通失败默认不能无痕重试;
- stream stall timeout 是明确处理的恢复例外;
- 若入口支持 segment,会先结束旧 segment,再开始新 segment;
- retry policy 既依赖错误类型,也依赖已经发生的外部可见输出。
实践 B:取消后上下文
运行:
python -m pytest tests\agent\test_stop_preserves_context.py -vv观察:
- active task 如何被 cancel;
CancelledError在哪一层捕获;- runtime checkpoint 如何恢复;
- pending user turn 如何清理;
- Session 保存了哪些中间消息。
预期观察
取消不是抹掉整个 Turn。已经完成的 tool/assistant 中间状态会尽力保留,以便下一轮理解发生过什么。
实践 C:API SSE
运行:
python -m pytest tests\test_api_stream.py -vv观察:
process_direct()接收的on_stream/on_stream_end;- delta 如何变成 SSE chunk;
- final response 和
[DONE]; - exception/timeout 如何关闭流。
一张诊断表
| 现象 | 第一观察点 | 不要先做 |
|---|---|---|
| 一个字都没输出就等待 | Provider retry metadata、on_retry_wait | 重启整个 Gateway |
| 输出一半后重头开始 | stream id/segment、stall timeout | 假设是前端重复渲染 |
/stop 后下轮仍看到工具结果 | checkpoint restore | 认定 Session 损坏 |
| SSE 已断但后台仍运行 | request cancellation 是否传到 task | 直接删除 Session |
完成条件
- 三组测试均能运行或明确记录环境阻塞;
- 能分别画出 retry、cancel、SSE 三条调用链;
- 修正实践前预测表;
- 至少指出一个“外部可见后不可安全重放”的原因。
验证方法
保持 code-EN 只读,用 IDE 单步或 pytest --pdb 观察现有 fake response 顺序和断言。若确实要改变响应顺序,把最小 fake case 复制到 $nanobotLab 或本学习库的示例区域再实验;不要编辑源仓库测试,也不要为了让实验通过修改生产 retry policy。
复盘问题
- 为什么 stream stall 可以作为普通错误后的特殊恢复例外?
CancelledError为什么通常不应被改成普通错误字符串?- SSE 连接断开是否一定意味着 Provider task 已取消?
- checkpoint 保存和外部副作用回滚有什么本质区别?