튜토리얼
HiAPI API로 gpt-6.1-terra를 사용하는 방법: curl, Python과 실제로 동작하는 요청
gpt-6.1-terra 요청 하나에 실제로 필요한 것
HiAPI에서 gpt-6.1-terra를 호출하는 일은 모두 네 가지로 귀결됩니다. URL, 인증 헤더, 모델 식별자, 요청 본문입니다. 이 네 가지를 제대로 맞추면 이 글의 나머지는 파이프 연결에 불과합니다.
엔드포인트
모든 모델 호출은 HiAPI의 동일한 기본 URL을 통해 이루어집니다. 모델마다 호스트를 따로 바꿀 필요가 없습니다. 요청 본문에 담긴 모델 이름이 요청 라우팅의 기준입니다. 클라이언트를 HiAPI 기본 URL로 지정하고 이를 변수에 저장해 두면 한 곳만 수정하면 됩니다.
인증 헤더
인증은 Authorization 헤더에 bearer token을 전달하는 방식입니다. 이 token은 HiAPI 계정에서 발급하는 API 키입니다. 절대 소스 파일에 하드코딩하지 말고, 환경 변수(예: HIAPI_API_KEY)에서 읽어서 같은 코드가 로컬, CI, 프로덕션 환경에서 그대로 동작하게 하세요.
모델 식별자
요청 본문에는 모델을 명시해야 합니다: "model": "gpt-6.1-terra". 이 문자열은 대소문자를 구분하며 정확히 일치해야 합니다. 모델 이름이 틀리거나 잘리는 것이 요청 거부의 가장 흔한 원인 중 하나이므로, 즉석 문자열이 아니라 코드의 상수로 취급하세요.
요청 본문 필드
요청에는 최소한 모델과 입력 콘텐츠, 즉 모델이 처리할 프롬프트나 메시지가 담깁니다. 이 핵심 위에 모델이 지원하는 임의의 선택 파라미터(출력 형식 힌트, 길이 제어 등)를 추가할 수 있지만, 유효한 응답을 만드는 최소한의 요청 본문부터 시작하세요. 최소화된 요청 본문은 온갖 옵션을 붙인 요청 본문보다 디버깅이 쉽고, 실패가 요청 본문 문제인지 파이프 연결 문제인지 판별하는 데 도움이 됩니다.
일찍 익혀 둘 만한 구조적 요점이 하나 있습니다. 어떤 응답은 인라인으로 반환되지만, 어떤 것은 폴링해야 하는 작업으로 반환됩니다. 두 가지를 구분하는 방법은 뒤에서 다루겠지만, 지금부터 응답 처리가 재작성 없이 두 방식 사이를 전환할 수 있도록 요청 계층을 설계해야 합니다.
실제로 동작하는 curl 요청, 한 줄씩 해부하기
다음은 완전한 호출 예시로, 키를 export한 뒤 터미널에 붙여 넣어 바로 실행할 수 있습니다. HiAPI 기본 URL과 최소한의 JSON 요청 본문을 사용합니다.
curl -sS -X POST "$HIAPI_BASE_URL/v1/chat/completions" \
-H "Authorization: Bearer $HIAPI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "gpt-6.1-terra",
"messages": [
{"role": "user", "content": "用三句话总结水循环。"}
]
}'
각 플래그의 역할
-X POST는 HTTP 메서드를 설정합니다. 모델 호출은 요청 본문을 수반하므로 POST입니다.
-sS는 curl이 진행 정보를 숨기되 실제 오류는 표시하게 합니다. -S가 없으면 실패한 요청이 그저 침묵처럼 보일 수 있습니다.
-H "Authorization: Bearer $HIAPI_API_KEY"는 자격 증명을 추가합니다. Bearer 접두사와 그 뒤의 공백 하나는 인증 스킴의 일부입니다. 어느 하나라도 빠지면 인증이 실패하는데, 헤더와 무관해 보이는 실패입니다.
-H "Content-Type: application/json"는 서버에 요청 본문이 JSON임을 알립니다. 일부 게이트웨이는 콘텐츠 타입 누락을 용인하지만, 이것에 기대지 마세요.
-d '...'는 요청 본문입니다. 작은따옴표는 셸이 JSON을 보간하지 못하게 막습니다. 파일을 쓰고 싶다면 -d '...'를 -d @payload.json으로 바꾸고 JSON을 lint와 diff가 가능한 파일에 두세요.
시간을 아껴 주는 두 가지 습관
첫째, 실패가 발생하면 -i를 붙여 응답 상태 줄과 헤더를 출력하세요. 200을 반환했지만 본문이 이상한 응답은 401이나 422와 읽는 방식이 전혀 다르며, 그 차이를 즉시 보고 싶을 것입니다. 둘째, 출력을 JSON 포매터에 통과시키세요(curl ... | python -m json.tool). 그러면 중첩된 응답 필드를 읽을 수 있습니다. 두 플래그 모두 부작용이 없으며, 호출을 조정할 때 자주 쓰게 될 것입니다.
호출을 Python으로 이식하기
Python 버전은 curl 호출의 정확한 의미를 유지해야 합니다. 같은 URL, 같은 헤더, 같은 요청 본문, 같은 파싱 방식입니다. 표준 라이브러리를 사용하면 의존성이 0으로 유지되고 이식 과정이 한눈에 보입니다.
import json
import os
import urllib.request
HIAPI_BASE_URL = os.environ["HIAPI_BASE_URL"].rstrip("/")
HIAPI_API_KEY = os.environ["HIAPI_API_KEY"]
def call_terra(prompt: str, model: str = "gpt-6.1-terra") -> dict:
body = {
"model": model,
"messages": [{"role": "user", "content": prompt}],
}
req = urllib.request.Request(
f"{HIAPI_BASE_URL}/v1/chat/completions",
data=json.dumps(body).encode("utf-8"),
headers={
"Authorization": f"Bearer {HIAPI_API_KEY}",
"Content-Type": "application/json",
},
method="POST",
)
with urllib.request.urlopen(req, timeout=120) as resp:
return json.loads(resp.read().decode("utf-8"))
result = call_terra("用三句话总结水循环。")
print(json.dumps(result, indent=2))
이식에 관한 설명
헤더는 요청 객체 내부에서 한 번에 구성되며, 두 개의 -H 플래그에 대응합니다. 요청 본문은 Python dict이며 json.dumps로 직렬화하는데, 이는 작은따옴표로 감싼 curl 요청 본문과 프로그래밍적으로 동등하며 Windows 셸의 인용 문제를 없애 줍니다.
이 함수는 모델을 기본값이 있는 파라미터로 받아, 여러 곳에서 리터럴을 반복하지 않습니다. 이로써 모델 이름의 단일 진실 공급원이 생기고, 동시에 의도적으로 재정의할 수 있습니다.
여기서는 정상 경로를 읽기 쉽게 유지하기 위해 오류 처리를 의도적으로 생략했습니다. 실제 코드에서는 urlopen 호출을 감싸고 HTTP 오류 예외를 잡아, 오류 객체에서 .read()를 읽어 서버 메시지를 확인하세요. 타임아웃은 명시적으로 설정했습니다. 무한 블로킹은 결국 워커를 멈추게 합니다.
httpx나 requests를 선호한다면 매핑은 일대일입니다: requests.post(url, headers=..., json=body, timeout=120). json= 파라미터가 dict를 대신 직렬화해 줍니다. 의미는 전혀 달라지지 않으며, 바로 그 점이 핵심입니다. curl 호출이 계약이고 Python은 그 두 번째 표현일 뿐입니다.
단일 호출과 비동기 작업 사이에서 결정하기
모든 요청이 대기에 적합한 것은 아닙니다. 결정은 작업이 얼마나 오래 걸리는지, 그리고 호출자가 차단을 견딜 수 있는지에 달려 있습니다.
단일 호출이 적합한 경우
짧은 대화형 요청(요약, 분류, 소규모 리라이트)은 일반적인 동기 호출이어야 합니다. 응답이 인라인으로 반환되고, 파싱해서 돌려주면 끝입니다. 가장 단순한 코드 경로이며 테스트에서 추론하기도 가장 쉽습니다. 사용자가 UI에서 기다리고 있다면 동기 방식이 기대되는 형태입니다.
작업으로 취급해야 하는 경우
작업이 오래 실행되거나 산출물이 많아서 연결이 끊기면 비용이 큰 재작업을 의미할 때는 호출을 비동기로 취급해야 합니다. 이런 경우 요청을 제출하고 작업 식별자를 받은 뒤, 종료 상태에 도달할 때까지 폴링합니다. 제출 자체는 빠르게 반환되므로 워커나 HTTP 핸들러가 전체 과정 동안 점유되지 않습니다.
실용적인 규칙 하나: "호출을 성공시키기 위해" 예외적으로 긴 클라이언트 타임아웃을 설정하고 있음을 발견했다면, 그것이 작업으로 전환하라는 신호입니다. 타임아웃은 스케줄링 메커니즘이 아닙니다.
전환 비용을 낮게 유지하기
코드베이스를 두 개의 무관한 클라이언트로 분할하지 마세요. 두 경로를 하나의 함수 뒤에 감싸고, 그 함수가 즉시 결과나 작업 핸들을 반환하게 하며, 호출자가 원하는 모드를 요청하게 하세요. 그러면 요청 구성(모델, 인증, 요청 본문)은 공유되고 대기 전략만 달라집니다. 또한 디버깅이 일관된다는 뜻이기도 합니다. 요청 본문이 작을 때 비동기 실패를 동기 방식으로 재현할 수 있습니다.
플랫폼 동작을 추측하지 말고 자신의 워크로드에 솔직해지세요. 요청이 보통 몇 초 안에 끝난다면 동기로 유지하고 복잡성을 건너뛰세요. 비동기 경로의 가치는 정말 느린 요청에 있으며, 모든 요청에 있는 것은 아닙니다.
API를 두들기지 않고 완료 상태 폴링하기
폴링 루프에는 세 가지 책임이 있습니다. 예의를 지킬 만큼 충분히 기다리고, 결국 멈추고, 종료 상태를 명확히 제시하는 것입니다. 다음은 간결한 버전입니다.
import time
def poll_until_done(fetch_status, job_id, interval=3.0, timeout=600.0):
deadline = time.monotonic() + timeout
while True:
status = fetch_status(job_id)
state = status.get("status")
if state in ("succeeded", "failed", "cancelled"):
return status
if time.monotonic() > deadline:
raise TimeoutError(f"job {job_id} not finished after {timeout}s")
time.sleep(interval)
이 구조를 쓰는 이유
fetch_status가 주입되므로 HTTP로 폴링하든 내부 큐를 폴링하든 같은 루프가 동작합니다. 또한 running을 두 번 반환한 뒤 succeeded를 반환하는 가짜 객체로 테스트하기도 아주 쉽습니다.
상태 검사는 단일 "done" 문자열이 아니라 종료 상태에 대한 명시적 허용 목록입니다. 종료 상태는 성공, 실패, 취소 등으로 다르며, 한 가지 상태만 찾다가 타임아웃까지 헛돌지 않고 그중 어느 것이든 나타나면 멈추기를 원합니다.
마감 시간은 time.monotonic()을 사용하므로 벽시계 조정이 대기 시간을 예기치 않게 늘리거나 줄이지 않습니다.
폴링 간격 선택하기
몇 초부터 시작하고, 촘촘한 루프 폴링 충동을 참으세요. 1초 미만의 간격은 작업을 더 빨리 끝내 주지도 않으면서 요청 예산을 소모하고 부하를 높입니다. 작업이 길 것으로 예상된다면 완만한 백오프를 고려하세요. 예를 들어 검사할 때마다 상한까지 간격을 늘리는 방식입니다. 상한은 적당히 유지하세요. 목표는 요청을 줄이는 것이지 수 분짜리 사각지대를 만드는 것이 아닙니다.
타임아웃은 선택 사항이 아니다
항상 타임아웃을 설정하고, 작업에 비해 너무 짧지 않되 유한하게 두세요. 타임아웃이 발생하면 결과를 조용히 비워 반환하지 마세요. 예외를 던지고 작업 식별자를 기록한 뒤, 재시도할지는 호출자가 결정하게 하세요. 폴링 타임아웃이 난 작업은 여전히 실행 중일 수 있고, 무작정 다시 제출하면 작업이 중복될 수 있습니다.
출력 다운로드 및 저장
응답에 산출물 참조(URL, 파일 식별자, 스토리지 키)가 포함되어 있다면 이를 일시적인 것으로 취급하세요. 만료될 수 있고, 계정 범위로 제한될 수 있으며, 재시도 후에는 살아남지 못할 수도 있습니다. 영속적인 사본은 직접 저장한 그 사본입니다.
가져와서 영속화하는 패턴
작업이 완료되면 종료 상태에서 산출물 참조를 읽고, 한 번 가져와서, 통제 가능한 목적지에 쓰세요. 스크립트에는 로컬 경로, 프로덕션에 가까운 용도에는 객체 스토리지 버킷이 적합합니다. 산출물을 생성한 요청까지 추적할 수 있도록 작업 식별자를 저장한 파일과 함께 기록하세요.
def save_output(fetch_bytes, artifact_url, dest_path):
data = fetch_bytes(artifact_url)
with open(dest_path, "wb") as f:
f.write(data)
return dest_path
조용한 데이터 손실을 막는 방법
임시 경로에 쓴 뒤 성공 시 이름을 바꾸세요. 그러면 실패한 다운로드가 유효해 보이지만 반쯤만 쓰인 파일을 남기지 않습니다. 응답이 바이트 수나 체크섬을 노출한다면 검증하세요. 큰 파일은 전체 페이로드를 메모리에 올리지 말고 디스크로 스트리밍하며 쓰세요.
또한 최종 상태 호출의 원본 응답도 보관하세요. JSON 저장은 저렴하며, 누군가 파일이 왜 이상해 보이는지 물을 때 모델 이름, 타임스탬프, 작업 id가 담긴 상태 문서는 사건 경과를 재구성하는 가장 빠른 방법입니다.
마지막으로 검색과 처리를 분리하세요. 먼저 다운로드하고 저장한 뒤 변환하세요. 다운로드하면서 파싱하면 짧은 네트워크 오류가 포맷 버그처럼 보여서 오후 내내 잘못된 문제를 해결하게 됩니다.
흔한 장애 디버깅하기
대부분의 장애는 네 가지 범주에 속합니다. 각 범주에는 빠른 격리 테스트가 있습니다.
인증 오류
증상: 요청 본문의 어떤 내용도 중요해지기 전에 401 또는 403이 나타납니다. 키가 실제로 로드되었는지 확인하세요. 값이 아니라 환경 변수가 설정되었는지 출력해 보세요. Bearer 접두사와 그 뒤의 공백을 검증하세요. 헤더 이름의 철자가 맞는지, 프록시가 이를 제거하지 않는지 확인하세요. 같은 키가 curl에서는 되는데 애플리케이션에서는 안 된다면 애플리케이션이 헤더를 다르게 구성하는 것입니다. 헤더 dict 리터럴을 curl 플래그와 비교하세요.
요청 본문 형식 오류
증상: 요청 본문에 관한 메시지와 함께 400 또는 422가 나타납니다. 보내기 전에 JSON을 검증하세요. json.dumps는 의도하지 않게 만든 dict를 포함해 거의 모든 것을 직렬화합니다. 설정 파일에서 붙여 넣은 후행 쉼표, 문자열이어야 하는데 리스트로 전달된 내용, 누락된 필수 필드를 확인하세요. 첫 번째 절의 최소 요청 본문으로 줄인 뒤, 실패할 때까지 필드를 하나씩 다시 추가하세요.
모델 이름 오류
증상: 잘못된 요청이나 모델을 찾을 수 없다는 메시지처럼 보이는 거부. 모델 문자열은 정확히 gpt-6.1-terra여야 합니다. 흔한 원인은 헬퍼 함수의 오래된 기본값, 다른 값을 담은 환경 변수, 복사 대신 다시 입력해 생긴 오타입니다. 모듈 상수로 유지하고 중복 항목을 grep으로 찾으세요.
폴링 타임아웃
증상: 루프가 타임아웃으로 종료되는데 작업은 아직 살아 있습니다. 먼저 폴링하는 작업 식별자가 제출한 것과 같은지 확인하세요. 제출 시 둘 다 출력하는 것이 가장 간단한 검사입니다. 그런 다음 종료 상태 목록을 보세요. 예상하지 못한 상태가 있으면 루프가 영원히 돌게 됩니다. 마지막으로 간격과 마감 시간이 실제 작업 시간과 맞는지 확인하세요. 짧은 요청에 맞춘 마감 시간은 긴 요청에서 항상 발생합니다. 타임아웃 시 마지막 상태 페이로드를 기록해 두면 포기할 때 작업이 어떤 상태였는지 알 수 있습니다.
인증, 요청 본문, 모델, 폴링 순서로 처리하세요. 각 계층이 그 아래 계층을 가리기 때문입니다. 헤더가 깨지면 모든 요청 본문이 틀린 것처럼 보입니다.
관련 글
모든 아티클 보기 →-
튜토리얼
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 튜토리얼
요청 우선 실전 데모: 프롬프트에서 비디오 클립까지의 호출 구성, 요청 제출, 완료 상태 폴링 및 결과 다운로드