본문 바로가기
AI System/OpenAI API와 바이브 코딩으로 배우는 AI 서비스 개발

d02 - 05. Image - 01. generation

by Toddler_AD 2026. 6. 2.

01. 이미지 생성 API 직접 호출 v2

이 노트북은 Responses API의 image_generation 도구와 Images API의 images.generate를 직접 호출합니다. 같은 이미지 생성이라도 어느 API를 쓰는지, 어떤 매개변수가 요청에 들어가는지 눈으로 확인하는 것이 목표입니다.

# Colab 또는 로컬 노트북 실행 환경을 구분하기 위해 sys를 가져옵니다.
import sys
# 패키지 설치 명령을 현재 Python 커널에서 실행하기 위해 subprocess를 가져옵니다.
import subprocess
# 패키지 설치 여부를 확인하기 위해 importlib.util을 가져옵니다.
import importlib.util
# 환경 변수에서 API 키를 읽고 설정하기 위해 os를 가져옵니다.
import os
# API 키를 화면에 노출하지 않고 입력받기 위해 getpass를 가져옵니다.
import getpass
# .env 파일 위치를 다루기 위해 pathlib의 Path를 가져옵니다.
from pathlib import Path

# google.colab 모듈이 있으면 현재 런타임이 Google Colab이라고 판단합니다.
IN_COLAB = "google.colab" in sys.modules
# 노트북에서 사용하는 import 이름과 pip 패키지 이름을 짝지어 둡니다.
REQUIRED_PACKAGES = {
    "openai": "openai>=2.26.0",
    "dotenv": "python-dotenv>=1.2.2",
    "pydantic": "pydantic>=2.11.0",
    "PIL": "pillow>=12.1.1",
    "requests": "requests>=2.32.5",
    "numpy": "numpy>=2.3.3",
    "websockets": "websockets>=15.0.1",
    "websocket": "websocket-client>=1.8.0",
    "nest_asyncio": "nest-asyncio>=1.6.0",
}

# 현재 커널에서 import할 수 없는 패키지만 설치하는 함수입니다.
def ensure_package(import_name: str, package_name: str) -> None:
    # 이미 import 가능한 패키지는 설치를 건너뜁니다.
    if importlib.util.find_spec(import_name) is not None:
        # 설치가 필요 없음을 호출자에게 조용히 알리고 돌아갑니다.
        return
    # Colab에서는 현재 노트북 커널의 Python에 패키지를 설치해야 합니다.
    subprocess.check_call(
        [sys.executable, "-m", "pip", "install", "-q", package_name]
    )

# Colab 기본 런타임에는 일부 OpenAI 실습 패키지가 없을 수 있으므로 먼저 준비합니다.
if IN_COLAB:
    # 실습 전체에서 필요한 패키지를 하나씩 확인하고 부족한 것만 설치합니다.
    for import_name, package_name in REQUIRED_PACKAGES.items():
        # 누락된 패키지를 현재 Colab 런타임에 설치합니다.
        ensure_package(import_name, package_name)

# 패키지 설치 이후 .env 로딩 기능을 사용할 수 있도록 load_dotenv를 가져옵니다.
from dotenv import load_dotenv

# 현재 작업 폴더의 .env 파일이 있으면 로컬 실행처럼 환경 변수로 올립니다.
load_dotenv(Path.cwd() / ".env")
# OPENAI_API_KEY가 이미 있으면 그대로 사용하고, 없으면 Colab Secrets 또는 입력으로 보완합니다.
if not os.getenv("OPENAI_API_KEY"):
    # Colab Secrets에서 OPENAI_API_KEY를 읽어 볼 변수를 준비합니다.
    secret_key = None
    # Colab이 아닌 로컬 환경에서는 google.colab import가 실패할 수 있으므로 예외를 허용합니다.
    try:
        # Colab Secrets의 userdata API를 가져옵니다.
        from google.colab import userdata

        # Secrets에 저장된 OPENAI_API_KEY 값을 읽습니다.
        secret_key = userdata.get("OPENAI_API_KEY")
    except Exception:
        # Colab Secrets를 사용할 수 없으면 이후 수동 입력 단계로 넘어갑니다.
        secret_key = None
    # Secrets에서 키를 찾았다면 현재 런타임 환경 변수로 설정합니다.
    if secret_key:
        # OpenAI SDK가 자동으로 읽을 수 있도록 표준 환경 변수 이름에 저장합니다.
        os.environ["OPENAI_API_KEY"] = secret_key
    else:
        # 키가 없으면 노트북 실행자가 직접 입력하도록 요청합니다.
        entered_key = getpass.getpass("OPENAI_API_KEY를 입력하세요: ").strip()
        # 빈 문자열이 아닌 값을 입력한 경우에만 환경 변수로 등록합니다.
        if entered_key:
            # OpenAI SDK가 자동으로 읽을 수 있도록 표준 환경 변수 이름에 저장합니다.
            os.environ["OPENAI_API_KEY"] = entered_key

