Batch API

Batch API

OpenAI의 Batch API를 쓰면 비동기 요청 그룹을 50% 저렴한 비용으로, 훨씬 더 높은 별도의 레이트 리밋 풀로, 명확한 24시간 처리 시간 안에 보낼 수 있어요. 즉시 응답이 필요 없는 처리 작업에 특히 적합한 서비스죠. 개요부터 볼게요.

출처: 공식문서

개요

OpenAI 플랫폼을 쓸 때 동기 요청이 필요한 경우도 있지만, 즉시 응답이 필요 없거나 레이트 리밋 때문에 많은 쿼리를 빠르게 실행하지 못하는 경우가 훨씬 많아요. 배치 처리 작업은 보통 이런 사용 사례에 도움이 됩니다.

  1. 평가(evaluation) 실행
  2. 대용량 데이터셋 분류
  3. 콘텐츠 저장소 임베딩
  4. 대규모 오프라인 비디오 렌더 작업 큐잉

Batch API는 일련의 요청을 단일 파일로 모으고, 그 요청들을 실행하는 배치 처리 작업을 시작하고, 실행 중 배치 상태를 조회하고, 완료되면 수집된 결과를 가져오는, 비교적 단순한 엔드포인트 집합을 제공해요. 표준 엔드포인트를 직접 쓰는 것과 비교하면 Batch API는 이런 장점이 있습니다.

  1. 비용 효율: 동기 API 대비 50% 할인
  2. 더 높은 레이트 리밋: 동기 API보다 훨씬 더 많은 여유(headroom)
  3. 빠른 완료: 각 배치는 24시간 안에(그리고 보통 더 빠르게) 완료

시작하기

1. 배치 파일 준비하기

배치는 각 줄이 API에 대한 개별 요청 세부 사항을 담은 .jsonl 파일에서 시작해요. 현재 사용 가능한 엔드포인트는 이렇습니다.

  • /v1/responses (Responses API)
  • /v1/chat/completions (Chat Completions API)
  • /v1/embeddings (Embeddings API)
  • /v1/completions (Completions API)
  • /v1/moderations (Moderation 가이드)
  • /v1/images/generations (Images API)
  • /v1/images/edits (Images API)
  • /v1/videos (비디오 생성 가이드)

주어진 입력 파일에서 각 줄의 body 필드 파라미터는 기본 엔드포인트의 파라미터와 같아요. 각 요청은 완료 후 결과를 참조하는 데 쓸 수 있는 고유한 custom_id 값을 포함해야 합니다. 아래는 요청 2개가 든 입력 파일 예시인데, 각 입력 파일은 단일 모델에 대한 요청만 담을 수 있다는 점을 기억하세요.

비디오 생성 배치에서는 이 점을 유의하세요:

  • Batch는 현재 POST /v1/videos만 지원해요.
  • 비디오 배치 요청은 multipart가 아니라 JSON이어야 합니다.
  • 자산을 미리 업로드하고 multipart 업로드 대신 요청 본문에 지원되는 자산 참조를 넘기세요.
  • 이미지 가이드 생성 배치에는 input_reference를 쓰세요. JSON 요청에서는 input_referencefile_id 또는 image_url이 있는 객체로 넘깁니다.
  • multipart input_reference 업로드(비디오 참조 입력 포함)는 Batch에서 지원되지 않아요.
  • Batch로 생성된 비디오는 배치 완료 후 최대 24시간 동안 다운로드할 수 있습니다.

/v1/moderations를 대상으로 할 때는 모든 요청 본문에 input 필드를 포함해야 해요. Batch는 omni-moderation-latest로 평문 텍스트 입력과 텍스트·이미지 입력이 있는 콘텐츠 배열을 받아요. 배치 워커는 동기 모더레이션 엔드포인트와 마찬가지로 stream=true를 설정한 요청을 거부합니다.

{"custom_id": "request-1", "method": "POST", "url": "/v1/chat/completions", "body": {"model": "gpt-3.5-turbo-0125", "messages": [{"role": "system", "content": "You are a helpful assistant."},{"role": "user", "content": "Hello world!"}],"max_tokens": 1000}}
{"custom_id": "request-2", "method": "POST", "url": "/v1/chat/completions", "body": {"model": "gpt-3.5-turbo-0125", "messages": [{"role": "system", "content": "You are an unhelpful assistant."},{"role": "user", "content": "Hello world!"}],"max_tokens": 1000}}

모더레이션 입력 예시 — 텍스트만 요청:

{
  "custom_id": "moderation-text-1",
  "method": "POST",
  "url": "/v1/moderations",
  "body": {
    "model": "omni-moderation-latest",
    "input": "This is a harmless test sentence."
  }
}

