CATEGORY

카테고리 (683)
AI (81)
Language & Specs (260)
FrameWork (36)
Library (20)
App (41)
Git (10)
Build & Dependency (2)
AWS (15)
DataBase (45)
OS (33)
Tool (17)
IT (120)
SEEMINGLY ONLINE

Seemingly
Online

이모저모 방방곡곡 두루두루 개발지식 저장소

RECENT POSTS

AI/Gemini

gemini batch api 사용법 — python 예제와 요금

반응형

Gemini batch API는 요청 여러 개를 한 번에 맡겨 두고 나중에 결과를 받는 기능입니다. 요금은 표준의 50%이고, 공식 문서가 밝힌 목표 처리 시간은 24시간(대부분은 더 빠름)이며 48시간 안에 못 끝나면 만료됩니다. 파이썬에서는 client.batches.create(model=..., src=...) 하나로 만드는데, src에 요청 목록을 바로 넣거나(전체 20MB 미만) JSONL 파일을 올린 이름을 넣습니다. 결과는 batches.get()으로 상태가 JOB_STATE_SUCCEEDED가 된 뒤 읽고, 요청마다 붙인 key로 짝을 맞춥니다. 무료 등급에서는 쓸 수 없습니다. 아래는 2026년 10월 18일 공식 문서와 google-genai 파이썬 SDK 2.31.0 기준입니다.

1. gemini batch api란 — 언제 쓰나

평소의 generate_content는 요청을 보내면 그 자리에서 답이 옵니다. 배치는 요청을 묶어서 맡기고, 끝나면 찾아가는 방식입니다. 기다림을 받아들이는 대신 얻는 것은 이렇습니다.

  • 비용 50% 할인: 같은 모델의 표준 요금의 절반입니다. 생각(thinking) 토큰이 포함된 출력도 절반입니다.
  • 별도 속도 제한: 공식 문서 기준으로 배치는 일반 호출과 분리된 자기 한도를 씁니다.
  • 대량 처리: 입력 파일 하나가 최대 2GB입니다.

공식 문서가 드는 예는 데이터 전처리와 평가 실행입니다. 실무로 옮기면 쌓인 문의 분류, 상품 설명 일괄 생성, 지난달 로그 요약처럼 "오늘 안에만 끝나면 되는 일"이 대상입니다. 사용자가 화면 앞에서 기다리는 챗봇에는 맞지 않습니다.

⚠️ 쓰기 전에 확인할 두 가지
① 무료 등급은 안 됩니다. 요금 문서의 배치 표에서 Free Tier 칸은 모두 "Not available"입니다. 결제가 연결된 유료 등급 프로젝트의 키가 필요합니다(키 만드는 법은 gemini api 키 발급 글).
② generateContent API 전용입니다. 문서 첫 줄에 이 기능은 generateContent API에서만 된다고 적혀 있습니다. client.interactions.create()로 짠 코드는 배치에 그대로 넣을 수 없고, 요청을 contents 형식으로 다시 써야 합니다.

2. gemini batch api 사용법 — 인라인과 파일 두 가지

배치를 만드는 방법은 두 가지이고, src에 무엇을 넘기느냐만 다릅니다.

방식 src에 넣는 것 크기 결과가 오는 곳
인라인요청 dict 리스트전체 20MB 미만job.dest.inlined_responses
입력 파일File API로 올린 JSONL의 이름 (files/...)파일 하나 최대 2GBjob.dest.file_name (결과 JSONL)

요청 하나는 평소 generate_content에 넣던 것과 같습니다. contents가 기본이고, 문서 예제처럼 요청마다 config로 temperature, 시스템 지시(system_instruction), 도구(Google 검색 등)를 따로 줄 수 있습니다. 여기에 결과와 짝을 맞출 key를 붙여 둡니다.

3. gemini batch api python 예제 — 인라인 요청

질문 두 개를 인라인으로 맡기는 예제입니다. 실제 작업이라면 questions 자리에 수천 건이 들어간다고 보면 됩니다. API 키는 GEMINI_API_KEY 환경 변수에서 읽습니다.

import time
from google import genai

client = genai.Client()

questions = {
    "q-1": "하늘은 왜 파란가요? 한 문장으로.",
    "q-2": "광합성을 한 문장으로 설명해 주세요.",
}

