流式回答断连后,别让模型继续计费
文章指出,Next.js 流式接口的客户端断连后,上游模型请求仍可能继续生成,产生无人消费的 token 费用。示例利用自建 ReadableStream 的 cancel() 回调中止上游请求。
AI 深度解读
用户关掉聊天页面,模型却可能还在生成答案,账单也继续累积。作者指出,Next.js 流式接口能把 token 送进浏览器,只完成了链路的一半:浏览器断开后,还需要把取消操作传给上游模型。
这篇短文的价值在于提醒一个容易漏掉的生产细节:AI 接口除了能输出,还要能停止、能持续运行、能核算成本。不过,作者没有提供节省比例,也没有验证不同部署环境下的断连行为。
页面关了,为什么模型还在生成?
在 Next.js App Router 中,一次 AI 流式响应涉及两段连接:浏览器连接你的路由,路由再连接模型服务。
用户跳转页面或关闭标签页,首先断掉的是浏览器这一段。按作者描述,如果路由没有继续取消上游请求,模型就可能生成到答案结束。后续 token 无人阅读,却仍然产生费用。答案越长,用户越容易中途离开,这类浪费就越容易积累。
浏览器停止接收,不等于模型停止生成。
这里真正需要补齐的是取消传播:客户端不再需要结果时,服务端也要结束对应的上游工作。仅仅看到聊天窗口能逐字显示,还不足以判断这个接口已经适合上线。
用 cancel() 接住断连,再终止上游请求
作者自己构建 SSE(Server-Sent Events,服务端向浏览器持续推送事件)流,并通过 ReadableStream 的 cancel() 回调调用 upstream.abort()。
下面是原文的关键代码,用来把下游流的取消传递给 Anthropic 上游请求:
export function POST(req: Request) {
const upstream = anthropic.messages.stream({ model, max_tokens, messages });
const body = new ReadableStream({
async start(controller) {
for await (const event of upstream) controller.enqueue(encode(event));
controller.close();
},
cancel() {
// 浏览器已离开,停止生成和继续付费。
upstream.abort();
},
});
return new Response(body, {
headers: {
"content-type": "text/event-stream",
"cache-control": "no-cache, no-transform",
},
});
}
关键是 cancel() 里的 upstream.abort()。model、max_tokens、messages 控制上游生成请求,encode(event) 则负责把事件编码后写入响应流;这些变量和函数的定义,原文没有展开。
作者把客户端断连描述为触发 cancel() 的时机。但这段代码仍应放到实际部署环境中验证,不能仅凭本地演示就认定关闭标签页后一定能立即终止生成。原文也没有展示取消之后,异步迭代抛错等情况的处理代码。
不缓冲、不超时、算得清,才是完整链路
作者还提醒了三个配套问题。
首先是代理缓冲。某些代理会把 SSE 内容攒起来,最后一次性返回,用户看到的便不是持续输出。代码里的 Cache-Control: no-cache, no-transform 包含了要求中间层不要转换响应的提示。它值得保留,但原文没有给出具体代理配置,也没有证明这个响应头能解决所有缓冲情况。
其次是执行时长。长回答可能超过平台默认超时,需要调整路由的 maxDuration。原文没有提供默认值或建议值,实际设置要结合部署平台限制和业务允许的等待时间。
最后是成本计量。作者建议按请求记录 input、output 和 cache tokens,再按模型价格计算费用。否则,即使加入了取消逻辑,也很难知道实际省了多少。原文提到作者另有计量 schema,但没有在这里给出字段结构。
我的判断
我认同把取消传播和成本记录放进 AI 应用基础设施。前端能正常显示流式内容,只能证明数据送达;它没有回答用户离开后资源是否释放、长请求是否被截断、每次调用花了多少钱。
但「加一个方法就不再漏钱」这个结论需要收窄。取消不能抹去已经产生的 token 费用,实际节省还取决于断连何时被发现、上游何时停止。作者没有给出修复前后的费用对照,因此这里是工程经验和实现建议,还不是经过量化验证的收益报告。
如果项目已经使用封装好的流式 SDK,可以先检查它是否传递取消信号,再决定是否自行构建 ReadableStream。原文没有比较不同方案的完整性与维护成本。
文章末尾也在推广作者的 Shipwright:一个基于 Next.js 16 + Claude 的 starter kit,打包了按请求计费、typed tool use、带 eval harness 的版本化提示词,以及按套餐限制的配额。作者称其演示会显示每次请求的确切成本,但文中没有展开核算细节,不能据此判断计费精度。
能拿来做什么
- 检查现有 Next.js AI 聊天路由,确认关闭标签页或跳转页面后,取消操作是否真的传到了上游模型请求。
- 在实际部署环境测试 SSE,分别观察首批内容何时到达、代理是否缓冲,以及长回答是否触发超时,再调整响应头和
maxDuration。 - 为每次模型请求记录 input、output 和 cache tokens,并关联具体模型价格;评估取消效果时使用实际费用记录,不把前端少显示的字符数当成节省金额。