# API 키가 끝까지 없으면 다음 OpenAI API 호출이 실패하므로 명확한 오류를 냅니다.
if not os.getenv("OPENAI_API_KEY"):
    # Colab에서는 Secrets 또는 입력, 로컬에서는 .env 또는 환경 변수를 설정해야 함을 알려 줍니다.
    raise RuntimeError(
        "OPENAI_API_KEY가 없습니다. Colab Secrets, 수동 입력, 또는 .env 파일로 설정해 주세요."
    )

# 이후 셀에서 Colab 여부를 참고할 수 있도록 간단히 출력합니다.
print(f"개발환경: {'Colab' if IN_COLAB else '로컬'}")
# API 키를 직접 출력하지 않고 준비 완료 여부만 알려 줍니다.
print("OPENAI_API_KEY 준비 완료")
개발환경: 로컬
OPENAI_API_KEY 준비 완료
 

실습 전에 보는 핵심 개념과 API

핵심 개념

  • 이 노트북은 이미지 생성 요청을 두 가지 방식으로 비교합니다. 하나는 Responses API에서 image_generation 도구를 호출하는 방식이고, 다른 하나는 Images API를 직접 호출하는 방식입니다.
  • Responses API는 텍스트 지시와 도구 호출을 한 요청 안에서 함께 처리하기 좋습니다. 반대로 Images API는 이미지 생성 전용 인터페이스라서 size, quality, background 같은 옵션을 더 직접적으로 읽고 비교하기 쉽습니다.
  • 생성 결과는 보통 Base64 문자열로 반환되므로, 실제 실습에서는 이를 PNG 같은 파일로 저장하고 메타데이터와 함께 남겨 두는 흐름이 중요합니다.

API 소개

  • client.responses.create(...): 멀티모달 입력, 텍스트 응답, 도구 호출을 함께 다루는 통합 API입니다.
  • client.images.generate(...): 이미지 생성만 수행하는 전용 API입니다.
  • 이 노트북은 두 API가 같은 프롬프트를 어떻게 처리하는지 비교하고, 이후 단계에서 출력 옵션을 조절해 결과 차이를 확인합니다.

API 호출부와 주요 매개변수

response = client.responses.create(
    model=MODEL_NAME,
    input=prompt,
    tools=[{"type": "image_generation"}],
)

