콘텐츠로 이동

API 레퍼런스

Continuum Router는 OpenAI 호환 API를 제공하며 모니터링과 관리를 위한 추가 관리 엔드포인트도 함께 노출합니다. 이 문서는 사용 가능한 모든 엔드포인트, 요청/응답 형식, 오류 처리에 대한 상세 정보를 담고 있습니다.

개요

기본 URL

http://localhost:8080

Content Type

별도로 명시되지 않는 한 모든 요청과 응답은 application/json을 사용합니다.

OpenAI 호환성

Continuum Router는 다음 OpenAI 호환 API 표면을 제공합니다:

  • 스트리밍을 포함한 채팅 완료
  • 텍스트 완료
  • 임베딩
  • 리랭킹 (Cohere 호환 /v1/rerank)
  • 모더레이션 (/v1/moderations, 설정된 가드레일로 분류)
  • System One 결정 (/v1/decisions, 별칭 /v1/systemone)
  • 희소 임베딩 (TEI/Jina 호환 /embed_sparse)
  • 이미지 생성
  • 이미지 편집/인페인팅
  • 이미지 변형
  • Files API (업로드, 목록, 조회, 삭제)
  • 채팅 완료에서의 파일 해결 (image_file 참조)
  • 모델 목록
  • 오류 응답 형식

x-backend로 백엔드 지정하기

여러 백엔드가 동일한 모델 id를 제공할 수 있습니다. /v1/models는 이들을 하나의 통합 항목으로 합치고, 그 항목의 backends 배열에 해당 id를 제공하는 모든 백엔드 이름을 담습니다. 기본 동작은 이들 사이의 부하 분산입니다.

원하는 백엔드를 지정하려면 선택적 요청 헤더 x-backend를 보내십시오.

curl -X POST http://localhost:8080/v1/chat/completions \
  -H "Content-Type: application/json" \
  -H "x-backend: provider-openai-a1b2c3d4" \
  -d '{"model": "gpt-5.6-terra", "messages": [{"role": "user", "content": "안녕하세요"}]}'

적용 범위

라우터는 모델로 라우팅하는 모든 추론 엔드포인트(스트리밍·비스트리밍 공통)에서 x-backend를 읽습니다:

  • /v1/chat/completions, /v1/completions, /v1/responses
  • /anthropic/v1/messages, /anthropic/v1/messages/count_tokens(루트 수준 별칭 /v1/messages, /v1/messages/count_tokens 포함)
  • /v1/embeddings, /v1/rerank, /embed_sparse
  • /tokenize, /detokenize (및 /v1 별칭)
  • /v1/images/generations, /v1/images/edits, /v1/images/variations
  • /v1/audio/transcriptions, /v1/audio/translations, /v1/audio/speech
  • /v1/realtime WebSocket 핸드셰이크

다음 경로에서는 헤더를 읽지 않습니다:

경로 이유
ACP 프롬프트 Agent Client Protocol은 HTTP 요청이 아니라 JSON-RPC 호출을 전달하므로, 백엔드 선택 지점에 헤더가 도달하지 않습니다.
/v1/batches 및 Batch API 경로 (control-plane 빌드) 배치 백엔드는 요청의 모델이 아니라 control_plane.batch.backend 설정(또는 디스패치 가능한 첫 OpenAI 계열·Anthropic 계열 백엔드)으로 정해집니다. 상태 조회, 취소, 결과 조회는 배치를 접수한 백엔드에 도달해야 합니다.
/v1/fine_tuning/jobs* 작업 CRUD는 completion 디스패치가 아닙니다. 생성은 요청의 model로 백엔드를 선택하고, 목록은 호출자가 접근할 수 있는 파인튜닝 지원 백엔드 하나를 선택하며, 조회와 취소는 작업을 생성한 백엔드를 사용합니다.
/v1/vector_stores* 벡터 스토어 호출은 모델을 지정하지 않습니다. 생성과 목록은 호출자가 접근할 수 있는 벡터 스토어 지원 백엔드 하나를 선택하고, 스토어 범위 호출은 스토어를 생성한 백엔드를 사용합니다.

동작 방식

x-backend는 라우팅 선호도이며 고정 핀(pin)이 아닙니다. 다음 네 가지 적격 조건이 모두 충족될 때 첫 시도에 지정한 백엔드가 사용됩니다:

  1. 사용자 라우팅 가능: 백엔드가 활성 상태(enabled: true)이고 내부 전용(internal: false)이 아닙니다.
  2. 허용 목록 포함: API 키에 allowed_backends 허용 목록이 설정되어 있으면 그 목록에 포함됩니다.
  3. 후보 풀 포함: 헤더가 없었을 때 요청이 선택할 수 있었던 백엔드 후보 집합에 속합니다:
    • 모델 기반 라우팅 엔드포인트: 요청한 모델을 제공하는 백엔드들입니다.
    • Nano Banana (Gemini 네이티브) 이미지 요청: 선택된 백엔드의 자격 증명을 실어 Gemini 엔드포인트로 요청을 보내므로, 반드시 type: gemini 백엔드를 지정해야 합니다(백엔드 이름과 URL은 보지 않으며, 비-Gemini 백엔드는 자격 증명을 빌려 쓰지 않고 거부합니다).
    • OpenAI 호환 /v1/images/edits 및 /v1/images/variations: 지정한 백엔드 자신의 URL과 자격 증명으로 직접 요청을 보내므로 기능 필터를 적용하지 않습니다. 가시적이고 allowed_backends를 통과한 백엔드라면 이미지 편집·변형 기능이 없는 백엔드도 지정할 수 있으며, 이 경우 일반 선택으로 폴백하지 않고 해당 백엔드에서 실패합니다.
  4. 헬스 및 서킷 브레이커 통과: 백엔드가 정상(healthy) 상태이고 서킷 브레이커가 요청을 허용합니다.

조건 중 하나라도 충족하지 않으면 라우터는 경고를 기록하고 일반 선택 로직으로 폴백합니다. 따라서 헤더가 없거나, 알 수 없거나, 숨겨졌거나, 허용되지 않거나, 비정상인 백엔드 이름을 보내도 요청은 실패하지 않으며 헤더를 보내지 않은 것처럼 동작합니다.

  • 첫 시도에만 적용: 재시도와 폴백 체인 홉에는 일반 선택 로직이 적용되므로, 요청 도중 백엔드가 실패해도 정상적으로 페일오버됩니다.
  • /v1/realtime 세션: 실시간 세션은 WebSocket 핸드셰이크 시점에 백엔드를 한 번만 선택하며, 세션이 닫힐 때까지 해당 백엔드에 고정됩니다. 두 번째 선택이나 세션 도중 페일오버는 없습니다.

어느 백엔드가 처리했는지 확인하기

x-backend를 보낸 요청에는 실제로 응답을 생성한 백엔드 이름을 담은 x-served-backend 응답 헤더가 반환됩니다(재시도나 폴백 체인 이동 이후에는 실제로 응답한 최종 백엔드 이름을 보고합니다):

curl -sS -D - -o /dev/null http://localhost:8080/v1/chat/completions \
  -H "Content-Type: application/json" \
  -H "x-backend: provider-openai-a1b2c3d4" \
  -d '{"model": "gpt-5.6-terra", "messages": [{"role": "user", "content": "안녕하세요"}]}' \
  | grep -i '^x-served-backend:'
  • 보낸 x-backend와 값이 같음: 선호도가 반영되어 지정한 백엔드가 처리했습니다.
  • 보낸 x-backend와 값이 다름: 지정한 백엔드가 적격 조건을 충족하지 못했거나(예: 이름 오타, 비정상 상태) 재시도·폴백이 발생해 다른 백엔드가 처리했습니다.
  • 헤더가 없음: 업스트림 백엔드가 직접 생성한 응답이 아니거나(아래 표 참고), 해당 경로가 x-backend를 읽지 않는 경우입니다. 헤더가 없는 것을 '선호도가 적용되었다'는 뜻으로 해석하지 마십시오.

백엔드가 돌려준 오류

라우터는 요청에 x-backend가 있었고 업스트림 백엔드가 직접 생성한 HTTP 응답(예: 업스트림 429 할당량 초과)을 받았을 때만 x-served-backend를 붙입니다. 라우터가 직접 작성한 응답이나 전송 실패에는 HTTP 상태 코드와 무관하게 이 헤더가 붙지 않습니다.

x-served-backend 포함 x-served-backend 생략
지원 경로의 업스트림 2xx 응답 (아래 mid-stream 릴레이 및 캐시 적중 제외). 라우터가 직접 작성한 오류: 요청 검증(400), 키별 허용 목록 거부(403), 정상 백엔드 없음(503), 서킷 브레이커 진입 거절, 폴백 체인 소진.
공용 재시도/폴백 퍼널을 쓰는 엔드포인트(/v1/chat/completions, /v1/completions, 텍스트 /v1/embeddings, /v1/rerank, /embed_sparse, 비스트리밍 /v1/images/generations)의 업스트림 HTTP 오류(429, 5xx 등) 및 스트리밍 /v1/chat/completions의 업스트림 4xx 핸드셰이크. 전송 실패 및 타임아웃: 백엔드로부터 받은 HTTP 응답이 없습니다.
비스트리밍 응답의 가드레일 Transform 판정, 그리고 스트림 도중 가드레일 차단 전에 SSE 핸드셰이크 헤더가 이미 전송된 스트리밍 응답. 가드레일 Block 판정 (비스트리밍): 라우터가 백엔드 응답을 폐기하고 차단 응답(200 또는 400)을 새로 작성한 경우입니다.
/v1/chat/completions에서 성공한 web_search 도구 루프의 최종 턴 응답 (클라이언트가 받는 본문을 생성한 마지막 턴의 백엔드 보고). web_search 도구 루프 실패: 실패한 턴의 응답이 라우터 오류로 변환됩니다.
/v1/chat/completions의 첫 바이트 이전(pre-stream) 네이티브 프로토콜 폴백 홉, pre-stream 전용 스트리밍, 체인이 없는 스트리밍. Mid-stream 릴레이 스트리밍 (fallback.enabled에 모델 체인이 있고 fallback.mid_stream_enabled: true인 경우): 응답 헤더 전송 후에도 릴레이가 백엔드를 바꿀 수 있습니다.
백엔드 /tokenize 또는 /v1/messages/count_tokens가 직접 반환한 응답. 로컬 생성 또는 캐시 응답: 응답 캐시·프리픽스 캐시 적중, 그리고 로컬 문자 기반 추정값으로 답한 /anthropic/v1/messages/count_tokens.
오류 귀속 미적용 경로의 실패: /anthropic/v1/messages, /v1/responses, /v1/responses/compact, responses_only 패스스루, /v1/images/edits, /v1/images/variations, 멀티모달 /v1/embeddings, count_tokens, mid-stream SSE 오류 이벤트.
비-HTTP 또는 미지원 핸드셰이크: /v1/realtime 101 Switching Protocols 업그레이드, ACP 프롬프트, /v1/batches*.

메트릭 레이블이 아닙니다

x-backend는 클라이언트가 보낸 값이므로 Prometheus 레이블로 사용하지 않습니다. 메트릭은 항상 실제로 요청을 처리한 백엔드를 기록합니다.

지원 여부 확인 (backend_preference_header_v1)

  • Admin API: 라우터가 x-backend를 지원하면 GET /admin/capabilities의 capabilities 배열에 backend_preference_header_v1을 광고합니다(Admin REST API 레퍼런스 참고).
  • 추론 API: 일반 추론 요청에 x-backend를 보내고, 캐시되지 않은 성공(200) 응답에 x-served-backend가 있는지 확인하십시오. 헤더가 있으면 라우터가 이 기능을 지원한다는 뜻이며, 보낸 값과 비교해 선호도 적용 여부를 확인할 수 있습니다.

인증

Continuum Router는 설정 가능한 적용 모드를 통한 API 키 인증을 지원합니다.

인증 모드

라우터는 API 엔드포인트에 대해 두 가지 인증 모드를 지원합니다:

모드 동작
permissive (기본값) API 키 없는 요청이 허용됩니다. 유효한 API 키가 있는 요청은 인증되어 사용자별 기능에 접근할 수 있습니다.
blocking 인증된 요청만 처리됩니다. 유효한 API 키가 없는 요청은 401 Unauthorized를 받습니다.

설정

api_keys:
  # 인증 모드: "permissive" (기본값) 또는 "blocking"
  mode: blocking

  # API 키 정의
  api_keys:
    - key: "${API_KEY_1}"
      id: "key-production-1"
      user_id: "user-admin"
      organization_id: "org-main"
      scopes: [read, write, files, admin]

보호된 엔드포인트 (mode가 blocking일 때)

