05. Streaming、取消与 Events
本章说明 agent run 如何从“一次性返回结果”变成“运行中持续输出”。流式输出、取消和事件模型是后续真实 agent 体验的基础。
读完本章后,应该理解:
- streaming route 与普通 JSON route 的区别
AbortSignal为什么要传过 runtime 边界- agent event 与前端事件为什么要分开
- projection boundary 如何把内部事件转换成 UI 可消费事件
背景
同步 agent endpoint 很难使用。真实 agent 工作会持续一段时间,并有可观察阶段:
- prompt built
- model started
- assistant text streaming
- tool requested
- tool started
- tool finished
- run succeeded or failed
Agent run 需要在执行过程中持续暴露进度。
Streaming Route
项目加入:
app/api/agent/stream/route.ts这个 route 返回 Server-Sent Events。它仍然是 route boundary,不是 agent runtime。
后端发出内部 AgentEvent,route 把它投影成 frontend SSE events。
取消边界
取消被设计成真实 abort chain,而不是发给模型的一句 prompt instruction:
React AbortController
-> fetch signal
-> NextRequest.signal
-> AgentRunContext.signal
-> OpenAI SDK request option
-> stream chunk guard
-> tool runtime checks关键文件:
lib/agent-run-context.tsRuntime 会在关键 checkpoint 调用 assertAgentRunNotAborted(...)。
Agent Events
lib/agent-events.ts 引入内部 event model。
例子:
run_started
model_started
assistant_delta
tool_requested
tool_started
tool_finished
step_created
run_succeeded
run_failed后续阶段又加入:
model_requested
model_completed
tool_permission_decided
approval_requestedProjection Boundary
lib/agent-stream-projection.ts 把内部 events 映射成 frontend events。
这让浏览器不会变成 runtime。前端观察;服务端拥有模型调用和工具执行。
数据流
Git 证据
相关提交:
d5f8ad8 Stream agent progress and answer
edf8405 Add cancellable agent runtime boundaries
72bed76 Add agent harness event state
6fb0b86 Project agent events to stream responses取舍
Streaming 带来更多 event names,但保留了清晰方向:
runtime event -> projection -> frontend event这个决定让后来的 Debug Console 成为可能。
常见误解
误解一:流式就是把最终答案拆成小块
Agent 流式不只是答案分块。它还包括模型过程文本、工具开始、工具结束、错误、取消和最终提交等事件。
误解二:取消只需要前端停止显示
取消必须进入 runtime。否则前端虽然不显示了,后端仍可能继续调用模型或执行工具。
误解三:内部事件可以直接给 UI
内部事件通常包含 runtime 细节。投影层可以稳定前端协议,也为未来 telemetry 保留更完整的内部语义。
本章小结
这一章把 agent run 变成 live process:前端可以持续接收事件,runtime 可以响应取消,内部事件通过 projection boundary 转换成前端协议。
本章验证点
验证两件事:校验失败不会打开 SSE 流;run 的终态事件有确定性测试兜底。
- 空 body 打
/api/agent/stream(无需 key)。实测返回的是普通 JSON 400(content-type: application/json),不是 SSE error event——validation 在流打开之前就完成了:
curl -s -i -X POST http://localhost:3000/api/agent/stream \
-H 'Content-Type: application/json' -d '{}'HTTP/1.1 400 Bad Request
content-type: application/json
{"ok":false,"error":"Request body validation failed.","validationErrors":{"formErrors":[],"fieldErrors":{"task":["Field `task` is required."]}}}- 终态事件测试(无需 key,fake gateway):
npx tsx --test tests/agent-run-terminal-events.test.ts✔ an aborted run emits run_cancelled as its terminal event
✔ a failed run emits run_failed as its terminal event
ℹ pass 2