image_response = client.images.generate(
    model=IMAGE_MODEL,
    prompt=prompt,
    size="1024x1024",
    quality="high",
    background="opaque",
    output_format="png",
    n=1,
)
  • model
    • responses.create(...)에서는 보통 gpt-5, gpt-5-mini, gpt-5.5처럼 응답 생성을 담당할 모델명을 넣습니다.
    • images.generate(...)에서는 보통 gpt-image-1, gpt-image-1-mini, gpt-image-1.5 같은 이미지 모델을 넣습니다.
    • 기본값: responses.create(...)에는 사실상 기본값이 없어서 명시하는 것이 안전합니다. 이미지 생성 쪽 문서 기준 기본값은 gpt-image-1입니다.
  • input / prompt
    • responses.create(...)의 input에는 문자열 하나를 넣을 수도 있고, 메시지 배열이나 멀티모달 입력 블록을 넣을 수도 있습니다.
    • images.generate(...)의 prompt에는 생성하고 싶은 장면 설명 문자열을 넣습니다.
    • 기본값: 둘 다 실질적인 기본값은 없습니다. 생략하면 모델이 무엇을 생성해야 하는지 정할 수 없으므로 직접 넣는 것이 전제입니다.
  • tools
    • Responses API에서는 [{"type": "image_generation"}]처럼 도구 배열을 넘겨 모델이 이미지 생성 도구를 사용할 수 있게 합니다.
    • 문서상 tools는 선택 매개변수이며, 함수 도구(function calling, 함수 호출), file_search, image_generation 같은 여러 도구 타입이 올 수 있습니다.
    • 기본값: 생략하면 도구를 사용하지 않는 것으로 보면 됩니다.
  • size
    • GPT 이미지 모델에서는 보통 1024x1024, 1024x1536, 1536x1024, auto를 사용합니다.
    • 더 최신 이미지 모델에서는 WIDTHxHEIGHT 형식의 더 다양한 해상도도 허용될 수 있습니다.
    • 기본값: 최신 문서 기준 기본값은 auto입니다.
  • quality
    • 가능한 값은 low, medium, high, auto입니다.
    • 품질이 높아질수록 대체로 생성 시간과 비용이 커질 수 있습니다.
    • 기본값: 최신 문서 기준 기본값은 auto입니다.
  • background
    • 가능한 값은 transparent, opaque, auto입니다.
    • 배경 제거가 필요한 에셋 이미지면 transparent, 일반 썸네일이나 사진 스타일이면 opaque를 주로 생각하면 됩니다.
    • 기본값: 최신 문서 기준 기본값은 auto입니다.
  • output_format
    • 가능한 값은 png, webp, jpeg입니다.
    • 투명 배경 보존이 필요하면 보통 png, 파일 크기를 더 줄이고 싶으면 webp나 jpeg를 고려합니다.
    • 기본값: 최신 문서 기준 기본값은 png입니다.
  • n
    • 한 번의 요청에서 몇 장의 후보 이미지를 만들지 지정하는 정수입니다.
    • 학습용 실습에서는 비용과 비교 난이도를 낮추기 위해 보통 1을 많이 사용합니다.
    • 기본값: 모델과 엔드포인트에 따라 동작이 달라질 수 있어, 이 노트북처럼 1을 명시하는 것이 가장 안전합니다.

요청/응답 필드에서 볼 것

  • 실습을 시작할 때는 요청 쪽에서 model, prompt 또는 input, tools, size, quality를 먼저 읽고, "어느 API에 무엇을 부탁했는지"를 한 줄로 설명해 보는 것이 좋습니다.
  • Responses API 결과를 확인할 때는 먼저 response.id, response.status, response.output_text가 있는지 보고, 그다음 image_generation 출력 안의 result 또는 관련 필드에서 실제 이미지 Base64를 찾으면 됩니다.
  • Images API 결과를 확인할 때는 data[0].b64_json을 바로 저장해 보고, 저장이 끝난 뒤 data[0].revised_prompt가 있으면 원래 프롬프트와 어떻게 달라졌는지 비교해 보세요.
  • 결과가 기대와 다르면 가장 먼저 size, quality, background, output_format, n이 요청에서 실제로 어떤 값으로 들어갔는지 메타데이터 JSON과 함께 다시 확인하면 됩니다.
  • 학습 로그에는 response.id, 저장한 파일 경로, usage를 함께 남겨 두면 같은 프롬프트를 다른 설정으로 다시 실행했을 때 비교가 쉬워집니다.
 

1단계. 실행 환경 준비

API 키와 모델명, 저장 폴더를 먼저 정합니다. 이미지 생성은 결과 파일과 메타데이터가 함께 남아야 나중에 비교와 편집 실습으로 이어질 수 있습니다.

# 운영체제 환경 변수를 읽기 위해 os 모듈을 가져옵니다.
import os
# JSON 메타데이터를 저장하고 보기 좋게 출력하기 위해 json 모듈을 가져옵니다.
import json
# 이미지의 base64 문자열을 파일 바이트로 바꾸기 위해 base64 모듈을 가져옵니다.
import base64
# API 호출 시간을 측정하기 위해 time 모듈을 가져옵니다.
import time
# 파일 경로를 운영체제에 맞게 안전하게 다루기 위해 Path를 가져옵니다.
from pathlib import Path
# 타입 힌트에서 응답 객체의 유연한 구조를 표현하기 위해 Any를 가져옵니다.
from typing import Any
# .env 파일에 저장된 OPENAI_API_KEY를 현재 프로세스 환경 변수로 올리기 위해 load_dotenv를 가져옵니다.
from dotenv import load_dotenv
# OpenAI API를 직접 호출하기 위해 공식 SDK의 OpenAI 클라이언트를 가져옵니다.
from openai import OpenAI

