拆解 24K Star 的 CLIProxyAPI:Go 实现多模型代理与负载均衡

6 阅读7分钟

拆解 24K Star 的 CLIProxyAPI:Go 实现多模型代理与负载均衡

9 个月 24000 Star,日更到 v6.9.19。这篇文章从源码层面拆解它的架构。


它到底做了什么

一句话:CLIProxyAPI 是一个用 Go 写的反向代理,把 Claude Code、Gemini CLI、OpenAI Codex、Qwen Code 这些 AI CLI 工具的 OAuth 订阅,包装成标准的 OpenAI / Claude / Gemini 兼容 API。

开发者只需启动这个代理,配置好自己的账号,就能在 Cursor、Cline、自己写的脚本里,通过标准 API 格式调用这些模型。多个账号还能自动负载均衡和故障转移。

听起来简单,实际不简单。Claude 的 Messages API、Gemini 的 GenerateContent API、OpenAI 的 Chat Completions API 格式各不相同,还要处理 OAuth 刷新、流式响应、多账号调度。

下面从源码拆解它是怎么做的。


整体架构

先看目录结构,项目的分层非常清晰:

CLIProxyAPI/
├── cmd/server/          # 入口
├── internal/            # 应用层(不可外部引用)
│   ├── auth/            # 各 Provider 的 OAuth 认证
│   ├── runtime/executor/# 各 Provider 的执行器
│   ├── translator/      # 格式转换器(NxN 矩阵)
│   ├── config/          # 配置管理
│   ├── watcher/         # 文件监听 & 热重载
│   ├── store/           # 持久化(文件/PG/Git/S3)
│   ├── tui/             # 终端管理界面
│   └── api/             # HTTP 路由 & Middleware
├── sdk/                 # SDK 层(可嵌入其他 Go 项目)
│   ├── cliproxy/        # 核心服务
│   │   ├── auth/        # 调度器 & 账号管理
│   │   ├── executor/    # 执行器接口
│   │   └── usage/       # 用量统计
│   ├── translator/      # 翻译管线 & 注册表
│   ├── auth/            # 认证抽象
│   └── api/handlers/    # API Handler
└── config.example.yaml  # 配置模板

核心有三层:

  1. Executor 层:每个 AI Provider 一个执行器,负责实际调用上游 API
  2. Translator 层:N×N 格式转换矩阵,在 OpenAI / Claude / Gemini 等协议间互转
  3. Scheduler 层:多账号调度,负载均衡 + 故障转移 + 冷却恢复

一个请求的完整生命周期:客户端请求 → API Handler → 格式转换 → Scheduler 选账号 → Executor 调上游 → 格式转回 → 返回客户端


核心设计一:Executor 模式

每个 AI Provider 都有自己的 Executor,实现统一的 ProviderExecutor 接口:

type ProviderExecutor interface {
    Identifier() string
    Execute(ctx context.Context, auth *Auth, req Request, opts Options) (Response, error)
    ExecuteStream(ctx context.Context, auth *Auth, req Request, opts Options) (*StreamResult, error)
    Refresh(ctx context.Context, auth *Auth) (*Auth, error)
    CountTokens(ctx context.Context, auth *Auth, req Request, opts Options) (Response, error)
    HttpRequest(ctx context.Context, auth *Auth, req *http.Request) (*http.Response, error)
}

源码里能看到这些 Executor 实现:

Executor对应服务认证方式
claude_executorClaude CodeOAuth + PKCE
gemini_executorGemini APIAPI Key
gemini_cli_executorGemini CLIGoogle OAuth
codex_executorOpenAI CodexOAuth
codex_websockets_executorCodex WebSocketOAuth
qwen_executorQwen CodeOAuth
iflow_executoriFlowCookie/OAuth
antigravity_executorAntigravityOAuth
kimi_executorKimiOAuth
openai_compat_executorOpenAI 兼容上游API Key

每个 Executor 内部处理自己 Provider 的特殊逻辑。比如 claude_executor 里有 claude_signing.go 处理 Claude 特有的请求签名,codex_websockets_executor 处理 OpenAI Codex 的 WebSocket 协议。

这种设计的好处:新增一个 Provider,只需实现 ProviderExecutor 接口,注册到 Manager 里。调度逻辑、翻译逻辑、API 路由都不用动。


核心设计二:N×N 格式转换矩阵

这是整个项目最精巧的部分。

各家 AI 的 API 格式完全不同:

  • OpenAI:/v1/chat/completions,JSON 格式
  • Claude:/v1/messages,自己的 JSON 格式
  • Gemini:/v1beta/models/xxx:generateContent,又是另一种格式

