我在本机跑了一套 gpt-load——一个 Go 单二进制 + SQLite 的自托管 AI 网关,把 Kimi、DeepSeek、本地 Ollama 统一收到一个入口,再给 Claude Code、Dify、各 agent 发各自的 AccessKey。这篇手记把从零到能用的全过程记下来, 包括四个不踩就卡死的坑。
开源项目地址:github.com/tbphp/gpt-load (6.9k stars,自托管 AI 网关:多渠道多凭据统一接入,含密钥与订阅账号、调度容错、日志与用量)。 配套部署脚本在 github.com/Devkid-Til/gpt-load-deploy-skill, 照着脚本就能跑。也可以参考前面那篇《从零配置 Claude Code 全家桶》, 让 Claude Code 加载这个仓库里的 skill(gpt-load-setup),回答几个问题就自动完成安装和分组配置。
🛠️ 总览:七步
/ section完整链路是七步:① 起 docker 服务 → ② 登录管理 UI → ③ 建分组(group)→ ④ 挂模型(models)→ ⑤ 发 AccessKey → ⑥ 客户端接入 → ⑦ 验证。gpt-load 的核心概念就三个: 分组(channel,对应一个上游)、模型(挂在分组下,可设别名)、AccessKey(发给客户端的令牌)。 每一步都有对应脚本,见下方各节。
1. 起 docker 服务
/ column脚本:bash setup.sh [DATA_DIR] [PORT](源码)—— 含 data 目录属主修正、compose 生成、健康检查。
# 目录结构
mkdir -p /ws/dev/services/gpt-load && cd /ws/dev/services/gpt-load
# 三个文件:docker-compose.yml、data/(SQLite)、credentials.txt # docker-compose.yml
services:
gpt-load:
image: ghcr.io/tbphp/gpt-load:2
container_name: gpt-load
ports:
- "3001:3001"
environment:
HOST: 0.0.0.0
PORT: 3001
DATA_DIR: /app/data
restart: always
volumes:
- ./data:/app/data docker compose up -d
docker logs gpt-load --tail 5 # 确认起来了 管理密钥是首次启动自动生成的——gpt-load 会把它写到 ${DATA_DIR}/auth.key (容器内 /app/data/auth.key),同时生成一把 encryption.key 用于加密存储上游凭据。 取出来就能登录 http://localhost:3001:
docker exec gpt-load cat /app/data/auth.key 这把密钥是唯一副本,丢了要重置,务必存到一个 600 权限的文件里、别进任何版本库。 想自己指定密钥的话,在启动前设 AUTH_KEY 环境变量覆盖即可(那样就不生成 auth.key)。
2. 建分组(group)
/ column脚本:bash create-group.sh <NAME> <CHANNEL_ID> <BASE_URL>(源码)—— 含 Idempotency-Key 头(坑二)和 price_multiplier 必填(坑三)。分组就是「一个上游渠道」,管理 API 认证头是 Authorization: Bearer <AUTH_KEY>(不是 X-Auth-Key)。 下面是我建的三个分组的真实参数:
{
"name": "kimi-code",
"channel_id": "anthropic",
"connection_type": "api_key",
"params": {"base_url": "https://api.kimi.com/coding"},
"price_multiplier": "1",
"confirm_same_target": true
} deepseek 分组的 base_url 填 https://api.deepseek.com(不带 /anthropic)—— SDK 按协议自己拼 /anthropic/v1/messages,带尾巴会拼两遍报 404。本地 Ollama 是 openai_compatible 渠道,base_url 指向宿主机的 11500。
3. 挂模型 + 设别名
/ column脚本:bash add-models.sh <GROUP_ID> '<JSON>'(源码)—— 先读现有清单、确认后再全量替换(坑五)。模型支持别名:id 是上游真名,alias 是客户端看到的名字, alias_enabled: true 才生效。比如 Kimi 分组里 k3 的别名是 k3[1m], 客户端用 k3[1m] 调,实际走的是上游的 k3。
PUT /api/groups/1/models
{
"models": [
{"id": "k3", "alias": "k3[1m]", "alias_enabled": true},
{"id": "k3-256k", "alias": "", "alias_enabled": false}
]
} ★ ⚠️ 改模型清单是全量替换
4. 发 AccessKey
/ column每个客户端发一把独立的 AccessKey,按使用方逐个分发(jiaqi / dify / 各 agent),形如 sk-gl-<名>-<hex>。 好处是某把 key 出问题可以单独吊销,不波及其他客户端。AccessKey 可以过滤协议、模型、IP——比如 Dify 只放行 embedding 模型,agent 只放行它该用的那个分组。
5. 客户端接入
/ column客户端把 ANTHROPIC_BASE_URL 指向 http://localhost:3001,ANTHROPIC_API_KEY 填发的那把 AccessKey。Dify 容器内用宿主机 IP(host.docker.internal 在容器里不解析)。
# Claude Code / 各 agent
export ANTHROPIC_BASE_URL=http://localhost:3001
export ANTHROPIC_API_KEY=sk-gl-<你的>-<hex>
# Dify(容器内)
# base 指向 http://<宿主机IP>:3001/v1 6. 验证
/ column# 管理 API 验证分组状态
curl -s -H "Authorization: Bearer <AUTH_KEY>" http://localhost:3001/api/groups
# 返回里每个分组的 service_status 应为 available 客户端侧跑一次真实调用,确认 alias 生效。比如用 k3[1m] 调 Kimi 分组,返回正常就说明 别名重写生效了。
7. 四个坑(都验证过)
/ column★ 坑一:data 目录属主必须是 10001
★ 坑二:建分组必须带 Idempotency-Key 头
★ 坑三:建分组必填 price_multiplier 和 confirm_same_target
★ 坑四:deepseek 的 base_url 不带 /anthropic
一个 Go 单二进制 + SQLite,把三个上游收进一个入口,再给每个客户端发独立 key。装的时候卡在哪一步,回来翻这篇。
站长手记 · 教程