텍스트와 이미지 입력 요청:

{
  "custom_id": "moderation-mm-1",
  "method": "POST",
  "url": "/v1/moderations",
  "body": {
    "model": "omni-moderation-latest",
    "input": [
      {
        "type": "text",
        "text": "Describe this image"
      },
      {
        "type": "image_url",
        "image_url": {
          "url": "https://api.nga.gov/iiif/a2e6da57-3cd1-4235-b20e-95dcaefed6c8/full/!800,800/0/default.jpg"
        }
      }
    ]
  }
}

특히 멀티모달 모더레이션 요청에서는 base64 블롭 대신 image_url로 원격 자산을 참조하는 것을 권장해요. 그러면 .jsonl 파일을 Batch 업로드 한도인 200MB 아래로 유지할 수 있습니다.

2. 배치 입력 파일 업로드

파인튜닝 API와 비슷하게, 배치를 시작할 때 올바르게 참조할 수 있도록 먼저 입력 파일을 업로드해야 해요. Files API로 .jsonl 파일을 업로드합니다.

from openai import OpenAI

client = OpenAI()

batch_input_file = client.files.create(
    file=open("batchinput.jsonl", "rb"), purpose="batch"
)

print(batch_input_file)
curl https://api.openai.com/v1/files \
  -H "Authorization: Bearer ***" \
  -F purpose="batch" \
  -F file="@batchinput.jsonl"

업로드한 입력 파일의 File 객체 ID(여기서는 file-abc123으로 가정)로 배치를 만들 수 있어요. 현재 완료 창(completion window)은 24h로만 설정할 수 있고, 선택적으로 metadata 파라미터로 커스텀 메타데이터를 줄 수 있습니다.

batch = client.batches.create(
    input_file_id=batch_input_file.id,
    endpoint="/v1/chat/completions",
    completion_window="24h",
    metadata={"description": "nightly eval job"},
)
print(batch)

이 요청은 배치에 대한 메타데이터를 담은 Batch 객체를 반환해요:

{
  "id": "batch_abc123",
  "object": "batch",
  "endpoint": "/v1/chat/completions",
  "errors": null,
  "input_file_id": "file-abc123",
  "completion_window": "24h",
  "status": "validating",
  "output_file_id": null,
  "error_file_id": null,
  "created_at": 1714508499,
  "in_progress_at": null,
  "expires_at": 1714536634,
  "completed_at": null,
  "failed_at": null,
  "expired_at": null,
  "request_counts": {
    "total": 0,
    "completed": 0,
    "failed": 0
  },
  "metadata": null
}

3. 배치 상태 확인

배치 상태는 언제든 조회할 수 있고, 조회 시에도 Batch 객체가 반환돼요.

batch = client.batches.retrieve(batch.id)
print(batch)
curl https://api.openai.com/v1/batches/batch_abc123 \
  -H "Authorization: Bearer ***" \
  -H "Content-Type: application/json"

Batch 객체의 상태는 다음과 같을 수 있어요.

상태 설명
validating 배치가 시작되기 전에 입력 파일을 검증하는 중
failed 입력 파일이 검증 과정에서 실패함
in_progress 입력 파일이 성공적으로 검증되고 배치가 실행 중
finalizing 배치가 완료되고 결과를 준비 중
completed 배치가 완료되고 결과가 준비됨
expired 24시간 창 안에 배치를 완료하지 못함
cancelling 배치를 취소하는 중(최대 10분 소요)
cancelled 배치가 취소됨

4. 결과 가져오기

배치가 완료되면 Batch 객체의 output_file_id 필드에서 Files API로 출력을 요청하고, 이 경우엔 batch_output.jsonl로 로컬 파일에 써서 받아올 수 있어요.

import os

from openai import OpenAI

output_file_id = os.environ["OPENAI_BATCH_OUTPUT_FILE_ID"]
client = OpenAI()

file_response = client.files.content(output_file_id)
print(file_response.text)
curl https://api.openai.com/v1/files/file-xyz123/content \
  -H "Authorization: Bearer ***" > batch_output.jsonl

출력 .jsonl 파일에는 입력 파일의 각 성공 요청 줄에 대해 하나씩 응답 줄이 생겨요. 배치에서 실패한 요청은 그 오류 정보가 배치의 error_file_id로 찾을 수 있는 오류 파일에 기록됩니다. /v1/videos의 경우 완료된 배치 결과에는 이미 completed, failed, expired 같은 종료 상태에 도달한 비디오 객체가 들어 있어요. 반환된 비디오 ID로 배치가 끝나자마자 최종 자산을 내려받을 수 있습니다.

