这几天我把自己的一套模型调用链路重新整理了一遍,核心是把 CLIProxyAPI(这里简称 CPA) 和 NewAPI 串起来,再通过域名暴露出一个更稳定、可控、方便统一接入的入口。
这篇文章记录一下这套结构是怎么搭起来的、每一层各自负责什么、踩过哪些坑,以及为什么我最后会保留这样的分层设计。
一、先说结论:我最后采用的是两层结构
整体结构可以概括成这样:
1客户端 / Hermes / 其他工具2 ↓3 NewAPI(3000)4 ↓5 CPA / CLIProxyAPI(8317)6 ↓7 上游模型供应商再加上对外访问层,就是:
1https://newapi.innilove.xyz2 ↓3 Nginx 反向代理4 ↓5 本机 NewAPI :30006 ↓7 Channel #1 → 127.0.0.1:83178 ↓9 CPA / CLIProxyAPI10 ↓11 各模型上游这套方案里:
- CPA / CLIProxyAPI 更像底层能力层,负责真正和上游模型打交道
- NewAPI 是外层统一入口,负责渠道、模型映射、统一鉴权和对外接口组织
- Nginx + 域名 负责把本地服务变成一个可直接接入的公网入口
二、为什么要分成两层,而不是直接一个服务顶到底
一开始很多人都会想:既然最终都是转发给模型,为什么不直接让客户端打底层?
我最后还是保留两层,主要是因为下面几个原因。
1. 对外入口和底层代理的职责不一样
底层的 CPA 更像是“真正出请求的人”,而 NewAPI 更像是“统一门面”。
这样拆开之后:
- 外层可以统一接 OpenAI 兼容调用
- 内层可以按自己的方式维护上游渠道
- 某一层需要替换时,不至于把整条链路一起拆掉
2. 更适合做模型映射和渠道管理
我这里实际会出现这种情况:
- 对外暴露的是一个统一模型名
- 内部真正走的是某个 channel
- channel 再转发给底层 CPA
比如我的实际配置里就有一条关键链路:
- NewAPI 运行在 3000 端口
- Channel #1(CodeX)转发到
http://127.0.0.1:8317 - 底层
8317就是 CPA / CLIProxyAPI
这个设计的好处是,外部接入方不需要知道底层到底是哪一个代理进程、跑在什么容器里,也不需要直接接触上游供应商细节。
3. 更容易做日志排查
分层之后,日志能清楚分成两类:
- NewAPI 日志:看外层请求有没有进来、路由到了哪里
- CPA 日志:看底层真正打上游时发生了什么
这对排查特别重要。很多时候问题不是“服务没起来”,而是:
- 外层路由到了错误渠道
- 底层某个上游没有可用 auth
- 或者流式请求中途断开
如果所有逻辑都揉在一个服务里,排错会非常痛苦。
三、我这套环境的实际落地结构
1. NewAPI:外层统一入口
我的 NewAPI 本地运行在:
127.0.0.1:3000
它是通过 systemd —user 方式启动的,启动参数里显式指定了日志目录。
实际启动命令是:
1/home/agentuser/new-api --port 3000 --log-dir /home/agentuser/newapi-logs所以它的关键位置是:
- 服务文件:
/home/agentuser/.config/systemd/user/newapi.service - 日志目录:
/home/agentuser/newapi-logs
这意味着 NewAPI 本身不是一个“跑一下就算了”的临时进程,而是一个比较正式的本地常驻服务。
2. 对外域名:newapi.innilove.xyz
本地 3000 端口并没有直接裸露到外网,而是通过 Nginx 反向代理 对外暴露。
主入口域名是:
http://newapi.innilove.xyz
后来也补上了 HTTPS,证书是 Let’s Encrypt,443 已经配置完成。
这一层的意义很直接:
- 对客户端来说,不需要关心本地端口
- 可以统一走域名
- 后续换端口、换服务实现、换内部结构,外部接入方都不用跟着改
3. CPA / CLIProxyAPI:底层真实代理
底层我实际使用的是 CLIProxyAPI,它跑在:
8317端口
并且是 Docker 部署 的。
也就是说:
3000是外层 NewAPI8317是底层 CPA
这一点非常关键。因为我一开始排查时也踩过一个很典型的误区:
当某个模型调用失败时,不能简单地认为是 3000 端口挂了。
很多时候真实情况是:
- NewAPI 本身还活着
- 3000 也在监听
- 只是它转发到的上游请求失败了
比如我在排查 gpt-5.4 时,见过这些典型日志:
503 auth_unavailable / no auth available408 stream disconnected before response.completed
这说明问题可能出在:
- 底层上游授权不可用
- 某次流式请求中途断掉
- 某个 channel 当前不可用
而不是 NewAPI 进程本身已经挂掉。
四、日志路径要分清,不然排错会很绕
这套分层里,最重要的经验之一就是:
先分清你要看的是哪一层的日志。
1. NewAPI 日志
NewAPI 的日志目录:
1/home/agentuser/newapi-logs这个目录适合用来查:
- 服务有没有正常启动
- 请求有没有打到外层
- 外层有没有报路由错误
- 基础接口是否正常
2. CPA / CLIProxyAPI 日志
CLIProxyAPI 是 Docker 部署,但日志不是只能进容器里看。
我这里它的日志已经挂载到了宿主机:
- 宿主机路径:
/home/agentuser/CLIProxyAPI/logs - 容器内路径:
/CLIProxyAPI/logs
也就是说,不一定非得 docker exec 进去,直接在宿主机上看就行。
这个目录更适合查:
- 上游实际报错
- 某个模型请求失败细节
- 某次 chat/completions 错误原因
如果看到类似下面这种文件名,基本就是底层真实报错:
1error-v1-chat-completions-xxxx.log五、为什么我后来强调“验证时优先看真实域名,而不是只看 localhost”
这是这次实践里一个非常实际的教训。
理论上,服务本地通了,很多人会觉得“那就没问题了”。但实际上不是。
因为在真实链路里,还多了一层:
- Nginx
- 域名
- HTTPS
- 反向代理头
- 外网访问路径
所以如果只验证:
1curl http://127.0.0.1:3000那你只能证明:
- NewAPI 本机端口可能活着
但你不能证明:
- 域名一定通
- 反向代理没配错
- 外部客户端也能正常访问
所以后面我的验证习惯就变成了:
第一优先级
直接验证真实外部入口:
1http://newapi.innilove.xyz第二优先级
再看本地服务端口:
1http://127.0.0.1:3000这个顺序更接近真实用户视角,也更容易提前发现反向代理层的问题。
六、这套结构带来的几个实际好处
1. 调用入口统一
对外只需要记住一个域名入口,不用关心底层到底连了几个模型、几个渠道、几个代理。
2. 可替换性更高
未来如果我要:
- 替换底层代理实现
- 新增别的 provider
- 对某个模型单独调路由
- 做灰度切换
都可以优先在内层做,而不影响外部调用方式。
3. 更适合做个人化中间层
像我这种场景,不只是“把请求转发出去”,而是还希望有:
- 自定义模型名
- 渠道映射
- 统一接口风格
- 统一日志入口
- 更顺手的域名访问
那 NewAPI 作为外层就非常合适。
4. 出问题时更容易定位
可以快速判断问题在哪一层:
- 域名不通 → 先看 Nginx / 443 / 反代
- 3000 正常、请求失败 → 看 NewAPI 路由
- NewAPI 正常、上游报错 → 看 CPA / CLIProxyAPI
- 底层日志显示 auth unavailable → 看上游认证池
这种分层定位,比单体结构的“全堆在一起”清晰太多了。
七、这次实践里踩到的坑
坑 1:把“端口活着”和“整条链路正常”混为一谈
3000 端口活着,不代表:
- 上游一定可用
- 某个模型一定可用
- 流式输出一定稳定
服务“活着”和“能正确完成某类请求”,是两回事。
坑 2:看到失败就以为外层挂了
尤其是遇到:
503 no auth available408 stream disconnected
这种报错时,很容易本能觉得是外层服务炸了。
但实际上,很多时候只是:
- 外层还在
- 路由还在
- 底层某个上游请求失败了
这个判断如果错了,会把排障方向带偏。
坑 3:日志不分层
一旦把 NewAPI 日志和 CPA 日志混在一起看,信息会非常乱。
正确做法是:
- 先看外层有没有收到请求
- 再看外层转发到了哪里
- 最后看底层上游为什么失败
这样会省很多时间。
八、如果你也想复刻这套结构,可以怎么搭
一个最简思路如下:
第一步:先把底层代理单独跑起来
先让 CPA / CLIProxyAPI 在本机某个端口稳定运行,比如:
1127.0.0.1:8317确保它本身可以完成最基础的模型请求。
第二步:再让 NewAPI 接到它前面
在 NewAPI 里配置一个 channel,把模型请求转发给:
1http://127.0.0.1:8317先只做一条最小可用链路,不要一开始就堆太多模型和太多路由规则。
第三步:把 NewAPI 作为本地常驻服务托管
用 systemd、supervisor 或容器都行,但核心是:
- 服务要能稳定常驻
- 重启后能自动恢复
- 日志目录要单独留出来
第四步:最后再上域名和 HTTPS
先保证本机链路通,再上反向代理。
推荐顺序:
- 本地
3000 -> 8317通 - Nginx 反代通
- 域名通
- HTTPS 通
- 再做真实客户端接入验证
不要一上来就一边调反代一边调底层模型,那样会把变量搅在一起。
九、我现在怎么看这套架构
如果只是“能用就行”,它看起来有点绕。
但如果你已经有下面这些需求:
- 多个模型来源
- 多渠道转发
- 统一接口风格
- 域名入口
- 后续可能扩展更多 provider
- 希望出问题时能快速分层排查
那 CPA + NewAPI 这种两层结构其实很顺手。
它不是最短路径,但对“长期维护”和“自己掌控整条链路”来说,体验会好很多。
我自己这次折腾下来最大的感受就是:
真正重要的,不只是把服务跑起来,而是把“入口、转发、日志、排障”这四件事都理顺。
只有这样,这套东西后面才不会越用越乱。
十、最后做个简短总结
我的这套链路核心就是:
- NewAPI 在外层:负责统一入口、渠道映射、对外接口
- CPA / CLIProxyAPI 在内层:负责真实打上游模型
- Nginx + 域名在最外面:负责公网访问和 HTTPS
已知关键点:
- NewAPI:
3000 - CPA / CLIProxyAPI:
8317 - NewAPI 对外域名:
newapi.innilove.xyz - NewAPI 日志:
/home/agentuser/newapi-logs - CPA 日志:
/home/agentuser/CLIProxyAPI/logs
如果后面我再继续完善这套链路,我大概率会继续补这几块:
- 更稳定的超时与重试策略
- 更细的模型/渠道健康检查
- 更清晰的错误分层提示
- 更方便复用的部署文档
如果你也在折腾自己的模型网关,希望这篇记录能帮你少踩几个坑。
Some information may be outdated