调 AI 接口不需要 new-api:Caddy 换个 header 就够了
Agent 只拿一个网关 token,真实 key 只存在中转机上。 2026-10-01 在国内机上接好了 DeepSeek 和 AutoDL,2026-10-02 在海外机上接好了 CodeProxy(图片生成)和 OpenRouter(网络搜索、音频转录、音乐生成、Jev 结构化判断,2026-10-08 又用它接了微软 MAI-Voice 语音合成,2026-10-10 接了微软、OpenAI、Mercury 三家结构化判断,中转都没改),2026-10-10 国内机又接了下载狗,2026-10-17 接了 TikHub,2026-10-18 DeepSeek 开放的搜索、抓取接口也走原来的 DeepSeek 中转(中转没改),其余厂商还没接。Agent 端用法见调用 DeepSeek、解析分享链接、网络搜索、读取网页、生成语音、生成视频、生成、编辑图片、转录音频、生成音乐和结构化判断。
决策
- 只做透明转发:验 token,换
Authorization,其余原样转发。Agent 照官方 SDK 用,只换 base URL 和 key。 - 例外是下载狗:它的 token 在 JSON 正文里,中转用
request_body把调用方的GET /?url=<链接>改写成带 token 的 POST。仍然只用 Caddy,不加别的服务。 - 用 Caddy,版本必须 ≥ 2.11,
caddy version确认:SSE、WebSocket、长连接默认就能用,证书自动签。不用 nginx,因为这些都要手配;不用 new-api,可以但没必要。 - 国内机接国内厂商,海外机接 OpenAI、Anthropic、Gemini。一家厂商一个子域名、一个
.caddy文件。DeepSeek 是https://deepseek-proxy.liuhetian.work,AutoDL 是https://autodl-proxy.liuhetian.work,下载狗是https://xiazaitool-proxy.liuhetian.work,TikHub 是https://tikhub-proxy.liuhetian.work(海外服务,但抓的是抖音、小红书,也放国内机,国内机连它 0.7 秒);海外机上的 CodeProxy 是https://codeproxy.reverse-pro.xyz,OpenRouter 是https://openrouter.reverse-pro.xyz。 - token 由用户自己定,各家用同一个。Agent 端写在
.env的MY_API_KEY里。 - 日志只留时间和状态码,请求头、URL、IP、聊天内容都不记。
- 要签名或者令牌会过期的厂商(即梦、Bedrock、腾讯云 TC3、可灵、Vertex),Caddy 做不了,改用这家有 Bearer key 的 OpenAI 兼容接口。
- 配置全放
/etc/caddy,唯一的例外是 systemd drop-in:
| 文件 | 内容 |
|---|---|
/etc/caddy/Caddyfile | 原有的静态站,末尾 import /etc/caddy/sites-enabled/*.caddy |
/etc/caddy/sites-enabled/deepseek.caddy、autodl.caddy、xiazaitool.caddy、tikhub.caddy | 一家一个:验 token、换 key、转发、过滤日志 |
/etc/caddy/gateway.env | 每家两行 <厂商>_API_KEY、<厂商>_GATEWAY_TOKEN,root 所有,0600 |
/etc/systemd/system/caddy.service.d/gateway.conf | 读 env 文件,去掉 --environ |
assets/gateway/deepseek.caddy
deepseek-proxy.liuhetian.work {
@authorized header Authorization "Bearer {$DEEPSEEK_GATEWAY_TOKEN}"
log_append vendor deepseek
handle @authorized {
log_append caller personal
log_append authenticated true
reverse_proxy https://api.deepseek.com {
header_up Authorization "Bearer {$DEEPSEEK_API_KEY}"
header_up -Proxy-Authorization
}
}
handle {
log_append caller unauthenticated
log_append authenticated false
header Content-Type application/json
header WWW-Authenticate Bearer
respond `{"error":{"message":"A valid gateway token is required.","type":"authentication_error"}}` 401
}
log deepseek_proxy {
output file /var/log/caddy/deepseek-proxy/access.json {
roll_size 10MiB
roll_keep 30
roll_keep_for 2160h
}
format filter {
wrap json
fields {
request delete
resp_headers delete
duration delete
size delete
bytes_read delete
user_id delete
}
}
}
}
assets/gateway/autodl.caddy
autodl-proxy.liuhetian.work {
@authorized header Authorization "Bearer {$AUTODL_GATEWAY_TOKEN}"
log_append vendor autodl
handle @authorized {
log_append caller personal
log_append authenticated true
reverse_proxy https://www.autodl.art {
header_up Authorization "{$AUTODL_API_KEY}"
header_up -Proxy-Authorization
}
}
handle {
log_append caller unauthenticated
log_append authenticated false
header Content-Type application/json
header WWW-Authenticate Bearer
respond `{"error":{"message":"A valid gateway token is required.","type":"authentication_error"}}` 401
}
log autodl_proxy {
output file /var/log/caddy/autodl-proxy/access.json {
roll_size 10MiB
roll_keep 30
roll_keep_for 2160h
}
format filter {
wrap json
fields {
request delete
resp_headers delete
duration delete
size delete
bytes_read delete
user_id delete
}
}
}
}
assets/gateway/xiazaitool.caddy
xiazaitool-proxy.liuhetian.work {
@authorized header Authorization "Bearer {$XIAZAITOOL_GATEWAY_TOKEN}"
log_append vendor xiazaitool
handle @authorized {
log_append caller personal
log_append authenticated true
# 下载狗的 token 在 JSON 正文里,不在请求头:调用方 GET /?url=<链接>,这里改写成上游要的 POST JSON
# 开头写 \\{ 不是笔误:import 进来的文件会多吃一层转义,只写 \{ 时 `{"url":"{…}` 整段被当成未知占位符替换为空,
# 上游收不到 url,只回一句「网络不稳定」
request_body {
set `\\{"url":"{http.request.uri.query.url}","token":"{$XIAZAITOOL_API_KEY}"}`
}
reverse_proxy https://api.xiazaitool.com {
method POST
rewrite /api/parseVideoUrl
header_up Content-Type application/json
header_up -Authorization
header_up -Proxy-Authorization
}
}
handle {
log_append caller unauthenticated
log_append authenticated false
header Content-Type application/json
header WWW-Authenticate Bearer
respond `{"error":{"message":"A valid gateway token is required.","type":"authentication_error"}}` 401
}
log xiazaitool_proxy {
output file /var/log/caddy/xiazaitool-proxy/access.json {
roll_size 10MiB
roll_keep 30
roll_keep_for 2160h
}
format filter {
wrap json
fields {
request delete
resp_headers delete
duration delete
size delete
bytes_read delete
user_id delete
}
}
}
}
assets/gateway/tikhub.caddy
tikhub-proxy.liuhetian.work {
@authorized header Authorization "Bearer {$TIKHUB_GATEWAY_TOKEN}"
log_append vendor tikhub
handle @authorized {
log_append caller personal
log_append authenticated true
reverse_proxy https://api.tikhub.io {
header_up Authorization "Bearer {$TIKHUB_API_KEY}"
header_up -Proxy-Authorization
}
}
handle {
log_append caller unauthenticated
log_append authenticated false
header Content-Type application/json
header WWW-Authenticate Bearer
respond `{"error":{"message":"A valid gateway token is required.","type":"authentication_error"}}` 401
}
log tikhub_proxy {
output file /var/log/caddy/tikhub-proxy/access.json {
roll_size 10MiB
roll_keep 30
roll_keep_for 2160h
}
format filter {
wrap json
fields {
request delete
resp_headers delete
duration delete
size delete
bytes_read delete
user_id delete
}
}
}
}
assets/gateway/gateway.conf
脚本不放在服务器上,要用的时候下载,用 sudo python3 跑:verify.py 做验收(只验 DeepSeek),stats.py 统计每日次数,audit.py 检查 journal 里有没有漏出 key。
踩过的坑
-
改完先校验,通过了再
reload。改过 env 文件要restart,加了 drop-in 要先daemon-reload:sudo -u caddy env -i HOME=/var/lib/caddy PATH=/usr/bin:/bin \ caddy validate --config /etc/caddy/Caddyfile --adapter caddyfile要用 caddy 用户跑:用 root 跑,会建出 root 属主的日志文件,Caddy 就写不进去了。
env -i让报错信息里带不出 key。 - 官方 unit 的--environ会把 key 打进 journal:所以 drop-in 里去掉了它。 -autosave.json里是明文 key:/var/lib/caddy/.config/caddy/autosave.json存的是展开后的配置。保持 0600,管理 API 只监听本机,配置导出来别往外贴。 - 中转只认Authorization: Bearer:Anthropic 兼容端点/anthropic也会原样转发,但 Anthropic SDK 默认把 key 放进x-api-key,这样会 401,要改用auth_token=。 - AutoDL 的真实令牌前面不带Bearer:所以autodl.caddy写的是header_up Authorization "{$AUTODL_API_KEY}"。Agent 那边照样发Bearer <网关 token>。 -sites-enabled/里的文件,request_body正文开头要写\\{:被import的文件会多吃一层转义,只写\{时{"url":"{…}整段被当成不认识的占位符替换成空。下载狗收不到 url,只回一句「500 网络不稳定」,看着像上游故障,绕了一个多小时。同一行直接写在主 Caddyfile 里又是好的,所以临时复现的配置也要经过import。 - Caddy 和直连结果不一样时,先抓 Caddy 实际发出的请求再猜:用管理 API 把这条路由的上游临时指到本机nc -l,发一次请求看完整报文,再reload恢复。 - 改了 token 要同步改每个 Agent 的.env:旧 token 马上就是 401。 - 验收要发真实请求:systemctl is-active只能说明进程活着,要跑verify.py。浏览器直接打开地址看到 401 是正常的,因为浏览器没带 token。 - 换了域名,服务器正常、电脑打不开:本机还缓存着旧 IP。比较三处:本机解析、权威 DNS、curl 实际连上的 IP。
key 放哪
厂商 key 和网关 token 用 scripts/age-seal.sh 加密成 age:…,打码写在这一节,一个 key 一行,写成 DEEPSEEK_API_KEY=age:… 的样子。写进来之前,先在厂商控制台给 key 设好用量上限。为什么能公开放,见打码。
DeepSeek 的 key 还没写进来。
还没做
- 多调用方:每个调用方一个 token、单独撤销、分别统计。现在只有一个调用方
personal,日志也分不出请求来自谁。 - 统计只有请求次数和状态码,不是 token 用量,也不是账单。账单看 DeepSeek 余额(
/user/balance)。 - 海外机 2026-10-02 重装过,本机的 SSH 公钥还没装回去:CodeProxy、OpenRouter 的中转配置没同步进
assets/gateway/,上面的文件表也只对应国内机,验收脚本没在海外机上跑过。 - 没定的事:每项能力用哪家,语音要不要实时(实时就走 WebSocket),每个调用方能用哪些厂商。