주요 콘텐츠로 건너뛰기
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 계정이 하나의 풀에서 경쟁하며, 각 백엔드의 모델 카탈로그에 따라 필터링됩니다.
  • 영구 상태 저장: 계정, 모델 카탈로그, 자격 증명, 고정 바인딩은 재시작 후에도 유지됩니다.
  • 관리용 웹 UI: 계정을 추가하고 관리하며, 모델 카탈로그를 편집하고, 최근 요청 200건을 확인할 수 있습니다.
  • 구조화된 관측성: 모든 응답에는 x-request-id가 포함되며, 로그에는 각 요청을 처리한 계정 레이블이 표시됩니다.

Underclass 설치 및 시작

옵션 1: Rust 프로젝트에서 실행

저장소에서 API 클라이언트와 관리 UI에 사용할 별도의 시크릿을 생성한 다음 서버를 시작합니다.

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

기본 리스너는 http://127.0.0.1:8080이며, 웹 인터페이스는 루트 URL에서 사용할 수 있습니다.

http://127.0.0.1:8080/

생성된 두 값을 모두 비밀번호 관리자 또는 런타임 시크릿 파일에 저장하세요. 프록시 키는 /v1/* 요청을 승인하고, UI 토큰은 관리 작업을 승인합니다. Underclass는 이 값을 진단 정보에 기록하지 않습니다.

옵션 2: Nix로 실행

이 저장소는 Nix 플레이크이기도 합니다. 설치하지 않고 프록시를 실행하려면 다음을 사용합니다.

nix run github:ghuntley/underclass -- serve

Nix 프로필에 패키지를 설치하려면 다음을 사용합니다.

nix profile install github:ghuntley/underclass

개발 환경에서는 제공된 셸에 진입한 후 테스트를 실행합니다.

nix develop --no-pure-eval
cargo test

devenv 개발 셸에는 --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에는 OpenCode에 Underclass 공급자를 추가하는 connect 명령이 포함되어 있습니다.

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 리스너로 설정하고 프록시 키를 베어러 토큰으로 제공하여 프록시를 사용할 수 있습니다.

사용 가능한 모델 나열

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는 해당 값을 하나의 구독에 연결합니다. OpenCode는 setCacheKey: true로 구성된 경우 세션 ID를 보낼 수 있습니다. 연결은 24시간 동안 유지되며 프록시를 다시 시작해도 유지됩니다.

할당된 계정이 쿨다운을 시작하면 Underclass는 동일한 백엔드의 다른 계정으로 세션을 우선적으로 다시 연결합니다. 이 설계는 업스트림 프롬프트 캐시의 이점을 유지하면서도 할당량 소진 상황에서 복구할 수 있도록 합니다.

할당량 및 인증 처리

관련 429 또는 사용량 제한 응답을 포함한 할당량 응답이 오면 계정은 쿨다운 상태로 전환됩니다. Underclass는 업스트림의 Retry-After 값을 사용할 수 있는 경우 사용하고, 그렇지 않으면 구성된 백엔드별 쿨다운을 사용합니다. 쿨다운이 끝나면 쿨다운 중인 계정은 자동으로 다시 사용 가능해집니다.

업스트림에서 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 모델 블록은 저장된 카탈로그를 따릅니다. 웹 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 스토어에 넣지 않아도 됩니다:

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 트레이트를 구현하고 애플리케이션에 등록해야 합니다.

아키텍처 배경은 프로젝트의 아키텍처 결정 기록을 참고하세요. 라우팅 모델은 ADR 0010에 설명되어 있으며, 프록시 간 요청 상관관계는 ADR 0012에서 다룹니다.

결론

Underclass는 여러 ChatGPT/Codex 및 GitHub Copilot 구독을 하나의 로컬 OpenAI 호환 서비스로 통합합니다. 고정 라우팅, 자동 쿨다운 복구, 모델을 고려한 계정 선택, 포화 상태에서 즉시 실패하는 동작을 통해 예측 가능한 클라이언트 동작을 유지하면서 수동 계정 전환을 줄여 줍니다. 서버를 시작하고 웹 UI에서 계정을 등록한 다음 underclass connect를 실행하고, OpenCode 또는 다른 호환 클라이언트가 생성된 엔드포인트를 가리키도록 설정하세요.