콘텐츠로 이동

오류 처리

Continuum Router는 HTTP 경계에서 OpenAI 호환 JSON 오류를 반환하고, 재시도·능동 헬스 필터링·모델 폴백을 통해 일시적인 백엔드 장애를 격리합니다.

오류 응답 형식

라우터가 생성하는 대부분의 오류는 다음 형식을 따릅니다.

{
  "error": {
    "message": "The service is temporarily unavailable. Please try again later.",
    "type": "service_unavailable",
    "param": null,
    "code": "service_unavailable"
  }
}

네 필드는 OpenAI Error 스키마에 맞춰 항상 포함됩니다. message와 type은 문자열이고, param과 code는 값이 없더라도 생략하지 않고 null로 반환합니다.

code는 안정적인 기계 판독용 식별자이며 HTTP 상태 코드가 아닙니다 (HTTP 상태 코드는 상태 줄에만 기록됩니다). param은 업스트림 제공자가 알려 준 문제 필드 이름입니다. 속도 제한 응답에는 Retry-After 헤더가 함께 포함될 수 있습니다.

업스트림 제공자가 자체 문자열 오류 코드를 반환하면 그 값을 그대로 전달하므로, 클라이언트는 insufficient_quota나 context_length_exceeded 같은 값으로 분기할 수 있습니다. 아래 표는 라우터가 직접 생성하는 오류 코드입니다.

업스트림 출력 토큰 오류

백엔드가 거부한 출력 예산의 필드를 식별할 수 있으면, 라우터는 클라이언트가 보낸 max_tokens, max_completion_tokens 또는 Responses의 max_output_tokens 이름을 유지합니다. max_model_len, max_total_tokens 같은 엔진 한도 식별자가 포함된 오류는 출력 예산을 조정하거나 입력을 줄이라는 메시지로 바꿉니다. 엔진 설정값을 실제로 사용할 수 있는 출력 예산으로 안내하지는 않습니다.

그 밖의 백엔드 오류는 민감한 정보를 제거한 진단 문구를 유지합니다. 원인 필드를 알 수 없으면 param: null로 응답합니다. Anthropic 오류에도 자체 응답 형식 안에서 같은 메시지 규칙을 적용하며, 원래 업스트림 진단은 서버 로그에 남습니다.

라우터가 생성하는 오류 코드

