Doubao API는 Volcano Ark가 제공하며 인터페이스 형태는 OpenAI와 비슷하지만 오류 의미에는 자체적인 특징이 있습니다. 가장 대표적인 것은 404입니다. 서비스가 죽은 것이 아니라 추론 엔드포인트 ID, 모델명, 리전 또는 경로가 문제인 경우가 많습니다. 429도 자주 오판됩니다. 속도 제한, 동시성, 할당량, 연체, 리소스 패키지 소진 중 어느 것인지에 따라 처리 방식이 완전히 다릅니다.

확인 순서는 다섯 단계로 고정하는 것이 좋습니다. HTTP 상태 코드를 보고, 응답 본문의 code와 message를 읽고, 요청 ID를 얻고, 최소 요청으로 재현하고, 마지막에야 파라미터를 바꾸거나 모델을 교체합니다. 이 글은 401, 404, 400, 429, 시간 초과, 5xx로 나누어 오류 처리 코드와 로그 체크리스트를 제공합니다. 모델과 과금은 모델 가격을 기준으로 하십시오.

안내: Doubao, Volcano Ark, Volcano Engine은 바이트댄스의 상표입니다. 본 문서는 기술 설명 목적이며 바이트댄스와 소속, 대리 또는 협력 관계가 없습니다. 오류 코드, 속도 제한 기준, 인터페이스 동작은 버전에 따라 조정될 수 있으므로 공식 문서와 실제 응답을 최종 기준으로 하십시오.

1. 먼저 두 계층의 오류를 구분하기

HTTP 상태 코드는 프로토콜 계층의 결과를 설명하고, 응답 본문의 code, type, message는 구체적인 원인을 설명합니다. 같은 401이라도 Key를 빠뜨렸거나, Key가 틀렸거나, 만료되었거나, 프로젝트 권한이 없을 수 있습니다. 같은 429라도 요청이 너무 빠르거나, 동시성이 높거나, 할당량이 소진되었거나, 연체일 수 있습니다. 상태 코드만 기록하면 문제 해결은 추측에 불과합니다.

HTTP 일반적 의미 Doubao 장면 재시도 여부
401 인증 실패 Key 누락, 형식 오류, 만료 또는 리전 불일치 아니요, 자격 증명 먼저 수정
403 권한 없음 Key에 해당 모델 또는 엔드포인트 권한 없음 아니요, 권한 확인
404 리소스 없음 엔드포인트 ID, 모델명, 경로 또는 리전 오류 아니요, 먼저 대조
400 파라미터 오류 컨텍스트 초과, max_tokens 잘못됨, 이미지 형식 미지원 아니요, 요청 수정
429 속도 제한 또는 할당량 RPM, TPM, 동시성 초과, 연체 또는 리소스 패키지 소진 원인에 따라 다름
500/502/503 서버 오류 업스트림 이상, 게이트웨이 변동, 일시적 불가 예, 백오프 재시도
시간 초과 연결 또는 읽기 시간 초과 네트워크, 대기열, 입력 과다, 출력 과다 제한적 재시도

필드명은 실제 반환값을 기준으로 합니다. 어떤 것은 error.code에 있고 어떤 것은 최상위에 있으므로 코드가 한 가지 구조만 가정해서는 안 됩니다.

2. 401: 인증 실패의 네 가지 원인

401은 모델 성능이나 잔액과 무관하므로 자격 증명 경로를 먼저 확인합니다.

  • 요청 헤더가 Authorization: Bearer <key>인지, 콜론 뒤에 공백이 하나 있는지.
  • Key가 환경 변수에서 올바르게 로드되었는지, 앞뒤에 따옴표, 공백, 줄바꿈이 없는지.
  • Key가 삭제, 교체 또는 유효 기간 만료되지 않았는지.
  • Key가 속한 프로젝트, 리전, Base URL이 일치하는지.

채팅 창에서 복사한 Key에는 보이지 않는 문자가 섞이기 쉽습니다. 한 단어만 출력하는 최소 요청을 먼저 보내면 대부분의 저급 오류를 걸러낼 수 있습니다. 같은 Key가 콘솔에서는 되는데 코드에서 401이 나오면 환경 변수와 리버스 프록시가 인증 헤더를 버리는지 확인하십시오.

