콘텐츠로 이동

서버 & 백엔드

서버 섹션

HTTP 서버 동작을 제어합니다:

server:
  bind_address: "0.0.0.0:8080"    # 바인드할 호스트와 포트
  workers: 4                       # 워커 스레드 (0 = 자동)
  connection_pool_size: 100        # HTTP 연결 풀 크기

다중 바인드 주소 및 Unix 소켓

서버는 Unix 도메인 소켓(Unix 계열 시스템)을 포함하여 여러 주소에 동시에 바인딩하는 것을 지원합니다. 그래서 IPv4와 IPv6 주소 모두에서 수신하거나, 외부 클라이언트용 TCP 포트와 로컬 서비스용 Unix 소켓을 동시에 노출하거나, 보안 강화를 위해 Unix 소켓을 통한 리버스 프록시를 운영할 수 있습니다.

단일 주소:

server:
  bind_address: "0.0.0.0:8080"

다중 주소:

server:
  bind_address:
    - "127.0.0.1:8080"           # IPv4 localhost
    - "[::1]:8080"               # IPv6 localhost
    - "0.0.0.0:9090"             # 포트 9090의 모든 인터페이스

Unix 소켓 바인딩 (Linux, macOS, Windows 10 1809+ 지원):

server:
  bind_address:
    - "0.0.0.0:8080"             # 외부 액세스용 TCP
    - "unix:/var/run/continuum-router.sock"  # 로컬 서비스용 Unix 소켓
  socket_mode: 0o660              # 선택 사항: Unix 소켓의 파일 권한 (8진수)

설정 옵션:

옵션 타입 기본값 설명
bind_address 문자열 또는 배열 "0.0.0.0:8080" 바인드할 주소. TCP 형식: host:port. Unix 소켓 형식: unix:/path/to/socket
socket_mode 정수 (8진수) null Unix 소켓의 파일 권한 (예: 0o660은 소유자/그룹 읽기-쓰기)
max_concurrent_requests 정수 null 동시 처리 요청 상한 (1..1000000). 상한 초과 시 503 및 Retry-After로 요청을 거부함 (재시작 필요)
max_stream_buffer_bytes 정수 4294967296 (4 GiB) 활성 SSE 파서, 라인 버퍼, 스트림별 릴레이 큐가 공유하는 프로세스 단위 바이트 예산 (최소 1, 0은 파싱 시 거부, 재시작 필요)

Unix 소켓 참고사항:

  • Unix 소켓 주소는 unix: 접두사로 시작해야 합니다
  • 기존 소켓 파일은 바인딩 전에 자동으로 삭제됩니다
  • 소켓 파일은 정상적인 종료 시 정리됩니다
  • Windows 10 1809+ (Build 17063+)에서는 socket2 크레이트를 통해 Unix 소켓이 완전히 지원됩니다
  • 기타 비Unix 플랫폼에서는 unix: 주소가 경고를 기록하고 건너뜁니다
  • Windows는 Unix 파일 권한 모드를 지원하지 않습니다. socket_mode 옵션은 허용되지만 무시됩니다
  • Unix 소켓 연결은 IP 기반 인증 검사를 우회합니다 (클라이언트 IP는 unix로 보고됨)

Nginx 리버스 프록시 예제:

upstream continuum {
    server unix:/var/run/continuum-router.sock;
}

server {
    listen 443 ssl;
    location /v1/ {
        proxy_pass http://continuum;
    }
}

성능 튜닝:

  • workers: 자동 감지를 위해 0으로 설정하거나 CPU 코어 수와 일치
  • connection_pool_size: 고부하 시나리오에서는 증가 (200-500)

CORS 설정

CORS (Cross-Origin Resource Sharing)는 라우터가 다른 출처에서 실행되는 웹 브라우저의 요청을 수락할 수 있게 합니다. 다음 환경에 continuum-router를 임베딩할 때 필수적입니다:

  • Tauri 앱: tauri://localhost와 같은 출처를 사용하는 WebView
  • Electron 앱: 사용자 정의 프로토콜
  • 별도의 웹 프론트엔드: 다른 포트의 개발 서버
server:
  bind_address: "0.0.0.0:8080"
  cors:
    enabled: true
    allow_origins:
      - "tauri://localhost"
      - "http://localhost:*"        # 와일드카드 포트 매칭
      - "https://example.com"
    allow_methods:
      - "GET"
      - "POST"
      - "PUT"
      - "DELETE"
      - "OPTIONS"
      - "PATCH"
    allow_headers:
      - "Content-Type"
      - "Authorization"
      - "X-Request-ID"
      - "X-Trace-ID"
    expose_headers:
      - "X-Request-ID"
      - "X-Fallback-Used"
    allow_credentials: false
    max_age: 3600                   # 프리플라이트 캐시 기간 (초)

CORS 설정 옵션:

