Flue useResponseStart/Finish 详解:拦截响应的每一刻
【免费下载链接】flueThe sandbox agent framework.项目地址: https://gitcode.com/GitHub_Trending/flue1/flue
在Flue这个轻量级 sandbox agent framework(沙箱智能体框架)中,useResponseStart和useResponseFinish是两枚埋点在响应生命周期两端的观测钩子:前者在响应的真正开始时精确触发一次,后者在响应的真正结束时精确触发一次。它们让你在毫秒级抓住 Agent 响应的每一次唤醒与收尾,把耗时、token 用量、应用标记等元数据直接"盖章"到响应消息上,供客户端在正文之外读取。
什么是响应生命周期钩子
Flue 的 Agent 是一个普通函数,函数体内通过一系列use*钩子声明能力:useModel选模型、useTool挂工具、useSkill挂载技能……而事件钩子则负责在生命周期的关键"接缝"上运行回调。
其中共有四个事件钩子,触发时机各不相同:
| 钩子 | 触发时机 | 可否异步 |
|---|---|---|
useResponseStart | 响应的真正开始,每个响应仅一次 | ❌ 同步 |
useAgentStart | 每收到一条投递消息,运行一次 | ✅ 可异步 |
useAgentFinish | 每个"本应停止"的点都会运行 | ✅ 可异步 |
useResponseFinish | 响应的真正结束,每个响应仅一次 | ❌ 同步 |
理解这对钩子的关键是"响应(response)"这个概念:一次响应可以吸收多条投递进来的消息(消息在轮次边界"加入"正在进行的响应)。此时useAgentStart会为每条消息重新触发,但响应本身只唤醒一次——useResponseStart和useResponseFinish只跟响应绑定,各触发一次,不多不少。
useResponseStart:在响应醒来的一瞬
它的执行时机极其靠前:在任何模型调用之前、在任何useAgentStart回调之前,同步地运行一次。
回调的签名很克制:接收一个上下文,返回一个普通对象或什么都不返回。
useResponseStart(({ metadata }) => ({ ...metadata, startedAt: Date.now() }));- 收到的
ctx.metadata是本响应目前已累积的元数据(按声明顺序、来自更先的钩子); - 返回的对象会被深度合并(deep-merge)到响应消息的
metadata字段上——这是 AI SDK 惯例中的信封字段,客户端可以在消息正文之外读取它; - 不返回任何东西则只观测、不附着,完全合法。
官方钩子实现就几行代码,可读性很高:use-response-start.ts,类型定义(ResponseStartContext等)在 message-output.ts。
useResponseFinish:在响应的真正结尾
useAgentFinish在每个"本应停止"的点运行,且可以追加信号让响应继续;而useResponseFinish运行在最后一轮收尾循环结束之后——此时响应真正落地,它拿到的response聚合数据是最终的:
useResponseFinish(({ metadata, response }) => ({ finishedAt: Date.now(), elapsed: Date.now() - (metadata.startedAt as number), totalTokens: response.usage.totalTokens, toolCalls: response.toolCalls.length, }));它的上下文比 start 侧多了一份response对象:
response.usage—— 整个响应跨所有轮次、所有重试的聚合 token 用量;response.toolCalls—— 该响应发起过的全部工具调用(来自持久化记录日志);ctx.metadata—— 包含useResponseStart附着的内容(从持久化日志读取,因此重试后依然保留)。
实现见 use-response-finish.ts。
典型用法:给响应盖上"计量章"
把两个钩子组合起来,是最经典的模式——记录响应耗时与用量:
useResponseStart(() => ({ startedAt: Date.now() })); useResponseFinish(({ metadata, response }) => ({ elapsed: Date.now() - (metadata.startedAt as number), totalTokens: response.usage.totalTokens, }));项目里就有现成的例子:评估场景中的服务状态 Agent 用useResponseFinish把用量与模型名写进元数据,供测试框架从回复上读取——见 service-status.ts 与 harness.ts。
在可观测性文档中,官方也推荐把useResponseFinish作为在响应元数据上盖 token 计量的正确位置:observability.md。
新手必知的 5 个行为细节 📌
- 同步且只观测:回调不能
append、不能dispatch、拿不到 harness,也不能是异步——返回 Promise 会直接令提交失败。需要异步的启动工作请放进useAgentStart,异步收尾工作请放进useAgentFinish。 - Fail-fast:回调抛异常会直接失败整个提交,没有重试、没有恢复。
- 元数据对模型不可见:写入的 metadata 永远不会进入模型提示词,也永远不会重新触发 Agent——纯粹是面向客户端的信封数据。
- 至少一次语义:钩子回调按 at-least-once 运行;中断后的重试会重跑,但持久化结果不会重复。
- 子 Agent 中不可用:在 subagent 渲染里调用这两个钩子会直接抛错——响应元数据只装饰面向客户端的公开对话。
延伸阅读
- 钩子总览指南(含事件钩子完整示例):agent-hooks.md
- API 参考(含
useResponseStart()/useResponseFinish()的完整契约与合并规则):agent-hooks-api.md - 运行时钩子源码目录:packages/runtime/src/hooks/
从"唤醒"到"落地",useResponseStart与useResponseFinish用两行声明就帮你在响应的每一刻留痕——这正是 Flue 响应式钩子体系优雅的地方:像写组件一样写 Agent,观测与计量信手拈来。
【免费下载链接】flueThe sandbox agent framework.项目地址: https://gitcode.com/GitHub_Trending/flue1/flue
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考