Modal 的免费额度很慷慨:注册就送每月 $30 算力,而且它的 Endpoints 功能一条命令就能把开源大模型架成推理服务,闲时自动缩到零。拿它白嫖一个 Kimi K3,条件近乎完美。
但真接进日常工具,总觉得哪里都差半口气:官方确实提供了 OpenAI 兼容接口,能直连,可用起来别别扭扭。与其凑合,不如自己造一个顺手的。于是决定用 Cloudflare Workers 做一层转发,把 Modal 端点包成一个干干净净的 API。
流水账记录一下。照例,这篇由 Fable 5 代笔,哈哈。
一条命令,Kimi K3 就有了
Modal Endpoints 支持 Qwen、Kimi、DeepSeek、GLM、GPT-OSS 等一大票开源模型,部署真的只要一条命令:
1 | modal endpoint create --model moonshotai/Kimi-K3 |
Modal 会自动解析模型、挑选推理引擎和 GPU 配方,然后给你一个专属端点,URL 长这样:
1 | https://<workspace>--ep-kimi-k3-server.us-west.modal.direct |
重点是计费方式:只按容器实际运行的算力收费,负载上来自动扩容,没人用就缩到零。对“每月 $30 额度、坚决白嫖”的个人用户来说,scale-to-zero 就是最重要的功能,没有之一。
官方直连:两个 token 拼一个 key
端点默认带鉴权,先创建一对 proxy token:
1 | modal workspace proxy-tokens create |
会得到一个 token ID(wk- 开头)和一个 secret(ws- 开头,只显示这一次,赶紧存好)。官方的用法很有意思:把两个 token 中间加一个点拼起来,当成一个普通的 API key 用:
1 | Authorization: Bearer wk-<id>.ws-<secret> |
而端点本身就提供 OpenAI Chat Completions 接口,路径在 /v1 下。所以理论上任何 OpenAI 兼容客户端都能直连:
1 | from openai import OpenAI |
官网还提供了一个统一入口 https://inference.us-west.modal.direct/v1,同一个拼接 key 也能用。
到这一步其实已经“能用”了,但真往日常工具里塞,就发现几个别扭的地方:
- 模型名诡异。尤其走统一入口时,模型名不是干净的
kimi-k3,而是一长串带端点信息的名字。不少客户端会拿模型名做展示甚至能力判断,要么难看,要么直接出岔子,搞不好还得再垫一层名字转换。 - 真凭证满天飞。
wk-/ws-这对 token 是 workspace 级的门票,每个工具里都贴一份,哪天想换 token,得挨个客户端改一遍。 - 单账号额度有限。$30 烧完或者端点被打挂,就只能干瞪眼。
我的方案:让 Cloudflare Worker 看大门
既然横竖要垫一层,干脆把这一层做成自己的“前台”:一个不到一百行的 Cloudflare Worker,对外只暴露我自己发的 API key,对内替我保管 Modal 凭证。
1 | 各种客户端 |
这里有个恰到好处的细节:Modal 除了 Authorization: Bearer 的拼接写法,还支持把 token 拆成两个独立的请求头传过去:
1 | Modal-Key: wk-... |
文档里说,这条通道就是为“Authorization 头要留给别的 token”的场景准备的。正好:Authorization 让出来放我自己的 key,Modal 凭证走专用头,两层鉴权互不打架。
主备账号和故障切换
Worker 里配了两个 Modal 账号(也就是两份每月 $30),但不做负载均衡:所有请求固定先走主账号,只有主账号自身出问题——凭证失效(401)、额度耗尽(402/403)、限流(429)、服务端错误(5xx)——才切到备用账号重试。至于 400 这类请求本身的错误,换个账号也一样失败,不折腾:
1 | function shouldFailOver(response) { |
一开始也想过随机分流,让两个账号平摊额度,后来想明白对这种场景是负优化,关键在缓存:
- 前缀缓存要集中。聊天和 agent 类请求会反复携带同一段长前缀——系统提示词、越滚越长的对话历史,推理引擎会缓存这部分计算结果,命中了就不用重算。随机分流等于把请求拆到两个互不相通的端点上,缓存命中率直接腰斩,而每一次未命中,都是实打实按 GPU 时间计费的重新预填充,又慢又贵。
- scale-to-zero 要成全。流量集中在主账号,备用账号的端点就能安安稳稳缩在零上,一分钱不烧;随机分流则是两边容器轮流被唤醒,闲置消耗直接翻倍。
所以现在的分工是:主账号用到底,备用账号纯待机,主账号限流或者抽风时才顶上,客户端全程无感。哪天主账号的 $30 真烧干了,多半也是以这几类账号级错误的面目出现,请求会自动落到备用账号头上——用到底,烧干自动换。
这里有一个小坑:请求的 body 是流,只能被读一次。第一次转发就把它消费掉了,等想故障切换时已经没有 body 可发了。所以要在首发之前 request.clone() 留个底,重试时用副本。另外失败响应的流记得 body?.cancel() 释放掉,不然它会一直占着连接。
部署
代码全部加起来九十行左右,部署就是标准 wrangler 流程,凭证全部塞进 secret:
1 | wrangler deploy |
之后所有工具里的配置就统一成了:
1 | base_url: https://kimi-proxy.<你的子域>.workers.dev/v1 |
上游换账号、换 token,甚至哪天把 Kimi K3 换成别的模型,客户端配置一个字都不用改。
算账环节
- Modal:两个账号,每月各 $30 免费额度,端点闲时缩到零,零消耗
- Cloudflare Workers:免费计划目前每天 10 万次请求,对个人使用是天文数字
- 合计:¥0