옵션 유형 기본값 설명
enabled boolean false CORS 미들웨어 활성화/비활성화
allow_origins array [] 허용된 출처 (*로 모든 출처 허용, http://localhost:*와 같은 포트 와일드카드 지원)
allow_methods array ["GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH"] 허용된 HTTP 메서드
allow_headers array ["Content-Type", "Authorization", "X-Request-ID", "X-Trace-ID"] 허용된 요청 헤더
expose_headers array [] 클라이언트 JavaScript에 노출되는 헤더
allow_credentials boolean false 쿠키와 인증 헤더 허용
max_age integer 3600 프리플라이트 응답 캐시 기간 (초)

출처 패턴 매칭:

패턴 예시 설명
* * 모든 출처 매칭 (allow_credentials: true와 호환되지 않음)
정확한 URL https://example.com 정확히 일치
사용자 정의 스킴 tauri://localhost 사용자 정의 프로토콜 (Tauri, Electron)
포트 와일드카드 http://localhost:* localhost의 모든 포트 매칭

보안 고려사항:

  • 출처에 *를 사용하면 모든 웹사이트에서 요청할 수 있습니다 - 공개 API에만 사용하세요
  • allow_credentials가 true일 때 출처에 *를 사용할 수 없습니다 - 정확한 출처를 지정하세요
  • 개발 시에는 유연성을 위해 http://localhost:*와 같은 포트 와일드카드를 사용하세요
  • 프로덕션에서는 보안을 위해 항상 정확한 출처를 지정하세요

재적용 동작: CORS는 시작 시 구성되는 Axum 라우터 계층입니다. server.cors를 변경한 뒤에는 재시작하세요.

백엔드 섹션

요청을 라우팅할 LLM 백엔드를 정의합니다:

backends:
  - name: "unique-identifier"        # 모든 백엔드에서 고유해야 함
    backend_id: "stable-identity"    # 선택 사항: 안정적인 회계 식별자 (아래 참고)
    type: "generic"                  # 백엔드 유형 (선택 사항, 기본값 "generic")
    url: "http://backend:port"       # 백엔드 기본 URL
    weight: 1                        # 로드 밸런싱 가중치 (1-100)
    api_key: "${API_KEY}"            # API 키 (선택 사항, 환경 변수 참조 지원)
    org_id: "${ORG_ID}"              # 조직 ID (선택 사항, OpenAI용)
    models: ["model1", "model2"]     # 선택 사항: 정확한 ID 또는 글롭 패턴 ("모델 글롭" 참고)
    region_name: "us"                # 선택 사항: 운영자가 정의한 데이터 상주 태그 ("데이터 상주 태그" 참고)
    internal: false                  # 선택 사항: 인프라 전용 모델을 사용자 노출에서 숨김 (기본값 false)
    enabled: true                    # 선택 사항: false면 프로브는 가능하되 사용자 트래픽 차단 (기본값 true)
    http_version: auto               # 선택 사항: auto(기본값), http1, http2 ("HTTP 프로토콜 버전" 참고)
    responses_api: auto              # 선택 사항: auto(기본값) 또는 native ("네이티브 Responses API" 참고)
    replay: enabled                  # 선택 사항: enabled(기본값) 또는 부수 효과가 있는 백엔드에 disabled ("재전송 정책" 참고)
    anthropic_version: "2023-06-01"  # 선택 사항, type: anthropic 전용: "2023-06-01"(기본값) 또는 "2026-10-21" ("Anthropic API 버전" 참고)
    history_reasoning_field: as_sent # 선택 사항: as_sent(기본값) 또는 reasoning_content ("대화 이력의 assistant 추론" 참고)
    supports_response_format: false  # 선택 사항: response_format 지원 여부 선언. 없으면 백엔드 유형으로 추론("response_format 지원 선언" 참고)
    record_usage_missing: false      # 선택 사항: usage 블록이 없는 2xx 응답에 0토큰 usage_missing 레코드 기록 (기본값 false)
    retry_override:                  # 선택 사항: 백엔드별 재시도 설정
      max_attempts: 5
      initial_delay: "200ms"
      max_delay: "10s"
      backoff_multiplier: 2.0
      jitter: true
      retryable_status_codes: [429, 502, 503, 504]
      retryable_errors: [ConnectionError, TimeoutError]
      timeout: "30s"

모델 글롭

backends[].models는 정확한 서빙 ID뿐 아니라 글롭 항목(*)도 받습니다. 따라서 한 항목으로 공급자의 모델 계열 전체를 지정할 수 있으며, 모든 ID를 나열할 필요가 없습니다:

- name: openai
  type: openai
  models: ["gpt-*", "o3-*"]

패턴은 라우팅 규칙으로만 사용됩니다. /v1/models는 패턴 문자열을 노출하지 않고, 백엔드 자체 모델 목록이 알리는 ID 중 패턴과 일치하는 구체적인 ID를 나열합니다. 따라서 공급자가 시작 이후 추가한 모델도 설정을 고치지 않아도 다음 갱신 때 나타납니다. 실시간 모델 목록이 없는 백엔드(Bedrock)는 패턴과 일치하는 요청을 라우팅하지만, 그 패턴에 대해서는 아무것도 나열하지 않습니다.

해석 규칙은 다음과 같습니다.

  • 정확한 항목이 패턴보다 우선합니다. 어떤 백엔드든 요청된 ID를 정확히(또는 정확한 항목의 날짜 스냅샷으로) 나열하면 그 백엔드만 후보가 되며, 패턴으로만 일치하는 백엔드는 후보가 아닙니다.
  • 어떤 백엔드도 ID를 정확히 나열하지 않으면, 일치하는 패턴을 가진 모든 백엔드가 후보입니다. 같은 정확한 ID를 나열한 두 백엔드가 트래픽을 나누는 것과 같습니다.
  • 두 백엔드의 패턴이 겹치면(예: gpt-*와 gpt-5*) 설정은 로드되지만, 라우터가 로드 시 경고를 기록하고 continuum-router config validate가 backends.<name>.models에 보고합니다. 특정 백엔드에 고정하려면 그 백엔드에 ID를 정확히 나열하세요.
  • **를 포함한 항목과 빈 항목은 로드 오류입니다.
  • 키별 allowed_models 목록은 이전처럼 요청된 ID와 비교되므로, 패턴이 키가 호출할 수 있는 범위를 넓히지 않습니다.
  • Continuum Hub 백엔드 동기화는 이런 백엔드를 미리보기 전용(지원하지 않는 필드 models)으로 내보냅니다. Hub 프로필의 allowed_models는 패턴이 아니라 모델 ID를 담기 때문입니다.

데이터 상주 태그

region_name은 이 백엔드의 추론이 실행되는 위치를 표시하는 선택적 운영자 정의 태그입니다. 추론이 아니라 라우팅 태그이므로, 라우터는 운영자가 작성한 문자열을 비교하며 URL, 공급자 계정, AWS 리전 식별자에서 리전을 끌어내지 않습니다. 태그는 [A-Za-z0-9._-]로 이루어진 1자 이상 64자 이하이며 정확히 일치하고 대소문자를 구분하며, 잘못된 값은 설정 로드 실패로 이어집니다.

api_keys[].allowed_regions가 비어 있지 않은 API 키는 태그가 그 목록에 있는 백엔드로만 라우팅할 수 있습니다. region_name이 없는 백엔드는 리전 미상이며 그런 키의 조건을 절대 만족하지 않습니다. 키가 allowed_backends도 설정하면 두 목록이 교집합이 되고, 교집합이 비면 요청한 리전 밖으로 라우팅하는 대신 403 Forbidden으로 fail closed합니다. 이 규칙은 폴백 홉과 헤지를 포함한 모든 디스패치 경로에 적용됩니다. 키별 데이터 상주 제어를 참고하세요.

리전 제한 키가 도달하지 않아야 하는 백엔드에서는 이 필드를 생략하세요. 엔드포인트 URL을 템플릿으로 만드는 Bedrock 전용 region 필드와는 무관합니다.

- name: openai-eu
  type: openai
  region_name: eu
  api_key: "${OPENAI_EU_API_KEY}"
  models: ["gpt-5.6-terra"]

엔드포인트 URL 규칙

백엔드 url은 전체 요청 URL이 아니라 기본 URL입니다. 라우터는 각 요청 경로(/v1/chat/completions 등)를 이 값 뒤에 이어 붙이므로, 경로 뒤에 붙은 내용은 백엔드로 가는 모든 요청을 조용히 망가뜨립니다. 그래서 백엔드 URL이 라우터에 들어오는 모든 경로(config.yaml, Admin 백엔드 API, 허브 관리 백엔드 동기화, 임시 백엔드 프로브)에 하나의 엔드포인트 규칙이 적용됩니다.

  • 값은 절대 URL로 파싱되어야 합니다.
  • 자격 증명(user:password@)을 포함할 수 없습니다. backends[].url은 설정 마스커가 의도적으로 가리지 않는 필드라서, 여기에 넣은 자격 증명은 GET /admin/config/full이 평문으로 반환하고 WebUI가 그대로 렌더링합니다. 반면 api_key는 마스킹됩니다.
  • 쿼리 문자열이나 프래그먼트를 포함할 수 없습니다. 끝에 붙은 빈 ?나 #도 마찬가지입니다.
  • 스킴은 라우터가 실제로 접속할 수 있는 전송(http://, https://, unix://)이어야 합니다.

이 규칙은 시작, 핫 리로드, Admin 백엔드 API, 허브 설정 동기화, continuum-router config validate(위반을 오류로 보고하고 0이 아닌 코드로 종료) 등 모든 설정 입력 경로에서 동일하게 강제됩니다. 오류 메시지는 백엔드 이름과 문제 속성만 명시하고 URL 내용은 일절 반향하지 않으므로, URL에 잘못 포함된 자격 증명이 로그나 오류 화면으로 유출되지 않습니다. engine_stats.metrics_url에도 동일한 기본 검증과 추가 제약(HTTP(S) 스킴만 허용, engine_stats.allow_external_metrics_url이 없으면 백엔드 URL과 동일한 호스트여야 함, HTTPS에서 HTTP로의 다운그레이드 금지)이 적용됩니다.

백엔드 URL의 Basic 인증

자격 증명을 검사하는 역방향 프록시 뒤의 자체 호스팅 엔진에서는 자격 증명을 api_key에 저장하고 auth.type: basic을 지정합니다.

- name: local-vllm
  type: vllm
  url: "http://10.0.0.5:8000/v1"
  api_key: "${VLLM_BASIC_CREDENTIAL}"   # "svc:hunter2"
  auth:
    type: basic

라우터는 와이어로 Authorization: Basic base64(username:password)를 전송하면서 설정 마스커가 가리는 필드에 자격 증명을 보관합니다.

auth.type: basic은 자체 호스팅 엔진 타입과 continuumrouter에서 허용됩니다. vllm, sglang, ollama, lmstudio, llamacpp, mlxcel, generic, continuumrouter입니다. api_key는 username:password 형태여야 하고 RFC 7617에 따라 첫 번째 콜론에서 나뉘므로 비밀번호 안에 콜론이 있어도 됩니다. 클라우드 제공자는 각자의 인증 스킴을 쓰므로 그쪽에 basic을 지정하면 조용히 무시되지 않고 설정 오류가 됩니다.

설정된 인증 스킴은 내부 Bearer 생성자 가시성 제한과 소스 코드 감사 테스트(tests/authorization_scheme_audit_test.rs)를 통해 자격 증명을 전송하는 모든 호출 지점에서 강제됩니다.

  • 적용 경로 (auth.type: basic 및 type: continuumrouter): type: continuumrouter는 원격 Continuum Router나 Backend.AI GO 인스턴스에 연결하며, 로컬 엔진과 마찬가지로 Basic 인증을 요구하는 역방향 프록시 뒤에 놓일 수 있습니다. 설정된 스킴은 자격 증명을 전송하는 모든 경로에 적용됩니다.
    • 모델 탐색 및 Anthropic 인그레스: Unix 소켓 모델 탐색 경로, Anthropic API 요청을 OpenAI 호환 엔진으로 변환하는 /anthropic/v1/messages 경로(HTTP 및 Unix 소켓 모두), 그리고 /anthropic/v1/messages/count_tokens.
    • 스트리밍 채팅 완성: 초기 디스패치, 스트림 중간 폴백 홉, 자동 백엔드 선택, Unix 소켓 스트리밍 전송. 스트림 중간 홉은 홉마다 스킴을 다시 판정하므로 Bearer 기본 백엔드에서 basic 예비 백엔드로 페일오버할 수 있습니다.
    • 기타 추론 및 제어 경로: OpenAI 와이어 백엔드용 /v1/responses 변환 경로(ResponsesApiStrategy::ConvertToChatCompletions, 비스트리밍 및 스트리밍), ACP session/prompt 디스패치, 기동 시 연결 프리워밍, 허브가 지시하는 합성 프로브, 실시간 WebSocket 업그레이드, 그리고 스트리밍 이미지 생성·변형·편집 경로(백엔드 이름이나 URL에 openai가 포함된 백엔드를 모두 허용하므로 auth.type: basic이 설정된 generic 백엔드도 포함됨).
  • 스트리밍 경로의 OAuth (auth.type: oauth): 등록된 OAuth 전략은 비스트리밍 경로와 동일하게 네 가지 스트리밍 채팅 완성 빌더 전체(초기 디스패치, 스트림 중간 폴백 홉, 자동 백엔드 선택, Unix 소켓 스트리밍 전송)에 적용됩니다.
    • 만료가 임박한 토큰은 자동으로 갱신되며, 전략의 Authorization 헤더와 공급자별 추가 헤더(ChatGPT 구독 엔드포인트의 모든 요청에 필요한 originator 및 Codex User-Agent)가 함께 전송됩니다.
    • 스트림 중간 폴백 홉은 스트림 시작 시점의 토큰을 재사용하지 않고 홉마다 인증 전략을 다시 조회하므로, 토큰 회전, 핫 리로드, 공급자 간 폴백이 다음 홉에 즉시 반영됩니다.
    • auth.type: oauth가 선언되었으나 토큰 저장소 누락·손상으로 전략이 로드되지 않은 경우에는 Authorization 헤더를 보내지 않으며 정적 api_key로 대체하지도 않습니다. ChatGPT 구독 백엔드가 서빙하는 모델에 대한 요청은 해당 백엔드에 /chat/completions 엔드포인트가 없으므로 /v1/responses로 변환됩니다.
  • 공급자 전용 스킴 (service_account 및 sigv4): 각각을 소유한 백엔드 타입(gemini의 service_account, bedrock의 sigv4)에서만 동작합니다. 다른 타입에 선언하면 시작 로그와 continuum-router config validate가 경고를 출력하며, 해당 백엔드는 정적 api_key를 Bearer로 전송하거나 키가 없으면 Authorization 헤더 없이 요청합니다.

type: generic도 설정된 자격 증명을 싣습니다. 맞는 전용 타입이 있다면 해당 엔진의 상태 검사 계약과 엔진 통계 어댑터를 함께 얻을 수 있으므로 전용 타입을 지정하는 편이 권장되지만 인증 자체에는 generic으로도 충분합니다.

파싱되지 않는 URL은 하드 로드 오류이고 타입이 지정된 백엔드의 빈 url은 해당 타입의 기본 URL을 사용한다는 뜻입니다.

안정적인 백엔드 식별자

backend_id는 운영자가 직접 부여하는 선택적 백엔드 식별자입니다. 컨트롤 플레인을 켰을 때만 의미가 있습니다. 라우터는 이 값을 하트비트 인벤토리로 Continuum Hub에 보고하고 각 요청을 실제로 처리한 백엔드의 사용량 레코드에 찍습니다. Hub는 이를 근거로 백엔드 단위 코스트 센터에 사용량을 청구합니다.

name으로는 이 일을 할 수 없어서 별도 필드가 필요합니다. name은 핫 리로드 때 언제든 바꿀 수 있는 표시용 레이블이고 (provider, model) 쌍은 백엔드가 아닙니다. 여러 백엔드가 같은 쌍을 제공할 수 있기 때문입니다. 둘 중 어느 쪽에 비용을 귀속시켜도 서로 다른 두 비용 주체가 조용히 합쳐지거나, 이름을 바꾸는 순간 하나의 비용 주체가 둘로 갈라집니다. 청구가 끝난 뒤에는 어느 쪽도 눈에 띄지 않습니다.

그래서 라우터는 이 값을 절대 유도하지 않습니다. name, url, type, 제공하는 provider/model 쌍, 목록상의 위치, 가변 필드의 해시 중 무엇으로도 합성하지 않습니다. 운영자가 부여하거나, 아니면 해당 백엔드는 식별자를 보고하지 않습니다.

backends:
  - name: "openai-primary"
    backend_id: "openai-eu-1"
    type: "openai"
    url: "https://api.openai.com/v1"

규칙

항목 값
선택 사항 생략하면 해당 백엔드는 식별자를 보고하지 않습니다. 오류가 아니라 정상 상태이며 사용량은 provider/model 단위로 귀속됩니다.
길이 1~128자.
문자 집합 [A-Za-z0-9._-] (ASCII 영문자, 숫자, ., _, -). 공백, /, :, ,, 제어 문자, 비 ASCII는 거부합니다. 이 값은 Hub가 바이트 단위로 대조하고 정규화하지 않는 불투명한 키로 전달되기 때문입니다.
고유성 한 라우터의 백엔드 안에서 고유해야 합니다. 두 백엔드가 같은 식별자를 쓰면 하나의 비용 주체로 합쳐지므로 로드 시점, config validate, Admin API, 핫 리로드 모두에서 거부됩니다.
범위 플릿 전체가 아니라 라우터 단위입니다. Hub가 인증된 라우터 단위로 식별자를 구분하므로 두 라우터가 각자의 로컬 백엔드를 모두 primary라고 불러도 충돌하지 않습니다.

안정성

이 값은 부팅할 때 새로 만들어내지 않고 설정 파일에서 읽으므로 재시작해도 그대로이고 별도 필드이므로 이름을 바꿔도 그대로입니다.

# 초기 표시 이름: "openai-eu-1" 비용 주체로 청구
backends:
  - name: "openai-primary"
    backend_id: "openai-eu-1"

---

# 변경된 표시 이름: 여전히 "openai-eu-1"로 청구
backends:
  - name: "openai-frankfurt"
    backend_id: "openai-eu-1"

중복되거나 형식이 잘못된 식별자가 들어온 핫 리로드는 통째로 거부되고 실행 중인 라우터는 식별자 맵이 일부만 적용된 상태로 넘어가지 않고 마지막 정상 설정으로 계속 서비스합니다.

무엇이 보고되는가

식별자는 백엔드로 요청을 보내는 시점에 확정됩니다. 따라서 두 번째 백엔드로 폴백한 요청은 처음 선택된 백엔드가 아니라 실제로 응답을 만든 백엔드에 귀속됩니다. 프로바이더 배치는 작업을 제출할 때 확정한 식별자를 그대로 들고 있으므로, 몇 시간 뒤 완료되어도 그 배치를 실행한 백엔드를 정확히 지목합니다. 라우터 로컬 캐시가 처리한 응답은 백엔드로 나간 적이 없으므로 식별자를 보고하지 않습니다. 식별자가 없을 때 라우터가 표시용 이름으로 대신 채우는 일은 없습니다.

하트비트 인벤토리도 같은 식별자를 보고합니다. Hub는 자체 백엔드 인벤토리를 갖고 있지 않으므로, 운영자가 코스트 센터에 배정할 수 있는 식별자 목록을 확인하는 경로가 바로 이것입니다.

내부(인프라 전용) 백엔드

백엔드에 internal: true를 설정하면 해당 백엔드의 모델을 사용자 노출 표면에서 제외하면서도 라우터 내부 트래픽(예: 가드레일 분류기, 내부 요약기, 임베딩 모델)에는 계속 제공할 수 있습니다. 기본값은 false(공개)입니다.

백엔드가 내부(internal)일 때:

  • 모델이 GET /v1/models(그리고 /v1/models/extended, 단일 모델 /v1/models/{id}, Anthropic /anthropic/v1/models 목록 변형)에서 제외됩니다.
  • 내부 백엔드에서만 제공되는 모델로의 사용자 요청은 model-not-found로 거부됩니다.
  • 라우터 내부 호출자는 영향을 받지 않습니다: 가드레일 프로바이더가 backend: 참조로 해당 백엔드를 이름으로 해석할 수 있고 관리자 표면에서는 계속 표시됩니다.

내부 백엔드와 공개 백엔드가 동시에 제공하는 모델은 공개 백엔드를 통해 계속 노출되고 라우팅되며 목록에 내부 백엔드 이름이 노출되지 않습니다.

backends:
  - name: "guard-classifier"
    type: "vllm"
    url: "http://localhost:8001"
    internal: true
    models: ["llama-guard-3"]

비활성 백엔드

백엔드에 enabled: false를 설정하면 설정은 유지하되 서비스에서 제외합니다. 기본값은 true입니다.

백엔드가 비활성 상태일 때:

  • 백엔드 풀에 여전히 구성되고 헬스 체크와 후보 프로브가 계속 동작하며 관리자와 인벤토리 표면에도 계속 표시됩니다.
  • 관리자 검색 엔드포인트(POST /admin/backends/{name}/models/discover, POST /admin/backends/probe)를 통해 요청 시점에 실시간으로 검색할 수 있습니다. 반면 GET /v1/models 뒤의 주기적 모델 목록 집계는 전원이 꺼진 대기 백엔드의 연결 타임아웃으로 전체 집계가 지연되지 않도록 이 백엔드에 접속하지 않습니다.
  • 사용자 트래픽과 라우터 내부 트래픽을 전혀 받지 않습니다(내부 호출자에게는 여전히 서빙하는 internal: true와 다른 점입니다).
  • OpenAI와 Anthropic 모델 목록에 모델을 제공하지 않습니다. 비활성 백엔드와 활성 백엔드가 같은 모델을 서빙하면 그 모델은 활성 백엔드를 통해 계속 보이고 라우팅됩니다.

weight는 활성화 여부와 무관하게 1..=1000 범위를 유지하므로 가중치 0이 두 번째 의미를 얻지 않습니다. enabled 전환은 핫 리로드로 즉시 반영됩니다.

backends:
  - name: "standby-vllm"
    type: "vllm"
    url: "http://localhost:8002"
    enabled: false
    models: ["llama-4-70b"]

HTTP 프로토콜 버전

http_version은 라우터가 백엔드 하나와 통신할 때 사용하는 HTTP 프로토콜을 정합니다. 값은 auto(기본값), http1, http2 중 하나입니다.

값 프로토콜 사용 시점
auto TLS ALPN이 결정합니다. https:// 서버가 h2를 제공하면 HTTP/2, 그렇지 않으면 HTTP/1.1을 사용합니다. 평문 http://는 항상 HTTP/1.1입니다. 아래 경우에 해당하지 않는 모든 백엔드.
http1 HTTP/1.1만 사용합니다(ALPN은 http/1.1만 제안). 진행 중인 요청마다 풀링된 연결을 하나씩 사용합니다. HTTP/2 인그레스나 로드 밸런서 뒤에 있으면서 그 스트림 제한이 라우터가 보내는 동시성보다 낮은 백엔드(주로 장시간 스트리밍 생성).
http2 HTTP/2만 사용합니다. https://에서는 ALPN h2, http://에서는 사전 지식(h2c)을 사용합니다. h2c를 지원하며 멀티플렉싱을 원하는 평문 백엔드. HTTP/2를 지원하지 않는 서버에서는 모든 요청이 실패합니다.

http1이 존재하는 이유

HTTP/2에서는 라우터가 한 호스트로 가는 모든 요청을 하나의 연결에 멀티플렉싱하며 첫 번째 연결이 서버의 SETTINGS_MAX_CONCURRENT_STREAMS에 도달해도 두 번째 연결을 열지 않습니다. 그러면 N+1번째 요청은 이전 스트림이 닫힐 때까지 HTTP 클라이언트 내부에서 대기합니다. 128개 스트림을 광고하는 서버가 수 분씩 스트리밍되는 생성 요청 앞에 있으면 엔진이 유휴 상태여도 진행 중 요청이 128개에 이르는 순간 라우터의 새 요청을 받지 않게 됩니다.

http_version: http1을 사용하면 풀이 동시 요청마다 연결을 하나씩 열므로 스트림 제한이 적용되지 않습니다. 대가로 새 연결마다 TCP 및 TLS 핸드셰이크가 한 번씩 발생합니다. 유휴 연결은 server.http_client.pool_idle_timeout과 pool_max_idle_per_host에 따라 재사용을 위해 계속 유지됩니다.

backends:
  - name: "dynamo-frontend"
    type: "generic"
    url: "https://dynamo.example.internal"   # nginx ingress that negotiates h2
    http_version: http1
    models: ["solar-pro-4"]

기존 연결이 가득 찬 뒤 호스트당 HTTP/2 연결을 더 여는 방식은 제공하지 않습니다. 라우터의 HTTP 클라이언트는 호스트당 HTTP/2 연결을 하나만 풀링하고 스트림을 인식하는 두 번째 연결을 위한 훅이 없어서 라우터가 직접 관리하는 연결 풀이 필요하기 때문입니다. HTTP/1.1은 그런 풀 없이도 제한을 없애 줍니다.

적용 범위

  • 적용 대상 경로: 라우터가 HTTP(S)로 백엔드에 프록시하는 모든 요청에 적용됩니다. chat completions(스트리밍·비스트리밍, 재시도, 폴백 홉, 미드스트림 폴백 포함이며 각각 대상 백엔드의 설정을 따름), completions, embeddings, Responses API(패스스루, compact, Chat Completions·Anthropic·Gemini 변환), Anthropic Messages 인그레스 및 count_tokens, 이미지 생성·편집·변형, ACP 프롬프트가 포함됩니다.
  • 클라이언트 풀링: 모든 http1 백엔드는 하나의 클라이언트를, 모든 http2 백엔드는 또 다른 클라이언트를 공유하며 두 클라이언트 모두 기본 공유 클라이언트와 동일한 server.http_client 튜닝과 타임아웃으로 생성됩니다. auto 백엔드는 기본 공유 클라이언트를 그대로 사용합니다.
  • 제외되는 내부 경로: 헬스 체크, 모델 디스커버리, 연결 프리워밍, 엔진 통계 스크레이프, 프로바이더 배치 호출, 네이티브 Gemini 스트리밍은 전용 클라이언트를 사용하므로 영향을 받지 않습니다. 자체 reqwest::Client를 주입하는 라이브러리 호출자는 auto 백엔드에서 해당 클라이언트를 유지하며, http1과 http2 백엔드는 라우터가 생성한 클라이언트를 사용합니다(기동 시 warn 로그 1회 기록).
  • TLS 초기화 실패 시 폴백: 기동 시 http1 또는 http2 클라이언트 생성에 실패하면 라우터는 error 로그를 한 번 남기고 프로세스 재시작 전까지 공유 auto 클라이언트로 폴백합니다.

검증과 핫 리로드

  • auto, http1, http2 이외의 값은 로드 오류입니다.
  • unix: 백엔드에 http2를 지정하면 로드 오류입니다. Unix 소켓 전송은 HTTP/1.1만 지원합니다. http1과 auto는 로드되며 아무것도 바꾸지 않습니다.
  • 기본값은 다시 기록되지 않으므로, 수정하지 않은 설정은 그대로 왕복(round-trip)됩니다.
  • 선택은 각 요청이 라우팅되는 시점의 설정에서 백엔드 값을 읽으므로, 핫 리로드, Admin API 백엔드 수정, 설정 동기화 변경은 다음 요청부터 적용됩니다. 이미 진행 중인 요청은 시작한 연결에서 끝까지 처리됩니다(점진적 클래스).
  • http_version만 수정해도 리로드 로그와 요약에 백엔드 변경으로 보고됩니다. 백엔드는 통계와 진행 중 요청 카운터를 유지한 채 풀에 남습니다.
  • Continuum Hub v1 백엔드 프로필에는 프로토콜 필드가 없으므로, auto가 아닌 경우 로컬 내보내기는 http_version을 지원되지 않는 필드로 나열하며 해당 항목은 미리보기 전용이 됩니다.

스트림 제한 정체 확인

라우터는 클라이언트 내부의 HTTP/2 스트림 슬롯 대기를 직접 관측할 수 없으므로, 자신의 쪽에서 보이는 것을 보고합니다.

  • continuum_backend_http_in_flight{backend, http_version}은 설정된 http_version으로 레이블링된 백엔드의 진행 중 요청 수입니다. auto 또는 http2 백엔드에서 값이 100, 128, 250 같은 딱 떨어지는 수에서 평평해지면 그것이 서버의 스트림 제한입니다.
  • continuum_backend_response_wait_seconds{backend, http_version, outcome}은 요청을 HTTP 클라이언트에 넘긴 시점부터 응답 헤더가 도착하거나(outcome="response"), 전송 오류로 끝나거나(error), 그 전에 요청이 버려질 때(cancelled, 예: 대기 중 클라이언트 연결 종료)까지의 시간입니다. 가득 찬 HTTP/2 연결 뒤에 대기한 요청은 상위 버킷에 나타납니다.
  • http2 백엔드 또는 https:// URL의 auto 백엔드의 진행 중 요청이 100개에 도달하면 백엔드당 최대 1분에 한 번 warn 로그가 남으며 해결책으로 http_version: http1을 언급합니다. http://의 auto 백엔드는 항상 HTTP/1.1을 쓰므로 경고하지 않습니다. debug 레벨에서는 모든 디스패치가 백엔드, 해당 http_version, 진행 중 요청 수를 로그로 남깁니다.

쿼리는 메트릭을 참고하세요.

네이티브 Responses API

responses_api는 /v1/responses 요청이 백엔드 하나에 도달하는 방식을 고릅니다. auto(기본값) 또는 native입니다.

auto에서는 Responses API 라우팅 전략에 설명한 대로 백엔드 유형이 결정합니다. openai와 azure는 요청을 백엔드 자체의 /v1/responses로 패스스루하고 anthropic, bedrock, gemini는 각자의 네이티브 API로 변환하며 나머지 유형은 모두 Chat Completions로 변환합니다. Responses API를 직접 구현한 셀프 호스팅 서버에 요청을 그대로 보내려면 type: openai로 선언하는 수밖에 없었는데 그러면 상용 OpenAI 프로바이더로도 취급됩니다(owned_by, 자격 증명 처리 등 유형별 동작).

native는 OpenAI 와이어 형식을 쓰는 모든 유형에서 패스스루를 선택하며 유형이 결정하는 다른 동작은 바꾸지 않습니다. 세션이 도구 기록과 파일을 보관하는 에이전트 런타임처럼, 라우터가 재구성한 대화 기록으로는 대신할 수 없는 상태를 백엔드가 소유할 때 사용합니다.

backends:
  - name: agent-runtime
    type: generic
    url: https://agents.example.internal
    responses_api: native
    api_key: "${AGENT_RUNTIME_KEY}"
    models:
      - research-agent

native 백엔드에서는 다음과 같이 동작합니다.

  • 스트리밍과 비스트리밍 POST /v1/responses가 previous_response_id, conversation, 라우터가 모델링하지 않는 필드와 입력 항목(예: mcp_approval_response)을 포함해 변경 없이 <url>/v1/responses로 전달됩니다. 라우터가 모델링하지 않는 출력 항목(예: mcp_approval_request)을 포함한 백엔드의 출력은 그대로 클라이언트에 전달됩니다.
  • POST /v1/responses/compact는 <url>/v1/responses/compact로 전달됩니다.
  • background: true는 전달되며 결과는 라우터를 거쳐 가져올 때 미터링되고 가드레일이 있으면 검사됩니다. 라우터를 거쳐 가져오지 않은 결과에 대한 제한 사항을 포함해 백그라운드 응답을 참고하세요.
  • 이 백엔드가 처리한 응답에 대한 /v1/responses/{id}의 GET, DELETE, POST .../cancel은 저장된 응답 조회, 삭제 또는 취소에 설명한 검사를 거쳐 이 백엔드로 전달됩니다.
  • 정적 자격 증명은 백엔드에 설정된 방식으로 보냅니다. api_key는 Bearer로, auth.type: basic이면 Basic으로 보냅니다.
  • 백엔드가 상태를 보관하므로 라우터는 previous_response_id에 대한 기록 재구성, item_reference 항목 해석, 저장된 기록에 대한 가드레일 입력 단계를 실행하지 않습니다. 다른 패스스루 백엔드와 마찬가지로 가드레일 단계는 요청과 응답을 계속 검사합니다.

버릴 수 있는 권고 필드

변환 백엔드는 기본적으로 네이티브 전용 /v1/responses 필드를 모두 거부합니다. responses_drop은 클라이언트가 잃어도 되는 권고 필드를 받아들이게 합니다.

backends:
  - name: vllm-b300
    type: vllm
    url: http://10.0.0.21:8000
    responses_api: auto
    responses_drop: [include, prompt_cache_key, safety_identifier, reasoning.summary]
    models:
      - qwen3-coder
  • 버릴 수 있는 필드: include(모든 요청 값이 허용 목록에 있을 때만, 현재는 reasoning.encrypted_content뿐이며, 빈 목록은 아무것도 요청하지 않으므로 권고 필드), prompt_cache_key, safety_identifier, reasoning.summary. reasoning.summary는 요약만 제거하므로 reasoning.mode와 reasoning.context는 계속 거부됩니다.
  • 의미 필드: text, conversation, prompt_cache_options, prompt_cache_retention, multi_agent, context_management, max_tool_calls, reasoning.mode, reasoning.context, 그리고 모든 도구, 프로그래매틱 호출, 입력 항목 거부. 목록 설정과 무관하게 항상 400 unsupported_request_parameter로 응답합니다. 허용 목록 밖의 include 값도 의미 필드입니다.
  • 기본값: 빈 목록입니다. 선택한 백엔드에 responses_drop이 없으면 모든 응답이 이전과 바이트 단위로 같은 거부입니다.
  • 백엔드별 적용: 선택한 백엔드의 목록만 적용됩니다. responses_api: native는 목록을 완전히 무시하며 아무것도 버리지 않습니다.
  • 로드 오류: 알 수 없는 이름이나 의미 필드 이름은 설정 로드에 실패하며, 오류 메시지가 해당 필드 이름을 알려 줍니다.
  • 고지: 성공 응답은 버린 필드 이름만 정렬하고 중복을 제거해 x-continuum-dropped-fields에 담습니다(예: include, prompt_cache_key). 이 헤더는 CORS로 노출되며, 클라이언트나 업스트림의 값을 이어받지 않고, 응답 캐시 적중에서도 재생됩니다.
  • 메트릭: responses_drop_fields_total{field}는 처리된 요청마다 버린 필드 하나당 한 번 증가합니다. 캐시 적중은 해당 요청에서 필드를 버리지 않았으므로 헤더만 재생하고 카운터를 늘리지 않습니다.
  • 라우터 캐시: prompt_cache_key를 버려도 라우터의 프롬프트, 접두사, 시맨틱 캐시 키는 바뀌지 않습니다. 키는 버리기 전 요청에서 계산됩니다. 동의한 요청은 응답 캐시 대상 자격을 유지합니다.
  • 핫 리로드: 목록은 각 요청이 라우팅되는 설정에서 읽으므로 다음 요청부터 적용되며, 편집은 백엔드 변경으로 보고됩니다.

previous_response_id는 이 목록에 포함되지 않습니다. 변환은 이 필드를 버리는 대신 기록을 재구성해 해석합니다.

검증과 핫 리로드

  • auto나 native가 아닌 값은 로드 오류입니다.
  • anthropic, bedrock, gemini에서 native는 로드 오류입니다. 이 유형에는 패스스루할 /v1/responses 엔드포인트가 없습니다.
  • unix: 백엔드 URL에서 native는 로드 오류입니다. Responses 패스스루는 HTTP(S)로만 디스패치합니다.
  • 기본값은 다시 기록되지 않으므로 손대지 않은 설정은 그대로 왕복합니다.
  • 전략은 각 요청이 라우팅되는 설정에서 읽으므로 핫 리로드, Admin API 백엔드 편집(생성과 수정의 responses_api), config-sync 변경은 다음 요청부터 적용됩니다(즉시 클래스). 이미 처리된 응답은 바인딩을 유지하며 그 백엔드가 더 이상 패스스루로 제공되지 않으면 해당 응답에 대한 GET, DELETE, cancel은 404를 반환합니다.
  • responses_api만 바꾼 편집은 리로드 로그와 요약에 백엔드 변경으로 보고됩니다.
  • Continuum Hub v1 백엔드 프로필에는 이 필드가 없으므로, auto가 아니면 로컬 내보내기에서 responses_api를 지원되지 않는 필드로 표시하고 해당 항목은 미리보기 전용이 됩니다.

재전송 정책

replay는 라우터가 이미 백엔드로 디스패치한 요청을 다시 보내도 되는지를 정합니다. 값은 enabled(기본값) 또는 disabled입니다.

라우터는 여러 곳에서 스스로 요청을 다시 보냅니다. 재시도 루프는 실패한 시도를 같은 백엔드나 다른 백엔드에 반복하고 폴백 체인은 요청을 다른 모델로 옮기며 스트리밍 릴레이는 스트림 도중에 백엔드를 바꾸고 fallback.quality_check는 체인의 다음 항목에 다시 물을 수 있습니다. 추론 백엔드라면 반복 요청은 토큰 비용만 듭니다. 그러나 도구를 실행하는 에이전트 런타임처럼 요청에 부수 효과가 있는 백엔드라면 같은 작업이 두 번 수행될 수 있습니다. 백엔드가 요청을 받아들인 뒤 연결이 끊기면 라우터는 작업이 실행되었는지 알 수 없습니다.

backends:
  - name: "agent-runtime"
    type: "generic"
    url: "http://agent.internal:8000"
    models: ["agent:probe"]
    replay: disabled

disabled가 보장하는 것

백엔드로의 디스패치가 요청을 전달했을 가능성이 생긴 뒤에는 어떤 실패든 그 실패로 요청이 끝납니다. 연결 끊김, 타임아웃, 503 같은 재시도 가능한 상태 코드 모두 마찬가지입니다. 이후 라우터는 그 호출을 어떤 백엔드에도 다시 보내지 않습니다.

  • retry.max_attempts와 retryable_errors 설정과 상관없이 같은 백엔드나 같은 모델을 제공하는 다른 백엔드로 재시도하지 않습니다.
  • trigger_conditions 설정과 상관없이 fallback.fallback_chains의 대상으로 넘어가지 않습니다.
  • 스트리밍 요청에서는 스트림 도중 전환도, 스트림 시작 전 체인 순회도 하지 않습니다.
  • 이 백엔드가 응답을 제공했다면 품질 검사로 인한 홉을 하지 않습니다. 판정 자체는 실행되며 그 결과는 로그로 남고 skipped로 집계됩니다.
  • 채팅 모델에서 에뮬레이션된 /v1/decisions 요청은 실패하거나 라우터가 사용할 수 없는 응답을 받은 뒤에도 홉하지 않습니다.
  • 분리형 서빙(disaggregated serving)의 통합 폴백으로 다시 디스패치하지 않습니다.

백엔드가 응답했다면 클라이언트는 백엔드가 보낸 상태 코드와 본문을 그대로 받고 응답이 없었다면 라우터의 전송 오류를 받습니다. 스트리밍 요청에서는 한 번의 디스패치가 업스트림 5xx를 오류를 담은 200 SSE 스트림으로 전달합니다. 체인이 설정되어 있었다면 순회를 이어 가다 HTTP 오류 상태로 응답했을 경우입니다.

요청 바이트가 전송되기 전에 발생한 실패는 전송 계층이 미전송 여부를 증명할 수 있는지에 따라 다음과 같이 처리됩니다.

  • HTTP(S)의 전송 전 연결 실패: Chat Completions, Completions, 임베딩, 이미지 생성 경로에서 http(s):// 백엔드로의 TCP 또는 TLS 연결 자체가 실패하면(연결 거부, 도달 불가, 연결 타임아웃) 백엔드에 아무것도 전달되지 않았으므로 일반 백엔드와 동일하게 재시도하고 폴백합니다. 디스패치 전 라우터 측 거부(서킷 오픈, 폴백 다이얼 한도 포화, 백엔드 속도 제한)도 마찬가지로 영향을 받지 않습니다.
  • 전송 전 구분이 없는 전송 경로: unix: 소켓 전송과 Bedrock runtime/converse 타입 디스패치는 연결 단계 오류와 전송 후 실패를 구분하지 않으므로, 모든 실패를 전달되었을 가능성이 있는 것으로 간주하여 즉시 중단합니다.
  • 스트리밍 및 공급자 형식 인그레스: 처음부터 replay: disabled 백엔드로 선택된 스트리밍 요청은 폴백 체인 순회 없이 한 번만 디스패치되며, 스트리밍 폴백 체인이 replay: disabled 백엔드로 홉한 경우에는 TCP/TLS 연결 자체가 실패하지 않는 한 거기서 멈춥니다. 공급자 형식 인그레스(/v1/responses, /anthropic/v1/messages)는 replay: disabled 백엔드로 디스패치를 확정한 뒤에는 연결 거부 시에도 다른 백엔드로 넘어가지 않습니다.

또한 각 클라이언트 호출은 다른 요청의 실행 결과를 공유하지 않고 개별적으로 전달됩니다.

  • 진행 중 요청 병합(coalescing) 제외: replay: disabled 백엔드가 서빙할 수 있는 모델에 대해서는 동일한 동시 비스트리밍 요청을 병합하지 않습니다. (일반적으로는 temperature: 0을 명시한 /v1/chat/completions 및 /v1/completions, /v1/embeddings, /v1/rerank, /embed_sparse, /v1/models에서 동일한 디스패치 본문과 중복 제거 허용 목록 헤더를 가진 동시 요청이 선행 요청의 업스트림 실행을 공유합니다.)
  • 응답 캐시 제외: response_cache는 모든 캐시 경로(정확 일치, 접두사, 시맨틱, 스트리밍 재생, Responses, Anthropic)에서 replay: disabled 백엔드로 라우팅된 응답을 저장하거나 제공하지 않습니다.
  • 합성 프로브 제외: Continuum Hub 합성 프로브는 의도치 않은 부수 효과를 막기 위해 replay: disabled 백엔드를 대상으로 삼지 않습니다.

바뀌지 않는 것

  • 백엔드는 여전히 다른 백엔드와 똑같이 선택, 부하 분산, 헬스 체크, 서킷 브레이커의 대상입니다. 일반 백엔드로 라우팅된 요청이 실패해 replay: disabled 백엔드로 폴백할 수는 있으며 부수 효과가 있는 백엔드에 도달한 바로 그 시도가 마지막이 됩니다.
  • auth.type: oauth 자격 증명에 401로 응답한 백엔드에는 토큰 갱신 후 요청을 한 번 더 보냅니다. 401은 백엔드가 요청 수락을 거부했다는 뜻이기 때문입니다.
  • 라우터가 전달하는 트레이스 ID(X-Request-ID, traceparent)는 호출자가 여러 요청에 재사용할 수 있는 상관 ID일 뿐 멱등성 키가 아니며 이 정책은 트레이스 ID에 의존하지 않습니다.
  • responses_api: native 백엔드에서 패스스루 POST /v1/responses(버퍼링, 스트리밍, background: true 모두)는 다른 Responses 디스패치와 마찬가지로 한 번의 시도이므로, 체인은 그 실패에서 멈춥니다. 대기열에 들어간 백그라운드 응답은 성공이며 라우터가 다시 생성하지 않습니다. 수명 주기 호출(GET, DELETE, POST /v1/responses/{id}/cancel)은 바인딩된 백엔드로 한 번만 전달되며 재시도나 폴백이 없습니다.
  • retry_override는 대안이 아닙니다. 라우터는 디스패치 시 이 값을 적용하지 않으며 적용하더라도 폴백 체인이나 스트리밍 릴레이는 다루지 못합니다.

검증과 핫 리로드

  • enabled나 disabled 이외의 값은 로드 오류입니다. 기본값은 다시 기록되지 않습니다.
  • 정책은 각 요청이 라우팅되는 설정에서 읽으므로 핫 리로드, Admin API 백엔드 수정(생성과 수정 모두 replay 지원), config-sync 변경은 다음 요청부터 적용됩니다. 이미 진행 중인 요청은 시작할 때의 정책을 유지합니다(immediate 클래스).
  • replay만 수정해도 리로드 로그와 요약에 백엔드 변경으로 보고됩니다.
  • Continuum Hub v1 백엔드 프로필에는 재전송 필드가 없으므로, disabled인 경우 로컬 내보내기는 replay를 지원되지 않는 필드로 나열하며 해당 항목은 미리보기 전용이 됩니다. 이를 채택하면 재시도와 폴백이 조용히 다시 켜지기 때문입니다.
  • Hub가 관리하는 백엔드(backend_config_v1)나 AppProxy 워커가 조정하는 백엔드는 이 필드를 담을 수 없으므로 항상 enabled입니다. authoritative 백엔드 동기화 모드에서는 Hub 스냅샷이 immutable_backend_ids에 없는 모든 백엔드를 관리하며 로컬 replay: disabled 백엔드를 재전송이 켜진 백엔드로 대체할 수 있으므로, 그런 백엔드는 이 목록에 넣으세요.

요청 확장

request_extensions를 사용하면 운영자가 백엔드의 라우팅 선호도와 출처 표기(attribution)를 모든 클라이언트가 매 요청에 실어 보내는 대신 설정에서 한 번만 지정할 수 있습니다. 대표적인 사례는 OpenRouter 같은 OpenAI 호환 애그리게이터로, 라우팅 지시는 요청 본문(provider, models, transforms)에서, 출처 표기는 헤더(HTTP-Referer, X-Title)에서 읽습니다. 본문에 병합되는 고정 JSON 조각과 고정 헤더 집합이며 둘 다 설정 로드 시점에 검증합니다. 범용 요청 재작성 기능이 아니므로 템플릿이나 표현식은 없습니다.

backends:
  - name: openrouter
    type: openai
    url: https://openrouter.ai/api/v1
    api_key: ${OPENROUTER_API_KEY}
    models: ["openai/gpt-5"]
    request_extensions:
      body:
        defaults:            # 클라이언트 우선
          provider:
            sort: throughput
        overrides:           # 설정 우선
          provider:
            data_collection: deny
      headers:
        HTTP-Referer: https://backend.ai
        X-Title: Backend.AI GO

병합 규칙

본문은 request_params와 같은 두 가지 모드를 가집니다.

  • defaults는 클라이언트가 해당 키를 설정하지 않았을 때(키가 없거나 JSON null)만 값을 채웁니다. 클라이언트가 우선합니다.
  • overrides는 클라이언트가 보낸 값을 무엇이든 대체합니다. 설정이 우선합니다. provider.data_collection: deny처럼 클라이언트가 완화해서는 안 되는 정책에 사용합니다.

defaults를 먼저 적용하고 그다음 overrides를 적용합니다. 두 모드 모두 JSON 객체를 깊게 병합합니다. 같은 키에서 클라이언트 값과 설정 값이 모두 객체이면 그 안으로 내려가서 병합합니다. 그 밖의 경우는 해당 키에서 통째로 결정합니다. 배열은 원소 단위로 병합하지 않으며 타입 충돌(설정은 객체인데 클라이언트는 문자열)은 defaults에서는 클라이언트 값을 유지하고 overrides에서는 설정 값을 사용합니다.

위 예시에서 "provider": {"sort": "price", "data_collection": "allow"}를 담은 클라이언트 요청은 다음과 같이 OpenRouter에 도달합니다.

{
  "provider": {"sort": "price", "data_collection": "deny"}
}

defaults는 클라이언트가 설정한 값을 절대 대체하지 않으므로 클라이언트의 sort가 유지되고 overrides가 그 리프 하나를 소유하므로 data_collection은 대체됩니다. provider가 전혀 없는 요청은 {"sort": "throughput", "data_collection": "deny"}를 담아 업스트림에 도달합니다.

적용 범위

  • 백엔드 타입: body는 OpenAI 호환 와이어 형식을 사용하는 백엔드 타입(generic, openai, azure, vllm, sglang, tensorrt-llm, llamacpp, mlxcel, ollama, lmstudio, continuumrouter)에서만 허용되며, anthropic, gemini, bedrock에서는 로드 오류입니다. headers는 내부에서 SigV4로 서명하는 endpoint_type: runtime 또는 converse인 bedrock을 제외한 모든 백엔드 타입(기본 엔드포인트 타입인 Bedrock mantle 포함)에서 허용됩니다.
  • 엔드포인트: body 조각은 /v1/chat/completions, /v1/completions, /v1/responses, /v1/embeddings에 적용되며, OpenAI 와이어 백엔드로 변환되는 인그레스(Anthropic Messages 및 Chat Completions로 변환되는 Responses API)도 포함합니다. 모델 목록, 이미지, 오디오, 파일, rerank에는 적용되지 않습니다.
  • 헤더: 백엔드 자체 인증을 싣는 모든 요청에는 설정된 확장 헤더도 함께 전송됩니다.
    • 적용 경로: 모든 인그레스의 추론, 모델 검색(집계 카탈로그 갱신 및 기동 시 자동 검색), 헬스 프로브, 연결 프리워밍, 백엔드 타입 감지, llama.cpp /props 기능 감지, 엔진 통계 수집, Gemini 컨텍스트 캐시 관리, 프로바이더 배치 수명 주기 호출, 컨트롤 플레인 합성 프로브, 가드레일 backend: 및 스마트 라우팅 분류기 호출, 실시간 WebSocket 핸드셰이크. api_key가 설정되지 않은 백엔드도 해당 경로에서 확장 헤더를 전송합니다.
    • 우선순위 및 핫 리로드: 설정한 확장 헤더는 동일한 이름의 클라이언트 전달 헤더를 덮어쓰며, 인증 헤더 이름은 로드 시점에 거부됩니다. 블록은 매 전송 시점에 읽으므로 핫 리로드, Admin 백엔드 수정, 컨트롤 플레인 설정 동기화 변경이 다음 호출에 즉시 반영됩니다(헤더 변경 시 엔진 통계 폴링 태스크도 재시작됨).
    • Admin 프로브 및 Unix 소켓 예외: Admin 백엔드 생성 시 실행되는 타입 프로브는 후보 헤더가 검증을 통과한 경우에만 헤더를 첨부합니다. Unix 소켓(unix:) 백엔드로 가는 헬스 프로브는 자격 증명과 마찬가지로 확장 헤더를 보내지 않습니다. 헬스 프로브 상태는 백엔드 URL을 키로 관리되므로, 동일한 URL을 공유하는 두 백엔드는 한쪽 백엔드의 헤더 집합으로만 프로브됩니다.
  • 순서: body 조각은 요청 파라미터 정책, 인그레스 변환, 프로바이더별 정규화가 모두 끝난 뒤 선택된 백엔드의 최종 와이어 페이로드에 마지막으로 병합됩니다.

stable-diffusion.cpp 이미지 파라미터

stable-diffusion.cpp의 sd-server는 prompt 안의 <sd_cpp_extra_args> JSON 객체에서 생성 파라미터를 읽습니다. 해당 백엔드에서 타입이 지정된 어댑터를 명시적으로 활성화하세요. 라우터는 URL, 백엔드 이름, 모델 이름으로 엔진을 추정하지 않습니다.

backends:
  - name: stable-diffusion-cpp
    type: generic
    url: http://localhost:7860
    models: [sd-cpp-local]
    request_extensions:
      sd_cpp_image_parameters: true

어댑터는 /v1/images/generations에만 적용되며, 다음 최상위 요청 필드의 JSON 타입이 맞을 때 변환합니다.

요청 필드 sd_cpp_extra_args 경로 JSON 타입
negative_prompt negative_prompt 문자열
seed seed 정수
clip_skip clip_skip 정수
steps sample_params.sample_steps 정수
sampler sample_params.sample_method 문자열
scheduler sample_params.scheduler 문자열
cfg_scale sample_params.guidance.txt_cfg 숫자

원본 필드와 공급자별 필드는 요청 본문에 그대로 남습니다. 프롬프트의 첫 번째 sd_cpp_extra_args 블록이 유효한 객체이면 라우터가 객체를 병합합니다. 같은 리프에서는 명시적인 요청 필드가 우선하고, 그 밖의 인라인 필드는 유지됩니다.

첫 번째 블록이 잘못된 JSON이거나 닫히지 않았거나 객체가 아니면 요청을 그대로 전달하며, 새 블록을 추가하지 않습니다. 생성한 JSON에서는 <를 이스케이프하므로, negative_prompt 같은 문자열이 태그를 일찍 닫을 수 없습니다. 재시도와 폴백 홉은 원본 본문에서 시작하므로, 활성화된 백엔드에서 수정된 프롬프트가 일반 백엔드로 넘어가지 않습니다.

이 어댑터는 OpenAI 와이어 백엔드 타입에서만 사용할 수 있습니다. 정적 request_extensions.body가 이미지 요청에 적용되도록 범위를 넓히지 않으며, 확장 헤더의 적용 범위도 바꾸지 않습니다.

폴백과 재시도

확장은 디스패치 시점에 선택된 백엔드마다 적용되며 모든 재시도와 모든 폴백 홉(스트림 시작 전 및 스트림 도중 스트리밍 폴백 포함)에 적용됩니다. 다른 백엔드로 넘어가는 홉은 확장이 적용되지 않은 요청에서 시작해 그 백엔드의 확장(없으면 없음)을 싣고 이전 백엔드의 확장은 절대 싣지 않습니다. 체인에서 확장이 필요한 모든 백엔드에 블록을 설정해야 합니다.

검증

아래 규칙은 모든 설정 수집 경로(시작, 핫 리로드, Admin 설정 및 백엔드 API, 설정 가져오기, 컨트롤 플레인 동기화, continuum-router config validate, MCP 검증 도구)에서 설정 로드 시점에 강제됩니다. 오류는 backends[2].request_extensions.body.overrides.temperature처럼 정확한 경로를 명시합니다.

예약된 본문 키(defaults와 overrides의 최상위에서 대소문자를 구분하지 않고 비교):

분류 키
요청 식별과 내용 model, messages, prompt, input, instructions, suffix, prediction
전송과 세션 stream, stream_options, previous_response_id, conversation, store, background
요청 파라미터 temperature, top_p, max_tokens, max_completion_tokens, max_output_tokens, presence_penalty, frequency_penalty, top_k, min_p
도구, 출력 형태, 식별 tools, tool_choice, functions, function_call, parallel_tool_calls, response_format, guided_json, guided_regex, guided_choice, guided_grammar, structured_outputs, n, user, metadata, prompt_cache_key, safety_identifier
클라이언트가 해석하는 응답 형태 text(Responses API의 response_format), logprobs, top_logprobs, prompt_logprobs, 임베딩의 encoding_format과 dimensions, include와 include_reasoning, modalities와 audio, 레거시 Completions의 echo
라우터 관리 cache_salt, reasoning_effort, reasoning
이 백엔드에서 제거되는 키 continuumrouter를 제외한 모든 OpenAI 와이어 타입의 speed, thinking, output_config; api.openai.com의 chat_template_kwargs, thinking_budget_tokens, enable_thinking, preserve_thinking, repeat_penalty; auth.oauth.provider가 Codex 플로를 쓰는 백엔드에서 요청을 Codex /responses 형태로 변환할 때 지우는 stop, max_output_tokens, temperature, top_p, presence_penalty, frequency_penalty, user

본문 키가 예약된 이유와 설정 가능한 키는 다음과 같습니다.

  • 세션 및 출력 형태 제어 키: background(Responses)는 즉시 결과 대신 폴링이 필요한 queued 응답을 반환하며 store: true를 요구합니다. include와 include_reasoning은 출력 필드(reasoning.encrypted_content, message.output_text.logprobs)를 추가합니다. "audio"를 포함한 modalities와 audio(Chat)는 choices[].message.audio를 추가하며, echo(레거시 /v1/completions)는 프롬프트를 choices[].text 앞에 붙입니다. guided_json, guided_regex, guided_choice, guided_grammar 및 structured_outputs(vLLM 0.12+)는 response_format과 동일하게 생성 출력을 제약하며, prompt_logprobs는 프롬프트 토큰의 로그 확률을 추가합니다.
  • 호출자별 식별 및 프롬프트 키: prompt_cache_key는 호출자별 캐시 키이며(전역 고정 시 모든 호출자가 단일 버킷을 공유하게 됨), safety_identifier는 user와 같은 최종 사용자 식별자이고, prediction(Predicted Outputs)은 messages처럼 클라이언트 프롬프트 본문에 속합니다.
  • 설정 가능한 생성 및 계정 키: 샘플링 값은 타입과 범위를 검사하는 request_params에서 설정해야 합니다. 반면 stop, seed, logit_bias는 응답 스키마를 바꾸지 않고 생성만 조정하므로 request_extensions에서 백엔드별로 설정할 수 있습니다(단, stop을 제거하는 Codex OAuth 백엔드는 제외). 운영자 계정의 비용·지연 등급인 service_tier와 캐시 보존 설정인 prompt_cache_retention / prompt_cache_options도 설정 가능합니다. 허용된 최상위 키 아래의 중첩 키는 자유 형식이며 null은 조각 내 어디에서도 거부됩니다.

예약된 헤더 이름은 여러 프록시와 서버(CGI 방식 환경 등)가 _와 -를 동일하게 취급하므로 대소문자를 구분하지 않고 모든 _를 -로 정규화해 비교합니다(따라서 x_api_key도 거부되고 X-Title 옆의 X_Title도 중복으로 처리됨). 라우터와 인증 계층이 직접 관리하므로 확장 헤더로 지정할 수 없는 이름은 다음과 같습니다.

  • 표준 HTTP 및 전송 헤더: Authorization, Proxy-Authorization, Host, Content-Length, Content-Type, Content-Encoding, Transfer-Encoding, Connection, Keep-Alive, Proxy-Connection, Upgrade, TE, Trailer, Expect, Cookie, Set-Cookie, Accept, Accept-Encoding, User-Agent, 그리고 sec-websocket-으로 시작하는 모든 헤더.
  • 공급자 및 인증 헤더: x-api-key, api-key, x-goog-api-key, x-auth-token, x-access-token, anthropic-version, anthropic-beta, OpenAI-Organization, OpenAI-Project, chatgpt-account-id, originator, 그리고 x-amz- 또는 x-amzn-으로 시작하는 모든 헤더.
  • 트레이싱 및 루프 감지 헤더: X-Request-ID, X-Trace-ID, X-Correlation-ID, traceparent, tracestate, baggage, x-continuum-router-hops, 그리고 tracing.headers에서 이름을 변경한 모든 헤더.
  • 라우터 내부 귀속 헤더: x-continuum-resolved-user-profile-id.

anthropic-user-profile-id는 단일 테넌트 백엔드의 정적 Anthropic 사용자 프로필로 사용할 수 있습니다. 헤더 이름은 하이픈으로 적어야 하고, 값은 uprof_ 뒤에 ASCII 영문자, 숫자, _, -가 1자 이상 128자 이하로 와야 합니다. 이 헤더는 bedrock 백엔드에서 거부되며, API 키에 프로필이 있으면 전송 시 키의 값이 이 헤더를 대체합니다. 자세한 내용은 Anthropic 최종 고객별 귀속을 참고하세요.

한도:

한도 값
직렬화된 본문 조각(defaults와 overrides 합계) 16 KiB
한 모드 안의 중첩 깊이 8단계
백엔드당 헤더 수 16
헤더 이름 RFC 7230 토큰, 최대 128바이트
헤더 값 1~1024바이트, 가시 ASCII만(SP부터 ~까지, 제어 문자와 그 외 비ASCII 바이트 없음)

SP-~ 범위를 벗어난 헤더 값(café, 한글, 줄바꿈 없는 공백 U+00A0)은 obs-text로 전송되는 대신 로드 시점에 거부됩니다. 디스패치의 HeaderValue::from_str은 0x80-0xFF 바이트를 받아들이는데 RFC 9110은 이 범위를 폐기 예정으로 다루므로 업스트림과 중개 서버마다 처리 방식이 다릅니다. 어떤 곳은 요청을 400으로 거부하고 어떤 곳은 바이트를 Latin-1로 읽습니다. 비ASCII 텍스트는 퍼센트 인코딩해서 넣습니다. 오류 메시지에는 헤더 값이 절대 포함되지 않습니다.

비밀 값

헤더 값은 api_key와 똑같이 ${ENV_VAR} 치환을 지원합니다. 애그리게이터 키는 어떤 헤더 이름으로도 전달될 수 있으므로 headers 아래의 모든 값은 헤더 이름과 무관하게 비밀 값으로 취급합니다. Admin 설정 및 백엔드 조회, YAML/TOML/JSON 내보내기, 설정 이력, WebUI에서 값은 마스킹되고 헤더 이름은 그대로 보입니다. 마스킹된 문서를 PUT /admin/config/backends나 PUT /admin/backends/{name}으로 다시 보내면 저장된 값이 유지됩니다. config diff와 MCP 도구는 값을 가리고 확장 헤더 값은 로그에 절대 기록되지 않으며 config show --resolved는 api_key와 똑같이 평문으로 출력합니다. 본문 조각 값은 비밀 값이 아니므로 설정한 그대로 표시됩니다.

핫 리로드와 Continuum Hub

  • 실시간 디스패치 및 헬스 프로브: request_extensions 변경은 재시작 없이 다음 요청부터 적용됩니다. 새 설정이 게시되면 백엔드 디스패치 블록이 갱신되고 헬스 체커의 프로브 헤더와 모델 목록도 함께 재검증됩니다.
  • 응답 캐시 네임스페이스: 비어 있지 않은 body 조각을 가진 백엔드는 해당 조각의 지문(fingerprint)에서 파생된 네임스페이스에 캐시 항목을 저장합니다. 따라서 리로드로 defaults나 overrides가 바뀌면 이전 조각으로 생성된 응답을 반환하지 않고 캐시 미스로 처리됩니다. 확장 headers는 캐시 키에 영향을 주지 않으며, body 조각이 없는 백엔드는 표준 캐시 키를 유지합니다.
  • 진행 중 요청 중복 제거: 재시도 계층의 진행 중(in-flight) 요청 중복 제거는 동일한 게시 설정에서 처리되는 동시 결정론적 비스트리밍 요청만 하나로 합칩니다. 따라서 설정이 리로드되면 새로운 중복 제거 범위가 시작됩니다.
  • Continuum Hub 호환성: Continuum Hub v1 백엔드 프로필은 request_extensions 블록을 포함하지 않습니다. Hub 소유 백엔드에는 이 블록이 없으며, Hub 동기화는 유지되는 로컬 백엔드의 블록을 건드리지 않고, 로컬 백엔드 내보내기는 request_extensions를 지원되지 않는 필드로 표시(값은 제외)하여 미리보기 전용 항목으로 분류합니다. authoritative 백엔드 동기화 모드에서 request_extensions를 유지해야 하는 로컬 백엔드는 immutable_backend_ids에 등록하세요.

선두 시스템 메시지 하나만 받는 모델

일부 채팅 템플릿은 messages 배열 맨 앞(인덱스 0)에 정확히 하나의 system 항목만 허용합니다. 예를 들어 모든 Qwen/Qwen3.8-* 템플릿은 첫 번째 메시지가 아닌 위치에 system 항목이 오면 System message must be at the beginning. 오류를 발생시키며, vLLM은 이를 HTTP 400으로 반환하고 라우터는 이 메시지를 클라이언트에 그대로 전달합니다. 이 제약은 추론 엔진이 아니라 모델 템플릿 자체에 있으며, 다음 세 가지 요청 형태에서 발생합니다.

  • Claude Code가 두 번째 턴부터 /anthropic/v1/messages로 보내는 대화 중간 system 메시지(Anthropic 인그레스는 이를 system 역할로 유지함).
  • 첫 번째 system 항목 바로 뒤에 이어지는 두 번째 system 항목.
  • /v1/chat/completions에서 클라이언트가 배열 중간에 직접 넣은 system 항목(패스스루 계약에 따라 그대로 전달됨).

이 제약은 모델과 함께 배포되는 특성이므로 requires_single_leading_system 모델 메타데이터 키로 설정합니다.

  • 기본 카탈로그 지원: 기본 제공되는 model-metadata.yaml은 Qwen3.8 계열(qwen3.8-27b, qwen3.8-2.4t-a95b, qwen3.8-flash-next)에 이 플래그를 활성화하고 있습니다. Qwen/Qwen3.8-27B-FP8 같은 Hugging Face 형식의 모델 ID도 네임스페이스 접두사 제거와 양자화 포맷 정규화를 통해 모든 OpenAI 와이어 백엔드 타입(vllm, sglang, generic, openai 등)에서 자동으로 매칭됩니다.
  • 백엔드별 또는 드롭인 설정: model-metadata.yaml을 로드하지 않거나 자체 모델 이름을 사용하는 배포에서는 백엔드의 model_configs 항목(또는 model-metadata.d/ 드롭인)에 플래그를 직접 선언합니다.
backends:
  - name: qwen-engine
    type: vllm
    url: http://localhost:8003
    model_configs:
      - id: qwen38-27b-fp8
        metadata:
          requires_single_leading_system: true

항목 전체를 통째로 덮어쓰는 다른 메타데이터와 달리 requires_single_leading_system은 소스 간에 필드 단위로 해석됩니다. 가격이나 한도만 지정하고 이 키를 비워 둔 항목은 카탈로그 값을 그대로 상속하며, 명시적으로 requires_single_leading_system: false를 지정한 경우에만 카탈로그 기본값이 해제됩니다(자체 --chat-template으로 실행한 엔진 등).

모델에 대해 이 플래그가 활성화되면 라우터는 /v1/chat/completions 페이로드를 다음과 같이 정규화합니다.

  • 시스템 메시지 병합: 페이로드 내 모든 system 항목을 순서대로 빈 줄(\n\n)로 이어 붙여 맨 앞의 단일 system 메시지로 합치며, 나머지 항목들의 상대적 순서는 그대로 유지합니다. system 항목이 없거나 이미 맨 앞에 하나만 있는 페이로드는 재정렬이나 재인코딩 없이 그대로 전달됩니다.
  • 멀티모달 및 구조화 콘텐츠: 텍스트 파트 배열인 system 항목은 해당 텍스트가 병합되며, 이미지처럼 평탄화할 수 없는 파트는 순서대로 보존되어 전체가 파트 배열 형태로 합쳐집니다. 내용이 문자열도 파트 배열도 아닌 항목이 있으면 페이로드를 수정 없이 그대로 전달합니다.

예를 들어 다음 입력 배열은:

[
  {"role": "system", "content": "You are a coding assistant."},
  {"role": "user", "content": "Read README.md and summarize it."},
  {"role": "assistant", "content": "Here is the summary."},
  {"role": "system", "content": "Be brief from now on."},
  {"role": "user", "content": "Now the second file."}
]

백엔드에 다음과 같이 전달됩니다.

[
  {"role": "system", "content": "You are a coding assistant.\n\nBe brief from now on."},
  {"role": "user", "content": "Read README.md and summarize it."},
  {"role": "assistant", "content": "Here is the summary."},
  {"role": "user", "content": "Now the second file."}
]
  • 디스패치 범위 및 핫 리로드: 병합은 클라이언트 대화를 전달하는 모든 /v1/chat/completions 송신 지점(OpenAI, Anthropic Messages, Responses, ACP 인그레스)과 각 폴백 홉에서 해당 홉의 백엔드·모델 기준으로 실행됩니다(따라서 미드스트림 릴레이의 다음 홉은 원본 배열에서 다시 시작함). 핫 리로드, Admin 백엔드 편집, 설정 동기화, 메타데이터 드롭인 변경은 다음 요청부터 반영됩니다. 네이티브 anthropic, gemini, bedrock 백엔드와 단일 턴 컨트롤 플레인 프로브에는 영향을 주지 않습니다.
  • 트레이드오프: 병합을 통해 지시문을 user 텍스트로 격하하지 않고 system 우선순위를 유지할 수 있지만, 원래 N번째 턴부터 적용하려던 지시문이 대화 맨 앞으로 이동해 첫 턴부터 적용됩니다. 이러한 이유로 모든 백엔드의 기본값이 아니라 모델별 속성으로 동작합니다.

대화 이력의 assistant 추론

assistant 턴의 추론을 다시 보내는 클라이언트는 두 필드 이름 중 하나를 씁니다. 라우터가 응답에 쓰는 reasoning_content와 최신 vLLM이 응답에 쓰는 reasoning입니다. vLLM은 둘 다 읽습니다. 반면 Dynamo 프런트엔드는 reasoning_content만 읽고, 프롬프트를 만들기 전에 reasoning을 버립니다. 그래서 모델은 그 추론을 보지 못합니다.

history_reasoning_field: reasoning_content를 설정하면 해당 백엔드로 보낼 때 필드를 옮깁니다.

backends:
  - name: solar-pro4
    type: vllm
    url: http://solar-pro4-frontend:8000
    history_reasoning_field: reasoning_content
    models:
      - solar-pro4

이 백엔드로 보내는 messages의 assistant 메시지는 다음과 같이 바뀝니다.

  • 문자열 reasoning: reasoning_content로 옮깁니다.
  • 두 필드가 모두 있을 때: reasoning_content를 유지하고 reasoning은 제거합니다. 백엔드는 필드 하나만 받습니다. reasoning_content가 null이거나 빈 문자열이면 없는 것으로 보고, 문자열 reasoning으로 채웁니다.
  • null인 reasoning: 제거합니다.
  • 그 밖의 reasoning 값(객체, 배열): reasoning_content는 문자열 필드이므로 그대로 보냅니다.

다른 역할의 메시지는 건드리지 않습니다.

검증 및 핫 리로드

  • 값은 as_sent(기본값, 클라이언트가 보낸 그대로 전송)와 reasoning_content입니다. 그 밖의 값은 로드 오류입니다.
  • anthropic, bedrock, gemini에 reasoning_content를 설정하면 로드 오류입니다. 이 유형들은 messages 배열을 다른 형식으로 변환하기 때문입니다.
  • 변환은 시스템 메시지 병합과 같은 /v1/chat/completions 디스패치 지점에서 폴백 홉마다 실행됩니다. 따라서 이 필드가 없는 백엔드로 넘어가는 홉은 클라이언트가 보낸 이력을 그대로 보냅니다.
  • 요청마다 라우팅에 쓰인 설정에서 값을 읽습니다. 핫 리로드와 Admin API 백엔드 편집(생성과 수정의 history_reasoning_field)은 다음 요청부터 적용됩니다(즉시 반영 등급).
  • Continuum Hub v1 백엔드 프로필에는 이 필드가 없습니다. 따라서 값이 as_sent가 아니면 로컬 내보내기가 history_reasoning_field를 지원하지 않는 필드로 표시합니다.
  • Hub가 관리하는 백엔드(backend_config_v1)는 이 필드를 담을 수 없으므로 항상 as_sent입니다. authoritative 백엔드 동기화 모드에서는 Hub 스냅샷이 immutable_backend_ids에 없는 모든 백엔드를 관리하며, 로컬 reasoning_content 백엔드를 as_sent로 되돌릴 수 있습니다. 그런 백엔드는 이 목록에 넣으세요.

response_format 지원 선언

/v1/chat/completions와 /v1/completions에서 라우터는 response_format이 포함된 요청을 이 필드를 프로바이더까지 전달하는 백엔드로만 라우팅하며, 요청한 모델에 그런 백엔드가 없으면 400 unsupported_request_parameter로 응답합니다. 기본적으로 이 판단은 백엔드 유형에서 추론합니다. OpenAI 호환 엔진은 라우터가 본문을 그대로 전달하므로 필드를 전달하는 것으로 추론되는데, 구버전 llama.cpp처럼 엔진 자체가 json_schema를 거부하거나 무시하는 경우에도 마찬가지입니다.

supports_response_format으로 엔진의 실제 동작을 선언합니다.

backends:
  - name: old-llamacpp
    type: llamacpp
    url: http://llamacpp:8080
    supports_response_format: false
    models:
      - local-model
값 동작
없음 백엔드 유형에서 추론합니다.
false 이 필드가 포함된 요청은 이 백엔드를 우회하며, 해당 모델의 다른 후보 중 필드를 전달하는 백엔드가 없으면 400 응답 대상이 됩니다. 이 백엔드가 서빙하는 모델은 GET /v1/models의 supported_parameters에서 response_format이 빠집니다. 이 필드가 없는 요청에는 영향이 없습니다.
true 변환이 필드를 전달하는 경우에만 적용됩니다. anthropic과 bedrock은 변환 과정에서 필드가 삭제되므로 선언해도 효과가 없습니다.

선언되지 않은 백엔드 찾기

continuum_response_format_inferred_dispatch_total{backend}는 response_format 요청에서 선언이 아닌 추론으로 유지된 후보를 요청당 한 번씩 집계합니다. 이 검사에서 거부된 요청은 집계하지 않습니다. 가드레일처럼 이후 단계에서 거부되거나 응답 캐시로 처리된 요청은 집계합니다. 값이 계속 늘어나는 백엔드는 실제 지원 여부를 확인한 뒤 선언하는 것이 좋습니다.

검증 및 핫 리로드

  • anthropic이나 bedrock에 supports_response_format: true를 설정해도 로드는 되지만, 로드 시 경고 로그가 남고 continuum-router config validate가 backends.<name>.supports_response_format 경로로 경고를 보고합니다.
  • 요청마다 라우팅에 쓰인 설정에서 값을 읽습니다. 핫 리로드와 Admin API 백엔드 편집은 다음 요청부터 적용됩니다(즉시 반영 등급). Admin API 수정 요청에서 clear_supports_response_format: true를 보내면 선언을 제거합니다.
  • Continuum Hub v1 백엔드 프로필에는 이 필드가 없습니다. 따라서 값이 설정되어 있으면 로컬 내보내기가 supports_response_format을 지원하지 않는 필드로 표시합니다.
  • Hub가 관리하는 백엔드(backend_config_v1)와 AppProxy 워커가 조정하는 백엔드는 이 필드를 담을 수 없으므로 항상 추론으로 동작합니다. authoritative 백엔드 동기화 모드에서 선언을 유지하려면 해당 백엔드를 immutable_backend_ids에 넣으세요.

모델 아티팩트 식별 정보

동일한 모델 ID라도 백엔드마다 서로 다른 가중치(다른 양자화, 다른 Hugging Face 리비전, 재내보낸 체크포인트)로 서빙되는 경우가 많습니다. artifact와 draft_model 메타데이터 키는 백엔드가 서빙하는 정확한 아티팩트를 선언하며, 해당 백엔드에 도달한 모든 사용량 레코드에 해석된 model_digest와 draft_model을 기록합니다. 공통 선언은 주로 model-metadata.d/ 드롭인에 두고(모델 아티팩트 식별 정보 참고), 다른 빌드를 서빙하는 백엔드는 model_configs에 자체 값을 선언합니다.

backends:
  - name: qwen-fp8
    type: vllm
    url: http://localhost:8003
  - name: qwen-int4
    type: vllm
    url: http://localhost:8004
    model_configs:
      - id: qwen3.8-27b
        metadata:
          artifact:
            digest: sha256:0b0b000000000000000000000000000000000000000000000000000000000002
            registry_version: v2.1-int4
  • 백엔드별 우선순위: 오버라이드는 해당 백엔드에만 키 단위(artifact와 draft_model 각각 독립적)로 적용됩니다. 위 예시에서 qwen-int4는 자체 artifact 다이제스트를 보고하면서 드롭인의 draft_model을 그대로 상속하고, qwen-fp8은 드롭인의 artifact를 유지합니다. 오버라이드된 artifact는 공유 artifact 객체 전체를 대체하므로 드롭인에만 선언된 revision 등은 넘어오지 않습니다.
  • 사용량 레코드 및 API 노출: 재시도나 폴백 홉을 거친 요청을 포함해, 각 백엔드가 실제로 처리한 사용량 레코드에는 해당 백엔드의 다이제스트가 기록됩니다. GET /admin/models는 모든 서빙 백엔드의 식별 정보를 artifacts에 나열하고, GET /v1/models/{model}의 metadata 블록은 첫 번째 서빙 백엔드의 값을 표시합니다.
  • 검증 및 공개 source 보안: model_configs에 잘못된 다이제스트가 있으면 설정 로드가 거부되며, config validate는 vllm, sglang, tensorrt-llm 항목이 다이제스트 없이 draft_model을 선언하면 경고를 출력합니다. artifact.source는 두 엔드포인트 모두에서 공개되는 카탈로그 데이터이므로 사전 서명된(presigned) URL이나 토큰이 포함된 URL(?token=, ?sig=)을 절대 사용하지 말고, hf://org/model이나 oci://registry/repo@sha256:... 같은 안정적인 공개 경로를 사용하세요.

백엔드 없이 시작하기

라우터는 빈 백엔드 목록(backends: [])으로 시작할 수 있으며 다음과 같은 경우에 유용합니다:

  • 인프라 부트스트래핑: 라우터를 먼저 시작한 다음 Admin API를 통해 동적으로 백엔드 추가
  • 컨테이너 오케스트레이션: 백엔드 서비스보다 라우터 컨테이너가 먼저 준비될 수 있음
  • 개발 워크플로우: 백엔드가 프로비저닝되기 전에 관리자 엔드포인트 테스트
  • 점진적 롤아웃: 백엔드 없이 시작하여 점진적으로 추가

백엔드 없이 실행할 때:

  • /v1/models는 {"object": "list", "data": []}를 반환
  • /v1/chat/completions 및 기타 라우팅 엔드포인트는 503 No backends available 반환
  • /health는 정상 상태 반환 (라우터 자체는 작동 중)
  • POST /admin/backends를 통해 백엔드 추가 가능

동적 백엔드 관리를 위한 최소 구성 예제:

server:
  bind_address: "0.0.0.0:8080"

backends: []  # 백엔드 없이 시작 - 나중에 Admin API로 추가

health_checks:
  interval: "30s"
  timeout: "5s"
  unhealthy_threshold: 3
  healthy_threshold: 2
  endpoint: "/health"

logging:
  level: info
  format: json

admin:
  auth:
    method: bearer
    bearer_token: "${ADMIN_TOKEN}"

지원되는 백엔드 유형:

유형 설명 기본 URL
generic OpenAI 호환 API (기본) 지정 필수
openai 내장 설정이 있는 네이티브 OpenAI API https://api.openai.com/v1
gemini Google Gemini API (OpenAI 호환 엔드포인트) https://generativelanguage.googleapis.com/v1beta/openai
azure Azure OpenAI Service 지정 필수
vllm vLLM 서버 지정 필수
sglang SGLang 서버 (sglang.srt HTTP 서버) http://localhost:30000
tensorrt-llm TensorRT-LLM HTTP/OpenAI 호환 서버 지정 필수
ollama Ollama 로컬 서버 http://localhost:11434
llamacpp llama.cpp llama-server (GGUF 모델) http://localhost:8080
mlxcel MLxcel 서버 (MLX 기반, llama-server 호환, macOS 전용) http://localhost:8080
lmstudio LM Studio 로컬 서버 http://localhost:1234
anthropic Anthropic Claude API (요청/응답 변환 포함 네이티브) https://api.anthropic.com
bedrock Amazon Bedrock (mantle + runtime + converse; runtime과 converse는 --features bedrock-sigv4 빌드가 필요) https://bedrock-mantle.{region}.api.aws 또는 https://bedrock-runtime.{region}.amazonaws.com (리전 템플릿)
continuum-router 원격 Continuum Router 또는 Backend.AI GO 인스턴스 (연합 라우팅) 지정 필수

네이티브 OpenAI 백엔드

type: openai를 사용하면 라우터는 다음을 제공합니다:

  • 기본 URL: https://api.openai.com/v1 (프록시용으로 오버라이드 가능)
  • 내장 모델 메타데이터: 자동 가격, 컨텍스트 윈도우, 기능
  • 환경 변수 지원: CONTINUUM_OPENAI_API_KEY 및 CONTINUUM_OPENAI_ORG_ID에서 자동 로드

최소 OpenAI 설정:

backends:
  - name: "openai"
    type: openai
    models:
      - gpt-5.6-sol
      - gpt-5.6-terra
      - o3-mini

명시적 API 키가 있는 전체 OpenAI 설정:

backends:
  - name: "openai-primary"
    type: openai
    api_key: "${CONTINUUM_OPENAI_API_KEY}"
    org_id: "${CONTINUUM_OPENAI_ORG_ID}"     # 선택 사항
    models:
      - gpt-5.6-sol
      - gpt-5.6-terra
      - o1
      - o1-mini
      - o3-mini
      - text-embedding-3-large

프록시와 함께 OpenAI 사용:

backends:
  - name: "openai-proxy"
    type: openai
    url: "https://my-proxy.example.com/v1"   # 기본 URL 오버라이드
    api_key: "${PROXY_API_KEY}"
    models:
      - gpt-5.6-sol

ChatGPT 구독 / Codex 헤드리스 로그인 (OAuth 디바이스 플로우)

이 섹션은 라우터 자체 클라이언트를 위한 업스트림으로서의 Codex를 다룹니다. Codex CLI 자체를 라우터에 연결하려면 Codex CLI와 통합하기를 참고하세요.

유료 OpenAI API 키 없이도 ChatGPT Plus / Pro / Enterprise 구독으로 OpenAI Codex 백엔드(https://chatgpt.com/backend-api/codex)에 인증할 수 있습니다.

OpenAI 공급자는 RFC 8628 표준 디바이스 플로우를 구현하지 않고 공식 Codex CLI가 헤드리스 디바이스 로그인에 사용하는 자체 3단계 흐름을 사용합니다:

  1. auth.openai.com/api/accounts/deviceauth/usercode에서 일회성 user_code를 받습니다.
  2. 사용자가 브라우저에서 코드를 승인할 때까지 auth.openai.com/api/accounts/deviceauth/token을 폴링합니다.
  3. 받은 인증 코드를 auth.openai.com/oauth/token에서 PKCE로 액세스/리프레시 토큰과 교환합니다.

auth.openai.com 앞단에 있는 Cloudflare가 트래픽을 받아주도록 모든 요청에 originator: codex_cli_rs 헤더가 함께 전송됩니다.

일회성 로그인

OpenAI 인증 URL을 브라우저로 열 수 있는 환경에서 디바이스 플로우 로그인을 실행합니다. 라우터가 인증 URL과 짧은 사용자 코드를 출력하고 디바이스가 승인될 때까지 토큰 엔드포인트를 폴링합니다.

continuum-router auth login --backend openai-chatgpt

토큰은 설정된 token_store 경로에 Unix에서 0600 모드로 원자적으로 기록됩니다. 로그인이 끝나면 라우터가 토큰을 자동으로 사용하고 만료 60초 전에 투명하게 갱신합니다. 백엔드가 401을 응답하면 강제로 한 번 갱신한 뒤 한 번 재시도하고 그래도 실패하면 클라이언트에 오류가 전달됩니다.

백엔드 설정
backends:
  - name: openai-chatgpt
    type: openai
    url: https://chatgpt.com/backend-api/codex
    auth:
      type: oauth
      oauth:
        provider: openai
        token_store: ~/.continuum-router/auth/openai.json
    # Codex 백엔드는 실제 모델 목록을 라이브 엔드포인트에서 가져옵니다(아래 "동적 모델
    # 열거" 참고). 이 목록이 비어 있지 않으면 열거된 모델 중 노출할 것을 선택하며, 열거가
    # 실패하거나 결과가 없을 때의 폴백으로도 쓰입니다. 비워 두면 열거된 모델을 모두 노출합니다.
    models:
      - gpt-5
      - codex-mini

client_id와 scope는 auth.openai.com이 받아주는 공식 Codex CLI의 공개 값으로 기본 설정되어 있어, 자체 OAuth 클라이언트를 등록한 경우에만 따로 지정하면 됩니다.

설정 항목
필드 필수 설명
auth.type 예 디바이스 플로우 인증을 켜려면 oauth로 지정합니다.
auth.oauth.provider 예 OAuth 공급자. 현재 openai만 지원합니다.
auth.oauth.client_id 아니오 공개 OAuth 클라이언트 ID. 기본값은 auth.openai.com이 ChatGPT 구독 헤드리스 로그인에 받아주는 Codex CLI의 공개 client_id입니다. 별도 OAuth 클라이언트를 등록한 경우에만 오버라이드합니다.
auth.oauth.scope 아니오 디바이스 인가 시 요청하는 공백 구분 스코프 문자열. 기본값은 "openid profile email offline_access"입니다.
auth.oauth.token_store 예 JSON 토큰 저장 경로 (예: ~/.continuum-router/auth/openai.json). 틸드와 ${ENV_VAR}가 확장됩니다.
auth.oauth.device_code_endpoint 아니오 디바이스 인가(사용자 코드) 엔드포인트 오버라이드. 기본값은 공급자별 well-known URL입니다.
auth.oauth.token_poll_endpoint 아니오 디바이스 플로우에서 사용하는 토큰 폴링 엔드포인트 오버라이드 (Codex 전용으로, 표준 token_endpoint와 별개입니다). 기본값은 공급자별 well-known URL입니다.
auth.oauth.token_endpoint 아니오 PKCE 교환과 갱신에 사용하는 토큰 엔드포인트 오버라이드. 기본값은 공급자별 well-known URL입니다.
auth.oauth.verification_url 아니오 auth login 실행 시 출력되는 사용자용 인증 URL 오버라이드. openai의 기본값은 https://auth.openai.com/codex/device입니다.
auth.oauth.redirect_uri 아니오 PKCE 교환에 사용하는 redirect URI 오버라이드. 기본값은 공급자별 well-known URL입니다.
auth.oauth.originator 아니오 originator 요청 헤더 오버라이드. provider: openai의 기본값은 auth.openai.com의 Cloudflare 프런트가 허용하는 codex_cli_rs입니다. 환경상 다른 값이 필요할 때만 변경합니다.
auth.oauth.user_agent 아니오 디바이스 플로우와 갱신 요청에 사용되는 User-Agent 헤더 오버라이드. auth.openai.com이 Cloudflare 뒤에 있고 reqwest 기본 UA를 JS 챌린지로 거부하기 때문에, provider: openai의 기본값은 Codex CLI 호환 값입니다.
동적 모델 열거

정적 OpenAI API 백엔드와 달리, ChatGPT Codex 백엔드는 표준 /v1/models 엔드포인트를 제공하지 않습니다. 대신 GET <base>/models?client_version=<ver> 주소에서 계정 구독 플랜에 따라 필터링된 모델 목록을 반환합니다. 라우터는 모델 검색 시(그리고 일반 갱신 주기에) 로드된 OAuth 토큰으로 이 엔드포인트를 조회하여 /v1/models 응답과 라우팅을 채웁니다.

  • 요청에는 Authorization: Bearer <access_token>, originator: codex_cli_rs 헤더, 그리고 id_token에서 추출한 chatgpt-account-id 헤더가 함께 전송됩니다.
  • 사용자에게 노출되는 모델만 남깁니다. visibility가 list이고 available_in_plans가 비어 있거나 계정 플랜을 포함하는 항목만 통과합니다(id_token의 chatgpt_plan_type 클레임에서 읽음). codex-auto-review처럼 visibility가 hide인 내부 항목은 절대 노출되지 않습니다.
  • 다른 백엔드와 똑같이 설정한 models: 선택이 열거 결과에도 적용됩니다. 목록이 비어 있지 않으면 열거된 모델을 그 부분집합으로 좁히고 비어 있으면 열거된 전체 집합(예: gpt-5.5, gpt-5.4, gpt-5.4-mini)을 노출합니다. 열거된 모델의 owned_by에는 원시 백엔드 이름 대신 정리된 값 openai가 들어갑니다.
  • 네트워크 오류, 2xx 아닌 응답, 빈 목록 등 어떤 실패가 발생해도 라우터는 설정된 models: 목록으로 폴백하여 라우팅이 정상적으로 이어집니다. 동적 열거는 Codex OAuth 백엔드에만 적용되며 다른 OAuth 및 정적 키 백엔드는 설정된 모델 목록을 사용합니다.
요청 처리

ChatGPT Codex 백엔드는 추론 엔드포인트로 <base>/responses 하나만 제공하며(기본 주소 기준 https://chatgpt.com/backend-api/codex/responses), 공개 OpenAI Responses API의 더 엄격한 부분집합을 구현합니다. 라우터는 Codex OAuth 백엔드로 라우팅된 /v1/chat/completions 요청을 Responses API 형식으로 변환한 뒤 Codex가 받아들이는 형태에 맞게 조정합니다:

  • input은 항상 아이템 리스트로 전송합니다. 단일 메시지 요청이 만들어내는 문자열 축약형은 Codex가 거부합니다.
  • Codex가 거부하는 max_output_tokens, temperature, top_p, presence_penalty, frequency_penalty, stop 필드는 제거합니다.
  • 특정 함수를 강제하는 tool_choice는 "required"로 낮추고 경고를 로그에 남깁니다. Codex는 문자열 모드만 지원하므로, 도구 이름을 지정하지 않은 채 도구 호출을 유도하는 형태로 대체됩니다.
  • 요청에 시스템 메시지가 없으면 instructions 필드를 최소 기본값으로 채웁니다. Codex는 instructions가 없는 요청을 거부하기 때문입니다.

Codex는 stream: true와 store: false 조합만 허용하므로, 라우터는 변환된 업스트림 호출마다 두 값을 강제로 설정합니다:

  • 스트리밍 클라이언트: 평소처럼 변환된 SSE 스트림을 그대로 받습니다.
  • 비스트리밍 클라이언트 (stream: false): 라우터가 업스트림 SSE 스트림을 끝까지 읽어 단일 chat completion JSON 본문으로 합쳐서 응답합니다 (text/event-stream content type 없이 SSE 본문을 보내는 응답도 감지해 처리합니다).
  • 상태 비저장 (store: false): store가 항상 false이므로 OpenAI 측에는 아무것도 저장되지 않으며, 대화 상태는 일반 Chat Completions 클라이언트처럼 요청에 담아 전달합니다.
운영 시 유의사항
  • 토큰은 로그, 트레이스, 메트릭 어디에도 노출되지 않습니다. 갱신 시 짧게 마스킹된 접두어만 로그에 남습니다.
  • 갱신은 tokio::sync::Mutex로 single-flight 처리되므로, 만료 시점에 동시 진행 중인 요청들이 OAuth 공급자에 갱신 요청을 폭주시키지 않습니다.
  • 정적 api_key 설정은 영향을 받지 않고 그대로 동작하며 OAuth는 백엔드별로 auth.type: oauth 블록을 통해 옵트인됩니다.
  • continuum-router auth login --backend <name>을 다시 실행하면 토큰 저장소가 원자적으로 다시 기록되며 라우터가 동작 중이어도 안전합니다.
  • 토큰 저장소는 읽을 때 형식을 관대하게 받아들입니다. expires_at은 라우터가 기록하는 표준 형태인 epoch 초 외에 숫자 문자열과 RFC3339 날짜 문자열도 허용하므로, 다른 도구가 만든 토큰 파일도 수정 없이 로드됩니다.
  • OpenAI 공급자의 인증 엔드포인트는 Cloudflare로 보호되고 있어, 기본 user_agent와 originator: codex_cli_rs 헤더가 공식 Codex CLI를 모방하도록 설정되어 있습니다. 환경상 다른 값이 필요할 때만 auth.oauth.user_agent 또는 auth.oauth.originator를 변경하세요.
  • YAML에서는 OAuth가 oauth로 직렬화되며 호환성을 위해 serde 기본 snake_case가 만들어내던 o_auth 표기도 별칭으로 받아들입니다.

OpenAI 환경 변수

변수 설명
CONTINUUM_OPENAI_API_KEY OpenAI API 키 (type: openai 백엔드용으로 자동 로드)
CONTINUUM_OPENAI_ORG_ID OpenAI 조직 ID (선택 사항)

모델 검색

집계 모델 서비스는 실행 중인 백엔드 카탈로그를 갱신하고 결과를 캐시합니다. 일반 HTTP 백엔드는 백엔드 URL에서 계산한 OpenAI 호환 모델 목록 경로를 요청하고, Unix 소켓 백엔드는 /v1/models를 사용합니다.

  • backends[].models가 비어 있지 않으면 검색 결과에 적용되는 허용 목록으로 동작합니다.
  • 네이티브 Anthropic: x-api-key와 백엔드의 anthropic-version 헤더(anthropic_version을 지정하지 않으면 2023-06-01)를 사용하여 HTTP 또는 Unix 도메인 소켓을 통해 GET {base}/v1/models(빈 url은 기본값 https://api.anthropic.com 사용)로 커서 페이지네이션(has_more / after_id) 기반 실시간 모델 검색을 지원하며, 실시간 검색이 실패하면 라우터의 내장 Claude 모델 카탈로그로 폴백합니다. Anthropic 백엔드는 설정 검증 시 명시적인 models 목록을 요구하지 않으며, models 또는 allowed_models에 날짜가 없거나 -latest가 붙은 정규 ID(예: claude-haiku-4-5, claude-haiku-4-5-latest)를 쓰면 실시간 Anthropic 모델의 날짜 접미사 ID(예: claude-haiku-4-5-20251001)와 매칭합니다.
  • Amazon Bedrock: 모델 목록 엔드포인트가 없으므로 명시적인 models 또는 model_configs 목록이 필요하며, 집계 서비스는 HTTP /v1/models 요청 없이 설정된 목록에서 바로 Bedrock 모델을 제공합니다.
  • enabled: false인 백엔드는 건너뜁니다. 집계 서비스는 이 백엔드에 요청을 보내지 않으며 모델도 노출하지 않습니다. 비활성 백엔드의 실시간 카탈로그가 필요하면 관리자 검색 엔드포인트를 사용하세요.
  • 모델 목록 엔드포인트가 반환한 404와 405는 401/403과 마찬가지로 영구 실패로 처리하여 재시도 없이 즉시 실패 처리합니다. 5xx 응답과 연결 오류는 재시도 예산을 모두 사용합니다.
  • ChatGPT Codex 흐름이 아닌 OAuth 백엔드는 설정된 models 목록을 사용합니다. Codex 흐름 백엔드는 인증된 Codex 모델 엔드포인트에서 계정에 표시되는 모델을 열거한 뒤, 설정된 목록이 비어 있지 않으면 허용 목록으로 적용합니다.
  • 실패한 백엔드는 해당 갱신 결과에서 제외됩니다. 모든 백엔드가 실패하면 빈 모델 목록을 반환하고 캐시합니다. 일반 OpenAI 호환 검색 실패에는 하드코딩된 폴백 카탈로그가 없습니다 (네이티브 Anthropic만 내장 Claude 카탈로그 폴백을 사용합니다).
  • model_configs는 백엔드 메타데이터와 매칭 정보를 제공하지만, 실시간 검색을 지원하지 않는 백엔드의 필수 models 목록을 대체하지는 않습니다.

온디맨드 및 관리자 검색 엔드포인트:

  • POST /v1/models/refresh 또는 POST /admin/models/refresh: 집계 카탈로그를 즉시 갱신합니다 (공개 경로에는 속도 제한이 적용됩니다).
  • POST /admin/backends/{name}/models/discover: 설정된 models: 허용 목록을 적용하기 전 특정 백엔드 하나의 실시간 카탈로그를 조회합니다. 이 엔드포인트는 읽기 전용이며 models:, 모델 별칭, 헬스 상태, 서킷 브레이커 상태, 집계 모델 캐시를 변경하지 않습니다. Codex OAuth 백엔드는 일반 집계와 동일한 계정·플랜 인식 검색 경로를 사용하고, 지원하지 않는 백엔드 타입은 설정 모델명으로 폴백하지 않고 model_discovery_unsupported 오류를 반환합니다.
  • POST /admin/backends/probe: 백엔드 등록 전에 후보 URL·키를 검증하고 초기 모델 선택기를 채웁니다 (/admin/capabilities에서 transient_backend_probe_v1로 지원 여부를 확인할 수 있습니다):
    • 등록된 검색과 동일한 백엔드 타입, URL 구성, 인증 헤더, 파서, 정규화, 응답 크기 상한, 모델 개수 제한을 사용합니다.
    • 요청 범위 내에서만 실행되며 설정, 히스토리, 풀, 헬스 상태, 서킷 상태, 캐시, 환경 파일, 토큰 저장소에 기록되지 않습니다. 설정 폴백은 models 또는 model_configs를 받으며 백엔드별 모델 개수 제한이 그대로 적용됩니다.
    • 인증이 필요 없는 헬스 체크 성공을 자격 증명 유효성으로 오인하지 않도록 health.credential_status와 catalog.source를 분리해 보고합니다. 등록 전에는 수명 주기 관리형 OAuth 인증 전략을 사용할 수 없으므로 임시 OAuth 헬스 상태는 명시적으로 unknown을 반환합니다.

업스트림 공급사와 메타데이터

OpenRouter 같은 OpenAI 호환 애그리게이터는 라우터의 model-metadata.yaml에 없는 모델을 수백 개 나열하며, 이 모델들의 공급사, 컨텍스트 윈도우, 출력 상한, 가격 정보는 애그리게이터 카탈로그에서만 얻을 수 있습니다. 모델 검색은 각 /v1/models 항목에서 해당 필드를 다음과 같이 읽습니다.

공급사(owned_by): 정규화된 모델 ID를 기준으로 항목마다 결정합니다.

  1. 업스트림이 보낸 실제 owned_by 값은 그대로 유지합니다.
  2. 업스트림이 owned_by를 보내지 않았고(필드 없음, null, 공백) ID에 vendor/model 네임스페이스가 있으면, 첫 번째 경로 세그먼트를 그대로 공급사로 사용합니다. OpenRouter는 owned_by를 보내지 않으므로 anthropic/claude-haiku-4.5는 anthropic, openrouter/auto는 openrouter로 표시됩니다. 세그먼트 자체가 플레이스홀더 값(vllm/... 등)이면 사용하지 않습니다.
  3. 그 밖의 플레이스홀더(system, organization_owner, 네임스페이스 없는 ID의 빈 값 등)는 백엔드 타입의 소유자로 바꿉니다. type: openai는 openai가 되고 generic처럼 정해진 소유자가 없는 타입은 값을 그대로 유지합니다.

이 규칙은 ID의 첫 번째 경로 세그먼트만 읽으므로, owned_by가 없는 경로 형태의 ID는 해당 세그먼트가 공급사가 아니어도 그대로 보고합니다(예: hf.co/org/model은 hf.co). 명시적 플레이스홀더는 값이 없는 것과 다르게 처리됩니다(예: OpenAI 카탈로그의 owned_by: "system"은 openai로 변환됩니다). model-metadata.yaml이나 백엔드 model_configs의 모델별 owned_by는 항상 우선하며, response_defaults.owned_by는 플레이스홀더로 남은 값에만 적용되므로 네임스페이스에서 추출한 공급사가 전역 기본값보다 우선합니다.

  • 컨텍스트 윈도우와 출력 상한:
    • 컨텍스트 길이는 max_model_len(vLLM, SGLang) 또는 context_length(OpenRouter)에서 읽으며, 둘 다 있으면 max_model_len이 우선합니다. 유효 메타데이터에 limits가 없을 때 1..=10000000 범위의 값이 limits.context_window를 채웁니다.
    • limits.max_output은 항목의 top_provider.max_completion_tokens가 존재하고 1..=context_length 범위이면 그 값을, 아니면 컨텍스트 길이를 사용합니다.
    • 여러 백엔드가 같은 ID를 제공하면 두 값 모두 백엔드 간 최솟값을 게시합니다.
  • 가격:
    • pricing.prompt와 pricing.completion을 토큰당 USD(JSON 문자열 또는 숫자)로 읽어 100만 토큰당 USD인 pricing.input_tokens와 pricing.output_tokens로 게시합니다(예: "0.000001"은 1.0). 두 값이 모두 유효해야 가격을 게시합니다.
    • 유한한 음이 아닌 숫자로 파싱되지 않는 값(가변 가격을 뜻하는 OpenRouter의 "-1" 포함)이나 변환 결과가 100만 토큰당 100,000 USD를 넘는 값은 0이 아니라 가격 없음으로 처리합니다.
    • pricing.input_cache_read는 1 - cache_read / prompt 비율이 0과 1 사이일 때 cached_input_discount로 변환됩니다. input_cache_write, web_search 같은 다른 가격 키나 객체가 아닌 pricing 값은 무시합니다. 게시된 가격은 설정된 가격과 동일하게 스마트 라우팅의 티어 추론과 비용 점수 계산에 사용됩니다.
  • limits 우선순위: 설정된 메타데이터가 우선합니다. 업스트림의 컨텍스트 윈도우와 출력 상한은 model-metadata.yaml, model-metadata.d/, 백엔드 model_configs, 내장 OpenAI 카탈로그에서 매칭된 메타데이터에 limits가 없을 때만 채워집니다.
  • pricing 우선순위:
    • 가격은 모델을 제공하는 공급자마다 다르므로, 매칭 방식(정확한 ID, 별칭, 날짜·양자화 정규화, vendor/ 접두사 제거)과 관계없이 업스트림 가격이 공유 model-metadata.yaml(model-metadata.d/ 포함) 및 내장 OpenAI 카탈로그 가격보다 우선합니다. 이를 통해 셀프 호스팅용 0/0 기본 항목 때문에 애그리게이터의 minimax/minimax-m2가 무료로 표시되는 문제를 방지합니다.
    • 업스트림 가격보다 우선하는 것은 backends[].model_configs에서 해당 백엔드에 직접 지정한 가격뿐입니다.
    • 해당 ID를 제공하는 백엔드 중 하나라도 유효한 업스트림 가격을 보고하면, 각 백엔드는 model_configs 고정 가격 또는 업스트림 가격을 후보로 제출합니다(둘 다 없거나 "-1"인 백엔드는 제외). 후보들 중 각 가격 요소의 최댓값과 cached_input_discount의 최솟값(할인율이 없는 후보가 있으면 할인율 제외)이 최종 가격으로 게시됩니다. 유효한 업스트림 가격을 보고한 백엔드가 하나도 없으면 설정된 메타데이터의 가격을 사용합니다.
  • 기타 메타데이터 필드: owned_by를 포함한 나머지 모든 메타데이터 필드는 설정된 메타데이터가 우선합니다. 업스트림이 보낸 metadata 객체는 라우터 메타데이터로 신뢰하지 않습니다.
  • 노출 위치:
    • GET /v1/models: OpenAI 필드 집합(id, object, created, owned_by)을 유지하며 owned_by만 반영됩니다.
    • GET /v1/models/extended: 매핑된 metadata와 함께 나머지 업스트림 필드(pricing, architecture, supported_parameters, top_provider, canonical_slug 등)를 그대로 전달합니다. 단, 라우터가 직접 작성하는 키(id, object, created, owned_by, backends, metadata, tier, domains)는 중복 전달하지 않으며, max_model_len과 context_length가 존재할 때는 게시된 metadata.limits.context_window 값으로 통일합니다 (메타데이터 포함 모델 목록 참고).
    • GET /v1/models/{model}: 매핑된 metadata와 max_tokens를 반환합니다. 네임스페이스가 포함된 ID의 /는 원문과 퍼센트 인코딩 모두 지원합니다 (/v1/models/anthropic/claude-haiku-4.5와 /v1/models/anthropic%2Fclaude-haiku-4.5는 동일). extended, proxy, refresh 세그먼트는 전용 라우트로 예약되어 있습니다.

네이티브 Gemini 백엔드

type: gemini를 사용하면 라우터는 다음을 제공합니다:

  • 기본 URL: https://generativelanguage.googleapis.com/v1beta/openai (OpenAI 호환 엔드포인트)
  • 자동 이중 API(Dual-API) 라우팅: 일반 채팅 요청은 OpenAI 호환 엔드포인트를 사용하고 Gemini 전용 기능이 요청되거나 네이티브 API 선호 설정이 켜진 경우 자동으로 Gemini Native API(/v1beta/models/{model}:generateContent 및 :streamGenerateContent?alt=sse)로 전환
  • 내장 모델 메타데이터: Gemini 모델에 대한 자동 컨텍스트 윈도우 및 기능
  • 환경 변수 지원: CONTINUUM_GEMINI_API_KEY에서 자동 로드
  • 확장 스트리밍 타임아웃: 사고 모델용 300초 타임아웃 (gemini-3.1-pro, gemini-3-flash, gemini-2.5-pro)
  • 자동 max_tokens 조정: 사고 모델용, 아래 참조

최소 Gemini 설정:

backends:
  - name: "gemini"
    type: gemini
    models:
      - gemini-3.1-pro-preview
      - gemini-3-flash-preview
      - gemini-2.5-pro
      - gemini-2.5-flash
      - nano-banana-2
      - nano-banana-pro

API 키를 사용한 전체 Gemini 설정:

backends:
  - name: "gemini"
    type: gemini
    api_key: "${CONTINUUM_GEMINI_API_KEY}"
    weight: 2
    models:
      - gemini-3.1-pro-preview
      - gemini-3-flash-preview
      - gemini-2.5-pro
      - gemini-2.5-flash
      - gemini-2.5-flash-image
      - nano-banana
      - nano-banana-2
      - nano-banana-2-lite
      - nano-banana-pro

Gemini 이중 API 라우팅 및 전용 기능

type: gemini 백엔드는 Google의 OpenAI 호환 표면(/v1beta/openai/chat/completions)과 Gemini Native API(/v1beta/models/{model}:generateContent 및 :streamGenerateContent?alt=sse, x-goog-api-key 또는 OAuth Bearer 토큰 인증)를 모두 지원합니다:

  • 요청별 자동 전환: 일반 /v1/chat/completions 요청은 기본적으로 OpenAI 호환 엔드포인트를 사용합니다. 요청에 다음 Gemini 전용 기능이 하나라도 포함되면 라우터가 자동으로 네이티브 generateContent 형식으로 변환하여 호출하고 응답(및 SSE 스트림)을 다시 OpenAI 호환 형식으로 정규화합니다:
    • Google Search 그라운딩 (tools의 google_search / web_search 또는 web_search_options)
    • Python 코드 실행 (tools의 code_execution / code_interpreter)
    • 네이티브 이미지 출력 (modalities: ["text", "image"], image_config, 또는 Gemini 이미지 생성 모델)
    • 멀티턴 어시스턴트 images 히스토리
    • extra_body.google 네이티브 전용 필드 (grounding, response_modalities, safety_settings, media_resolution, cached_content)
  • 항상 Gemini Native API 선호: 전용 도구 유무와 상관없이 모든 Gemini /v1/chat/completions 트래픽을 네이티브 generateContent / streamGenerateContent 엔드포인트로 라우팅하려면 전역 설정에서 gemini_context_cache.prefer_native_api: true를 켜거나 백엔드 url을 네이티브 베이스(https://generativelanguage.googleapis.com/v1beta)로 지정합니다:
gemini_context_cache:
  enabled: false
  prefer_native_api: true
전용 기능 지원 엔드포인트 요청 트리거 정규화된 OpenAI 응답 출력
Google Search 그라운딩 /v1/chat/completions, /v1/responses, /v1/images/generations, /v1/images/edits tools: [{"type": "google_search"}] (또는 web_search), 이미지 엔드포인트의 google_search: true (nano-banana-pro, nano-banana-2) choices[0].message.annotations (type: "url_citation") 및 grounding_metadata (webSearchQueries, groundingChunks, groundingSupports)
Python 코드 실행 /v1/chat/completions, /v1/responses tools: [{"type": "code_execution"}] (또는 code_interpreter) content 내 python·output 코드 블록 및 구조화된 choices[0].message.code_execution (executable_code, code_execution_result)
멀티턴 이미지 생성 및 편집 /v1/chat/completions, /v1/responses modalities: ["text", "image"] 또는 Gemini 이미지 모델 (gemini-2.5-flash-image, nano-banana*) + messages 내 이전 턴 assistant.images choices[0].message.images ([{"type": "image_url", "image_url": {"url": "data:image/png;base64,..."}}])
Thinking 추론 추출 /v1/chat/completions, /v1/responses reasoning_effort (minimal, low, medium, high) 또는 extra_body.google.thinking_config 네이티브 thought: true 파트를 choices[0].message.reasoning_content로 분리 추출하고 도구 호출 시 thought_signature 보존

Gemini 이미지 모델 (Nano Banana 제품군):

모델 / 별칭 실제 Gemini 모델 ID 최대 해상도 검색 그라운딩 (google_search: true)
nano-banana gemini-2.5-flash-image 1K 미지원 (400 반환)
nano-banana-pro gemini-3-pro-image-preview 최대 4K (1K, 2K, 4K) 지원
nano-banana-2 gemini-3.1-flash-image-preview 최대 4K (1K, 2K, 4K) 지원
nano-banana-2-lite gemini-3.1-flash-lite-image 1K 전용 미지원 (400 반환)

Gemini 인증 방법

Gemini 백엔드는 두 가지 인증 방법을 지원합니다:

API 키 인증 (기본)

Google AI Studio API 키를 사용하는 가장 간단한 인증 방법:

backends:
  - name: "gemini"
    type: gemini
    api_key: "${CONTINUUM_GEMINI_API_KEY}"
    models:
      - gemini-3.1-pro-preview

서비스 계정 인증

엔터프라이즈 환경 및 Google Cloud Platform (GCP) 배포의 경우, 자동 OAuth2 토큰 관리와 함께 서비스 계정 인증을 사용할 수 있습니다:

backends:
  - name: "gemini"
    type: gemini
    auth:
      type: service_account
      key_file: "/path/to/service-account.json"
    models:
      - gemini-3.1-pro-preview
      - gemini-3-flash-preview

키 파일 경로에 환경 변수 사용:

backends:
  - name: "gemini"
    type: gemini
    auth:
      type: service_account
      key_file: "${GOOGLE_APPLICATION_CREDENTIALS}"
    models:
      - gemini-3.1-pro-preview

서비스 계정 인증 기능:

기능 설명
자동 토큰 갱신 OAuth2 토큰이 만료 5분 전에 자동으로 갱신됩니다
토큰 캐싱 인증 오버헤드를 최소화하기 위해 토큰이 메모리에 캐시됩니다
스레드 안전 동시 요청이 토큰 갱신 작업을 안전하게 공유합니다
환경 변수 확장 키 파일 경로가 ${VAR} 및 ~ 확장을 지원합니다

서비스 계정 키 생성:

  1. Google Cloud Console로 이동
  2. IAM & Admin > Service Accounts로 이동
  3. 새 서비스 계정을 만들거나 기존 계정 선택
  4. Keys > Add Key > Create new key 클릭
  5. JSON 형식을 선택하고 키 파일 다운로드
  6. 키 파일을 안전하게 저장하고 설정에서 참조

필요한 권한:

Gemini API 접근을 위해 서비스 계정에 다음 역할이 필요합니다:

  • roles/aiplatform.user - Vertex AI Gemini 엔드포인트용
  • 또는 generativelanguage.googleapis.com용 적절한 Google AI Studio 권한

인증 우선순위

여러 인증 방법이 설정된 경우:

우선순위 방법 조건
1 (최고) auth 블록 auth.type이 지정된 경우
2 api_key 필드 auth 블록이 없는 경우
3 환경 변수 CONTINUUM_GEMINI_API_KEY로 폴백

api_key와 auth가 모두 지정된 경우, auth 블록이 우선되며 경고가 기록됩니다.

Gemini 사고 모델: 자동 max_tokens 조정

Gemini 사고(thinking) 모델(gemini-3.1-pro, gemini-3-flash, gemini-2.5-pro, -pro-preview 접미사가 있는 모델)은 응답 생성 전에 확장된 추론을 수행합니다. 응답 잘림을 방지하기 위해 라우터가 자동으로 max_tokens를 조정합니다:

조건 동작
max_tokens 미지정 자동으로 16384로 설정
max_tokens < 4096 자동으로 16384로 증가
max_tokens >= 4096 클라이언트 값 유지

이렇게 하면 클라이언트 라이브러리의 낮은 기본값으로 인한 잘림 없이 사고 모델이 완전한 응답을 생성할 수 있습니다.

Gemini 환경 변수

변수 설명
CONTINUUM_GEMINI_API_KEY Google Gemini API 키 (type: gemini 백엔드용으로 자동 로드)
GOOGLE_APPLICATION_CREDENTIALS 서비스 계정 JSON 키 파일 경로 (표준 GCP 환경 변수)

네이티브 Anthropic 백엔드

type: anthropic을 사용하면 라우터는 다음을 제공합니다:

  • 기본 URL: https://api.anthropic.com (프록시용으로 오버라이드 가능)
  • 네이티브 API 변환: OpenAI 형식 요청을 Anthropic Messages API 형식으로 자동 변환 및 역변환
  • Anthropic 전용 헤더: x-api-key 및 anthropic-version 헤더 자동 추가 (버전은 백엔드의 anthropic_version 값이며 "Anthropic API 버전" 참고)
  • 환경 변수 지원: CONTINUUM_ANTHROPIC_API_KEY에서 자동 로드
  • 확장 스트리밍 타임아웃: 확장 사고 모델용 600초 타임아웃 (Claude Opus, Sonnet 4)

최소 Anthropic 설정:

backends:
  - name: "anthropic"
    type: anthropic
    models:
      - claude-sonnet-5
      - claude-haiku-4-5

전체 Anthropic 설정:

backends:
  - name: "anthropic"
    type: anthropic
    api_key: "${CONTINUUM_ANTHROPIC_API_KEY}"
    weight: 2
    anthropic_fast_mode: false       # opt-in: 적합한 모델에 fast mode 활성화 (기본값 false)
    models:
      - claude-fable-5
      - claude-mythos-5   # 한정 출시(Project Glasswing); 승인된 액세스 필요
      - claude-opus-4-8
      - claude-opus-4-7
      - claude-opus-4-6
      - claude-sonnet-4-6
      - claude-haiku-4-5

Anthropic API 버전

anthropic_version은 라우터가 type: anthropic 백엔드로 보내는 anthropic-version 헤더 값을 정합니다. 값은 2023-06-01(필드가 없을 때의 기본값) 또는 2026-10-21입니다.

backends:
  - name: "anthropic"
    type: anthropic
    api_key: "${CONTINUUM_ANTHROPIC_API_KEY}"
    anthropic_version: "2026-10-21"
    models:
      - claude-sonnet-5

값은 따옴표로 감싸세요. YAML에서는 두 표기 모두 해석되지만, TOML에서는 따옴표 없는 날짜가 문자열이 아니라 날짜 리터럴로 해석됩니다.

버전이 적용되는 요청

이 설정은 라우터가 해당 백엔드로 보내는 모든 요청에 적용됩니다.

  • Messages API로 변환되는 OpenAI 형식 요청(/v1/chat/completions, 버퍼링 및 스트리밍)과 /v1/responses 요청. HTTP와 Unix 소켓 모두 해당합니다.
  • 네이티브 /anthropic/v1/messages 및 /anthropic/v1/messages/count_tokens 인그레스. 단, 클라이언트가 anthropic-version을 보내지 않은 경우에만 적용됩니다. 클라이언트 값은 어느 방향이든 그대로 전달되므로, 2023-06-01을 보낸 클라이언트는 상향되지 않고, 2026-10-21을 보낸 클라이언트는 기본값 백엔드에도 그 버전으로 도달합니다.
  • 헬스 체크(anthropic-version이라는 health_check.headers 항목이 있으면 그 값이 우선합니다), 실시간 모델 검색, 연결 사전 준비(pre-warm), Continuum Hub 프로브, Message Batch 호출.

외부로 나가는 인증은 x-api-key를 유지합니다. API는 x-api-key와 Authorization: Bearer를 모두 받지만 Foundry에서 호스팅되는 엔드포인트는 x-api-key를 요구하므로, 바꿔도 얻는 것이 없습니다. 들어오는 방향에서는 2026-10-21 SDK가 보내는 Authorization: Bearer <라우터 키>를 /anthropic/v1/messages에서 x-api-key와 똑같이 처리합니다. 인증, 허용 목록, 속도 제한 버킷, 사용량 귀속이 모두 같습니다.

2026-10-21에서 달라지는 요청

2026-10-21에서 Anthropic은 세 가지 요청 형태를 HTTP 400으로 거부합니다. 라우터는 OpenAI 및 Responses 형식에서 변환하는 본문을 다음과 같이 조정합니다.

거부되는 형태 라우터의 처리
모든 모델에서 temperature, top_p, top_k 변환된 본문에서 제거합니다. Claude Opus 4.7 이상에서 이미 하던 처리와 같습니다.
user 메시지 안에서 citations 배열이 비어 있지 않은 text 블록(직접 포함되거나 search_result 블록 안에 있는 경우) citations 배열을 제거합니다. assistant 메시지의 인용과 문서의 citations: {enabled: true}는 유지합니다.
assistant 메시지에 직접 들어 있는 document 블록 백엔드 이름과 버전을 밝힌 라우터 자체 400으로 요청을 거부합니다. 변환 과정에서 assistant의 파일 파트가 이런 블록이 되며, 내용을 user 차례로 옮기면 대화의 의미가 바뀌기 때문입니다.

네이티브 /anthropic/v1/messages 요청도 샘플링을 지원하지 않는 Anthropic 홉에서 클라이언트 샘플링 필드를 제거합니다. 클라이언트 버전이 백엔드 버전보다 우선하며, 모든 버전에서 샘플링을 거부하는 모델도 해당 필드를 제거합니다. 다른 제공자 홉에서는 클라이언트 샘플링 값을 유지하고, Bedrock 동작은 바뀌지 않습니다.

request_params의 샘플링 값

2026-10-21 백엔드로 보내질 request_params 또는 Continuum Hub의 샘플링 값은, 적용됐다고 보고한 뒤 버려지는 대신 디스패치 전에 라우터 자체 400으로 거부됩니다. OpenAI, Responses, 네이티브 Messages 경로 모두 같습니다. 이 동작은 2023-06-01에서 샘플링을 여전히 받는 모델(Claude Opus 및 Sonnet 4.7 미만, Haiku 4.5)에 영향을 줍니다.

상위 API에 대체 수단이 없으므로, 해당 백엔드 모델의 샘플링 필드를 정책에서 빼거나 그 모델을 서빙하는 백엔드는 2023-06-01로 유지하세요. continuum-router config validate는 목록에 있는 모델이 영향을 받을 때, 또는 백엔드에 모델 목록이 없고 정책이 전역으로 샘플링 필드를 지정할 때 backends[N].anthropic_version 경로로 경고합니다.

검증과 핫 리로드

  • 2023-06-01과 2026-10-21 외의 값은 로드 오류입니다. 다른 백엔드 타입에 이 필드를 지정해도 로드 오류이며, 본문 버전(bedrock-2023-05-31)에 문서화된 2026년 대응 버전이 없는 bedrock도 마찬가지입니다. 오류 메시지는 backends[N].anthropic_version을 가리킵니다.
  • 버전은 각 요청이 라우팅되는 설정에서 읽으므로, 핫 리로드, Admin API 백엔드 수정(생성과 수정에서 anthropic_version, 제거는 clear_anthropic_version), config-sync 변경은 다음 요청부터 적용됩니다(즉시 클래스). 프로브에 쓰이는 풀의 백엔드 객체도 같은 리로드에서 다시 만들어집니다.
  • Continuum Hub v1 백엔드 프로필에는 버전 필드가 없으므로, 로컬 내보내기는 이 필드가 지정된 경우 anthropic_version을 지원되지 않는 필드로 표시합니다.

Anthropic API 변환

라우터는 OpenAI와 Anthropic API 형식 간의 변환을 자동으로 처리합니다:

OpenAI 형식 Anthropic 형식
role: "system"이 있는 messages 배열 별도의 system 매개변수
Authorization: Bearer <key> x-api-key: <key> 헤더
선택적 max_tokens 필수 max_tokens (누락 시 자동 채움)
choices[0].message.content content[0].text
finish_reason: "stop" stop_reason: "end_turn"
finish_reason: "content_filter" stop_reason: "refusal"
usage.prompt_tokens usage.input_tokens

Anthropic이 stop_reason: "refusal"을 반환하면, 라우터는 이를 OpenAI 호환 finish_reason: "content_filter"로 매핑합니다. 업스트림 stop_details 객체(거부 카테고리 포함)는 응답 choice의 stop_details 필드에 그대로 전달되며 없을 경우 생략됩니다.

요청 변환 예제:

OpenAI 형식 (클라이언트에서 들어오는):

{
  "model": "claude-sonnet-4-5-20250929",
  "messages": [
    {"role": "system", "content": "당신은 도움이 됩니다."},
    {"role": "user", "content": "안녕하세요"}
  ],
  "max_tokens": 1024
}

Anthropic 형식 (API로 전송되는):

{
  "model": "claude-sonnet-4-5-20250929",
  "system": "당신은 도움이 됩니다.",
  "messages": [
    {"role": "user", "content": "안녕하세요"}
  ],
  "max_tokens": 1024
}

Anthropic 네이티브 API 엔드포인트

OpenAI 형식 요청을 Anthropic 백엔드로 라우팅하는 것 외에도, 라우터는 네이티브 Anthropic API 엔드포인트도 제공합니다:

엔드포인트 설명
POST /anthropic/v1/messages 네이티브 Anthropic Messages API
POST /anthropic/v1/messages/count_tokens 계층적 백엔드 지원을 통한 토큰 카운팅
GET /anthropic/v1/models Anthropic 형식의 모델 목록

이 엔드포인트를 통해 Anthropic의 네이티브 API 형식을 사용하는 클라이언트(예: Claude Code)가 요청/응답 변환 오버헤드 없이 직접 연결할 수 있습니다.

Claude Code 호환성

Anthropic 네이티브 API 엔드포인트는 Claude Code 및 기타 고급 Anthropic API 클라이언트와의 완전한 호환성을 포함합니다:

프롬프트 캐싱 지원:

라우터는 요청/응답 파이프라인 전체에서 cache_control 필드를 유지합니다:

  • 시스템 프롬프트 텍스트 블록
  • 사용자 메시지 콘텐츠 블록 (텍스트, 이미지, 문서)
  • 도구 정의
  • 도구 사용 및 도구 결과 블록

헤더 전달:

헤더 동작
anthropic-version 네이티브 Anthropic 백엔드로 전달됨
anthropic-beta 베타 기능 활성화를 위해 전달됨 (예: prompt-caching-2024-07-31, interleaved-thinking-2025-05-14)
x-request-id 요청 추적을 위해 전달됨

캐시 사용량 보고:

네이티브 Anthropic 백엔드의 스트리밍 응답에는 캐시 사용량 정보가 포함됩니다:

{
  "usage": {
    "input_tokens": 2159,
    "cache_creation_input_tokens": 2048,
    "cache_read_input_tokens": 0
  }
}

Anthropic의 input_tokens는 두 캐시 버킷을 모두 제외합니다. 프로토콜 브리지에 따라 라우터는 각 대상 API의 규칙에 맞춰 캐시 사용량을 변환합니다:

  • 정방향 브리지 (Anthropic/Bedrock -> /v1/chat/completions): OpenAI 규칙을 따릅니다. prompt_tokens는 input_tokens + cache_read_input_tokens + cache_creation_input_tokens가 되고, prompt_tokens_details.cached_tokens에 캐시 읽기가 담기며, 최상위 cache_read_input_tokens와 cache_creation_input_tokens도 함께 유지됩니다. Bedrock Converse도 동일한 방식(inputTokens + cacheReadInputTokens + cacheWriteInputTokens)으로 매핑됩니다.
  • 역방향 브리지 (OpenAI/Responses -> /anthropic/v1/messages): Anthropic 규칙을 따릅니다:
    • input_tokens를 업스트림 프롬프트 수에서 캐시 읽기(prompt_tokens_details.cached_tokens, input_tokens_details.cached_tokens, 또는 최상위 cache_read_input_tokens)와 캐시 쓰기를 뺀 값으로 보고합니다.
    • 두 버킷은 비스트리밍 본문과 스트리밍 message_start/message_delta 사용량 모두에서 cache_read_input_tokens와 cache_creation_input_tokens로 반환됩니다 (이미 Anthropic 형식인 사용량 블록은 그대로 통과합니다).
    • input_tokens + cache_read_input_tokens + cache_creation_input_tokens를 합산하는 클라이언트(컨텍스트 윈도우 계산기 등)는 비스트리밍 본문과 브리지 스트림(로컬 캐시 재생 및 TokenUsage::from_json_value의 최종 message_delta 재파싱 포함) 모두에서 캐시된 프롬프트 토큰을 중복 없이 한 번만 계산합니다.
    • 브리지된 스트림을 자체 네이티브 Anthropic 스트리밍 추적기로 계측하는 다운스트림 라우터도 동일한 합계에 도달합니다. 브리지된 스트림의 message_start에는 input_tokens: 0이 실리고 실제 입력·캐시 값은 message_delta에 도착하므로, 추적기는 message_delta에서도 input_tokens, cache_read_input_tokens, cache_creation_input_tokens를 읽습니다.
  • Responses API 브리지 (Anthropic -> /v1/responses): Responses API 규칙을 따릅니다. 비스트리밍 본문과 스트림의 response.completed 이벤트 모두에서 input_tokens는 캐시를 포함한 전체 프롬프트 수이며, input_tokens_details(형제 필드는 output_tokens_details, 입력 시 단수형 input_token_details도 허용)에 cached_tokens(캐시 읽기)와 cache_write_tokens(캐시 쓰기)가 담깁니다.

도구 호출 id 정규화:

Anthropic 프로토콜에서 tool_use.id는 대화 전체 범위를 가지는데 이 엔드포인트 뒤에 있는 일부 OpenAI 호환 백엔드는 응답 하나 안에서만 id 고유성을 보장합니다. 예를 들어 vLLM의 Kimi-K3 템플릿은 {tool_name}:{index} 카운터를 매 응답마다 다시 시작합니다. 라우터는 이미 대화 전체에서 고유하다고 알려지지 않은 아웃바운드 id를 crt_<nonce:12><base64url_nopad(raw_id)>로 다시 쓰고 클라이언트가 그 id를 재전송하면 원본 바이트를 복원합니다. 그래서 설정을 따로 하지 않아도 턴을 넘나드는 tool_use/tool_result 짝짓기가 유지됩니다. 이미 대화 전체에서 고유한 id(toolu_, call_, fc_, chatcmpl-tool- 접두사)는 그대로 통과합니다.

대화 중간 시스템 메시지:

첫 사용자 턴 이후의 role: "system" 메시지(Opus 4.8+ 형태로, Claude Code가 두 번째 턴부터 보냄)는 OpenAI 호환 백엔드에 system 롤로 제자리에 전달되어 지시가 system 우선순위를 유지합니다. chat template이 선두 system 항목 하나만 받는 모델은 이를 거부하므로, 라우터는 메타데이터에 requires_single_leading_system이 설정된 모델에 대해 항목을 합칩니다. 카탈로그는 Qwen3.8 계열에 이 값을 싣고 있습니다.

Anthropic 확장 사고 모델

확장 사고를 지원하는 모델 (Claude Opus, Sonnet 4, Claude Opus 4.7, Claude Opus 4.8, Claude Sonnet 5, Claude Haiku 4.5, Claude Haiku 5.5)은 더 긴 응답 시간이 필요할 수 있습니다. 라우터는 자동으로:

  • 사고 모델에 대해 더 높은 기본 max_tokens (16384) 설정
  • 이 모델에 대해 확장 스트리밍 타임아웃 (600초) 사용

Claude Opus 4.7/4.8, Claude Sonnet 5.5, Claude Haiku 5.5, Mythos급 모델(Fable 5, Mythos 5)에는 다음 규칙이 적용됩니다:

  • 적응형 사고 필수: 적응형 사고 API(thinking.type: "adaptive" + output_config.effort)를 사용해야 하며 레거시 budget_tokens 형식을 거부합니다. 라우터는 이 모델들에 대한 명시적 레거시 thinking.type: "enabled" 요청을 적응형 사고로 자동 정규화합니다.
  • 샘플링 파라미터 자동 제거: temperature, top_p, top_k를 허용하지 않으므로 라우터가 전달 전에 자동으로 제거합니다.
  • 명시적 사고 비활성화 생략: Fable 5, Mythos 5, Sonnet 5.5는 명시적 thinking.type: "disabled"를 HTTP 400으로 거부하므로 라우터가 해당 필드를 생략합니다.

Claude Sonnet 5.5

Claude Sonnet 5.5(claude-sonnet-5-5)는 적응형 사고와 low부터 max까지의 effort를 사용하며 reasoning_effort: "xhigh"는 max로 매핑됩니다. 명시적 비활성 thinking과 any, tool 강제 tool_choice를 거부합니다. 라우터는 비활성 thinking 필드를 생략하고 강제 선택은 전달 전에 거부합니다. auto와 none 도구 선택은 계속 허용됩니다.

Claude Fable 5

Claude Fable 5(claude-fable-5-*, alias claude-fable-5-latest)는 Anthropic의 가장 강력한 모델로, Opus 4.8보다 한 단계 위에 있는 Mythos급 플래그십입니다. 주요 특성:

  • 컨텍스트 윈도우: 입력 1M 토큰
  • 최대 출력: 128K 토큰
  • 가격: 입력/출력 100만 토큰당 \(10/\)50
  • 사고: 적응형 전용(thinking.type: "adaptive" + output_config.effort). 레거시 enabled + budget_tokens 형식은 HTTP 400을 반환하고 명시적 thinking.type: "disabled"도 HTTP 400을 반환합니다(라우터가 대신 thinking 파라미터를 생략).
  • Effort: low, medium, high, max를 지원합니다(reasoning_effort: "xhigh"는 max로 매핑).
  • 샘플링 파라미터: temperature, top_p, top_k를 허용하지 않습니다. 라우터가 전달 전에 자동으로 제거합니다.

Claude Mythos 5(claude-mythos-5-*, alias claude-mythos-5-latest)는 Fable 5와 동일한 기반 모델에서 안전 분류기를 해제한 버전으로, Anthropic의 한정 출시 프로그램인 Project Glasswing을 통해서만 제공됩니다. 위의 모든 특성(컨텍스트 윈도우, 최대 출력, 가격, 사고 effort, 샘플링 파라미터 처리)을 그대로 공유하며 라우터는 두 ID를 동일하게 취급합니다.

Claude Opus 4.8

Claude Opus 4.8(claude-opus-4-8-*, alias claude-opus-4-8-latest)은 Claude 4.8 계열의 플래그십 모델입니다. 주요 특성:

  • 컨텍스트 윈도우: 입력 1M 토큰
  • 최대 출력: 128K 토큰
  • 가격: 입력/출력 100만 토큰당 \(5/\)25
  • 학습 데이터 기준일: 2026년 1월
  • 사고: 적응형 전용(thinking.type: "adaptive" + output_config.effort). 레거시 enabled + budget_tokens 형식은 HTTP 400 반환
  • 샘플링 파라미터: temperature, top_p, top_k 허용 안 됨. 라우터가 전달 전 자동으로 제거
  • Effort 기본값: high. reasoning_effort를 생략하거나 auto로 설정하면 output_config.effort를 명시하지 않는 한 Anthropic이 high effort를 적용

Claude Haiku 5.5

Claude Haiku 5.5(claude-haiku-5-5)는 대량 처리와 짧은 지연 시간이 필요한 작업을 위한 Anthropic의 가장 빠른 모델입니다. ID는 날짜 접미사와 -latest 별칭이 없는 고정 값입니다. 주요 특성:

  • 컨텍스트 윈도우: 입력 1M 토큰
  • 최대 출력: 128K 토큰
  • 가격: 프롬프트 100,000 토큰 이하는 입력/출력 100만 토큰당 \(0.10/\)0.50, 100,000 토큰 초과는 \(0.50/\)2.50
  • 사고: 기본으로 켜져 있는 적응형 사고. 레거시 enabled + budget_tokens 형식은 HTTP 400을 반환하므로 라우터가 적응형 사고로 변환합니다. 명시적 thinking.type: "disabled"는 허용됩니다.
  • Effort: low부터 max까지 지원합니다(reasoning_effort: "xhigh"는 max로 매핑). Anthropic 기본값은 medium입니다.
  • 샘플링 파라미터: temperature, top_p, top_k를 허용하지 않습니다. 라우터가 전달 전에 자동으로 제거합니다.

Claude Haiku 4.5(claude-haiku-4-5-*)는 수동 확장 사고를 유지합니다. 라우터는 Sonnet 4.x와 마찬가지로 reasoning_effort를 thinking.budget_tokens 설정으로 변환합니다.

Anthropic Fast Mode

Fast mode는 Anthropic의 가속 추론 경로를 통해 적합한 Claude 모델의 지연 시간을 줄입니다. 백엔드별 opt-in 설정 플래그로 제어되며 네이티브 Anthropic 백엔드에서만 적용됩니다(Bedrock, Vertex AI 제외).

Fast Mode 활성화

백엔드 설정에서 anthropic_fast_mode: true로 설정합니다:

backends:
    - name: "anthropic-fast"
      type: anthropic
      api_key: "${CONTINUUM_ANTHROPIC_API_KEY}"
      anthropic_fast_mode: true
      models:
        - claude-opus-4-8
        - claude-opus-4-7
        - claude-opus-4-6

anthropic_fast_mode가 활성화되면 라우터는 적합한 요청에 anthropic-beta: fast-mode-2026-02-01 헤더를 추가합니다.

적합한 모델

Fast mode는 네이티브 Anthropic 백엔드의 Opus 4.6, 4.7, 4.8 모델에 적용됩니다. Bedrock 또는 Vertex AI 백엔드 요청은 이 플래그가 설정되어 있어도 무시됩니다.

Speed 필드 패스스루

클라이언트도 요청 본문의 speed 필드로 fast mode를 명시적으로 요청할 수 있습니다:

{
  "model": "claude-opus-4-8",
  "speed": "fast",
  "messages": [...]
}

speed 필드는 OpenAI 호환 경로에서 전달됩니다. 응답은 usage.speed에 결정된 속도를 반환합니다("fast" 또는 "standard").

가격

Fast mode 요청은 프리미엄 요금으로 청구됩니다. fast-mode 관련 비용은 Anthropic의 현재 가격 정책을 확인하세요.

백엔드 제한

Fast mode는 네이티브 Anthropic API 백엔드에서만 사용 가능합니다. Bedrock 및 Vertex AI 백엔드는 anthropic-beta: fast-mode-2026-02-01 헤더를 지원하지 않습니다.

대화 중간 시스템 메시지 (Claude Opus 4.8+)

Claude Opus 4.8부터 Anthropic API는 messages 배열 내 어느 위치에서나 role: "system" 메시지를 허용합니다. 라우터는 이를 지원하는 모델에서 이 기능을 활성화합니다.

모델 버전별 동작

모델 계열 대화 중간 role: "system" 지원
Claude Fable 5 / Mythos 5 (claude-fable-5-*, claude-mythos-5-*) 배열 내 어느 위치에서든 유지
Claude Opus 4.8+ (claude-opus-4-8-*) 배열 내 어느 위치에서든 유지
Claude 4.7 이하 (및 모든 Sonnet/Haiku) 평탄화: 첫 사용자 턴 이후의 시스템 메시지를 포함한 모든 시스템 메시지가 최상위 system 필드로 합쳐짐(배열 내 유지 안 됨)

동작 방식

라우터가 첫 번째 사용자 턴 이후에 role: "system" 항목이 포함된 messages 배열을 수신하고 대상 모델이 Opus 4.8 이상인 경우, 해당 항목은 messages 배열 내에 그대로 유지됩니다. 첫 번째 사용자 턴 이전의 선두 시스템/개발자 메시지는 여전히 최상위 system 필드를 채웁니다.

Claude 4.7 이하 모델에서는 모든 시스템 메시지가 추출되어 최상위 system 필드로 합쳐지며 대화 중간의 시스템 메시지도 배열 내에 유지되지 않고 이 최상위 필드로 함께 합쳐집니다.

대화 중간 시스템 메시지가 포함된 요청 예시:

{
  "model": "claude-opus-4-8",
  "messages": [
    {"role": "system", "content": "당신은 도움이 되는 어시스턴트입니다."},
    {"role": "user", "content": "프랑스의 수도는 어디인가요?"},
    {"role": "assistant", "content": "파리입니다."},
    {"role": "system", "content": "이제 한국어로만 응답하세요."},
    {"role": "user", "content": "독일의 수도는요?"}
  ]
}

Opus 4.8+ 에서는 라우터가 선두 시스템 메시지를 최상위 system 필드로 보내고 대화 중간의 role: "system" 항목을 Anthropic으로 전송되는 messages 배열 내에 그대로 유지합니다.

OpenAI <-> Claude 추론 매개변수 변환

라우터는 OpenAI의 추론 매개변수와 Claude의 thinking 매개변수 간을 자동으로 변환하여 크로스 프로바이더 추론 요청을 가능하게 합니다.

지원되는 OpenAI 형식:

형식 API 예제
reasoning_effort (플랫) Chat Completions API "reasoning_effort": "high"
reasoning.effort (중첩) Responses API "reasoning": {"effort": "high"}

두 형식이 모두 있으면 reasoning_effort (플랫)가 우선합니다.

Effort 레벨에서 Budget 토큰 매핑:

Effort 레벨 Claude thinking.budget_tokens
none (사고 비활성화)
minimal 1,024
low 4,096
medium 10,240
high 32,768

요청 예제 - Chat Completions API (플랫 형식):

// 클라이언트가 OpenAI Chat Completions API 요청을 보냄
{
  "model": "claude-sonnet-4-6",
  "reasoning_effort": "high",
  "messages": [{"role": "user", "content": "이 복잡한 문제를 풀어주세요"}]
}

// 라우터가 Claude 형식으로 변환
{
  "model": "claude-sonnet-4-6",
  "thinking": {"type": "enabled", "budget_tokens": 32768},
  "messages": [{"role": "user", "content": "이 복잡한 문제를 풀어주세요"}]
}

요청 예제 - Responses API (중첩 형식):

// 클라이언트가 OpenAI Responses API 요청을 보냄
{
  "model": "claude-sonnet-4-6",
  "reasoning": {"effort": "medium"},
  "messages": [{"role": "user", "content": "이 데이터를 분석해주세요"}]
}

// 라우터가 Claude 형식으로 변환
{
  "model": "claude-sonnet-4-6",
  "thinking": {"type": "enabled", "budget_tokens": 10240},
  "messages": [{"role": "user", "content": "이 데이터를 분석해주세요"}]
}

추론 콘텐츠가 있는 응답:

{
  "choices": [{
    "message": {
      "role": "assistant",
      "content": "최종 답변은...",
      "reasoning_content": "단계별로 분석해 보겠습니다..."
    }
  }]
}

중요 사항:

  • thinking 매개변수가 명시적으로 제공되면 reasoning_effort 및 reasoning.effort보다 우선합니다
  • reasoning_effort (플랫)가 두 형식이 모두 있을 때 reasoning.effort (중첩)보다 우선합니다
  • 확장 사고를 지원하는 모델 (Opus 4.x, Sonnet 4.x, Opus 4.7, Opus 4.8, Haiku 4.5, Haiku 5.5)만 추론이 활성화됩니다
  • 추론이 활성화되면 temperature 매개변수가 자동으로 제거됩니다 (Claude API 요구 사항)
  • Claude Opus 4.7, Opus 4.8, Haiku 5.5에서는 사고 활성화 여부와 상관없이 temperature, top_p, top_k가 항상 제거됩니다
  • 스트리밍 응답의 경우 사고 콘텐츠가 reasoning_content delta 이벤트로 반환됩니다

Anthropic 환경 변수

변수 설명
CONTINUUM_ANTHROPIC_API_KEY Anthropic API 키 (type: anthropic 백엔드용으로 자동 로드)

Amazon Bedrock 백엔드

type: bedrock을 사용하면 라우터가 요청을 Amazon Bedrock을 통해 라우팅합니다. 진입점은 셋이 있습니다.

항목 mantle runtime converse
URL https://bedrock-mantle.{region}.api.aws/anthropic/v1/messages https://bedrock-runtime.{region}.amazonaws.com/model/{modelId}/invoke[-with-response-stream] https://bedrock-runtime.{region}.amazonaws.com/model/{modelId}/converse[-stream]
모델 Anthropic Claude 전용 Anthropic Claude 전용 Bedrock의 모든 채팅 모델 (Claude, Nova, Llama, Mistral, Cohere, AI21 Jamba, DeepSeek 등)
요청 본문 네이티브 Anthropic Messages API와 동일 같은 형식에 "anthropic_version": "bedrock-2023-05-31"을 추가하고 model은 URL 경로로 이동합니다. 라우터가 OpenAI 형식에서 변환한 Converse 태그드 콘텐츠 블록 형식
인증 Authorization: Bearer $AWS_BEARER_TOKEN_BEDROCK AWS SigV4 서명 AWS SigV4 서명
스트리밍 표준 text/event-stream AWS 이진 event-stream (application/vnd.amazon.eventstream) AWS 이진 event-stream (application/vnd.amazon.eventstream)
헤더 anthropic-version 없음, x-api-key 없음 Content-Type: application/json, Accept: application/json 또는 application/vnd.amazon.eventstream runtime과 동일
Cargo 기능 항상 사용 가능 빌드 시 --features bedrock-sigv4 필요 빌드 시 --features bedrock-sigv4 필요

세 모드 모두 동일한 BackendTypeConfig::Bedrock 변형과 동일한 models: 설정 인터페이스를 공유합니다. 모드 구분은 클라이언트에 보이지 않으며 클라이언트는 그대로 /v1/chat/completions로 호출하면 프록시가 본문과 헤더를 변환합니다. endpoint_type 설정값이 유일한 분기점이고 기본값은 mantle입니다.

Mantle 설정

backends:
    - name: bedrock
      type: bedrock                      # 별칭: aws-bedrock, bedrock-anthropic, AmazonBedrock
      endpoint_type: mantle              # 기본값
      region: us-east-1                  # 필수; URL에 템플릿됨
      api_key: ${AWS_BEARER_TOKEN_BEDROCK}
      weight: 2
      models:
        - anthropic.claude-opus-4-7
        - us.anthropic.claude-sonnet-4-5
        - anthropic.claude-haiku-4-5
        # global.anthropic.<family>은 AWS 최저가 크로스 리전 티어를 사용합니다.
        # eu.<family>, jp.<family>, au.<family>은 해당 지역 내에서 라우팅됩니다.

리전 선택

라우터는 region을 https://bedrock-mantle.{region}.api.aws에 템플릿하여 업스트림 URL을 만듭니다. AWS가 리전을 정기적으로 추가하기 때문에 소문자로 된 비어 있지 않은 리전 식별자라면 모두 허용됩니다. us-east-1, us-west-2, eu-west-1, ap-northeast-1 등을 그대로 사용할 수 있습니다. 빈 값이나 대문자가 포함된 리전은 설정 로드 시 거부됩니다.

Bedrock 전용 포워드 프록시 환경에서는 백엔드에 명시적인 url: 필드를 지정해 리전 템플릿을 덮어쓸 수 있습니다. 일반 운영자는 url을 설정하지 않고 region만으로 URL을 결정하는 방식을 권장합니다.

모델 ID 형식

Bedrock 모델 식별자는 네 가지 형식을 가지며 라우터는 모두 인식하고 업스트림으로 그대로 전달합니다:

형식 예시 동작
일반 Anthropic anthropic.claude-opus-4-7 백엔드에 설정된 리전으로 라우팅됩니다.
지리적 프로파일 us.anthropic.claude-sonnet-4-5, eu.anthropic.claude-opus-4-7, jp.anthropic.claude-haiku-4-5, au.anthropic.claude-opus-4-7 AWS가 해당 지역 내에서 라우팅합니다. 데이터 레지던시 요건이 있을 때 선택합니다.
글로벌 프로파일 global.anthropic.claude-opus-4-7 AWS가 전 세계에서 가장 낮은 지연 리전을 선택합니다. 인퍼런스 프로파일의 최저가 티어입니다.
전체 ARN arn:aws:bedrock:us-east-1:123456789012:inference-profile/... 고객 관리형 인퍼런스 프로파일이나 크로스 계정 참조에 사용합니다.

모델 ID는 models: 목록에 명시적으로 나열합니다. 네이티브 Anthropic ID에서 Bedrock ID로 자동 매핑하는 기능은 의도적으로 구현하지 않았습니다. 지역 접두사는 실제 청구와 데이터 레지던시 결과를 수반하므로, 설정에서 명시적으로 표현되어야 합니다.

지원 기능

bedrock-mantle 경로는 네이티브 Anthropic 백엔드가 지원하는 모든 기능을 그대로 상속합니다:

  • 스트리밍 SSE 응답 (Anthropic SSE → OpenAI SSE 변환기를 그대로 재사용)
  • 시스템 프롬프트 (OpenAI messages[role=system]에서 Anthropic의 별도 system 필드로 변환)
  • 도구 호출 및 도구 결과 왕복
  • 비전 (base64 또는 URL 형식의 이미지 입력)
  • Claude 4 시리즈 모델의 확장 사고 Opus 4.7의 adaptive thinking API 포함

인증

api_key: 필드에 Bedrock Bearer 토큰을 지정합니다. 운영 환경에서는 리터럴 문자열 대신 환경 변수를 사용합니다:

api_key: ${AWS_BEARER_TOKEN_BEDROCK}

라우터는 모든 요청에 Authorization: Bearer ${AWS_BEARER_TOKEN_BEDROCK}를 전송하고 클라이언트가 보낸 x-api-key와 anthropic-version 헤더를 포워딩 전에 제거합니다. Bedrock은 이 Anthropic 전용 헤더가 있으면 HTTP 400을 반환하기 때문입니다.

Runtime 설정 (bedrock-runtime)

runtime 모드는 AWS 네이티브 Invoke API인 https://bedrock-runtime.{region}.amazonaws.com/model/{modelId}/invoke[-with-response-stream]로 요청을 보냅니다. 라우터가 각 요청에 SigV4 서명을 붙이고 이진 application/vnd.amazon.eventstream 스트리밍 응답을 다시 OpenAI 형식 SSE로 디코딩해서 클라이언트에 전달합니다.

빌드 요건

runtime 경로는 bedrock-sigv4 Cargo 기능 뒤에 위치하며 서명과 event-stream 파싱에 필요한 최소한의 AWS SDK 조각(aws-sigv4, aws-smithy-eventstream, aws-credential-types, aws-config)만 끌어옵니다. 기본 빌드에는 이 크레이트들이 포함되지 않습니다. runtime 백엔드를 사용하기 전에 기능을 활성화해서 빌드합니다.

cargo build --release --features bedrock-sigv4

기능 없이 endpoint_type: runtime을 설정하면 시작 시 재빌드 플래그를 안내하는 오류가 표시되고 기본 mantle 경로는 기능 활성화 여부와 무관하게 동작합니다.

설정 예시
backends:
    - name: bedrock-iam
      type: bedrock
      endpoint_type: runtime               # SigV4 경로 선택
      region: us-east-1
      weight: 1
      auth:
        type: sigv4                        # runtime에는 필수
        # 아래 자격 증명 오버라이드 중 최대 하나만 지정합니다. 아무것도
        # 지정하지 않으면 표준 AWS 체인(env, shared config, IMDS, IRSA,
        # ECS)이 자격 증명을 해석합니다.
        # aws:
        #   profile: my-bedrock-profile
        # aws:
        #   access_key_id: ${AWS_ACCESS_KEY_ID}
        #   secret_access_key: ${AWS_SECRET_ACCESS_KEY}
        #   session_token: ${AWS_SESSION_TOKEN}
      models:
          - anthropic.claude-opus-4-7
          - us.anthropic.claude-sonnet-4-5
          - global.anthropic.claude-haiku-4-5
          # 프로비저닝된 처리량이나 커스텀 인퍼런스 프로파일을 위한 전체 ARN도 지원합니다:
          # - arn:aws:bedrock:us-east-1:123456789012:inference-profile/anthropic.claude-opus-4-7
자격 증명 해석 순서

runtime 백엔드는 다음 순서로 자격 증명을 해석합니다.

  1. 인라인 정적 자격 증명: auth.aws.access_key_id + auth.aws.secret_access_key. STS에서 발급한 임시 자격 증명은 선택적 session_token을 함께 지정합니다.
  2. 이름 지정 프로파일: auth.aws.profile로 ~/.aws/credentials와 ~/.aws/config에서 가져옵니다.
  3. 표준 AWS 체인: 환경 변수(AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_SESSION_TOKEN), shared config, IMDS(EC2), IRSA / EKS pod identity, ECS task role 순으로 시도합니다.

요청마다 체인을 호출하지만 내부 AWS 프로바이더가 자체 TTL로 캐시하므로 실제 비용은 보통 네트워크 호출이 아닌 해시맵 조회 수준입니다.

필요한 IAM 권한

사용하려는 모델 ARN에 대해 bedrock:InvokeModel과 bedrock:InvokeModelWithResponseStream 권한을 부여하는 정책을 연결합니다. 최소 예시는 다음과 같습니다.

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "bedrock:InvokeModel",
                "bedrock:InvokeModelWithResponseStream"
            ],
            "Resource": [
                "arn:aws:bedrock:us-east-1::foundation-model/anthropic.claude-opus-4-7",
                "arn:aws:bedrock:us-east-1:*:inference-profile/us.anthropic.claude-sonnet-4-5"
            ]
        }
    ]
}

지리적/글로벌 인퍼런스 프로파일을 사용하는 경우, 라우터는 프로파일 ID를 URL 경로로 그대로 사용하고 AWS가 서버 측에서 확장합니다. models:에서 인퍼런스 프로파일을 참조한다면 IAM 리소스 목록에 해당 인퍼런스 프로파일 ARN과 그 기반 파운데이션 모델 ARN을 둘 다 포함해야 합니다.

지리적/글로벌 프로파일

네 가지 모델 ID 형식은 runtime에서도 mantle과 동일하게 동작하지만 IAM 검증을 위해 위에서 언급한 자원 ARN을 정확히 지정해야 한다는 점만 다릅니다. 데이터 레지던시와 비용 요구사항에 따라 접두사를 선택합니다.

접두사 AWS 라우팅 위치 청구 티어
anthropic.<family> 백엔드에 설정된 리전. 리전별 단가.
us.<family>, eu.<family>, jp.<family>, au.<family> 지정된 지리적 권역 내. 리전별 단가; 단일 고정 리전 대비 지연이 낮아질 수 있습니다.
global.<family> 요청 시점에 AWS가 가장 빠르다고 판단한 리전. 최저가 티어.
전체 ARN 인퍼런스 프로파일이 가리키는 위치. ARN 기반 프로파일의 청구 기준.
스트리밍 세부사항

runtime 스트리밍 응답은 application/vnd.amazon.eventstream 프레임으로 도착합니다. 각 chunk 프레임에는 base64로 인코딩된 JSON이 들어 있고 디코딩하면 Anthropic SSE 이벤트(message_start, content_block_delta 등) 하나가 됩니다. 라우터가 이 흐름을 클라이언트가 기대하는 OpenAI 형식 SSE로 다시 변환해주기 때문에, mantle 경로에서 이미 동작하던 애플리케이션은 코드를 바꾸지 않고 그대로 사용할 수 있습니다. AWS 전용 오류 프레임(ThrottlingException, ValidationException 등)은 합성 event: error SSE 청크로 노출됩니다.

제한사항
  • runtime 경로는 의도적으로 Invoke API에 범위를 한정합니다. 멀티 프로바이더 Bedrock 모델(Nova, Llama, Mistral 등)은 아래의 endpoint_type: converse 모드로 접근합니다.
  • Invoke 호출의 인라인 X-Amzn-Bedrock-GuardrailIdentifier 헤더는 이 백엔드에 연결되어 있지 않습니다. Bedrock 가드레일을 적용하려면 모델 호출과 독립적으로 ApplyGuardrail API를 호출하여 모든 백엔드에서 동작하는 가드레일 설정의 독립형 bedrock_guardrail 프로바이더를 사용하세요.
  • Bedrock Prompt management(prompt-router ARN)는 범위 밖입니다.
  • 프로비저닝된 처리량과 Application Inference Profile ARN은 다른 ARN과 같은 URL 인코딩 경로로 동작해야 하지만 자동 테스트는 파운데이션 모델 ARN과 인퍼런스 프로파일 ARN까지만 보장합니다.

Converse 설정 (멀티 프로바이더)

endpoint_type: converse 모드는 https://bedrock-runtime.{region}.amazonaws.com/model/{modelId}/converse[-stream]의 Converse API로 요청을 보내므로, 백엔드 하나로 Bedrock의 모든 채팅 모델을 하나의 통합 와이어 형식으로 사용할 수 있습니다. runtime 경로의 SigV4 서명, 자격 증명 체인, 이진 event-stream 디코딩을 그대로 재사용하기 때문에 동일한 bedrock-sigv4 Cargo 기능 뒤에 위치합니다.

backends:
    - name: bedrock-converse
      type: bedrock
      endpoint_type: converse            # 별칭: Converse, bedrock-converse
      region: us-east-1
      auth:
        type: sigv4                      # endpoint_type: runtime과 같은 인증 인터페이스
      models:
        # Anthropic (endpoint_type: runtime으로도 접근 가능; 여기 나열하면
        # Converse를 통해 노출되어 통합 도구가 동작합니다)
        - anthropic.claude-opus-4-7
        - anthropic.claude-sonnet-4-5
        # Amazon Nova
        - amazon.nova-pro-v1:0
        - amazon.nova-lite-v1:0
        - amazon.nova-micro-v1:0
        # Meta Llama
        - meta.llama4-maverick-17b-instruct-v1:0
        - meta.llama3-3-70b-instruct-v1:0
        # Mistral
        - mistral.mistral-large-2407-v1:0
        # Cohere
        - cohere.command-r-plus-v1:0
        - cohere.command-r-v1:0
        # AI21 Jamba
        - ai21.jamba-1-5-large-v1:0
        # DeepSeek
        - deepseek.deepseek-r1-v1:0

클라이언트는 표준 OpenAI 형식의 /v1/chat/completions 요청을 그대로 보냅니다. 라우터가 메시지를 Converse 콘텐츠 블록으로 변환하고(시스템 프롬프트는 최상위 system 배열로 이동, 도구는 toolConfig / toolUse / toolResult로, 샘플링 파라미터는 inferenceConfig로 매핑), SigV4 서명을 붙이고 응답(이진 converse-stream 이벤트 스트림 포함)을 다시 OpenAI 형식으로 변환합니다. 이미 Converse 네이티브 본문(inferenceConfig, toolConfig, 최상위 system 배열)을 담은 요청은 감지되어 최소한의 변형만 거쳐 전달되며 이중 변환은 일어나지 않습니다.

모델별 기능 매트릭스

Bedrock 모델들은 받아들이는 기능 범위가 크게 다릅니다. 라우터는 AWS 공개 데이터로 시딩한 하드코딩 기능 테이블을 유지하고 대상 모델이 지원하지 않는 기능을 요구하는 요청을 서명이나 AWS 왕복 이전에 거부된 기능 이름을 담은 HTTP 4xx로 거절합니다. 라우터가 모르는 모델은 차단하지 않으며 그 경우 AWS가 최종 판단합니다.

모델 패밀리 도구 비전 문서 프롬프트 캐싱 추론
anthropic.claude-* 지원 지원 지원 지원 지원
amazon.nova-micro 지원 미지원 미지원 지원 미지원
amazon.nova-lite / nova-pro 지원 지원 지원 지원 미지원
meta.llama4-* 지원 지원 미지원 미지원 미지원
meta.llama3-1-* / llama3-3-* 지원 미지원 미지원 미지원 미지원
mistral.mistral-large-* 지원 미지원 지원 미지원 미지원
cohere.command-r* 지원 미지원 지원 미지원 미지원
ai21.jamba-1-5-* 지원 미지원 지원 미지원 미지원
deepseek.* 미지원 미지원 미지원 미지원 지원

나열된 모든 패밀리는 채팅, 스트리밍, 가드레일을 지원합니다. 지역 접두사(us., eu., apac., global. 등)와 전체 인퍼런스 프로파일 ARN은 조회 전에 정규화되므로 us.amazon.nova-pro-v1:0도 amazon.nova-pro 행에 매칭됩니다.

additionalModelRequestFields를 통한 벤더 전용 옵션

라우터가 모든 벤더 옵션을 개별 필드로 모델링하지는 않습니다. 벤더 전용 기능을 사용하려면 OpenAI 요청의 최상위 extra_body 객체에 넣거나(또는 additionalModelRequestFields를 직접 전송) 하면, 라우터가 해당 필드를 그대로 전달합니다. 예: Claude 확장 사고 Nova 추론 확장.

{
  "model": "anthropic.claude-sonnet-4-5",
  "messages": [{ "role": "user", "content": "..." }],
  "extra_body": {
    "thinking": { "type": "enabled", "budget_tokens": 2048 }
  }
}

extra_body 안의 guardrailConfig 객체는 AWS가 기대하는 Converse 최상위 레벨로 올려집니다. 벤더 전용 응답 확장은 additionalModelResponseFields로 그대로 돌아옵니다.

비전 입력

이미지 콘텐츠 블록은 base64 데이터 URL을 직접 받습니다. 원격 http(s) 이미지 URL은 라우터가 가져와서(SSRF 검증, 크기 제한 적용) base64로 인라인합니다. Converse API에는 URL 이미지 소스가 없기 때문입니다.

Claude 라우팅 우선순위

전용 Claude 경로(endpoint_type: runtime)와 Converse 백엔드가 같은 Claude 모델을 나열하면 표준 로드 밸런서 규칙을 따르며 암묵적인 우선순위는 없습니다. Anthropic 네이티브 기능(anthropic_beta, 세밀한 사고 제어)이 필요한 운영자는 해당 모델을 runtime 백엔드로 명시적으로 라우팅하고 A/B 비교가 필요하면 가중치 로드 밸런서로 두 경로에 트래픽을 분산할 수 있습니다.

필요한 IAM 권한

Converse는 runtime 경로와 같은 bedrock:InvokeModel과 bedrock:InvokeModelWithResponseStream IAM 액션을 사용하므로, 위에 제시한 정책이 두 모드를 모두 커버합니다.

Converse 제한사항
  • 채팅 모델 전용입니다. 이미지 생성(Stability, Titan Image), 임베딩(Titan Embeddings, Cohere Embed), 비디오 모델은 Converse API에 없으므로 범위 밖입니다.
  • converse 백엔드는 네이티브 Anthropic Messages 엔드포인트(/v1/messages)를 제공하지 않으며 해당 요청은 endpoint_type: runtime 사용을 안내하는 오류로 거절됩니다.
  • 오디오 입력은 Converse API가 지원하지 않으므로 명확한 오류로 거절됩니다.

네이티브 vLLM 백엔드

type: vllm을 사용하면 라우터는 해당 백엔드를 vLLM OpenAI 호환 서버로 다룹니다:

  • 기본 URL: 없음. 서버 URL을 지정해야 합니다 (vLLM 자체 기본값은 http://localhost:8000)
  • 헬스 체크: /health를 기본으로 사용하고 (모델 로드 후 사용 가능), /v1/models로 폴백
  • 모델 검색: /v1/models에서 자동 검색하며 명시적인 model-metadata.yaml 제한이 없으면 각 항목의 max_model_len이 모델 메타데이터의 컨텍스트 윈도우로 반영됩니다. 여러 백엔드가 같은 모델 ID를 서로 다른 max_model_len으로 서빙하면 모든 백엔드가 지킬 수 있는 최솟값을 게시하고 불일치를 디버그 레벨로 기록합니다 (model-metadata.yaml 또는 model-metadata.d/의 명시적 제한은 항상 우선합니다)
  • 토큰 계산: /v1/messages/count_tokens는 엔진의 /tokenize 엔드포인트로 토큰화합니다
  • 패스스루: 비표준 샘플링 필드(top_k, min_p, repetition_penalty 등)를 그대로 전달합니다
  • 엔진 통계: engine_stats.enabled: true이면 라우터가 vLLM의 GET /metrics(API 포트에서 제공되며 추가 서버 플래그가 필요 없고 --api-key의 보호 대상이 아님)를 스크레이프하고 컨텍스트 길이는 /v1/models에서, 엔진 버전은 /version에서 읽습니다

최소 vLLM 설정:

backends:
  - name: "vllm-server"
    type: vllm
    url: "http://localhost:8000"
    # 서버를 --api-key로 시작한 경우에만 api_key 지정

네이티브 SGLang 백엔드

type: sglang을 사용하면 라우터는 SGLang(sglang.srt HTTP 서버이자 SGLang Model Gateway의 엔진)을 네이티브로 지원합니다. 지원 별칭: SGLang, sg-lang, sg_lang.

  • 기본 URL: http://localhost:30000 (sglang.srt 기본 포트)
  • 헬스 체크: /health를 기본으로 사용하고 /v1/models로 폴백합니다. SGLang의 /health는 엔진이 시작 중이거나 종료 중일 때 503을 반환하며 라우터는 이를 워밍업/비정상 상태로 다룹니다
  • 모델 검색: /v1/models에서 자동 검색하며 명시적인 model-metadata.yaml 제한이 없으면 각 항목의 max_model_len(모델 컨텍스트 길이와 동일)이 모델 메타데이터의 컨텍스트 윈도우로 반영됩니다. 여러 백엔드가 같은 모델 ID를 서로 다른 max_model_len으로 서빙하면 모든 백엔드가 지킬 수 있는 최솟값을 게시하고 불일치를 디버그 레벨로 기록합니다 (model-metadata.yaml 또는 model-metadata.d/의 명시적 제한은 항상 우선합니다)
  • 토큰 계산: /v1/messages/count_tokens는 vLLM 형식의 {"model", "prompt"} 페이로드로 엔진의 /v1/tokenize에서 토큰화합니다
  • Responses API: /v1/responses 요청은 vLLM 계열의 다른 타입과 마찬가지로 챗 컴플리션으로 변환됩니다
  • 패스스루: SGLang 요청 확장(top_k, min_p, min_tokens, regex, ebnf, repetition_penalty, lora_path, session_params, separate_reasoning, chat_template_kwargs 등)을 그대로 전달하고 메시지와 스트림 델타의 reasoning_content를 보존하며 stream_options.include_usage 설정 시 마지막 usage 청크도 그대로 전달합니다
  • 인증: 선택 사항. 서버를 --api-key로 시작한 경우 api_key를 설정하면 라우터가 Authorization: Bearer를 전송합니다 (키가 있어도 SGLang의 /health는 열려 있습니다)

최소 SGLang 설정:

backends:
  - name: "sglang"
    type: sglang
    # 기본 http://localhost:30000 사용 시 URL 불필요
    # 서버가 키를 강제하지 않으면 API 키 불필요

전체 SGLang 설정:

backends:
  - name: "sglang"
    type: sglang
    url: "http://192.168.1.100:30000"  # 필요 시 커스텀 URL
    weight: 2
    api_key: "${SGLANG_API_KEY}"       # 선택: --api-key로 시작한 경우에만
    # 모델은 /v1/models에서 자동 검색됩니다

엔진 통계: engine_stats.enabled: true이면 라우터가 GET /v1/loads?include=core를 읽습니다. 추가 서버 플래그는 필요 없고 백엔드에 키가 있으면 Bearer 키를 함께 보냅니다. /v1/loads가 없는 구버전 서버는 사용 중단된 GET /get_load로, 그다음 --enable-metrics로 시작해야 하는 GET /metrics로 폴백합니다. 데이터 병렬 배포는 DP 랭크마다 항목을 보고하며 라우터는 개수를 합산하고 사용률은 랭크 간 최댓값을 취합니다.

네이티브 TensorRT-LLM 백엔드

type: tensorrt-llm을 사용하면 라우터는 백엔드를 TensorRT-LLM HTTP/OpenAI 호환 서버로 다룹니다. 지원 별칭: TensorRTLLM, TensorRT-LLM, tensorrtllm, tensorrt_llm, trtllm, trt-llm, trt_llm.

  • 기본 URL: 없음. 서버 URL을 지정해야 합니다
  • 헬스 체크: /health를 기본으로 사용하고 /v1/models로 폴백합니다
  • 모델 검색: vLLM 호환 카탈로그 파서를 통해 /v1/models에서 자동 검색합니다
  • 토큰 계산: /v1/messages/count_tokens는 vLLM 호환 /tokenize 경로로 토큰화합니다
  • Responses API: /v1/responses 요청은 vLLM 계열의 다른 타입과 마찬가지로 챗 컴플리션으로 변환됩니다
  • KV 리스너 salt echo: prefix_routing.enabled: true와 prefix_routing.salt_echo: true이면, 클라이언트가 이미 cache_salt를 제공하지 않은 챗 컴플리션 요청에 라우터가 자체 cache_salt를 삽입합니다. identity: salt_echo로 설정된 continuum-kv-listener 소스는 TensorRT-LLM blocks[].cache_salt 이벤트를 읽어 /detokenize 없이 블록을 귀속할 수 있습니다. 형식이 잘못됐거나 백엔드 이름이 맞지 않는 salt는 detokenization으로 폴백합니다.
backends:
  - name: "trtllm"
    type: tensorrt-llm
    url: "http://localhost:8000"

엔진 토크나이저 엔드포인트

POST /tokenize와 POST /detokenize(별칭 /v1/tokenize, /v1/detokenize)는 요청한 모델을 서빙하는 엔진의 토크나이저에 도달합니다. 지원 여부는 백엔드 type으로 정해지며 별도의 설정 필드는 없습니다.

백엔드 타입 토크나이저 엔드포인트 엔진 경로
vllm 지원 /tokenize, /detokenize
sglang 지원 /v1/tokenize, /v1/detokenize
llamacpp, mlxcel prompt만 지원, messages는 거부 /tokenize, /detokenize
tensorrt-llm, ollama, lmstudio 미지원: 현재 릴리스에서 공개된 토큰화/역토큰화 경로 쌍을 찾지 못함 (2026-10-02 확인)
generic, openai, azure, anthropic, gemini, bedrock, continuumrouter 미지원

네 라우터 경로는 요청을 받을 때 서로 바꿔 쓸 수 있으며 엔진에 보내는 경로는 표와 같이 백엔드 타입으로만 정해집니다. 엔진 경로는 backends[].url 뒤에 붙고 http://host:8000/v1처럼 /v1로 끝나는 기본 URL은 끝의 /v1 하나를 먼저 제거합니다. 따라서 vllm, llamacpp, mlxcel은 여전히 /tokenize, /detokenize로, sglang은 /v1/tokenize, /v1/detokenize로 호출됩니다. /openai를 포함한 다른 경로 접두사는 유지됩니다.

vLLM은 /tokenize와 /detokenize를 조건 없이 등록합니다. 이는 vLLM main의 커밋 af2a582d5c를 읽어 확인한 것(register_vllm_serve_api_routers가 작업 종류나 플래그 조건 없이 attach_tokenize_router를 호출)이며 실제 서버 테스트가 아닙니다. --enable-tokenizer-info-endpoint가 필요한 것은 GET /tokenizer_info뿐이고 라우터는 이 경로를 프록시하지 않습니다.

llamacpp의 경로와 응답 형식은 /detokenize의 응답 {"content": "..."}를 포함해 llama-server 빌드 11344에서 검증했습니다. mlxcel 행은 해당 프로젝트의 경로 소스를 따른 것이고 tensorrt-llm, ollama, lmstudio의 미지원 행은 각 프로젝트의 소스나 API 문서에서 확인한 것입니다. 이 네 가지는 실행 중인 엔진으로 검증하지 않았습니다.

미지원 타입만 서빙하는 모델은 501 tokenization_not_supported로 답합니다. 두 종류가 함께 서빙하는 모델이면 요청은 항상 지원 백엔드로 갑니다. 계약은 토큰화와 역토큰화를 참고하세요.

네이티브 llama.cpp 백엔드

type: llamacpp를 사용하면 라우터는 llama.cpp llama-server에 대한 네이티브 지원을 제공합니다:

  • 기본 URL: http://localhost:8080 (llama-server 기본 포트)
  • 헬스 체크: /health 엔드포인트 사용 (/v1/models로 폴백)
  • 모델 검색: llama-server의 하이브리드 /v1/models 응답 형식 파싱
  • 풍부한 메타데이터: 응답에서 컨텍스트 윈도우, 파라미터 수, 모델 크기 추출
  • 엔진 통계: engine_stats.enabled: true이면 라우터가 GET /slots(기본 활성, --no-slots로 비활성)와, 서버를 --metrics로 시작한 경우 GET /metrics를 읽습니다. 소스는 GET /props의 endpoint_slots·endpoint_metrics 불리언으로 선택하며, default_generation_settings.n_ctx가 슬롯당 컨텍스트 길이(전체 컨텍스트를 병렬 슬롯 수로 나눈 값)를 제공합니다. 두 엔드포인트가 모두 꺼진 서버는 backend_engine_stats_scrape_success 0만 보고합니다 (type: mlxcel도 동일하게 적용)

최소 llama.cpp 설정:

backends:
  - name: "local-llama"
    type: llamacpp
    # 기본 http://localhost:8080 사용 시 URL 불필요
    # 로컬 서버에는 API 키 불필요

전체 llama.cpp 설정:

backends:
  - name: "local-llama"
    type: llamacpp
    url: "http://192.168.1.100:8080"  # 필요시 커스텀 URL
    weight: 2
    # 모델은 /v1/models 엔드포인트에서 자동 검색

llama.cpp 기능

기능 설명
GGUF 모델 GGUF 양자화 모델 네이티브 지원
로컬 추론 클라우드 API 의존성 없음
하드웨어 지원 CPU, NVIDIA, AMD, Apple Silicon
스트리밍 완전한 SSE 스트리밍 지원
임베딩 /v1/embeddings 엔드포인트 지원
도구 호출 감지 /props 엔드포인트를 통한 도구 호출 지원 자동 감지

도구 호출 자동 감지

라우터는 모델 검색 중 /props 엔드포인트를 쿼리하여 llama.cpp 백엔드의 도구 호출 기능을 자동으로 감지합니다. 수동 설정 없이 자동으로 함수 호출 지원이 가능해집니다.

작동 방식:

  1. llama.cpp 백엔드가 검색되면 라우터가 /props 엔드포인트를 조회합니다
  2. chat_template 필드를 정밀한 Jinja2 패턴 매칭으로 분석하여 도구 관련 구문을 감지합니다
  3. 도구 호출 패턴이 감지되면 모델의 function_calling 기능이 자동으로 활성화됩니다
  4. 감지 결과가 참조용으로 저장됩니다 (채팅 템플릿의 해시 포함)

감지 패턴:

라우터는 오탐을 줄이기 위해 정밀한 패턴 매칭을 사용합니다:

  • 역할 기반 패턴: message['role'] == 'tool', message.role == "tool"
  • 도구 반복: for tool in tools, for function in functions
  • 도구 호출 접근: .tool_calls, ['tool_calls'], message.tool_call
  • 도구 키워드가 포함된 Jinja2 블록: {{ "{% if tools %}" }}, {{ "{% for tool_call in ... %}" }}

분석되는 /props 응답 예제:

{
  "chat_template": "{% for message in messages %}{% if message['role'] == 'tool' %}...",
  "default_generation_settings": { ... },
  "total_slots": 1
}

폴백 동작:

  • /props를 사용할 수 없는 경우: 도구 호출이 지원되는 것으로 가정 (최신 llama.cpp 버전에 대한 낙관적 폴백)
  • /props가 오류를 반환하는 경우: 도구 호출이 지원되는 것으로 가정 (새로운 모델과의 호환성 보장)
  • 채팅 템플릿이 64KB를 초과하는 경우: 감지가 건너뛰어지고 지원되는 것으로 기본 설정
  • 최대 호환성을 위해 대소문자 구분 없음
  • 결과는 model-metadata.yaml의 기존 모델 메타데이터와 병합됩니다
  • 감지된 기능은 /v1/models/{model_id} 응답의 features 필드에 나타납니다

모델 메타데이터 추출

라우터는 llama-server 응답에서 풍부한 메타데이터를 추출합니다:

필드 소스 설명
컨텍스트 윈도우 meta.n_ctx_train 학습 컨텍스트 윈도우 크기
파라미터 수 meta.n_params 모델 파라미터 (예: 4B)
모델 크기 meta.size 파일 크기 (바이트)
기능 models[].capabilities 모델 기능 배열

llama-server 시작하기

# 기본 시작
./llama-server -m model.gguf --port 8080

# GPU 레이어 사용
./llama-server -m model.gguf --port 8080 -ngl 35

# 커스텀 컨텍스트 크기 사용
./llama-server -m model.gguf --port 8080 --ctx-size 8192

llama.cpp 백엔드 자동 감지

타입이 지정되지 않은 백엔드가 추가되면 (기본값 generic), 라우터는 자동으로 /v1/models 엔드포인트를 탐색하여 백엔드 타입을 감지합니다. llama.cpp 백엔드는 다음으로 식별됩니다:

  1. 응답의 owned_by: "llamacpp"
  2. llama.cpp 전용 메타데이터 필드 존재 (n_ctx_train, n_params, vocab_type)
  3. models[]와 data[] 배열이 모두 있는 하이브리드 응답 형식

이 자동 감지는 다음에 적용됩니다:

  • 핫 리로드 설정 변경
  • 명시적 타입 없이 Admin API를 통해 추가된 백엔드
  • type: generic 또는 타입이 지정되지 않은 설정 파일

예제: Admin API를 통한 자동 감지 백엔드:

# 타입 지정 없이 백엔드 추가 - llama.cpp 자동 감지
curl -X POST http://localhost:8080/admin/backends \
  -H "Content-Type: application/json" \
  -d '{
    "name": "local-llm",
    "url": "http://localhost:8080"
  }'

네이티브 MLxcel 백엔드

type: mlxcel을 사용하면 라우터는 Apple Silicon을 사용하는 macOS용 MLX 기반 모델 서빙 백엔드인 MLxcel에 대한 네이티브 지원을 제공합니다:

  • 기본 URL: http://localhost:8080 (llama-server와 동일)
  • API 호환성: llama-server (llama.cpp) API와 완전 호환
  • 모델 형식: Apple의 MLX 프레임워크를 통해 SafeTensor 형식 모델 제공
  • 헬스 체크: /health를 기본으로 사용하고 /v1/models를 폴백으로 사용
  • 플랫폼: Apple Silicon이 탑재된 macOS 전용

최소 MLxcel 설정:

backends:
  - name: "mlxcel-local"
    type: mlxcel
    # 기본 http://localhost:8080 사용 시 URL 불필요

전체 MLxcel 설정:

backends:
  - name: "mlxcel-local"
    type: mlxcel
    url: "http://192.168.1.100:8080"  # 필요시 커스텀 URL
    weight: 2
    models:
        - mlx-community/Qwen3-4B-4bit

자동 감지 미지원

MLxcel은 llama.cpp와 동일한 응답 형식(owned_by: "llamacpp" 포함)을 반환하므로 /v1/models 응답에서 자동 감지할 수 없습니다. 설정에서 type: mlxcel을 명시적으로 설정해야 합니다. 이렇게 하면 모델 식별에 적절한 owned_by 메타데이터(mlxcel)가 사용됩니다.

네이티브 LM Studio 백엔드

type: lmstudio를 사용하면 라우터는 LM Studio 로컬 서버에 대한 네이티브 지원을 제공합니다:

  • 기본 URL: http://localhost:1234 (LM Studio 기본 포트)
  • 헬스 체크: /v1/models (OpenAI 호환)를 기본으로 사용하고 /api/v1/models (네이티브 API)를 폴백으로 사용
  • 모델 검색: /v1/models 엔드포인트에서 모델 자동 검색
  • owned_by 속성: 적절한 모델 속성을 위해 "lmstudio" 보고
  • 엔진 통계: engine_stats.enabled: true이면 라우터가 GET /api/v0/models를 읽습니다. 추가 서버 플래그는 필요 없고 로드된 모델(state: "loaded")만 스냅샷에 들어가며 max_context_length가 컨텍스트 길이로 보고됩니다. LM Studio에는 실행/대기 요청 수 개념이 없어 해당 시리즈는 스크레이프 실패가 아니라 설계상 부재로 남습니다

최소 LM Studio 설정:

backends:
  - name: "lmstudio"
    type: lmstudio
    # 기본 http://localhost:1234 사용 시 URL 불필요
    # 로컬 서버에는 API 키 불필요

전체 LM Studio 설정:

backends:
  - name: "lmstudio"
    type: lmstudio
    url: "http://192.168.1.100:1234"  # 필요시 커스텀 URL
    weight: 2
    api_key: "${LM_API_TOKEN}"        # 선택: LM Studio API 토큰 (v0.4.0+)
    # 모델은 /v1/models 엔드포인트에서 자동 검색

LM Studio 기능

기능 설명
OpenAI 호환 API /v1/chat/completions, /v1/completions, /v1/embeddings 완전 지원
네이티브 REST API 모델 관리를 위한 추가 /api/v1/* 엔드포인트
로컬 추론 클라우드 API 의존성 없음
자동 검색 /v1/models에서 모델 자동 감지
선택적 인증 Authorization: Bearer 헤더를 통한 API 토큰 지원 (v0.4.0+)

네이티브 Continuum Router / Backend.AI GO 백엔드

type: continuum-router를 사용하면 라우터는 연합 LLM 라우팅을 위해 원격 Continuum Router 인스턴스 또는 Backend.AI GO 배포에 연결합니다. 지원 별칭: continuum-router, continuum_router, ContinuumRouter, backendai, backend-ai, backend_ai.

  • 헬스 체크: 기본 /health, 폴백 /v1/models
  • 모델 검색: 원격 인스턴스의 /v1/models 엔드포인트에서 자동 검색
  • 인증: Authorization: Bearer <key> 헤더를 통한 Bearer 토큰
  • 요청 패스스루: 변환 없이 요청 전달 (두 시스템 모두 OpenAI 호환 API 사용)
  • owned_by 속성: 검색된 모델에 대해 "continuum-router" 보고
  • 전송: HTTP 및 Unix Domain Socket 전송 모두 지원

최소 설정:

backends:
  - name: "remote-cr"
    type: continuum-router
    url: "https://remote.example.com"
    api_key: "${REMOTE_API_KEY}"
    # 모델은 원격 /v1/models 엔드포인트에서 자동 검색됨

명시적 모델이 있는 전체 설정:

backends:
  - name: "remote-backendai"
    type: continuum-router
    url: "https://remote-backend-ai.example.com"
    api_key: "${REMOTE_BACKEND_AI_API_KEY}"
    weight: 2
    models:
      - gpt-5.6-sol
      - claude-sonnet-4-5-20250929

사용 사례:

  • 다중 지역 배포: Continuum Router 인스턴스 간 지역 기반 요청 라우팅
  • 연합 라우팅: 독립적인 CR 또는 Backend.AI GO 배포 연결
  • 계층적 접근: 할당량 관리를 위해 중앙 Backend.AI GO 인스턴스를 통한 라우팅
  • 고가용성: 장애 조치를 위한 여러 Backend.AI GO 인스턴스 구성

Continuum Router 백엔드 기능

기능 설명
연합 라우팅 원격 Continuum Router 또는 Backend.AI GO 인스턴스로 요청 전달
자동 검색 원격 /v1/models에서 모델 자동 검색
Bearer 인증 API 키를 Authorization: Bearer 헤더로 전달
SSE 스트리밍 채팅 완성에 대한 완전한 스트리밍 지원
변환 없음 요청을 그대로 패스스루 (양쪽 모두 OpenAI 호환)
Unix 소켓 지원 unix:///path/to/socket.sock 전송 URL 지원

포워딩 루프와 홉 제한

라우터(이 라우터 자신 포함)를 가리키는 백엔드는 처리하는 요청마다 대상 라우터에 중첩 요청을 생성합니다. 백엔드 URL이 라우터 자신의 리스너이거나 라우터 간에 순환(A -> B -> A)이 있으면, 각 홉이 다음 홉을 기다리는 동안 인바운드 요청을 붙잡아 요청이 끝없이 중첩될 수 있습니다. 이는 continuumrouter, generic, openai, Hub 관리형 백엔드 토폴로지 등 라우터를 가리키는 모든 백엔드 타입에 해당합니다.

모든 라우터는 요청이 거쳐 온 라우터 수를 x-continuum-router-hops 헤더로 추적합니다:

  • 진입 전 검사: 상위 단계의 재시도를 유발하지 않도록 인증, 레이트 리밋, 동시 처리 한계보다 먼저 이 헤더를 검사합니다:
    • 헤더가 없으면 0으로 간주합니다.
    • 값이 8 이상인 요청은 핸들러가 시작되기 전에 508 Loop Detected(type: server_error, code: router_loop_detected)로 거부하므로 stream: true 요청도 이벤트 스트림 대신 JSON 오류 응답을 받습니다. /anthropic/* 경로에서는 본문이 type: api_error인 Anthropic 오류 형식을 사용합니다.
    • 그 외에는 헤더를 수신한 값에 1을 더한 단일 값으로 다시 씁니다.
  • 아웃바운드 전파:
    • 클라이언트 헤더를 전달하는 모든 디스패치는 백엔드 타입과 무관하게 증가된 홉 수를 전송합니다.
    • 자체 헤더를 구성하는 경로는 값을 명시적으로 설정합니다: 토크나이저 경로, /anthropic/v1/messages/count_tokens, 이미지 편집, Realtime WebSocket 핸드셰이크, /v1/decisions 패스스루, 컨트롤 플레인 프로바이더 배치 호출(/v1/batches 생성, 조회, 목록, 취소, 결과; 백그라운드 배치 폴러는 요청을 서빙하지 않으므로 전송하지 않음).
  • 내부 하위 요청 (가드레일 및 System One):
    • 라우터가 요청을 대신해 보내는 호출도 해당 요청의 홉 수를 그대로 전달합니다: LLM 전송 가드레일(self_hosted_classifier, classifier, custom_classifier, endpoint 및 backend 모두; webhook, endpoint만)과 System One 결정 호출(systemone 가드레일, 스마트 라우팅 system_one 분류기, evaluator: systemone인 safeguards.action_safety, fallback.quality_check).
    • 따라서 자기 자신의 /v1/chat/completions나 /v1/systemone 에뮬레이션 엔드포인트를 가리키는 가드레일도 홉 제한에서 안전하게 종료됩니다. 가드레일 엔드포인트의 508은 재시도하지 않고 가드레일 서킷 브레이커에도 집계하지 않으며, on_error 설정과 무관하게 fail-closed로 처리됩니다.
  • 응답 후 품질 판정 (usage.quality_judgment): 응답 완료 후 요청 외부에서 실행되며 판정 대상 요청이 가졌던 홉 수를 전달합니다. /v1/systemone을 통해 다시 라우터로 이어지는 판정은 순환마다 최소 2홉씩 증가하므로, 클라이언트 요청 하나당 최대 네 번의 판정 후 508로 종료되며 failed로 기록됩니다.
  • /v1/decisions 패스스루: /v1/decisions 대상에서 발생한 508은 fallback_chains의 다음 항목으로 넘어가지 않고 즉시 508 router_loop_detected로 반환됩니다.
  • 헤더 검증 및 예약:
    • 음이 아닌 정수가 아닌 값(빈 값 포함)은 400 invalid_router_hops로 거부합니다. 값이 여러 개(여러 헤더 줄 또는 쉼표 구분)이면 가장 큰 값을 사용하며, 32비트 범위를 초과하는 값은 루프로 간주해 거부합니다.
    • 홉 제한값 8은 고정입니다 (정상적인 연합 체인은 보통 라우터 2~3개 깊이입니다).
    • x-continuum-router-hops 헤더는 예약되어 있어 request_extensions.headers에서 어떤 대소문자 조합으로도 설정할 수 없습니다.

따라서 루프는 최대 9개의 중첩 요청 후 종료되며, 가장 안쪽의 508 응답이 모든 단계의 최종 응답으로 전파됩니다. 이 508은 재시도하지 않고, 폴백 체인을 진행하지 않으며, 백엔드 서킷 브레이커에도 실패로 집계되지 않습니다(기본 failure_status_codes는 500, 502, 503, 504).

시작 시 및 config validate 실행 시, 백엔드 URL의 host:port가 server.bind_address 항목과 일치하면 경고를 출력합니다(0.0.0.0과 [::]는 같은 포트의 모든 루프백 호스트와, localhost는 루프백 주소와 일치). DNS 이름을 해석하지 않으므로 직접 자기 참조만 감지하며, 다중 노드 순환은 런타임 홉 제한이 차단합니다. /v1/models 집계는 자체 요청을 생성하며 홉 헤더를 보내지 않습니다.

루프 거부 로그는 분당 1회 WARN으로 기록되고 같은 1분 동안의 반복은 DEBUG로 기록됩니다. 윈도우가 끝나면 억제된 횟수를 포함한 요약 한 줄이 기록됩니다(윈도우 경과 후 첫 거부가 기록하거나 거부가 더 오지 않으면 30초 안에 백그라운드 플러시가 기록합니다).

Unix Domain Socket 백엔드

Continuum Router는 로컬 LLM 백엔드를 위해 TCP 대신 Unix Domain Socket(UDS)을 전송 수단으로 지원합니다. Unix 소켓은 다음을 제공합니다:

  • 향상된 보안: TCP 포트 노출 없음 - 파일 시스템을 통한 통신
  • 낮은 레이턴시: 로컬 통신에서 네트워크 스택 오버헤드 없음
  • 더 나은 성능: 컨텍스트 스위칭 및 메모리 복사 감소
  • 간단한 접근 제어: 표준 Unix 파일 권한 사용

URL 형식:

unix:///path/to/socket.sock

플랫폼 지원:

플랫폼 지원
Linux 네이티브 AF_UNIX를 통한 완전 지원
macOS 네이티브 AF_UNIX를 통한 완전 지원
Windows socket2 크레이트를 통한 완전 지원 (Windows 10 1809+ / Build 17063+)
기타 미지원; 주소가 경고와 함께 건너뜀

설정 예제:

Windows에서는 드라이브 문자 경로를 사용합니다 (예: unix://C:/temp/llama.sock). Linux/macOS에서는 표준 절대 경로를 사용합니다 (예: unix:///var/run/llama.sock).

backends:
  # Unix 소켓을 사용하는 llama-server
  - name: "llama-socket"
    type: llamacpp
    url: "unix:///var/run/llama-server.sock"
    weight: 2
    models:
      - llama-3.2-3b
      - qwen3-4b

  # Unix 소켓을 사용하는 Ollama
  - name: "ollama-socket"
    type: ollama
    url: "unix:///var/run/ollama.sock"
    weight: 1
    models:
      - llama3.2
      - mistral

  # Unix 소켓을 사용하는 vLLM
  - name: "vllm-socket"
    type: vllm
    url: "unix:///tmp/vllm.sock"
    weight: 3
    models:
      - meta-llama/Llama-3.1-8B-Instruct

Unix 소켓으로 백엔드 시작하기:

# llama-server
./llama-server -m model.gguf --unix /var/run/llama.sock

# Ollama
OLLAMA_HOST="unix:///var/run/ollama.sock" ollama serve

# vLLM
python -m vllm.entrypoints.openai.api_server \
  --model meta-llama/Llama-3.1-8B \
  --unix-socket /tmp/vllm.sock

소켓 경로 규칙:

경로 사용 사례
/var/run/*.sock 시스템 서비스 (root 권한 필요)
/tmp/*.sock 임시, 사용자 접근 가능
~/.local/share/continuum/*.sock 사용자별 영구 소켓
~/Library/Application Support/*.sock macOS 애플리케이션 데이터 (공백이 포함된 경로 지원)

헬스 체크: 라우터는 TCP 백엔드와 동일한 엔드포인트(/health, /v1/models)를 사용하여 Unix 소켓 백엔드에 대해 자동으로 헬스 체크를 수행합니다.

플랫폼 지원과 제한:

  • 스트리밍: Server-Sent Events(SSE) 스트리밍은 Unix 소켓 백엔드에서도 동작하며 채팅 완성과 Anthropic Messages 표면 모두 지원됩니다. 각 이벤트는 엔진이 쓰는 즉시 클라이언트에 도달하고 timeouts.streaming.*(first_byte, chunk_interval, total)가 TCP에서와 같은 방식으로 적용됩니다. 타입 지정 네이티브 백엔드(예: url이 unix://인 type: gemini 백엔드)도 마찬가지입니다. 소켓은 백엔드의 HTTP 스트리밍 클라이언트만큼(Gemini 300초, Anthropic과 Bedrock mantle 600초) 응답 헤더를 기다리고 이어지는 본문은 스트리밍 타임아웃이 제한합니다.
  • Windows: Windows 10 1809+ (빌드 17063+)에서는 afunix.sys 커널 드라이버를 통해 AF_UNIX 소켓이 지원됩니다.
  • 최대 응답 크기: 메모리 고갈을 방지하기 위해 비스트리밍 응답 본문은 기본적으로 100MB로 제한됩니다. 스트리밍 본문은 청크 단위로 전달되며 메모리에 통째로 보관되지 않습니다.

문제 해결:

오류 원인 해결 방법
Socket file not found 서버가 실행되지 않음 백엔드 서버 시작
Permission denied 파일 권한 chmod 660 socket.sock
Connection timeout 서버가 연결을 수락하지 않음 서버가 리스닝 중인지 확인
Response body exceeds maximum size 응답이 너무 큼 max_response_size 증가 또는 TCP 백엔드로 스트리밍 사용

엔진 지표 식별자

engine_identity는 백엔드를 외부 엔진 지표에 연결하는 선택 항목입니다. kind는 endpoint_id 또는 deployment_id, value는 엔진의 실제 소문자·하이픈 UUID를 사용합니다. 과금 식별자인 backend_id와 독립적이며 여러 복제본이 하나의 엔진 식별자를 공유할 수 있습니다. 자격 증명, URL, 백엔드 이름, 모델 이름에서 유추하지 않습니다.

backends:
  - name: inference-primary
    type: vllm
    url: http://localhost:8000
    engine_identity:
      kind: endpoint_id
      value: "3f2504e0-4f89-41d3-9a0c-0305e82c3301"

AppProxy ROUTER 회로의 모든 복제본은 컨트롤 플레인 스냅샷의 엔드포인트 UUID를 사용합니다. UUID가 없으면 미해결 상태이며 엔드포인트 UUID가 없는 회로에는 식별자를 부여하지 않습니다. 프로토콜 버전은 와이어 호환성을 위해 0을 사용합니다. 알 수 없는 wire 종류는 파싱할 수 있지만 설정은 알려진 두 종류만 받습니다.

Admin 생성·조회·후보 탐색에서 이 메타데이터를 보존합니다. 수정 시 engine_identity를 생략하면 현재 값이 유지되고 대체 값 없이 clear_engine_identity: true를 보내면 제거됩니다. Hub v1 백엔드 프로필은 이 항목을 표현하지 못하므로 로컬 내보내기는 engine_identity를 미지원으로 표시하고 미리보기만 허용합니다. Hub의 authoritative 백엔드 동기화에서도 보존하려면 로컬 백엔드를 immutable_backend_ids에 넣으세요.