Qwen API에는 흔히 쓰이는 두 가지 호출 방식이 있습니다. 알리바바 클라우드 바이리안의 네이티브 DashScope 인터페이스와 OpenAI 호환 인터페이스입니다. 두 방식의 오류 구조는 완전히 같지 않지만 확인 순서는 동일합니다. 먼저 HTTP 상태 코드를 보고, 응답 본문의 code와 message를 읽고, request_id를 찾은 뒤, 최소 요청으로 재현하고, 마지막에야 파라미터를 바꾸거나 모델을 교체합니다.
이 글은 401, 404, 429, 400, 5xx와 시간 초과로 나누어 오류 코드 대조표, 로그 필드, 재시도 코드, 지원팀 문의 전 체크리스트를 제공합니다. 모델, 할당량, 과금은 공식 문서와 사이트의 모델 가격을 기준으로 하십시오.
안내: Qwen, Tongyi Qianwen, 알리바바 클라우드, 바이리안, DashScope는 알리바바 그룹 및 관련 권리자의 상표입니다. 본 문서는 기술적 문제 해결 논의일 뿐이며 해당 회사와 소속, 대리 또는 협력 관계가 없습니다. 오류 코드, 속도 제한 기준, 인터페이스 동작은 버전에 따라 조정될 수 있으므로 공식 문서와 실제 응답을 최종 기준으로 삼으십시오.
1. 먼저 인터페이스 모드와 오류 구조를 구분하기
네이티브 DashScope 인터페이스는 보통 응답 최상위에 code, message, request_id를 반환합니다. OpenAI 호환 인터페이스는 OpenAI 스타일을 따르며 오류 본문이 {"error": {"message": "...", "type": "...", "code": "..."}} 형태입니다. 호환 모드의 request_id는 응답 헤더나 본문에 나타날 수 있으므로 필드명은 실제 반환값을 기준으로 하십시오.
엔지니어링에서는 한 가지 구조만 파싱하지 마십시오. 먼저 전체 응답 본문을 원문 그대로 로그에 남기고, error.code, 최상위 code 순서로 값을 읽습니다. 이렇게 하면 인터페이스 모드를 전환할 때 오류 처리 코드를 다시 작성할 필요가 없습니다.
2. 401: 인증 실패, 먼저 리전 확인
401은 신원 검증에 실패했다는 뜻이며 모델 성능이나 잔액과는 보통 무관합니다. 바이리안에서 흔한 세 가지 오류는 다음과 같습니다.
InvalidApiKey또는invalid_api_key: Key가 틀렸거나 복사가 불완전하거나, Base URL과 Key가 속한 리전이 일치하지 않는 경우입니다.invalid access token or token expired: 토큰이 만료되었거나, 요금제 전용 주소에 일반 Key를 설정한 경우입니다.NOT AUTHORIZED: WorkspaceId가 유효하지 않거나 계정이 해당 워크스페이스에 속하지 않는 경우입니다.
Qwen의 API Key는 리전에 묶여 있어 베이징과 싱가포르 Key를 섞어 쓸 수 없습니다. 가장 놓치기 쉬운 부분입니다. 코드는 그대로 두고 Base URL만 바꾸면 401이 안정적으로 재현됩니다.
확인은 다음 순서로 진행합니다.
- 요청 헤더가
Authorization: Bearer <key>인지, 콜론 뒤에 공백이 하나 있는지 확인합니다. - 환경 변수 접두사를 출력해 빈 값, 오래된 값, 줄바꿈 포함 여부를 확인합니다.
- Base URL과 Key의 소속 리전이 일치하는지 대조합니다.
- Key 유형과 인터페이스가 맞는지 확인합니다. 종량제, Coding 요금제, Token 요금제의 주소가 다릅니다.
- 새 Key로 최소 요청을 보내 Key 문제와 프록시, 게이트웨이 문제를 구분합니다.
3. 404와 400: 모델명, 개통 상태와 컨텍스트
404는 대부분 서비스 장애가 아니라 모델명이나 사용 가능성 문제입니다. ModelNotFound 또는 model_not_found는 세 가지를 먼저 확인해야 합니다. 모델명 철자가 맞는지, 모델이 개통되었는지, 해당 모델이 현재 리전에서 사용 가능한지입니다. model_not_supported는 모델이 OpenAI 호환 모드를 지원하지 않는다는 뜻이므로 인터페이스나 모델을 바꿔야 합니다.
400의 원인은 더 다양합니다. InvalidParameter는 파라미터 범위 초과이며 예를 들어 temperature나 top_p가 범위를 벗어난 경우입니다. InvalidParameter.NotSupportEnableThinking은 모델이 사고 모드 파라미터를 지원하지 않는다는 뜻입니다. 컨텍스트 초과는 보통 Range of input length should be [1, xxx]를 반환하며 하위 코드는 InternalError.Algo.InvalidParameter이고, 전체 메시지 길이가 모델 상한을 넘은 경우일 수도 있습니다. 처리 방식은 이력을 잘라내거나 청크로 요약하거나 장문 컨텍스트 모델로 바꾸는 것이지, 반복 재시도가 아닙니다.
4. 429: 네 가지 속도 제한과 두 가지 할당량을 구분하기
429는 가장 잘못 처리되기 쉬운 오류입니다. 바이리안의 429에는 최소한 다음 유형이 포함되며, 표에는 속도 제한과 혼동하기 쉬운 연체 및 미개통 오류도 함께 정리했습니다.
| HTTP | 오류 코드 | 의미 | 재시도 적합 여부 |
|---|---|---|---|
| 429 | Throttling.RateQuota, LimitRequests |
분당 요청 수(RPM) 초과 | 예, 백오프 후 재시도 |
| 429 | Throttling.AllocationQuota, insufficient_quota |
분당 토큰 수(TPM) 초과 | 예, 백오프하고 입력 축소 |
| 429 | Throttling.BurstRate |
단시간 트래픽 급증으로 보호 발동 | 예, 전송 속도 평활화 |
| 429 | Throttling.Concurrency |
동시 요청 과다 | 예, 동시성 축소 |
| 429 | AllocationQuota.FreeTierOnly |
무료 할당량 소진 및 무료 전용 스위치 켜짐 | 아니요, 충전 또는 스위치 해제 |
| 429 | BudgetLimitExceeded |
계정 예산 소진 | 아니요, 예산 조정 |
| 400 | Arrearage |
계정 연체 또는 상태 이상 | 아니요, 먼저 계정 복구 |
| 403 | AccessDenied.Unpurchased |
서비스 미개통 | 아니요, 먼저 개통 |
속도 제한은 알리바바 클라우드 주 계정 기준으로 집계되며 RAM 하위 계정, 워크스페이스, 여러 Key의 호출이 합산됩니다. 따라서 "서비스마다 Key 하나"로는 총 할당량을 우회할 수 없고 서로의 점유만 피할 수 있습니다. 속도 제한은 보통 분 단위로 회복되므로 대기 중에 고빈도 재시도를 하지 마십시오.
5. 시간 초과와 5xx: 세 구간으로 나누어 진단
시간 초과는 세 구간으로 나눠 봅니다. 연결 시간 초과는 보통 DNS, 출구 또는 프록시에 도달할 수 없는 경우입니다. 첫 토큰 지연은 대부분 업스트림 대기열이나 입력 과다입니다. 읽기 시간 초과는 출력이 너무 길거나 추론 시간이 긴 경우일 수 있습니다. 세 가지 처리 방식이 다르므로 타임아웃 값만 늘려서는 안 됩니다.
바이리안의 RequestTimeOut은 요청 시간 초과를 뜻하며 공식 설명의 요청 제한 시간은 300초입니다. GatewayTimeout.InputDownload는 입력 파일 다운로드와 관련됩니다. OpenAI 호환 인터페이스는 엔진 과부하를 뜻하는 503을 반환할 수도 있습니다. 스트리밍 요청이 중단되면 오류 코드가 없을 수 있으므로, 수신한 내용, 마지막 데이터 조각, 종료 이유를 먼저 기록한 뒤 네트워크 변동, 프록시 단절, 업스트림 시간 초과를 판단하십시오.
6. request_id와 로그: 문제 해결의 증거
공식 설명에 따르면 request_id는 응답과 함께 반환되며 응답 헤더나 본문에 있을 수 있습니다. 엔지니어링에서는 이중으로 기록하는 것이 좋습니다. 클라이언트가 생성한 trace_id를 모든 재시도에 유지하고, 동시에 응답 헤더의 요청 식별자와 본문의 request_id를 원문 그대로 저장합니다. 기록이 없다면 콘솔의 모델 모니터링 로그에서 찾을 수 있으며, 추론 로그가 활성화되어 있어야 합니다.
로그에는 최소한 시간, trace_id, Base URL, 리전, 모델명, HTTP 상태 코드, 오류 코드, 오류 메시지, request_id, 소요 시간, 재시도 횟수, 스트리밍 여부를 포함합니다. Key는 앞 몇 자리나 해시만 기록하고, 사용자 원문과 전체 응답을 통째로 저장하지 마십시오. 지원팀에 문의할 때 이런 필드가 담긴 기록 한 건이 "가끔 오류가 납니다"보다 훨씬 유용합니다.
7. 재시도와 백오프: 예제 코드
아래 코드는 SDK 자동 재시도를 끄고 복구 가능한 429와 5xx에만 재시도하며 상태 코드, 오류 코드, request_id를 함께 출력합니다. 연체, 무료 할당량 소진, 예산 초과는 재시도 범위에 포함되지 않습니다.
import os
import random
import time
import uuid
import httpx
from openai import OpenAI, APIConnectionError, APIStatusError, APITimeoutError
client = OpenAI(
api_key=os.environ["DASHSCOPE_API_KEY"],
base_url=os.environ.get(
"DASHSCOPE_BASE_URL",
"https://dashscope.aliyuncs.com/compatible-mode/v1",
),
timeout=httpx.Timeout(connect=5.0, read=180.0, write=30.0, pool=10.0),
max_retries=0, # SDK 자동 재시도를 끄고 아래 로직에서 일괄 제어
)
RETRYABLE_429 = {
"Throttling",
"Throttling.RateQuota",
"Throttling.AllocationQuota",
"Throttling.BurstRate",
"Throttling.Concurrency",
"LimitRequests",
"limit_requests",
"ResourceExhausted",
}
def unpack_error(exc: APIStatusError):
body = {}
try:
body = exc.response.json()
except Exception:
pass
err = body.get("error", body) if isinstance(body, dict) else {}
code = err.get("code") if isinstance(err, dict) else None
request_id = None
if isinstance(body, dict):
request_id = body.get("request_id")
request_id = (
request_id
or exc.response.headers.get("x-request-id")
or exc.response.headers.get("x-dashscope-request-id")
)
return code, request_id, exc.response.text[:500]
def ask(prompt: str, model: 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=model,
messages=[{"role": "user", "content": prompt}],
)
return resp.choices[0].message.content
except APIStatusError as exc:
code, request_id, body = unpack_error(exc)
print(
f"trace={trace_id} attempt={attempt} "
f"http={exc.status_code} code={code} "
f"request_id={request_id} body={body}"
)
retryable = exc.status_code >= 500 or (
exc.status_code == 429 and code in RETRYABLE_429
)
if not retryable or attempt == max_attempts:
raise
except (APITimeoutError, APIConnectionError) as exc:
print(f"trace={trace_id} attempt={attempt} err={type(exc).__name__}")
if attempt == max_attempts:
raise
wait = min(2 ** (attempt - 1), 30) + random.uniform(0, 1)
time.sleep(wait)
raise RuntimeError("재시도 횟수를 모두 사용했습니다")
세 가지 규칙을 보충합니다. 서버가 Retry-After를 반환하면 우선 따르고, 재시도 예산은 전역으로 통제해 여러 서비스가 동시에 트래픽을 증폭하지 않게 하며, 부작용이 있는 요청은 이미 실행되었는지 먼저 확인한 뒤 재전송 여부를 결정합니다.
8. 동시성 관리: 재시도 횟수를 늘리는 것보다 효과적
속도 제한 문제는 보통 재시도가 아니라 진입점 관리로 해결합니다.
- 업무별로 Key를 나누고 각각 동시성 상한을 설정해 하나의 배치 작업이 온라인 서비스를 마비시키지 않게 합니다.
- 배치 작업을 분할하고 시간대를 분산해 정각 트래픽 폭주를 피합니다.
- 대기열로 피크를 깎고 실패 작업은 지연 대기열로 보내 제자리에서 반복하지 않게 합니다.
- 429 비율과 p95 지연을 모니터링하고 급증하면 먼저 동시성을 낮춥니다.
- 오프라인 작업은 Batch 인터페이스를 우선 사용해 실시간 속도 제한을 피합니다.
- 대체 모델이나 대체 진입점을 준비하고 전환 전에 파라미터 호환성을 확인합니다.
속도 제한 임계값은 모델과 계정별로 설정되며 충전해도 RPM이나 TPM이 자동으로 올라가지 않습니다. 더 높은 할당량이 필요하면 상업 신청 절차를 밟고 다른 곳의 수치를 그대로 복사하지 마십시오.
9. 지원팀 문의 전 체크리스트
- 발생 시간, 시간대, 장애가 집중적으로 폭발했는지 여부.
- Base URL, 리전, 인터페이스 경로, 모델명, 요청 방식.
- HTTP 상태 코드, 오류 코드, 전체 오류 메시지.
request_id. 없으면 직접 생성한trace_id를 제공합니다.- 재현 가능한 최소 curl 요청 한 건. Key는 마스킹합니다.
- 스트리밍 여부, 장문 컨텍스트 여부, 최근 설정 변경 여부.
- 영향 범위: 단일 Key, 단일 워크스페이스, 전체 서비스 중 어디까지인지.
10. 자주 묻는 질문 (FAQ)
1. 429는 항상 속도 제한인가요?
아닙니다. 429는 무료 할당량 소진이나 예산 초과일 수도 있습니다. 연체는 400의 Arrearage, 서비스 미개통은 403으로 반환될 수 있습니다. 먼저 오류 코드를 읽고 백오프, 충전, 설정 변경 중 무엇이 필요한지 결정하십시오.
2. 401에서 새 Key로 바꿔도 계속 실패하면 어떻게 하나요?
리전과 Key 유형을 중점적으로 확인하십시오. Qwen Key는 리전에 묶여 있어 베이징 Key에 싱가포르 주소를 쓰면 401이 반환됩니다. 요금제 전용 주소에 일반 Key를 써도 실패합니다. 이어서 요청 헤더, 환경 변수, 리버스 프록시가 인증 정보를 버리는지 확인하십시오.
3. 컨텍스트 초과는 어떻게 처리하나요?
먼저 이전 메시지를 잘라내고 무관한 첨부와 중복 단락을 제거합니다. 다음으로 긴 문서를 청크로 요약하거나 검색을 적용합니다. 그래도 부족하면 장문 컨텍스트 모델로 바꿉니다. max_tokens를 높여 입력 초과를 해결하려 하지 마십시오.
4. 시간 초과는 재시도해야 하나요?
연결 실패와 읽기 시간 초과에만 제한된 횟수로 재시도하고 지수 백오프와 상한을 설정합니다. 부작용이 있는 업무는 실행 여부를 먼저 확인해 중복 주문이나 중복 쓰기를 피하십시오.
5. request_id는 어디에서 찾나요?
먼저 응답 헤더와 본문을 확인하고 필드명은 실제 인터페이스를 기준으로 합니다. 둘 다 기록되지 않았다면 콘솔의 모델 모니터링 로그에서 찾으며, 추론 로그가 활성화되어 있어야 합니다. 지원팀에는 trace_id와 최소 재현 요청을 함께 제공하십시오.
다음 단계
먼저 ZSQH AI Gateway에서 테스트 Key를 만들고 401, 404, 429, 컨텍스트 초과, 시간 초과를 각각 한 번씩 리허설한 뒤 운영 모니터링에 연결하십시오. 사용 가능한 모델과 과금 방식은 모델 가격에서 확인할 수 있습니다.
