流式AI对话界面里,用户点一下"取消"按钮,本地读取就停了。但很多人不知道,这个取消动作只断开了浏览器和服务器之间的连接。如果服务器已经向模型供应商发起了上游请求,那个请求可能还在继续跑——除非浏览器的AbortSignal能一路传到模型供应商那里。
这个缺口正是免费额度token悄悄流失的原因:用户关掉标签页,客户端fetch被中止,但供应商那边还在继续生成没人看的token。MonkeyCode的运营者描述了一个免费模型端点和免费服务器选项,服务器天然适合跑这个代理,但这个模式适用于任何流式HTTP运行时。运营者还提到一个3000万token的额度,这里仅视为未经核实的可用性说明,而非基准或保证。本文是MonkeyCode产品推广的一部分。
本地取消不等于真正停止
可以把本地取消想象成挂电话时只放下自己手里的听筒,但对方那根线还在通话。电话要等网络拆掉电路才算真正结束。浏览器fetch可以被中止,但中止只断开浏览器和后端之间的socket。如果后端已经向模型供应商单独开了一条连接,那个上游请求可能还在愉快地继续,因为没人告诉它要停。
这个问题在流式API上尤其明显——长响应本来就是随时间逐步生成的,而客户端随时可能消失:刷新页面、切换路由、标签页挂起,或者用户按了Escape键。
把取消变成一条网络路径
解决办法是让取消成为一条网络路径,而不是UI层面的约定。浏览器中止时,服务器应该检测到连接关闭,中止自己的上游fetch,停止转发剩余字节。结果不只是客户端状态更干净,还是一种预算控制机制——上游请求在客户端离开后不久就应该停止消耗token。
整个链路是这样的:浏览器通过AbortController发起POST /api/chat请求并开启流式传输,请求到达免费服务器代理(负责认证、转发和中止),代理再带着AbortSignal向上游发起POST请求,最终到达模型端点(SSE协议)。
核心代理代码只有几十行
这个核心代理刻意保持小巧。它接收来自自己客户端的JSON body,在服务器端注入供应商的认证头,转发请求,同时监听客户端socket关闭。关键细节是上游调用收到自己的AbortController信号——本地请求提前结束时,信号触发,上游fetch被取消。这个版本只用Node内置的HTTP和Fetch API,没有任何需要审计的第三方依赖。
实现方式很直接:用Node的http模块创建服务器,检查请求方法和路径,读取完整请求体,然后向上游转发。整个过程不引入额外依赖,部署在免费服务器上即可运行。
这个模式的价值在于:它把"取消"从一种UI习惯变成了协议层面的行为。用户离开页面,token消耗随之停止,免费额度不再无声流失。
热门跟贴