3. 404: 엔드포인트 ID와 모델명이 가장 혼동되기 쉬움

Volcano Ark에서 흔한 모델 지정 방식은 두 가지입니다. 콘솔에서 만든 추론 엔드포인트로 보통 ep-로 시작하며, 일부 모델이 지원하는 모델 ID 직접 호출입니다. 둘 다 model 필드에 넣지만 권한, 속도 제한, 적용 범위가 다를 수 있습니다. 엔드포인트 ID와 모델명을 섞거나 다른 프로젝트의 엔드포인트를 복사하면 404 또는 이와 유사한 "리소스 없음"이 반환됩니다.

확인 순서는 다음과 같습니다.

  1. Base URL이 https://ark.cn-beijing.volces.com/api/v3 같은 리전 주소인지, /api/v3를 더 쓰거나 빠뜨리지 않았는지 확인합니다.
  2. 콘솔에서 엔드포인트가 존재하고 현재 프로젝트에 속하며 중지되지 않았는지 대조합니다.
  3. 현재 호출 방식이 엔드포인트 ID를 요구하는지 모델 ID를 요구하는지 확인하고 기억에 의존해 섞지 않습니다.
  4. curl로 최소 요청을 보내 SDK의 자동 경로 결합과 프록시 재작성의 영향을 배제합니다.
  5. 응답 콘텐츠 유형을 봅니다. HTML 오류 페이지가 오면 요청이 Ark에 도달하지 못하고 게이트웨이나 리버스 프록시에 막혔을 수 있습니다.

404에 HTML이 붙으면 보통 경로나 프록시 문제이고, JSON 오류 본문이 붙어야 모델이나 엔드포인트 문제에 가깝습니다.

4. 400: 컨텍스트 초과와 파라미터 오류

400의 범위는 넓고 가장 흔한 것은 컨텍스트 초과입니다. 컨텍스트 창은 보통 입력 토큰과 출력 토큰의 합으로 계산하며 max_tokens가 너무 크면 원래 통과하던 요청이 경계를 넘습니다. 이미지, 도구 정의, 이전 메시지도 토큰을 차지합니다.

해결 방식은 상한을 높이는 것이 아니라 입력에 여유를 두는 것입니다.

  • 시스템 프롬프트를 압축하거나 분할하고 중복 규칙을 삭제합니다.
  • 이전 대화를 요약하고 최근 몇 턴과 핵심 사실만 남깁니다.
  • 긴 문서는 먼저 검색한 뒤 결합하고 전문을 통째로 넣지 않습니다.
  • 이미지는 먼저 압축하고 형식과 크기가 지원 범위인지 확인합니다.
  • temperature, top_p가 범위를 벗어나지 않았는지, 메시지 역할이 유효한지 확인합니다.

토큰 계산은 추정값이므로 상한에 가까울 때는 10~20%의 여유를 두는 것이 안정적입니다.

5. 429: 속도 제한, 동시성, 할당량을 분리해 처리하기

429에는 네 가지 원인이 있습니다.

  • 속도 제한: 분당 요청 수 또는 토큰 수가 요금제 상한을 초과했으며 백오프 후 보통 회복됩니다.
  • 동시성 제한: 동시에 진행 중인 요청이 너무 많아 동시성을 낮추거나 대기열에 넣어야 하며, 단순 재시도는 혼잡을 악화시킵니다.
  • 할당량 또는 리소스 패키지 소진: 재시도는 의미가 없고 충전이나 요금제 조정이 필요합니다.
  • 계정 상태 이상: 연체, 미실명, 권한 변경이며 콘솔 안내에 따라 처리합니다.

응답에 Retry-After가 있으면 그 값만큼 기다리고, 없으면 지수 백오프에 무작위 지터를 더합니다. 할당량 유형과 연체 유형의 429는 재시도하지 마십시오. 장애 시간만 늘어납니다.

스트리밍 429는 연결 수립 시 발생할 수도 있고 스트림 내부 오류 이벤트로 나타날 수도 있습니다. 클라이언트는 상태 코드와 스트림 내부 오류를 동시에 처리해야 하며 첫 조각만 보고 판단해서는 안 됩니다.

6. 시간 초과와 스트림 중단: 세 구간으로 나누어 진단