CLIProxyAPI 需要在这些格式之间互相转换。它的做法是建一个 翻译注册表(Translator Registry)

type Registry struct {
    requests  map[Format]map[Format]RequestTransform
    responses map[Format]map[Format]ResponseTransform
}

一个二维 Map。requests[OpenAI][Claude] 存的就是"把 OpenAI 格式的请求翻译成 Claude 格式"的函数。

看源码里的 translator 目录结构就能明白这个矩阵有多大:

internal/translator/
├── claude/gemini/            # Claude → Gemini
├── claude/openai/chat-completions/  # Claude → OpenAI
├── claude/openai/responses/  # Claude → OpenAI Responses
├── codex/claude/             # Codex → Claude
├── codex/gemini/             # Codex → Gemini
├── codex/openai/chat-completions/   # Codex → OpenAI
├── gemini/claude/            # Gemini → Claude
├── gemini/openai/chat-completions/  # Gemini → OpenAI
├── openai/claude/            # OpenAI → Claude
├── openai/gemini/            # OpenAI → Gemini
└── ... (更多组合)

每种组合都有 init.go,通过 Go 的 init() 函数在启动时自动注册到全局 Registry。

翻译过程被封装成 Pipeline,支持中间件:

func (p *Pipeline) TranslateRequest(ctx context.Context, from, to Format, req RequestEnvelope) (RequestEnvelope, error) {
    terminal := func(ctx context.Context, input RequestEnvelope) (RequestEnvelope, error) {
        translated := p.registry.TranslateRequest(from, to, input.Model, input.Body, input.Stream)
        input.Body = translated
        return input, nil
    }
    // 从后往前包裹中间件
    handler := terminal
    for i := len(p.requestMiddleware) - 1; i >= 0; i-- {
        mw := p.requestMiddleware[i]
        next := handler
        handler = func(ctx context.Context, r RequestEnvelope) (RequestEnvelope, error) {
            return mw(ctx, r, next)
        }
    }
    return handler(ctx, req)
}

洋葱模型。翻译函数是核心,外围可以套任意数量的中间件来处理通用逻辑(日志、metrics 等)。

为什么用 NxN 而不是星型(所有格式先转中间格式)?

因为不同格式之间的直接转换更高效。星型需要 2N 个转换器,但每次转换要经过两步,精度有损。NxN 需要 N² 个转换器,但一步到位。对于 API 格式转换这种场景,N 目前只有 6-7 种,N² 可控,精度更重要。


核心设计三:多级调度器

多账号负载均衡是 CLIProxyAPI 的杀手级功能。源码里的实现分了三层:

第一层:Provider 级别

Manager 维护一个 providerOffsets Map,追踪每个模型跨 Provider 的轮询状态。比如 claude-sonnet-4 可能在 Claude Provider 和 Gemini Provider 上都有对应模型,Manager 会在这两个 Provider 之间轮询。

第二层:Provider 内的 Auth 轮询

每个 Provider 可以有多个认证(多个 Google 账号、多个 Claude 订阅)。providerScheduler 管理这些 Auth:

type providerScheduler struct {
    providerKey string
    auths       map[string]*scheduledAuthMeta
    modelShards map[string]*modelScheduler
}

按模型分片。每个模型有自己的 modelScheduler

第三层:Priority Bucket + 冷却队列

最精巧的部分。每个 modelScheduler 把 Auth 按 优先级(priority) 分桶:

type modelScheduler struct {
    modelKey        string
    entries         map[string]*scheduledAuth
    priorityOrder   []int
    readyByPriority map[int]*readyBucket  // 按优先级分桶
    blocked         cooldownQueue         // 冷却队列
}
  • Ready 状态:可以使用的 Auth,放在 readyBucket 里按优先级分组
  • Cooldown 状态:刚遇到 429 或错误的 Auth,放入冷却队列,等 nextRetryAt 到了再恢复
  • Blocked/Disabled 状态:彻底不可用

调度策略支持两种:

routing:
  strategy: "round-robin"  # 或 "fill-first"
  • round-robin:在同优先级的 Auth 之间均匀轮询
  • fill-first:优先用同一个 Auth 直到限速,再切下一个

还有一个细节:Gemini 的虚拟 Auth(一个 Google 账号下多个 Project)通过 virtualParent 机制做子账号分组轮询,避免全部请求打到一个 Project 上。


认证:OAuth 流程的工程化处理