# ① 배치 만들기 — 요청 목록을 src 에 바로 넘긴다 (전체 20MB 미만)
inline_requests = [
    {
        "contents": [{"parts": [{"text": text}], "role": "user"}],
        "config": {"temperature": 0.2},
        "metadata": {"key": key},
    }
    for key, text in questions.items()
]
job = client.batches.create(
    model="gemini-3.8-flash",
    src=inline_requests,
    config={"display_name": "faq-batch"},
)
print("created:", job.name, job.state.name)

# ② 끝 상태가 될 때까지 30초마다 확인
done = {"JOB_STATE_SUCCEEDED", "JOB_STATE_FAILED",
        "JOB_STATE_CANCELLED", "JOB_STATE_EXPIRED"}
while job.state.name not in done:
    time.sleep(30)
    job = client.batches.get(name=job.name)
    print("poll:", job.state.name)

# ③ 결과 읽기 — metadata 의 key 로 짝을 맞춘다
if job.state.name == "JOB_STATE_SUCCEEDED":
    for r in job.dest.inlined_responses:
        key = r.metadata.get("key") if r.metadata else None
        if r.response:
            print(key, r.response.text)
        else:
            print(key, "ERROR", r.error.code, r.error.message)
else:
    print("not succeeded:", job.state.name, job.error)

API 키 없이 SDK 2.31.0에 가짜 서버를 붙여 돌려 보면, 배치 생성은 POST /v1beta/models/gemini-3.8-flash:batchGenerateContent로 나갑니다. 이때 SDK가 요청을 REST 형식으로 바꿔 주는데, metadata는 request 바깥으로 빠지고 config의 temperature는 generationConfig로 바뀌어 나갑니다. 가짜 서버가 한 건을 400 에러로 돌려주게 해 두었을 때 출력은 아래와 같습니다. 답 문장은 가짜 서버가 정한 값입니다.

created: batches/inline123 JOB_STATE_PENDING
poll: JOB_STATE_RUNNING
poll: JOB_STATE_SUCCEEDED
q-1 하늘이 파란 건 레일리 산란 때문입니다.
q-2 ERROR 400 Invalid argument
💡 SUCCEEDED여도 실패가 섞인다
배치 상태가 JOB_STATE_SUCCEEDED라는 건 배치가 끝났다는 뜻이지 모든 요청이 성공했다는 뜻이 아닙니다. 위 출력의 q-2처럼 요청마다 response 대신 error가 올 수 있으니 하나씩 확인합니다. 문서는 REST 응답의 batchStats에서 failedRequestCount를 보라고 권하지만, 파이썬 SDK 2.31.0의 BatchJob 객체에는 이 값이 실리지 않습니다(가짜 서버가 batchStats를 넣어 보내도 job.completion_stats는 None). SDK로는 결과를 돌면서 직접 세는 쪽이 확실합니다.

4. gemini batch api 파일 입력 — JSONL 예제

요청이 많으면 JSONL 파일을 쓰는 것이 공식 권장입니다. 한 줄에 요청 하나를 적고, 줄마다 key와 request를 둡니다.

import json
from google import genai
from google.genai import types

client = genai.Client()

questions = {
    "q-1": "하늘은 왜 파란가요? 한 문장으로.",
    "q-2": "광합성을 한 문장으로 설명해 주세요.",
}

# ① 한 줄에 요청 하나: key + request
with open("requests.jsonl", "w", encoding="utf-8") as f:
    for key, text in questions.items():
        row = {"key": key, "request": {"contents": [{"parts": [{"text": text}]}]}}
        f.write(json.dumps(row, ensure_ascii=False) + "\n")

# ② File API 로 올리고, 파일 이름으로 배치 만들기
uploaded = client.files.upload(
    file="requests.jsonl",
    config=types.UploadFileConfig(display_name="requests", mime_type="jsonl"),
)
job = client.batches.create(
    model="gemini-3.8-flash",
    src=uploaded.name,
    config={"display_name": "faq-batch-file"},
)
print("created:", job.name)

# (상태 확인 반복문은 인라인 예제와 같다)