시간 초과는 세 구간으로 나눕니다.

  • 연결 시간 초과: DNS, TLS, 출구 프록시 또는 네트워크 도달 불가이며 보통 몇 초 안에 실패합니다.
  • 첫 토큰 시간 초과: 요청은 업스트림에 도달했지만 대기열에 있거나 긴 입력을 처리 중이라 첫 조각이 오랫동안 오지 않습니다.
  • 읽기 시간 초과: 두 조각 사이 간격이 너무 길며 출력이 길거나 추론 시간이 길거나 중간 장비가 연결을 끊은 경우입니다.

세 구간에는 서로 다른 타임아웃 설정이 필요합니다. 전체 타임아웃이 너무 크면 장애 요청이 연결 풀을 오래 점유하고, 너무 작으면 정상적인 긴 출력을 잘못 끊습니다. 연결 타임아웃은 5초 전후, 읽기 타임아웃은 업무 P95에 맞추고 애플리케이션 계층에서 총 예산을 두는 것이 좋습니다.

스트리밍 장면에서는 리버스 프록시 버퍼링도 확인해야 합니다. 버퍼링은 조각 전송을 지연시켜 "멈춘 것처럼" 보이게 합니다. 스트림이 끊긴 뒤 반쪽 내용을 완전한 결과로 저장하지 마십시오.

7. 요청 ID와 로그: 문제 해결의 증거

Volcano Ark 응답에는 보통 요청 식별자가 있으며 흔한 필드는 X-Request-Id이고 일부 인터페이스는 X-Tt-Logid 같은 로그 ID를 반환할 수 있습니다. 필드명은 실제 응답을 기준으로 하며 하나만 고르지 말고 응답 헤더 전체를 보존해야 합니다.

클라이언트도 자체 trace_id를 생성하고 같은 비즈니스 요청의 모든 재시도에 동일하게 유지해야 합니다. 로그에는 최소한 시간, trace_id, Base URL, 모델 또는 엔드포인트, 상태 코드, 오류 code와 message, 소요 시간, 재시도 횟수, 스트리밍 여부, 토큰 사용량을 기록합니다.

Key는 앞 몇 자리나 해시만 기록하고 사용자 원문과 전체 응답을 통째로 저장하지 마십시오. 오류 응답 본문은 앞 500자만 보관하면 충분합니다.

8. 재시도와 백오프: 바로 쓸 수 있는 코드

아래 코드는 SDK 자동 재시도를 끄고 상태 코드와 오류 내용으로 재시도 여부를 판단하며 Retry-After를 존중하고 논리 요청마다 trace_id를 생성합니다.

import os
import random
import time
import uuid

import httpx
from openai import OpenAI, APIConnectionError, APIStatusError, APITimeoutError

client = OpenAI(
    api_key=os.environ["ARK_API_KEY"],
    base_url=os.environ.get(
        "ARK_BASE_URL", "https://ark.cn-beijing.volces.com/api/v3"
    ),
    timeout=httpx.Timeout(connect=5.0, read=180.0, write=30.0, pool=10.0),
    max_retries=0,  # SDK 자동 재시도를 끄고 아래 로직에서 일괄 제어
)

def parse_error(exc: APIStatusError) -> tuple[str, str]:
    try:
        body = exc.response.json()
    except Exception:
        return "", exc.response.text[:200]
    err = body.get("error", body)
    return str(err.get("code") or err.get("type") or ""), str(err.get("message") or "")

def is_retryable(exc: Exception) -> bool:
    if isinstance(exc, (APITimeoutError, APIConnectionError)):
        return True
    if not isinstance(exc, APIStatusError):
        return False
    if exc.status_code >= 500:
        return True
    if exc.status_code != 429:
        return False
    code, message = parse_error(exc)
    text = f"{code} {message}".lower()
    # 할당량, 연체, 권한 유형의 429는 재시도가 무의미합니다. 실제 문구에 맞춰 키워드를 보강하십시오.
    return not any(k in text for k in ("quota", "arrears", "balance", "permission"))