每个 Provider 的 OAuth 流程都不一样,CLIProxyAPI 对每个做了单独处理。以 Claude 为例:

internal/auth/claude/
├── anthropic.go          # Anthropic 认证常量
├── anthropic_auth.go     # OAuth 认证逻辑
├── pkce.go              # PKCE 流程
├── oauth_server.go      # 本地回调服务器
├── token.go             # Token 存储 & 刷新
├── utls_transport.go    # TLS 指纹伪装
└── errors.go            # 错误处理

几个有意思的工程细节:

PKCE(Proof Key for Code Exchange):Claude 的 OAuth 用了 PKCE 增强安全性,源码里的 pkce.go 生成 code_verifier 和 code_challenge。

本地回调服务器oauth_server.go 起一个临时 HTTP 服务器接收 OAuth 回调,拿到 authorization code 后交换 token。

TLS 指纹utls_transport.go 用 uTLS 库模拟浏览器 TLS 指纹,避免被上游检测为非浏览器流量。

Token 持久化:Token 支持四种存储后端——本地文件、PostgreSQL、Git 仓库、对象存储(S3 兼容)。通过统一的 TokenStore 接口抽象:

// 注册方式(main.go 里):
if usePostgresStore {
    sdkAuth.RegisterTokenStore(pgStoreInst)
} else if useGitStore {
    sdkAuth.RegisterTokenStore(gitStoreInst)
} else {
    sdkAuth.RegisterTokenStore(sdkAuth.NewFileTokenStore())
}

这意味着你可以把 Token 存在 Git 仓库里做多机同步,或者存 PostgreSQL 做集群部署。


热重载 & 运维友好

internal/watcher/ 实现了配置文件的热重载。修改 config.yaml 后不用重启服务,Watcher 会检测变更,触发 config_diffauth_diff → 调度器重建。

还有个 TUI 模式:

cliproxyapi -tui          # 终端管理界面
cliproxyapi -tui -standalone  # 内嵌服务 + TUI

TUI 用 Bubble Tea 框架(Go 生态的 TUI 标准),有 Dashboard、Auth 管理、配置编辑、日志查看、用量统计等 Tab。


SDK 化:可以嵌入你的 Go 项目

项目分了 internal/sdk/ 两个包。sdk/ 是可被外部引用的:

import "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy"

官方文档里给了嵌入式用法:

svc := cliproxy.NewService(cfg,
    cliproxy.WithTokenProvider(myProvider),
    cliproxy.WithAPIKeyProvider(myKeyProvider),
)
svc.RegisterUsagePlugin(myPlugin) // 监控用量
svc.Run(ctx)

这意味着你可以把 CLIProxyAPI 作为库嵌入到自己的项目里,不需要单独部署一个进程。


架构总结

CLIProxyAPI 的架构可以用一张图概括:

客户端请求 (OpenAI/Claude/Gemini 格式)
    │
    ▼
┌─────────────────────────────────────────┐
│           API Handler Layer             │
│   /v1/chat/completions                  │
│   /v1/messages                          │
│   /v1beta/models/...:generateContent    │
└──────────────┬──────────────────────────┘
               │
               ▼
┌─────────────────────────────────────────┐
│         Translator Pipeline             │
│   客户端格式 → Provider 格式 (NxN 矩阵) │
└──────────────┬──────────────────────────┘
               │
               ▼
┌─────────────────────────────────────────┐
│           Scheduler Layer               │
│   Provider 轮询 → Auth 轮询 → Priority  │
│   Bucket → Cooldown Queue               │
└──────────────┬──────────────────────────┘
               │
               ▼
┌─────────────────────────────────────────┐
│          Executor Layer                 │
│   Claude | Gemini | Codex | Qwen | ...  │
│   OAuth → 上游 API → 响应               │
└──────────────┬──────────────────────────┘
               │
               ▼
┌─────────────────────────────────────────┐
│         Translator Pipeline (返回)       │
│   Provider 格式 → 客户端格式             │
└─────────────────────────────────────────┘

四个核心设计决策:

  1. 接口抽象ProviderExecutor 统一了所有 Provider 的调用方式,扩展性极强
  2. NxN 翻译矩阵:直接转换比星型中间格式精度更高,用 init() 自动注册简化维护
  3. 三级调度:Provider → Auth → Priority Bucket,层层递进,兼顾负载均衡和故障恢复
  4. SDK 化sdk/ 包独立可嵌入,支持二次开发

这套架构不只能做 AI CLI 代理。任何需要"多上游、多格式、多账号"的代理场景,都能参考这个模式。