튜토리얼
hiapi에서 멀티 프로바이더 AI API를 위한 로드 밸런서 구축하기
무엇을 구축하게 되는가
hiapi의 chat completions 엔드포인트 앞에 가벼운 라우팅 계층을 추가하세요: 요청별로 모델을 선택하고, 일시적 장애에 대해 재시도하며, 특정 프로바이더가 다운되거나 속도 제한에 걸렸을 때 다른 프로바이더로 폴백하고, 저렴한 요청을 저렴한 모델로 라우팅하여 비용을 추적합니다. 이는 프로덕션 시스템이 단일 업스트림 프로바이더가 단일 장애점이 되는 것을 피하기 위해 채택하는 것과 동일한 패턴입니다—다만 여기서 '프로바이더'는 '모델 ID'를 의미할 뿐인데, hiapi가 수십 개 프로바이더의 OpenAI 호환 액세스를 동일한 엔드포인트와 동일한 API 키 뒤에 두기 때문입니다.
이 가이드는 텍스트/대화 모델만 다룹니다. hiapi의 이미지, 비디오 및 오디오 모델은 별도의 비동기 작업 큐 API(/v1/tasks)에서 실행되며, 자체적인 폴링/콜백 메커니즘을 가집니다—둘 모두에 걸쳐 라우팅해야 하는 경우 끝부분의 설명을 참조하세요.
사전 요구 사항
- hiapi API 키. 대시보드에서 발급받으세요—아래의 모든 예제는
Authorization: Bearer <key>만 필요로 합니다. - Python 3.9+ 및
requests설치(pip install requests), 또는 먼저 셸에서 테스트하고 싶다면curl을 사용하세요. - SDK는 필요 없습니다—chat completions는 일반적인 OpenAI 호환 REST 엔드포인트이므로 HTTP를 지원하는 모든 도구로 사용할 수 있습니다.
최소 실행 예제
모든 hiapi 텍스트 모델은 동일한 /v1/chat/completions 구조를 따릅니다. 다음은 curl을 사용한 단일 호출입니다:
curl https://api.hiapi.ai/v1/chat/completions \
-H "Authorization: Bearer $HIAPI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "deepseek-v4.1-flash",
"messages": [{"role": "user", "content": "Summarize load balancing in one sentence."}],
"max_tokens": 100
}'
그리고 이에 상응하는 Python 버전으로, 이 가이드의 나머지 부분은 이를 기반으로 합니다:
import os
import requests
HIAPI_BASE = "https://api.hiapi.ai/v1"
API_KEY = os.environ["HIAPI_API_KEY"]
def chat(model: str, messages: list[dict], **kwargs) -> dict:
resp = requests.post(
f"{HIAPI_BASE}/chat/completions",
headers={
"Authorization": f"Bearer {API_KEY}",
"Content-Type": "application/json",
},
json={"model": model, "messages": messages, **kwargs},
timeout=30,
)
resp.raise_for_status()
return resp.json()
result = chat("deepseek-v4.1-flash", [{"role": "user", "content": "Say hi in five words."}])
print(result["choices"][0]["message"]["content"])
이렇게 하면 동작은 하지만 장애 조치가 없습니다: deepseek-v4.1-flash가 속도 제한에 걸리거나 일시적으로 다운되면 요청이 그대로 실패합니다. 이 가이드의 나머지 부분에서는 이 단일 호출을 라우터로 바꿉니다.
라우팅할 수 있는 범위 알아보기
기억에 의존해 모델 목록을 하드코딩하지 마세요—GET /v1/models를 조회하고 supported_endpoint_types로 필터링하세요. chat completions와 호환되는 모델은 "openai"를 보고합니다; hiapi의 이미지/비디오/오디오 모델은 "task"를 보고하며, 이 가이드의 코드와 함께 사용할 수 없습니다:
def list_chat_models() -> list[str]:
resp = requests.get(f"{HIAPI_BASE}/models",
headers={"Authorization": f"Bearer {API_KEY}"}, timeout=15)
resp.raise_for_status()
models = resp.json().get("data", [])
return [m["id"] for m in models if "openai" in (m.get("supported_endpoint_types") or [])]
시작 시 이 목록에서 라우팅 계층을 구축하거나(또는 몇 시간 동안 캐시) 코드에 모델 ID를 고정하지 마세요—프로바이더는 추가, 중단 또는 이름을 변경하며, 오래된 하드코딩 목록을 기반으로 구축된 계층은 조용히 존재하지 않는 모델로 라우팅하는 것으로 퇴화합니다.
프로덕션 패턴 1: 백오프를 이용한 재시도
모든 실패가 재시도할 가치가 있는 것은 아닙니다. model_not_found가 포함된 400은 다음 시도에서도 동일하게 실패합니다—재시도는 지연만 낭비합니다. 429 또는 5xx는 일반적으로 일시적이며 짧은 백오프를 둘 가치가 있습니다:
import time
RETRYABLE_STATUS = {429, 500, 502, 503, 504}
def chat_with_retry(model: str, messages: list[dict], max_attempts: int = 3, **kwargs) -> dict:
last_exc = None
for attempt in range(max_attempts):
try:
resp = requests.post(
f"{HIAPI_BASE}/chat/completions",
headers={"Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json"},
json={"model": model, "messages": messages, **kwargs},
timeout=30,
)
if resp.status_code in RETRYABLE_STATUS:
raise requests.HTTPError(f"retryable {resp.status_code}: {resp.text}", response=resp)
resp.raise_for_status()
return resp.json()
except requests.HTTPError as e:
last_exc = e
if attempt < max_attempts - 1:
time.sleep(2 ** attempt) # 1s, 2s, 4s
raise last_exc
400, 401, 403은 raise_for_status()를 그대로 통과하여 재시도되지 않습니다—잘못된 모델 ID 또는 잘못된 키는 기다린다고 해서 저절로 고쳐지지 않습니다.
프로덕션 패턴 2: 클라이언트 타임아웃
위의 requests.post(..., timeout=30)은 장식이 아닙니다—이것이 없으면 매달린 업스트림 연결이 라우터를 무한정 차단하며, 재시도를 위해 제어권을 다시 얻지 못하므로 아무리 많은 재시도 로직도 무용지물이 됩니다. 타임아웃을 충분히 엄격하게 설정하여 멈춘 요청이 스레드를 계속 점유하는 대신 재시도/장애 조치 경로로 빠르게 실패하도록 하세요. 단일 chat completion의 경우 20–30초가 합리적입니다; 지연에 민감한 요청을 라우팅하고 있고 느린 모델을 기다리는 것보다 더 빠른 모델로 장애 조치하는 것을 선호한다면 더 낮출 수 있습니다.
프로덕션 패턴 3: 비용 인식 계층형 라우팅
모든 요청에 가장 비싼 모델이 필요한 것은 아닙니다. 실용적인 패턴: 계층을 정의하고, 성공할 가능성이 있는 가장 저렴한 계층을 먼저 시도하고, 실패할 때만 상위로 올라갑니다:
TIERS = [
["deepseek-v4.1-flash", "deepseek-v4-flash"], # budget tier
["glm-5.3", "kimi-k3"], # mid tier
["grok-4.6", "claude-opus-4-8"], # premium tier, used last
]
def route(messages: list[dict], **kwargs) -> dict:
last_exc = None
for tier in TIERS:
for model in tier:
try:
return chat_with_retry(model, messages, **kwargs)
except requests.HTTPError as e:
last_exc = e
continue # try next model in this tier, then next tier
raise last_exc
이것은 프리미엄 계층에 비용을 쓰기 전에 예산 계층의 모든 모델을 시도하며, 현재 계층의 모든 모델이 실패한 후에만 한 계층 위로 이동합니다—따라서 단일 프로바이더의 다운은 트래픽을 가장 비싼 모델로 밀어내지 않지만 전체 계층의 다운은 그렇게 합니다. 모델이 어느 계층에 속하는지 결정하기 전에 가격 페이지에서 현재 모델별 가격을 확인하세요—hiapi는 /v1/models 응답에 가격을 노출하지 않으므로, 오래되었을 수 있는 숫자를 하드코딩하는 대신 거기서 확인하세요.
프로덕션 패턴 4: 멱등성과 중복 요청 안전성
/v1/chat/completions는 일반 POST이며 서버 측 멱등성 키가 없습니다—hiapi의 /v1/tasks 작업 API와 달리, 클라이언트 생성 키를 서버에 제공하고 재시도 요청을 중복 제거하도록 할 방법이 없습니다. 이는 요청이 모델이 생성을 완료한 후지만 응답이 도달하기 전에 타임아웃되면, 순진한 재시도가 동일한 생성에 대해 두 번 비용을 지불하게 됨을 의미합니다.
두 가지 실용적 완화책:
- 클라이언트 타임아웃을 충분히 엄격하게 유지하여 '타임아웃'이 거의 항상 '모델에 도달하지 않음'을 의미하도록 하세요, '응답이 전송 중에 손실됨'이 아니라.
- chat 호출의 결과가 다운스트림 쓰기를 구동하는 경우(예: 모델 출력으로 데이터베이스 행을 채우는 경우), 그 쓰기를 멱등성 있게 만드세요—안정적인 키에 대해 insert 대신 upsert를 수행—중복 API 호출이 낭비적이지만 안전하지 않도록.
폴링과 콜백에 관한 설명(이미지/비디오 모델)
위의 모든 내용은 /v1/chat/completions의 텍스트 모델에 적용되며, 이는 동기식입니다—한 번의 HTTP 왕복으로 전체 응답을 얻습니다. hiapi의 이미지, 비디오 및 오디오 모델은 다르게 작동합니다: /v1/tasks에 작업을 제출한 다음 종료 상태를 폴링하거나 콜백 URL을 등록합니다. 라우터가 텍스트와 미디어 모델 모두를 커버해야 하는 경우 두 개의 별도 코드 경로로 취급하세요—미디어 작업 ID는 chat completion을 백오프로 재시도하듯 재시도할 수 있는 대상이 아니며, 이 가이드의 재시도 로직을 /v1/tasks에 직접 적용하면 중복 생성 작업만 생성됩니다.
오류 처리 참조
hiapi의 chat 엔드포인트는 OpenAI 호환 오류 봉투를 반환합니다. 라우터를 구축할 때 가장 자주 마주치는 두 가지:
// 400 — bad model id, don't retry
{
"error": {
"code": "model_not_found",
"message": "The requested model does not exist or is not available. Check the exact model ID with GET /v1/models and try again.",
"type": "hiapi_error"
}
}
// 401 — bad or revoked key, don't retry
{
"error": {
"code": "permission_denied",
"message": "This API key is invalid. Check that it is correct or use another API key and try again.",
"type": "hiapi_error"
}
}
error.type가 아니라 error.code를 기반으로 라우팅하세요—모든 오류는 동일한 "type": "hiapi_error"를 공유하므로, code야말로 '포기'(model_not_found, permission_denied)와 '재시도'(속도 제한 및 서버 오류, 별개의 코드가 아닌 HTTP 상태를 통해 제시됨)를 실제로 구분하는 기준입니다.
모두 합치기
class Router:
def __init__(self, api_key: str, tiers: list[list[str]]):
self.api_key = api_key
self.tiers = tiers
def complete(self, messages: list[dict], **kwargs) -> dict:
last_exc = None
for tier in self.tiers:
for model in tier:
try:
return chat_with_retry(model, messages, **kwargs)
except requests.HTTPError as e:
last_exc = e
raise RuntimeError(f"all models exhausted across {len(self.tiers)} tiers") from last_exc
router = Router(API_KEY, TIERS)
out = router.complete([{"role": "user", "content": "Explain failover in one sentence."}])
print(out["choices"][0]["message"]["content"])
계층에 삽입할 수 있는 각 모델에 대한 심층 소개는 DeepSeek V4.1 Flash, Claude Sonnet 4.6 및 GLM-5.3 API 가이드를 참조하세요.
자주 묻는 질문
hiapi에 내장된 로드 밸런서나 자동 장애 조치가 있나요? 없습니다—hiapi는 여러 모델에 걸쳐 하나의 엔드포인트와 하나의 키를 제공하지만, 라우팅 로직(어떤 모델을, 어떤 순서로, 어떤 재시도 정책으로 사용할지)은 클라이언트의 관심사이며, 이것이 바로 이 가이드가 구현하는 것입니다.
hiapi의 '모델 라우터'와 단일 모델을 직접 호출하는 것의 차이는 무엇인가요? 단일 모델을 직접 호출하면 단일 장애점이 생깁니다: 해당 모델이 속도 제한에 걸리거나 일시적으로 다운되면 요청이 실패합니다. 라우터는 포기하기 전에 대체 모델을 시도합니다—가능하면 서로 다른 업스트림 프로바이더에 걸쳐서.
동일한 방식으로 이미지 또는 비디오 생성 모델에 걸쳐 라우팅할 수 있나요?
이 코드로는 불가능합니다. 이들은 /v1/chat/completions가 아닌 비동기 /v1/tasks API에서 실행되며, 의미가 다릅니다(제출한 다음 폴링하거나 콜백을 수신). 위의 설명을 참조하세요.
현재 어떤 모델이 라우팅에 사용 가능한지 어떻게 알 수 있나요?
GET /v1/models를 호출하고 supported_endpoint_types에서 "openai"를 필터링하세요—가용성이 변하므로 모델 목록을 하드코딩하지 마세요.
더 비싼 모델 계층으로 업그레이드하는 트리거는 무엇이어야 하나요? 위의 패턴에서는 현재 계층의 모든 모델이 실패할 때만 업그레이드됩니다. 자체 신호로 이를 확장할 수 있습니다—예를 들어 예산 계층의 출력이 사용 사례의 품질 검사를 통과하지 못할 때 업그레이드—하지만 그 로직은 hiapi API 자체의 범위를 벗어납니다.
설계 시 고려해야 할 속도 제한이 있나요?
개념적으로 있습니다: 재시도 로직은 HTTP 429를 종료 실패가 아닌 백오프를 동반한 재시도 가능으로 처리해야 합니다(위에 표시된 대로). 대시보드에서 계획한 구체적인 제한을 확인하세요.
관련 글
모든 아티클 보기 →-
튜토리얼
HiAPI API로 hailuo-2.3-fast/image-to-video 호출하는 방법: curl과 Python 실전
빠르고 저렴한 이미지-투-비디오 등급의 첫 실행 요청 — 모델 ID부터 MP4 다운로드까지
-
튜토리얼
HiAPI API로 hailuo-2.3/text-to-video를 호출하는 방법: curl, Python 및 실행 가능한 요청 예시
-
튜토리얼
HiAPI에서 Kandinsky 6.0 Pro 텍스트-투-비디오 사용하기: curl 및 Python 튜토리얼
요청 우선 실전 데모: 프롬프트에서 비디오 클립까지의 호출 구성, 요청 제출, 완료 상태 폴링 및 결과 다운로드