API 키 미들웨어는 코어 API 라우터의 모든 경로를 보호합니다: /v1/models*, /v1/quota, /v1/chat/completions, /v1/completions, /v1/embeddings, /v1/responses*, /v1/images/*, /v1/rerank, /v1/moderations, /v1/fine_tuning/jobs*, /v1/vector_stores*, /tokenize, /detokenize(및 /v1 별칭), /v1/decisions, /v1/systemone, /embed_sparse, /anthropic/v1/messages*, /v1/messages*, /anthropic/v1/models, 기능 게이트가 적용된 /v1/batches*.

키에 비어 있지 않은 scopes 목록이 있으면 blocking 모드는 라우트의 read 또는 write 등급도 검사합니다. write는 read를 포함하고 admin은 둘 다 포함하며 files는 별도 Files API를 제어할 뿐 /v1 read/write 라우트를 만족하지 않습니다. scopes가 비어 있거나 없으면 기존 호환성을 위해 제한 없음으로 남고 permissive 모드는 인증된 키의 scope가 좁다는 이유만으로 요청을 거부하지 않습니다. 전체 라우트 분류는 보안 & 관리를 참고하세요.

참고: 생존, 준비성, 버전 엔드포인트(/health, /health/ready, /version)는 항상 인증 없이 접근 가능합니다. Admin, Files, Metrics 엔드포인트는 별도의 인증 메커니즘을 가집니다.

키는 prompt_logging_consent(allowed 또는 denied)로 프롬프트 캡처 동의를 나타낼 수도 있습니다. 키의 동의가 오는 곳을 참고하세요.

인증된 요청 만들기

보호된 모든 경로는 API 키를 Authorization: Bearer <api_key> 또는 Anthropic 고유의 x-api-key: <api_key> 두 헤더 형식 중 하나로 받습니다. 두 형식 모두 /anthropic/* 엔드포인트뿐 아니라 모든 경로에서 동작하므로 ANTHROPIC_API_KEY만 설정하고 이를 x-api-key로 보내는 Anthropic SDK와 Claude Code도 OpenAI 방식 클라이언트와 동일하게 인증됩니다. 요청에 두 헤더가 모두 있으면 Authorization: Bearer가 우선합니다.

POST /v1/chat/completions HTTP/1.1
Authorization: Bearer sk-your-api-key
Content-Type: application/json

{
  "model": "gpt-5.6-sol",
  "messages": [{"role": "user", "content": "Hello"}]
}
POST /v1/chat/completions HTTP/1.1
x-api-key: sk-your-api-key
Content-Type: application/json

{
  "model": "gpt-5.6-sol",
  "messages": [{"role": "user", "content": "Hello"}]
}

인증 오류

인증 실패 시 API는 다음을 반환합니다:

{
  "error": {
    "message": "Missing or invalid API key. Expected Authorization: Bearer <api_key> or x-api-key",
    "type": "authentication_error",
    "code": "invalid_api_key"
  }
}

상태 코드:

  • 401 Unauthorized: API 키가 없거나 유효하지 않음

Core API 엔드포인트

헬스 체크

라우터 서비스의 헬스 상태를 확인합니다.

GET /health

응답:

{
  "status": "ok",
  "service": "continuum-router",
  "version": "2.0.0"
}

상태 코드:

  • 200: 서비스가 정상

준비성 체크

이 레플리카에 활성화된 비내부 백엔드가 하나 이상 있는지 확인합니다. 드레이닝 중인 백엔드는 세지 않습니다. 업스트림 헬스와 서킷 브레이커 상태는 계속 요청 선택에서 처리하며 이 엔드포인트의 결과를 바꾸지 않습니다.

GET /health/ready

준비 완료 응답:

{
  "status": "ready",
  "service": "continuum-router",
  "routable_backend_count": 2
}

서빙 풀에 라우팅 가능한 백엔드가 없으면 같은 본문에서 "status": "not_ready", 개수 0을 반환합니다.

상태 코드:

  • 200: 활성화된 비내부 백엔드가 하나 이상 활성 상태
  • 503: 활성화된 비내부 백엔드가 없음

버전

실행 중인 라우터 버전을 반환합니다.

GET /version

응답:

{
  "version": "2.0.0"
}

상태 코드:

  • 200: 항상 반환되며 인증이 필요하지 않음

호출자 할당량

GET /v1/quota는 호출자가 인증한 신원에 묶인 한도와 각 한도의 남은 양을 반환합니다. 매개변수와 본문이 없고, 신원에 묶인 할당량을 소비하지 않으므로 조회한 값을 쓰지 않고 반복해서 호출할 수 있습니다.

응답에는 호출자 신원에 묶인 값만 담깁니다. 공유 함대 한도(global, per_client, per_model, per_backend), 배치 차원, 키 ID·이름·사용자·팀은 포함되지 않습니다.

{
  "object": "quota",
  "degraded": false,
  "dimensions": [
    {"dimension": "request_rate", "unit": "requests", "limit": 20, "remaining": 17, "reset_seconds": 9, "window_seconds": null, "scope": "replica"},
    {"dimension": "token_rate", "unit": "tokens", "limit": 20000, "remaining": 18400, "reset_seconds": 6, "window_seconds": null, "scope": "fleet"},
    {"dimension": "token_budget", "unit": "tokens", "limit": 5000000, "remaining": 4100000, "reset_seconds": null, "window_seconds": 2592000, "scope": "replica"}
  ]
}
dimension 포함 조건 보고 값
request_rate 키에 키별 요청 속도(rate_limiting.limits.per_api_key 또는 api_keys[].rate_limit)가 있을 때 버킷 용량과 남은 요청 수. x-ratelimit-limit, x-ratelimit-remaining과 일치
token_rate rate_limiting.limits.per_api_key.tokens_per_minute가 설정되었을 때 분당 토큰 용량과 남은 토큰. x-ratelimit-tokens-*와 일치
user_request_rate rate_limiting.limits.per_user가 설정되고 키가 사용자를 해석할 때 사용자가 공유하는 요청 버킷
team_request_rate rate_limiting.limits.per_team이 설정되고 키가 팀을 해석할 때 팀이 공유하는 요청 버킷
inflight per_api_key에 max_in_flight_requests가 설정되었을 때 동시 처리 상한과 남은 슬롯
token_budget 키에 max_tokens_budget이 있을 때 예산, 그리고 예산에서 사용량과 처리 중 예약을 뺀 값. window_seconds에 롤링 윈도 길이를 담음
hub_rpm, hub_input_tpm, hub_output_tpm, hub_monthly_tokens, hub_monthly_requests 토큰이 해당 한도를 가진 허브 동기화 키와 일치할 때 티어 한도와 남은 단위. x-continuum-ratelimit-*와 일치

scope는 값이 유효한 범위를 나타냅니다. 모든 레플리카가 공유하는 스토어에서 읽은 값은 fleet, 그 밖의 값은 replica입니다. request_rate, user_request_rate, team_request_rate, inflight, token_budget은 레플리카별입니다. 설정된 공유 스토어에 연결할 수 없어 이 레플리카 값으로 대체했거나 rate_limiting.redis.fallback_to_memory에 따라 Redis 속도 제한 스토어가 폴백했으면 degraded가 true입니다.

호출자별 응답:

호출자 응답
blocking 모드, 토큰 누락 또는 무효, 폐기된 허브 키 기존 401
scope에 read가 없는 키 기존 403
permissive 모드, 토큰 없음 또는 미등록 토큰 200과 dimensions: []
한도가 설정되지 않은 등록 키 200과 dimensions: []. 위 행과 동일
bypass_keys에 있거나 화이트리스트 IP에서 온 등록 키 request_rate와 token_rate 생략. token_budget과 허브 행은 그대로 적용
그 밖의 등록 키 적용되는 모든 행

한도에 도달한 키도 거부되지 않고 remaining: 0과 함께 200을 받습니다. 공유 global과 per_client 상한은 /v1/models 비용인 0.1로 계속 차감되므로 이 경로도 다른 API 경로처럼 인증 전에 계량됩니다. 다만 두 값의 수준은 보고하지 않습니다. 할당량 응답에는 x-ratelimit-* 또는 x-continuum-ratelimit-* 헤더가 없습니다.

상태 코드:

  • 200: 할당량 조회. dimensions가 비어 있을 수 있음
  • 401: blocking 모드에서 키가 없거나 유효하지 않음
  • 403: 키 scope에 read가 없음
  • 429: 공유 global 또는 per_client 상한이 조회를 거부함
  • 503: rate_limiting.redis.fallback_to_memory: false이고 Redis 스토어에 연결할 수 없음

읽기 동작과 장애 시 동작은 속도 제한을 참고하세요.


모델 목록

구성에서 활성화된 백엔드의 통합 모델 카탈로그를 조회합니다.

GET /v1/models

응답:

{
  "object": "list",
  "data": [
    {
      "id": "gpt-5.6-sol",
      "object": "model",
      "created": 1677610602,
      "owned_by": "openai",
      "context_length": 400000,
      "max_output_tokens": 128000,
      "pricing": {
        "input_tokens": 1.25,
        "output_tokens": 10.0
      },
      "capabilities": ["chat", "vision", "tool"],
      "supported_parameters": ["temperature", "top_p", "max_tokens", "presence_penalty", "frequency_penalty", "logprobs", "top_logprobs"]
    },
    {
      "id": "llama2:7b",
      "object": "model",
      "created": 1677610602,
      "owned_by": "library",
      "context_length": 4096,
      "max_output_tokens": 4096,
      "supported_parameters": ["temperature", "top_p", "max_tokens", "presence_penalty", "frequency_penalty", "top_k", "min_p", "logprobs", "top_logprobs"]
    },
    {
      "id": "anthropic/claude-haiku-4.5",
      "object": "model",
      "created": 1760547638,
      "owned_by": "anthropic",
      "context_length": 200000,
      "max_output_tokens": 64000,
      "pricing": {
        "input_tokens": 1.0,
        "output_tokens": 5.0
      },
      "capabilities": ["chat", "vision", "tool"],
      "supported_parameters": ["temperature", "top_p", "max_tokens", "presence_penalty", "frequency_penalty", "top_k", "min_p", "logprobs", "top_logprobs"]
    }
  ]
}

상태 코드:

  • 200: 모델이 성공적으로 조회됨
  • 503: 모든 백엔드가 비정상

기능:

  • 모델 집계: 구성에서 활성화된 백엔드의 모델을 결합
  • 중복 제거: 백엔드 간 중복 모델 제거
  • 캐싱: 내부 60초 하드 TTL, stale-while-revalidate 및 요청 병합 사용
  • 장애 시 유지: 실시간 검색에 실패하면 라우터는 해당 백엔드의 마지막으로 확인된 모델을 카탈로그에 유지합니다. 설정이나 대체 목록(Bedrock, OAuth 백엔드, 내장 Anthropic 목록)으로 채운 검색 결과는 백엔드를 정상으로 표시하지 않으며, 실제 응답을 받은 경우에만 정상으로 표시합니다.
  • 호출 가능한 모델만 노출: 호출자가 지금 요청할 수 있는 모델만 목록에 나옵니다. 기준은 다음과 같습니다.
    • 호출자의 키가 사용할 수 있는 백엔드 중 하나 이상이 비정상(unhealthy)으로 판정되지 않았습니다. 아직 검사 전이거나 워밍업 중인 백엔드는 사용 가능한 것으로 봅니다.
    • 또는 모델 이름에 활성화된 폴백 체인이 구성되어 있습니다. 이 경우 성능이 저하된 기본 모델도 계속 보이므로, 이 엔드포인트로 모델 선택기를 구성하는 클라이언트도 폴백에 도달할 수 있습니다.
    • 둘 다 아니면 백엔드가 복구될 때까지 목록에서 빠집니다. 이때도 GET /v1/models/{model}은 available: false와 함께 200을 반환하고, GET /admin/models는 해당 모델을 degraded로 표시합니다. /v1/models/extended, /v1/models/refresh, /anthropic/v1/models에도 같은 기준이 적용됩니다.
  • 백엔드 별칭: 활성화된 백엔드의 model_configs에 선언된 aliases 항목은 별칭 디스패치가 그 이름을 해석하는 동안 별도 항목으로 목록에 나오며, 정규 모델의 backends와 메타데이터를 그대로 가집니다.
    • 사용자가 라우팅할 수 있는 백엔드가 정규 모델을 제공하는 동안에만 나오고, 정규 모델이 빠지는 새로고침에서 함께 빠집니다.
    • 키별 allowed_models 목록에는 별칭 자체가 있어야 합니다. 백엔드 허용 목록, 내부 백엔드, 호출 가능 모델 기준은 정규 모델의 백엔드를 기준으로 적용됩니다.
    • 어떤 백엔드가 그 이름을 그대로 제공하면 해당 항목이 유지되고, 모델 실험이 응답하는 이름은 실험이 처리합니다.
    • model-metadata.yaml의 별칭과 model_aliases 섹션(haiku, sonnet, exact 등)은 목록에 나오지 않습니다. GET /admin/models는 선언된 별칭을 정규 항목의 aliases에 표시합니다.
  • 공급사 표시: OpenRouter처럼 백엔드가 vendor/model ID에 owned_by를 보내지 않으면 ID의 공급사 네임스페이스를 owned_by로 사용합니다(anthropic/claude-haiku-4.5는 anthropic). 업스트림 공급사와 메타데이터를 참고하세요.
  • 기능 메타데이터: 라우터가 값을 아는 모델에는 다음 필드가 함께 나옵니다. 값의 출처는 라우터가 확정한 모델 메타데이터(model-metadata.yaml, 백엔드가 보고한 값, 내장 카탈로그 항목)입니다.

    • context_length: 컨텍스트 창(토큰)
    • max_output_tokens: 최대 출력 토큰 수
    • pricing: 100만 토큰당 USD 단가
    • capabilities: chat, vision, tool 같은 기능 태그
    • supported_parameters: 라우터가 백엔드까지 전달하는 채팅 요청 파라미터로 temperature, top_p, max_tokens, presence_penalty, frequency_penalty, top_k, min_p, logprobs, top_logprobs 중에서 고릅니다. 모델을 제공하는 각 백엔드의 변환 규칙에서 도출합니다(로컬 엔진과 OpenAI 호환 원본 백엔드는 모두 전달하고 api.openai.com과 Gemini는 top_k와 min_p를 제외하며, Anthropic과 Bedrock은 temperature, top_p, max_tokens, top_k를 전달하되 샘플링 필드를 거부하는 Claude 모델에는 max_tokens만 전달하고 Bedrock Converse는 top_k도 제외합니다). 모델을 제공하는 모든 백엔드가 전달하는 파라미터만 나열합니다. capabilities에 chat도 업스트림 카탈로그가 보고하는 text 출력 모달리티도 없는 모델(예: 이미지나 임베딩 모델)에는 supported_parameters가 없습니다. 요청 형태에 따라 범위가 더 좁아지는 경우(예: reasoning을 담은 Claude 요청은 temperature를 제외)는 반영하지 않습니다.

    model-metadata.yaml 항목이 limits를 지정하지 않으면 context_length는 모델을 제공하는 엔진이 보고한 컨텍스트 창(/v1/models의 max_model_len 또는 context_length)에서 가져오며, 어느 백엔드든 요청을 처리할 수 있으므로, 백엔드 간 최솟값을 씁니다. 값을 알 수 없는 필드는 null로 보내지 않고 생략합니다. 메타데이터도 엔진 보고도 없는 모델은 id, object, created, owned_by, supported_parameters만 가집니다.


메타데이터 포함 모델 목록

GET /v1/models와 같은 통합 카탈로그를 조회하되, 라우터가 확정한 메타데이터와 각 모델을 제공하는 백엔드를 함께 반환합니다.

GET /v1/models/extended

응답:

{
  "object": "list",
  "data": [
    {
      "id": "qwen3-32b",
      "object": "model",
      "created": 1700000000,
      "owned_by": "vllm",
      "backends": ["vllm-large", "vllm-small"],
      "metadata": {
        "limits": {
          "context_window": 32768,
          "max_output": 32768
        }
      },
      "max_model_len": 32768,
      "root": "Qwen/Qwen3-32B"
    }
  ]
}

필드:

필드 타입 설명
id, object, created, owned_by GET /v1/models와 같음
backends array 모델을 제공하는 백엔드 이름. 호출자의 백엔드 가시성으로 필터링됨
metadata object 라우터가 확정한 메타데이터. 설정된 model-metadata.yaml과 model-metadata.d/ 항목, 백엔드 model_configs, 백엔드가 보고한 값을 단일 모델 조회에서 설명한 방식으로 결합합니다. 알려진 정보가 없으면 생략됨
tier, domains 스마트 라우팅 티어 정보. 스마트 라우팅이 지정한 경우에만 존재
그 밖의 키 업스트림 필드. 아래 참고

업스트림 필드: 그 밖의 최상위 키는 해당 모델에 대한 한 백엔드의 /v1/models 항목에서 복사됩니다(백엔드가 여럿이면 어느 백엔드인지는 정해져 있지 않습니다). 이 키들은 계약이 아니라 그대로 전달되는 값입니다. 백엔드가 보낸 경우에만 나타나며 그 백엔드의 형태를 유지합니다. 라우터가 직접 직렬화하는 키(id, object, created, owned_by, backends, metadata, tier, domains)는 업스트림에서 가져오지 않습니다.

어느 값이 기준인가: metadata가 라우터의 답이며 라우터가 실제로 따르는 값입니다. 업스트림 필드가 metadata와 같은 내용을 다시 표시하면 metadata를 읽으세요.

  • 컨텍스트 윈도는 라우터가 맞춰 주는 유일한 예외입니다. max_model_len(vLLM 계열 엔진)과 context_length(OpenRouter 같은 애그리게이터)는 존재할 때 metadata.limits.context_window 값으로 다시 쓰이므로 객체가 윈도를 두 개 표시하지 않습니다. 이 윈도는 메타데이터가 limits를 제공하면 운영자가 설정한 값이고 그렇지 않으면 요청이 어느 백엔드로든 갈 수 있으므로 제공 백엔드들이 보고한 값의 최솟값입니다. 백엔드가 보내지 않은 키는 추가하지 않으며 게시된 context_window가 0(해당 없음)이면 다시 쓰지 않습니다.
  • 그 밖의 업스트림 키는 그대로 유지됩니다. 특히 애그리게이터의 pricing(토큰당 USD, 문자열)과 top_provider.max_completion_tokens는 업스트림이 보낸 그대로 두며 백엔드 전체에 걸쳐 결합한 라우터의 값은 metadata.pricing(100만 토큰당 USD)과 metadata.limits.max_output에 있습니다. 업스트림 공급사와 메타데이터를 참고하세요.

상태 코드:

  • 200: 모델이 성공적으로 조회됨
  • 503: 모든 백엔드가 비정상

모델 필터 조회

capability와 컨텍스트 길이 조건으로 카탈로그를 서버 측에서 필터링합니다. 이 엔드포인트는 GET /v1/models/extended와 같은 모델 객체를 반환하되, 카탈로그가 명시된 모든 조건을 충족한다고 증명할 수 있는 모델만 남깁니다. 조건을 충족하는 모델이 없으면 에러가 아니라 빈 data 배열의 성공 응답을 돌려줍니다.

POST /v1/models/query

요청 바디 (모든 필드는 선택적입니다. 바디가 없거나 비어 있으면 보이는 모든 모델을 선택하므로, 이 경우 엔드포인트는 GET /v1/models/extended와 동일하게 동작합니다):

{
  "capabilities": ["chat", "vision"],
  "min_context_length": 64000,
  "max_output": 4096,
  "q": "gpt",
  "limit": 20
}

필드:

필드 타입 설명
capabilities array 필수 capability (AND): 모델의 metadata.capabilities가 나열된 모든 값을 포함해야 합니다. capability 메타데이터가 없는 모델은 매칭되지 않습니다
min_context_length number 최소 컨텍스트 윈도(토큰): metadata.limits.context_window가 이 값 이상이어야 합니다. limit 메타데이터가 없는 모델은 매칭되지 않습니다
max_output number 최소 최대 출력 크기(토큰): metadata.limits.max_output가 이 값 이상이어야 합니다. limit 메타데이터가 없는 모델은 매칭되지 않습니다
q string 대소문자를 구분하지 않는(ASCII) 부분 문자열 매칭: 모델 id나 metadata.display_name에 이 값이 포함되어야 합니다. 표시 이름이 없는 모델은 id로만 매칭합니다
limit number 반환할 최대 모델 수. 모든 필터를 적용한 뒤 목록 순서를 유지한 채 자릅니다. 생략하면 제한이 없습니다

필터는 같은 호출자에게 GET /v1/models/extended가 반환하는 목록(별칭과 실험 이름, 호출자의 backend/model allow-list, internal 백엔드 필터, 라우팅 가능 여부 필터까지 적용한 목록)에 그대로 적용됩니다. 따라서 쿼리는 그 목록을 좁히기만 합니다.

검증:

  • capability는 capabilities의 다른 표기로 받으며, 마찬가지로 배열을 받습니다. 두 표기를 함께 쓴 바디는 400(invalid_query)을 반환합니다.
  • 알 수 없는 필드는 400(invalid_query)으로 거부합니다. 필드 이름을 잘못 써도 모든 모델이 반환되지 않습니다.
  • capabilities는 각 1~64바이트인 항목을 최대 32개까지 받습니다. 대소문자를 구분해 정확히 일치하는 값만 매칭합니다.
  • q는 1~256바이트여야 합니다.
  • limit은 1~1000이어야 합니다.
  • 바디 크기는 16 KiB로 제한하며, 더 큰 바디는 413을 반환합니다.
  • 스코프: 다른 모델 목록과 마찬가지로 read API 키 스코프가 필요합니다. 상태를 바꾸지 않고 추론도 보내지 않기 때문입니다.

예시:

curl -s -X POST http://localhost:8080/v1/models/query \
  -H 'Content-Type: application/json' \
  -d '{"capabilities":["vision"],"min_context_length":64000}'

상태 코드:

  • 200: 필터링된 모델 목록 반환 (비어 있을 수 있음)
  • 400: 요청 바디가 있지만 유효한 쿼리가 아님 (OpenAI 형식 오류 객체, 코드 invalid_query)
  • 413: 요청 바디가 16 KiB를 넘음
  • 503: 모든 백엔드가 비정상

백엔드 모델 목록 프록시

모든 백엔드를 집계하지 않고 선택된 적격 백엔드 하나의 원본 /v1/models 응답을 반환합니다. 이 호환 엔드포인트에는 일반 백엔드 선택, 재시도, 상태 및 호출자 백엔드 가시성 규칙이 적용됩니다.

GET /v1/models/proxy

라우터가 중복 제거한 통합 카탈로그에는 GET /v1/models를 사용하십시오. 단일 백엔드의 제공자 네이티브 모델 목록 페이로드가 필요한 클라이언트에만 이 엔드포인트를 사용하십시오.

상태 코드:

  • 200: 선택된 백엔드 응답 반환
  • 502: 백엔드 연결 또는 게이트웨이 오류
  • 503: 적격 정상 백엔드 없음

모델 강제 새로고침

구성된 모든 백엔드에서 동기적으로 다시 집계하고 모델 집계 캐시를 교체합니다. 백엔드를 검색할 수 없으면 성공한 백엔드의 최신 결과를 우선하면서 해당 백엔드의 마지막으로 확인된 모델을 유지합니다. 반환되는 목록에는 GET /v1/models와 같은 호출 가능 모델 기준이 적용됩니다. GET /v1/models와 동일한 OpenAI 호환 모델 목록 형태를 반환하므로 호출자가 응답을 바로 사용할 수 있습니다.

이 엔드포인트는 데스크톱 클라이언트의 대화형 '새로고침' 동작 (예: backend.ai-go의 '모델 새로고침' 버튼)을 위해 설계되었습니다. stale-while-revalidate와 소프트 TTL 윈도우를 우회하여, 응답하기 전에 모든 백엔드에서 업스트림 가져오기를 트리거합니다.

POST /v1/models/refresh

응답:

{
  "object": "list",
  "data": [
    {
      "id": "gpt-5.6-sol",
      "object": "model",
      "created": 1677610602,
      "owned_by": "openai-compatible"
    }
  ]
}

상태 코드:

  • 200: 새로고침이 완료되고 갱신된 모델 목록이 반환됨
  • 429 Too Many Requests: 강제 새로고침 속도 제한 초과 (5초 버스트 윈도우당 3 요청, 분당 12 요청)

속도 제한:

이 엔드포인트는 호출할 때마다 구성된 모든 백엔드에서 업스트림 가져오기가 트리거되기 때문에 일반 GET /v1/models보다 더 엄격한 예산을 적용합니다. 기본 제한은 5초 윈도우당 3 요청, 분당 12 요청입니다. 유효한 API 키는 API 키 ID를 기준으로 별도의 버킷을 받습니다. 익명 호출자와 유효하지 않은 bearer 토큰을 사용한 요청은 하나의 전역 익명 버킷을 공유하므로, Authorization, X-Forwarded-For, X-Real-IP 헤더를 위조해도 강제 새로고침 예산을 우회할 수 없습니다. 제한을 초과한 클라이언트는 429를 받으며 재시도 전에 기다려야 합니다.

캐시 정책:

모델 목록 캐시는 현재 내부 고정 기본값인 60초 하드 TTL, 80% 소프트 TTL, stale-while-revalidate, 요청 병합, 제한된 빈 결과 백오프를 사용합니다. 이 값과 강제 새로고침 사용 여부는 공개 설정 스키마의 필드가 아닙니다.


단일 모델 조회

특정 모델의 정보를 가용성 상태, 그리고 선택적으로 제공되는 풍부한 메타데이터와 함께 조회합니다.

GET /v1/models/{model}

경로 파라미터:

파라미터 타입 필수 설명
model string 예 모델 식별자 (예: "gpt-5.6-sol", "llama2:7b", "anthropic/claude-haiku-4.5"). /가 포함된 네임스페이스 ID는 원문 형태와 퍼센트 인코딩 형태 모두 동작합니다: anthropic/claude-haiku-4.5와 anthropic%2Fclaude-haiku-4.5는 동일한 응답을 반환합니다. 원문 그대로의 extended, proxy, refresh, query 세그먼트는 이 조회가 아니라 각자의 엔드포인트로 라우팅됩니다. 예약 여부는 디코딩 전 원문 경로로 판단하므로 %65xtended 같은 퍼센트 인코딩 표기는 예약되지 않습니다. 이런 요청은 ID extended로 이 조회에 도달하며 다른 ID와 동일한 인증 및 가시성 검사를 거칩니다.

응답 (기본):

{
  "id": "gpt-5.6-sol",
  "object": "model",
  "created": 1677610602,
  "owned_by": "openai",
  "available": true
}

응답 (확장 메타데이터 포함):

model-metadata.yaml에 모델 메타데이터가 있으면 응답에 추가 필드가 포함됩니다. OpenRouter 같은 OpenAI 호환 애그리게이터가 제공하는 모델은 설정된 메타데이터에 limits가 없을 때 업스트림 카탈로그가 limits(와 그에 따른 max_tokens)를 보고할 수 있으며 백엔드 자체 model_configs에 가격이 없으면 업스트림 가격이 공유 model-metadata.yaml이나 내장 카탈로그의 가격을 대체합니다. GET /v1/models/extended는 같은 metadata와 함께 업스트림의 원본 필드도 제공합니다. 업스트림 공급사와 메타데이터를 참고하세요:

{
  "id": "gpt-5.6-sol",
  "object": "model",
  "created": 1704067200,
  "owned_by": "openai",
  "available": true,
  "supported_methods": ["chat.completions"],
  "features": ["chat", "vision", "code", "reasoning", "tool"],
  "max_tokens": 128000,
  "metadata": {
    "display_name": "GPT-5.6 Sol",
    "developer": "OpenAI",
    "summary": "Frontier GPT-5.6 model for complex professional work.",
    "knowledge_cutoff": "2026-02",
    "relative_speed": 3,
    "pricing": {
      "input_tokens": 5.0,
      "output_tokens": 30.0
    },
    "limits": {
      "context_window": 1050000,
      "max_output": 128000
    }
  }
}

응답 필드:

필드 타입 설명
id string 모델 식별자
object string 객체 타입 (항상 "model")
created integer 모델 생성 시점의 Unix 타임스탬프
owned_by string 모델을 소유/제공하는 조직
available boolean 모델을 현재 사용할 수 있는지 여부 (최소 하나의 정상 백엔드에서 제공되면 true)
supported_methods array (선택) 이 모델이 지원하는 API 메서드 (예: ["chat.completions"], ["images.generations"])
features array (선택) 모델 기능 (예: ["chat", "vision", "function_calling"])
max_tokens integer (선택) 이 모델의 최대 출력 토큰 수
metadata object (선택) 상세한 모델 정보를 담은 풍부한 메타데이터 객체

메타데이터 객체 필드:

필드 타입 설명
display_name string 사람이 읽기 쉬운 모델 표시 이름
developer string 모델을 만든 개발사 또는 조직
summary string 모델의 기능을 설명하는 간단한 요약
knowledge_cutoff string 지식 컷오프 날짜 (예: "2025-01")
relative_speed integer 상대 속도 지표 (1-5, 1이 가장 느리고 5가 가장 빠름)
pricing object 가격 정보 (단위는 USD). 가격 객체 필드 참조
limits object 모델 제한. context_window와 max_output은 토큰 단위이며 0은 '해당 없음'(이미지 모델)을 뜻한다. 이미지 모델은 선택 키를 추가로 갖는다: max_prompt_length(토큰이 아니라 문자 수), supported_sizes, max_n(요청당 최대 이미지 수), supported_qualities, supported_output_formats, supported_styles, supports_streaming(불리언)
artifact object (선택) 운영자가 선언한 가중치 식별 정보: digest(sha256: 또는 blake3: 뒤에 소문자 16진수 64자리), registry_version, revision, source. 아티팩트 식별 정보 참고
draft_model object (선택) 운영자가 선언한 추측 디코딩 드래프트: name과 선택 사항인 artifact
reasoning_efforts array (선택) 허용되는 reasoning_effort 레벨의 오름차순 목록 (["minimal", "low", "medium", "high", "xhigh", "max"]의 부분집합). 운영자 설정, 모델 메타데이터 또는 내장 OpenAI 카탈로그에서 가져오며 알 수 없을 때는 생략된다
rejects_reasoning_effort_none boolean (선택) 모델이 reasoning_effort: "none"을 거부할 때 true. false일 때는 생략된다

이미지 모델의 limits 객체는 토큰 한도와 함께 선택 이미지 키를 담는다. 모두 API 소비자를 위한 참고 정보다. 라우터는 이미지 요청 제한을 모델 계열별로 코드에서 강제하며 이 값을 읽지 않는다. dall-e-3는 다음과 같이 제공한다.

"limits": {
  "context_window": 0,
  "max_output": 0,
  "max_prompt_length": 4000,
  "supported_sizes": ["1024x1024", "1792x1024", "1024x1792"],
  "max_n": 1,
  "supported_qualities": ["standard", "hd"],
  "supported_styles": ["vivid", "natural"]
}

선택 limits 키는 값이 없으면 응답에서 아예 빠진다. 토큰 모델의 limits 객체는 이 필드들이 추가되기 전과 똑같이 직렬화된다.

아티팩트 식별 정보

artifact와 draft_model은 운영자가 model_configs나 메타데이터 계층에 선언한 자체 호스팅 모델에만 나타나며 내장 OpenAI 카탈로그가 설명하는 모델에는 나타나지 않는다. metadata의 다른 키와 마찬가지로 설정된 backends 순서에서 첫 번째 서빙 백엔드의 항목에서 가져오므로 뒤쪽 백엔드의 model_configs 오버라이드는 여기서 보이지 않는다. GET /admin/models는 모든 서빙 백엔드의 식별 정보를 artifacts에 나열하고 각 사용량 레코드는 실제로 응답한 백엔드의 식별 정보를 담는다. artifact.source는 모든 호출자에게 그대로 반환되므로 운영자는 여기에 사전 서명된 URL이나 토큰이 담긴 URL이 아닌 안정적인 공개 위치를 선언해야 한다.

"metadata": {
  "display_name": "Qwen 3.8 27B",
  "artifact": {
    "digest": "sha256:9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
    "registry_version": "v2.1-fp8",
    "revision": "4c2e9a1",
    "source": "hf://Qwen/Qwen3.8-27B-FP8"
  },
  "draft_model": {
    "name": "qwen3.8-0.6b"
  }
}

가격 객체 필드

필드 타입 단위 설명
input_tokens number 1M 토큰당 USD 텍스트 입력 요율. 항상 존재
output_tokens number 1M 토큰당 USD 출력 요율. 항상 존재
cached_input_discount number 0.0-1.0 비율 (선택) 캐시된 입력 토큰에 적용하는 할인. 가격이 아니라 비율
image_input_tokens number 1M 토큰당 USD (선택) 이미지 입력을 텍스트 입력과 별도로 과금하는 모델의 이미지 입력 요율
cached_image_input_tokens number 1M 토큰당 USD (선택) 캐시된 이미지 입력 요율. cached_input_discount와 달리 가격이다
per_image object 생성 이미지 1장당 USD (선택) 이미지 한 장의 가격. 크기 또는 티어 이름을 키로 쓴다
long_context object 1M 토큰당 USD (선택) 프롬프트 토큰 수가 threshold_tokens를 넘을 때 요청 전체에 적용하는 입력 및 출력 요율

input_tokens와 output_tokens는 1,000,000 토큰 기준이며 벤더가 정가를 표기하는 단위와 같다. smart_routing의 cost_per_1k_input_tokens는 1000 토큰 기준이므로 서로 다르다. 변환하려면 1000으로 나눈다.

long_context는 threshold_tokens, input_tokens, output_tokens로 구성됩니다. 프롬프트가 임계값과 같으면 기본 요율을 사용하고, 임계값을 넘으면 두 티어 요율을 요청 전체에 적용합니다. 이 필드는 API 소비자를 위한 메타데이터이며 라우터의 스마트 라우팅 티어 추론은 계속 기본 입력 요율을 사용합니다.

"pricing": {
  "input_tokens": 0.1,
  "output_tokens": 0.5,
  "long_context": {
    "threshold_tokens": 100000,
    "input_tokens": 0.5,
    "output_tokens": 2.5
  }
}

image_input_tokens와 cached_image_input_tokens는 텍스트 요율을 대체하지 않고 함께 쓰인다. gpt-image-2는 텍스트 입력을 input_tokens로, 이미지 입력을 image_input_tokens로 과금한다.

"pricing": {
  "input_tokens": 5.0,
  "output_tokens": 30.0,
  "cached_input_discount": 0.75,
  "image_input_tokens": 8.0,
  "cached_image_input_tokens": 2.0
}

per_image 값은 생성된 이미지 한 장당 USD로, 위 필드들과는 단위가 다르다. 키는 크기(1024x1024)이거나 티어 이름(ultra)이고 값은 단일 가격이거나 품질별 중첩 맵이다. 세 가지 형태를 모두 제공한다.

"per_image": {"1024x1024": 0.02, "512x512": 0.018, "256x256": 0.016}

"per_image": {"fast": 0.02, "standard": 0.04, "ultra": 0.06}

"per_image": {
  "1024x1024": {"standard": 0.04, "hd": 0.08},
  "1792x1024": {"standard": 0.08, "hd": 0.12}
}

이미지 단위로 과금하는 모델은 input_tokens: 0, output_tokens: 0을 보고한다. 이 0은 카탈로그에 실제로 적힌 값이며 데이터 누락이 아니다. per_image가 있다는 사실 자체가 해당 모델의 과금 축이 토큰이 아니라는 신호다. 클라이언트는 토큰 요율 0을 가격으로 표시하기 전에 per_image 존재 여부를 확인해야 한다.

위 선택 필드는 값이 없으면 응답에서 아예 빠진다. 토큰 가격만 있는 모델은 이 필드들이 추가되기 전과 똑같이 직렬화된다.

지원 메서드 매핑:

supported_methods 필드는 모델 기능에서 도출됩니다:

기능 API 메서드
chat, vision, code, reasoning, audio, video, function_calling, tool chat.completions
embedding embeddings
image_generation images.generations
image_edit images.edits
image_variation images.variations
moderation moderations

상태 코드:

  • 200: 모델이 조회되어 정보가 반환됨
  • 404: 구성된 모든 백엔드에 해당 모델이 존재하지 않음

기능:

  • OpenAI 호환: 응답 형식이 OpenAI API와 일치하며 추가 확장 필드를 포함
  • 헬스 인식: available 필드는 실시간 백엔드 헬스 상태를 반영
  • 프라이버시: 내부 백엔드 정보를 노출하지 않음
  • 선택적 메타데이터: 확장 필드는 메타데이터가 있을 때만 존재
  • 풍부한 메타데이터: 클라이언트 측 모델 선택에 참고할 수 있도록 표시 이름, 개발사, 기능, 컨텍스트 윈도우 제한을 포함

모델 가용성 알고리즘:

available 필드는 다음 알고리즘으로 결정됩니다:

조건 결과
헬스 체커 활성화 + 모델을 제공하는 백엔드 중 최소 하나가 정상 true
헬스 체커 활성화 + 모든 백엔드가 비정상 false
헬스 체커 비활성화 + 모델의 백엔드 목록이 비어 있지 않음 true
헬스 체커 비활성화 + 모델의 백엔드 목록이 비어 있음 false

알고리즘은 단락 평가를 사용합니다: 첫 번째 정상 백엔드를 찾는 즉시 true를 반환하므로 나머지 백엔드에 대한 불필요한 헬스 체크를 피합니다. 백엔드가 대체로 정상일 때 성능이 최적화됩니다.

성능 최적화:

  • 빠른 경로: 모델 캐시가 유효하면 조회는 O(n) (n = 모델 수)
  • 느린 경로: 캐시가 비어 있거나 만료되었으면 캐시 스탬피드를 방지하는 singleflight 보호와 함께 일회성 집계를 트리거
  • Stale-While-Revalidate: 캐시가 백그라운드에서 갱신되는 동안 오래된 데이터를 제공

요청 예시:

curl http://localhost:8080/v1/models/gpt-5.6-sol

응답 예시 (전체 메타데이터와 함께 모델 사용 가능):

{
  "id": "gpt-5.6-sol",
  "object": "model",
  "created": 1704067200,
  "owned_by": "openai",
  "available": true,
  "supported_methods": ["chat.completions"],
  "features": ["chat", "vision", "code", "reasoning", "tool"],
  "max_tokens": 128000,
  "metadata": {
    "display_name": "GPT-5.6 Sol",
    "developer": "OpenAI",
    "summary": "Frontier GPT-5.6 model for complex professional work, with text and image input.",
    "knowledge_cutoff": "2026-02",
    "relative_speed": 3,
    "pricing": {
      "input_tokens": 5.0,
      "output_tokens": 30.0
    },
    "limits": {
      "context_window": 1050000,
      "max_output": 128000
    }
  }
}

응답 예시 (확장 메타데이터가 없는 모델):

model-metadata.yaml에 메타데이터가 없는 모델은 기본 필드만 반환됩니다:

{
  "id": "custom-model",
  "object": "model",
  "created": 1677610602,
  "owned_by": "local",
  "available": true
}

응답 예시 (모델 존재하지만 사용 불가):

{
  "id": "gpt-5.6-sol",
  "object": "model",
  "created": 1704067200,
  "owned_by": "openai",
  "available": false,
  "supported_methods": ["chat.completions"],
  "features": ["chat", "vision", "code", "reasoning", "tool"],
  "max_tokens": 128000,
  "metadata": {
    "display_name": "GPT-5.6 Sol",
    "developer": "OpenAI"
  }
}

채팅 완료

OpenAI Chat API 형식을 사용하여 채팅 완료를 생성합니다.

POST /v1/chat/completions

요청 본문:

{
  "model": "gpt-5.6-luna",
  "messages": [
    {
      "role": "system",
      "content": "You are a helpful assistant."
    },
    {
      "role": "user",
      "content": "Explain quantum computing in simple terms."
    }
  ],
  "temperature": 0.7,
  "max_tokens": 150,
  "top_p": 1.0,
  "frequency_penalty": 0.0,
  "presence_penalty": 0.0,
  "stream": false,
  "stop": null,
  "logit_bias": {},
  "user": "user123"
}

파라미터:

파라미터 타입 필수 설명
model string 예 모델 식별자 (최소 하나의 정상 백엔드에서 사용 가능해야 함). 없거나 null이거나 문자열이 아니거나 공백뿐이면 400 missing_model(param: "model")로 응답합니다. POST /v1/completions도 같은 검사를 적용합니다
messages array 예 role과 content를 포함하는 메시지 객체 배열
temperature number 아니오 샘플링 온도 (0.0 ~ 2.0, 기본값: 1.0)
max_tokens integer 아니오 생성할 최대 토큰 수. 이 필드와 max_completion_tokens는 음이 아닌 정수 또는 null이어야 하며, 다른 값은 해당 필드를 지목하는 400 invalid_request_error(code는 invalid_type, 음수이면 integer_below_min_value)로 응답합니다. POST /v1/completions도 같습니다. 생략하면 라우터는 값 없이 요청을 전달하고 백엔드 기본값이 적용됩니다. 모든 요청에 라우터 측 예산을 두려면 request_params 기본값이나 한도를 설정하세요. 라우팅된 모델이 선언한 limits.max_output 또는 limits.context_window를 넘는 예산은 모든 서빙 백엔드가 그런 한계를 선언한 경우 디스패치 전에 400 invalid_max_tokens로 응답합니다. 백엔드의 model_configs 한계가 model-metadata.yaml보다 우선합니다
top_p number 아니오 핵 샘플링 파라미터 (0.0 ~ 1.0)
frequency_penalty number 아니오 빈도 패널티 (-2.0 ~ 2.0)
presence_penalty number 아니오 존재 패널티 (-2.0 ~ 2.0)
stream boolean 아니오 스트리밍 응답 활성화 (기본값: false)
stop string/array 아니오 중지 시퀀스
logit_bias object 아니오 토큰 로짓 바이어스
logprobs / top_logprobs boolean / integer 아니오 토큰 로그 확률. 패스스루 백엔드에서는 그대로 전달되고, 변환 전략(Anthropic Messages, Bedrock Converse, responses_only 모델의 Chat-to-Responses 브리지)에서는 로그 확률을 전달할 수 없으므로 400 unsupported_request_parameter로 거부됩니다
user string 아니오 추적을 위한 사용자 식별자
disable_fallbacks boolean 아니오 true이면 이 요청에 한해 첫 바이트 이전과 스트림 도중 모두에서 다른 모델로의 폴백 홉을 건너뜁니다. 요청 단위 폴백 해제를 참고하세요. 백엔드로 전달되지 않습니다
models 문자열 배열 아니오 요청 단위 폴백 목록(OpenRouter 형식, 최대 16개)입니다. primary가 실패하면 라우터가 설정된 폴백 메커니즘으로 목록의 다음 모델을 시도하고, 응답의 model은 실제로 응답한 모델을 가리킵니다. model이 없으면 첫 항목이 primary입니다. fallback.enabled가 켜져 있을 때만 적용되며, 모든 항목이 키의 모델 허용 목록에 있어야 하고, disable_fallbacks: true가 목록보다 우선합니다. 파싱되지 않는 항목은 400입니다. 백엔드로 전달되지 않습니다
mock_response string/object 아니오 테스트와 디버깅용 파라미터. 비스트리밍 요청이면 라우터가 백엔드에 연결하거나 사용량 기록을 남기지 않고 이 값으로 응답합니다. 문자열은 finish_reason: "stop"인 choice 하나의 assistant 내용이 되고, 객체는 그대로 반환됩니다. 인증, 요청 검증, 키의 모델 허용 목록은 그대로 적용되며, 다른 타입은 400 invalid_type으로 응답합니다. 스트리밍 요청은 평소처럼 디스패치합니다. 이 필드는 백엔드로 전달되지 않습니다
preset string 아니오 설정된 request_presets 슬러그를 지정합니다. 요청 프리셋을 참고하세요. 프리셋은 요청에 없는 모델과 요청이 비워 둔 샘플링 필드를 채우며, 요청에 명시한 필드가 우선합니다. 알 수 없는 슬러그는 400 unknown_request_preset, 문자열이 아닌 값은 400 invalid_type으로 응답하고 null은 무시합니다. 이 필드는 백엔드로 전달되지 않습니다
provider object 아니오 OpenRouter 형식의 요청 단위 라우팅 기본 설정입니다. proxy.client_provider_preferences가 켜져 있을 때만 해석하며 클라이언트 provider 기본 설정을 참고하세요. 스위치가 꺼져 있으면 객체를 그대로 전달합니다
reasoning_effort string 아니오 추론 가능 모델의 추론 노력 수준. 유효한 값은 백엔드와 모델마다 다릅니다(아래 참조). OpenAI 값은 모델별로 정확히 검증하며 조용히 다운그레이드하지 않습니다. auto는 Anthropic 적응형 사고용입니다.
reasoning object 아니오 대체 중첩 형식: {"effort": "high"}. 자동으로 reasoning_effort로 정규화됨.

클라이언트 provider 기본 설정

proxy.client_provider_preferences: true이면 클라이언트가 요청 본문에 OpenRouter의 provider 객체를 보내 이 요청이 사용할 수 있는 백엔드를 좁히고 정렬할 수 있습니다. 이 객체는 선택 사항입니다. 스위치가 꺼져 있으면(기본값) 지금과 똑같이 그대로 전달되므로, generic 백엔드 뒤의 애그리게이터 백엔드도 그대로 객체를 받습니다.

지원 필드:

필드 타입 동작
order 백엔드 이름 배열 소프트 선호입니다. 나열한 이름이 목록 순서대로 먼저 오고, 나머지 후보는 뒤에 오되 sort도 있으면 그 기준으로 정렬됩니다
only 백엔드 이름 배열 하드 허용 목록입니다. 요청의 기존 후보 집합과 교집합을 취합니다
ignore 백엔드 이름 배열 하드 차단 목록입니다. only를 적용한 뒤 후보 집합에서 뺍니다
sort price, throughput, latency 남은 후보를 실시간 텔레메트리로 정렬합니다. 예상 요청 비용 오름차순, 최신 엔진 처리량 내림차순, 기록된 평균 응답 시간 오름차순입니다
max_price prompt와 completion을 선택적으로 담은 객체 100만 토큰당 USD 기준의 하드 상한입니다. 모델 메타데이터 pricing과 같은 단위이며, 없는 키는 제한하지 않습니다
require_parameters boolean false만 허용합니다
data_collection "allow" 또는 "deny" "allow"만 허용합니다

보안과 우선순위:

  • 후보 집합은 다음 순서로 만들어집니다. 먼저 백엔드 가시성(internal, enabled), 다음으로 키의 allowed_models와 allowed_backends, 다음으로 Hub와 정책의 축소, 마지막으로 클라이언트의 provider 필터입니다. 기본 설정은 이 집합을 좁히거나 순서를 바꿀 수만 있으며, 허용 목록을 넓히거나 보이지 않거나 비활성화된 백엔드를 도달 가능하게 만들 수 없습니다.
  • only, ignore, order에 후보 집합 밖의 백엔드를 적어도 아무것도 넓히지 않습니다. 비어 있지 않은 후보 집합을 비우는 필터, 예를 들어 교집합이 빈 only는 400 invalid_request_error로 응답하며 해당 필드(provider.only, provider.ignore, provider.max_price)를 지목하고 아무 데도 디스패치하지 않습니다. 필터 이전부터 비어 있던 집합은 기존처럼 503입니다.
  • x-backend는 여전히 첫 시도 고정이며 provider보다 우선합니다. 그 첫 시도 뒤의 재시도에는 provider 객체가 다시 적용됩니다.
  • provider 객체를 담은 요청은 다른 요청의 진행 중인 디스패치에 합류하지 않습니다. 중복 제거는 디스패치되는 본문으로 키를 만들고 라우터는 그 본문에서 provider를 제거하므로, 기본 설정만 다른 두 요청이 하나의 디스패치를 공유해 한쪽이 자신이 배제한 백엔드의 응답을 받을 수 있기 때문입니다. 따라서 이 기본 설정이 있으면 요청은 자기 디스패치 하나를 더 치릅니다.
  • 스위치가 켜져 있으면 라우터가 객체를 소비해 제거하므로 어떤 백엔드도 이를 받지 않습니다. 운영자는 backends[].request_extensions.body.overrides.provider로 이 객체를 이해하는 백엔드에 provider 객체를 보낼 수 있으며, 이 값은 클라이언트 키를 제거한 뒤 디스패치 시점에 적용됩니다. request_extensions.body.defaults.provider로는 제거된 클라이언트 객체를 복원할 수 없습니다. 기본값은 비어 있는 키만 채우기 때문입니다. 따라서 정적 운영자 설정이 클라이언트 객체보다 우선하며, 클라이언트 객체가 업스트림에 원문 그대로 도달하는 일은 없습니다.
  • 스위치가 꺼져 있으면 아무것도 달라지지 않습니다. 객체는 지금처럼 그대로 전달됩니다.

거부하는 필드: 알 수 없는 필드는 필드 이름(provider.<field>)을 지목하는 400입니다. 이 라우터가 구현하지 않은 OpenRouter 필드(allow_fallbacks, quantizations 등)도 포함합니다. 조용히 무시된 라우팅 기본 설정은 거부된 것보다 나쁩니다. 같은 이유로 알려진 OpenRouter 필드 두 개는 이름을 밝혀 거부합니다. 라우터는 모든 요청 파라미터가 모든 후보의 변환을 거쳐 살아남는다고 보장할 수 없으므로 require_parameters: true를 받지 않고, 백엔드별 데이터 수집 메타데이터가 없으므로 data_collection: "deny"를 받지 않습니다.

검증 한도: 직렬화한 객체는 16 KiB로 제한합니다. 각 이름 목록은 16개 항목으로 제한하며, 각 이름은 1~128바이트이고 앞뒤 공백과 제어 문자를 허용하지 않습니다. 중복은 첫 등장만 남겨 합칩니다. provider는 JSON 객체여야 합니다. 객체 파싱을 거부할 때는 param: "provider"를 담은 400 invalid_request_error로 응답합니다. 후보 집합이 채울 수 없는 필터도 같은 상태와 타입으로 응답하며, 메시지에 해당 필드를 밝힙니다.

순위 대체 동작: sort와 order는 확인된 데이터로만 순위를 정합니다. 최신 엔진 스냅숏이 없거나 기록된 지연 시간이 없는 후보는 뒤의 한 순위 그룹에 합류하고, 텔레메트리가 전혀 없는 플릿은 그룹이 하나가 됩니다. 이때 선택은 정확히 설정된 selection_strategy를 따릅니다. 즉 기본 설정은 없는 데이터로 순서를 바꾸지 않습니다. max_price는 더 엄격합니다. 가격을 확인할 수 없으므로 가격이 없는 후보는 제외합니다.

확인된 모든 키에서 동률인 후보는 하나의 순위 그룹을 이룹니다. 곧 order상 위치(또는 목록에 없음)가 같고, sort가 있으면 그 값도 같은 후보의 집합입니다. 설정된 selection_strategy가 그 그룹 안에서 고르므로 같은 가격이나 같은 순서 등급에서도 첫 후보에 고정되지 않고 순환합니다.

Anthropic Messages 인그레스는 스위치가 켜져 있을 때 최상위 provider 키를 조용히 버리지 않고 400으로 거부합니다. 이 인그레스는 클라이언트 라우팅 기본 설정을 적용하지 않기 때문입니다.

입력 컨텍스트 제한

라우터는 입력 토큰 추정치가 라우팅된 모델의 context_window 이상이면 백엔드 전송 전에 요청을 거절합니다. 버퍼 응답과 스트리밍 응답 모두 400 invalid_request_error와 context_length_exceeded 코드를 반환하며, 메시지에 윈도우와 입력 토큰 추정치를 표시합니다. 출력 토큰 예산이 없어도 적용합니다.

기본 설정에서는 거절 시 더 큰 모델로 fallback하지 않습니다. fallback.fallback_policy.trigger_conditions.context_window_exceeded: true이면 라우터가 디스패치 전에 입력을 담을 수 있는 첫 번째 설정 체인 대상으로 홉하며, 적합한 대상이 없을 때만 원래 거절 응답을 반환합니다. 자세한 내용은 컨텍스트 윈도우 폴백을 참고하세요.

추정에는 메시지와 도구 스키마를 포함하며, fallback 허용 판단과 같은 일반 추정 방식을 사용합니다. 모델 라우팅과 선택적 컨텍스트 압축 후 계산하며, 모델별 토크나이저의 정확한 결과는 아닙니다. 컨텍스트 제한이 없거나 0이면 요청을 통과시킵니다.

허용된 서빙 백엔드별로 설정된 제한이 캐시된 엔진 보고보다 우선합니다. fallback 허용 판단과 마찬가지로, 다른 백엔드의 윈도우가 더 크거나 미확인이어도 확인된 양수 윈도우 중 최솟값을 적용합니다. 출력 예산 검증보다 보수적인 정책입니다. 메타데이터가 실제 백엔드 윈도우보다 작다면 모델 메타데이터를 수정해야 기존에 성공하던 요청을 다시 처리할 수 있습니다.

백엔드별 유효한 reasoning_effort 값:

  • OpenAI O-series (o1, o3, o4-mini): low, medium, high
  • OpenAI GPT-5 (mini/nano 포함): minimal, low, medium, high
  • OpenAI GPT-5.1: none, low, medium, high
  • OpenAI GPT-5.2 ~ GPT-5.5: none, low, medium, high, xhigh
  • OpenAI GPT-5.6: none, low, medium, high, xhigh, max
  • OpenAI GPT-5 Pro (Responses API 전용): high
  • OpenAI GPT-5.⅖.⅘.5 Pro (Responses API 전용): medium, high, xhigh
  • Anthropic Claude (Opus 4.7, Opus 4.6, Sonnet 4.6 등): none, minimal, auto, low, medium, high, xhigh*
    • auto는 적응형 사고({"type": "adaptive"})로 매핑됩니다. 모델이 사고의 시점과 정도를 동적으로 결정합니다.
    • *xhigh는 Opus 4.6/4.7에서 max로 매핑되고 그 외 모델에서는 high로 다운그레이드됩니다. Opus 4.7에서는 temperature, top_p, top_k가 제거됩니다.
  • Gemini (2.0-flash, 2.5-flash, 2.5-pro, 3-flash, 3-pro): none*, auto**, minimal, low, medium, high
    • *none은 Flash 모델 (2.0-flash, 2.5-flash, 3-flash)에서만 지원됩니다.
    • **auto는 medium으로 자동 다운그레이드됩니다.

OpenAI 모델은 스트리밍과 비스트리밍 요청에 동일한 모델별 검증을 적용합니다. 지원하지 않는 값은 400 invalid_request_error로 거절하며 auto, xhigh, max를 다른 effort로 바꾸지 않습니다. 다른 공급자는 위에 설명한 공급자별 변환 동작을 유지합니다.

응답 (비스트리밍):

{
  "id": "chatcmpl-123456789",
  "object": "chat.completion",
  "created": 1677652288,
  "model": "gpt-5.6-luna",
  "choices": [
    {
      "index": 0,
      "message": {
        "role": "assistant",
        "content": "Quantum computing is a revolutionary computing paradigm that harnesses quantum mechanical phenomena..."
      },
      "finish_reason": "stop"
    }
  ],
  "usage": {
    "prompt_tokens": 25,
    "completion_tokens": 150,
    "total_tokens": 175
  }
}

응답 (스트리밍): stream: true일 때 응답은 Server-Sent Events (SSE)를 사용합니다:

data: {"id":"chatcmpl-123","object":"chat.completion.chunk","created":1677652288,"model":"gpt-5.6-luna","choices":[{"delta":{"role":"assistant","content":""},"index":0,"finish_reason":null}]}

data: {"id":"chatcmpl-123","object":"chat.completion.chunk","created":1677652288,"model":"gpt-5.6-luna","choices":[{"delta":{"content":"Quantum"},"index":0,"finish_reason":null}]}

data: {"id":"chatcmpl-123","object":"chat.completion.chunk","created":1677652288,"model":"gpt-5.6-luna","choices":[{"delta":{"content":" computing"},"index":0,"finish_reason":null}]}

data: {"id":"chatcmpl-123","object":"chat.completion.chunk","created":1677652288,"model":"gpt-5.6-luna","choices":[{"delta":{},"index":0,"finish_reason":"stop"}]}

data: [DONE]

상태 코드:

  • 200: 완료가 성공적으로 생성됨
  • 400: 유효하지 않은 요청 형식 또는 파라미터
  • 404: 정상 백엔드에서 모델을 찾을 수 없음
  • 502: 백엔드 연결 오류
  • 504: 요청 타임아웃
  • 503: 모든 백엔드가 비정상

기능:

  • 모델 기반 라우팅: 요청된 모델을 제공하는 백엔드로 자동 라우팅
  • 로드 밸런싱: 정상 백엔드 간 로드 분산
  • 스트리밍 지원: SSE를 통한 실시간 응답 스트리밍
  • 오류 복구: 일시적 실패 시 자동 재시도
  • 요청 중복 제거: 동일한 요청의 중복 처리 방지
  • 추론 파라미터 정규화: 중첩된 reasoning 형식을 평면 reasoning_effort 형식으로 자동 정규화; 지원하지 않는 모델에서는 추론 파라미터 제거
  • 투명한 Responses-API 라우팅: 요청된 모델이 responses_only: true로 표시된 경우(예: gpt-5-pro, gpt-5.2-pro, gpt-5.4-pro, gpt-5.5-pro), 클라이언트는 /v1/chat/completions를 그대로 사용하면 됩니다. 전체 모델 목록과 설정은 Responses-API 전용 모델을 참조하세요.
    • 업스트림 디스패치: 라우터가 업스트림 /v1/responses 엔드포인트로 투명하게 디스패치한 뒤, 응답을 strict 모드의 chat.completion(스트리밍은 chat.completion.chunk) 봉투로 다시 변환합니다.
    • 보존되는 필드: reasoning_content, refusal(finish_reason: "content_filter"), incomplete_details 절단(finish_reason: "length" 또는 "content_filter"), 그리고 업스트림 failed/cancelled 오류 봉투가 그대로 보존됩니다.
    • 백엔드 요구사항: /v1/responses는 OpenAI와 Azure OpenAI 백엔드만 제공하며, 다른 백엔드 유형과 짝지어지면 디스패치 전에 400 invalid_request_error가 반환됩니다.
  • Gemini 네이티브 API 라우팅: Gemini 백엔드로 전달되는 요청 중 네이티브 전용 기능(tools: [{"google_search": {}}], tools: [{"code_execution": {}}], modalities: ["text", "image"], cachedContent, 다중 턴 어시스턴트 이미지 기록, 또는 gemini_context_cache.prefer_native_api: true)을 사용하는 요청은 자동으로 Gemini 네이티브 generateContent / streamGenerateContent 엔드포인트로 라우팅되며 응답은 reasoning_content, code_execution, annotations, grounding_metadata, 인라인 images를 포함한 OpenAI 호환 형식으로 변환됩니다.

Gemini 네이티브 전용 기능 요청/응답 예시 (/v1/chat/completions)

1. Google Search 그라운딩 및 Python 코드 실행:

{
  "model": "gemini-2.5-flash",
  "messages": [
    {
      "role": "user",
      "content": "최신 안정 버전 Rust 릴리스를 Google에서 검색하고 Python 코드로 계산해 주세요."
    }
  ],
  "tools": [
    { "type": "google_search" },
    { "type": "code_execution" }
  ]
}

정규화된 chat.completion 응답에는 포맷팅된 content와 함께 annotations(type: "url_citation"), grounding_metadata, 구조화된 code_execution 배열이 포함됩니다:

{
  "choices": [
    {
      "index": 0,
      "message": {
        "role": "assistant",
        "content": "```python\nprint(42)\n```\n\n```output\n42\n```\n최신 릴리스는...",
        "annotations": [
          {
            "type": "url_citation",
            "url_citation": {
              "url": "https://...",
              "title": "rust-lang.org",
              "start_index": 0,
              "end_index": 24
            }
          }
        ],
        "grounding_metadata": {
          "webSearchQueries": ["latest stable Rust release"],
          "groundingChunks": [],
          "groundingSupports": []
        },
        "code_execution": [
          { "type": "executable_code", "language": "PYTHON", "code": "print(42)\n" },
          { "type": "code_execution_result", "outcome": "OUTCOME_OK", "output": "42\n" }
        ]
      },
      "finish_reason": "stop"
    }
  ]
}

2. 멀티턴 대화형 이미지 생성 및 편집:

Gemini 이미지 모델(gemini-2.5-flash-image, nano-banana, nano-banana-2, nano-banana-pro)에 modalities: ["text", "image"]를 지정하고 이전 턴 응답의 assistant.images 배열을 다음 요청 messages에 그대로 포함하면 대화를 이어가며 이미지를 수정할 수 있습니다:

{
  "model": "gemini-2.5-flash-image",
  "modalities": ["text", "image"],
  "messages": [
    { "role": "user", "content": "파란색 원 모양의 플랫 아이콘을 생성해 주세요." },
    {
      "role": "assistant",
      "content": "",
      "images": [
        {
          "type": "image_url",
          "image_url": { "url": "data:image/png;base64,iVBORw0KGgo..." }
        }
      ]
    },
    { "role": "user", "content": "원 색상을 파란색에서 빨간색으로 바꾸고 가운데에 노란 별을 추가해 주세요." }
  ]
}

도구 변환 경고

요청을 실제 응답 백엔드의 형식으로 변환하면서 도구 선언을 생략하면, 성공 응답에 x-continuum-dropped-tools 헤더가 포함됩니다. Chat Completions, Responses, Anthropic Messages 변환 경로와 스트리밍 응답에 적용됩니다. 기존처럼 요청 처리는 계속되므로, 선언한 모든 도구가 필요한 애플리케이션은 이 헤더를 확인하세요.

값은 중복을 제거하고 정렬한 도구 유형 목록이며, 쉼표로 구분합니다. 알려진 유형은 web_search, web_search_preview, file_search, code_interpreter, computer_use_preview, image_generation, mcp, local_shell, custom이며, 나머지는 other로 표시합니다. CORS에서도 이 헤더를 노출합니다.

x-continuum-dropped-tools: file_search, other

실제로 생략한 도구만 표시합니다. Gemini 네이티브 내장 도구로 변환한 검색 및 코드 실행 도구는 포함하지 않습니다. 실패한 백엔드 시도의 경고는 성공한 폴백 응답에 포함되지 않습니다. 스트리밍에서는 응답 핸드셰이크까지 완료한 변환을 표시하며, 이후 OpenAI 형식의 릴레이 폴백은 도구 선언을 보존합니다.

캐시된 본문에는 변환 경고가 보존되지 않으므로, 함수 외 도구나 네이티브 도구 선언이 포함된 요청은 라우터 응답 캐시를 사용하지 않습니다. 정확 일치, 접두사, 시맨틱 응답 캐시에 모두 적용되며, 함수 도구만 포함한 요청은 계속 캐시할 수 있습니다. 제공자의 프롬프트 및 컨텍스트 캐시는 기존대로 동작합니다.

Responses API

OpenAI의 Responses API 형식을 사용하여 응답을 생성합니다. 이 엔드포인트는 Chat Completions에 대한 대안적 인터페이스를 제공하며 내부적으로 요청을 Chat Completions 형식으로 변환하여 백엔드에서 처리합니다.

클라이언트가 취소했거나, 타임아웃되었거나, 가드레일에 차단되었거나, 도중에 실패한 스트리밍 응답은 끊긴 시점까지 생성된 토큰만큼 과금됩니다. 해당 종료 결과를 담은 사용량 레코드 하나가 기록되며 수치는 엔진 사용량이 도착했으면 그 값을, 아니면 라우터의 델타 수를 씁니다. Anthropic이나 Gemini 업스트림이 입력은 보고했지만 출력 수(Anthropic의 message_delta, Gemini 마지막 청크의 candidatesTokenCount)를 보고하기 전에 끊기면, 그 입력과 라우터의 델타 수로 과금됩니다.

POST /v1/responses

요청 본문:

{
  "model": "gpt-5.6-sol",
  "input": "양자 컴퓨팅을 간단한 용어로 설명해주세요.",
  "instructions": "당신은 복잡한 주제를 간단하게 설명하는 유용한 어시스턴트입니다.",
  "max_output_tokens": 1000,
  "temperature": 0.7,
  "reasoning": {
    "effort": "high"
  }
}

파라미터:

파라미터 타입 필수 설명
model string 예 모델 식별자 (최소 하나의 정상 백엔드에서 사용 가능해야 함)
input string/array 예 입력 텍스트 또는 입력 항목 배열
instructions string 아니오 모델에 대한 시스템 지시사항 (시스템 메시지로 변환됨)
max_output_tokens integer 아니오 생성할 최대 토큰 수
temperature number 아니오 샘플링 온도 (0.0 ~ 2.0)
top_p number 아니오 핵 샘플링 파라미터 (0.0 ~ 1.0)
stream boolean 아니오 스트리밍 응답 활성화 (기본값: false)
store boolean 아니오 응답을 세션 저장소에 보존할지 여부 (기본값: true). 저장하지 않을 임시(ephemeral) 요청은 false로 보냅니다 (Codex가 사용)
include_reasoning boolean 아니오 응답에 추론 콘텐츠 포함
reasoning object 아니오 중첩 형식의 추론 설정 (아래 참조)
tools array 아니오 모델에서 사용 가능한 도구 목록 (플랫 형식)
tool_choice string/object 아니오 도구 사용 제어
previous_response_id string 아니오 다중 턴 대화를 위한 이전 응답 참조
conversation string/object 아니오 응답이 속한 서버 측 대화. 패스스루 전략에서는 그대로 전달되고 변환 전략에서는 라우터가 대화 기록을 읽을 수 없으므로 400 unsupported_request_parameter로 거부됩니다

라우터가 모델링하지 않는 필드(conversation, background, service_tier, parallel_tool_calls, 그리고 API에 나중에 추가되는 필드)는 패스스루 백엔드로 보낸 그대로 전달됩니다. 변환 전략은 위 필드로만 요청을 만들고 나머지는 버리며 이런 필드가 하나라도 있는 요청은 응답 캐시에서 제공되지 않습니다. 라우터가 모델링하지 않는 유형의 입력 항목(예: mcp_approval_response, computer_call_output)도 패스스루 백엔드를 위해 그대로 보존되며 변환 전략은 이를 버리지 않고 400 unsupported_request_parameter로 거부합니다.

내장 목록에 없는 도구 유형에도 같은 규칙이 적용되며, Codex CLI는 이 규칙 덕분에 이 엔드포인트로 백엔드에 도달할 수 있습니다. 패스스루 백엔드는 responses_tools 정책이 허용한 도구를 클라이언트가 보낸 것과 같은 JSON 내용으로 받으며, 객체 키의 순서는 유지되지 않습니다. 변환 전략은 다음과 같이 처리합니다.

도구 type 기본 정책 변환 전략
custom (문법 format을 쓰는 자유 형식 도구) 항상 허용 tools[custom]을 명시한 400 unsupported_request_parameter
local_shell 항상 허용 tools[local_shell]을 명시한 400 unsupported_request_parameter
function 멤버만 있는 namespace 항상 허용 각 멤버가 {namespace}__{name} 이름의 평면 함수 도구가 되고, namespace가 있는 재전송 function_call의 이름도 같이 변경됨. 평면화한 이름이 64자를 넘으면 앞 55바이트(문자 경계까지 줄임), _, 전체 이름 SHA-256의 앞 16진수 8자로 전송
다른 멤버가 있는 namespace 멤버를 각각 판단 멤버와 네임스페이스를 명시한 400 unsupported_request_parameter
그 밖의 유형 (예: mcp, image_generation) allow에 없으면 거부 tools[<type>]을 명시한 400 unsupported_request_parameter

이 정책은 두 흐름 모두에서 백엔드 선택 전에 실행됩니다. 따라서 정책이 거부한 유형은 어떤 백엔드에서든 tools[<type>]을 명시한 400 unsupported_request_parameter를 받으며, 업스트림 요청은 나가지 않습니다. execution: "client"인 tool_search는 항상 허용되고, 다른 execution 값이면 "그 밖의 유형" 행을 따릅니다.

평면화한 이름이 서로 충돌해도 400 unsupported_request_parameter로 거부됩니다. name이 없는 function 도구처럼 알려진 type의 필수 필드가 빠지면 422로 실패합니다.

변환 전략은 평면화한 도구가 4096개를 넘는 요청도 400 unsupported_request_parameter로 거부합니다. 이때 namespace 멤버는 각각 하나로 셉니다. 평면화 비용은 도구 수에 비례해 늘어나며, 10 MiB 본문 한도에 가까운 요청은 요청 하나가 쓸 수 있는 CPU를 크게 넘습니다. 패스스루 백엔드는 영향을 받지 않습니다. 도구를 선언한 그대로 보내며 평면화하지 않습니다.

변환된 응답은 스트리밍 여부와 관계없이 평면화한 도구 호출을 원래 name과 namespace로 보고하므로, 이 조합으로 호출을 찾는 클라이언트도 호출을 처리할 수 있습니다.

모델링된 형식에 있는 필드 중 라우터가 모델링하지 않는 필드도 유지됩니다. 패스스루 백엔드는 이 필드를 그대로 받고, 변환 전략은 무시합니다.

  • 호스팅 도구: web_search(예: external_web_access, filters), file_search(vector_store_ids), code_interpreter(container), computer_use, programmatic_tool_calling.
  • 재전송 항목: message(id, status 및 그 밖의 모든 필드로, type 없이 role과 content를 가진 항목도 포함. type이 있을 때 phase, agent, caller, 비표준 id나 status가 있으면 변환 전략이 거부하는 네이티브 전용 항목이 됩니다), function_call(id, status, encrypted_content, encrypted_function_args)과 function_call_output(id, status). 이 필드는 입력 크기 제한에 포함됩니다.

백엔드가 모르는 id를 재전송하면(예: store: false인 경우) 백엔드를 직접 호출했을 때와 마찬가지로 거부될 수 있습니다.

tool_choice 형식:

tool_choice 패스스루 전략 변환 전략
auto, none, required 그대로 전달 백엔드의 해당 값으로 매핑
type: "function"과 name이 있고 namespace가 없는 객체 모든 필드를 유지해 전달 최상위 function에 같은 이름이 있으면 그 이름으로, 네임스페이스 하나만 선언했으면 평면화한 이름으로 전송. 어떤 도구도 선언하지 않은 이름은 그대로 전송. 둘 이상의 네임스페이스가 선언했으면 400 unsupported_request_parameter
type: "function", name, namespace가 있는 객체 모든 필드를 유지해 전달 해당 네임스페이스가 function 멤버를 선언했으면 평면화한 이름으로 전송. 아니면 tool_choice namespace member를 명시한 400 unsupported_request_parameter
type이 function이 아니고 name이 없는 객체 (예: allowed_tools, web_search) 같은 JSON 내용으로 전달 tool_choice[<type>]을 명시한 400 unsupported_request_parameter
type이 function이 아니고 name이 있는 객체 (예: mcp) 모든 필드를 유지해 전달 tool_choice[<type>]을 명시한 400 unsupported_request_parameter

알 수 없는 문자열, name이 없는 {"type": "function"}, namespace가 문자열이 아닌 이름 있는 객체 같은 그 밖의 값은 422로 실패합니다. Gemini 변환에서 tool_choice는 요청이 function 도구를 선언한 경우에만 tool_config로 변환됩니다. web_search나 code_interpreter만 있는 요청에는 tool_config가 없으므로 강제 선택이 효과가 없습니다. function 도구를 web_search나 code_interpreter와 함께 쓰는 요청에는 서버 측 도구 순환을 지원하는 모델이 필요합니다. gemini-2.5-flash는 tool_choice가 none이 아니면 이런 요청을 400으로 거부합니다. Gemini가 직접 실행한 도구는 불투명한 encrypted_content를 담은 reasoning 항목으로 돌아오고, function_call에도 encrypted_content가 있을 수 있습니다. 이후 세대 Gemini 모델은 이 값이 없는 호출을 재전송하면 400으로 응답하므로, 대화 기록을 직접 관리하는 클라이언트는 두 필드를 변경 없이 다시 보내야 합니다.

reasoning 항목의 encrypted_content는 이를 발급한 라우터 인스턴스가 서명합니다. 서명이 검증되지 않는 값(재시작 후 또는 다른 인스턴스에서 발급한 값)은 재전송되지 않고 버려집니다. 서버 측 도구 파트는 출력 시 검사되며, 마스킹 판정이 발생하면 해당 항목을 제거합니다.

/v1/chat/completions의 Gemini 네이티브 요청은 다음 도구 규칙을 따릅니다.

  • toolConfig 전송 조건: 함수 선언에 도달할 수 있을 때만 전송합니다. 인라인 선언이거나, 클라이언트가 관리하는 cachedContent에 담긴 선언이 여기에 해당합니다. 내장 도구(web_search_options, code_execution 등)만 있으면 toolConfig를 보내지 않으므로, required나 이름 지정 선택은 검증만 되고 강제되지 않습니다. 이런 요청마다 경고를 기록하고 tool_choice_unenforced_total{reason="forced_without_functions"}을 증가시킵니다.
  • 함수와 내장 도구를 함께 쓰는 경우: toolConfig에 include_server_side_tool_invocations: true를 넣습니다. 클라이언트가 tool_choice를 보내지 않아도 마찬가지입니다. 이 조합을 지원하지 않는 모델은 여전히 400으로 거부하며, gemini-2.5-flash가 그렇습니다.
  • tool_choice: "none": web_search_options에서 파생된 도구를 포함해 내장 도구를 본문에서 제거하므로, 요청은 도구 없이 실행됩니다. 제거할 때마다 tool_choice_unenforced_total{reason="none_with_built_ins"}을 증가시킵니다.

Gemini가 4xx로 응답하면 두 경로 모두에서 Google이 보낸 정제한 메시지(예: Invalid tool)를 클라이언트 오류에 담아 전달합니다. 5xx 본문은 전달하지 않으며, 클라이언트는 상태 코드로 만든 메시지를 받습니다.

도구 정의 형식:

Responses API는 함수 속성이 type 필드와 같은 레벨에 있는 플랫 도구 형식을 사용합니다. 이는 중첩된 function 객체를 사용하는 Chat Completions API와 다릅니다.

{
  "type": "function",
  "name": "get_weather",
  "description": "위치의 날씨 조회",
  "parameters": {
    "type": "object",
    "properties": {
      "location": {"type": "string", "description": "도시 이름"}
    },
    "required": ["location"]
  },
  "strict": true
}
필드 타입 필수 설명
type string 예 "function"이어야 함
name string 예 함수 이름
description string 아니오 함수가 수행하는 작업 설명
parameters object 아니오 함수 파라미터에 대한 JSON Schema
strict boolean 아니오 엄격한 파라미터 검증 활성화

기타 지원되는 도구 유형: code_interpreter, file_search, web_search, computer_use. Gemini 백엔드를 대상으로 할 때, web_search는 네이티브 google_search 그라운딩(url_citation 주석 및 grounding_metadata 제공)으로 자동 매핑되고 code_interpreter는 네이티브 code_execution(코드 블록 및 실행 결과 제공)으로 매핑됩니다.

Gemini 백엔드에서 MALFORMED_FUNCTION_CALL이나 TOO_MANY_TOOL_CALLS처럼 도구 또는 잘못된 출력과 관련된 종료 사유로 턴이 끝나면, 응답은 status: failed이고 소문자로 바꾼 사유를 error.code에 담습니다. 프롬프트 차단 없이 후보가 없는 응답은 empty_response 코드와 함께 failed로 보고하며, 스트리밍과 비스트리밍 모두 동일합니다. failed 응답은 HTTP 200이지만 아무 답변도 제공하지 않은 것입니다. 그대로 반환되며 캐시되지 않고, previous_response_id로 이어 쓸 수 있도록 저장되지 않으며, 사용 품질 판정에도 쓰이지 않습니다. 해당 요청은 upstream_error로 기록되지만 제공자가 보고한 토큰은 그대로 청구됩니다.

멀티모달 입력 유형:

input이 항목 배열인 경우 각 항목은 멀티모달 콘텐츠 파트를 포함하는 메시지가 될 수 있습니다. 다음 콘텐츠 파트 유형이 지원됩니다:

유형 설명
text 일반 텍스트 콘텐츠
input_text 사용자 메시지용 텍스트 콘텐츠 (Responses API 대체 형식)
output_text 어시스턴트 메시지용 텍스트 콘텐츠. input 항목에서 어시스턴트가 생성한 콘텐츠를 사용자 입력(input_text)과 구분합니다
input_file 파일 콘텐츠 (PDF, 이미지 또는 기타 파일)
input_image 이미지 콘텐츠
image_url URL 또는 base64 데이터의 이미지

메시지 역할:

각 메시지 항목은 다섯 가지 역할 중 하나를 가집니다. 라우터는 /v1/responses에서 다섯 가지를 모두 받아들이고 백엔드별로 변환합니다.

역할 용도 백엔드별 매핑
developer OpenAI 추론 모델(GPT-5.x, o-시리즈)용 애플리케이션 개발자 지시. 명령 우선순위가 가장 높고 해당 모델에서는 system을 대체합니다. OpenAI는 developer를 그대로 전달하고 Anthropic은 system 매개변수에 병합합니다(여러 개일 경우 \n\n로 이어붙임). Gemini는 system_instruction에 병합하고 Ollama / vLLM / LocalAI / LM Studio에서는 system으로 매핑합니다.
system 애플리케이션 지시. OpenAI / Ollama / vLLM / LocalAI / LM Studio에서는 system으로 그대로 전달합니다. Anthropic에서는 system 매개변수로 추출하고 Gemini에서는 system_instruction으로 추출합니다.
user 사용자 입력. 대상 백엔드의 user 턴으로 매핑합니다.
assistant 직전 모델 출력. 대상 백엔드의 assistant(또는 Gemini model) 턴으로 매핑합니다.
tool 모델에 반환되는 도구 호출 결과. 대상 백엔드의 도구 결과 블록이나 메시지로 매핑합니다.

developer와 system 메시지가 여러 개 들어오면 라우터는 도착 순서를 그대로 유지합니다. 대상 백엔드(Anthropic, Gemini)가 단일 지시 슬롯만 허용하는 경우, 빈 줄 구분자로 이어붙여서 어떤 지시 텍스트도 누락되지 않도록 합니다.

아이템 참조:

{"type": "item_reference", "id": "item_..."} 형태의 입력 항목은 이전에 저장된 응답의 출력 아이템을 가리킵니다. OpenAI와 Vercel AI SDK가 기본값으로 다단계 도구 왕복을 제출하는 방식이 바로 이것으로, 2단계 요청이 함수 호출 아이템 전체를 다시 보내는 대신 1단계에서 생성된 아이템을 참조합니다.

  • OpenAI / Azure OpenAI 패스스루 경로에서는 참조가 변경 없이 전달되고 업스트림이 해소합니다.
  • 변환 경로(Anthropic, Gemini, chat completions 기반 라우팅)에서는 라우터가 디스패치 전에 세션 저장소에서 각 참조를 해소해, 참조가 가리키는 인라인 function_call, message, function_call_output 아이템으로 다시 씁니다. 참조는 call_id 기준으로 중복이 제거됩니다(먼저 온 것이 유지됨).
  • 스트리밍 응답도 똑같이 참여합니다. 스트림이 완료되면 누적된 응답이 저장되므로, 후속 요청이 스트리밍 단계에서 생성된 아이템을 참조할 수 있습니다.
  • 해소할 수 없는 참조는 해당 id를 명시한 HTTP 400을 반환하며 요청 하나가 가질 수 있는 참조는 최대 256개입니다.

Input File 형식:

input_file 콘텐츠 파트는 세 가지 입력 방법을 지원합니다:

{
  "type": "input_file",
  "filename": "document.pdf",
  "file_data": "data:application/pdf;base64,JVBERi0xLjQ..."
}
필드 유형 설명
filename string 파일의 선택적 파일명
file_data string Base64 데이터 URL (예: data:application/pdf;base64,...)
file_url string 파일의 외부 URL (SSRF 검증됨)
file_id string Files API를 통해 업로드된 파일 참조

Input Image 형식:

{
  "type": "input_image",
  "image_url": "data:image/png;base64,iVBORw0KGgo...",
  "detail": "high"
}
필드 유형 설명
image_url string 이미지 URL 또는 base64 데이터 URL
file_id string Files API를 통해 업로드된 이미지 파일 참조
detail string 이미지 상세 수준: low, high 또는 auto (기본값)

image_url과 file_id 중 하나는 반드시 있어야 합니다. 둘 다 없으면 400 오류가 반환됩니다. file_id를 사용하면 백엔드로 요청을 보내기 전에 파일이 인라인 base64 데이터 URL로 변환되며 input_file + file_id 경로와 동일하게 파일 소유권이 검증되고 10MB 크기 제한이 적용됩니다.

멀티모달 요청 예시:

curl -X POST http://localhost:8080/v1/responses \
  -H "Content-Type: application/json" \
  -d '{
    "model": "gpt-5.6-sol",
    "input": [
      {
        "type": "message",
        "role": "user",
        "content": [
          {"type": "input_text", "text": "이 문서에 무엇이 적혀있나요?"},
          {"type": "input_file", "filename": "report.pdf", "file_data": "data:application/pdf;base64,..."}
        ]
      }
    ]
  }'

보안 참고 사항:

  • file_url의 외부 URL은 SSRF 공격 방지를 위해 검증됩니다
  • 개인 IP 주소와 localhost URL은 거부됩니다
  • 외부 파일에는 HTTPS URL만 권장됩니다
  • file_id 필드는 Files API를 통해 업로드된 파일을 참조합니다. 파일은 백엔드로 전송되기 전에 base64로 변환됩니다. 파일 소유권이 검증되며 파일 주입에 10MB 크기 제한이 적용됩니다

Reasoning 파라미터:

reasoning 파라미터는 추론 가능 모델의 추론 노력 수준을 제어합니다. 중첩 형식을 사용합니다:

{
  "reasoning": {
    "effort": "high"
  }
}

유효한 effort 값:

값 설명
none 지원 모델에서 추론 비활성화
minimal 원래 GPT-5 계열 및 지원되는 비OpenAI 모델의 최소 추론
auto 적응형 사고 (Anthropic)
low 최소 추론 노력, 더 빠른 응답
medium 균형 잡힌 추론 노력
high 최대 표준 추론 노력
xhigh 지원 모델 계열의 확장 추론
max GPT-5.6의 최대 추론

라우터는 이 중첩 형식을 Chat Completions 백엔드에서 사용하는 평면 reasoning_effort 형식으로 자동 변환합니다. 잘못된 노력 수준은 400 Bad Request 오류로 거부됩니다.

Reasoning을 포함한 요청 예시:

curl -X POST http://localhost:8080/v1/responses \
  -H "Content-Type: application/json" \
  -d '{
    "model": "o1",
    "input": "이 복잡한 수학 문제를 단계별로 풀어주세요.",
    "reasoning": {
      "effort": "high"
    }
  }'

응답:

{
  "id": "resp_abc123",
  "object": "response",
  "created_at": 1699000000,
  "model": "o1",
  "output": [
    {
      "type": "message",
      "id": "msg_001",
      "role": "assistant",
      "content": [
        {
          "type": "output_text",
          "text": "단계별로 풀어보겠습니다..."
        }
      ]
    }
  ],
  "usage": {
    "input_tokens": 25,
    "output_tokens": 150
  },
  "status": "completed"
}

상태 코드:

  • 200: 응답 생성 성공
  • 400: 잘못된 요청 형식, 파라미터 또는 잘못된 reasoning effort 값
  • 404: 정상 백엔드에서 모델을 찾을 수 없음
  • 502: 백엔드 연결 오류
  • 504: 요청 타임아웃
  • 503: 모든 백엔드가 비정상

기능:

  • 스마트 라우팅: 백엔드 기능에 따라 요청 라우팅 - OpenAI/Azure와 responses_api: native로 설정한 백엔드는 네이티브 패스스루, 기타는 자동 변환
  • 네이티브 패스스루: OpenAI 및 Azure OpenAI 백엔드, 그리고 responses_api: native로 설정한 모든 OpenAI 와이어 백엔드의 경우 요청이 백엔드의 /v1/responses 엔드포인트로 직접 전달되어 모든 네이티브 기능 유지
  • 자동 변환: 기타 백엔드(Anthropic, Gemini, vLLM, Ollama 등)의 경우 Responses API 형식을 해당 네이티브 형식으로 변환
  • 추론 지원: 타입 안전 검증을 통한 reasoning 파라미터 전체 지원
  • 다중 백엔드 지원: OpenAI, Anthropic, Gemini, Ollama, vLLM 및 기타 백엔드와 호환
  • 스트리밍 지원: stream: true일 때 SSE를 통한 실시간 응답 스트리밍
  • 세션 관리: previous_response_id를 통한 다중 턴 대화 지원

라우팅 전략:

라우터는 각 백엔드에 대해 최적의 전략을 자동으로 결정합니다:

백엔드 유형 전략 설명
OpenAI 패스스루 네이티브 Responses API 지원 - 요청 직접 전달
Azure OpenAI 패스스루 네이티브 Responses API 지원 - 요청 직접 전달
Anthropic 네이티브 변환 네이티브 Anthropic Messages API 형식으로 변환, PDF/이미지 완전 지원
Gemini 변환 Gemini generateContent API 형식으로 변환
vLLM 변환 Chat Completions 형식으로 변환
SGLang 변환 Chat Completions 형식으로 변환
Ollama 변환 Chat Completions 형식으로 변환
LlamaCpp 변환 Chat Completions 형식으로 변환
Generic 변환 Chat Completions 형식으로 변환
responses_api: native를 설정한 모든 OpenAI 와이어 유형 패스스루 백엔드 자체의 /v1/responses로 그대로 전달. 아래 참고

위 표는 각 유형의 기본값입니다. 자체 대화 상태를 유지하는 에이전트 런타임이나 SGLang의 /v1/responses처럼 Responses API를 직접 구현한 셀프 호스팅 서버는 해당 백엔드에 responses_api: native를 설정하면 변경 없이 제공할 수 있습니다(백엔드: 네이티브 Responses API 참고). 이 옵션은 OpenAI 와이어 형식을 쓰는 모든 유형(openai, azure, generic, continuumrouter, vllm, sglang, tensorrt-llm, ollama, llamacpp, mlxcel, lmstudio)에서 허용되며 anthropic, bedrock, gemini에서는 로드 시 거부됩니다. 이 옵션은 /v1/responses(와 /v1/responses/compact)가 백엔드에 도달하는 방식만 바꾸며 모델 목록, owned_by, 헬스 체크 등 다른 모든 엔드포인트는 유형의 동작을 그대로 따릅니다.

패스스루 전략에서 백엔드는 previous_response_id, conversation, 라우터가 모델링하지 않는 입력 항목(예: mcp_approval_response)을 포함해 클라이언트가 보낸 그대로의 요청을 받고 백엔드의 출력은 라우터가 모델링하지 않는 출력 항목(예: mcp_approval_request)을 포함해 스트리밍과 비스트리밍 경로 모두에서 그대로 클라이언트에 전달됩니다. responses_tools.deny_resource_fields 목록으로 이 전략의 previous_response_id, conversation, prompt 필드와 item_reference 입력 항목을 거부할 수 있고, input_item_resource_ids로 file_id나 container_id 같은 리소스 ID를 담은 입력 항목도 거부할 수 있으며, 이 다섯 항목은 기본적으로 꺼져 있습니다.

세 가지 예외로 거버넌스를 유지합니다. background: true는 패스스루 전략에서만 전달됩니다(백그라운드 응답 참고). 변환 전략은 이전과 같이 이 필드를 제거하고 동기적으로 응답합니다. 요청의 모델과 키에 가드레일 정책이 적용되는 동안 재사용 prompt는 입력 단계가 그 variables를 읽지 않으므로 검사할 수 없는 입력으로 처리됩니다(enforce 모드는 거부, monitor 모드는 기록). request_params나 허브 티어 제한이 top_k, min_p, max_tokens를 제어하는 동안에는 모델링되지 않은 필드 top_k, min_p, max_tokens, max_completion_tokens가 제한을 우회해 전달되지 않도록 400으로 거부됩니다.

패스스루 이점:

OpenAI 또는 Azure OpenAI 백엔드를 패스스루 모드로 사용할 때:

  • 네이티브 PDF 파일 지원 (Chat Completions는 이미지만 지원)
  • 턴 간 추론 상태 보존으로 성능 향상
  • 내장 도구(web_search, file_search 등) 접근 가능
  • 향상된 캐시 활용 (OpenAI 문서에 따르면 40-80% 개선)
  • 최신 OpenAI Responses API 기능과의 완벽한 호환성

Anthropic 네이티브 변환 이점:

Anthropic (Claude) 백엔드를 네이티브 변환 모드로 사용할 때:

  • Anthropic 문서 이해 기능을 통한 네이티브 PDF 파일 지원
  • 자동 형식 감지를 통한 이미지 파일 지원
  • Claude 3+ 모델의 확장 사고(Extended Thinking) 지원
  • 외부 파일 URL에 대한 SSRF 보호
  • 보안을 위한 미디어 타입 화이트리스트

가드레일: 가드레일이 설정되면 이 엔드포인트는 스트리밍 여부와 관계없이 모든 요청에 대해 응답 캐시, 백엔드 선택, 모든 폴백 시도보다 먼저 입력 단계를 실행하고 모든 비스트리밍 응답에 대해 store: true 세션을 저장하기 전에 출력 단계를 실행합니다. 차단된 요청은 SSE 이벤트를 보내기 전에 status가 incomplete이고 incomplete_details.reason이 content_filter인 response 객체(HTTP 200)로 응답하며 block_behavior: error에서는 OpenAI content_filter 오류 봉투(HTTP 400)로 응답합니다. 변환 전략에서는 previous_response_id 또는 item_reference 이어가기를 재구성된 히스토리에 대해 디스패치된 모델의 정책으로 다시 검사합니다. 차단 응답은 캐시되지 않으며 폴백 홉을 일으키지 않습니다. 가드레일: 라우트별 적용 범위를 참고하세요.


저장된 응답 조회, 삭제 또는 취소

store: true로 생성한 응답은 ID로 조회하거나 삭제할 수 있습니다.

  • 저장소 및 라우팅:
    • 라우터가 만든 응답 (변환 전략): 응답을 생성한 라우터 인스턴스의 메모리 내 세션 저장소에서 제공됩니다(인스턴스당 최대 10,000개, 1시간 후 만료). 한 프로세스에 내장된 두 라우터(예: serve_embedded나 라이브러리 빌더로 만든 라우터)는 저장소를 따로 두므로 서로의 응답을 읽지 않습니다.
    • 패스스루 백엔드가 만든 응답: 업스트림 백엔드가 저장하므로, 라우터는 호출을 응답을 처리한 백엔드로 전달합니다.
  • 소유권 및 범위 격리: 저장 세션은 응답을 생성한 인증된 API 키 주체(api_keys[].user_id) 범위로 격리됩니다. Authorization: Bearer <key>와 X-API-Key: <key> 자격 증명은 같은 설정 identity namespace를 사용합니다. 없거나, 만료되었거나, 인증되지 않았거나, 다른 범위의 요청은 모두 404를 반환합니다.

X-User-ID 범위 지정에서 마이그레이션

X-User-ID는 Responses API의 권한 경계가 아닙니다. 라우터는 저장된 응답 접근, previous_response_id 컨텍스트 재구성, item-reference 해석, 로컬 Files API 소유권 검사에서 이 헤더를 무시하며 백엔드로 전달하지 않습니다. 이전에 하나의 공유 라우터 API 키 뒤에서 여러 최종 사용자를 X-User-ID로 나누던 배포는 격리해야 하는 사용자 또는 테넌트마다 별도 라우터 API 키를 사용하거나, 라우터 앞의 신뢰할 수 있는 서비스에서 사용자별 상태를 관리하고 라우터의 저장된 Responses 세션 및 Files API 소유권 제어에 의존하지 않는 구조로 옮겨야 합니다.

GET /v1/responses/{response_id}
DELETE /v1/responses/{response_id}
POST /v1/responses/{response_id}/cancel

성공한 GET은 저장된 응답 객체를 반환합니다. 성공한 DELETE는 다음을 반환합니다.

{
  "id": "resp_abc123",
  "object": "response.deleted",
  "deleted": true
}

패스스루 응답

패스스루 백엔드가 store: true 응답을 처리하면, 라우터는 그 응답 ID를 처리한 백엔드를 응답을 만든 인증 주체 기준으로 기억합니다. 이후 그 ID에 대한 GET, DELETE, POST .../cancel은 같은 백엔드로 그 백엔드에 설정된 자격 증명과 함께 전달되고 백엔드의 상태 코드, Content-Type, 본문이 반환됩니다(?stream=true 같은 GET 쿼리 문자열도 전달됩니다). DELETE가 성공하면 바인딩도 잊습니다. 가드레일 출력 단계가 차단, 마스킹 또는 중단한 응답은 백엔드에 저장된 사본이 필터링되지 않은 텍스트이므로 바인딩하지 않으며 이에 대한 GET은 404를 반환합니다.

라우터는 기록하지 않은 ID를 어떤 백엔드로도 보내지 않으며 매 호출마다 현재 설정을 기준으로 바인딩을 다시 검사합니다. 백엔드가 여전히 설정되어 있고 활성 상태이며 internal이 아니고 패스스루로 제공되어야 하며 호출자의 키가 여전히 그 백엔드에 접근할 수 있어야 합니다(allowed_backends). 검사 중 하나라도 실패하면 백엔드에 연결하지 않고 알 수 없는 ID와 같은 404를 반환합니다. 바인딩은 라우터 인스턴스별로 메모리에 최대 10,000개까지 보관되며(가장 오래된 것부터 잊음), 재시작하면 사라집니다. 잊힌 ID도 404를 반환합니다. 라우터가 직접 만든 응답에 대한 cancel은 이미 완료된 응답이므로 400을 반환합니다.

백그라운드 응답

패스스루 백엔드에 보낸 background: true 요청은 그대로 전달되며 백엔드는 즉시 queued(또는 in_progress)로 응답하고 나중에 응답을 완성합니다. 승인 검사(속도 제한, 로컬 및 허브 예산)는 다른 요청과 마찬가지로 그 POST에서 실행됩니다.

  • 미터링. queued 응답은 아무것도 기록하지 않습니다. 라우터가 usage를 담은 최종 결과(completed, incomplete, failed, cancelled)를 GET /v1/responses/{id} 또는 POST .../cancel로 처음 전달할 때, 동기 응답과 같은 경로로 요청과 사용량을 한 번 기록하며 같은 주체의 다른 키가 가져오더라도 응답을 만든 키에 귀속합니다. 다시 가져와도 기록하지 않습니다.
  • 가드레일. 요청의 모델과 결과를 가져오는 키에 가드레일 정책이 적용되는 동안에는 결과를 가져올 때마다 전달 전에 출력 단계를 거치며 동기 응답과 같이 차단이나 마스킹이 적용됩니다. 클라이언트가 그 스트림을 나중에 라우터가 검사할 수 없는 GET ?stream=true로 재개할 수 있으므로, 가드레일이 적용되는 동안 stream: true와 background: true를 함께 보내면 400 unsupported_request_parameter로 거부됩니다.
  • 가드레일 없는 스트리밍 백그라운드는 전달되며 한 번 미터링됩니다. 클라이언트가 연결을 유지하면 스트림의 최종 이벤트에서, 그렇지 않으면 최종 결과를 stream 없이 처음 가져오는 GET이나 cancel에서 기록합니다. 클라이언트 연결이 끊기면 라우터는 스트림 읽기를 멈추지만 백엔드는 생성을 계속하기 때문입니다.
  • 재생. 아직 미터링되지 않은 백그라운드 결과(최종 이벤트 전에 클라이언트 연결이 끊긴 스트리밍 결과 포함)나 가드레일이 적용되는 결과에 대한 GET ?stream=true는 400 unsupported_request_parameter로 거부되므로, stream 없이 가져오세요.
  • 제한 사항. 라우터를 거쳐 가져오지 않은 백그라운드 결과(백엔드에서 직접 가져오거나 아예 가져오지 않은 경우)는 미터링되지 않습니다. 라우터는 결과를 전달할 때만 사용량을 알 수 있고 스스로 백엔드를 폴링하지 않습니다. 대기 중인 기록을 담는 바인딩은 라우터 인스턴스별 메모리에 있으며 재시작이나 제거 시 사라집니다.

Responses Compact

input 항목 시퀀스를 요약하거나 압축해 대화 컨텍스트를 줄입니다. OpenAI와 Azure OpenAI 백엔드, 그리고 responses_api: native로 설정한 백엔드의 경우 요청이 백엔드의 네이티브 /v1/responses/compact 엔드포인트로 그대로 전달됩니다. 다른 백엔드는 컨텍스트 압축을 네이티브로 지원하지 않으므로 HTTP 501(Not Implemented)을 반환합니다.

가드레일이 설정되면 input 항목과 instructions는 백엔드 선택 전에 입력 단계를 거치며 차단은 항상 OpenAI content_filter 오류 봉투(HTTP 400)로 응답합니다. 압축 결과는 불투명하므로 게이트되지 않습니다. 가드레일: 라우트별 적용 범위를 참고하세요.

POST /v1/responses/compact

요청 본문:

{
  "model": "gpt-5.6-sol",
  "instructions": "이 대화의 핵심 내용을 요약해주세요.",
  "input": [
    {
      "type": "message",
      "role": "user",
      "content": [{"type": "input_text", "text": "프랑스의 수도는 어디인가요?"}]
    },
    {
      "type": "message",
      "role": "assistant",
      "content": [{"type": "output_text", "text": "프랑스의 수도는 파리입니다."}]
    }
  ]
}

파라미터:

파라미터 타입 필수 설명
model string 예 모델 식별자
input array 예 압축할 대화를 나타내는 input 항목 배열
instructions string 아니오 압축 과정을 안내하는 선택적 지시사항

응답:

{
  "id": "cmpt_abc123",
  "object": "response.compaction",
  "created_at": 1699000000,
  "output": [
    {
      "type": "text",
      "text": "압축된 대화 요약..."
    }
  ],
  "usage": {
    "input_tokens": 120,
    "output_tokens": 45
  }
}

상태 코드:

  • 200: 압축 성공
  • 400: 잘못된 요청 (빈 모델, 빈 입력, 너무 긴 instructions)
  • 501: 백엔드가 네이티브 컨텍스트 압축을 지원하지 않음
  • 502: 백엔드 연결 오류
  • 504: 요청 타임아웃

Batch API (control-plane 빌드)

OpenAI 스타일 Batch API는 control-plane Cargo 기능으로 빌드할 때만 컴파일되며 기본 소스 빌드에는 없습니다. 공식 릴리스 바이너리에는 기능이 컴파일되지만 control_plane.enabled, 정책 적용, control_plane.batch.enabled가 활성화되고 Hub가 제시된 키/티어를 배치 적격으로 표시해야 라우트가 동작합니다.

메서드 경로 용도
POST /v1/batches 제공자 배치 제출
GET /v1/batches 호출 키가 소유한 배치 목록
GET /v1/batches/{batch_id} 소유한 배치 상태 조회
POST /v1/batches/{batch_id}/cancel 소유한 배치 취소
GET /v1/batches/{batch_id}/results 완료 결과를 JSONL로 반환

Hub 동기화 API 키가 필요합니다. 소유권 검사는 알 수 없는 ID와 다른 키의 ID에 의도적으로 동일한 404를 반환합니다. 결과가 준비되기 전에는 409를 반환합니다. 선택된 배치 백엔드는 OpenAI 호환 또는 네이티브 Anthropic일 수 있으며 라우터는 하나의 공통 표면을 제공합니다. x-continuum-batch: off는 항상 요청을 배치에서 제외하며 정책이 이를 덮어쓸 수 없습니다.

OpenAI 스타일 배치 생성 시 input_file_id는 제공자 소유 파일 또는 Continuum Router의 로컬 /v1/files 저장소에 있는 파일을 지정할 수 있습니다. 제공자 소유 ID는 변경 없이 전달합니다. 라우터 로컬 파일은 Files API 소유권 정책을 적용하고 purpose=batch인지 확인하며, 메타데이터 크기가 200 MiB를 넘으면 콘텐츠를 읽기 전에 거부합니다. 지연과 배치 풀 승인이 끝난 뒤 선택된 OpenAI 호환 백엔드의 URL, 자격 증명, 요청 확장 설정으로 파일을 한 번 스트리밍하고, 반환된 제공자 파일 ID로 배치 요청을 치환합니다. 네이티브 Anthropic 배치는 인라인 요청을 사용하므로 라우터 로컬 input_file_id를 받을 수 없습니다.

라우터는 POST /v1/batches를 전달하기 전에 선택한 백엔드를 API 키의 allowed_backends와 대조합니다. 또한 Hub 티어의 model_allowlist와 키의 로컬 allowed_models를 교집합으로 적용하며, Hub 허용 목록이 명시적인 빈 목록이면 모든 모델을 거부합니다. 네이티브 Anthropic 배치는 각 requests[].params.model을 검사합니다. 모델 제한이 있는 OpenAI 스타일 배치는 라우터 로컬 파일이면 업로드 전에 스캔하고, 제공자 소유 파일이면 해당 백엔드의 자격 증명으로 가져와 모든 JSONL body.model을 검사합니다. 제한이 없는 OpenAI 스타일 배치는 모델 스캔을 생략하며 요청의 모델 힌트는 사용량 귀속에만 사용할 수 있습니다.

입력 스캔은 전체 200 MiB, JSONL 레코드당 16 MiB로 제한되며 60초 기한을 둡니다. 잘못된 파일 ID, 올바르지 않은 JSONL, 누락된 모델, 가져오기 실패, 크기 제한 초과처럼 검증할 수 없는 입력은 거부합니다. 이 검사는 지연이나 배치 풀 승인 전에 완료됩니다. 라우터 로컬 입력은 저장소에서 스트리밍으로 스캔하며 현재 처리 중인 제한된 JSONL 레코드만 보관합니다. 제한이 없는 지연 요청은 파일 콘텐츠가 아니라 메타데이터만 보관합니다.

승인 후에는 백엔드를 다시 선택합니다. 설정 리로드로 백엔드 이름, URL, 인증 정보, 제공자 유형, 요청 확장 설정이 달라졌으면 슬롯을 반환하고 제공자 배치를 만들지 않은 채 batch_backend_changed를 반환합니다.

제공자 업로드는 한 번만 시도합니다. 업로드가 실패하면 제공자 오류를 반환하고, 전송 단계에서 실패하면 batch_input_upload_failed를 반환하며 배치를 만들지 않습니다. 첫 업로드가 제공자에 도달했지만 응답이 유실된 경우 클라이언트가 요청을 다시 보내면 제공자 파일이 하나 더 생길 수 있습니다. 제출에 성공한 뒤 제공자 복사본은 제공자의 보존 정책을 따릅니다. 라우터 로컬 파일을 삭제해도 배치를 취소하거나 제공자 복사본을 삭제하지 않습니다.

오류 코드 상태 의미
backend_not_allowed 403 API 키가 선택한 배치 백엔드를 사용할 수 없습니다.
model_not_allowed 403 번호가 표시된 배치 요청이 유효 허용 목록 밖의 모델을 지정했습니다.
batch_input_file_access_denied 403 호출자가 라우터 로컬 입력 파일을 읽을 수 없습니다.
batch_input_file_wrong_purpose 400 라우터 로컬 파일의 용도가 purpose=batch가 아닙니다.
batch_input_file_provider_unsupported 400 선택한 제공자는 인라인 배치 요청을 사용하므로 로컬 파일을 받을 수 없습니다.
invalid_input_file_id 400 제공자 파일 ID가 1~128자의 ASCII 영문자, 숫자, _, - 형식이 아닙니다.
batch_input_unverifiable 400 번호가 표시된 요청이 올바르지 않거나 사용할 수 있는 모델이 없습니다.
batch_input_too_large_to_verify 400 전체 입력 또는 JSONL 레코드 하나가 스캔 제한을 초과했습니다.
batch_input_fetch_failed 502 기한 안에 제공자 파일을 완전히 가져오지 못했습니다.
batch_input_read_failed 500 라우터 로컬 파일 메타데이터 또는 콘텐츠를 읽지 못했습니다.
batch_input_upload_failed 502 제공자가 응답하기 전에 업로드 전송이 실패했습니다.
batch_input_upload_invalid_response 502 제공자가 업로드를 받았지만 올바른 파일 ID를 반환하지 않았습니다.
batch_backend_changed 503 요청이 승인을 기다리는 동안 선택한 백엔드가 바뀌었습니다.

파인튜닝 API

OpenAI Fine Tuning 작업 경로는 선택된 OpenAI-wire 백엔드로 전달됩니다. 생성, 조회, 취소는 백엔드의 응답을 그대로 반환하고, 목록 본문은 호출자 자신의 작업만 남깁니다. 기능 게이트가 적용되는 Batch API와 달리 모든 빌드에 마운트됩니다.

메서드 경로 용도
POST /v1/fine_tuning/jobs model로 선택한 백엔드에 작업을 생성합니다
GET /v1/fine_tuning/jobs 파인튜닝을 지원하는 백엔드 하나에서 호출자의 작업 목록을 조회합니다
GET /v1/fine_tuning/jobs/{job_id} 작업을 생성한 백엔드에서 작업을 조회합니다
POST /v1/fine_tuning/jobs/{job_id}/cancel 작업을 생성한 백엔드에서 작업을 취소합니다

http 또는 https의 openai, generic 백엔드 유형만 후보입니다. Azure OpenAI는 파인튜닝 엔드포인트 경로가 다르고 api-version이 필수이므로 제외합니다. 후보가 처리할 수 없는 요청은 라우터 수준 404가 아니라 400 fine_tuning_not_supported를 반환합니다.

라우터는 (주체, 작업 ID)를 작업을 생성한 백엔드에 기록합니다. 조회와 취소는 이 바인딩을 실행 중인 설정과 호출자의 allowed_backends에 대해 다시 검사합니다. 알 수 없거나 다른 주체의 ID는 백엔드에 접속하지 않고 404 fine_tuning_job_not_found를 반환합니다. 목록은 라우터가 호출자에게 바인딩한 항목만 남기므로 다른 주체의 작업 ID나 학습 파일 ID를 노출하지 않습니다. 바인딩은 프로세스 메모리에 있으므로, 라우터를 재시작하기 전에 생성한 작업은 목록에 나오지 않고 not-found를 반환합니다.

작업 CRUD에는 인증, 속도 제한, 사용량 귀속이 적용되지만 요청 파라미터 정책, 가드레일, 응답 캐싱, 모델별 생성 예산은 적용되지 않습니다. 각 호출은 0 토큰의 non-completion 요청으로 기록됩니다. training_file과 validation_file은 선택된 백엔드에 이미 존재해야 합니다. 라우터 로컬 /v1/files ID는 프로바이더로 업로드되지 않습니다.

오류 코드 상태 의미
fine_tuning_not_supported 400 모델을 서빙하는 백엔드(목록의 경우 설정된 백엔드) 중 Fine Tuning API를 구현한 백엔드가 없습니다.
fine_tuning_job_not_found 404 작업 ID를 알 수 없거나, 다른 주체의 것이거나, 해당 백엔드를 더 이상 사용할 수 없습니다.

벡터 스토어 API

OpenAI Vector Stores 경로는 선택된 OpenAI 계열 백엔드로 전달됩니다. 스토어와 파일 작업은 백엔드 응답을 그대로 반환하며, 목록 응답은 호출자 자신의 스토어만 남깁니다. 이 경로는 모든 빌드에 마운트됩니다.

메서드 경로 용도
POST /v1/vector_stores 선택된 벡터 스토어 지원 백엔드 하나에 스토어를 생성합니다
GET /v1/vector_stores 선택된 벡터 스토어 지원 백엔드 하나에서 호출자의 스토어 목록을 조회합니다
GET /v1/vector_stores/{store_id} 스토어를 생성한 백엔드에서 스토어를 조회합니다
POST /v1/vector_stores/{store_id} 스토어를 생성한 백엔드에서 스토어를 수정합니다
DELETE /v1/vector_stores/{store_id} 스토어를 생성한 백엔드에서 스토어를 삭제합니다
POST /v1/vector_stores/{store_id}/files 스토어에 프로바이더 파일을 첨부합니다
GET /v1/vector_stores/{store_id}/files 스토어에 첨부된 파일 목록을 조회합니다
GET /v1/vector_stores/{store_id}/files/{file_id} 첨부된 파일 하나를 조회합니다
DELETE /v1/vector_stores/{store_id}/files/{file_id} 첨부된 파일 하나를 제거합니다
POST /v1/vector_stores/{store_id}/search 스토어를 검색합니다

http 또는 https의 openai, generic 백엔드 유형만 후보입니다. Azure OpenAI는 Vector Stores 엔드포인트 경로가 다르고 api-version이 필수이므로 제외합니다. 후보가 처리할 수 없는 요청은 라우터 수준 404가 아니라 400 vector_stores_not_supported를 반환합니다.

벡터 스토어 호출은 모델을 지정하지 않으므로 x-backend 헤더를 사용하지 않습니다. 생성과 목록은 설정된 선택 전략으로 지원 백엔드 하나를 고릅니다. 이어서 라우터는 (주체, 스토어 ID)를 스토어를 생성한 백엔드에 기록하고, 스토어 범위 호출은 이 바인딩을 실행 중인 설정과 호출자의 allowed_backends에 대해 다시 검사합니다. 알 수 없거나 다른 주체의 ID는 백엔드에 접속하지 않고 404 vector_store_not_found를 반환합니다. 목록은 라우터가 호출자에게 바인딩한 항목만 남기므로 다른 주체의 스토어 ID를 노출하지 않습니다. has_more, first_id, last_id는 백엔드 페이지를 그대로 설명하므로, 호출자는 has_more가 false가 될 때까지 페이지를 넘깁니다. 바인딩은 프로세스 메모리에 있으므로, 라우터를 재시작하기 전에 생성한 스토어는 목록에 나오지 않고 not-found를 반환합니다.

첨부는 공유 백엔드 자격 증명의 프로바이더 파일 ID를 지정합니다. 라우터는 어떤 주체가 프로바이더 파일을 업로드했는지 기록하지 않으므로, 유출된 프로바이더 파일 ID는 다른 주체의 스토어에 첨부되어 검색으로 읽힐 수 있습니다. 서로의 파일을 볼 수 없어야 하는 테넌트끼리 하나의 프로바이더 자격 증명을 공유하지 마십시오.

이 경로는 인증, API 키 scope, 속도 제한, 사용량 집계를 적용하지만 요청 파라미터 정책, 가드레일, 응답 캐시, 모델별 생성 예산은 적용하지 않습니다. 각 호출은 토큰 0개의 비-completion 요청으로 기록됩니다. JSON 요청 본문은 1MiB로 제한됩니다. 첨부는 프로바이더 파일 ID를 지정하므로, 라우터 로컬 /v1/files ID는 프로바이더에 업로드되지 않습니다.

오류 코드 상태 의미
vector_stores_not_supported 400 설정된 백엔드 중 OpenAI Vector Stores API를 구현한 백엔드가 없습니다.
vector_store_not_found 404 스토어 ID를 알 수 없거나, 다른 주체의 것이거나, 해당 백엔드를 더 이상 사용할 수 없습니다.
vector_store_file_not_found 404 첨부 파일 ID가 안전한 프로바이더 ID가 아닙니다.

이미지 생성

이미지 생성·편집·변형 요청은 백엔드로 전달하기 전에 모델 capability를 검사합니다. 비어 있지 않은 capability 목록에 요청한 작업이 없으면, HTTP 400과 unsupported_model_capability, param: "model"을 반환합니다. 목록이 없거나 비어 있는 모델은 사용자 정의 이미지 편집 모델을 포함해 계속 사용할 수 있습니다. 백엔드별 capability 설정이 다르면 해당 작업을 지원하는 백엔드만 선택합니다.

OpenAI의 DALL-E, GPT Image 모델 또는 Google의 Nano Banana (Gemini) 모델을 사용하여 이미지를 생성합니다.

POST /v1/images/generations

요청 본문:

{
  "model": "dall-e-3",
  "prompt": "A serene Japanese garden with cherry blossoms",
  "n": 1,
  "size": "1024x1024",
  "quality": "standard",
  "response_format": "url"
}

파라미터:

파라미터 타입 필수 설명
model string 아니오 이미지 모델: gpt-image-2.5-sunburst, gpt-image-2.5-flare, gpt-image-2, gpt-image-1, gpt-image-1.5, gpt-image-1-mini, nano-banana, nano-banana-2, nano-banana-2-lite, 또는 nano-banana-pro (기본값: gpt-image-2). dall-e-2와 dall-e-3도 이를 제공하는 백엔드를 위해 계속 받지만 OpenAI는 2026-05-12에 둘 다 종료했습니다.
prompt string 예 생성할 이미지에 대한 설명
n integer 아니오 이미지 수 (1-10, 모델에 따라 다름)
size string 아니오 이미지 크기 (모델에 따라 다름, 아래 참조)
quality string 아니오 이미지 품질 (모델에 따라 다름, 아래 참조)
style string 아니오 이미지 스타일: vivid 또는 natural (DALL-E 3만)
response_format string 아니오 응답 형식: url 또는 b64_json
output_format string 아니오 출력 파일 형식: png, jpeg, webp (GPT Image 모델만, 기본값: png)
output_compression integer 아니오 jpeg/webp의 압축 레벨 0-100 (GPT Image 모델만)
background string 아니오 배경: transparent, opaque, auto (GPT Image 모델만)
stream boolean 아니오 부분 이미지 스트리밍 활성화 (GPT Image 모델만, 기본값: false)
partial_images integer 아니오 스트리밍 중 부분 이미지 수 0-3 (GPT Image 모델만)
google_search boolean 아니오 nano-banana-pro 및 nano-banana-2 (gemini-3-pro-image / gemini-3.1-flash-image)에서 Google Search 그라운딩 (tools: [{"google_search": {}}]) 활성화
user string 아니오 추적을 위한 사용자 식별자

모델별 제약 조건:

모델 크기 n 품질 참고
dall-e-2 256x256, 512x512, 1024x1024 1-10 N/A 클래식 DALL-E 2
dall-e-3 1024x1024, 1792x1024, 1024x1792 1 standard, hd 프롬프트 수정이 포함된 고품질
gpt-image-1 1024x1024, 1536x1024, 1024x1536, auto 1-10 low, medium, high, auto 최신 GPT Image 모델, 스트리밍 지원
gpt-image-1.5 1024x1024, 1536x1024, 1024x1536, auto 1-10 low, medium, high, auto 4배 빠름, 향상된 텍스트 렌더링
gpt-image-1-mini 1024x1024, 1536x1024, 1024x1536, auto 1-10 low, medium, high, auto 비용 효율적인 옵션
gpt-image-2 프리셋 1024x1024, 1536x1024, 1024x1536, auto 또는 사용자 지정 WIDTHxHEIGHT(각 변이 16의 배수, 긴 변 3840 이하, 비율 1:3~3:1, 655,360~8,294,400 픽셀) 1-10 low, medium, high, auto GPT Image 2
gpt-image-2.5-sunburst, gpt-image-2.5-flare 프리셋 1024x1024, 1536x1024, 1024x1536, auto 또는 사용자 지정 WIDTHxHEIGHT(각 변이 16의 배수, 긴 변 3840 이하, 비율 1:3~3:1, 655,360~8,294,400 픽셀) 1-10 low, medium, high, xhigh, max, auto GPT Image 2.5 (Sunburst는 최고 성능, Flare는 빠름). xhigh와 max를 받는 유일한 GPT Image 모델
nano-banana, nano-banana-2 256x256 ~ 4096x4096 1-4 N/A Gemini 3.1 Flash Image (gemini-3.1-flash-image, 빠름, 최대 4K, google_search 지원)
nano-banana-2-lite 256x256 ~ 1024x1024 1-4 N/A Gemini 3.1 Flash Lite Image (gemini-3.1-flash-lite-image, 비용 효율적 1K 생성)
nano-banana-pro 256x256 ~ 4096x4096 1-4 N/A Gemini 3 Pro Image (gemini-3-pro-image, 고급, 최대 4K, google_search 지원)

품질 파라미터 (GPT Image 모델):

GPT Image 모델에서 standard는 medium으로, hd는 high로 매핑됩니다.

품질 설명
low 낮은 품질로 빠른 생성
medium 품질과 속도의 균형 (기본값)
high 최고 품질, 느린 생성
auto 모델이 최적 품질 선택

출력 형식 옵션 (GPT Image 모델):

형식 설명 투명도 지원
png 무손실 형식 (기본값) 예
jpeg 손실 형식, 작은 파일 크기 아니오
webp 현대적인 형식, 좋은 압축률 예

참고: 투명 배경 (background: "transparent")은 png 또는 webp 형식이 필요합니다.

Nano Banana (Gemini) 모델:

Nano Banana는 OpenAI 호환 인터페이스를 통해 Google의 Gemini 이미지 생성 기능에 대한 액세스를 제공합니다:

  • nano-banana / nano-banana-2: Gemini 3.1 Flash Image(gemini-3.1-flash-image)에 매핑 - 최대 4K 해상도 및 선택적 Google Search 그라운딩(google_search: true)을 지원하는 빠른 범용 이미지 생성. Google이 2026-09-18에 gemini-2.5-flash-image를 기존 사용 프로젝트로 제한하기 전까지는 nano-banana가 그 모델에 매핑했으며 이 id를 직접 지정하면 지금도 그대로 전달됩니다.
  • nano-banana-2-lite: Gemini 3.1 Flash Lite Image(gemini-3.1-flash-lite-image)에 매핑 - 경량 1K 이미지 생성.
  • nano-banana-pro: Gemini 3 Pro Image(gemini-3-pro-image)에 매핑 - 고해상도(최대 4K) 및 선택적 Google Search 그라운딩(google_search: true)을 지원하는 고급 모델.

Nano Banana 요청은 선택된 백엔드의 키를 실어 Google의 Gemini API로 보내므로, type: gemini로 구성한 백엔드만 이 요청을 처리합니다. 다른 타입의 백엔드는 이름이나 URL과 관계없이 선택되지 않습니다. Google의 OpenAI 호환 엔드포인트(https://generativelanguage.googleapis.com/v1beta/openai)를 가리키는 type: openai 또는 generic 백엔드로 Nano Banana를 제공하려면 type: gemini로 바꿔야 합니다.

Nano Banana 크기 매핑:

라우터는 OpenAI 스타일 크기 파라미터를 Gemini의 aspectRatio 및 imageSize 형식으로 자동 변환합니다:

OpenAI 크기 Gemini aspectRatio Gemini imageSize 참고
256x256 1:1 1K Gemini 최소값으로 폴백
512x512 1:1 1K Gemini 최소값으로 폴백
1024x1024 1:1 1K 기본값
1536x1024 3:2 1K 가로
1024x1536 2:3 1K 세로
1024x1792 9:16 1K 긴 세로
1792x1024 16:9 1K 넓은 가로
2048x2048 1:1 2K nano-banana-pro 및 nano-banana-2 (gemini-3.1-flash-image)
4096x4096 1:1 4K nano-banana-pro 및 nano-banana-2 (gemini-3.1-flash-image)
auto 1:1 1K 기본 폴백

변환은 다음 Gemini API 구조를 전송합니다:

{
  "contents": [{"parts": [{"text": "Your prompt"}]}],
  "generationConfig": {
    "imageConfig": {
      "aspectRatio": "3:2",
      "imageSize": "1K"
    }
  }
}

Nano Banana 요청 예제:

{
  "model": "nano-banana",
  "prompt": "A white siamese cat with blue eyes, photorealistic",
  "n": 1,
  "size": "1024x1024",
  "response_format": "b64_json"
}

응답:

{
  "created": 1677652288,
  "data": [
    {
      "url": "https://oaidalleapiprodscus.blob.core.windows.net/...",
      "revised_prompt": "A tranquil Japanese garden featuring..."
    }
  ]
}

응답 (b64_json 포함):

{
  "created": 1677652288,
  "data": [
    {
      "b64_json": "/9j/4AAQSkZJRgABAQAA...",
      "revised_prompt": "A tranquil Japanese garden featuring..."
    }
  ]
}

Nano Banana 응답 참고 사항:

  • Nano Banana에서 response_format: "url"을 사용할 때, Gemini의 네이티브 API가 인라인 base64 데이터를 반환하므로 이미지는 data URL (data:image/png;base64,...)로 반환됩니다
  • revised_prompt 필드에는 생성된 이미지를 설명하는 Gemini의 텍스트 응답이 포함됩니다
  • google_search: true (또는 tools: [{"google_search": {}}])를 사용할 때 Gemini가 반환한 groundingMetadata는 이미지 응답 최상위의 grounding_metadata 필드로 보존됩니다
  • Gemini 백엔드에 대한 /v1/chat/completions 또는 /v1/responses 요청에 tools: [{"google_search": {}}], tools: [{"code_execution": {}}], modalities: ["text", "image"], cachedContent, 또는 gemini_context_cache.prefer_native_api: true가 포함되면 라우터는 자동으로 Gemini 네이티브 generateContent / streamGenerateContent 엔드포인트로 디스패치하고 응답의 annotations, grounding_metadata, code_execution, 인라인 images를 OpenAI 응답 형식으로 변환합니다

스트리밍 이미지 생성 (GPT Image 모델):

GPT Image 모델에 대해 stream: true가 지정되면 응답이 Server-Sent Events (SSE)로 스트리밍됩니다:

스트리밍 요청 예제:

{
  "model": "gpt-image-1",
  "prompt": "A beautiful sunset over mountains",
  "stream": true,
  "partial_images": 2,
  "response_format": "b64_json"
}

스트리밍 응답 형식:

data: {"type":"image_generation.partial_image","partial_image_index":0,"b64_json":"...","created":1702345678}

data: {"type":"image_generation.partial_image","partial_image_index":1,"b64_json":"...","created":1702345679}

data: {"type":"image_generation.complete","b64_json":"...","created":1702345680}

data: {"type":"image_generation.usage","usage":{"input_tokens":25,"output_tokens":1024}}

data: {"type":"done"}

SSE 이벤트 타입:

이벤트 타입 설명
image_generation.partial_image 생성 중 중간 이미지
image_generation.complete 최종 완성 이미지
image_generation.usage 토큰 사용량 정보 (비용 추적용)
done 스트림 완료 마커

새 옵션이 포함된 GPT Image 요청 예제:

{
  "model": "gpt-image-1.5",
  "prompt": "A white cat with blue eyes, photorealistic",
  "size": "auto",
  "quality": "high",
  "output_format": "webp",
  "output_compression": 85,
  "background": "transparent",
  "response_format": "b64_json"
}

상태 코드:

  • 200: 이미지가 성공적으로 생성됨
  • 400: 유효하지 않은 요청 (예: 모델에 대해 유효하지 않은 크기, DALL-E 3에서 n > 1)
  • 401: 유효하지 않은 API 키
  • 429: 속도 제한 초과
  • 500: 백엔드 오류
  • 503: Gemini 백엔드 사용 불가 (Nano Banana 모델의 경우)

타임아웃 설정: 이미지 생성 요청은 설정 가능한 타임아웃을 사용합니다 (기본값: 3분). 설정의 timeouts.request.image_generation을 참조하세요.


이미지 편집 (인페인팅)

OpenAI의 인페인팅 기능 또는 Gemini의 자연어 기반 편집을 사용하여 기존 이미지를 편집합니다. 이 엔드포인트를 사용하면 텍스트 프롬프트와 선택적 마스크를 기반으로 하나 이상의 입력 이미지를 수정하거나 합성할 수 있습니다. GPT Image 모델, Nano Banana (Gemini) 모델, 그리고 DALL-E 2를 지원합니다.

POST /v1/images/edits
Content-Type: multipart/form-data

요청 파라미터 (multipart/form-data):

파라미터 타입 필수 설명
image / image[] file 예 편집할 하나 이상의 소스 이미지 (이미지당 < 4MB, 모델별 지원 형식 및 최대 개수는 아래 표 참고)
prompt string 예 원하는 편집에 대한 설명
mask file 아니오 편집 영역을 나타내는 마스크 이미지 (PNG, 첫 번째 소스 이미지와 동일한 크기, Nano Banana 모델에서는 무시됨)
model string 아니오 사용할 모델 (기본값: gpt-image-2)
n integer 아니오 생성할 이미지 수 (OpenAI 모델은 1-10, Nano Banana는 1, 기본값: 1)
size string 아니오 출력 크기 (모델에 따라 다름, 기본값: 1024x1024)
response_format string 아니오 응답 형식: url 또는 b64_json (기본값: url)
google_search boolean 아니오 nano-banana-pro 및 nano-banana-2에서 Google Search 그라운딩 (tools: [{"google_search": {}}]) 활성화
user string 아니오 추적을 위한 고유 사용자 식별자

지원되는 모델, 크기 및 입력 이미지 제한:

모델 크기 최대 입력 이미지 수 지원 입력 형식 참고
gpt-image-2, gpt-image-2.5-sunburst, gpt-image-2.5-flare 프리셋 또는 사용자 지정 WIDTHxHEIGHT, 생성과 같은 규칙 16 PNG, JPEG, WebP GPT Image 2 세대
gpt-image-1 1024x1024, 1536x1024, 1024x1536, auto 16 PNG, JPEG, WebP GPT Image 1 모델
gpt-image-1-mini 1024x1024, 1536x1024, 1024x1536, auto 16 PNG, JPEG, WebP 비용 최적화 버전
gpt-image-1.5 1024x1024, 1536x1024, 1024x1536, auto 16 PNG, JPEG, WebP 향상된 지시 따르기 기능
nano-banana, nano-banana-2 256x256, 512x512, 1024x1024, 1024x1792, 1792x1024, 1536x1024, 1024x1536, 2048x2048, 4096x4096, auto 14 PNG, JPEG, WebP, HEIC, HEIF Gemini 3.1 Flash Image (mask 무시, n=1, 최대 4K)
nano-banana-2-lite 256x256, 512x512, 1024x1024, 1024x1792, 1792x1024, 1536x1024, 1024x1536, auto 14 PNG, JPEG, WebP, HEIC, HEIF Gemini 3.1 Flash Lite Image (mask 무시, n=1, 1K)
nano-banana-pro nano-banana-2와 동일 (최대 4K) 14 PNG, JPEG, WebP, HEIC, HEIF Gemini 3 Pro Image (mask 무시, n=1)
dall-e-2 256x256, 512x512, 1024x1024 1 PNG (정사각형) 레거시 DALL-E 2 모델

참고: DALL-E 3는 이 엔드포인트를 통한 이미지 편집을 지원하지 않습니다. Nano Banana (nano-banana, nano-banana-2, nano-banana-2-lite, nano-banana-pro) 및 Gemini 이미지 모델은 자연어 기반 편집과 다중 이미지 합성(최대 14개 입력 이미지)을 지원합니다. Nano Banana 요청에 mask 필드가 포함되면 경고를 기록하고 무시하며 프롬프트 텍스트로 편집 영역을 결정합니다.

이미지 요구 사항:

  • 필드 이름: image 또는 image[] (여러 입력 이미지를 보내려면 필드를 반복 지정)
  • 크기: 이미지당 4MB 미만
  • 개수 제한: gpt-image-* 최대 16개, nano-banana / nano-banana-2 / nano-banana-2-lite / nano-banana-pro / Gemini 이미지 모델 최대 14개, dall-e-2 1개
  • 형식 및 해상도:
    • gpt-image-*: PNG, JPEG, WebP (비정사각형 허용)
    • nano-banana, nano-banana-2, nano-banana-2-lite, nano-banana-pro: PNG, JPEG, WebP, HEIC, HEIF (비정사각형 허용)
    • dall-e-2: PNG만 허용, 정사각형이어야 함 (가로 = 세로)

마스크 요구 사항:

  • 형식: 알파 채널이 있는 PNG (RGBA)
  • 크기: 첫 번째 소스 이미지(images[0])와 정확히 일치해야 함
  • 투명 영역: 편집/생성할 영역을 나타냄
  • 불투명 영역: 보존할 영역을 나타냄

요청 예제:

curl -X POST http://localhost:8080/v1/images/edits \
  -F "image=@source_image.png" \
  -F "mask=@mask.png" \
  -F "prompt=A sunlit indoor lounge area with a pool containing a flamingo" \
  -F "n=1" \
  -F "size=1024x1024" \
  -F "response_format=url"

요청 예제 (다중 입력 이미지):

curl -X POST http://localhost:8080/v1/images/edits \
  -F "model=gpt-image-2" \
  -F "image[]=@subject.png" \
  -F "image[]=@background.png" \
  -F "prompt=Place the subject from the first image into the background of the second image" \
  -F "n=1" \
  -F "size=1024x1024"

응답:

{
  "created": 1677652288,
  "data": [
    {
      "url": "https://oaidalleapiprodscus.blob.core.windows.net/..."
    }
  ]
}

응답 (b64_json 포함):

{
  "created": 1677652288,
  "data": [
    {
      "b64_json": "/9j/4AAQSkZJRgABAQAA..."
    }
  ]
}

상태 코드:

  • 200: 이미지가 성공적으로 편집됨
  • 400: 유효하지 않은 요청 (예: 정사각형이 아닌 이미지, 유효하지 않은 크기, 필수 필드 누락)
  • 401: 유효하지 않은 API 키
  • 503: OpenAI 백엔드 사용 불가

오류 예제:

정사각형이 아닌 이미지:

{
  "error": {
    "message": "Image must be square (800x600 is not square)",
    "type": "invalid_request_error",
    "param": "image",
    "code": "image_not_square"
  }
}

마스크 크기 불일치:

{
  "error": {
    "message": "Mask dimensions (256x256) do not match image dimensions (512x512)",
    "type": "invalid_request_error",
    "param": "mask",
    "code": "dimension_mismatch"
  }
}

지원되지 않는 모델:

{
  "error": {
    "message": "Model 'dall-e-3' does not support image editing. Supported models: gpt-image-2.5-sunburst, gpt-image-2.5-flare, gpt-image-2, gpt-image-1, gpt-image-1-mini, gpt-image-1.5, dall-e-2, nano-banana, nano-banana-2, nano-banana-2-lite, nano-banana-pro. Note: dall-e-3 does NOT support image editing.",
    "type": "invalid_request_error",
    "param": "model",
    "code": "unsupported_model"
  }
}

참고 사항:

  • 지원되는 모델: gpt-image-2.5-sunburst, gpt-image-2.5-flare, gpt-image-2, gpt-image-1, gpt-image-1-mini, gpt-image-1.5, dall-e-2, nano-banana, nano-banana-2, nano-banana-2-lite, nano-banana-pro 및 해당 모델이나 Gemini 이미지 모델로 해석되는 메타데이터 별칭(alias)
  • DALL-E 3는 API를 통한 이미지 편집을 지원하지 않습니다
  • Nano Banana (nano-banana, nano-banana-2, nano-banana-2-lite, nano-banana-pro)는 자연어 편집을 사용하여 Gemini 네이티브 generateContent API로 라우팅됩니다 (mask가 제공되면 무시되며 n은 1이어야 합니다)
  • 모델별 입력 한도(gpt-image-* 16개, nano-banana / nano-banana-2 / nano-banana-2-lite / nano-banana-pro 14개, dall-e-2 1개) 내에서 image 또는 image[] 필드를 반복하여 여러 입력 이미지를 업로드할 수 있습니다
  • 마스크가 제공되지 않으면 전체 이미지가 수정될 수 있습니다
  • 요청 타임아웃은 이미지 생성 타임아웃 설정을 사용합니다

이미지 변형

Nano Banana (Gemini) 또는 OpenAI의 DALL-E 2 모델을 사용하여 기존 이미지의 변형을 생성합니다.

POST /v1/images/variations
Content-Type: multipart/form-data

폼 필드:

파라미터 타입 필수 설명
image file 예 변형을 위한 단일 소스 이미지 (< 4MB, 1개의 입력 이미지만 허용됨)
model string 아니오 사용할 모델 (기본값: nano-banana. OpenAI의 유일한 변형 모델이던 dall-e-2는 2026-05-12에 종료되었습니다)
n integer 아니오 생성할 변형 수 (DALL-E 2는 1-10, Nano Banana는 1, 기본값: 1)
size string 아니오 출력 크기: 256x256, 512x512, 1024x1024 (기본값: 1024x1024)
response_format string 아니오 응답 형식: url 또는 b64_json (기본값: url)
user string 아니오 추적을 위한 사용자 식별자

요청 예제:

curl -X POST http://localhost:8080/v1/images/variations \
  -F "image=@source_image.png" \
  -F "model=dall-e-2" \
  -F "n=2" \
  -F "size=512x512" \
  -F "response_format=url"

응답:

{
  "created": 1677652288,
  "data": [
    {
      "url": "https://oaidalleapiprodscus.blob.core.windows.net/..."
    },
    {
      "url": "https://oaidalleapiprodscus.blob.core.windows.net/..."
    }
  ]
}

응답 (b64_json 포함):

{
  "created": 1677652288,
  "data": [
    {
      "b64_json": "/9j/4AAQSkZJRgABAQAA..."
    }
  ]
}

모델 지원:

모델 변형 지원 참고
nano-banana, nano-banana-2 예 (기본값) Gemini generateContent를 통해 지원 (n=1, 최대 4K; PNG, JPEG, WebP, HEIC, HEIF)
nano-banana-2-lite 예 Gemini generateContent를 통해 지원 (n=1, 1K; PNG, JPEG, WebP, HEIC, HEIF)
nano-banana-pro 예 Gemini generateContent를 통해 지원 (n=1, 최대 4K)
dall-e-2 예 (네이티브) 레거시 OpenAI 지원, 1-10개 변형 (PNG, 정사각형)
dall-e-3 아니오 OpenAI API에서 지원하지 않음
gpt-image-1 아니오 지원되지 않음

이미지 요구 사항:

  • 형식: PNG만
  • 크기: 4MB 미만
  • 크기: 정사각형이어야 함 (가로 == 세로)
  • 지원되는 입력 크기: 모든 정사각형 크기 (모델에서 처리됨)

오류 시나리오:

오류 상태 설명
PNG가 아닌 이미지 400 PNG 형식만 지원됨
정사각형이 아닌 이미지 400 이미지 크기가 동일해야 함
이미지가 너무 큼 400 이미지가 4MB 크기 제한 초과
지원되지 않는 모델 400 요청된 모델이 변형을 지원하지 않음
이미지 누락 400 image 필드 필수
유효하지 않은 n 값 400 n은 1과 10 사이여야 함
유효하지 않은 크기 400 크기는 지원되는 값 중 하나여야 함

상태 코드:

  • 200: 변형이 성공적으로 생성됨
  • 400: 유효하지 않은 요청 (유효하지 않은 형식, 정사각형이 아닌 이미지, 지원되지 않는 모델)
  • 401: 유효하지 않은 API 키
  • 429: 속도 제한 초과
  • 500: 백엔드 오류
  • 503: 백엔드 사용 불가

오디오 전사 및 번역 (Audio Transcriptions & Translations)

OpenAI 호환 multipart/form-data 패스스루를 사용하여 오디오를 원본 언어로 전사(POST /v1/audio/transcriptions)하거나 영어로 번역(POST /v1/audio/translations)합니다.

POST /v1/audio/transcriptions
POST /v1/audio/translations
Content-Type: multipart/form-data

폼 필드:

파라미터 타입 필수 설명
file file 예 전사 또는 번역할 오디오 파일 (proxy.audio.max_body_size로 제한, 기본값 25 MiB)
model string 예 사용할 오디오 모델 ID (예: whisper-1, gpt-4o-transcribe, gpt-4o-mini-transcribe)
response_format string 아니오 출력 형식: json, text, srt, vtt, verbose_json, diarized_json (업스트림 HTTP 상태, Content-Type, 원시 바이트 그대로 전달)
language string 아니오 ISO-639-1 형식의 입력 오디오 언어 (전사 전용)
prompt string 아니오 모델 스타일을 안내하는 선택적 텍스트
temperature number 아니오 0에서 1 사이의 샘플링 온도
timestamp_granularities[] array 아니오 verbose_json용 타임스탬프 단위 (word, segment)
stream boolean 아니오 stream=true는 400 Bad Request (streaming_not_supported)로 거부됩니다

라우팅 및 제한:

  • 모델 라우팅 및 정책: 멀티파트 폼의 model 필드를 추출하여 키별 allowed_models, Hub 티어 model_allowlist, rate_limiting.limits.per_model, 백엔드 선택(x-backend 및 x-served-backend 포함)에 동일하게 적용합니다.
  • 본문 크기 및 동시 업로드 제한: proxy.audio.max_body_size(기본값 26214400 / 25 MiB, 최대 100 MiB)와 proxy.audio.max_concurrent_uploads(기본값 8, 최대 64)로 제어됩니다. 제한을 초과하는 업로드는 전체 버퍼링 없이 즉시 413 Payload Too Large를 반환합니다.
  • 타임아웃 및 재시도 비활성화: timeouts.request.audio(기본값 first_byte: "60s", total: "300s") 및 timeouts.limits.max_audio_timeout(기본값 "600s")이 적용되며 업로드 본문은 재시도나 폴백 없이 단일 요청으로 전송됩니다.
  • 사용량 측정: 컨트롤 플레인 빌드에서는 과금 대상 오디오 길이를 초 단위 정수로 사용량 기록의 audio_seconds 필드에 기록하고(usage.type == "duration"이면 usage.seconds를 우선 사용하고 verbose_json 응답에서는 최상위 duration을 올림) audio_microusd_per_second로 요금을 산정하며 input_tokens, output_tokens, cached_input_tokens는 0으로 유지되어 토큰 속도 제한과 토큰 예산 창에 영향을 주지 않습니다.

오디오 음성 합성 (Audio Speech, TTS)

OpenAI 호환 JSON 패스스루로 텍스트에서 오디오를 생성합니다(POST /v1/audio/speech). 요청 본문은 application/json으로 선택된 백엔드의 /v1/audio/speech에 전달됩니다. 성공한 오디오 응답은 백엔드가 보내는 대로 클라이언트에 스트리밍하며, 백엔드의 상태 코드와 Content-Type(보통 audio/mpeg)을 그대로 씁니다. 그래서 합성이 끝나기 전에 재생을 시작할 수 있습니다.

POST /v1/audio/speech
Content-Type: application/json

요청 본문:

파라미터 타입 필수 설명
model string 예 사용할 TTS 모델 ID (예: tts-1, tts-1-hd, gpt-4o-mini-tts)
input string 예 합성할 텍스트. 빈 값은 허용하지 않음
voice string 아니오 사용할 음성 (예: alloy). 백엔드에서 검증합니다

그 외 OpenAI 필드(response_format, speed, instructions 등)는 변경 없이 전달됩니다. 별칭 디스패치가 설정되어 있으면 model 필드가 백엔드의 표준 모델 ID로 재작성됩니다.

라우팅 및 제한:

  • 모델 라우팅 및 정책: JSON 본문의 model 필드를 추출하여 키별 allowed_models, Hub 티어 model_allowlist, 백엔드 선택(x-backend 및 x-served-backend 포함)에 동일하게 적용합니다.
  • 본문 크기 제한: JSON 본문은 1 MiB까지 받으며, proxy.audio.max_body_size가 더 작으면 그 값을 씁니다. 더 큰 본문은 Content-Length 선언 여부와 관계없이 413 Payload Too Large를 반환하고, 라우터는 한도에서 읽기를 멈춥니다.
  • 속도 제한과 사용량 기록: 모델별 속도 제한은 본문의 model을 기준으로 적용하고, 이 경로에는 write API 키 스코프가 필요합니다. 처리한 호출마다 사용량 행을 하나 기록합니다. 오디오 본문이면 토큰 0개의 usage_missing, 백엔드가 JSON으로 응답하면 파싱한 usage를 기록하며, 기록 시점은 응답 헤더가 도착했을 때입니다.
  • 백엔드 오류: 전사 경로와 마찬가지로 백엔드 4xx는 정제한 백엔드 메시지로, 5xx는 일반 메시지로 응답합니다. 백엔드의 원본 본문은 그대로 전달하지 않습니다.
  • 타임아웃 및 재시도 비활성화: timeouts.request.audio(기본값 first_byte: "60s", total: "300s") 및 timeouts.limits.max_audio_timeout(기본값 "600s")이 적용되며 요청 본문은 재시도나 폴백 없이 단일 요청으로 전송됩니다.

오류:

오류 상태 코드 코드
model 필드 누락 400 missing_model
input 필드 누락 또는 빈 값 400 missing_input
본문이 유효한 JSON 객체가 아님 400 invalid_json
X-Model 헤더 불일치 400 conflicting_model
본문 크기 초과 413 payload_too_large
모델에 연결된 백엔드 없음 404 model_not_found
키의 allowed_models / allowed_backends에서 제외된 모델 또는 백엔드 403 model_not_permitted
백엔드 타임아웃 504 audio_timeout
백엔드 연결 오류 502 audio_connection_error

텍스트 완료

OpenAI Completions API 형식을 사용하여 텍스트 완료를 생성합니다.

POST /v1/completions

요청 본문:

{
  "model": "gpt-5.6-luna",
  "prompt": "Once upon a time in a distant galaxy",
  "max_tokens": 100,
  "temperature": 0.7,
  "top_p": 1.0,
  "frequency_penalty": 0.0,
  "presence_penalty": 0.0,
  "stream": false,
  "stop": null,
  "logit_bias": {},
  "user": "user123"
}

응답:

{
  "id": "cmpl-123456789",
  "object": "text_completion",
  "created": 1677652288,
  "model": "gpt-5.6-luna",
  "choices": [
    {
      "text": ", there lived a young explorer named Zara who dreamed of discovering new worlds...",
      "index": 0,
      "finish_reason": "stop",
      "logprobs": null
    }
  ],
  "usage": {
    "prompt_tokens": 10,
    "completion_tokens": 90,
    "total_tokens": 100
  }
}

스트리밍: stream: true이고 해당 라우트의 가드레일이 꺼져 있으면, TCP와 Unix 소켓 백엔드의 응답을 text/event-stream으로 즉시 전달하며 Content-Length를 보내지 않습니다. 생성이 끝나기 전에 첫 텍스트 청크를 받을 수 있습니다. 스트림 도중 오류가 발생하면 SSE 오류를 보내고 종료하며, 스트림을 수락한 뒤에는 재시도하지 않습니다.

라우터는 사용량 집계를 위해 항상 업스트림 사용량을 요청합니다. 마지막 사용량 전용 청크를 받으려면 stream_options.include_usage: true를 설정하세요. 해당 청크 전에 연결이 끊기거나 오류가 발생하면, 관측한 텍스트와 프롬프트 추정치로 사용량을 기록합니다. 해당 라우트의 가드레일이 켜져 있으면 차단하거나 마스킹할 텍스트가 노출되지 않도록 전체 출력을 버퍼링한 뒤 전달합니다.

가드레일: 가드레일이 설정되면 이 엔드포인트는 입력 단계와 출력 단계를 모두 실행합니다(가드레일: 라우트별 적용 범위 참고):

  • 입력 단계: 각 프롬프트(문자열 prompt, 또는 배열 prompt의 각 원소로 문자열이나 중첩된 문자열 배열)와 suffix(한 번)는 디스패치 전에 각각 따로 입력 단계를 거칩니다(프롬프트 최대 128개, 초과 시 enforce 모드에서 uninspectable_input으로 거부). 마스킹 판정은 각 프롬프트 세그먼트 자리에 적용됩니다. 토큰 ID 또는 prompt_embeds 프롬프트는 검사할 수 없으므로, 해당 모델에 입력 단계 검사가 적용될 때 enforce 모드에서는 거부되고 monitor 모드에서는 기록된 뒤 통과합니다.
  • 출력 단계: 응답의 choices[].text는 출력 단계를 거치며, 이 엔드포인트가 버퍼링해 응답하는 stream: true 응답도 포함됩니다.
  • 차단 응답: 차단된 요청은 기본적으로 finish_reason이 content_filter인 text_completion(HTTP 200)으로 응답하며, block_behavior: error에서는 OpenAI content_filter 오류 봉투(HTTP 400)로 응답합니다.

상태 코드: 채팅 완료와 동일


임베딩

알려진 capability 목록이 비어 있지 않다면 embedding을 포함해야 합니다. 포함하지 않으면 백엔드로 요청을 보내기 전에 HTTP 400과 unsupported_model_capability를 반환합니다. 목록이 없거나 비어 있으면 기존처럼 요청을 허용합니다. 멀티모달 임베딩 요청에도 같은 검사를 적용합니다.

OpenAI 임베딩 API 형식을 사용하여 입력 텍스트에 대한 임베딩을 생성합니다.

POST /v1/embeddings

요청 본문:

{
  "model": "text-embedding-3-small",
  "input": "The quick brown fox jumps over the lazy dog",
  "encoding_format": "float",
  "dimensions": 512,
  "user": "user123"
}

요청 파라미터:

필드 타입 필수 설명
model string 예 사용할 모델의 ID (예: text-embedding-3-small, gemini-embedding-2)
input string, array, 또는 콘텐츠 파트 예 임베딩할 입력. 단일 문자열, 문자열 배열, 토큰 배열, 또는 멀티모달 콘텐츠 파트 배열(Gemini Embedding 2 전용)이 될 수 있음
encoding_format string 아니오 임베딩 반환 형식: float 또는 base64 (기본값: float)
dimensions integer 아니오 출력 임베딩의 차원 수 (text-embedding-3-* 및 gemini-embedding-2* 모델 지원)
user string 아니오 최종 사용자의 고유 식별자

입력 형식 예시:

단일 문자열:

{
  "model": "text-embedding-3-small",
  "input": "Hello, world!"
}

문자열 배열:

{
  "model": "text-embedding-3-large",
  "input": [
    "임베딩할 첫 번째 텍스트",
    "임베딩할 두 번째 텍스트",
    "임베딩할 세 번째 텍스트"
  ]
}

토큰 배열:

{
  "model": "text-embedding-ada-002",
  "input": [[1234, 5678, 9012], [3456, 7890, 1234]]
}

멀티모달 콘텐츠 파트 (Gemini Embedding 2 전용):

{
  "model": "gemini-embedding-2",
  "input": [
    {"type": "text", "text": "골든 리트리버 사진"},
    {"type": "image_url", "image_url": {"url": "data:image/jpeg;base64,/9j/4AAQ..."}}
  ]
}

응답:

{
  "object": "list",
  "data": [
    {
      "object": "embedding",
      "index": 0,
      "embedding": [0.0023064255, -0.009327292, 0.0045318254, ...]
    }
  ],
  "model": "text-embedding-3-small",
  "usage": {
    "prompt_tokens": 8,
    "total_tokens": 8
  }
}

응답 필드:

필드 타입 설명
object string 항상 "list"
data array 임베딩 객체 배열
data[].object string 항상 "embedding"
data[].index integer 입력 배열에서 임베딩의 인덱스
data[].embedding array 임베딩 벡터 (부동소수점 배열)
model string 임베딩 생성에 사용된 모델
usage object 토큰 사용 정보
usage.prompt_tokens integer 입력의 토큰 수
usage.total_tokens integer 사용된 총 토큰 (임베딩의 경우 prompt_tokens와 동일)

지원 모델:

백엔드 모델 참고
OpenAI text-embedding-3-small 기본 1536 차원, dimensions 파라미터 지원
OpenAI text-embedding-3-large 기본 3072 차원, dimensions 파라미터 지원
OpenAI text-embedding-ada-002 1536 차원, 레거시 모델
Gemini gemini-embedding-2 3072 차원, 멀티모달 지원 (텍스트, 이미지, 오디오, 비디오, 문서)
Gemini gemini-embedding-2-preview Gemini Embedding 2 프리뷰, 멀티모달 지원
Gemini text-embedding-004 텍스트 전용, OpenAI 호환 엔드포인트 사용
자체 호스팅 bge-m3 1024 차원, 100개 이상 언어, 8192 컨텍스트. Dense, sparse, ColBERT 검색 지원
자체 호스팅 bge-large-en-v1.5 1024 차원, 영어 전용, 512 컨텍스트
자체 호스팅 multilingual-e5-large 1024 차원, 100개 이상 언어, 514 컨텍스트
vLLM 배포별 다름 배포된 모델에 따라 다름
llama.cpp 배포별 다름 네이티브 /v1/embeddings 지원
TEI 배포별 다름 Hugging Face Text Embeddings Inference 서버
Ollama 배포별 다름 Ollama 임베딩 모델 사용

상태 코드:

  • 200: 임베딩이 성공적으로 생성됨
  • 400: 유효하지 않은 요청 (model/input 누락, 유효하지 않은 dimensions)
  • 401: 유효하지 않은 API 키
  • 404: 모델을 찾을 수 없거나 임베딩을 지원하지 않음
  • 429: 속도 제한 초과
  • 500: 백엔드 오류
  • 503: 백엔드 사용 불가

기능:

  • 다양한 입력 형식: 단일 문자열, 문자열 배열, 토큰 배열 또는 멀티모달 콘텐츠 파트 지원
  • 멀티모달 임베딩: gemini-embedding-2 및 gemini-embedding-2-preview는 텍스트, 이미지, 오디오, 비디오, 문서를 하나의 요청에 혼합하여 처리 가능
  • 차원 제어: text-embedding-3-* 및 gemini-embedding-2* 모델의 경우 축소된 벡터 크기를 위한 사용자 정의 차원 지정
  • 백엔드 독립적: 모델에 따라 적절한 백엔드로 라우팅
  • 로드 밸런싱: 설정된 로드 밸런싱 전략 적용
  • 오류 처리: 텍스트 전용 모델에 멀티모달 입력을 전송하면 400 오류와 명확한 메시지 반환

멀티모달 콘텐츠 파트 타입 (Gemini Embedding 2 전용):

타입 필드 제약 조건
text text (string) 비어있지 않아야 함
image_url image_url.url (data URI 또는 file://file-* 참조) MIME: image/jpeg, image/png; 최대 20 MiB; 요청당 최대 6개 이미지
audio audio.data (base64), audio.format 형식: mp3, wav; 최대 20 MiB
video video.data (base64), video.format 형식: mp4, mov; 최대 20 MiB
document document.data (base64), document.format 형식: pdf; 최대 20 MiB

요청 예제:

curl -X POST http://localhost:8080/v1/embeddings \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer your-api-key" \
  -d '{
    "model": "text-embedding-3-small",
    "input": "The quick brown fox jumps over the lazy dog",
    "encoding_format": "float"
  }'

여러 입력 예제:

curl -X POST http://localhost:8080/v1/embeddings \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer your-api-key" \
  -d '{
    "model": "text-embedding-3-large",
    "input": [
      "첫 번째 문서 텍스트",
      "두 번째 문서 텍스트",
      "세 번째 문서 텍스트"
    ]
  }'

사용자 정의 차원 예제:

curl -X POST http://localhost:8080/v1/embeddings \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer your-api-key" \
  -d '{
    "model": "text-embedding-3-small",
    "input": "샘플 텍스트",
    "dimensions": 512
  }'

멀티모달 입력 예제 (Gemini Embedding 2):

curl -X POST http://localhost:8080/v1/embeddings \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer your-api-key" \
  -d '{
    "model": "gemini-embedding-2",
    "input": [
      {"type": "text", "text": "바다 위 석양 사진"},
      {"type": "image_url", "image_url": {"url": "data:image/jpeg;base64,/9j/4AAQ..."}}
    ],
    "dimensions": 768
  }'

리랭킹

Cohere 호환 Rerank API를 사용하여 쿼리와의 관련성을 기준으로 문서를 리랭킹합니다. 이는 일반적으로 초기 벡터 검색 후 정확도를 향상시키기 위한 2단계 검색 단계로 사용됩니다.

POST /v1/rerank

요청 본문:

{
  "model": "bge-reranker-v2-m3",
  "query": "머신러닝이란 무엇인가요?",
  "documents": ["문서 1 내용", "문서 2 내용", "문서 3 내용"],
  "top_n": 3,
  "return_documents": false
}

요청 파라미터:

필드 타입 필수 설명
model string 예 사용할 리랭킹 모델의 ID (예: bge-reranker-v2-m3, rerank-english-v3.0, jina-reranker-v2-base-multilingual)
query string 예 문서와 비교할 검색 쿼리
documents array 예 리랭킹할 문서 목록. 문자열 배열 또는 text 필드가 있는 객체 배열이 될 수 있음
top_n integer 아니오 반환할 상위 결과 수. 지정하지 않으면 모든 문서가 순위대로 반환됨
return_documents boolean 아니오 응답에 문서 텍스트를 포함할지 여부 (기본값: false)
max_chunks_per_doc integer 아니오 긴 문서 처리를 위해 문서당 처리할 최대 청크 수

문서 형식 옵션:

단순 문자열 배열:

{
  "model": "bge-reranker-v2-m3",
  "query": "딥러닝이란 무엇인가요?",
  "documents": [
    "딥러닝은 여러 층의 신경망을 사용합니다",
    "머신러닝은 인공지능의 하위 분야입니다",
    "자연어 처리는 텍스트 이해를 다룹니다"
  ]
}

text 필드가 있는 구조화된 문서:

{
  "model": "rerank-english-v3.0",
  "query": "딥러닝이란 무엇인가요?",
  "documents": [
    {"text": "딥러닝은 여러 층의 신경망을 사용합니다"},
    {"text": "머신러닝은 인공지능의 하위 분야입니다"}
  ]
}

응답:

{
  "results": [
    {
      "index": 0,
      "relevance_score": 0.95
    },
    {
      "index": 2,
      "relevance_score": 0.72
    },
    {
      "index": 1,
      "relevance_score": 0.45
    }
  ],
  "model": "bge-reranker-v2-m3",
  "id": "rerank-abc123",
  "usage": {
    "prompt_tokens": 150,
    "total_tokens": 150
  }
}

문서 포함 응답 (return_documents: true 인 경우):

{
  "results": [
    {
      "index": 0,
      "relevance_score": 0.95,
      "document": {
        "text": "딥러닝은 여러 층의 신경망을 사용합니다"
      }
    }
  ],
  "model": "bge-reranker-v2-m3"
}

응답 필드:

필드 타입 설명
results array 관련성 점수 순으로 정렬된 리랭킹 결과 목록 (높은 순)
results[].index integer 원본 입력 목록에서 문서의 인덱스
results[].relevance_score number 관련성 점수 (일반적으로 0.0에서 1.0, 높을수록 더 관련됨)
results[].document object 문서 텍스트 (return_documents가 true인 경우에만 존재)
model string 리랭킹에 사용된 모델
id string 이 요청의 고유 식별자 (선택적)
usage object 토큰 사용 정보 (선택적)

지원 백엔드:

백엔드 엔드포인트 참고
vLLM /v1/rerank Cohere 호환, BGE, Jina 리랭커 지원
llama.cpp /v1/rerank 시작 시 --reranking 플래그 필요
Hugging Face TEI /rerank Text Embeddings Inference 서버
Cohere API /v1/rerank 네이티브 Cohere rerank 엔드포인트
Jina AI /v1/rerank 네이티브 Jina rerank 엔드포인트

상태 코드:

  • 200: 문서가 성공적으로 리랭킹됨
  • 400: 유효하지 않은 요청 (model/query/documents 누락, 빈 documents 배열)
  • 401: 유효하지 않은 API 키
  • 404: 모델을 찾을 수 없거나 리랭킹을 지원하지 않음
  • 429: 속도 제한 초과
  • 500: 백엔드 오류
  • 503: 백엔드 사용 불가

사용 사례:

  • 2단계 검색: 벡터 검색으로 후보를 검색한 다음 더 높은 정밀도를 위해 리랭킹
  • RAG 시스템: LLM 처리 전에 검색된 문서를 리랭킹하여 컨텍스트 품질 향상
  • 검색 결과 정제: 의미적 관련성에 따라 검색 결과 재정렬

요청 예제:

curl -X POST http://localhost:8080/v1/rerank \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer your-api-key" \
  -d '{
    "model": "bge-reranker-v2-m3",
    "query": "재생 에너지의 장점은 무엇인가요?",
    "documents": [
      "태양광 패널은 햇빛을 전기로 변환합니다",
      "풍력 터빈은 바람에서 전력을 생성합니다",
      "석탄은 전기에 사용되는 화석 연료입니다"
    ],
    "top_n": 2
  }'

모더레이션

라우터에 설정된 가드레일로 텍스트를 OpenAI Moderations API 형식에 맞춰 분류합니다. 따라서 client.moderations.create()를 라우터에 그대로 사용할 수 있습니다.

POST /v1/moderations

요청 본문:

{
  "model": "gpt-5.6-terra",
  "input": ["분류할 첫 번째 텍스트", "분류할 두 번째 텍스트"]
}
필드 타입 필수 설명
input string 또는 array 예 텍스트 하나, 또는 최대 32개 항목의 배열입니다. 각 항목은 문자열이거나 {"type": "text", "text": "..."} 파트입니다. 이미지 파트는 400 invalid_input으로 거부합니다
model string 아니요 해당 모델의 가드레일 경로(guardrails.routes)를 선택하며, 같은 모델로 보낸 채팅 요청과 같은 경로를 씁니다. 모더레이션 모델을 지정하는 값은 아닙니다. 생략하면 전역 정책을 적용하고 응답에는 guardrails가 담깁니다

응답:

{
  "id": "modr-3f6c0d1e9a2b4c5d6e7f8a9b",
  "model": "gpt-5.6-terra",
  "results": [
    {
      "flagged": true,
      "categories": {"violence": true},
      "category_scores": {"violence": 0.91}
    },
    {
      "flagged": false,
      "categories": {},
      "category_scores": {}
    }
  ]
}
  • 입력마다 결과 하나를 입력 순서대로 반환합니다. 각 입력은 실제 요청과 같은 입력 단계 평가를 거칩니다. 먼저 deny와 allow 매치 목록을 확인하고 이어서 선택된 경로의 입력 단계에 참여하는 모든 프로바이더를 같은 임계값으로 실행합니다. 모더레이션 요청은 정책 조치가 아니라 분류를 묻는 요청이므로, 전역 guardrails.mode와 우회 목록은 적용하지 않습니다.
  • 카테고리는 OpenAI의 고정 카테고리 목록이 아니라 라우터의 가드레일 카테고리(violence, hate_speech, sexual_content, self_harm, harassment, dangerous, jailbreak, pii, profanity 또는 프로바이더 고유 레이블)입니다. 어떤 항목이 표시하거나 차단한 카테고리만 나타나며 category_scores에는 카테고리별로 가장 높은 프로바이더 점수가 담깁니다. deny 매치 목록에 걸리면 deny_list 카테고리와 점수 1.0으로 보고합니다.
  • 프로바이더 실패로는 텍스트를 표시하지 않습니다. 프로바이더의 실패 정책 판정(예: fail_closed)은 결과에서 제외합니다. 실행된 평가가 모두 실패하면 503 moderation_unavailable로 응답합니다.
  • 스코프와 비용: 입력마다 설정된 프로바이더를 모두 호출할 수 있으므로, 이 경로에는 write 스코프가 필요합니다.

오류:

오류 상태 코드
이 라우터에서 가드레일이 활성화되어 있지 않음 400 moderation_unavailable
input이 없거나 비어 있거나 문자열 또는 텍스트 파트가 아님 400 invalid_input
input 항목이 32개를 초과함 400 too_many_inputs
model이 문자열이 아님 400 invalid_type
본문이 올바른 JSON이 아님 400 invalid_json
모든 가드레일 프로바이더가 실패함 503 moderation_unavailable

토큰화와 역토큰화

모델을 서빙하는 엔진의 토크나이저로 텍스트를 토큰 ID로 바꾸거나, 토큰 ID를 다시 텍스트로 바꿉니다. 라우터는 이 경로를 위한 자체 토크나이저를 갖지 않으며 요청한 모델을 정확히 서빙하는 백엔드 하나로 요청을 전달합니다.

POST /tokenize
POST /v1/tokenize
POST /detokenize
POST /v1/detokenize

네 경로는 요청을 받을 때 서로 바꿔 쓸 수 있습니다. 어느 엔진이 모델을 서빙하든 두 표기 모두 같은 핸들러에 도달합니다. 라우터가 엔진에 호출하는 경로는 클라이언트가 사용한 경로가 아니라 백엔드 타입으로 정해집니다(엔진별 지원 참고). 네 경로 모두 read 스코프가 필요합니다.

토큰화 요청

본문은 vLLM과 SGLang의 형식입니다. llama.cpp와 mlxcel의 네이티브 본문에는 model이 없으므로 라우터가 변환해서 보냅니다.

{
  "model": "Qwen/Qwen3-32B",
  "prompt": "Hello, world!",
  "add_special_tokens": true,
  "return_token_strs": true
}
매개변수 타입 필수 설명
model string 예 토크나이저를 사용할 모델. 없거나 비어 있으면 400 missing_model
prompt string prompt / messages 중 하나 토큰화할 원문 텍스트. 프롬프트 목록은 400 unsupported_prompt_type
messages array prompt / messages 중 하나 엔진의 채팅 템플릿으로 렌더링한 뒤 토큰화할 대화
add_special_tokens boolean 아니오 토크나이저의 특수 토큰(BOS 등)을 추가할지 여부. 기본값은 아래 참고
return_token_strs boolean 아니오 토큰별 문자열을 token_strs로 함께 반환 (기본값 false). SGLang은 이 필드를 무시하고 token_strs를 반환하지 않으므로 sglang 백엔드에서는 400 tokenize_option_unsupported로 거부
add_generation_prompt, continue_final_message, chat_template, chat_template_kwargs, tools 아니오 messages용 채팅 템플릿 옵션. vllm과 continuumrouter에는 그대로 전달하며 llamacpp, mlxcel, sglang은 엔진별 지원 아래의 옵션 표 참고

prompt와 messages 중 정확히 하나만 있어야 합니다. 둘 다 있으면 400 conflicting_input, 둘 다 없으면 400 missing_input입니다.

add_special_tokens 기본값: prompt 요청에서는 모든 엔진에서 실효 기본값이 true입니다. vLLM과 SGLang은 자체 기본값이 true이고 llama.cpp와 mlxcel은 네이티브 add_special의 기본값이 false이므로 라우터가 항상 명시적으로 보내며 요청이 달리 지정하지 않으면 true를 보냅니다. messages 요청에서는 기본값이 false입니다(vLLM은 채팅 템플릿이 이미 특수 토큰을 넣으므로 false, llama.cpp와 mlxcel은 요청이 add_special_tokens: true를 지정하지 않는 한 add_special: false를 받으며 SGLang은 렌더링 전에 add_special_tokens를 제거하므로 SGLang에서 messages에 add_special_tokens: true를 보내면 거부됩니다).

토큰화 응답

{
  "count": 4,
  "tokens": [9707, 11, 1879, 0],
  "max_model_len": 32768,
  "token_strs": ["Hello", ",", " world", "!"]
}
필드 타입 설명
count integer 토큰 ID 개수
tokens array 토큰 ID
max_model_len integer 모델의 컨텍스트 길이. 엔진이 알려 줄 때만 포함 (vLLM, SGLang)
token_strs array 토큰별 문자열. return_token_strs가 true이고 엔진이 반환했을 때만 포함. vLLM에서는 Hugging Face 토크나이저의 토큰 문자열입니다(Qwen3 같은 바이트 수준 BPE 토크나이저는 "Ġworld" 같은 GPT-2 바이트 수준 조각). llama.cpp와 mlxcel에서는 손실이 있습니다. 단독으로 유효한 UTF-8이 아닌 토큰은 토큰 하나씩 따로 디코딩하므로, 여러 토큰에 걸쳐 나뉜 멀티바이트 문자(한글 음절 하나가 1바이트 토큰 세 개가 될 수 있음)는 토큰마다 U+FFFD 하나가 되고 문자열을 이어 붙여도 복원되지 않습니다. 정확한 텍스트는 ID를 /detokenize에 보내 얻으세요

역토큰화 요청과 응답

{
  "model": "Qwen/Qwen3-32B",
  "tokens": [9707, 11, 1879, 0],
  "skip_special_tokens": true
}
매개변수 타입 필수 설명
model string 예 토크나이저를 사용할 모델
tokens array 예 음이 아닌 정수의 평면 배열, 최대 1,048,576개. 중첩 배열(SGLang 배치 형식)은 400 invalid_tokens_type, 개수 초과는 백엔드에 접속하기 전에 400 too_many_tokens
skip_special_tokens boolean 아니오 vLLM과 SGLang에 전달(SGLang은 이를 반영하며 기본값은 true이고 vLLM은 DetokenizeRequest에 skip_special_tokens 필드가 없어 항상 특수 토큰을 렌더링함). llama.cpp와 mlxcel은 항상 특수 토큰을 렌더링하므로 오류 없이 무시
{
  "prompt": "Hello, world!",
  "text": "Hello, world!"
}

두 키는 항상 같은 문자열을 담습니다. 따라서 vLLM 기준으로 작성한 클라이언트(prompt)와 SGLang 기준으로 작성한 클라이언트(text) 모두 어느 엔진이 응답했든 그대로 읽을 수 있습니다.

엔진별 지원

백엔드 타입 토큰화 역토큰화 라우터가 보내는 본문
vllm POST /tokenize POST /detokenize model을 서빙 ID로 바꾼 클라이언트 본문
sglang POST /v1/tokenize POST /v1/detokenize SGLang이 무시하거나 다르게 렌더링할 옵션을 거부한 뒤 model을 서빙 ID로 바꾼 클라이언트 본문 (아래 참고)
llamacpp POST /tokenize POST /detokenize {"content", "add_special", "with_pieces"}와 {"tokens"}. messages 요청은 먼저 POST /apply-template로 렌더링한 뒤 렌더링된 프롬프트를 토큰화 (아래 참고)
mlxcel POST /tokenize POST /detokenize llamacpp와 동일하나 messages 옵션이 더 적음
continuumrouter POST /tokenize POST /detokenize model을 서빙 ID로 바꾼 클라이언트 본문. 하위 라우터도 같은 형식으로 답함. 하위 라우터의 404(이 경로가 없는 이전 버전)나 501은 501 tokenization_not_supported로 답함. 그 밖의 하위 4xx 가운데 이 라우터의 오류 객체(문자열 code와 type, 선택적 param)를 담은 응답은 상태 코드, 코드, 유형, param을 그대로 전달하고 메시지는 256바이트로 자름. 따라서 체인 배포도 직접 연결한 라우터와 같은 오류로 답함(예: 404 model_not_found, 또는 param에 옵션 이름을 담은 400 tokenize_option_unsupported). 401, 403, 407은 여전히 502로 답하고 그 밖의 본문은 tokenize_upstream_rejected로 답함. 하위 라우터가 자신의 엔진에 주는 시간보다 5초 긴 35초를 기다리므로, 이 쪽의 타임아웃보다 하위 라우터의 504가 먼저 도착함
tensorrt-llm 미지원 미지원 trtllm-serve는 내부용 /_internal/tokenize 경로만 등록하고 역토큰화 경로는 없음 (2026-10-02, main의 tensorrt_llm/serve/openai_server.py 기준)
ollama 미지원 미지원 Ollama API 문서에 토큰화/역토큰화 경로 없음 (2026-10-02 확인)
lmstudio 미지원 미지원 LM Studio REST API 문서에 토큰화/역토큰화 경로 없음 (2026-10-02 확인)
generic, openai, azure, anthropic, gemini, bedrock 미지원 미지원

엔진 경로는 backends[].url 뒤에 붙습니다. http://host:8000/v1처럼 /v1로 끝나는 기본 URL은 API 루트로 간주하여 끝의 /v1 하나를 먼저 제거합니다. 따라서 vllm, llamacpp, mlxcel은 /tokenize, /detokenize로, sglang은 /v1/tokenize, /v1/detokenize로 호출되며 /v1/v1/tokenize는 만들어지지 않습니다. 끝의 /v1 세그먼트만 제거하고(/openai 등 다른 접두사는 유지), /v1/messages/count_tokens도 같은 방식으로 엔진 URL을 만듭니다.

llamacpp나 mlxcel 백엔드로 가는 messages 요청은 같은 백엔드에 서킷 허용 판단 한 번으로 두 번 호출합니다. 먼저 model(서빙 ID), messages, 아래 옵션을 담아 POST /apply-template을 호출하고 렌더링된 프롬프트를 content로 담아 POST /tokenize를 호출합니다. 이때 add_special_tokens의 기본값은 vLLM의 채팅 토큰화 요청과 같이 false이므로, 이미 BOS를 내보내는 템플릿에 BOS가 한 번 더 붙지 않습니다. 두 호출은 30초 시간 예산을 공유하고 16 MiB 상한으로 응답을 읽으며 아래 서킷 규칙을 따릅니다. llamacpp, mlxcel, sglang에서 엔진이 무시하거나 다르게 해석할 옵션은 그 이름을 담은 400 tokenize_option_unsupported로 거부하므로 조용히 다른 응답을 받는 일은 없습니다. 모델을 서빙하는 다른 백엔드가 그 옵션을 처리할 수 있으면 요청은 그 백엔드로 갑니다.

옵션 llamacpp mlxcel sglang
return_token_strs: true (prompt 또는 messages) 반영 (with_pieces: true) 반영 (with_pieces: true) 거부. SGLang은 이 필드를 무시하고 token_strs를 반환하지 않음
chat_template_kwargs, tools 렌더링에 전달 렌더링에 전달 /v1/tokenize로 전달
messages의 add_special_tokens add_special로 /tokenize에 전달 (기본값 false) add_special로 /tokenize에 전달 (기본값 false) false(기본값)만. SGLang은 렌더링 전에 add_special_tokens를 제거함
add_generation_prompt 렌더링에 전달 true(기본값)만. mlxcel은 false를 무시하고 생성 프롬프트를 그대로 붙임 true(기본값)만. SGLang은 false를 무시하고 생성 프롬프트를 그대로 붙임
continue_final_message 렌더링에 전달. add_generation_prompt: false와 마지막 assistant 메시지가 필요 거부. mlxcel은 이 옵션을 무시함 거부. SGLang은 사용자 턴 뒤에서 이를 무시하고 마지막 assistant 턴을 vLLM의 렌더링과 다른 원시 접두사로 이어 씀
마지막 assistant 메시지 continue_final_message: true가 필요. vLLM은 새 턴을 여는 반면 llama.cpp는 이를 이어 쓸 접두사로 처리 거부. mlxcel은 스위치 없이도 이를 접두사로 이어 쓰며 그 접두사는 vLLM의 continue_final_message 렌더링과 다름(Qwen3에서는 템플릿이 그 턴에 내보내고 vLLM과 llama.cpp가 유지하는 빈 think 블록을 빠뜨림) 거부. continue_final_message가 없으면 SGLang은 마지막 assistant 메시지를 user 턴으로 다시 쓰고 continue_final_message: true이면 mlxcel과 같은 원시 접두사로 이어 씀
text가 아닌 콘텐츠 파트(이미지, 오디오, 비디오) 거부. 렌더링은 그 자리에 자리표시 문자열을 넣으며 그 문자열의 토큰화는 미디어의 토큰화가 아님 같은 이유로 거부 같은 이유로 거부 (멀티모달 모델에서는 SGLang이 미디어 URL도 가져옴)
chat_template과 그 밖의 옵션 거부. llama.cpp는 요청별 템플릿을 받지만 무시함 거부. mlxcel은 요청별 템플릿을 받지만 무시함 거부. SGLang은 요청별 템플릿을 받지만 무시함

실행 중인 엔진으로 검증한 내역은 다음과 같습니다:

  • vllm (2026-10-19, vllm serve 0.32.0 커밋 5df63a720, Qwen/Qwen3-0.6B; 픽스처: src/services/tokenize/fixtures/vllm_0_32_0.md):
    • /tokenize는 {"count", "max_model_len", "tokens", "token_strs"}로 답합니다(return_token_strs: true가 아니면 token_strs는 null이고, true이면 Hugging Face 토크나이저의 바이트 수준 BPE 토큰 문자열을 담음).
    • messages의 add_generation_prompt, continue_final_message, chat_template_kwargs, tools는 반영되며, 두 스위치가 모두 true이거나 신뢰하지 않는 요청별 chat_template은 400으로 답합니다.
    • /detokenize는 {"prompt": "..."}로 답하고 skip_special_tokens와 관계없이 항상 특수 토큰을 렌더링합니다. 어휘 범위를 벗어난 u32 ID([999999999], 단독이든 유효한 ID 사이든)는 Hugging Face tokenizers 디코더가 생략해 200으로 답하고, u32::MAX를 넘는 ID([5000000000])는 400으로 답합니다.
    • /v1 별칭은 없습니다(404). vLLM 소스(register_vllm_serve_api_routers)에서도 attach_tokenize_router는 조건 없이 호출되며, --enable-tokenizer-info-endpoint가 필요한 것은 GET /tokenizer_info뿐입니다.
  • sglang (2026-10-19, sglang.launch_server 0.5.22 커밋 baa9befa3, Qwen/Qwen3-0.6B; 픽스처: src/services/tokenize/fixtures/sglang_0_5_22.md):
    • /v1/tokenize는 {"tokens", "count", "max_model_len"}으로 답하고 prompt와 messages 모두에서 return_token_strs: true를 조용히 무시합니다.
    • messages에서는 chat_template_kwargs와 tools가 반영됩니다(기본, chat_template_kwargs, user_only의 토큰 ID는 vLLM, llama.cpp, mlxcel과 일치함). 반면 add_generation_prompt: false, add_special_tokens: true, chat_template, 사용자 턴 뒤의 continue_final_message: true는 무시됩니다.
    • continue_final_message: true가 붙은 마지막 assistant 메시지는 원시 접두사로 이어 쓰고(Qwen3에서 11토큰으로, vLLM의 15토큰 렌더링에 있는 4토큰 빈 think 블록이 빠짐), continue_final_message가 없는 마지막 assistant 메시지는 _handle_last_assistant_message가 user 턴으로 다시 씁니다(사용자 턴 두 개와 바이트 단위로 같은 16토큰).
    • /v1/detokenize는 {"text": "..."}로 답하며 skip_special_tokens(기본값 true)를 반영합니다. 어휘 범위를 벗어난 u32 ID는 vLLM과 같이 생략되어 200으로 답하고, u32::MAX를 넘는 ID([5000000000])는 500 InternalServerError로 답해 라우터가 502 tokenize_upstream_error로 매핑합니다.
  • llamacpp (2026-10-02 빌드 11344 커밋 ec7630a64, 및 /apply-template은 2026-10-06 빌드 11424 커밋 6c59c4007, Qwen3-0.6B):
    • /tokenize는 {"tokens": [...]}로, with_pieces를 주면 {"tokens": [{"id", "piece"}]}로 답합니다. add_special의 기본값은 false이고 /v1 별칭은 없습니다.
    • /detokenize는 {"content": "..."}로 답합니다(라우터는 prompt나 text 키에 담긴 역토큰화 텍스트도 받아들입니다).
    • /apply-template은 chat completions 본문을 받고 {"prompt": "..."}로 답합니다. add_generation_prompt, continue_final_message, chat_template_kwargs, tools는 반영되고 두 스위치가 모두 true이면 400으로 답하며, 요청별 chat_template은 무시되고 마지막 assistant 메시지는 스위치 없이도 접두사로 이어 씁니다.
  • mlxcel (2026-10-06, mlxcel-server 0.7.0 커밋 1e561f1e, mlx-community/Qwen3-0.6B-bf16):
    • /tokenize, /detokenize, /apply-template은 llama.cpp와 같은 형태로 응답하며, 기본·tools·chat_template_kwargs 렌더링과 렌더링된 프롬프트의 ID는 같은 모델의 llama.cpp 결과와 바이트 단위로 같습니다. 단독으로 유효한 UTF-8이 아닌 piece는 여러 바이트를 담을 수 있는 바이트 배열로 옵니다.
    • add_generation_prompt: false, continue_final_message, 요청별 chat_template은 무시되고, 마지막 assistant 메시지는 스위치 없이도 접두사로 이어 쓰입니다. /v1 별칭은 없습니다.
    • /detokenize에 어휘 범위를 벗어난 ID를 보내면 단독이든 유효한 ID 사이에 섞여 있든 llama.cpp와 같이 type: server_error인 500으로 답하므로 라우터는 502 tokenize_upstream_error로 답합니다(구버전 mlxcel 빌드는 200으로 답하고 그 ID를 텍스트에서 빼며, 라우터는 이를 실제 응답과 구분할 수 없어 정상 응답으로 돌려줍니다).
  • continuumrouter: 이 라우터 자신의 계약입니다. 이 경로를 추가한 릴리스 이전의 하위 라우터는 404로 답합니다.
  • 미지원 엔진 (tensorrt-llm, ollama, lmstudio): 표시한 날짜에 각 프로젝트의 소스나 API 문서를 읽어 확인한 결과이며, 라우터는 501로 답합니다.

디스패치 규칙

토큰 ID는 그것을 만든 바로 그 모델에서만 의미가 있으므로, 이 경로는 추론 경로와 다르게 동작합니다:

  • 단일 모델 선택: 요청한 모델을 정확히 서빙하고 엔진 토크나이저를 가진 백엔드 하나를 선택합니다. 메타데이터 별칭은 백엔드가 서빙하는 정식 ID로 디스패치합니다.
  • 재시도 및 폴백 없음: 요청은 한 번만 보냅니다. 재시도, fallback.fallback_chains 홉, 응답 캐시, 모델 실험 해석이 없습니다. hide_variant_models가 예약한 변형 모델은 404로 답합니다.
  • 백엔드 지정 및 출처 헤더: x-backend는 지원 백엔드 가운데에서 선택을 좁힙니다. x-served-backend는 백엔드가 만든 200 응답과 엔진이 반환한 응답으로 만든 오류(tokenize_upstream_rejected, tokenize_upstream_error, 그대로 전달한 하위 라우터 오류, 하위 라우터의 501)에 붙으며, 라우터가 만든 거부, 연결 실패, 타임아웃, 읽을 수 없는 응답에는 붙지 않습니다.
  • 접근 제어 및 미터링: 키의 allowed_models와 allowed_backends는 다른 경로와 똑같이 적용되고, internal이거나 비활성화된 백엔드는 선택되지 않습니다. 아무것도 생성하지 않으므로 사용량 레코드를 만들지 않고, 가드레일이 텍스트를 검사하지 않으며, 모델별 레이트 리밋을 차감하지 않습니다(전역, 클라이언트별, 키별 제한은 적용됨).
  • 서킷 브레이커 격리 (/anthropic/v1/messages/count_tokens의 엔진 tokenize 호출에도 동일하게 적용되며, 16 MiB 응답 상한과 30초 시간 예산을 공유함):
    • 열린 상태(Open): 다른 경로와 마찬가지로 요청을 거부합니다.
    • 닫힌 상태(Closed): 엔진이 돌려준 HTTP 응답(2xx, 4xx, 5xx)은 상태 코드와 관계없이 중립입니다. 토크나이저의 2xx는 생성 경로가 동작한다는 뜻이 아니므로 실패 횟수를 초기화하지 않으며, 엔진의 5xx는 클라이언트 입력이 원인일 수 있으므로 서킷을 열지 않습니다. 연결 실패와 타임아웃만 실패로 집계합니다.
    • 반열림 상태(Half-open): 프로브 슬롯 점유 상태와 관계없이 슬롯을 차지하지 않고 요청을 받아들이므로 생성 프로브를 늦추지 않으며, 응답은 프로브 진행으로 집계되지 않습니다. 서킷은 생성 요청이 닫거나 다시 열 때까지 half-open으로 남지만, 연결 실패나 타임아웃은 서킷을 다시 엽니다.

상태 코드

상태 코드 의미
200 라우터 형식으로 정리한 엔진의 응답
400 missing_model, missing_input, conflicting_input, unsupported_prompt_type, invalid_prompt_type, invalid_messages_type, invalid_parameter_type, missing_tokens, invalid_tokens_type, too_many_tokens 본문이 문서화된 형식이 아님. 업스트림으로 아무것도 보내지 않음
400 tokenize_option_unsupported 모델을 서빙하는 지원 백엔드 중 어느 것도 처리할 수 없는 옵션(SGLang의 return_token_strs: true 또는 위 표의 messages 옵션)을 /tokenize에 보냄. param이 그 옵션을 가리킴. continuumrouter 백엔드가 하위 라우터의 거부를 그대로 전달한 경우를 제외하면 업스트림으로 아무것도 보내지 않음
403 키의 allowed_models 또는 allowed_backends가 모델을 제외
404 모델을 서빙하는 노출된 백엔드가 없음, 또는 (model_not_found) continuumrouter 백엔드의 하위 라우터가 그 모델을 모름
4xx tokenize_upstream_rejected 엔진이 요청을 거부. 401, 403, 407(다음 행)을 제외하고 엔진의 상태 코드를 유지하며 본문은 로그에만 남기고 반환하지 않음. 엔진의 429는 엔진이 보낸 Retry-After를 유지. 예외로, continuumrouter 백엔드의 하위 라우터가 라우터 오류 객체를 담아 답한 4xx는 그 코드, 유형, param, 길이를 제한한 메시지를 반환
429, 503 지원 백엔드가 모두 백엔드별 제한에 걸렸거나 사용할 수 없음
501 tokenization_not_supported 모델을 서빙하는 백엔드는 있으나 엔진 토크나이저를 가진 것이 없음, 또는 continuumrouter 백엔드의 하위 라우터가 토큰화할 수 없음 (그 라우터의 501, 또는 이 경로가 없는 라우터의 404)
502 tokenize_upstream_error 엔진이 5xx로 응답. /detokenize에서는 모델의 어휘 범위나 정수 범위를 벗어난 토큰 ID가 흔한 원인입니다. llama.cpp와 mlxcel(1e561f1e 기준 0.7.0 이상)은 어휘 범위를 벗어난 ID에 500으로 답하고 SGLang은 u32::MAX를 넘는 ID에 500으로 답하며(vLLM은 이에 400으로 답하고 어휘 범위를 벗어난 u32 ID에는 vLLM과 SGLang 모두 구버전 mlxcel 빌드처럼 200으로 답하며 그 ID를 텍스트에서 뺌) 라우터는 어휘 크기를 알지 못하므로 이를 엔진 장애와 구분할 수 없습니다. vllm이나 sglang 백엔드로 가는 /tokenize에서는 클라이언트의 chat_template, chat_template_kwargs, tools, messages가 엔진에 그대로 전달되므로 예외를 일으키는 템플릿도 같은 결과를 낼 수 있습니다. 클라이언트 입력이 원인일 수 있으므로 두 경로 모두 엔진의 5xx는 백엔드의 서킷 브레이커에 실패로 집계하지 않으며 read 키로 정상 백엔드를 로테이션에서 뺄 수 없습니다 (서킷 규칙은 위 디스패치 규칙 참고)
502 tokenize_upstream_error 엔진이 401, 403, 407로 응답. 클라이언트의 키가 아니라 라우터 자신의 백엔드 자격 증명(backends[].api_key)을 거부한 것이므로 상태 코드를 그대로 전달하지 않음
502 tokenize_invalid_response 엔진이 2xx로 응답했으나 본문이 문서화된 형식이 아님
502 tokenize_response_too_large 엔진의 응답이 16 MiB를 초과
502 tokenize_connection_error 백엔드에 연결할 수 없음
504 tokenize_timeout 엔진이 30초 안에 응답하지 않음

예제

curl -X POST http://localhost:8080/tokenize \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $API_KEY" \
  -d '{"model": "Qwen/Qwen3-32B", "prompt": "Hello, world!"}'

curl -X POST http://localhost:8080/detokenize \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $API_KEY" \
  -d '{"model": "Qwen/Qwen3-32B", "tokens": [9707, 11, 1879, 0]}'

결정 (System One)

증거 한 건에 대해 타입이 지정된 질문을 던지고 확률이 붙은 타입 응답을 한 번의 호출로 받습니다. TypeSafe의 호스팅 Jev와 자체 호스팅 Laya(laya-serve), Kev, Nimble 서버가 사용하는 System One 결정 프로토콜을 그대로 따르므로, 이 서버들을 대상으로 작성한 클라이언트를 수정 없이 라우터에 연결할 수 있으며 라우터의 API 키, 스코프, 속도 제한, 예산, 폴백 체인이 그대로 적용됩니다.

POST /v1/decisions
POST /v1/systemone

/v1/systemone은 동작이 완전히 같은 별칭으로, 프로토콜 고유 경로를 고정해 둔 클라이언트를 위한 것입니다.

요청의 model이 응답 방식을 결정합니다:

  • 패스스루: decisions.targets[]에 등록된 이름이면 해당 System One 서버(인라인 endpoint, 또는 자체 호스팅 backends[] 항목을 가리키는 backend: 참조)로 보내고 usage를 제외한 응답을 그대로 돌려줍니다.
  • 에뮬레이션: 그 외 이름은 채팅 모델로 취급합니다. 질문 맵을 강제 호출되는 함수 도구 emit_decision 하나로 다시 구성하고(noul은 boolean, choice는 옵션 id enum, score는 정수 레벨 범위), 라우터의 일반 채팅 디스패치로 temperature: 0, 비스트리밍 요청을 보낸 뒤 도구 인자를 응답 형식으로 되돌립니다. 이름은 /v1/chat/completions와 같은 방식으로 해석합니다. 메타데이터 별칭은 백엔드가 서빙하는 정식 id로, 모델 실험의 클라이언트용 이름은 배정된 변형으로 디스패치합니다. 스마트 라우팅 가상 모델(예: auto)은 이 엔드포인트가 요청을 분류하지 않으므로 400 smart_routing_not_supported로 거부합니다.

요청 본문:

{
  "model": "jev",
  "state": "고객: 두 번 결제되었으니 두 번째 결제를 환불해 주세요.",
  "questions": {
    "refund": {"type": "noul", "instructions": "환불 요청입니까?"},
    "team": {
      "type": "choice",
      "instructions": "어느 팀이 처리해야 합니까?",
      "criteria": {"billing": "결제 및 청구", "tech": "기술 지원"}
    },
    "urgency": {
      "type": "score",
      "instructions": "얼마나 긴급합니까?",
      "criteria": ["낮음", "보통", "높음"]
    }
  }
}

요청 파라미터:

필드 타입 필수 설명
model string 예 decisions.targets[].name, 또는 에뮬레이션할 채팅 모델 이름.
state string, object, array 또는 null 예 질문의 근거가 되는 증거. 문자열이면 비어 있으면 안 되며 null은 protocol: llama_cpp 타깃의 lfm2_d1, lfm2_d1_omni, vllm_d3 계열에서만 허용됩니다.
questions object 예 질문 id에서 질문으로의 맵. 타깃 기본 한도는 64개이며 1–256으로 설정할 수 있습니다(max_questions).
questions.*.type string 예 noul(문장이 참일 확률), choice(라벨이 붙은 집합 중 하나), score(순서가 있는 척도 위의 위치), 또는 box(정규화된 경계 상자 {top, bottom, left, right}, protocol: typesafe·typesafe_images: true·jev-1.14.0 / jev-preview 필요).
questions.*.instructions string, object, array 또는 null 프로필별 판단할 내용, 최대 8 KiB. 명시적 프로필은 생략/null/구조화 필드를 보존하며 generic은 필수 텍스트를 유지합니다.
questions.*.criteria object 또는 array choice, score에서 필수 choice: 옵션 id에서 설명으로의 맵, 1개에서 255개(openjev는 1개에서 52개). score: 낮은 것부터 나열한 레벨 설명, 2개에서 10개(typesafe는 1개에서 10개). noul: 선택적 {"true": ..., "false": ...}.
assets object 아니오 state, instructions, criteria에서 참조하는 이름 붙은 이미지 에셋({"type": "image", "format": "png", "data_base64": "..."}). protocol: typesafe 및 typesafe_images: true 필요.
images / files array of strings 아니오 protocol: llama_cpp 타깃용 인라인 이미지·오디오 data: URL 목록(max_media_bytes 상한 적용, 기본 4 MiB, 최대 16 MiB).

프로바이더 프로필, TypeSafe의 실험적 assets/Box, llama.cpp 미디어는 결정 모델에 설명되어 있습니다. 두 라우트의 JSON 본문 상한은 2 MiB입니다. multipart는 명시적으로 415로 거부하며 압축 입력은 해제하지 않고 알 수 없는 필드는 거부하며 호환되지 않는 폴백 홉은 미디어를 버릴 수 없습니다. null state에는 명시적으로 지원되는 llama.cpp 계열이 필요합니다.

본문은 어떤 백엔드 호출보다 먼저 검증합니다. model이 없거나 비어 있거나 앞뒤에 공백이 있으면 400 invalid_model로 응답합니다. 모델별 속도 제한이 본문의 문자열 그대로를 기준으로 차감하므로, 디스패치하는 이름도 그 문자열과 같아야 해서 이름을 다듬지 않습니다. 프로토콜 위반(예: 옵션이 256개인 choice)은 code가 invalid_questions 또는 invalid_state인 422로 응답합니다.

응답 (패스스루):

{
  "model": "jev-1.13.0",
  "answers": {
    "refund": {"type": "noul", "noul": 0.97},
    "team": {
      "type": "choice",
      "choice": "billing",
      "probabilities": {"billing": 0.94, "tech": 0.06},
      "confidence": 0.88
    },
    "urgency": {
      "type": "score",
      "score": 1.4,
      "probabilities": {"0": 0.1, "1": 0.4, "2": 0.5},
      "confidence": 0.25
    }
  },
  "usage": {"input_tokens": 62, "output_tokens": 0}
}

model은 서버가 확정한 id입니다(jev-latest 같은 이동 별칭은 실제로 응답한 고정 버전으로 보고됩니다). usage.input_tokens와 usage.output_tokens는 모두 서버가 보고한 값을 보존합니다. TypeSafe의 출력 가격이 0이더라도 출력 토큰 수는 0이 아닐 수 있으며 라우터는 사용량을 지우지 않습니다. 선택적인 confidence가 없으면 없는 상태를 유지합니다.

응답 (에뮬레이션):

{
  "model": "gpt-4o-mini-2024-07-18",
  "answers": {
    "refund": {"type": "noul", "noul": 1.0},
    "team": {"type": "choice", "choice": "billing", "probabilities": {"billing": 1.0, "tech": 0.0}},
    "urgency": {"type": "score", "score": 2.0, "probabilities": {"0": 0.0, "1": 0.0, "2": 1.0}}
  },
  "usage": {"input_tokens": 180, "output_tokens": 22},
  "emulated": true
}

채팅 모델은 분포가 아니라 답 하나를 돌려주므로, 에뮬레이션 응답은 one-hot probabilities를 담고 confidence가 없으며 emulated: true로 표시됩니다. 에뮬레이션의 1.0을 보정된 확신도로 해석하면 안 됩니다. usage는 채팅 모델 자체의 토큰 사용량입니다.

폴백: 타깃 이름은 fallback.fallback_chains에서 일반 모델 이름과 같이 취급되므로, 실패한 실제 타깃에서 다른 타깃을 거쳐 엔드포인트가 에뮬레이션하는 채팅 모델로 끝나는 체인을 만들 수 있습니다(예: jev: [laya, gpt-4o-mini]). 체인은 설정된 트리거 조건에서 다음으로 넘어갑니다: 429, 529(백엔드 비정상으로 보고), 5xx, 타임아웃, 연결 실패. 타깃이 돌려준 422는 다른 곳에서 재시도하지 않습니다. 체인 홉이 응답한 경우 X-Fallback-Used, X-Original-Model, X-Fallback-Model, X-Fallback-Reason, X-Fallback-Attempts 헤더가 붙습니다.

접근 제어와 사용량 기록:

  • 스코프 및 허용 목록: 차단(blocking) 모드에서는 write 스코프가 필요하므로, 읽기 전용 키는 403으로 거부됩니다. 키별 allowed_models 목록은 폴백보다 먼저 요청한 model(타깃 이름 포함)을 검사하며, 키별·모델별 속도 제한과 컨트롤 플레인 예산이 적용됩니다.
  • 백엔드 격리 (allowed_backends): allowed_backends 목록이 비어 있지 않은 키는 목록에 해당 백엔드가 있을 때만 backend: 타깃에 도달할 수 있고, 인라인 엔드포인트 타깃(예: 호스팅 Jev)에는 절대 도달할 수 없습니다. 지정된 백엔드로 제한된 키의 state가 설정된 백엔드 밖으로 나가면 안 되기 때문입니다. 이런 타깃을 직접 요청하면 403으로 응답하고, 폴백 체인의 홉으로 만나면 아무것도 보내지 않고 그 홉을 건너뛴 뒤 체인을 계속합니다.
  • 사용량 귀속: 사용량은 실제로 응답한 모델 이름(결정 서버가 확정한 ID인 jev-1.13.0, 또는 에뮬레이션에 응답한 채팅 모델)으로 호출자 키에 기록됩니다.

가드레일과 요청 파라미터:

  • 에뮬레이션 홉: 채팅 완료 요청으로 실행되므로 /v1/chat/completions의 디스패치 전 게이트를 해당 홉의 모델 기준으로 똑같이 거칩니다:
    • 로컬과 허브를 합성한 request_params 정책(max_tokens 제한은 decisions.emulation.max_tokens를 잘라내고, 거부는 400 invalid_request_parameter 반환). 단, x-continuum-request-params 응답 헤더는 내보내지 않습니다.
    • 입력 가드레일 사전 검사(enforce 모드의 차단은 x-guardrail-* 헤더와 함께 400 반환, 변환은 프롬프트를 다시 씀).
    • 크기 제한: 에뮬레이션 프롬프트는 state를 decisions.emulation.max_state_bytes로, 직렬화한 질문 집합을 고정 64 KiB로 제한합니다(422 questions_too_large).
  • 패스스루 타깃: 채팅 완료가 아니므로 콘텐츠 가드레일을 실행하지 않습니다. state는 보낸 그대로 전달되고 타입이 지정된 응답이 돌아옵니다. Jev 같은 호스팅 타깃으로 보내는 state는 배포 환경 밖으로 나가므로, 개인정보에 민감한 트래픽은 자체 호스팅 타깃으로 보내세요.

상태 코드:

  • 200: 응답 완료(패스스루 또는 에뮬레이션)
  • 401: API 키 없음 또는 잘못됨(차단 모드)
  • 400: model이 없거나 비어 있거나 앞뒤에 공백이 있음(invalid_model), 스마트 라우팅 가상 모델을 지정함(smart_routing_not_supported), 요청 파라미터 정책이 에뮬레이션 홉을 거부함(invalid_request_parameter), 또는 입력 가드레일이 차단함
  • 403: 키에 write 스코프가 없거나, 허용 목록이 모델이나 타깃의 백엔드를 제외하거나, (allowed_backends 목록이 있을 때) 인라인 엔드포인트 타깃을 요청함
  • 404: 엔드포인트가 비활성화됨(decisions_disabled), 또는 이름이 타깃도 아니고 에뮬레이션할 수 있는 채팅 모델도 아님
  • 415: 지원하지 않는 미디어 또는 콘텐츠 타입(예: multipart/form-data)
  • 422: 요청 검증 실패, 타깃이 요청을 거부함(decision_validation_error), 또는 state나 질문 집합이 에뮬레이션 상한을 넘음(state_too_large, questions_too_large)
  • 429: 속도 제한 초과, 또는 타깃이 요청을 속도 제한함(retry-after 전달)
  • 502: 타깃이 실패했거나 잘못된 응답을 돌려줌, 또는 에뮬레이션 채팅 응답에 쓸 수 있는 emit_decision 호출이 없음(decision_emulation_failed)
  • 503: 타깃을 사용할 수 없음(decision_target_unavailable)
  • 504: 타깃이 timeout_ms 안에 응답하지 않음
  • 529: 타깃이 과부하를 보고함(retry-after 전달)

요청 예시:

curl -X POST http://localhost:8080/v1/decisions \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer your-api-key" \
  -d '{
    "model": "jev",
    "state": "메모리 한도를 낮춘 뒤 배포가 종료 코드 137로 실패했습니다.",
    "questions": {
      "oom": {"type": "noul", "instructions": "프로세스가 메모리 부족으로 종료되었습니까?"}
    }
  }'

타깃과 에뮬레이션은 decisions 섹션에서 설정합니다(설정 레퍼런스 참고).


희소 임베딩

TEI/Jina 호환 Sparse Embedding API를 사용하여 입력 텍스트에 대한 희소 임베딩을 생성합니다. 희소 임베딩(예: SPLADE)은 명시적인 용어 가중치를 통해 어휘 정보를 보존하여 하이브리드 검색에서 밀집 임베딩을 보완합니다.

POST /embed_sparse

요청 본문:

{
  "model": "naver/splade-v3",
  "input": "희소 임베딩이란 무엇인가요?"
}

요청 파라미터:

필드 타입 필수 설명
model string 예 사용할 희소 임베딩 모델의 ID (예: naver/splade-v3, naver/splade-cocondenser-ensembledistil)
input string 또는 array 예 임베딩할 입력 텍스트. 단일 문자열 또는 문자열 배열이 될 수 있음

입력 형식 예시:

단일 문자열:

{
  "model": "naver/splade-v3",
  "input": "희소 임베딩이란 무엇인가요?"
}

문자열 배열:

{
  "model": "naver/splade-v3",
  "input": [
    "임베딩할 첫 번째 텍스트",
    "임베딩할 두 번째 텍스트",
    "임베딩할 세 번째 텍스트"
  ]
}

응답:

{
  "data": [
    {
      "index": 0,
      "sparse_embedding": {
        "indices": [123, 456, 789, 1024, 2048],
        "values": [0.5, 0.3, 0.1, 0.8, 0.2]
      }
    }
  ],
  "model": "naver/splade-v3",
  "usage": {
    "prompt_tokens": 8,
    "total_tokens": 8
  }
}

응답 필드:

필드 타입 설명
data array 희소 임베딩 객체 배열
data[].index integer 입력 배열에서 임베딩의 인덱스
data[].sparse_embedding object 희소 임베딩 벡터
data[].sparse_embedding.indices array 어휘에서 0이 아닌 요소의 인덱스
data[].sparse_embedding.values array 해당 인덱스의 값
model string 임베딩 생성에 사용된 모델 (선택적)
usage object 토큰 사용 정보 (선택적)

희소 벡터 이해:

희소 벡터는 어휘 인덱스와 함께 0이 아닌 값만 저장합니다. 예를 들어:

  • indices: [123, 456, 789] - 어휘에서의 위치
  • values: [0.5, 0.3, 0.1] - 해당 용어의 가중치

이는 0이 아닌 요소가 적은 고차원 벡터에 대해 메모리 효율적입니다 (일반적으로 30,000개 이상의 어휘 크기 중 100-500개의 0이 아닌 값).

지원 백엔드:

백엔드 엔드포인트 참고
vLLM /embed_sparse OpenAI 호환 서버를 통한 SPLADE 모델 지원
Hugging Face TEI /embed_sparse --pooling splade 플래그 필요
Jina AI 네이티브 네이티브 희소 임베딩 지원

상태 코드:

  • 200: 희소 임베딩이 성공적으로 생성됨
  • 400: 유효하지 않은 요청 (model/input 누락, 빈 입력)
  • 401: 유효하지 않은 API 키
  • 404: 모델을 찾을 수 없거나 희소 임베딩을 지원하지 않음
  • 429: 속도 제한 초과
  • 500: 백엔드 오류
  • 503: 백엔드 사용 불가

사용 사례:

  • 하이브리드 검색: 더 나은 결과를 위해 밀집(의미) 및 희소(어휘) 검색 결합
  • 키워드 매칭: 학습된 가중치를 통한 정확한 용어 매칭
  • 도메인별 검색: 전문 용어 및 희귀 단어의 더 나은 처리
  • 교차 언어 검색: 일부 SPLADE 모델은 다국어 희소 검색 지원

요청 예제:

curl -X POST http://localhost:8080/embed_sparse \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer your-api-key" \
  -d '{
    "model": "naver/splade-v3",
    "input": "검색을 위한 희소 임베딩의 장점은 무엇인가요?"
  }'

여러 입력 예제:

curl -X POST http://localhost:8080/embed_sparse \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer your-api-key" \
  -d '{
    "model": "naver/splade-v3",
    "input": [
      "첫 번째 쿼리 텍스트",
      "두 번째 쿼리 텍스트"
    ]
  }'

Files API

Files API를 사용하면 채팅 완료에서 파일을 업로드, 관리 및 사용할 수 있습니다. 업로드된 파일은 image_file 콘텐츠 타입을 사용하여 메시지에서 참조할 수 있으며 라우터가 파일 콘텐츠를 주입하여 이러한 참조를 자동으로 해결합니다.

파일 업로드

채팅 완료에서 사용할 파일을 업로드합니다.

POST /v1/files
Content-Type: multipart/form-data

폼 필드:

필드 타입 필수 설명
file file 예 업로드할 파일
purpose string 예 파일의 용도: vision, assistants, fine-tune, batch, user_data, evals

예제:

curl -X POST http://localhost:8080/v1/files \
  -F "file=@image.png" \
  -F "purpose=vision"

응답:

{
  "id": "file-abc123def456",
  "object": "file",
  "bytes": 12345,
  "created_at": 1699061776,
  "filename": "image.png",
  "purpose": "vision"
}

상태 코드:

  • 200: 파일이 성공적으로 업로드됨
  • 400: 유효하지 않은 요청 (파일 누락, 유효하지 않은 용도)
  • 413: 파일이 너무 큼 (설정된 max_file_size 초과)

파일 목록

업로드된 파일 목록을 조회합니다.

GET /v1/files
GET /v1/files?purpose=vision

쿼리 파라미터:

파라미터 타입 필수 설명
purpose string 아니오 용도별 필터링
limit integer 아니오 한 페이지에 담을 최대 파일 수, 1부터 10000 (기본값 10000). 범위를 벗어난 값은 범위 안으로 조정됨
order string 아니오 desc(기본값, 최신순) 또는 asc. created_at 다음 파일 ID 순으로 정렬
after string 아니오 이전 페이지의 마지막 파일 ID. 목록은 그 파일 다음부터 이어짐

응답:

{
  "object": "list",
  "data": [
    {
      "id": "file-abc123def456",
      "object": "file",
      "bytes": 12345,
      "created_at": 1699061776,
      "filename": "image.png",
      "purpose": "vision"
    }
  ],
  "has_more": false
}

files.auth.enforce_ownership가 켜져 있으면 관리자가 아닌 호출자는 자기 파일만 조회합니다. 소유자 필터는 limit보다 먼저 적용되므로, 다른 소유자가 더 최근에 올린 파일이 몇 개든 한 페이지에 호출자의 파일이 최대 limit개까지 담깁니다. 페이지 뒤에 파일이 더 있으면 has_more가 true이고 반환된 마지막 id를 after에 넣어 다음 페이지를 요청합니다. 호출자의 목록에 없는 after ID(삭제된 파일, 다른 소유자의 파일, 존재하지 않는 ID)를 보내면 빈 페이지와 has_more: false가 반환됩니다. GET /admin/files도 전체 소유자를 대상으로 같은 limit, order, after 파라미터를 받습니다.


파일 메타데이터 가져오기

특정 파일의 메타데이터를 조회합니다.

GET /v1/files/{file_id}

응답:

{
  "id": "file-abc123def456",
  "object": "file",
  "bytes": 12345,
  "created_at": 1699061776,
  "filename": "image.png",
  "purpose": "vision"
}

상태 코드:

  • 200: 파일 메타데이터 조회됨
  • 404: 파일을 찾을 수 없음

파일 콘텐츠 다운로드

업로드된 파일의 콘텐츠를 다운로드합니다.

GET /v1/files/{file_id}/content

응답: 적절한 Content-Type 헤더와 함께 바이너리 파일 콘텐츠.

상태 코드:

  • 200: 파일 콘텐츠 반환됨
  • 404: 파일을 찾을 수 없음

파일 삭제

업로드된 파일을 삭제합니다.

DELETE /v1/files/{file_id}

응답:

{
  "id": "file-abc123def456",
  "object": "file",
  "deleted": true
}

상태 코드:

  • 200: 파일이 성공적으로 삭제됨
  • 404: 파일을 찾을 수 없음

작업 유형별 속도 제한

files.rate_limiting.enabled가 true이면 라우터는 세 가지 Files API 작업 범주에 대해 호출자별 분당 요청 토큰 버킷 제한을 각각 적용합니다.

범주 엔드포인트 설정 필드 기본값
files_upload POST /v1/files files.rate_limiting.uploads_per_minute 10
files_download GET /v1/files/{file_id}/content files.rate_limiting.downloads_per_minute 100
files_operations GET /v1/files, GET /v1/files/{file_id}, HEAD /v1/files/{file_id}, DELETE /v1/files/{file_id} files.rate_limiting.operations_per_minute 1000

버킷은 제시된 API 키(Authorization: Bearer 또는 x-api-key)의 SHA-256 해시, 인증된 사용자 ID, 또는 클라이언트 IP를 기준으로 호출자마다 분리됩니다. 모든 Files API 응답에는 x-ratelimit-limit, x-ratelimit-remaining, x-ratelimit-reset과 각 -requests 변형 헤더(x-ratelimit-limit-requests, x-ratelimit-remaining-requests, x-ratelimit-reset-requests)가 포함됩니다. 호출자가 범주 한도를 초과하면 라우터는 429 Too Many Requests, Retry-After 헤더, 그리고 초과된 범주 이름(files_upload, files_download, files_operations)을 error.details.limit_type에 담은 OpenAI 오류 봉투를 반환합니다.


오류 응답 본문

Files API의 모든 오류는 OpenAI 형식을 따릅니다. message, type, code를 담은 error 객체입니다.

클라이언트 요청이 원인인 오류는 무엇이 잘못됐는지 알려주며 호출자가 보낸 file_id도 그대로 포함합니다.

{
  "error": {
    "message": "file not found: file-abc123def456",
    "type": "invalid_request_error",
    "code": "file_not_found"
  }
}

서버 쪽 오류는 라우터 내부 진단 문구 대신 고정된 메시지를 반환합니다. type과 code는 원인을 그대로 구분해주므로, 이 두 필드로 분기하는 클라이언트는 영향을 받지 않습니다.

{
  "error": {
    "message": "storage error: the file operation could not be completed",
    "type": "server_error",
    "code": "storage_error"
  }
}

전체 진단 정보(files.storage_path 아래의 절대 경로, 업로드 임시 파일 이름, 하부 운영체제 오류)는 라우터 로그에 ERROR 수준으로 기록되며 응답 본문에는 절대 실리지 않습니다. 설정 스위치로 노출하지 않는 것은 의도된 선택입니다. 스토리지 장애를 조사하는 운영자는 로그를 보면 되고 호출자에게는 라우터의 파일시스템 구조가 쓸모없기 때문입니다. /admin/files* 엔드포인트, 그리고 /v1/chat/completions와 /anthropic/v1/messages에서 드러나는 file_id 해결 실패에도 같은 규칙이 적용됩니다.

code 상태 메시지
storage_error 500 고정: storage error: the file operation could not be completed
io_error 500 고정: io error: the file operation could not be completed
service_unavailable 503 고정: files api is disabled
그 외 4xx 요청 내용을 설명하며 file_id를 포함

채팅 완료에서의 파일 해결

라우터는 채팅 완료 요청에서 파일 참조를 자동으로 해결합니다. 메시지에 image_file 콘텐츠 블록이 포함되면 라우터는:

  1. 파일 ID 형식을 검증합니다
  2. 스토리지에서 파일 콘텐츠를 로드합니다
  3. 파일을 base64 data URL로 변환합니다
  4. image_file 블록을 image_url 블록으로 대체합니다

지원되는 파일 타입

채팅 완료에서의 파일 해결은 다음 파일 타입을 지원합니다:

파일 타입 MIME 타입 지원
PNG image/png 모든 백엔드
JPEG image/jpeg 모든 백엔드
GIF image/gif 모든 백엔드
WebP image/webp 모든 백엔드
PDF application/pdf OpenAI, Anthropic
Plain Text text/plain Anthropic

참고: PDF 및 일반 텍스트 지원은 Anthropic 백엔드(및 OpenAI의 PDF)에서 사용할 수 있습니다. 파일 변환기는 각 백엔드에 적합한 형식으로 문서 파일을 자동으로 변환합니다 (OpenAI는 PDF에 file 블록을 사용하고 Anthropic은 PDF와 일반 텍스트 모두에 document 블록을 사용합니다). 이미지가 아닌 파일과 문서가 아닌 파일은 지원되는 파일 타입을 안내하는 유용한 메시지와 함께 400 Bad Request 오류를 반환합니다.

파일 참조가 포함된 요청:

{
  "model": "gpt-5.6-sol",
  "messages": [
    {
      "role": "user",
      "content": [
        {"type": "text", "text": "What's in this image?"},
        {"type": "image_file", "image_file": {"file_id": "file-abc123def456"}}
      ]
    }
  ]
}

변환된 요청 (백엔드로 전송됨):

{
  "model": "gpt-5.6-sol",
  "messages": [
    {
      "role": "user",
      "content": [
        {"type": "text", "text": "What's in this image?"},
        {"type": "image_url", "image_url": {"url": "data:image/png;base64,..."}}
      ]
    }
  ]
}

파일 해결 오류:

오류 상태 설명
유효하지 않은 파일 ID 형식 400 파일 ID는 file-로 시작해야 함
파일을 찾을 수 없음 404 참조된 파일이 존재하지 않음
파일 참조가 너무 많음 400 요청에 20개 이상의 파일 참조 포함
해결 타임아웃 504 파일 해결에 30초 이상 소요

지원되는 MIME 타입:

  • image/png - 모든 백엔드
  • image/jpeg - 모든 백엔드
  • image/gif - 모든 백엔드
  • image/webp - 모든 백엔드
  • application/pdf - OpenAI, Anthropic (최대 32MB, 100페이지)
  • text/plain - Anthropic (최대 32MB)

Anthropic 네이티브 API

Continuum Router는 Anthropic의 API 형식을 직접 사용하면서도 라우터의 로드 밸런싱, 장애 조치 및 다중 백엔드 라우팅 기능을 활용할 수 있는 네이티브 Anthropic API 엔드포인트를 제공합니다.

클라이언트가 취소했거나, 타임아웃되었거나, 가드레일에 차단되었거나, 도중에 실패한 스트리밍 메시지는 끊긴 시점까지 생성된 토큰만큼 과금됩니다. 해당 종료 결과를 담은 사용량 레코드 하나가 기록되며 수치는 엔진의 message_delta 사용량이 도착했으면 그 값을, 아니면 라우터의 델타 수와 message_start의 입력을 씁니다. 클라이언트가 떠난 뒤 업스트림이 멈추거나 실패하면 타임아웃이나 오류가 아니라 client_cancelled로 기록됩니다.

메시지 API

Anthropic의 네이티브 API 형식을 사용하여 메시지를 전송합니다.

POST /anthropic/v1/messages

같은 핸들러가 루트 수준에도 POST /v1/messages로 마운트되어 있으므로, 베이스 URL을 서버 루트로 지정한 클라이언트(/anthropic 접두사 없이)도 설정 변경 없이 Anthropic Messages ingress에 도달합니다. 토큰 카운팅에도 같은 루트 수준 별칭이 있습니다: POST /v1/messages/count_tokens.

헤더:

헤더 필수 설명
x-api-key 또는 Authorization: Bearer 예* 인증용 API 키 (blocking 모드에서 필수). 두 헤더 형식 모두 허용되며 둘 다 있으면 Authorization: Bearer가 우선
anthropic-version 아니오 API 버전 (예: 2023-06-01). 네이티브 Anthropic 백엔드로 전달됨
anthropic-beta 아니오 베타 기능 (예: prompt-caching-2024-07-31). 네이티브 Anthropic 백엔드로 전달됨

API 버전 정책

Messages와 토큰 카운팅은 루트 수준 별칭을 포함하여 동일한 anthropic-version 정책을 적용합니다.

  • 생략: 네이티브 Anthropic 요청에는 백엔드의 anthropic_version 설정을 사용합니다. 설정이 없으면 2023-06-01을 사용하며, 이는 라우터가 제공하는 편의 기능입니다.
  • 지정: 쉼표로 구분하거나 반복해서 보낸 각 값은 2020–2099년의 유효한 YYYY-MM-DD 날짜여야 합니다. 값 주변의 공백을 허용하며, 클라이언트 값은 네이티브 Anthropic 백엔드로 전달됩니다.
  • 오류: 빈 값, 잘못된 날짜 또는 유효하지 않은 토큰은 백엔드 요청 전에 HTTP 400 invalid_request_error를 반환합니다. 로컬 추정 방식의 토큰 카운팅에도 적용됩니다.

요청 본문:

{
  "model": "claude-sonnet-4-5-20250929",
  "max_tokens": 1024,
  "messages": [
    {
      "role": "user",
      "content": "안녕하세요, Claude!"
    }
  ],
  "stream": false
}

요청 파라미터:

파라미터 타입 필수 설명
model string 예 모델 식별자
max_tokens integer 예 생성할 최대 토큰 수
messages array 예 메시지 객체 배열
system string/array 아니오 시스템 프롬프트 (문자열 또는 콘텐츠 블록 배열)
stream boolean 아니오 스트리밍 활성화 (기본값: false)
temperature number 아니오 샘플링 온도 (0-1)
top_p number 아니오 핵 샘플링 파라미터
top_k integer 아니오 Top-k 샘플링 파라미터
stop_sequences array 아니오 정지 시퀀스
metadata object 아니오 요청 메타데이터
tools array 아니오 함수 호출을 위한 도구 정의
tool_choice object 아니오 도구 선택 설정
thinking object 아니오 확장 사고 설정. type은 enabled, adaptive, disabled 중 하나이며 budget_tokens는 선택입니다. OpenAI 호환 백엔드로 나가는 확장 사고 참조
output_config object 아니오 Claude 4.6+ 추론 강도. effort는 low, medium, high, xhigh, max 중 하나입니다. thinking.budget_tokens보다 우선하며 OpenAI 호환 백엔드로 나가는 확장 사고 참조

응답 (비스트리밍):

{
  "id": "msg_01XFDUDYJgAACzvnptvVoYEL",
  "type": "message",
  "role": "assistant",
  "content": [
    {
      "type": "text",
      "text": "안녕하세요! 무엇을 도와드릴까요?"
    }
  ],
  "model": "claude-sonnet-4-5-20250929",
  "stop_reason": "end_turn",
  "stop_sequence": null,
  "usage": {
    "input_tokens": 12,
    "output_tokens": 15,
    "cache_creation_input_tokens": 0,
    "cache_read_input_tokens": 0
  }
}

백엔드 라우팅:

라우터는 대상 백엔드에 따라 요청을 자동으로 변환합니다:

백엔드 타입 변환
Anthropic 네이티브 API 형식으로 패스스루
Gemini Gemini 형식으로 직접 변환
OpenAI/vLLM/Ollama OpenAI 형식으로 변환 후 응답을 다시 변환
responses_only 모델이 적용된 OpenAI / Azure OpenAI Responses API 형식으로 변환해 /v1/responses로 dispatch한 뒤, 응답을 Anthropic Messages JSON(또는 Anthropic SSE 이벤트)으로 다시 변환

프롬프트 캐싱

라우터는 프롬프트 캐시 사용량을 그대로 보고하지만 임의로 만들어 내지 않습니다. cache_read_input_tokens와 cache_creation_input_tokens는 서빙 업스트림이 캐시 사용을 보고할 때만 usage에 나타납니다. 아무것도 보고하지 않는 업스트림에서는 input_tokens와 output_tokens만 반환됩니다.

  • 네이티브 Anthropic 및 Bedrock: 클라이언트의 cache_control 블록이 네이티브 본문에 그대로 전달됩니다.
  • vLLM 및 SGLang: 프리픽스 캐싱이 자동으로 동작하며 요청별 브레이크포인트가 필요하지 않습니다. 엔진이 캐시된 토큰을 보고하도록 vLLM에서는 --enable-prompt-tokens-details를, SGLang에서는 --enable-cache-report를 켜면, 라우터가 이 값을 cache_read_input_tokens로 매핑합니다.
  • 그 밖의 OpenAI 와이어 백엔드: 변환된 요청에서는 클라이언트의 cache_control 브레이크포인트가 버려집니다. OpenRouter, LiteLLM, 네이티브 Anthropic을 앞단에 두는 피어 라우터처럼 이 필드를 받아들이는 백엔드에서는 backends[].forward_cache_control: true를 설정하십시오. 그러면 시스템 텍스트, 메시지 텍스트, 이미지, 문서 텍스트 파트의 브레이크포인트가 변환이 만든 OpenAI 와이어 파트로 복사됩니다. 기본값은 type: continuum-router에서 true, 그 밖의 타입에서는 false입니다. 엄격한 클라우드 엔드포인트와 대부분의 로컬 엔진이 이 필드를 거부하거나 무시하기 때문입니다. tool_use, tool_result, thinking 블록과 도구 정의의 브레이크포인트는 전달하지 않습니다.

라우터 자체의 anthropic_auto_cache_control 주입은 별개 동작입니다. 클라이언트가 브레이크포인트를 보내지 않았을 때 네이티브 Anthropic 시스템 프롬프트에 하나를 추가합니다.

OpenAI 호환 백엔드로 나가는 확장 사고

Anthropic과 Bedrock 백엔드는 thinking과 output_config를 그대로 받습니다. OpenAI 호환 백엔드는 대신 이 값들에서 유도한 OpenAI reasoning_effort를 받으며 Anthropic 고유 필드는 와이어까지 나가지 않습니다. Gemini 백엔드는 세 번째 경우이며 아래 구간표가 적용되지 않습니다. budget_tokens를 generation_config.thinking_config.thinking_budget 토큰 수로 받고 output_config는 아예 받지 않습니다.

유도는 요청에 thinking 블록이 있을 때만 수행됩니다. thinking: {"type": "enabled", "budget_tokens": N}은 다음 구간표를 따릅니다:

budget_tokens reasoning_effort
N <= 4096 low
4097 <= N <= 10240 medium
N > 10240 high

budget_tokens가 없는 thinking: {"type": "enabled"}는 medium입니다. 이를 덮어쓸 output_config.effort가 없다면, type이 adaptive이거나 disabled일 때 reasoning_effort를 전혀 만들지 않으므로 백엔드가 자체 기본값을 적용합니다.

output_config.effort가 요청에 있으면 thinking.type이 무엇이든 그 값이 우선합니다. low, medium, high, xhigh는 수준 그대로 매핑되고 max는 xhigh가 되며 그 밖의 문자열은 로그를 남기고 high로 처리합니다. 교차 공급자 폴백 홉은 두 가지 점에서 다릅니다. 홉에서는 disabled thinking 설정이 effort에 자리를 내주지 않고 오히려 effort를 억제하며 알 수 없는 effort 문자열은 high가 되는 대신 budget 기반 매핑으로 넘어갑니다.

이 엔드포인트의 thinking.budget_tokens 검증

thinking: {"type": "enabled"} 블록은 디스패치 전에 Anthropic Messages 계약에 따라 검증됩니다. 라우터가 모든 백엔드 경로에서 이 필드를 재작성하거나 제거하므로 원시 값은 어떤 업스트림에도 도달하지 않습니다. 다음은 400 invalid_request_error로 거부됩니다.

  • budget_tokens가 없는 경우
  • budget_tokens가 1024 미만인 경우
  • budget_tokens가 max_tokens 이상인 경우. 단, anthropic-beta 헤더가 interleaved-thinking- 토큰을 담고 있으면 제외합니다. 이 베타에서 Anthropic은 budget이 max_tokens를 넘는 것을 허용합니다.

응답의 thinking 블록

OpenAI 호환 백엔드의 추론 텍스트(reasoning_content 또는 reasoning)와 Responses API의 reasoning 항목은 요청의 thinking.type이 enabled 또는 adaptive일 때만 thinking 블록(스트리밍에서는 thinking_delta 이벤트)이 됩니다. thinking 파라미터가 없거나 disabled이면 추론 텍스트를 버리고 Anthropic API와 같이 텍스트나 tool-use 블록으로 응답을 시작합니다. DeepSeek와 최신 vLLM 같은 백엔드는 모든 응답에 추론 내용을 담으므로 이 규칙은 백엔드가 무엇을 반환하든 적용됩니다.

토큰 집계는 thinking 블록 표시 여부와 독립적입니다. OpenAI 호환 업스트림의 Anthropic usage.output_tokens는 추론을 포함한 completion_tokens를 유지합니다. 완료 토큰 20개 중 추론 상세 토큰이 20개라면 출력은 40개가 아니라 20개입니다. 추론이 완료 토큰을 모두 사용해 보이는 텍스트가 없어도 업스트림 finish_reason: "length"는 버퍼·스트리밍 응답 모두 stop_reason: "max_tokens"로 변환됩니다.

Gemini 백엔드는 추론에 불투명한 thought signature를 붙이며, Gemini 3은 다시 보낸 함수 호출에 이 값이 없으면 함수 호출 루프의 다음 턴을 거부합니다. 라우터는 이 값을 서명 대상 tool_use 바로 앞의 thinking 블록에 signature로 담아 반환합니다(스트리밍에서는 signature_delta 이벤트). Gemini가 추론 텍스트를 보내지 않았다면 thinking 텍스트는 비어 있고, 요청이 thinking을 켰는지와 상관없이 이 블록을 내보냅니다. 클라이언트가 이 블록을 다시 보내면 라우터는 서명을 해당 함수 호출에 되돌려 쓰며, 다른 백엔드에는 전달하지 않습니다.

Note: 라우터가 만든 thinking 블록에는 Anthropic 서명이 없습니다. 서명을 제공하지 않는 백엔드의 thinking 블록에는 signature 필드가 없고, Gemini 서명은 그 값을 발급한 Gemini 백엔드만 검증할 수 있습니다. 이런 대화 기록은 Anthropic API로 직접 보내지 말고 라우터를 거쳐 다시 보내세요.

이 엔드포인트의 두 변환, 즉 Chat Completions 변환과 responses_only 모델용 Responses API 변환이 모두 같은 구간표를 사용하며 OpenAI 호환 백엔드로 가는 교차 공급자 폴백 홉도 마찬가지입니다. 이 구간표는 라우터가 반대 방향에서 적용하는 reasoning_effort → budget_tokens 매핑의 역함수입니다. 홉 쪽 동작과 특정 대상 어휘에 맞추는 방식은 thinking 설정은 reasoning_effort가 됩니다를 참고하세요.

responses_only 모델용 투명한 Responses-API 라우팅

요청한 model이 model-metadata.yaml이나 내장 OpenAI 레지스트리에서 responses_only: true로 표시된 경우(예: gpt-5-pro, gpt-5.2-pro, gpt-5.4-pro, gpt-5.5-pro), 라우터는 Anthropic 형식 요청을 /v1/chat/completions 대신 업스트림 /v1/responses로 투명하게 보냅니다:

  • 요청은 dispatch 전에 Anthropic Messages 형식에서 Responses API 모양으로 변환됩니다.
  • 업스트림 응답은 Anthropic Messages JSON으로 다시 변환되며 스트리밍의 경우 Anthropic SSE 이벤트 시퀀스로 변환됩니다.
  • 도구 호출 왕복, 웹 검색 에뮬레이션, Unix 소켓 전송 모두 같은 플래그에서 분기합니다.

dispatch는 클라이언트에 투명합니다. 클라이언트는 /anthropic/v1/messages를 그대로 호출하고 Anthropic 형식 응답을 계속 받게 됩니다. /v1/responses는 OpenAI와 Azure OpenAI 백엔드만 제공하며 responses_only 모델이 다른 유형의 백엔드와 짝지어지면 업스트림 호출 전에 400 invalid_request_error(이 surface에서는 Anthropic 모양)가 반환됩니다. 전체 모델 목록과 설정 방법, 새 모델을 표시하는 방법은 Responses-API 전용 모델을 참조하세요.

파일 참조와 해석 한도

이미지와 문서 블록은 base64를 직접 싣는 대신 Files API로 업로드한 파일을 가리킬 수 있습니다:

{"type": "image", "source": {"type": "file_id", "file_id": "file-abc123def456"}}

이런 참조는 요청을 백엔드로 보내기 전에 모두 로드되어 base64로 인코딩되며 tool_result 콘텐츠 배열 안에 중첩된 참조도 포함됩니다. 참조마다 인코딩된 데이터 사본이 따로 생기므로, 요청 비용은 서로 다른 파일의 개수가 아니라 참조 개수에 비례해 늘어납니다. 요청 단위로 다음 한도가 적용됩니다:

한도 값 초과 시
요청당 참조 개수 100 400 invalid_request_error, 파일을 읽기 전에 판정
참조 파일 합계 크기 32 MiB 400 invalid_request_error. 참조마다 계산하므로 10 MiB 파일을 네 번 참조하면 40 MiB로 계산됩니다
동시 파일 로드 8 오류 없음. 초과분은 슬롯이 날 때까지 대기
전체 해석 시간 30초 504

참조된 개별 파일에는 10 MiB 상한이 추가로 적용됩니다.

참조 개수 상한은 /v1/chat/completions의 20개보다 의도적으로 높게 잡았습니다. Messages API는 매 턴마다 대화 전체를 다시 보내므로, 긴 멀티모달 세션에서는 단일 chat-completions 호출에서는 나올 수 없는 개수의 참조가 정상적으로 쌓입니다. base64는 원본 바이트를 약 4/3배로 늘리므로 32 MiB 예산은 출력 기준 약 43 MiB에 해당합니다.

토큰 카운팅

메시지 요청 페이로드의 토큰 수를 계산합니다.

POST /anthropic/v1/messages/count_tokens

요청 본문:

{
  "model": "claude-sonnet-4-5-20250929",
  "messages": [
    {
      "role": "user",
      "content": "안녕하세요, 어떠세요?"
    }
  ],
  "system": "당신은 도움이 되는 어시스턴트입니다."
}

응답:

{
  "input_tokens": 18
}

계층적 토큰 카운팅

토큰 카운팅은 백엔드 타입에 따라 다른 전략을 사용합니다:

백엔드 타입 전략 정확도
Anthropic 네이티브 /v1/messages/count_tokens API 프록시 정확
llama.cpp, mlxcel 백엔드 /tokenize 엔드포인트 프록시 정확
Continuum Router (체인) 하위 라우터의 /tokenize 하위 라우터의 답. 하위 라우터가 토큰화할 수 없으면(그 라우터의 501, 또는 이 경로가 없는 이전 릴리스의 404) 이 라우터의 문자 기반 추정값
vLLM 백엔드 /tokenize 엔드포인트 프록시 정확
SGLang 백엔드 /v1/tokenize 엔드포인트 프록시 (bare /tokenize 경로 아님) 정확
기타 문자 기반 추정 (~4자/토큰) 근사

엔진 /tokenize 호출이 실패하면(2xx가 아닌 상태 코드, 읽을 수 없는 응답, 16 MiB를 넘는 응답, 연결 실패, 30초 뒤의 타임아웃) 아래의 문자 기반 추정으로 답합니다. 이 호출은 /tokenize 경로의 서킷 규칙을 따릅니다. 엔진 응답은 상태 코드와 관계없이 백엔드 서킷에 중립이고 연결 실패와 타임아웃만 집계합니다.

참고: 네이티브 토큰화를 지원하지 않는 백엔드의 경우, 라우터는 토큰당 약 4자를 기준으로 하는 문자 기반 추정을 사용합니다. 이는 계획 목적으로 합리적인 근사치를 제공하지만 모델이 사용하는 정확한 토큰 수와 일치하지 않을 수 있습니다.

모델 목록

Anthropic API 형식으로 사용 가능한 모델을 나열합니다.

GET /anthropic/v1/models

응답:

{
  "data": [
    {
      "id": "claude-sonnet-4-5-20250929",
      "type": "model",
      "display_name": "Claude Sonnet 4",
      "created_at": "2025-05-14T00:00:00Z"
    },
    {
      "id": "claude-opus-4-5-20251101",
      "type": "model",
      "display_name": "Claude Opus 4",
      "created_at": "2025-05-14T00:00:00Z"
    }
  ],
  "has_more": false,
  "first_id": "claude-sonnet-4-5-20250929",
  "last_id": "claude-opus-4-5-20251101"
}

Claude Code 호환성

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

프롬프트 캐싱

프롬프트 캐싱은 콘텐츠 블록의 cache_control 필드를 통해 완벽하게 지원됩니다:

{
  "model": "claude-sonnet-4-5-20250929",
  "max_tokens": 1024,
  "system": [
    {
      "type": "text",
      "text": "당신은 광범위한 지식을 가진 유용한 코딩 어시스턴트입니다...",
      "cache_control": {"type": "ephemeral"}
    }
  ],
  "messages": [
    {
      "role": "user",
      "content": [
        {
          "type": "text",
          "text": "이전 컨텍스트...",
          "cache_control": {"type": "ephemeral"}
        },
        {
          "type": "text",
          "text": "현재 질문"
        }
      ]
    }
  ]
}

지원되는 cache_control 위치:

  • 시스템 프롬프트 텍스트 블록
  • 사용자 메시지 텍스트 블록
  • 사용자 메시지 이미지 블록
  • 도구 정의
  • 도구 사용 블록
  • 도구 결과 블록

베타 기능 헤더

anthropic-beta 헤더는 네이티브 Anthropic 백엔드로 전달되어 베타 기능을 활성화합니다:

curl -X POST http://localhost:8080/anthropic/v1/messages \
  -H "x-api-key: $ANTHROPIC_API_KEY" \
  -H "anthropic-version: 2023-06-01" \
  -H "anthropic-beta: prompt-caching-2024-07-31,interleaved-thinking-2025-05-14" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "claude-sonnet-4-5-20250929",
    "max_tokens": 1024,
    "messages": [{"role": "user", "content": "안녕하세요"}]
  }'

스트리밍에서의 캐시 사용량

네이티브 Anthropic 백엔드로 스트리밍할 때 캐시 사용량 정보가 message_start 이벤트에 포함됩니다:

{
  "type": "message_start",
  "message": {
    "id": "msg_01XFDUDYJgAACzvnptvVoYEL",
    "type": "message",
    "role": "assistant",
    "content": [],
    "model": "claude-sonnet-4-5-20250929",
    "usage": {
      "input_tokens": 2159,
      "cache_creation_input_tokens": 2048,
      "cache_read_input_tokens": 0
    }
  }
}

인터리브드 씽킹

인터리브드 출력을 포함한 확장 사고(Extended Thinking)는 스트리밍 모드에서 지원됩니다. 모델이 사고와 텍스트 콘텐츠를 교대로 생성할 때, 스트리밍 이벤트는 각 블록에 대해 적절한 content_block_start, content_block_delta, content_block_stop 이벤트와 함께 이 인터리브드 구조를 올바르게 표현합니다.


Admin API

admin 기능으로 컴파일하면 관리 경로가 /admin 아래에 마운트됩니다. admin.auth가 없으면 401 Unauthorized로 실패 시 차단하며, method가 없는 auth 블록은 기본적으로 api_key를 사용합니다. 인증하지 않는 개발 접근은 admin.auth.method: none을 명시한 경우에만 활성화됩니다.

자주 사용하는 조회 작업:

GET /admin/capabilities
GET /admin/health
GET /admin/backends
GET /admin/models
GET /admin/config/full
GET /admin/config/hot-reload-status
GET /admin/stats

변경, 스키마, 가져오기/내보내기, API 키, 프롬프트, 캐시, 스마트 라우팅, 가드레일, Files, ACP, 기능 조건부 컨트롤 플레인 경로는 Admin REST API 레퍼런스를 참고하세요. 해당 문서에는 현재의 재적용 경계와 대기 중인 변경을 적용하는 것처럼 사용하면 안 되는 설정 엔드포인트도 설명되어 있습니다.

오류 처리

오류 응답 형식

모든 오류는 일관된 JSON 구조를 따릅니다:

{
  "error": {
    "message": "Human-readable error description",
    "type": "error_type_identifier",
    "param": null,
    "code": "error_code_identifier",
    "details": {
      "additional": "context information"
    }
  }
}

code는 문자열 또는 null이며 HTTP 상태 코드가 아닙니다. 상태 코드는 HTTP 상태 줄에만 실립니다. param도 문자열 또는 null이며 값이 없어도 생략하지 않습니다. 라우터가 생성하는 코드 전체 목록은 오류 처리를 참고하세요.

오류 타입

타입 HTTP 코드 설명
bad_request 400 유효하지 않은 요청 형식 또는 파라미터
unauthorized 401 인증 필요 (차단 인증 모드)
forbidden 403 접근 거부 (스코프 또는 소유권 부족)
model_not_found 404 요청된 모델을 사용할 수 없음
rate_limit_exceeded 429 속도 제한 초과
internal_error 500 라우터 내부 오류
bad_gateway 502 백엔드 연결/응답 오류
service_unavailable 503 모든 백엔드가 비정상
gateway_timeout 504 백엔드 요청 타임아웃

오류 응답 예제

모델을 찾을 수 없음:

{
  "error": {
    "message": "Model 'invalid-model' not found on any healthy backend",
    "type": "model_not_found",
    "param": null,
    "code": "model_not_found",
    "details": {
      "requested_model": "invalid-model",
      "available_models": ["gpt-5.6-sol", "gpt-5.6-luna", "llama2"]
    }
  }
}

백엔드 오류:

{
  "error": {
    "message": "Failed to connect to backend 'local-ollama'",
    "type": "bad_gateway",
    "param": null,
    "code": "upstream_error",
    "details": {
      "backend": "local-ollama",
      "backend_error": "Connection refused"
    }
  }
}

서비스 사용 불가:

{
  "error": {
    "message": "All backends are currently unhealthy",
    "type": "service_unavailable",
    "param": null,
    "code": "service_unavailable",
    "details": {
      "healthy_backends": 0,
      "total_backends": 3
    }
  }
}

속도 제한

설정에서 rate_limiting을 활성화하면 라우터가 여러 차원에 걸쳐 토큰 버킷 제한을 적용합니다:

  • 클라이언트(IP 주소)별 제한
  • API 키별 제한 (키별 오버라이드 포함)
  • 사용자별·팀별 제한
  • 백엔드별 및 전역 제한
  • 모델별 제한
  • 토큰 예산 (tokens_per_minute, tokens_per_day)

제한을 초과한 요청은 Retry-After 헤더와 함께 429 Too Many Requests를 받고, 제한 내 응답에는 호출자 본인의 per_api_key 버킷만 보고하는 X-RateLimit-* 헤더가 실립니다. GET /v1/quota는 같은 신원 기반 한도를 조회하는 경로이며, 조회한 값을 소비하지 않고 반복 호출할 수 있습니다. 적용되는 키별, 사용자별, 팀별, 동시 처리, 장기 예산, 허브 티어 값을 보고하고 공유 global, per_client, per_model, per_backend 수준은 제외하며, 자체 속도 제한 헤더는 싣지 않습니다. 설정, 클라이언트 식별, 우회 옵션, 스토리지 백엔드는 호출자 할당량과 속도 제한을 참조하세요.

스트리밍

Server-Sent Events (SSE)

stream: true가 지정되면 응답은 다음과 함께 Server-Sent Events로 전송됩니다:

Content-Type: text/event-stream Cache-Control: no-cache Connection: keep-alive

SSE 형식

data: {"id":"chatcmpl-123","object":"chat.completion.chunk",...}

data: {"id":"chatcmpl-123","object":"chat.completion.chunk",...}

data: [DONE]

SSE 호환성

라우터는 최대 호환성을 위해 여러 SSE 형식을 지원합니다:

  • 표준 형식: data: {...}
  • 스페이스 형식: data: {...}
  • 혼합 줄 바꿈: \r\n, \n, \r 처리
  • 빈 줄: 청크 구분자 올바르게 처리

연결 관리

  • Keep-Alive: 스트리밍 중 연결 유지
  • 타임아웃: 장시간 실행 요청에 대해 5분 타임아웃
  • 오류 처리: 부분 응답에 오류 정보 포함
  • 클라이언트 연결 해제: 클라이언트 연결 해제를 정상적으로 처리

예제

기본 채팅 완료

curl -X POST http://localhost:8080/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{
    "model": "gpt-5.6-luna",
    "messages": [
      {"role": "user", "content": "Hello, how are you?"}
    ]
  }'

스트리밍 채팅 완료

curl -X POST http://localhost:8080/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{
    "model": "gpt-5.6-luna",
    "messages": [
      {"role": "user", "content": "Write a short story"}
    ],
    "stream": true,
    "max_tokens": 200
  }'

파라미터가 포함된 텍스트 완료

curl -X POST http://localhost:8080/v1/completions \
  -H "Content-Type: application/json" \
  -d '{
    "model": "gpt-5.6-luna",
    "prompt": "The future of AI is",
    "max_tokens": 50,
    "temperature": 0.8,
    "top_p": 0.9
  }'

백엔드 상태 확인

curl http://localhost:8080/admin/backends | jq

서비스 헬스 모니터링

curl http://localhost:8080/admin/health | jq '.services'

사용 가능한 모델 목록

curl http://localhost:8080/v1/models | jq '.data[].id'

Python 클라이언트 예제

import requests
import json

# 클라이언트 설정
BASE_URL = "http://localhost:8080"

def chat_completion(messages, model="gpt-5.6-luna", stream=False):
    """채팅 완료 요청 전송"""
    response = requests.post(
        f"{BASE_URL}/v1/chat/completions",
        headers={"Content-Type": "application/json"},
        json={
            "model": model,
            "messages": messages,
            "stream": stream,
            "temperature": 0.7
        },
        stream=stream
    )

    if stream:
        # 스트리밍 응답 처리
        for line in response.iter_lines():
            if line:
                line = line.decode('utf-8')
                if line.startswith('data: '):
                    data = line[6:]  # 'data: ' 접두사 제거
                    if data == '[DONE]':
                        break
                    try:
                        chunk = json.loads(data)
                        content = chunk['choices'][0]['delta'].get('content', '')
                        if content:
                            print(content, end='', flush=True)
                    except json.JSONDecodeError:
                        continue
        print()  # 스트리밍 후 새 줄
    else:
        # 비스트리밍 응답 처리
        result = response.json()
        return result['choices'][0]['message']['content']

# 사용 예제
messages = [
    {"role": "user", "content": "Explain machine learning in simple terms"}
]

print("스트리밍 응답:")
chat_completion(messages, stream=True)

print("\n비스트리밍 응답:")
response = chat_completion(messages, stream=False)
print(response)

JavaScript/Node.js 클라이언트 예제

const fetch = require('node-fetch');

const BASE_URL = 'http://localhost:8080';

async function chatCompletion(messages, options = {}) {
    const response = await fetch(`${BASE_URL}/v1/chat/completions`, {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json',
        },
        body: JSON.stringify({
            model: options.model || 'gpt-5.6-luna',
            messages: messages,
            stream: options.stream || false,
            temperature: options.temperature || 0.7,
            ...options
        })
    });

    if (options.stream) {
        // 스트리밍 응답 처리
        const reader = response.body.getReader();
        const decoder = new TextDecoder();

        while (true) {
            const { done, value } = await reader.read();
            if (done) break;

            const chunk = decoder.decode(value);
            const lines = chunk.split('\n');

            for (const line of lines) {
                if (line.startsWith('data: ')) {
                    const data = line.slice(6);
                    if (data === '[DONE]') return;

                    try {
                        const parsed = JSON.parse(data);
                        const content = parsed.choices[0]?.delta?.content;
                        if (content) {
                            process.stdout.write(content);
                        }
                    } catch (e) {
                        // JSON 파싱 오류 무시
                    }
                }
            }
        }
        console.log(); // 새 줄
    } else {
        const result = await response.json();
        return result.choices[0].message.content;
    }
}

// 사용 예제
const messages = [
    { role: 'user', content: 'What is the meaning of life?' }
];

// 스트리밍
console.log('스트리밍 응답:');
await chatCompletion(messages, { stream: true });

// 비스트리밍
console.log('\n비스트리밍 응답:');
const response = await chatCompletion(messages);
console.log(response);

Agent-to-Agent (A2A) 프로토콜

config.yaml에서 a2a.enabled가 true이면 Continuum Router는 표준 A2A Agent Card 탐색 엔드포인트와 JSON-RPC 2.0 태스크 실행 엔드포인트를 노출합니다. a2a 섹션이 없거나 a2a.enabled가 false이면 두 엔드포인트 모두 404 Not Found를 반환합니다.

a2a:
  enabled: true
  name: "Continuum Router"
  description: "High-performance LLM API router with A2A protocol support"
  default_model: "gpt-5.6-luna"
  max_tasks: 1000
  task_ttl: "1h"

Agent Card 탐색 (GET /.well-known/agent.json)

라우터 표시 이름, /a2a 엔드포인트 URL, 크레이트 버전(CARGO_PKG_VERSION), 지원 기능(streaming: true, stateTransitionHistory: true), 인증 스킴(Bearer, ApiKey), 기본 입출력 모드, 노출 스킬 목록을 담은 A2A Agent Card JSON을 반환합니다.

JSON-RPC 2.0 태스크 엔드포인트 (POST /a2a)

JSON-RPC 2.0 요청을 처리하며 라우터의 API 키 / Bearer 인증 및 키별 allowed_models, allowed_backends 허용 목록을 동일하게 적용합니다(차단 모드에서 자격 증명이 없거나 유효하지 않으면 401 Unauthorized, 요청한 모델이나 서빙 백엔드가 키 허용 목록 밖이면 403 Forbidden).

메서드 설명
message/send 라우터의 채팅 완료 프록시 파이프라인을 통해 태스크를 동기 실행하고(configuration.blocking이 false이면 submitted 상태로 등록) Task 객체(submitted, working, completed, failed, canceled)를 반환합니다.
message/stream Server-Sent Events(text/event-stream)로 태스크를 실행하며, 완료 또는 취소될 때까지 JSON-RPC 2.0 status-update와 증분 artifact-update 이벤트를 스트리밍합니다.
tasks/get 기존 태스크 ID의 현재 상태, 생성된 아티팩트, 메시지 히스토리를 조회합니다.
tasks/cancel 실행 중(submitted 또는 working)인 태스크를 취소하고 상태를 canceled로 전환합니다.

다음 단계로는 운영 환경에 따라 Admin REST API로 런타임 설정을 조작하거나, 속도 제한과 로드 밸런싱 가이드에서 트래픽 제어 옵션을 확인할 수 있습니다. 오류 코드와 재시도 전략은 오류 처리에 정리되어 있습니다.