출력 줄 순서는 입력 줄 순서와 일치하지 않을 수 있어요. 결과 처리에서 순서에 의존하지 말고, 출력 파일의 각 줄에 있는 custom_id 필드로 입력 요청과 출력 결과를 매핑하세요.

{"id": "batch_req_123", "custom_id": "request-2", "response": {"status_code": 200, "request_id": "req_123", "body": {"id": "chatcmpl-123", "object": "chat.completion", "created": 1711652795, "model": "gpt-3.5-turbo-0125", "choices": [{"index": 0, "message": {"role": "assistant", "content": "Hello."}, "logprobs": null, "finish_reason": "stop"}], "usage": {"prompt_tokens": 22, "completion_tokens": 2, "total_tokens": 24}, "system_fingerprint": "fp_123"}}, "error": null}
{"id": "batch_req_456", "custom_id": "request-1", "response": {"status_code": 200, "request_id": "req_789", "body": {"id": "chatcmpl-abc", "object": "chat.completion", "created": 1711652789, "model": "gpt-3.5-turbo-0125", "choices": [{"index": 0, "message": {"role": "assistant", "content": "Hello! How can I assist you today?"}, "logprobs": null, "finish_reason": "stop"}], "usage": {"prompt_tokens": 20, "completion_tokens": 9, "total_tokens": 29}, "system_fingerprint": "fp_3ba"}}, "error": null}

출력 파일은 배치 완료 30일 후 자동으로 삭제됩니다.

5. 배치 취소

필요하다면 진행 중인 배치를 취소할 수 있어요. 배치 상태는 진행 중 요청이 완료될 때까지(최대 10분) cancelling으로 바뀌었다가, 이후 cancelled로 바뀝니다.

import os

from openai import OpenAI

batch_id = os.environ["OPENAI_BATCH_ID"]
client = OpenAI()

client.batches.cancel(batch_id)
curl https://api.openai.com/v1/batches/batch_abc123/cancel \
  -H "Authorization: Bearer ***" \
  -H "Content-Type: application/json" \
  -X POST

6. 배치 목록 확인

언제든 모든 배치를 확인할 수 있어요. 배치가 많다면 limitafter 파라미터로 페이지네이션하면 됩니다.

from openai import OpenAI

client = OpenAI()

client.batches.list(limit=10)
curl https://api.openai.com/v1/batches?limit=10 \
  -H "Authorization: Bearer ***" \
  -H "Content-Type: application/json"

모델 가용성

Batch API는 대부분의 모델에서 널리 사용할 수 있지만 전부는 아니에요. 사용하는 모델이 Batch API를 지원하는지 모델 레퍼런스 문서로 확인해 보세요.

레이트 리밋

Batch API 레이트 리밋은 기존 모델별 레이트 리밋과 분리돼 있어요. Batch API에는 세 종류의 레이트 리밋이 있습니다.

  1. 배치별 한도: 단일 배치는 최대 50,000개 요청을 포함할 수 있고, 배치 입력 파일은 최대 200MB입니다. /v1/embeddings 배치는 배치의 모든 요청을 합쳐 최대 50,000개의 임베딩 입력으로 제한된다는 점도 기억하세요.
  2. 모델당 큐 프롬프트 토큰: 각 모델은 배치 처리에 큐에 넣을 수 있는 최대 프롬프트 토큰 수가 있어요. 플랫폼 설정 페이지에서 확인할 수 있습니다.
  3. 배치 생성 레이트 리밋: 시간당 최대 2,000개 배치를 만들 수 있어요. 더 많은 요청을 제출해야 한다면 배치당 요청 수를 늘리세요.

Batch API에는 현재 출력 토큰 한도가 없어요. Batch API 레이트 리밋은 새롭고 별개의 풀이므로, Batch API를 사용해도 표준 모델별 레이트 리밋의 토큰을 소모하지 않아요. 따라서 API를 조회할 때 사용할 수 있는 요청 수와 처리 토큰 수를 늘리는 편리한 방법이 되어 줍니다.

배치 만료

시간 내에 완료되지 않는 배치는 결국 expired 상태로 이동하고, 배치 내 미완료 요청은 취소되며 완료된 요청에 대한 응답은 배치의 출력 파일로 제공돼요. 완료된 요청에서 소모된 토큰에 대해서는 과금됩니다.

만료된 요청은 아래처럼 오류 파일에 기록되고, 만료된 요청의 데이터를 찾으려면 custom_id를 쓰면 됩니다.

{"id": "batch_req_123", "custom_id": "request-3", "response": null, "error": {"code": "batch_expired", "message": "This request could not be executed before the completion window expired."}}
{"id": "batch_req_123", "custom_id": "request-7", "response": null, "error": {"code": "batch_expired", "message": "This request could not be executed before the completion window expired."}}

더 알아보기 (Learn more)