# 실습용 기준 폴더를 현재 실행 위치로 저장합니다.
CURRENT_DIR = Path.cwd()
# 루트에서 실행할 때 사용할 이미지 트랙 폴더를 찾습니다.
if (CURRENT_DIR / "05.image").exists():
    # 워크스페이스 루트에서 실행 중이면 05.image 폴더를 실습 루트로 사용합니다.
    IMAGE_ROOT = CURRENT_DIR / "05.image"
# 하위 실습 폴더에서 실행할 때를 처리합니다.
elif "05.image" in [part for part in CURRENT_DIR.parts]:
    # 현재 경로 조각 중 05.image까지를 찾아 실습 루트로 복원합니다.
    IMAGE_ROOT = Path(
        *CURRENT_DIR.parts[: CURRENT_DIR.parts.index("05.image") + 1]
    )
# 예상 밖 위치에서 실행할 때도 상대 경로로 실습 루트를 구성합니다.
else:
    # 현재 위치 기준의 05.image 폴더를 실습 루트로 사용합니다.
    IMAGE_ROOT = CURRENT_DIR / "05.image"
# .env 파일이 있는 워크스페이스 루트는 이미지 트랙 폴더의 상위입니다.
WORKSPACE_ROOT = IMAGE_ROOT.parent
# 생성물 저장 폴더를 정합니다.
OUTPUT_DIR = IMAGE_ROOT / "output" / "v2"
# 생성물 저장 폴더가 없으면 새로 만듭니다.
OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
# 워크스페이스 루트의 .env 파일을 읽습니다.
load_dotenv(WORKSPACE_ROOT / ".env")
# API 키가 없으면 뒤쪽 API 호출이 모호하게 실패하므로 여기서 명확히 중단합니다.
if not os.getenv("OPENAI_API_KEY"):
    # 교육생이 바로 해결할 수 있도록 필요한 환경 변수 이름을 메시지에 포함합니다.
    raise RuntimeError(
        "OPENAI_API_KEY가 없습니다. 워크스페이스 루트의 .env 파일을 확인해 주세요."
    )
# OpenAI API 클라이언트를 생성합니다.
client = OpenAI()
# Responses API에서 이미지 생성과 이미지 이해를 함께 실습할 모델명을 읽습니다.
RESPONSES_MODEL = os.getenv("OPENAI_RESPONSES_MODEL", "gpt-5-mini")
# Images API에서 이미지 생성과 편집을 실습할 모델명을 읽습니다.
IMAGE_MODEL = os.getenv("OPENAI_IMAGE_API_MODEL", "gpt-image-1-mini")

# 워크스페이스 기준 상대 경로를 보여 주는 작은 함수를 정의합니다.
def workspace_path(path: Path) -> str:
    # 절대 경로를 워크스페이스 루트 기준의 / 구분 경로로 바꿉니다.
    return path.resolve().relative_to(WORKSPACE_ROOT.resolve()).as_posix()

# base64 이미지 문자열을 PNG 파일로 저장하는 작은 함수를 정의합니다.
def save_base64_png(image_base64: str, path: Path) -> Path:
    # base64 문자열을 실제 이미지 바이트로 디코딩합니다.
    image_bytes = base64.b64decode(image_base64)
    # 디코딩한 바이트를 지정한 파일에 씁니다.
    path.write_bytes(image_bytes)
    # 저장된 파일 경로를 호출한 쪽에 반환합니다.
    return path

