跳到主要内容
AI教程

使用 Underclass 构建高韧性的 AI 订阅池

了解如何运行 Underclass,通过 OAuth 设备流程添加订阅账户,连接 OpenCode,配置路由与冷却时间,检查模型,并使用 Cargo、Nix 或 NixOS 安全运行代理。

使用 Underclass 构建高韧性的 AI 订阅池

Underclass 的作用

Underclass 是一个本地代理,可将多个 ChatGPT/Codex 和 GitHub Copilot 订阅整合到单一的 OpenAI 兼容端点。客户端通过熟悉的 /v1/* 路由进行通信,Underclass 会选择符合条件的订阅、跟踪配额状态,并将请求转发到相应的上游服务。

当每个订阅都有独立配额时,这一功能非常实用。无需在某个账户达到限制后手动切换账户,只需将账户放入共享池即可。配额耗尽的账户会进入冷却状态,并在其配额窗口重置后自动恢复轮换。

Underclass 旨在表现得像一个配置充足的服务提供商,同时保留账户状态、会话亲和性和提示缓存的有效性。

主要功能

  • OpenAI 兼容 API:客户端可以使用 /v1/responses、/v1/chat/completions 和 /v1/models。
  • 多个后端:池中可以包含 ChatGPT/Codex 账户、GitHub Copilot 账户,或两者兼有。
  • 固定会话:带有 prompt_cache_key 或 promptCacheKey 的请求会尽可能持续分配给同一订阅。
  • 自动冷却:配额响应会将账户移出轮换,直到其重试窗口结束。
  • 快速返回饱和响应:如果某个模型的所有符合条件的账户都处于冷却状态,Underclass 会返回带有最早可用 Retry-After 值的 429,而不是无限排队。
  • 最少进行中请求选择:符合条件的 Codex 和 Copilot 账户会在扁平池中竞争,并根据各后端的模型目录进行筛选。
  • 持久化状态:账户、模型目录、凭据和固定绑定会在重启后保留。
  • 管理 Web 界面:添加和管理账户、编辑模型目录,并查看最近 200 个请求。
  • 结构化可观测性:每个响应都包含 x-request-id,日志会标识处理每个请求的账户标签。

安装并启动 Underclass

选项 1:从 Rust 项目运行

在仓库中,为 API 客户端和管理界面分别生成密钥,然后启动服务器:

UNDERCLASS_PROXY_KEY="$(openssl rand -hex 32)" \
UNDERCLASS_UI_TOKEN="$(openssl rand -hex 32)" \
cargo run -- serve

默认监听地址为 http://127.0.0.1:8080,Web 界面位于根 URL:

http://127.0.0.1:8080/

请将生成的两个值保存到密码管理器或运行时密钥文件中。代理密钥用于授权对 /v1/* 的请求,UI 令牌用于授权管理操作。Underclass 不会将这些值写入诊断信息。

选项 2:使用 Nix 运行

该仓库也是一个 Nix flake。无需安装即可运行代理:

nix run github:ghuntley/underclass -- serve

要将软件包安装到 Nix 配置文件中,请使用:

nix profile install github:ghuntley/underclass

进行开发时,进入提供的 shell 并运行测试:

nix develop --no-pure-eval
cargo test

devenv 开发 shell 要求使用 --no-pure-eval 选项。nix run 或 nix profile install 不需要该选项。

将账户添加到池中

  1. 打开 http://127.0.0.1:8080/。
  2. 粘贴配置为 UNDERCLASS_UI_TOKEN 的值。
  3. 选择 添加账户。
  4. 选择 ChatGPT / Codex 或 GitHub Copilot。
  5. 打开显示的设备授权 URL,并输入设备代码。
  6. 等待引导流程完成。Underclass 会使用账户的电子邮件地址或用户名为其设置标签。
  7. 对所有应参与池的订阅重复此过程。

UI 还可以启用、禁用、移除或重新登录账户。被禁用的账户仍会保留配置,但不会被选用于处理请求。

连接 OpenCode

Underclass 包含一个 connect 命令,可向 OpenCode 添加 Underclass 提供商:

cargo run -- connect

默认情况下,该命令会更新 ~/.config/opencode/ 下的全局 OpenCode 配置。它会将提供商配置块合并到 opencode.json 或 opencode.jsonc,将凭据写入 auth.json,并创建备份。该操作具有幂等性,因此可以安全地重复运行。

使用池中公开的模型启动 OpenCode:

opencode --provider underclass --model underclass/gpt-5.5

若要改为写入项目本地配置,请添加 --project:

cargo run -- connect --project

预览合并后的配置而不写入任何文件:

cargo run -- connect --dry-run

不再需要时,移除生成的集成配置:

cargo run -- connect --remove

如果需要自定义生成的提供商配置,该命令还接受 --base-url、--api-key、--model 和 --no-default-model。

使用 OpenAI 兼容 API

任何兼容的客户端都可以使用该代理:将其基础 URL 设置为 Underclass 监听地址,并将代理密钥作为 bearer 令牌提供。

列出可用模型

curl http://127.0.0.1:8080/v1/models \
  -H "Authorization: Bearer $UNDERCLASS_PROXY_KEY"

models 端点返回一个合并限制后的联合目录。该目录预置了有文档记录的 Codex 模型系列和 Copilot 的实时模型列表。由于目录以数据形式存储,因此无需修改代理代码,即可通过 UI 或管理 API 进行编辑。

发送聊天补全请求

curl http://127.0.0.1:8080/v1/chat/completions \
  -H "Authorization: Bearer $UNDERCLASS_PROXY_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "gpt-5.5",
    "messages": [
      {"role": "user", "content": "Explain sticky session routing."}
    ]
  }'

代理还支持 POST /v1/responses。这两个请求路由都会通过选定的账户流式传输结果。流式传输开始后,上游故障会直接传递,而不会悄悄重新发送已部分完成的请求。

了解路由和故障转移

粘性会话行为

当请求包含 prompt_cache_key 或 promptCacheKey 时,Underclass 会将该值绑定到一个订阅。配置 setCacheKey: true 后,OpenCode 可以发送其会话 ID。绑定持续 24 小时,并且在代理重启后仍然有效。

如果分配的账户开始进入冷却状态,Underclass 会优先将会话重新绑定到同一后端上的其他账户。此设计有助于保留上游提示缓存的优势,同时仍能从配额耗尽中恢复。

配额和身份验证处理

配额响应,包括相关的 429 或使用限制响应,会使账户进入冷却状态。上游提供 Retry-After 值时,Underclass 会使用该值;否则使用已配置的后端专属冷却时间。冷却结束后,冷却中的账户会自动重新获得资格。

对于上游 401,Underclass 会刷新一次令牌并重试一次。如果身份验证仍然失败,则账户需要重新登录。

池饱和

如果没有符合条件的账户可用于请求的模型,Underclass 不会将请求放入队列等待,而是立即返回 429,并通过 Retry-After 报告已知的最早重置时间。

配置代理

可将可选的持久化配置放置在 ~/.config/underclass/config.toml 中:

bind = "127.0.0.1:8080"
proxy_key = "replace-with-a-client-secret"
ui_token = "replace-with-an-admin-secret"
codex_cooldown_secs = 1800
copilot_cooldown_secs = 1800

当上游服务未提供重试间隔时,各后端的默认冷却时间为 1,800 秒。

环境变量会覆盖对应的配置值:

  • UNDERCLASS_BIND
  • UNDERCLASS_PROXY_KEY
  • UNDERCLASS_UI_TOKEN

针对模拟服务进行测试时,请使用 UNDERCLASS_CODEX_UPSTREAM 和 UNDERCLASS_COPILOT_UPSTREAM。它们的默认值指向真实的上游端点。

Underclass 会将凭据、粘性绑定、生成的密钥和模型目录存储在 ~/.local/share/underclass/pool.db 中。删除该数据库即可完全重置本地状态。

高级运维提示

检查日志并关联请求

每个已处理的响应都包含 x-request-id。日志采用结构化格式,可通过 --log-format json 或 --log-format pretty 使用 JSON 或易读输出。使用 RUST_LOG 控制筛选。

将 preflight 放置在 Underclass 前面时,作为 x-request-id 提供的有效 UUIDv4 会保留在响应、路由日志、重试日志和请求历史中。无效、重复、nil、缺失或非 v4 标识符会被替换。该标识符仅用于关联,不代表授权或检查已完成。

谨慎管理模型目录

路由资格和 OpenCode 模型块都遵循存储的目录。使用 Web UI 或管理目录 API 检查或编辑后端模型映射。未知的模型标识符会直接传递给 Codex,因此新引入的 Codex 模型无需发布代理新版本即可使用。

默认保持服务私有

Underclass 默认监听 localhost。除非确实需要远程访问并已采取保护措施,否则请保留此默认设置。代理不会记录访问令牌、刷新令牌、授权标头或提示正文,但账户标签会显示在日志和 UI 中。

  • 不要提交 pool.db 或生成的 *.bak 文件。
  • 将 UI 令牌与客户端代理密钥分开保护。
  • 妥善保护 OpenCode 生成的 auth.json;Underclass 会以 0600 权限写入该文件。
  • 在将服务暴露到 localhost 之外之前,使用安全隧道或同等审慎的保护措施。

在 NixOS 上运行 Underclass

该 flake 导出一个 NixOS 模块。服务配置可以启用 Underclass、将其绑定到本地地址、从运行时环境文件加载密钥,并设置后端冷却时间:

{
  inputs.underclass.url = "github:ghuntley/underclass";

  outputs = { nixpkgs, underclass, ... }: {
    nixosConfigurations.my-host = nixpkgs.lib.nixosSystem {
      system = "x86_64-linux";
      modules = [
        underclass.nixosModules.default
        {
          services.underclass = {
            enable = true;
            bindAddress = "127.0.0.1:8080";
            environmentFile = "/run/secrets/underclass.env";
            settings = {
              codex_cooldown_secs = 1800;
              copilot_cooldown_secs = 1800;
            };
          };
        }
      ];
    };
  };
}

运行时环境文件可以保存这些密钥,而无需将它们放入 Nix store:

UNDERCLASS_PROXY_KEY=sk-underclass-replace-me
UNDERCLASS_UI_TOKEN=replace-me

该模块使用动态用户,将持久化数据存储在 /var/lib/underclass,默认绑定到 localhost,并保持防火墙关闭。只有在你明确需要绑定到 localhost 之外时,才启用 services.underclass.openFirewall。

开发和测试项目

使用 Cargo 构建项目并运行测试套件:

cargo build
cargo test

该项目结合了单元测试、Hegel 属性测试和端到端测试。池与健康检查核心是同步的,并使用注入的时钟,而异步行为则保留在边界层。新提供程序需要实现 provider::Backend trait,并向应用程序注册。

如需了解架构背景,请查阅项目的架构决策记录。路由模型在 ADR 0010 中有说明,跨代理请求关联则由 ADR 0012 介绍。

结论

Underclass 将多个 ChatGPT/Codex 和 GitHub Copilot 订阅转换为一个本地、兼容 OpenAI 的服务。其粘性路由、自动冷却恢复、感知模型的账户选择以及快速失败的饱和行为,减少了手动切换账户的需要,同时保持客户端行为的可预测性。启动服务器,通过 Web UI 接入账户,运行 underclass connect,然后将 OpenCode 或其他兼容客户端指向生成的端点。