# ③ 결과 파일 내려받기 — 줄마다 response 또는 error
job = client.batches.get(name=job.name)
if job.state.name == "JOB_STATE_SUCCEEDED":
    raw = client.files.download(file=job.dest.file_name)
    for line in raw.decode("utf-8").splitlines():
        row = json.loads(line)
        if "response" in row:
            text = row["response"]["candidates"][0]["content"]["parts"][0]["text"]
            print(row.get("key"), text)
        else:
            print(row.get("key"), "ERROR", row["error"]["message"])

같은 가짜 서버로 돌리면 배치 생성 본문은 {"batch": {"inputConfig": {"fileName": "files/..."}, ...}}로, 요청 내용 없이 파일 이름만 나갑니다. files.download()는 bytes를 돌려주므로 decode("utf-8") 후 줄 단위로 파싱합니다.

created: batches/file123
q-1 답 1
q-2 ERROR Invalid argument
⚠️ 파일 줄에는 SDK 이름을 쓰지 않는다
인라인 요청의 config는 SDK가 generationConfig 같은 REST 이름으로 바꿔 보내지만, JSONL 파일은 SDK가 손대지 않고 그대로 올라갑니다. 그래서 파일 줄에는 REST 형식으로 적어야 하고, 공식 문서 예시는 "generation_config": {"temperature": 0.7}처럼 request 안에 바로 둡니다. 인라인 코드를 파일 방식으로 옮길 때 config를 그대로 복사하지 않도록 주의합니다.

5. batch 상태값 — pending이 오래 갈 때

SDK는 서버의 BATCH_STATE_* 값을 JOB_STATE_*로 바꿔서 줍니다. job.state는 문자열이 아니라 enum이라, 비교할 때는 위 예제처럼 job.state.name을 씁니다.

state 뜻 (공식 문서)
JOB_STATE_PENDING만들어졌고 처리 순서를 기다리는 중
JOB_STATE_RUNNING처리 중
JOB_STATE_SUCCEEDED끝남, 결과를 받을 수 있음
JOB_STATE_FAILED배치 자체가 실패, job.error 확인
JOB_STATE_CANCELLED사용자가 취소함
JOB_STATE_EXPIRED대기·처리가 48시간을 넘어 만료, 받을 결과 없음

JOB_STATE_PENDING이 오래 가는 건 고장이 아닙니다. 목표 처리 시간이 24시간이고 실제 시간은 시스템 부하와 배치 크기에 따라 달라진다고 문서가 적어 둡니다. 다만 48시간을 넘기면 EXPIRED가 되고 결과가 하나도 남지 않으니, 문서 권고대로 큰 배치는 잘게 나눠 보내면 중간 결과를 먼저 받을 수 있습니다.

그 밖에 알아 둘 동작은 이렇습니다.

  • 생성은 멱등이 아닙니다. 같은 생성 요청을 두 번 보내면 배치가 두 개 생기고 비용도 두 번 듭니다. 재시도 로직에 batches.create를 넣을 때 조심합니다.
  • 결과 보관은 6주입니다. 그 뒤에는 영구 삭제됩니다.
  • 취소는 client.batches.cancel(name=job.name)(SDK가 POST /v1beta/batches/{id}:cancel로 보냄), 삭제는 client.batches.delete(name=job.name), 목록은 client.batches.list()입니다.
  • 반복 확인 대신 웹훅도 됩니다. client.webhooks.create()에 batch.succeeded·batch.failed 이벤트를 구독하면 끝났을 때 내 서버로 알림이 옵니다.

6. gemini batch api pricing — 얼마나 싸지나

요금 문서에는 모델마다 Standard 표와 Batch 표가 따로 있습니다. Gemini 3.8 Flash 기준으로 보면 이렇습니다(1M 토큰당 · 2026년 10월 18일 확인).

Gemini 3.8 Flash 표준 입력 / 출력 배치 입력 / 출력
2026년 12월 31일까지$0.75 / $3.75$0.375 / $1.875
2027년 1월 1일부터$1.50 / $7.50$0.75 / $3.75
무료 등급무료사용 불가

예를 들어 질문 10,000건을 건당 입력 400토큰, 출력 200토큰으로 돌리면 올해 가격으로 표준 4M × $0.75 + 2M × $3.75 = $10.50, 배치 4M × $0.375 + 2M × $1.875 = $5.25입니다. 내년 1월부터는 표준 $21.00, 배치 $10.50이 됩니다. 3.8 Flash 가격은 기간 한정이라 표에 날짜가 붙어 있으니, 견적을 낼 때는 요금 문서를 다시 확인하세요.