# 응답 객체에서 이미지 생성 결과의 base64 문자열을 직접 찾아내는 함수를 정의합니다.
def first_image_base64(response: Any) -> str:
    # Responses API의 output 배열을 하나씩 확인합니다.
    for item in getattr(response, "output", []) or []:
        # 이미지 생성 도구 호출에는 result 또는 result 배열에 base64가 들어올 수 있습니다.
        result = getattr(item, "result", None)
        # result가 문자열이면 바로 이미지 base64로 사용합니다.
        if isinstance(result, str) and result:
            return result
        # result가 리스트이면 첫 번째 문자열 값을 찾습니다.
        if isinstance(result, list):
            # 리스트 안의 각 후보를 확인합니다.
            for candidate in result:
                # 후보가 문자열이면 이미지 base64로 반환합니다.
                if isinstance(candidate, str) and candidate:
                    return candidate
        # SDK 버전에 따라 b64_json이라는 이름으로 들어오는 경우도 확인합니다.
        b64_json = getattr(item, "b64_json", None)
        # b64_json이 문자열이면 반환합니다.
        if isinstance(b64_json, str) and b64_json:
            return b64_json
    # 어떤 위치에서도 찾지 못하면 원인 파악을 위해 상태를 포함해 중단합니다.
    raise RuntimeError(
        "이미지 base64 결과를 찾지 못했습니다. "
        f"status={getattr(response, 'status', None)}"
    )

# 환경과 저장 위치를 출력합니다.
print(f"이미지 실습 폴더: {IMAGE_ROOT}")
# 결과 저장 위치를 출력합니다.
print(f"결과 저장 폴더: {OUTPUT_DIR}")
# 사용할 모델명을 출력합니다.
print(f"Responses 모델: {RESPONSES_MODEL}")
# 사용할 이미지 모델명을 출력합니다.
print(f"Images 모델: {IMAGE_MODEL}")
이미지 실습 폴더: c:\Users\USER\Desktop\AS.1.1\d02\05.image
결과 저장 폴더: c:\Users\USER\Desktop\AS.1.1\d02\05.image\output\v2
Responses 모델: gpt-5-mini
Images 모델: gpt-image-1-mini
 

2단계. Responses API로 이미지 프롬프트 설계

이미지 생성 자체는 전용 Images API로 실행하지만, 먼저 Responses API로 프롬프트를 정리합니다. 이렇게 나누면 교육생이 client.responses.create(...)와 client.images.generate(...)의 역할 차이를 분명히 볼 수 있습니다.

# 이미지 생성에 사용할 초안 아이디어를 작성합니다.
idea = "밝은 교실 책상 위에 놓인 작은 AI 학습 키트 제품 사진"
# Responses API 호출 시작 시간을 기록합니다.
started = time.perf_counter()
# Responses API를 직접 호출해 이미지 생성용 프롬프트를 다듬습니다.
prompt_response = client.responses.create(
    # 텍스트 프롬프트를 설계할 모델입니다.
    model=RESPONSES_MODEL,
    # 모델의 역할을 프롬프트 디자이너로 지정합니다.
    instructions=(
        "당신은 이미지 생성 프롬프트를 짧고 명확하게 다듬는 "
        "교육용 도우미입니다."
    ),
    # 교육생이 이해하기 쉬운 이미지 프롬프트를 요청합니다.
    input=(
        "다음 아이디어를 이미지 생성 프롬프트 한 문장으로 다듬어 주세요: "
        f"{idea}"
    ),
    # gpt-5 계열은 reasoning 토큰을 많이 사용할 수 있으므로 낮은 추론 강도를 명시합니다.
    reasoning={"effort": "low"},
    # 짧은 결과라도 reasoning 토큰과 출력 텍스트가 함께 들어갈 수 있도록 한도를 넉넉히 둡니다.
    max_output_tokens=800,
)
# Responses API 호출 시간을 계산합니다.
elapsed = time.perf_counter() - started
# 불완전 응답의 세부 원인을 안전하게 읽습니다.
incomplete_details = getattr(prompt_response, "incomplete_details", None)
# SDK가 dict를 돌려준 경우 reason 키를 직접 확인합니다.
if isinstance(incomplete_details, dict):
    # 불완전 응답 원인을 문자열 키로 읽습니다.
    incomplete_reason = incomplete_details.get("reason")
# SDK가 객체를 돌려준 경우 reason 속성을 확인합니다.
else:
    # 객체 속성에서 불완전 응답 원인을 읽습니다.
    incomplete_reason = getattr(incomplete_details, "reason", None)
