前言
火山引擎方舟平台对每个模型每天提供 50 万 tokens 的免费额度,而且平台上有大量免费模型可用------不只是豆包系列,还有 DeepSeek、GLM 等第三方模型。配置的模型越多,每天能白嫖的额度就越多。
但问题来了:
- 手动切换模型太麻烦
- 用着用着某个模型额度就没了,请求直接报错
- 本地用 Codex、Cursor 这些工具时,没法动态切换模型
- 模型那么多,谁记得住哪个用完了哪个没用完?
于是我写了 VolcProxy,一个 OpenAI 兼容的代理服务,自动在多个免费模型之间轮换,额度用完无感切换,把免费 tokens 吃干抹净。
项目已开源:github.com/talkcozy/vo...
火山引擎免费模型(部分)
以下是我常用的几个最新模型,实际平台上还有更多免费模型可以配置:
| 模型 | 每日免费额度 | 特点 |
|---|---|---|
| doubao-seed-2-0-code-preview-260215 | 50万 | 编程专用,代码生成质量高 |
| doubao-seed-2-0-pro-260215 | 50万 | 综合能力强 |
| doubao-seed-2-0-mini-260215 | 50万 | 轻量快速 |
| doubao-seed-2-0-lite-260215 | 50万 | 轻量版 |
| doubao-seed-1-8-251228 | 50万 | 上一代主力模型 |
| deepseek-v3-2-251201 | 50万 | DeepSeek V3 |
| glm-4-7-251222 | 50万 | 智谱 GLM-4 |
光这 7 个就有 350 万 tokens/天,实际可配置的远不止这些。你可以在模型列表中查看所有支持免费额度的模型,加到配置里就能自动轮换。
这些模型都支持 OpenAI 兼容的 /v1/chat/completions 接口,鉴权方式也很简单,Bearer Token 即可。
设计思路
核心需求就三个:
- 透明代理:对外暴露 OpenAI 兼容接口,客户端无需关心后面用的是哪个模型
- 自动轮换:按优先级选模型,额度用完自动切下一个
- 用量追踪:实时统计每个模型的 token 消耗,提供 Web 面板查看
架构很简单:
scss
Codex / Cursor / aider / 任意 OpenAI 兼容客户端
↓
VolcProxy (:9088)
┌─────────┼─────────┐
│ │ │
模型选择器 额度管理器 Web面板(:9089)
│ │
└────┬────┘
↓
火山引擎 API
核心实现
项目用 Go 写,单二进制部署,无外部依赖(SQLite 用的纯 Go 实现)。
模型选择器
最核心的逻辑其实很简单------遍历优先级列表,找到第一个没用完额度的模型:
go
func (s *Selector) Select(ctx context.Context) (string, *apikey.Key, error) {
key, err := s.keyPool.Get()
if err != nil {
return "", nil, err
}
for _, m := range s.models {
if !s.quotaMgr.IsExhausted(m) {
return m, key, nil
}
}
return "", nil, errors.New("all models exhausted for today")
}
调用失败时自动跳到下一个模型重试:
go
func (s *Selector) SelectNext(ctx context.Context, failedModel string) (string, *apikey.Key, error) {
// 从失败模型的下一个开始找
// 找不到就 wrap around 从头找
// 全部用完返回错误
}
额度追踪
火山引擎的 API 响应里自带 usage 字段,每次请求都会返回本次消耗的 token 数:
json
{
"usage": {
"prompt_tokens": 100,
"completion_tokens": 200,
"total_tokens": 300
}
}
我们只需要从响应里提取这个字段,累加到本地计数器就行。流式请求需要设置 stream_options.include_usage = true,代理会自动注入这个参数:
go
if isStream {
so, _ := reqMap["stream_options"].(map[string]interface{})
if so == nil {
so = make(map[string]interface{})
}
so["include_usage"] = true
reqMap["stream_options"] = so
}
计数器持久化到 SQLite,服务重启不丢失。每天 0 点(Asia/Shanghai)自动重置。
失败自动切换
不只是额度用完才切换。如果火山引擎返回了 429、quota exceeded 等错误,代理会立即标记该模型为已满,然后重试下一个模型:
go
func isQuotaError(errStr string) bool {
lower := strings.ToLower(errStr)
return strings.Contains(lower, "quota") ||
strings.Contains(lower, "rate limit") ||
strings.Contains(lower, "429") ||
strings.Contains(lower, "insufficient") ||
strings.Contains(lower, "exceeded")
}
这样即使本地计数不准(比如有其他地方也在用同一个 API Key),也能正确处理。
流式转发
流式响应是 SSE 格式,代理逐行读取上游响应,原样转发给客户端:
go
scanner := bufio.NewScanner(resp.Body)
for scanner.Scan() {
line := scanner.Text()
fmt.Fprintf(w, "%s\n", line)
if line == "" {
flusher.Flush() // 每个 chunk 结束时 flush
}
}
最后一个 chunk 里提取 usage 信息用于计数。
使用方式
最简单:直接当 OpenAI 代理
bash
# 构建
make build
# 配置(复制模板,填入你的火山引擎 API Key)
cp config.example.yaml config.yaml
# 启动
./bin/volcproxy start
然后设置环境变量:
bash
export OPENAI_BASE_URL=http://localhost:9088/v1
export OPENAI_API_KEY=sk-any # 随便填,代理不校验
Codex、aider、Cursor 等工具直接就能用了。
进阶:对接 New API,让 Claude Code 也能用
Claude Code 用的是 Anthropic 格式,不能直接连 OpenAI 兼容接口。但如果你有 New API,可以把 VolcProxy 作为一个渠道接入:
css
Claude Code → New API (Anthropic→OpenAI 格式转换) → VolcProxy → 火山引擎
在 New API 后台添加渠道:
| 字段 | 值 |
|---|---|
| 类型 | OpenAI |
| Base URL | http://<volcproxy地址>:9088 |
| 密钥 | sk-any |
| 模型映射 | claude-sonnet-4-20250514 → doubao-seed-2-0-code-preview-260215 |
这样 Claude Code 请求 claude-sonnet-4-20250514 时,实际走的是火山引擎的免费模型。
Web 管理面板
启动后访问 http://localhost:9089,可以实时查看:
- 每个模型的用量和剩余额度
- 进度条直观展示(绿色→黄色→红色)
- API Key 管理
- 最近 7 天使用趋势
服务器部署
Go 的好处就是交叉编译一把梭:
bash
# 编译 Linux 版本
GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -o bin/volcproxy-linux ./cmd/volcproxy
# 上传
scp bin/volcproxy-linux your-server:~/volcproxy/volcproxy
# 启动
ssh your-server "~/volcproxy/manage.sh start"
项目自带 manage.sh 管理脚本,支持 start/stop/restart/status/log。
用 crontab 设置开机自启:
bash
@reboot cd ~/volcproxy && nohup ./volcproxy start > volcproxy.log 2>&1 &
一些细节
为什么不用 CGO?
最初用的 go-sqlite3(CGO),交叉编译很麻烦。后来换成了 modernc.org/sqlite(纯 Go 实现),CGO_ENABLED=0 直接编译出静态二进制,扔到任何 Linux 机器上就能跑。
为什么不直接调管控面 API 查用量?
我研究过火山引擎的管控面 API(GetUsage),确实能查到历史用量。但它是按 Endpoint 维度聚合的,而我们是通过模型名直接调用,维度对不上。而且有几分钟延迟,不适合做实时额度判断。
最终方案是从每次响应的 usage 字段实时累计,最准确也最简单。
额度缓冲
每个模型 50 万 tokens 的限额,我没有设任何缓冲。原因是:即使本地计数到了 50 万,实际可能还没用完(因为火山引擎的计费可能有细微差异)。真正用完时火山引擎会返回错误,代理检测到后立即切换,不会丢请求。
项目结构
bash
volcproxy/
├── cmd/volcproxy/main.go # CLI 入口
├── internal/
│ ├── config/config.go # 配置管理
│ ├── proxy/handler.go # 代理处理器
│ ├── model/
│ │ ├── selector.go # 模型选择器
│ │ └── client.go # 火山引擎 API 客户端
│ ├── quota/manager.go # 额度追踪
│ ├── apikey/pool.go # API Key 池
│ └── web/ # Web 管理面板
├── pkg/db/sqlite.go # SQLite 封装
├── config.example.yaml # 配置模板
├── manage.sh # 管理脚本
└── Makefile
总共不到 2000 行 Go 代码,没有任何花哨的框架,标准库 net/http 搞定一切。
总结
| 特性 | 说明 |
|---|---|
| 每日免费额度 | 数百万 tokens(取决于配置的模型数量) |
| 接口兼容 | OpenAI /v1/chat/completions |
| 模型轮换 | 按优先级自动切换,失败自动重试 |
| 额度追踪 | 实时统计,SQLite 持久化 |
| 管理面板 | Web UI,10 秒自动刷新 |
| 部署方式 | 单二进制,零依赖 |
如果你也在用火山引擎的免费额度,或者想给 Codex/Cursor 接一个免费的模型后端,可以试试这个项目。
GitHub:github.com/talkcozy/vo...
欢迎 Star 和 PR。