컨텍스트 캐싱과 함께 쓸 수 있습니다. 배치 안의 요청 설정에 cached_content로 캐시 이름을 넣으면 되고, 캐시에 적중한 부분은 문서 표현 그대로 표준 컨텍스트 캐싱 요금이 붙습니다. 3.8 Flash의 배치 표에는 캐싱 입력도 $0.0375(올해)로 따로 적혀 있습니다. 캐시를 만드는 법은 gemini 컨텍스트 캐싱 글에 정리해 두었습니다.

7. gemini batch api limits — 한도와 quota

배치는 일반 호출과 분리된 한도를 씁니다. 모든 모델에 공통인 한도는 아래와 같습니다.

항목 한도 (공식 문서)
동시에 돌릴 수 있는 배치100개
입력 파일 크기2GB
파일 저장 용량20GB
인라인 요청전체 20MB 미만

여기에 모델마다 대기 토큰(Batch enqueued tokens) 한도가 있습니다. 진행 중인 모든 배치에 걸쳐, 한 모델에 처리 대기로 쌓아 둘 수 있는 토큰의 합입니다. Gemini 3.8 Flash는 등급별로 다음과 같습니다.

등급 대기 토큰 한도 (3.8 Flash)
Tier 13,000,000
Tier 2400,000,000
Tier 31,000,000,000

Tier 1의 300만 토큰은 생각보다 작습니다. 위 비용 예시(입력만 400만 토큰)는 Tier 1에서는 한 번에 다 넣을 수 없으니 나눠 보내야 합니다. 모델마다 값이 다르므로 쓰는 모델의 줄을 rate limits 문서에서 확인하세요. 일반 호출에서 한도에 걸렸을 때의 증상과 대처는 gemini api 429 에러 글에 있습니다.

자주 묻는 질문 (FAQ)

Q. gemini batch api response time은 얼마나 걸리나요?
공식 문서가 보장하는 숫자는 없고, 목표(SLO)가 24시간이며 "대부분은 훨씬 빠르다"고만 적혀 있습니다. 크기와 시스템 부하에 따라 달라지고, 48시간을 넘으면 만료됩니다. 몇 분 안에 답이 필요하면 배치가 아니라 일반 호출을 씁니다.

Q. 결과 순서가 입력 순서와 같나요?
공식 문서에는 결과 순서에 대한 서술이 없고, 대신 요청마다 붙인 key가 결과에 같은 이름으로 달려 온다고 설명합니다. 그래서 위 예제처럼 순서가 아니라 key로 짝을 맞춥니다.

Q. OpenAI·클로드 batch api와 뭐가 다른가요?
할인 폭(50%)은 셋이 같습니다. 입력 방식은 제미나이가 둘 다 지원하는 셈입니다. OpenAI는 JSONL 파일을 올려야 하고(openai batch api 사용법), 클로드는 요청 배열을 본문에 바로 넣는데(claude batch api 사용법), 제미나이는 20MB 미만이면 인라인, 크면 파일입니다. 만료 기준은 OpenAI·클로드가 24시간, 제미나이는 48시간입니다. 참고로 제미나이는 OpenAI 호환 레이어로도 배치를 받습니다.

Q. 임베딩도 배치로 되나요?
됩니다. 공식 문서에 임베딩 배치 절이 따로 있고, 인라인 결과는 job.dest.inlined_embed_content_responses에 담깁니다.

마무리

정리하면 gemini batch api는 요청을 src에 넘기고(20MB 미만이면 인라인, 크면 JSONL 파일), 끝나면 key로 결과를 맞추는 대신 요금이 절반인 방식입니다. 놓치기 쉬운 곳은 네 가지입니다. 무료 등급과 Interactions API에서는 안 되고, SUCCEEDED여도 요청별 error를 확인해야 하며, 생성 요청을 두 번 보내면 배치가 두 개 생기고, Tier 1의 대기 토큰 한도가 생각보다 작다는 점입니다. 바로 답이 필요 없는 대량 작업부터 옮겨 보세요.


📚 참고 출처 (2026년 10월 18일 확인 · google-genai 파이썬 SDK 2.31.0)
· Batch API — Gemini API
· Rate limits — Gemini API
· Pricing — Gemini API
· Batch API reference — Gemini API

반응형

COMMENTS