# output_text가 비어 있을 수 있으므로 먼저 직접 필드를 확인합니다.
direct_text = getattr(prompt_response, "output_text", "") or ""
# 직접 필드에서 얻은 텍스트 양쪽 공백을 제거합니다.
prompt = direct_text.strip()
# output_text가 비어 있으면 output 배열 안의 텍스트 블록도 함께 확인합니다.
if not prompt:
    # 분산된 텍스트 조각을 모을 리스트를 준비합니다.
    fragments = []
    # 응답 output 배열의 각 항목을 순회합니다.
    for item in getattr(prompt_response, "output", []) or []:
        # 각 output 항목의 content 블록 목록을 읽습니다.
        content = getattr(item, "content", None)
        # content가 리스트가 아니면 텍스트 블록 검사 없이 다음 항목으로 넘어갑니다.
        if not isinstance(content, list):
            continue
        # content 안의 각 블록을 순회합니다.
        for block in content:
            # 블록 종류를 읽어 텍스트 블록인지 판단합니다.
            block_type = getattr(block, "type", None)
            # 텍스트 블록이면 실제 텍스트를 추출합니다.
            if block_type in {"output_text", "text"}:
                # 텍스트 값을 문자열로 바꾸고 공백을 제거합니다.
                text = str(getattr(block, "text", "") or "").strip()
                # 비어 있지 않은 텍스트만 수집합니다.
                if text:
                    fragments.append(text)
    # 여러 텍스트 조각을 줄바꿈으로 이어 붙여 최종 프롬프트 후보로 만듭니다.
    prompt = "\n".join(fragments).strip()
# 프롬프트가 여전히 비어 있으면 원래 아이디어를 안전한 기본값으로 사용합니다.
if not prompt:
    # 빈 응답에도 실습이 이어지도록 기본 프롬프트를 구성합니다.
    prompt = idea + ", 깔끔하고 현실적인 제품 사진 스타일"
# 프롬프트 설계 결과 파일 경로를 정합니다.
metadata_path = OUTPUT_DIR / "01-02_generation_prompt_plan.json"
# 프롬프트 설계 메타데이터를 구성합니다.
metadata = {
    # 어떤 API를 사용했는지 기록합니다.
    "api": "responses.create",
    # 실제 응답 모델명을 기록합니다.
    "model": prompt_response.model,
    # 나중에 응답을 다시 조회할 수 있도록 응답 ID를 기록합니다.
    "response_id": prompt_response.id,
    # 응답 완료 상태를 기록합니다.
    "status": prompt_response.status,
    # 불완전 응답이었다면 세부 원인을 기록합니다.
    "incomplete_reason": incomplete_reason,
    # 호출 경과 시간을 초 단위로 기록합니다.
    "elapsed_seconds": round(elapsed, 3),
    # 원래 아이디어 문장을 기록합니다.
    "idea": idea,
    # 최종 프롬프트 문장을 기록합니다.
    "prompt": prompt,
}
# 메타데이터를 JSON 파일로 저장합니다.
metadata_path.write_text(
    json.dumps(metadata, ensure_ascii=False, indent=2) + "\n",
    encoding="utf-8",
)
# 응답이 불완전했으면 원인을 함께 출력해 학습자가 바로 확인할 수 있게 합니다.
if incomplete_reason:
    print(f"[주의] Responses 응답이 불완전했습니다: {incomplete_reason}")
# 설계된 프롬프트를 출력합니다.
print(prompt)
밝고 환한 교실의 책상 위에 놓인 소형 AI 학습 키트가 자연광을 받아 선명하게 보이는 제품 사진, 깔끔한 배경과 얕은 심도, 컬러풀한 구성품과 현대적인 디자인을 강조한 고해상도 실사 이미지.
 

3단계. Images API 이미지 생성

Images API는 이미지 생성 전용 엔드포인트입니다. 같은 결과라도 client.images.generate(...) 호출 형태가 더 직접적이며, size, quality 같은 이미지 전용 매개변수를 명확하게 볼 수 있습니다.