def ask(prompt: str, max_attempts: int = 4) -> str:
    trace_id = uuid.uuid4().hex[:12]
    for attempt in range(1, max_attempts + 1):
        try:
            resp = client.chat.completions.create(
                model=os.environ["ARK_MODEL"],  # 엔드포인트 ID 또는 모델 ID
                messages=[{"role": "user", "content": prompt}],
                max_tokens=1024,
            )
            return resp.choices[0].message.content
        except Exception as exc:
            status = getattr(exc, "status_code", "")
            code, message = parse_error(exc) if isinstance(exc, APIStatusError) else ("", "")
            print(
                f"trace={trace_id} attempt={attempt} status={status} "
                f"code={code} message={message[:200]}"
            )
            if attempt == max_attempts or not is_retryable(exc):
                raise
            retry_after = getattr(
                getattr(exc, "response", None), "headers", {}
            ).get("retry-after")
            try:
                wait = float(retry_after) if retry_after else min(2 ** (attempt - 1), 20)
            except ValueError:
                wait = min(2 ** (attempt - 1), 20)
            time.sleep(wait + random.uniform(0, 1))
    raise RuntimeError("재시도 횟수를 모두 사용했습니다")

세 가지 규칙을 보충합니다. 부작용이 있는 요청은 멱등 키로 상태를 먼저 확인하고, 재시도 예산은 전역으로 통제하며, 429 비율과 P95 지연을 모니터링하다가 급증하면 먼저 동시성을 낮추십시오.

9. 지원팀 문의 전 체크리스트

  1. 장애 시간과 시간대, 지속인지 간헐인지.
  2. Base URL, 전체 경로, 모델명 또는 엔드포인트 ID.
  3. HTTP 상태 코드, 오류 code, message, 요청 ID.
  4. 재현 가능한 최소 curl 요청 한 건, Key는 마스킹.
  5. 스트리밍 여부, 장문 컨텍스트, 이미지 또는 도구 호출 포함 여부.
  6. 최근 Key, 엔드포인트, 할당량, 프록시 변경 여부.
  7. 영향 범위: 단일 Key, 단일 프로젝트, 단일 리전, 전체 서비스 중 어디까지인지.

이 정보를 준비해야 지원팀이 업스트림 로그와 바로 대조할 수 있습니다. 장애가 끝날 때마다 모니터링이나 경보를 하나씩 보강하십시오.

10. 자주 묻는 질문 (FAQ)

1. 404는 항상 모델이 종료되었다는 뜻인가요?

아닙니다. 더 흔한 원인은 엔드포인트 ID나 모델명 오류, 엔드포인트 삭제, 리전 불일치, 경로에서 /api/v3 누락입니다. 응답이 JSON인지 HTML 오류 페이지인지 먼저 보고 항목별로 대조하십시오.

2. 429는 몇 번 재시도해야 하나요?

속도 제한과 일시적 혼잡에는 2~3회 지수 백오프 재시도가 적절합니다. 할당량 소진, 연체, 권한 부족으로 인한 429는 재시도하지 말고 계정이나 요금제를 먼저 처리하십시오. Retry-After를 존중하십시오.

3. 컨텍스트 길이는 어떻게 계산되나요?

보통 입력 토큰과 출력 토큰의 합으로 계산하며 max_tokens도 총량을 차지합니다. 이미지, 도구 정의, 이전 메시지가 모두 입력에 포함되므로 상한에 가까울 때는 여유를 두십시오.

4. 스트리밍 요청의 시간 초과는 어떻게 설정하나요?

읽기 타임아웃은 보통 두 조각 사이의 간격을 제한하며 전체 시간이 아닙니다. 연결 타임아웃은 따로 설정하고 애플리케이션 계층에서 총 예산과 유휴 타임아웃을 둡니다. 프록시 버퍼링은 조각 전송을 지연시킵니다.

5. 게이트웨이를 붙인 뒤 오류가 늘면 어떻게 하나요?

오류가 게이트웨이에서 온 것인지 업스트림에서 온 것인지 먼저 확인합니다. 응답 본문 형식, 상태 코드, 요청 ID를 보고 trace ID를 보존한 채 직결과 게이트웨이로 각각 최소 요청을 보내 비교하십시오. 대부분 경로 결합, 인증 헤더 전달, 시간 초과 문제입니다.

다음 단계

먼저 ZSQH AI Gateway에서 테스트 Key를 만들고 401, 404, 429, 시간 초과를 각각 한 번씩 리허설한 뒤 운영 모니터링에 연결하십시오. 사용 가능한 모델과 과금 방식은 모델 가격에서 확인할 수 있습니다.

관련 글