
매달 초 API 청구서를 열어보고 생각보다 커진 금액에 한숨 쉬어보신 적 있으신가요? 실시간 응답이 필요 없는 작업이라면 LLM 배치 API로 비용을 절반까지 줄이는 방법이 이미 마련돼 있습니다. 다만 모든 작업에 무작정 적용할 수 있는 건 아니어서, 어떤 작업이 되고 어떤 작업이 안 되는지부터 구분하는 게 순서입니다.
배치 API가 정확히 뭘 절반으로 깎아주는 걸까요?
OpenAI와 Anthropic이 각각 제공하는 배치 API는 요청을 실시간으로 즉시 처리하지 않는 대신, 최대 24시간 이내에 결과를 모아서 돌려주는 방식으로 동작합니다. 그 대가로 토큰당 요금을 절반 수준으로 깎아주는 구조입니다. OpenAI 공식 문서에서는 이를 “동일한 모델을 동기 API 대비 50% 낮은 가격으로 사용할 수 있다”고 명시하고 있습니다(OpenAI Batch API 가이드).
핵심은 이 할인이 ‘즉시 응답’을 포기하는 대가로 주어진다는 점입니다. 요청을 jsonl 파일 형태로 한꺼번에 업로드하면, 서버가 자체적으로 유휴 시간대의 컴퓨팅 자원을 활용해 순서대로 처리하고, 완료되면 결과 파일을 돌려주는 구조입니다. Anthropic 쪽도 Message Batches API라는 이름으로 같은 개념의 기능을 제공하고 있고, 마찬가지로 표준 요금 대비 절반 수준의 가격이 적용됩니다(Anthropic 배치 처리 문서).
배치로 돌려도 되는 작업과 안 되는 작업이 갈리는 기준
배치 처리와 일반 동기 처리를 가르는 기준은 딱 하나, “이 요청의 결과를 지금 당장 봐야 하는가”입니다. 사용자가 화면 앞에서 응답을 기다리고 있다면 배치는 애초에 후보가 될 수 없고, 반대로 서버 쪽에서 조용히 돌아가도 되는 작업이라면 배치가 거의 항상 이득입니다.

| 구분 | 적합한 작업 예시 | 이유 |
|---|---|---|
| 대량 문서 요약 | 수천 건의 리포트·계약서 일괄 요약 | 사람이 결과를 다음날 확인해도 무방함 |
| 임베딩 생성 | 검색 인덱스용 문서 벡터화 | 배치 결과가 준비되면 인덱싱만 하면 됨 |
| 데이터 라벨링·분류 | 리뷰 감성 분석, 카테고리 태깅 | 야간에 돌리고 아침에 결과 취합 가능 |
| 대규모 평가·테스트셋 채점 | 프롬프트 버전 A/B 성능 비교 | 실험이므로 실시간성이 필요 없음 |
| 콘텐츠 모더레이션 로그 재검토 | 과거 게시물 일괄 재점검 | 실시간 차단이 아닌 사후 검증 |
반대로 챗봇처럼 사용자와 실시간으로 대화하는 서비스, 스트리밍으로 토큰이 한 글자씩 화면에 찍혀야 하는 UI, 이전 응답을 바로 다음 프롬프트에 넣어야 하는 멀티턴 체이닝 작업은 배치와 맞지 않습니다. 배치는 언제 끝날지 정확히 예측할 수 없어서, 다음 단계가 그 결과를 즉시 필요로 하는 파이프라인에서는 오히려 병목이 됩니다.
OpenAI 배치 API, 코드로는 이렇게 동작합니다
OpenAI Batch API는 요청을 jsonl 파일로 만들어 업로드하고, 그 파일 ID로 배치 작업을 생성하는 두 단계로 이루어집니다. 각 줄은 custom_id로 구분되는 개별 요청이고, 실제 요청 본문은 평소 사용하던 Chat Completions 요청과 동일한 형식입니다.
# batchinput.jsonl 한 줄 예시
{"custom_id": "req-1", "method": "POST", "url": "/v1/chat/completions", "body": {"model": "gpt-4o-mini", "messages": [{"role": "user", "content": "이 문단을 한 문장으로 요약해줘: ..."}]}}
{"custom_id": "req-2", "method": "POST", "url": "/v1/chat/completions", "body": {"model": "gpt-4o-mini", "messages": [{"role": "user", "content": "다음 리뷰의 감정을 긍정/부정/중립으로 분류해줘: ..."}]}}
from openai import OpenAI
client = OpenAI()
batch_input_file = client.files.create(
file=open("batchinput.jsonl", "rb"),
purpose="batch"
)
batch = client.batches.create(
input_file_id=batch_input_file.id,
endpoint="/v1/chat/completions",
completion_window="24h"
)
print(batch.id, batch.status)

공식 문서 기준으로 batch 객체의 status 값은 validating → in_progress → finalizing → completed 순서로 바뀌며, 문제가 생기면 failed나 expired로 전환됩니다. completed 상태가 되면 output_file_id로 결과 jsonl 파일을 받아 각 줄의 custom_id로 원래 요청과 매칭해 파싱하면 됩니다. 이 방식은 응답을 하나씩 받는 동기 API와 달리, 결과 파일 전체를 후처리하는 코드가 별도로 필요하다는 점을 감안해야 합니다.
배치 요청이 24시간 다 되도록 안 끝나면 어떻게 되나요?
완료 창(completion window)으로 설정한 시간 안에 처리가 끝나지 않으면 해당 배치는 expired 상태로 전환되고, 그때까지 처리되지 않은 요청은 결과 없이 종료됩니다. 이미 처리가 끝난 요청은 결과 파일에 포함되므로 완전히 날아가는 건 아니지만, 처리되지 못한 나머지는 다시 배치를 생성해서 재요청해야 합니다.
이 특성 때문에 배치는 “몇 분 안에 끝날 수도, 24시간 가까이 걸릴 수도 있다”는 전제를 깔고 설계해야 합니다. 마감 시한이 정해진 작업이라면 여유 시간을 넉넉히 두고 배치를 미리 돌려야 하고, 실시간성이 조금이라도 필요한 작업이라면 애초에 배치 대상에서 제외하는 편이 안전합니다. 또한 배치 전용 요청 한도는 동기 API의 요청 한도와 별도로 관리되므로, 동기 API 한도가 여유 있다고 배치도 무제한은 아니라는 점도 확인이 필요합니다.
이 기준으로 오늘 밤 배치로 옮길 작업부터 골라보세요
정리하면, LLM 배치 API로 비용을 절반 줄이는 전략은 ‘지금 당장 결과가 필요 없는 대량 작업’에만 적용해야 효과가 있습니다. 문서 요약, 임베딩 생성, 데이터 라벨링, 프롬프트 평가처럼 야간에 돌려도 무방한 작업부터 배치로 옮기는 것이 가장 안전한 시작점입니다. 반대로 사용자 응답, 스트리밍 UI, 멀티턴 체이닝처럼 즉시성이 필요한 작업은 배치 대상에서 제외해야 합니다.
지금 운영 중인 파이프라인 중에 사람이 결과를 몇 시간 뒤에 확인해도 되는 작업이 있다면, 그중 하나를 골라 jsonl 파일 100줄 정도 규모로 먼저 배치 API를 테스트해보는 것을 추천합니다. 실제 처리 시간과 결과 형식을 확인한 뒤 점진적으로 대상을 늘리면, 리스크 없이 비용 구조를 바꿔볼 수 있습니다.