# Images API 전용 프롬프트를 준비합니다.
image_prompt = prompt
# Images API 호출 시작 시간을 기록합니다.
started = time.perf_counter()
# Images API의 generate 메서드를 직접 호출합니다.
image_response = client.images.generate(
    # 이미지 전용 모델명을 전달합니다.
    model=IMAGE_MODEL,
    # 생성할 이미지 내용을 프롬프트로 전달합니다.
    prompt=image_prompt,
    # 실습에서 다룰 이미지를 한 장만 생성합니다.
    n=1,
    # 결과 이미지를 base64 JSON으로 받습니다.
    # 실습 파일 크기와 품질의 균형을 위해 정사각형 크기를 지정합니다.
    size="1024x1024",
    # 비용과 속도를 고려해 기본 품질을 사용합니다.
    quality="medium",
)
# Images API 호출 시간을 계산합니다.
elapsed = time.perf_counter() - started
# 첫 번째 이미지 결과 객체를 꺼냅니다.
image_item = image_response.data[0]
# 결과 객체에서 base64 이미지 문자열을 읽습니다.
image_base64 = image_item.b64_json
# 저장할 파일 경로를 정합니다.
image_path = OUTPUT_DIR / "01-03_generation_images_api.png"
# base64 이미지를 PNG 파일로 저장합니다.
save_base64_png(image_base64, image_path)
# 메타데이터 파일 경로를 정합니다.
metadata_path = OUTPUT_DIR / "01-03_generation_images_api_meta.json"
# 이미지 전용 API 호출 정보를 메타데이터로 구성합니다.
metadata = {
    "api": "images.generate",
    "model": IMAGE_MODEL,
    "elapsed_seconds": round(elapsed, 3),
    "prompt": image_prompt,
    "size": "1024x1024",
    "quality": "medium",
    "output_path": workspace_path(image_path),
}
# 메타데이터를 JSON 파일로 저장합니다.
metadata_path.write_text(
    json.dumps(metadata, ensure_ascii=False, indent=2) + "\n",
    encoding="utf-8",
)
# 저장된 이미지 위치를 출력합니다.
print(f"이미지 저장: {workspace_path(image_path)}")
이미지 저장: 05.image/output/v2/01-03_generation_images_api.png
 

4단계. 생성 옵션 비교

마지막 단계에서는 같은 API 호출에서 바꿀 수 있는 옵션을 코드로 드러냅니다. 여러 장을 무작정 만들기보다 하나의 요청을 기록하고, 어떤 옵션이 품질과 비용에 영향을 주는지 메타데이터로 남깁니다.

# 옵션 비교용 프롬프트를 준비합니다.
control_prompt = (
    "초보 개발자가 OpenAI 이미지 API를 배우는 장면을 "
    "현대적인 온라인 강의 썸네일처럼 만들어 주세요."
)
# 이번 실습에서 사용할 생성 옵션을 dict로 정리합니다.
options = {
    "size": "1024x1024",
    "quality": "medium",
}
# 옵션 적용 호출 시작 시간을 기록합니다.
started = time.perf_counter()
# Images API를 직접 호출하면서 옵션 dict를 펼쳐 전달합니다.
controlled_response = client.images.generate(
    # 이미지 전용 모델을 지정합니다.
    model=IMAGE_MODEL,
    # 생성할 이미지 내용을 전달합니다.
    prompt=control_prompt,
    # 한 장만 생성해 실행 검증 시간을 줄입니다.
    n=1,
    # size, quality, response_format 옵션을 명시적으로 전달합니다.
    **options,
)
# 호출 시간을 계산합니다.
elapsed = time.perf_counter() - started
# 응답의 첫 번째 이미지 base64를 읽습니다.
image_base64 = controlled_response.data[0].b64_json
# 저장할 파일 경로를 정합니다.
image_path = OUTPUT_DIR / "01-04_generation_controls.png"
# base64 이미지를 PNG로 저장합니다.
save_base64_png(image_base64, image_path)
# 비교 기록 파일 경로를 정합니다.
metadata_path = OUTPUT_DIR / "01-04_generation_controls_meta.json"
# 비교에 필요한 옵션과 결과 경로를 메타데이터로 구성합니다.
metadata = {
    "api": "images.generate",
    "model": IMAGE_MODEL,
    "elapsed_seconds": round(elapsed, 3),
    "prompt": control_prompt,
    "options": options,
    "output_path": workspace_path(image_path),
}
# 메타데이터를 JSON으로 저장합니다.
metadata_path.write_text(
    json.dumps(metadata, ensure_ascii=False, indent=2) + "\n",
    encoding="utf-8",
)
# 실습 결과 위치를 출력합니다.
print(f"옵션 적용 이미지 저장: {workspace_path(image_path)}")
# 적용한 옵션을 출력합니다.
print(json.dumps(options, ensure_ascii=False, indent=2))
옵션 적용 이미지 저장: 05.image/output/v2/01-04_generation_controls.png
{
  "size": "1024x1024",
  "quality": "medium"
}