Alchemy v2.0.0-beta.55 实战解析:AI Gateway 账户级花费上限与 Fetcher 桥接工具
【免费下载链接】t3code项目地址: https://gitcode.com/GitHub_Trending/t3/t3code
本指南基于仓库内 beta.55 发布说明 展开。v2.0.0-beta.55 为 Alchemy 的 Cloudflare 集成带来两个关键能力:
Cloudflare.AiGatewaySpendingLimit资源,为账户内所有 AI Gateway 设置声明式的美元硬性花费上限,防止失控循环演变成失控账单;以及从alchemy/Cloudflare/Bridge入口重新导出的 Fetcher/Socket 互操作工具,让 TanStack Start、Astro 等框架 Worker 能够把普通 service binding 包装成完全类型化的 EffectHttpClient。读完本文,你将掌握花费上限的完整配置、一次性引导充值与自动充值机制,以及四种框架侧调用 Effect 后端的方式。
为什么 AI Gateway 需要花费上限
AI Gateway 位于所有模型调用之前,意味着它也同时位于"失控循环"与"失控账单"之间。一旦某个重试循环或异常调用风暴发生,账单会以肉眼可见的速度增长。beta.55 把这个护栏放进了你的基础设施声明中:Cloudflare.AiGatewaySpendingLimit为账户内所有gateway 的累计 AI Gateway 花费设置硬性美元上限,并且像其他任何 Alchemy 资源一样被声明与调和(reconcile)——不是部署后的手动配置,而是与你的栈一起版本化、可审计、可复现的声明式资源。
从源码结构看,它与 Gateway 资源、BYOK 网关供应商配置 同属alchemy/Cloudflare命名空间下的账户级 AI Gateway 资源族,CHANGELOG 确认其在 beta.55 中随 PR #569(作者 Matthew Aylward 与 Sam Goodwin)加入。
Cloudflare.AiGatewaySpendingLimit:声明式账户级上限
基本声明与单位约定
金额单位是cents(美分)——这是 Cloudflare 的原生计费单位,最小值为1_00(即 $1.00)。花费在一个daily、weekly或monthly的窗口内累计,窗口策略支持fixed(在窗口边界重置)或sliding(滚动窗口):
import * as Cloudflare from "alchemy/Cloudflare"; // Cloudflare 要求必须先手动充值一次信用额度才能设置花费上限。 // `topUp` 调和了这一要求:provider 观察计费状态, // 仅当账户从未充值过时,才会向账户的默认支付方式发起一次性扣款。 const cap = yield* Cloudflare.AiGatewaySpendingLimit("ai-spend-cap", { amount: 250_00, // cents -> $250.00(最小 1_00 = $1.00) duration: "monthly", topUp: { amount: 10_00 }, // cents -> $10.00(Cloudflare 最小充值额) });| 配置项 | 取值 | 说明 |
|---|---|---|
amount | 整数,单位 cents,最小1_00($1.00) | 账户级 AI Gateway 花费的硬性上限 |
duration | daily/weekly/monthly | 花费累计的时间窗口 |
| 窗口策略 | fixed(窗口边界重置)/sliding(滚动窗口) | 决定窗口如何滚动,见发布说明描述 |
topUp.amount | 整数,单位 cents(Cloudflare 最小充值额 $10.00) | 一次性引导充值的金额 |
topUp.threshold | 整数,单位 cents | 触发自动充值的余额阈值 |
topUp.autoRecharge | 布尔,默认true | 关闭后仅做一次性引导充值 |
topUp与 Cloudflare 的引导要求
topUp属性处理的是 Cloudflare 的"引导(bootstrap)"要求:一个账户在通过至少一次手动充值加载 Unified Billing 信用额度之前,无法设置花费上限(对应错误码NO_MANUAL_TOPUP)。
Alchemy 的 provider 会观察实时计费 API 上的first_topup_success字段:
- 仅当账户从未充值过时,才执行那一次性扣款;
- 一旦账户完成引导,
topUp属性即变为惰性(inert),永远不会再次扣款。
如果支付需要交互式确认(如 3-D Secure 验证),资源会以类型化的AiGatewaySpendingLimitTopupRequired错误失败,此时必须到 Cloudflare 控制台完成充值。
自动充值(默认开启)
自动充值默认开启——当信用余额低于threshold时,Cloudflare 自动按amount充值:
const cap = yield* Cloudflare.AiGatewaySpendingLimit("ai-spend-cap", { amount: 250_00, duration: "monthly", topUp: { amount: 20_00, threshold: 10_00 }, // 余额低于 $10 时自动充值 $20 });如果需要仅一次性引导、不再自动充值,则显式关闭:
const cap = yield* Cloudflare.AiGatewaySpendingLimit("ai-spend-cap", { amount: 250_00, duration: "monthly", topUp: { amount: 10_00, autoRecharge: false }, });一个重要的使用注意点
一个值得反复强调的约束:Cloudflare 在每个账户只存储一个花费上限,因此该资源是一个"每账户单例(per-account singleton)"——在栈中恰好声明一个即可,声明多个会破坏该约束。这与同账户下可以声明多个 AI Gateway 资源(每个 gateway 拥有自己的cacheTtl、collectLogs等配置)形成对比:gateway 可以有多条,但账户级上限永远只有一条。
Fetcher 工具:把 service binding 变成类型化HttpClient
从alchemy/Cloudflare/Bridge导出
Fetcher/Socket 互操作辅助函数——fromCloudflareFetcher、toCloudflareFetcher、toHttpClient、fromCloudflareSocket——现已从alchemy/Cloudflare/Bridge入口导出(PR #581)。同时,fromCloudflareFetcher与fromCloudflareSocket现在可以直接接受全局的Fetcher/Socket类型,而不再局限于@cloudflare/workers-types的命名空间类型。
仓库中的 Bridge.ts 是这一入口的实现:
export * from "./Fetcher.ts"; export * from "./Workers/InferEnv.ts"; export * from "./Workers/Rpc.ts"; export * from "./Workers/RpcAsync.ts";四个函数的具体定位(依据 Fetcher.ts 源码):
| 函数 | 方向 | 作用 |
|---|---|---|
fromCloudflareFetcher | CloudflareFetcher→ AlchemyFetcher | 接受cf.Fetcher \| globalThis.Fetcher,包装为支持 EffectHttpClientRequest/HttpServerRequest的适配器 |
toCloudflareFetcher | AlchemyFetcher→ CloudflareFetcher | 反向适配,把 Effect 侧的 fetch 暴露成 workerd 可调用的cf.Fetcher |
toHttpClient | AlchemyFetcher→ EffectHttpClient | 把暴露 server 形态fetch的对象(DO stub、service binding)转换为 EffectHttpClient |
fromCloudflareSocket | CloudflareSocket→ EffectSocket | 把全局/workers-types 的 Socket 归一化为 EffectSocket.Socket |
框架 Worker 中的典型场景
这一组合在框架 Worker(TanStack Start、Astro 等)中尤其重要:你的 Effect 后端以普通 service binding的形式出现在env上,而不是以 Effect 生态的类型出现。过去你可能需要各种 cast,现在只需要两步调用即可把它转成完全类型化的 EffectHttpClient——零 cast:
import * as Cloudflare from "alchemy/Cloudflare/Bridge"; import * as Effect from "effect/Effect"; import * as HttpServerResponse from "effect/unstable/http/HttpServerResponse"; const client = Cloudflare.toHttpClient( Cloudflare.fromCloudflareFetcher(env.BACKEND), ); const res = await client .get(`https://backend/?key=${encodeURIComponent(key)}`) .pipe(Effect.runPromise); return HttpServerResponse.toWeb( HttpServerResponse.fromClientResponse(res), );请求走的是账户内 service binding,而不是公共网络——这意味着更低延迟、无公网暴露,并且请求会经过 Cloudflare 的边界网络基础设施。
一个 gateway 对象,四种调用方式对照
仓库中的cloudflare-website-tanstack-start示例的/api/hello路由 现在用四种方式并排提供同一个对象,每种都有对应的集成测试覆盖:
- 原始 R2 binding 直调:
env.BUCKET.get(key)/env.BUCKET.put(...)——完全绕过 Effect,直接操作异步 binding; - service binding 的
fetch:env.BACKEND.fetch("https://backend/?key=...")——workerd 原生形态,无类型保障; - 类型化 RPC:
Cloudflare.toRpcAsync<Backend>(env.BACKEND)之后调用backend.hello(key)——把 wire-shape binding 包装为 Promise 视图,Effect.fail时抛错、自动解包 stream 信封,且hello为只读 RPC(PUT 不支持via=rpc); HttpClient形态:Cloudflare.toHttpClient(Cloudflare.fromCloudflareFetcher(env.BACKEND))——完整获得 EffectHttpClient的请求构建、重试、错误处理能力。
四种方式的 GET/PUT 差异见下表:
| 方式 | GET | PUT |
|---|---|---|
binding | env.BUCKET.get(key) | env.BUCKET.put(key, body, { httpMetadata }) |
fetch | env.BACKEND.fetch(url) | env.BACKEND.fetch(url, { method: "PUT", body, headers }) |
rpc | backend.hello(key)(只读 RPC) | 不支持(400) |
http-client | client.get(url) | client.execute(HttpClientRequest.fromWeb(request))(204 时不带 body 响应) |
源码级原理:HandlerNotReady重试机制
fromCloudflareFetcher内部有一段值得注意的健壮性设计(见 Fetcher.ts):新部署的 Durable Object / service script 是最终一致的——部署后短时间内,workerd 可能把.fetch()路由到仍没有 fetch handler 的旧脚本版本,表现为"Handler does not export a fetch() function"错误,通常数秒内随新版本传播而消失。
Alchemy 的处理方式:
- 每次尝试前
request.clone(),保持原请求未被消费,使重试可安全重放; - 将底层 promise 的拒绝提升为类型化的
HandlerNotReady错误,按Schedule.exponential("100 millis")指数退避重试最多 8 次; - 因为请求从未到达 handler,无副作用已提交,重试是安全的;
- 预算耗尽后,重新抛出原始 defect;
toHttpClient层面再把任何残存的HandlerNotReady(及一切失败 cause)转换为类型化的TransportError(HttpClientError),而不是再次重试。
由于该窗口适配逻辑位于所有 binding 流经的底层适配器上,RPC(toRpcAsync)、HttpClient、client/server 重载等所有上层包装都自动获得了这一韧性,且不影响 RPC/stream 解码(响应及其流式 body 保持原样)。
本版本其他修复
- 修复了阻止
alchemy deploy/alchemy destroy退出的悬挂进程:本地RpcProvider服务层(providerServices/providerServicesEffect)现在只在AlchemyContext.dev被设置时才构造,因此一次性 CLI 运行不再启动本地 dev provider 机制(PR #580)。从 LocalRuntime.ts 可以看到RpcProvider.providerServicesEffect在本地运行时路径中的使用位置。
扩展阅读
- 完整的上线流程:添加 AI Gateway 指南——声明
Cloudflare.AI.Gateway资源、绑定进 Worker、构建LanguageModelLayer、实现/generate与/stream路由,以及缓存/限流/DLP 调优; - 前端框架集成——TanStack Start、Astro、Next.js 等框架 Worker 与 Effect 后端的组合方式;
- 本版本完整变更见仓库根目录 CHANGELOG.md。
【免费下载链接】t3code项目地址: https://gitcode.com/GitHub_Trending/t3/t3code
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考