type
Post
status
Published
date
May 26, 2026
slug
agent51
summary
tags
Agent
category
icon
password
核心观点:Agent 的流式输出不应该只理解为“模型边生成边展示 token”,而应该设计成一个可观察的 Agent 执行事件流。在包含工具调用、RAG 检索、多轮 LLM 交互、权限确认、失败重试的场景里,真正影响用户体验的不是“每个字快一点出来”,而是用户能否持续理解:Agent 现在在做什么、为什么暂停、下一步会发生什么、是否需要我介入、结果是否可信。
截至 2026 年 5 月,几个成熟或活跃的 Agent / LLM 工程方案都已经把“流式输出”从简单 token 级别扩展到了事件级别:OpenAI API 用 SSE 支持实时响应流,函数调用把模型连接到外部系统;Claude API 的 streaming 明确定义了
message_start、content_block_delta、message_stop 等事件,并在工具调用时以增量 JSON 形式流式输出工具参数;Claude Code Agent SDK 可以通过 include_partial_messages 接收原始流事件,工具调用可观察 content_block_start、input_json_delta、content_block_stop;OpenCode 的 opencode run --format json 支持 raw JSON events,且其 TUI/Server/SDK 架构天然适合多客户端订阅 Agent 状态;Vercel AI SDK 的 streamText().fullStream 明确包含 text、reasoning、tool-call、tool-call-delta、tool-result、start-step、finish-step 等事件;LangGraph 则把流分成 updates、messages、custom、checkpoints、tasks、debug 等不同模式。(OpenAI开发者)一、为什么普通 Token Streaming 不够?
传统 ChatBot 的流式输出很简单:
但 Agent 的真实执行流程通常是:
这会造成一个典型 UX 问题:用户看到一小段文字后,界面突然“沉默”几十秒。这段沉默可能发生在工具参数生成、权限等待、RAG 检索、代码执行、网页抓取、测试运行、二次 LLM 推理等阶段。Claude 官方文档也说明,当模型使用工具时,streaming 事件之间可能存在延迟,因为模型会先累积工具
input 的 key/value,再以 content_block_delta 形式输出部分 JSON。(Claude Platform)所以 Agent 流式输出的本质目标不是“永远打字”,而是:
把不可见的 Agent 执行过程,转换成用户可理解、可中断、可确认、可恢复的实时状态。
二、行业实践对比:成熟 Agent 都在流什么?
方案 | 流式输出设计重点 | 对 Agent UX 的启发 |
OpenAI API | 使用 SSE 做实时响应;Function Calling 让模型通过 JSON schema 调用外部系统 | 模型 token 流和工具调用流要统一成应用层事件协议 |
Claude API / Claude Code | content_block_start/delta/stop、input_json_delta、partial message streaming、fine-grained tool streaming | 工具参数可以增量生成,但应用层必须能处理 partial / invalid JSON |
OpenCode | CLI 支持 --format json raw JSON events;Server 暴露 OpenAPI;TUI 是 Server 的客户端 | Agent 不应绑定单一 UI,事件流应该支持 CLI、TUI、Web、IDE 多端消费 |
Vercel AI SDK | fullStream 包含 text、reasoning、tool-call、tool-call-delta、tool-result、start-step、finish-step | 最好把“步骤”和“工具”变成一等事件,而不是藏在模型文本里 |
LangGraph | 支持 updates、messages、custom、tasks、debug 等 stream mode | 复杂 Agent 需要区分“给用户看的流”和“给调试/观测看的流” |
OpenClaw | Local-first Gateway、多通道 inbox、Live Canvas、工具和事件控制平面 | Agent 输出不一定只是聊天气泡,也可以是 Canvas、任务面板、设备状态、多通道通知 |
OpenCode 文档显示,它的 Server 是一个 headless HTTP server,TUI 只是与 Server 通信的客户端,这种设计让 OpenCode 可以支持多个客户端并可通过 OpenAPI/SDK 编程化交互;它的
run 命令也支持 --format json 输出 raw JSON events。(OpenCode) OpenCode 还内置了 glob、grep、view、write、edit、bash、fetch、agent 等工具,并通过 permission 配置决定动作是 allow、ask 还是 deny。(GitHub)Claude Code 的 Agent SDK 默认在每次 Claude 完成一轮响应后才返回完整
AssistantMessage,若要获取文本和工具调用的增量更新,需要开启 include_partial_messages / includePartialMessages;工具调用流可以观察工具开始、参数片段到达、工具调用完成等事件。(Claude Code) Claude 的 fine-grained tool streaming 可以不等待服务端完整 JSON 缓冲与校验就开始流工具参数,但官方也提醒这种模式可能收到 invalid 或 partial JSON。(Claude Platform)三、设计目标:Agent Streaming 要解决 6 个体验问题
1. 降低首屏等待
用户提交请求后,最好在 300ms~1s 内看到状态变化。即使模型还没生成最终答案,也可以先展示:
正在理解任务正在检查是否需要检索知识库正在准备调用工具
这类状态不需要暴露模型隐藏推理,只需要展示可解释的执行阶段。
2. 消除“黑盒沉默”
Agent 调工具时,最糟糕的体验是界面没任何变化。更好的体验是展示工具卡片:
正在检索知识库:query = “Agent streaming design”已召回 24 条文档,正在 rerank正在读取src/session/stream.ts正在运行测试:npm test
3. 让用户知道什么时候需要介入
当工具需要权限时,流式输出应该停在一个明确的 permission event,而不是让模型继续胡乱解释:
需要确认:是否允许执行npm install?原因:需要安装缺失依赖以运行测试。操作:允许一次 / 本次会话允许 / 拒绝
Claude Code 文档明确区分 allow、ask、deny,且说明权限由 Claude Code 执行层 enforcing,而不是由模型 prompt 决定。(Claude Code) 这点非常关键:权限状态必须是运行时事件,不应该只是模型生成的自然语言。
4. 支持多轮 LLM + 多工具链路
一次 Agent Run 可能包含多个 Step:
前端不能只关心“最后一条 assistant 消息”,而要能展示整个执行轨迹。
5. 支持取消、恢复、重连
用户关闭页面、网络断开、工具执行很久、浏览器刷新,都不应该导致任务状态丢失。事件流要有
seq、run_id、step_id,并持久化到 Event Store,前端重连时从上次 seq 继续消费。6. 支持调试与审计
生产级 Agent 出问题时,不能只看最终答案。需要知道:
哪一步慢?哪个工具失败?哪次模型输出了非法工具参数?哪次权限被拒绝?RAG 召回了什么?哪个文档导致幻觉?
OpenAI Agents SDK 文档也提醒,streaming run 只有在 async iterator 结束后才算真正完成;最后一个可见 token 出来后,仍可能还有 session persistence、approval bookkeeping、history compaction 等后处理。(OpenAI)
四、推荐架构:三层流式输出模型
我建议把 Agent Streaming 拆成三层:
这套架构的关键是:不要让前端直接消费各模型厂商的原始 stream。
不同模型的事件结构差异很大,例如 Claude 有
content_block_delta / input_json_delta,Vercel AI SDK 有 tool-call-delta / tool-result,OpenCode CLI 有 raw JSON events。应用层应该做一层 Event Normalizer,把它们映射成自己的稳定协议。(Claude Platform)五、核心事件协议设计
一个生产可用的 Agent 事件至少应该包含这些字段:
其中最重要的是
visibility:public:可以展示给普通用户。debug:只给开发者、管理员、可观测平台。private:仅后端内部使用,绝不下发到前端。
这可以避免两个问题:第一,泄露模型隐藏推理;第二,把工具参数、密钥、内部路径、检索原文等敏感信息直接展示给用户。
六、Agent Run 状态机
复杂 Agent 的流式输出可以抽象成下面这个状态机:
注意,
ToolCallBuilding 和 ToolRunning 是两个完全不同的阶段:ToolCallBuilding:模型正在生成工具调用参数,工具还没有执行。ToolRunning:参数已经校验通过,权限也通过,工具正在真实执行。
这两个阶段在 UI 上要分开,否则用户会误以为“模型一说要执行 bash,bash 就已经执行了”。
七、后端实现:把模型流转换成 Agent 事件流
下面是一个简化版 TypeScript 伪代码,表达的是事件转换思想,而不是绑定某个具体 SDK:
这段代码体现了几个关键原则:
第一,模型原始事件不直接暴露给用户。第二,工具参数增量可以被 debug 流消费,但用户侧只展示安全摘要。第三,工具调用必须等参数完整、JSON 校验通过、权限通过后才能执行。第四,工具结果要同时服务两个对象:给用户看的 summary,给模型看的 observation。第五,Agent Run 的完成条件不是“最后一个 token 到达”,而是 orchestration loop 结束、事件持久化完成、最终状态写入完成。
八、前端展示:不要只做一个聊天气泡
一个好的 Agent UI 至少应该有三块区域:
1. 最终回答区
这里展示用户真正关心的答案,可以 token-by-token 输出。
适合展示:
分析结论代码解释RAG 汇总操作结果下一步建议
不适合展示:
原始 tool args原始 stdout 长日志模型隐藏推理未脱敏文件内容内部 prompt系统路径和密钥
2. 执行时间线
时间线用于消除等待焦虑。比如:
13:05:01 开始分析任务13:05:03 正在检索知识库13:05:05 召回 18 条文档,保留 Top 613:05:07 正在读取stream.ts13:05:10 正在运行单元测试13:05:18 测试失败,正在定位错误13:05:25 已修复并重新运行测试13:05:31 生成最终总结
这类展示不需要暴露 chain-of-thought,只需要展示外部可验证的执行动作。
3. 工具卡片
每次工具调用可以渲染成一张可折叠卡片:
工具:grep状态:完成输入摘要:搜索 “streamText”结果摘要:命中 4 个文件耗时:1.2s展开:查看 debug 详情
对于危险工具:
工具:bash命令:rm -rf dist状态:需要确认风险:删除构建目录操作:允许 / 拒绝
OpenCode 和 Claude Code 都有明确的工具权限模型,OpenCode 使用
allow / ask / deny,Claude Code 也区分 allow、ask、deny,并强调 deny 规则优先。(OpenCode)九、RAG 场景下的流式输出设计
RAG 的流式输出不能只在最终答案里给 citations。更好的方式是把检索过程本身拆成事件:
前端可以这样展示:
正在改写检索 query正在检索知识库:Agent streaming tool call UX已召回 42 条候选片段正在 rerank,保留 Top 8已准备 5 个引用来源正在基于引用生成答案
但要注意:
rag.document.found 不一定要把完整文档内容下发给前端。生产系统更推荐只展示标题、来源、时间、可信度、片段摘要,完整 chunk 留在后端,最终答案中只展示必要引用。RAG 流式事件建议设计为:
十、多轮 LLM 场景:Step 是一等公民
Vercel AI SDK 在 tool calling 文档里强调可以通过
steps 或 onStepFinish 访问中间工具调用和结果,onStepFinish 会在一个 step 的 text deltas、tool calls、tool results 都可用后触发。(AI SDK) 这说明生产级 Agent 不应该只有一个“assistant message”,而应该有多个 step。推荐的 Step 数据结构:
前端不要展示“模型正在思考第 3 步”,而应该展示用户能理解的动作:
正在确认依赖版本正在读取配置文件正在运行测试验证修改正在整理最终回答
十一、工具参数流:可以看见,但不能急着执行
工具调用 streaming 最大的坑是:参数还没完整,前端已经显示了,后端甚至错误地执行了。
Claude fine-grained tool streaming 明确提醒,跳过 JSON buffering / validation 后,可能收到 invalid 或 partial JSON,尤其在达到
max_tokens 等情况下,stream 可能在参数中途结束。(Claude Platform) 因此工程上要遵守:工具参数 delta 可以流式接收。工具参数摘要可以流式展示。工具执行必须等完整参数闭合、schema 校验通过、权限通过。
推荐处理流程:
如果参数 JSON 不合法,不应该直接报错给用户,而应该进入修复分支:
工具参数格式异常,正在自动修正修正失败,已停止该工具调用需要用户重新描述任务
十二、长耗时工具:必须有 Heartbeat 和 Partial Result
有些工具天然耗时:
网页抓取代码编译单元测试数据库查询多文件扫描大文档解析多轮 RAG浏览器自动化多 Agent 子任务
这时不能只等工具返回后发一个
tool.completed。应该设计:例如:
如果工具无法提供真实进度,也至少要有 heartbeat:
但 heartbeat 不能伪造进度。不要把“不知道进度”显示成 “80%”。
十三、并行工具调用:流式输出要保持有序
多工具并行时,事件可能交错:
因此事件协议必须有:
seq:全局递增顺序tool_call_id:归属哪个工具step_id:归属哪个 Agent steptimestamp:用于耗时分析parent_id:可选,用于子任务树
前端渲染时不要按到达时间盲目拼接文本,而要按
tool_call_id 归并到对应工具卡片里。十四、SSE、WebSocket、JSONL 怎么选?
1. Web 产品:优先 SSE + 控制接口
OpenAI streaming responses 使用 SSE。(OpenAI开发者) 对大多数 Web Chat / Agent UI 来说,SSE 足够简单稳定:
服务端单向推事件浏览器原生支持 EventSource方便通过 HTTP infra 部署易于做 JSON event stream
但 SSE 不适合复杂双向控制。权限确认、取消、用户补充信息可以走普通 HTTP POST:
2. IDE / TUI / 多端协同:WebSocket 更合适
如果需要实时双向交互,例如 IDE 插件、终端 TUI、远程设备、多用户协作,WebSocket 更自然。
OpenClaw 的 README 描述了本地优先 Gateway、多通道 inbox、Live Canvas、工具和事件控制平面,这类场景通常不是单向 token 流,而是多端状态同步。(GitHub)
3. CLI / 自动化:JSONL 最实用
CLI 场景下最好的格式是 NDJSON / JSONL:
十五、前端渲染策略:把事件映射成 UI 状态
前端可以用一个 reducer 消费事件:
关键点:最终回答文本和执行过程状态要分开存。 不要把所有东西都拼进一个 Markdown 字符串,否则会导致重连、折叠、权限确认、审计、局部刷新都很难做。
十六、安全设计:不要为了“透明”泄露敏感信息
Agent Streaming 越透明,越容易泄露信息。尤其是:
shell stdout文件内容数据库查询结果HTTP headerAPI key.env用户隐私数据prompt / system messageRAG 原文 chunk内部评分和 rerank 细节
推荐做三层脱敏:
1. 工具入参脱敏
2. 工具输出截断
3. 前端可见摘要
一句话:debug 流可以详细,public 流必须克制。
十七、错误与重试:错误也要流式展示
Agent 工具调用失败很常见。好的流式体验不是隐藏错误,而是展示可恢复状态:
事件可以设计为:
不要把底层 stack trace 直接给用户。用户需要知道:
发生了什么影响是什么系统是否在重试需要他做什么最终答案是否受影响
十八、Agent Streaming 的 10 条工程准则
1. 先定义应用层事件协议,再接模型 SDK。不要让前端绑定 Claude / OpenAI / Vercel / LangGraph 的原始事件结构。
2. Token stream 只负责答案,Agent event stream 负责过程。最终回答和执行轨迹分开建模。
3. 工具调用必须有 start / args / approval / running / result / error。不要只有“调用了工具”这一种状态。
4. 工具参数可以增量接收,但不能增量执行。必须等 JSON 完整、schema 校验、权限确认。
5. 所有事件都要有 seq。解决乱序、重连、回放、审计问题。
6. 公开流和调试流分离。public 给用户,debug 给开发者,private 仅内部。
7. 长耗时工具必须有 heartbeat。但 heartbeat 不能伪装成真实进度。
8. 权限是运行时控制,不是 prompt 约束。模型说“我会小心”没有任何安全意义,必须由执行层 enforce。
9. 最后一个 token 不等于任务完成。要等 orchestration loop、工具结果、持久化、最终状态都完成后再发run.completed。
10. 用户永远应该能取消。cancel要能中断模型流、工具执行、排队任务,并产生run.cancelled事件。
十九、可观测指标:如何判断流式体验是否真的变好了?
建议埋点这些指标:
指标 | 含义 |
TTFE | Time To First Event,用户提交后多久看到第一个事件 |
TTFT | Time To First Token,第一个答案 token 的时间 |
First Tool Time | 第一次工具调用开始时间 |
Silent Gap Max | UI 无任何事件更新的最长时间 |
Tool Duration P95 | 工具执行耗时 P95 |
Step Count | 单次任务平均 step 数 |
Approval Wait Time | 用户权限确认等待时间 |
Retry Count | 工具失败重试次数 |
Cancel Rate | 用户取消率 |
Resume Success Rate | 断线重连恢复成功率 |
Finalization Lag | 最后 token 到 run.completed 的时间差 |
尤其要关注
Silent Gap Max。很多 Agent 看起来“慢”,不是因为真的慢,而是因为用户不知道它在做什么。二十、最终推荐方案
如果我要从 0 设计一个生产级 Agent Streaming 系统,我会采用下面这套方案:
协议上:
Web 用 SSE 下发 public events。权限确认、取消、追加输入用 HTTP POST 或 WebSocket。CLI 用 JSONL。IDE / TUI / 多端协作用 WebSocket。所有事件进入 Event Store,支持 replay。所有模型供应商事件先归一化,再进入 UI。
UI 上:
最终答案继续 token streaming。工具调用用卡片。RAG 用检索进度和 citation ready。多轮 LLM 用 step timeline。危险动作进入 permission modal。长耗时任务用 heartbeat。Debug 信息折叠,不默认暴露。
工程上:
不信任 partial JSON。不信任模型自述权限。不把 hidden reasoning 展示给用户。不把工具 stdout 原样展示给普通用户。不让 final token 代表 run completed。不把 provider-specific stream 泄漏到前端协议。
结语
Agent 的用户体验不是“模型打字速度”的问题,而是“复杂自动化过程如何被用户理解和控制”的问题。
成熟 Agent 的流式输出,应该从:
升级为:
再进一步升级为:
当你的 Agent 能在检索、调用工具、等待权限、执行命令、失败重试、多轮总结的每一步都给出清晰、稳定、安全的反馈时,用户才会真正觉得它“可靠”。
分享
