여러 요청을 Batch로 처리하기
JSONL 파일로 요청을 제출하고 처리 상태, 확정 비용과 결과 파일을 확인하세요.
초과 유닛 사용이 허용된 스페이스를 대상으로 하며 실시간 API와 입력 조건이 다르고 작업 완료 뒤에도 정산을 기다릴 수 있습니다.
파일과 작업을 구별하기
| 지금 가진 것 | 바로 할 일 |
|---|---|
| Batch를 처음 실행함 | 시작 조건과 입력을 확인하고 한 줄로 시험 |
| 이미 Batch ID가 있음 | 같은 ID로 처리, 결과 파일, 정산을 각각 확인 |
| 업로드 또는 제출 응답을 받지 못함 | 어느 단계의 원래 요청인지 먼저 확인 |
새 Batch를 제출하면 별도 실행과 비용이 생길 수 있습니다. 상태 확인을 위해 새 작업을 제출하지 마세요.
파일 업로드 → Batch 생성 → 상태 조회 → 결과 파일 다운로드 순서로 진행합니다.
- Batch Files: 입력, 결과 및 오류 파일을 다룹니다. 업로드만으로 작업이 실행되지는 않습니다.
- Batches: 업로드한 파일 ID로 실행 작업을 만듭니다. 반환된 Batch ID로 상태와 결과 파일 ID를 조회합니다.
파일 ID와 Batch ID는 다릅니다. 같은 파일로 새 작업을 제출하면 별도의 실행과 비용이 생길 수 있습니다.
시작 전 준비
Batch는 여러 요청을 파일 하나에 담아 비동기로 처리하는 기능입니다. 즉시 응답이 필요한 요청은 첫 API 호출을 사용합니다.
- 스페이스: 초과 유닛 사용이 허용되어 있어야 합니다. 관리자(Admin)에게 확인하세요. 보유 유닛이나 유닛 구매만으로 충족되지 않습니다. 관리자가 확인할 수 없다면 로그인 후 플랫폼 내 문의 기능을 이용합니다.
- API 키: AI 게이트웨이 API 키를 준비합니다.
- 모델: Models API의
supports_batch에서 Batch 지원을 확인합니다. 안내 값이며 제출 시 다시 검사합니다. - 입력 조건: 에셋에서 지원 API와 입력 한도를 확인합니다. 실시간 API 지원만으로 Batch 지원을 판단하지 않습니다.
예제는 Python과 OpenAI SDK를 사용합니다. 한 줄로 실행해 결과를 확인한 다음 요청 수를 늘리세요. 시험에도 사용량에 따른 비용이 발생합니다.
실시간 API에서 성공한 요청도 Batch에서는 실패할 수 있습니다. 입력 파라미터가 실시간 호출처럼 변환되지 않으므로 아래 모델 및 API별 조건을 확인하세요.
입력 파일 만들기
input.jsonl 파일에 요청마다 JSON 객체 한 줄을 씁니다. 아래 세 예제 중 사용할 API에 맞는 하나로 시작하세요. 예제 모델도 위의 Models API와 에셋에서 지원 여부 및 입력 조건을 확인합니다.
한 파일에는 같은 모델과 같은 endpoint의 요청만 넣습니다. 각 행의 custom_id는 파일 안에서 고유해야 합니다. 결과 순서 대신 이 값으로 원래 요청과 결과를 연결합니다.
Chat Completions
{"custom_id":"request-1","method":"POST","url":"/v1/chat/completions","body":{"model":"gpt-4o-mini","messages":[{"role":"user","content":"Batch 처리를 한 문장으로 설명해 주세요."}],"service_tier":"default"}}실시간 요청과 배치에서 다른 필드
API와 모델에 맞는 필드
| 사용하는 API 또는 모델 | 입력 조건 |
|---|---|
| Chat Completions, Responses | service_tier: "default"를 명시합니다. |
| Embeddings | service_tier를 넣지 않습니다. |
| GPT-4o 및 GPT-4.1 계열의 Chat Completions | 출력 상한은 max_tokens를 사용합니다. |
| GPT-5 계열의 Chat Completions | 출력 상한은 max_completion_tokens를 사용합니다. max_tokens는 업로드를 통과해도 처리 중 실패할 수 있습니다. |
| Responses | 출력 상한은 max_output_tokens를 사용합니다. |
Responses 모델의 추가 조건
| 사용하는 모델 | 입력 조건 |
|---|---|
| 일반 Responses 입력 | gpt-5.2-pro, gpt-5.5-pro 및 정책상 허용된 날짜 스냅샷 ID를 사용합니다. |
gpt-4.1, gpt-4.1-mini, gpt-5.5, gpt-5.6-sol의 Responses 입력 | tools: [{"type": "web_search"}]가 필요합니다. 없으면 업로드에서 400 invalid_line의 endpoint_requires_web_search 사유로 거부됩니다. |
| Responses 전용 모델 | Chat Completions로 제출하지 않습니다. 모델의 지원 endpoint를 확인합니다. |
출력 상한은 선택 항목입니다. GPT-5 Chat에서 max_tokens 오류가 나면 숫자가 아니라 필드명을 max_completion_tokens로 바꿉니다. Responses에는 max_output_tokens를 사용하며, max_tokens나 max_completion_tokens를 넣으면 업로드에서 invalid_line의 unknown_field 사유로 거부됩니다. Embeddings에는 출력 토큰 상한 필드를 넣지 않습니다.
Chat은 모델별 출력 파라미터 지원 여부를 사전 검증하지 않습니다. 업로드와 제출이 성공해도 처리 뒤 전량 실패할 수 있으므로, 실시간 호출 성공만으로 확인을 끝내지 말고 한 줄짜리 Batch의 결과까지 확인하세요.
stream, stream_options, functions, function_call, audio, modalities, prediction, web_search_options, store는 Batch 입력에서 받지 않습니다. 파일 크기와 행 수 제한은 Batch Files에서 확인합니다.
긴 입력은 제출 전에 에셋의 컨텍스트 한도를 확인하세요. Models의 max_input_tokens: null은 무제한을 뜻하지 않습니다. 업로드 후에도 실패할 수 있으므로 긴 입력의 한 줄 시험을 거칩니다. 처리 성공인데 답변이 비어 있다면 추론 토큰과 출력 상한을 확인하세요.
업로드하고 제출하기
업로드 시 외부 AI 제공업체로 전달됩니다. 이후 제출이 거부되거나 제출하지 않아도 외부 전달이 취소되지 않습니다. 제출 조건과 파일 내용을 확인한 뒤 업로드하세요.
공개된 처리 업체와 이전 범위는 데이터 처리 위탁 현황에서 확인합니다.
실행 환경과 예제 파일 준비
가상 환경을 만들고 SDK를 설치합니다. 아래 Bash 명령은 키를 화면이나 셸 히스토리에 남기지 않고 입력받습니다.
python3 -m venv .venv
source .venv/bin/activate
python -m pip install openai
read -rsp "AI 게이트웨이 API 키: " LETSUR_API_KEY && printf '\n'
export LETSUR_API_KEY다음 코드를 batch_example.py로 저장합니다. SDK 클라이언트에는 게이트웨이 주소를 지정하고 자동 재시도는 끕니다. 제출 응답을 받지 못했을 때는 아래의 같은 파일 ID, 본문과 요청 키로 직접 재시도합니다.
batch_example.py 전체 코드
import json
import os
import sys
from pathlib import Path
from openai import OpenAI
def main():
client = OpenAI(
api_key=os.environ["LETSUR_API_KEY"],
base_url="https://gw.letsur.ai/v1",
max_retries=0,
)
action = sys.argv[1]
if action == "upload":
with open(sys.argv[2], "rb") as source:
print(client.files.create(
file=source, purpose="batch",
extra_headers={"Idempotency-Key": sys.argv[3]},
).id)
elif action == "submit":
job = client.batches.create(
input_file_id=sys.argv[2],
endpoint=sys.argv[3],
completion_window="24h",
extra_headers={"Idempotency-Key": sys.argv[4]},
)
print(job.id)
elif action in {"status", "download"}:
job = client.batches.retrieve(sys.argv[2])
data = job.model_dump()
print(json.dumps(data, ensure_ascii=False, indent=2))
if action == "status":
return
settlement = data.get("settlement_status")
if settlement == "quarantined":
print("정산이 보류됐습니다. Batch ID로 지원을 요청하세요.")
elif settlement != "settled":
print("비용은 미확정입니다. 같은 Batch ID로 나중에 조회하세요.")
elif data.get("settled_units") is None:
print("확정 비용을 확인할 수 없습니다. Batch ID로 지원을 요청하세요.")
if not any(data.get(field) for field in ("output_file_id", "error_file_id")):
raise SystemExit("받을 파일 ID가 없습니다. 작업 및 정산 상태와 errors를 확인하고, 처리 중이면 같은 Batch ID로 다시 조회하세요.")
for field, suffix in [("output_file_id", "output"), ("error_file_id", "error")]:
if file_id := data.get(field):
target = Path(f"{job.id}-{suffix}.jsonl")
content = client.files.content(file_id).content
with target.open("xb") as output:
output.write(content)
print(f"저장: {target}")
elif action == "cancel":
print(client.batches.cancel(sys.argv[2]).model_dump_json())
else:
raise SystemExit("upload / submit / status / download / cancel 중 하나를 사용하세요.")
if __name__ == "__main__":
main()1. 입력 파일 업로드
위의 전체 코드를 펼쳐 batch_example.py로 저장하고, 앞에서 준비한 input.jsonl과 같은 폴더에서 실행합니다.
업로드하고 출력된 파일 ID를 기록합니다. my-input-upload-001은 이 업로드에 사용할 고유 요청 키의 예입니다.
python batch_example.py upload input.jsonl my-input-upload-001업로드 응답을 받지 못했다면 24시간 안에 같은 업로드 키와 같은 파일 바이트로 다시 요청합니다. 파일을 수정했다면 새 업로드 키를 사용합니다. 업로드 키와 아래 작업 제출 키는 서로 다른 요청을 보호합니다. 자세한 오류 처리는 업로드 재시도를 확인하세요.
2. Batch 작업 제출
FILE_ID를 반환된 값으로 바꾸고, JSONL의 url과 같은 endpoint로 제출합니다. Responses나 Embeddings 예제를 골랐다면 아래 endpoint도 바꿉니다. my-batch-run-001은 이 실행에만 사용할 고유 요청 키의 예입니다.
python batch_example.py submit FILE_ID /v1/chat/completions my-batch-run-001출력된 Batch ID를 저장합니다. 같은 파일을 의도적으로 다시 실행하려면 새 요청 키를 사용합니다. 응답 유실 뒤 재시도할 때는 24시간 계약 안에서 같은 요청 키와 같은 본문을 사용합니다. 헤더 없이 제출하면 새 Batch가 생성되어 중복 실행과 추가 비용이 발생할 수 있습니다. 충돌과 진행 중 응답의 처리는 재시도 계약을 확인하세요.
상태를 확인하고 결과 받기
Batch ID와 해당 작업에 접근할 수 있는 AI 게이트웨이 API 키가 필요합니다. Batch ID는 생성 응답의 id이며 입력 파일 ID와 다릅니다. 아래 조회는 기존 작업을 확인하며 새 Batch를 제출하지 않습니다.
1. 처리 상태 확인
Bash와 cURL에서 다음 명령을 실행합니다. URL의 BATCH_ID를 확인하려는 작업 ID로 바꾸세요. 서버 환경 변수 LETSUR_API_KEY를 사용하며, 변수가 없으면 화면에 표시하지 않고 키를 입력받습니다. batch_example.py나 SDK 설치는 필요 없습니다.
(
if [ -z "${LETSUR_API_KEY:-}" ]; then
IFS= read -rsp "AI 게이트웨이 API 키: " LETSUR_API_KEY
printf '\n'
fi
test -n "${LETSUR_API_KEY:-}" || { echo "LETSUR_API_KEY가 필요합니다." >&2; exit 1; }
env -u LETSUR_API_KEY curl --fail-with-body "https://gw.letsur.ai/v1/batches/BATCH_ID" \
--header @- <<EOF
Authorization: Bearer ${LETSUR_API_KEY}
EOF
)응답의 status를 확인합니다. 한 번씩 조회하는 예제이므로 처리 중이면 간격을 두고 같은 명령을 다시 실행합니다. 이 페이지의 Python 명령을 사용하려면 실행 환경과 예제 파일 준비에서 SDK와 batch_example.py를 준비한 뒤 다음 명령을 실행하세요. 이미 Batch ID가 있으므로 업로드와 제출 단계는 다시 실행하지 않습니다.
python batch_example.py status BATCH_IDstatus | 다음 행동 |
|---|---|
validating, in_progress, finalizing | 같은 Batch ID로 나중에 다시 조회 |
completed | request_counts.completed와 failed로 행별 성공 수 확인 |
failed, cancelled, expired | Batch의 errors, 제공되는 파일과 정산 상태 확인 |
completed는 모든 요청의 성공이나 정산 완료를 뜻하지 않습니다. 예를 들어 total: 1, completed: 0, failed: 1이면 처리는 끝났지만 성공한 요청은 없습니다.
2. 반환된 파일 받기
output_file_id 또는 error_file_id가 있으면 Batch Files의 콘텐츠 다운로드로 받습니다. 다운로드 API는 정산 완료를 별도 조건으로 검사하지 않습니다. 파일 ID가 없다면 아래의 상태별 대응을 확인합니다.
전체 Python 예제를 준비했다면 다음 명령으로 반환된 파일을 저장할 수 있습니다.
python batch_example.py download BATCH_ID- 성공 결과는
BATCH_ID-output.jsonl, 개별 오류는BATCH_ID-error.jsonl에 저장됩니다. 결과에 따라 한쪽만 있을 수 있습니다. - 같은 이름의 파일이 있으면 덮어쓰지 않고 중단합니다. 기존 파일을 보관하고 다른 디렉터리에서 실행하세요.
- 각 행의
custom_id로 입력과 연결합니다.response.status_code, 응답 본문과error까지 확인해야 합니다. - 결과 파일은 Batch 완료 후 30일이 지나면 만료됩니다. 준비되면 바로 저장하세요. ID가 남아 있어도 만료된 파일의 재다운로드를 보장하지 않습니다. 파일 보관과 만료를 확인하세요.
파일 ID가 없다면
현재 결과 파일 ID는 유닛 차감 뒤 준비됩니다. completed 직후에도 기다려야 할 수 있습니다.
| 확인한 상황 | 다음 행동 |
|---|---|
처리 중이거나 정산이 pending, queued, processing, settling | 같은 Batch ID로 다시 조회. 새 Batch를 제출하지 않음 |
정산이 quarantined | 자동 조회를 멈추고 Batch ID로 플랫폼 내 문의 |
정산이 settled인데 한쪽 ID가 없음 | 해당 종류의 결과가 없을 수 있음. 요청 수, errors와 다른 파일 ID 확인 |
| Batch 전체 제출 검증 실패 | 파일 대신 Batch 객체의 errors에서 원인 확인 |
어느 경우에도 파일 ID가 없다는 이유만으로 무료나 전체 실패로 판단하지 않습니다. 원인을 확인할 수 없다면 지원 요청에 필요한 정보를 준비합니다.
3. 비용 확정 확인
settlement_status == "settled"이고 settled_units != null일 때 확정 비용을 읽습니다. 파일 수령과 별도로 확인하세요.
- 정산 진행 중이면 같은 Batch ID로 다시 조회합니다.
quarantined는 정산 보류입니다. 자동 조회를 멈추고 Batch ID로 문의합니다. 무료나 완료를 뜻하지 않습니다.settled인데 금액을 확인할 수 없다면 Batch ID로 문의합니다.null을 0으로 바꾸지 않습니다.
실패한 요청을 고쳐 다시 실행하기
- 결과, 오류 파일의
custom_id를 원래 입력 파일과 대조해 다시 실행할 요청을 고릅니다. 이미 성공한 요청은 제외합니다. - 오류에 맞게 해당 요청의 본문을 고칩니다.
unsupported_parameter이면 모델, API에 맞는 필드를 사용하고,context_length_exceeded이면 입력을 줄이거나 나눕니다. - 수정한 요청 중 한 건만 넣은 시험용 JSONL 파일을 만들고 업로드해 반환된 파일 ID를 기록합니다.
- 시험 파일 ID와 새 요청 키로 제출하고 결과를 확인합니다. 정상 처리되면 나머지 요청을 별도 JSONL 파일로 업로드하고 또 다른 새 요청 키로 제출합니다. 시험에 성공한 요청은 나머지 파일에서 제외합니다. 원래 키에 수정된 본문을 보내면
409 idempotency_conflict가 발생합니다.
이는 실패한 입력을 수정한 새 실행입니다. 응답을 받지 못해 원래 제출을 확인하는 재시도와는 다릅니다.
취소하기
처리 중인 작업의 취소를 요청하려면 다음 명령을 사용합니다.
python batch_example.py cancel BATCH_ID취소 요청 성공은 최종 취소 완료가 아닙니다. cancelling 동안 기다린 뒤 상태와 정산을 다시 확인합니다. 취소 전에 완료된 요청의 결과와 비용이 남을 수 있습니다. 처리 시간은 요청과 모델에 따라 달라집니다.
비용과 콘솔에서 확인할 내용
확정 비용과 잔액
확정 비용은 settlement_status가 settled이고 settled_units가 있는 경우에만 읽습니다. settled_units는 유닛 금액의 문자열이며, null은 비용이 아직 확정되지 않았거나 값을 확인할 수 없다는 뜻입니다. 예를 들어 정산 중의 null은 무료가 아니며, 정산 완료 뒤 금액 문자열 "0"이 반환된 경우와 구분합니다. 사용량 분석과 유닛 잔액은 서로 다른 값을 보여줍니다.
사용 한도
스페이스의 초과 유닛 사용 허용과 API 키 및 구성원의 사용 한도는 별도 조건입니다. 초과 사용이 허용되어 있어도 적용된 사용 한도를 이미 초과한 상태에서는 신규 Batch 제출이 차단될 수 있습니다. 아직 정산되지 않은 작업 비용을 미리 예약하는 방식은 아니므로 월 사용 한도를 전체 지출 상한 보장으로 해석하지 않습니다.
콘솔과 API 키의 접근 범위
콘솔에서는 일반 멤버가 본인 작업을 확인하고, 관리자는 같은 스페이스의 작업 이력을 조회합니다. 관리자는 유효한 사람 소유자 귀속이 있는 멤버의 결과 파일도 다운로드할 수 있지만 타인의 작업을 취소할 수는 없습니다. API 키 경로에는 콘솔 관리자 권한이 적용되지 않습니다. 개인 키는 소유자 기준, 공유 키는 해당 키 기준으로 접근합니다. 개인 키를 교체했다면 같은 소유자의 유효한 키와 해당 스페이스 접근 권한을 확인하세요.
문제를 해결할 때
응답을 받지 못했다면 어느 단계인지 먼저 구분하세요. 업로드 키와 제출 키는 별개입니다. 아래는 같은 스페이스와 소유 주체에서 24시간 계약 안의 원래 요청을 확인하는 경로입니다.
Batch 제출에서 새 키를 쓰거나 키를 생략하면 별도 실행과 비용을 만들 수 있습니다. 24시간을 넘긴 경우에도 재제출 전에 원래 생성 여부를 확인합니다.
| 증상 | 다음 행동 |
|---|---|
업로드에서 invalid_line | 행 번호와 사유를 확인해 모델, endpoint와 필드를 수정합니다. |
제출에서 endpoint_mismatch | 입력 파일의 url과 제출 endpoint를 일치시킵니다. |
403 OVERAGE_NOT_ALLOWED | 관리자에게 초과 유닛 사용 허용 여부를 확인합니다. 유닛 구매로 해결되지 않습니다. 확인이 어렵다면 시작 전 준비의 문의 경로를 따릅니다. |
| 파일 또는 작업에 접근할 수 없음 | 현재 스페이스, 소유 주체와 유효한 키를 확인합니다. 콘솔과 API의 접근 범위를 확인하고, 파일 존재 여부를 오류만으로 단정하지 않습니다. |
429 policy_limit_exceeded | 적용된 한도와 사용량을 확인합니다. 새 요청 키로 우회하지 않습니다. |
400 idempotency_key_invalid | 요청 키가 빈 문자열이나 공백뿐인지 확인합니다. 키를 사용하지 않으려면 헤더를 생략합니다. |
409 idempotency_conflict 또는 idempotency_in_flight | 같은 제출의 재시도와 새 실행을 구분해 처리합니다. |
| HTTP 200인데 출력이 비어 있음 | finish_reason, 추론 토큰과 출력 상한을 확인합니다. |
| 작업 완료 후 결과 파일이 없음 | 정산 상태와 errors를 먼저 확인합니다. |
| 처리 시간이 길어짐 | 같은 ID로 조회합니다. 반복 제출은 별도 실행과 비용을 만들 수 있습니다. |
지원 요청에는 Batch ID, 요청 시각, 사용한 모델과 endpoint, 오류 코드를 남깁니다. 파일 문제라면 파일 ID도 함께 전달합니다. API 키나 요청에 포함된 민감한 원문은 보내지 마세요.
긴 입력은 처리 뒤 실패할 수 있습니다
컨텍스트 한도를 넘는 입력은 업로드와 제출을 통과한 뒤 개별 요청이 실패할 수 있습니다. /v1/models에는 max_input_tokens 필드가 있지만 현재 값은 null입니다. 무제한이라는 뜻이 아니므로 제출 전에 에셋 화면에서 모델의 토큰 한도와 입력 크기를 확인하고, 긴 요청도 한 줄짜리 Batch로 먼저 시험하세요. 다른 요청이 성공했다면 그 성공분에는 비용이 발생할 수 있습니다.
요청은 성공했는데 답변 내용이 비어 있는 경우
추론 모델은 답변을 쓰기 전에 추론에도 토큰을 사용합니다. Chat Completions의 max_completion_tokens는 추론에 쓴 토큰과 실제 답변 토큰을 합친 상한입니다. 예를 들어 상한을 500으로 두고 추론에 500토큰을 모두 쓰면, HTTP 200으로 처리돼도 답변 내용은 빈 문자열일 수 있습니다. 이미 토큰을 사용했으므로 비용이 발생할 수 있습니다.
결과 행의 response.body에서 다음을 함께 확인하세요.
choices[0].message.content: 실제 답변 내용이 있는지 확인합니다.choices[0].finish_reason:length이면 토큰 상한 때문에 답변이 끝났을 수 있습니다.usage.completion_tokens와 제공되는usage.completion_tokens_details.reasoning_tokens: 출력 예산 중 추론에 사용한 양을 확인합니다.
이 경우에는 출력 상한을 늘리거나 요청을 단순하게 바꿔 한 건으로 다시 시험합니다. 재실행에는 추가 비용이 발생할 수 있으므로 빈 답변이 있다는 이유로 전체 파일을 자동 재제출하지 마세요. 파라미터 오류로 실행이 거부된 HTTP 400과 구분해야 합니다.
파라미터 오류로 모든 행이 실패한 경우
오류 파일의 response.body.error.code가 unsupported_parameter이고 param이 max_tokens이면 모델과 API에 맞는 출력 상한 필드로 고칩니다. 수정한 파일을 새로 업로드하고 새 파일 ID와 새 제출 키로 한 줄만 시험하세요.
파라미터 오류로 전량 거부돼 토큰 사용량이 0이었다면 정산 완료 뒤 확정 비용도 0인지 확인합니다. HTTP 200의 빈 답변은 추론 토큰을 사용했을 수 있으므로 같은 경우로 판단하지 않습니다.