코드 의미 주요 상태
invalid_request 요청이 잘못되었거나 처리할 수 없음 400
invalid_json 본문이 올바른 JSON이 아니거나 application/json으로 보내지 않았거나 엔드포인트의 요청 형식과 맞지 않음. message에 파서가 밝힌 이유가 담깁니다. /anthropic/*와 루트 /v1/messages 경로에서는 같은 거부를 type: invalid_request_error인 Anthropic 봉투로 반환합니다 400, 415, 422
invalid_api_key 자격 증명이 없거나 유효하지 않음 401
insufficient_permissions 인증은 되었으나 해당 자원 사용 권한이 없음 403
model_not_found 라우터에 등록되지 않은 모델 404
timeout 라우터 또는 백엔드 기한 만료 408, 504
request_too_large 요청 본문이 설정된 한도를 초과함 413
rate_limit_exceeded 라우터 또는 제공자 속도 제한 초과 429
internal_error 라우터 설정 또는 내부 오류 500
upstream_error 백엔드가 오류로 응답했거나 스트림·연결이 실패함 502
service_unavailable 요청을 처리할 정상 백엔드가 없음 503
server_overloaded 라우터가 동시 처리 한계에 도달함 503
invalid_router_hops x-continuum-router-hops 헤더가 음이 아닌 정수가 아님 400
router_loop_detected 요청이 이미 라우터 8개를 거침 (백엔드가 자신이 속한 라우터 체인을 다시 가리킴, 백엔드: 포워딩 루프와 홉 제한 참고) 508

첫 바이트를 전송하기 전에 발생한 스트리밍 오류는 일반 HTTP 오류 응답으로 반환됩니다. SSE 응답이 시작된 뒤에는 상태 코드를 변경할 수 없으므로, 스트림에 오류 이벤트를 기록하고 연결을 종료합니다. Chat Completions 와이어에서는 오류 이벤트가 HTTP 본문과 동일한 error 객체를 담으며, Responses 와이어에서는 평평한 ResponseErrorEvent 형태로 type 옆에 code, message, param, sequence_number를 배치합니다.

상태 코드

상태 일반적인 원인 재시도 지침
400 잘못된 요청 또는 제공자가 매개변수를 거부함 요청 수정
401 자격 증명이 없거나 유효하지 않음 인증 정보 수정
403 인증된 호출자에게 요청 자원 권한이 없음 권한 또는 키 정책 수정
404 모델 또는 저장된 응답을 찾을 수 없음 식별자 확인
408 라우터 요청 기한 만료 재시도가 안전한 작업일 때만 재시도
413 요청 본문이 설정된 한도를 초과함 요청 크기 축소
429 라우터/제공자 속도 제한 또는 할당량 소진 Retry-After 준수; 할당량 소진 오류는 자동 재시도하지 않음
500 라우터 설정 또는 내부 오류 로그와 설정 확인
502 백엔드 연결, 스트림 또는 게이트웨이 오류 일반적으로 일시적
503 백엔드를 사용할 수 없거나 적격 백엔드가 모두 비정상 일반적으로 일시적
504 백엔드 시간 초과 일반적으로 일시적

백엔드 오류 메시지

백엔드가 4xx로 요청을 거부하면, 라우터는 클라이언트가 대응할 수 있도록 백엔드의 원래 메시지를 전달합니다. 예를 들어 컨텍스트 길이 초과와 잘못된 파라미터는 서로 다른 메시지로 반환됩니다. /v1/chat/completions와 /anthropic/v1/messages는 각자의 오류 형식으로 같은 메시지를 반환합니다. /v1/responses에서는 Responses API를 직접 지원하는 백엔드의 JSON 오류 본문을 그대로 전달하며, 변환 경로는 아래 규칙을 따릅니다.

  • 인식하는 본문 형식: OpenAI 및 Gemini {"error":{"message":...}}, Anthropic {"type":"error","error":{...}}, {"error":"..."}, 최상위 message, FastAPI detail(문자열 또는 검증 목록), 짧은 일반 텍스트.
  • 마스킹: 절대 URL, URL에 포함된 자격 증명, Bearer 토큰, 공급자 API 키, key=value 형태의 비밀 값, IP 주소, host:port 엔드포인트는 [redacted-url], [redacted], [redacted-host]로 바뀝니다. 제어 문자는 제거되며, 메시지는 500바이트로 제한됩니다.
  • Anthropic 오류 유형: /anthropic/v1/messages에서 백엔드의 400과 422는 invalid_request_error, 413은 request_too_large, 529는 overloaded_error가 됩니다.
  • 인식하지 못하는 4xx 본문: 민감 정보를 가리고 길이를 제한한 본문을 Backend rejected the request: HTTP <status>: 뒤에 붙여 전달합니다. 이 본문이 백엔드가 요청을 거부한 이유를 설명하는 유일한 정보인 경우가 많습니다.
  • 일반 메시지 대체: 5xx 본문, HTML 페이지, 빈 본문, 이진 본문은 상태별 일반 메시지로 대체됩니다. 따라서 스택 트레이스나 인프라 정보가 클라이언트에 노출되지 않습니다.

운영자 로그에는 항상 백엔드 본문 전체가 기록됩니다.

재시도

최상위 retry 섹션은 선택된 백엔드에 대한 재시도를 제어합니다.

retry:
  max_attempts: 3
  initial_delay: 100ms
  max_delay: 10s
  backoff_multiplier: 2.0
  jitter: true
  retryable_status_codes: [429, 502, 503, 504]
  retryable_errors: [ConnectionError, TimeoutError]
  timeout: 30s

위 값은 기본값이기도 합니다. 라우터는 선택적 지터가 포함된 지수 백오프를 적용합니다. 제공자 429 응답은 일시적인 속도 제한으로 분류될 때만 재시도하며, 할당량·크레딧·결제 한도 소진은 즉시 실패 처리합니다. 업스트림 Retry-After 또는 Google 재시도 지연 힌트는 유효성 검증 후 반영하며 최대 24시간으로 제한합니다.

재시도는 동일한 백엔드에 요청을 다시 보내는 동작이며, 조건에 해당하는 오류 발생 시 다른 모델이나 제공자로 전환하는 폴백과는 구별됩니다.

서킷 브레이커

Continuum Router에는 설정 가능한 3상태 서킷 브레이커와 Admin 제어 기능이 포함되어 있습니다.

circuit_breaker:
  enabled: true
  failure_threshold: 5
  failure_rate_threshold: 0.5
  minimum_requests: 10
  timeout: 60s
  half_open_max_requests: 3
  half_open_success_threshold: 2
  failure_status_codes: [500, 502, 503, 504]
  timeout_as_failure: true
  sliding_window_size: 60s
  sliding_window_type: count_based
  backends:
    openai:
      failure_threshold: 10
      timeout: 120s

sliding_window_type에는 count_based 또는 time_based를 지정할 수 있습니다. circuit_breaker.backends 하위 항목은 지원되는 백엔드별 설정만 덮어씁니다.

일반 LLM 프록시 경로는 요청 결과를 이 상태 머신에 기록하고, 백엔드를 선택할 때 상태를 확인합니다. 서킷이 열린 백엔드는 후보에서 제외되며, 호환되는 모든 백엔드의 서킷이 열려 있으면 라우터는 503 service-unavailable 오류를 반환하고 설정된 폴백을 진행합니다. 서킷 브레이커는 능동 헬스 체크, 재시도, 폴백을 대체하지 않고 보완합니다. 결과 기록과 허용 판단의 세부 사항은 서킷 브레이커를 참고하십시오.

Admin API 인증이 허용되는 경우 다음 엔드포인트에서 서킷 상태를 조회하거나 제어할 수 있습니다.

메서드 경로 용도
GET /admin/circuit/all 모든 서킷 상태 조회
GET /admin/circuit/{backend}/status 단일 백엔드 상태 조회
POST /admin/circuit/{backend}/open 서킷 강제 열기
POST /admin/circuit/{backend}/close 서킷 강제 닫기
POST /admin/circuit/{backend}/reset 추적 중인 서킷 상태 초기화

인증 방법과 전체 엔드포인트 설명은 Admin API를 참고하십시오.

모델 폴백

폴백 체인은 설정된 오류 조건이 발생했을 때 대체 모델을 순차적으로 선택합니다.

fallback:
  enabled: true
  mid_stream_enabled: true
  fallback_chains:
    gpt-5.4:
      - gpt-5.4-mini
      - claude-sonnet-4-6
  fallback_policy:
    trigger_conditions:
      error_codes: [429, 500, 502, 503, 504]
      timeout: true
      connection_error: true
      model_not_found: true
      backend_unhealthy: true
    max_fallback_attempts: 3
    fallback_timeout_multiplier: 1.5
    preserve_parameters: true
  model_settings:
    gpt-5.4:
      fallback_enabled: true
      notify_on_fallback: true

notify_on_fallback: true(기본값)이면 성공한 폴백 응답에 다음 헤더가 포함될 수 있습니다.

  • X-Fallback-Used
  • X-Original-Model
  • X-Fallback-Model
  • X-Fallback-Reason
  • X-Fallback-Attempts: 기본 시도를 포함한 전체 모델 시도 횟수 (폴백으로 응답한 경우 항상 2 이상)

모델 설정에서 notify_on_fallback: false를 지정하면 폴백은 그대로 수행하면서 이 헤더들만 생략합니다.

요청 단위 폴백 해제

/v1/chat/completions 또는 /v1/completions 요청 본문에 "disable_fallbacks": true를 넣으면 해당 요청에 한해 첫 바이트 이전과 스트림 도중 모두에서 다른 모델로의 홉을 건너뜁니다. 이때는 기본 모델의 오류를 그대로 반환합니다. 같은 모델을 제공하는 백엔드 사이의 재시도와 장애 조치는 계속 적용되고, 다른 요청은 체인을 그대로 사용합니다. 라우터는 값과 관계없이 디스패치 전에 이 필드를 제거하므로, 백엔드는 이 필드를 받지 않습니다. 이 플래그는 해당 요청에서 라우터가 하는 일을 줄이기만 하므로, Continuum Hub에 귀속된 요청도 설정할 수 있습니다.

인그레스 적용 범위와 오류 응답 형식

  • 적용 대상 경로: 폴백 체인은 요청된 모델로 대상 백엔드를 결정하는 모든 인그레스에서 실행됩니다. OpenAI 호환 경로는 공용 디스패치 파이프라인을 통해 실행되며, /anthropic/v1/messages와 /v1/responses는 비스트리밍 요청과 첫 SSE 바이트 전송 이전의 스트리밍 요청 모두에서 자체 후보 선택 및 네이티브 디스패치를 통해 체인을 실행합니다.
  • 스트리밍 핸드셰이크 확정: 업스트림 핸드셰이크가 성공하여 스트림 전송이 시작된 시도는 확정되며, 이후 다른 모델로 홉하지 않습니다.
  • 제외 경로: /v1/responses/compact와 count_tokens는 폴백 체인을 실행하지 않습니다.
  • 제공자 간 와이어 변환: 제공자 간 폴백 시에도 요청 페이로드는 표준 형태를 유지한 채 모델 이름만 교체되며, 새로 선택된 백엔드에 맞춘 와이어 형식 변환은 홉 시점이 아니라 디스패치 시점에 수행됩니다.
  • 체인 소진 및 비트리거 오류: 체인의 모든 후보가 소진되었거나 발생한 오류가 트리거 조건에 해당하지 않으면, 마지막 시도에서 발생한 오류를 해당 인그레스의 네이티브 형식(/anthropic/v1/messages는 Anthropic {"type":"error",...} 본문, /v1/responses는 OpenAI {"error":{...}} 본문)으로 반환합니다. 인그레스별 세부 규칙은 모델 폴백을 참고하십시오.

타임아웃 배율과 스트림 중간 복구

  • fallback_timeout_multiplier (1.0–5.0, 기본값 1.5): 각 폴백 홉의 요청 타임아웃에 곱해지는 배율입니다. 1차 시도는 설정에서 결정된 기본 타임아웃(base)을 그대로 사용하고, n번째 홉은 base * multiplier^(n-1)을 적용하되 해당 타임아웃 분류의 timeouts.limits 상한으로 제한(clamp)됩니다. 기준값(base)은 폴백 체인이 없었을 때 해당 시도가 사용했을 타임아웃이므로, 모델별로 넉넉하게 설정한 타임아웃 예산은 폴백 홉에서도 비례하여 유지됩니다.
  • 배율이 적용되지 않는 항목: timeouts.connection, 폴백 다이얼 퍼밋 대기 시간, 단일 홉 내부 재시도, 클라이언트가 이미 스트림을 수신 중인 상태에서 발생하는 스트림 중간 홉에는 적용되지 않습니다.
  • timeouts.streaming_fallback_budget_multiplier와의 관계: timeouts.streaming_fallback_budget_multiplier는 스트리밍 요청의 모든 시도가 공유하는 단일 벽시계(wall-clock) 전체 예산을 결정합니다. 스트리밍 폴백 홉은 먼저 fallback_timeout_multiplier로 스케일된 뒤 남은 공유 벽시계 예산으로 다시 상한이 걸리므로, 두 설정은 함께 적용됩니다.
  • mid_stream_enabled: mid_stream_enabled: true로 설정하면 이를 지원하는 스트리밍 경로에서 스트림 중간 복구를 허용합니다. 출력 버퍼링이 발생할 수 있으므로, 스트림 시작 후 복구보다 즉각적인 토큰 전달이 더 중요하다면 비활성화하십시오.

시간 초과와 요청 크기

timeouts 섹션에서 라우터 및 모델 기한을 설정하고, 문서화된 서버/백엔드 섹션에서 요청 크기 제한을 설정하십시오. 재시도 타임아웃을 호출자의 전체 기한보다 길게 잡으면 호출자가 연결을 끊은 뒤에도 재시도 작업이 계속될 수 있으므로 피해야 합니다.

생성된 기본 설정 파일을 각 필드의 기준 자료로 활용하십시오.

continuum-router --generate-config > config.yaml
continuum-router config validate config.yaml

관측 및 진단

문서에 없는 디버그 엔드포인트 대신 공식 구현된 인터페이스를 사용하십시오.

# 공개 프로세스/백엔드 상태
curl http://localhost:8080/health

# Admin 상태와 서킷 상태 (설정한 Admin 자격 증명 추가)
curl http://localhost:8080/admin/health
curl http://localhost:8080/admin/circuit/all

# 서버를 시작하지 않고 설정 검증
continuum-router config validate /etc/continuum-router/config.yaml

metrics 기능과 런타임 메트릭 설정이 활성화되면 설정된 메트릭 경로에서 Prometheus 메트릭을 제공합니다. 로그는 tracing을 사용하며, logging.level, CONTINUUM_LOG_LEVEL, 또는 RUST_LOG로 로그 상세도를 조정할 수 있습니다.

백엔드 거절 로그의 빈도 제한

요청한 모델을 서빙할 수 있는 백엔드가 하나도 없을 때(모든 백엔드가 비정상이거나 백엔드별 속도 제한 한도에 도달한 경우), 반복되는 거절 로그는 로그 지점(site)과 모델 단위로 빈도가 제한됩니다:

  • 최초 거절: 각 로그 지점에서 처음 발생한 거절은 전체 상세 정보와 함께 WARN 수준으로 기록됩니다.
  • 60초 억제 구간: 이후 60초 동안 동일한 (site, model) 조합에서 반복되는 거절은 DEBUG 수준으로만 기록됩니다. 따라서 클라이언트가 짧은 간격으로 재시도하더라도 WARN 로그는 로그 지점과 모델당 분당 최대 1줄만 추가됩니다. 모든 개별 거절 로그를 확인하려면 RUST_LOG=info,continuum_router=debug를 설정하십시오.
  • 구간 종료 후 요약 로그: 억제된 반복이 있는 60초 구간이 끝나면 500 requests refused for model 'gpt-4o' in the last 61s: all backends unhealthy와 같은 WARN 요약 로그 1줄이 기록되며, 여기에는 site, model, reason, refused, window_secs 필드가 포함됩니다. 구간이 지난 뒤 처음 발생하는 거절이 이 줄을 남기고 다음 거절이 오지 않으면 30초마다 실행되는 백그라운드 플러시가 남기므로, 억제 구간 도중에 요청 버스트가 멈춰도 마지막 누적 횟수가 구간 종료 후 최대 30초 안에 기록됩니다. 둘 중 먼저 실행된 쪽만 요약을 남기므로, 같은 요약이 두 번 기록되지 않습니다. 억제된 반복이 없는 구간은 요약을 남기지 않고 유휴 상태의 라우터는 아무것도 기록하지 않습니다. 어느 경우든 Prometheus 메트릭에는 거절된 모든 요청이 집계됩니다.
  • 알림 설정 시 유의 사항 (WARN 대 ERROR): 백엔드 거절 로그는 ERROR가 아니라 WARN 수준으로 기록됩니다. 기존에 ERROR 수준의 Router error: All backends are unhealthy, Exhausted all retry attempts, No healthy backend found 문자열을 감지하던 알림 규칙은 WARN 수준을 대상으로 변경하거나 거절 메트릭을 기준으로 전환해야 합니다.

라우터는 /admin/errors/* 또는 /admin/debug/error 엔드포인트를 제공하지 않습니다. 대신 구조화 로그, 메트릭, /admin/health, /admin/circuit/*를 확인하십시오.

클라이언트 권장 사항

  1. 예상되는 라우터/백엔드 타임아웃보다 여유 있게 클라이언트 측 기한(deadline)을 설정합니다.
  2. 애플리케이션에 중복 제거 로직이 없다면 멱등(idempotent) 작업만 재시도합니다.
  3. 지터가 포함된 지수 백오프를 사용하고 Retry-After 헤더를 준수합니다.
  4. 인증, 권한, 유효성 검증, 할당량 소진 오류는 요청 내용이나 자격 증명을 수정하기 전에 재시도하지 않습니다.
  5. 클라이언트 자체 로그에 상관관계/요청 식별자를 기록하고, API 키나 평문으로 해석된 설정 출력은 로그에 남기지 않습니다.