Agent 前端交互:流式输出、工具状态、审批与恢复
# Agent 前端交互:流式输出、工具状态、审批与恢复
从前端视角设计 Agent 事件协议和状态机,让用户知道系统正在做什么、何时需要确认,以及失败后怎样继续。
# 先记住一句话
Agent UI 不是一个逐字输出框,而是任务状态的可视化:文本、工具、进度、审批、错误和最终结果都应通过明确事件呈现。
# 为什么普通聊天 UI 不够
Agent 可能运行几十秒、调用多个工具、暂停等待确认或后台继续。只显示转圈会让用户不知道是否卡住;只展示模型自然语言又可能把 “准备执行” 误解为 “已经执行”。
# 推荐事件协议
type AgentEvent =
| { type: 'task.started'; taskId: string }
| { type: 'message.delta'; text: string }
| { type: 'tool.started'; callId: string; name: string; summary: string }
| { type: 'tool.completed'; callId: string; resultSummary: string }
| { type: 'approval.required'; actionId: string; preview: unknown; expiresAt: string }
| { type: 'progress.updated'; completed: number; total?: number; label: string }
| { type: 'task.incomplete'; reason: string; resumable: boolean }
| { type: 'task.failed'; code: string; recoverable: boolean }
| { type: 'task.completed'; answer: string; citations: Citation[] }
2
3
4
5
6
7
8
9
10
事件有稳定 ID 和序号,前端重连后从最后收到的位置继续;服务端状态才是权威来源,前端不能靠已显示的气泡推断任务完成。
# SSE 还是 WebSocket
SSE(Server-Sent Events,服务器推送事件)通过一条持续的 HTTP 连接,让服务端单向向浏览器发送更新。它适合 Agent 持续返回文本和任务状态;如果双方都需要高频、实时地互相发送数据,再考虑 WebSocket。
| 方案 | 优势 | 适用 |
|---|---|---|
| SSE | HTTP 友好、自动重连、服务端单向流简单 | 大多数 Agent 输出与状态推送 |
| WebSocket | 真正双向、低延迟连续交互 | 实时语音、协作控制、高频双向事件 |
| 轮询 | 实现简单但延迟和请求多 | 低频后台任务兜底 |
用户提交消息、审批和取消仍可使用普通 HTTP;输出用 SSE,往往已经足够。
# 前端状态机
idle → submitting → running
├─ requires_action → running
├─ incomplete → running(继续)
├─ failed → retrying / ended
└─ completed
2
3
4
5
每个状态只允许有限操作。例如 requires_action 显示批准、修改和拒绝;running 显示取消;completed 不再接收旧工具事件。
# 工具和推理展示到什么程度
展示 “正在搜索 Agent 笔记”“找到 5 条结果”“等待保存薄弱点确认” 这类可验证进度即可。不要暴露隐藏思维链,也不要把原始数据库对象和敏感参数直接渲染。错误显示用户可行动的信息,详细堆栈留在受控 Trace。
# 审批界面
审批卡必须清楚展示:动作、目标、关键参数、影响范围、是否可撤销和过期时间。点击批准发送 actionId 和用户身份,服务端重新校验;按钮应防重复提交,但真正幂等仍在后端。
# 中断、重连和恢复
- 页面刷新后用 taskId 查询服务端状态;
- SSE 携带 lastEventId,避免重复或漏事件;
- 网络断开不等于取消任务;
- 用户主动取消要由后端设置取消标记,并让后端任务执行单元(Worker)在安全位置停止;
- 达到最大轮数显示已有结果、原因和 “继续” 入口;
- 恢复任务时显示此前完成步骤,而不是重新开始动画。
# 贯穿项目的页面
左侧是学习主题和会话,中间显示回答与来源,右侧显示可折叠任务轨迹。模拟面试时突出当前问题、倒计时和追问;工具轨迹默认折叠;保存薄弱点出现审批卡;最终给出掌握度、证据和下一步复习链接。
# 高频面试题与回答
回答顺序:先说结论,再解释原因,最后补一个例子或工程边界。不要逐字背诵,记住这条表达主线即可。
1. 2 分钟回答:你怎样设计 Agent 前端参考答案
我不会把 Agent 前端只当成聊天气泡,而会把它当成长任务状态机。后端通过服务器推送事件(SSE)发送文本、工具调用、进度、审批、失败和完成等事件,每个事件都带 taskId 和 eventId,前端按任务状态决定展示什么和允许什么操作。权威状态保存在服务端,页面刷新后通过 taskId 查询快照,再从最后一个事件继续订阅。审批卡只负责展示和收集决定,真正的权限、过期检查和幂等由后端完成。任务达到上限时展示已有结果和继续入口,而不是只报一个模糊错误。
2. 为什么不把每个 Token 都直接追加到同一个字符串?参考答案
因为 Agent 流里不只有文本,还有工具调用、进度、审批和错误等不同事件,全部拼成字符串会丢失结构。并且每个 Token 都触发一次界面更新会造成大量渲染。前端应该按事件类型更新状态,文本增量则按小批次刷新。
3. 页面刷新后怎样知道任务是否还在运行?参考答案
任务必须在后端独立运行并持久化状态,不能依赖当前页面或浏览器内存。刷新后,前端用 taskId 查询最新快照,再从保存的 eventId 重新订阅后续事件,就能恢复到正确界面。
4. 前端怎样防止重复审批?参考答案
前端提交后可以禁用按钮并忽略重复事件,但这只能防止普通误点。真正保证只执行一次的是服务端:审批要绑定 actionId 和参数哈希,并保存幂等记录。即使用户重复点击、刷新或网络重试,也不能重复执行写操作。
# 接下来学什么
下一篇学习 Agent 框架选型,重点比较不同方案的语言生态、编排能力、可观测性和迁移成本,并根据项目约束做选择。