Image Generation

Ma Mahalakshmi V Updated 16 Sep 2026
13 min read ·Lesson 28 of 224

From Understanding Images to Creating Them

Lessons 1 and 2 covered giving the model images and documents as input — visual content the model reads and reasons about. This lesson covers the reverse direction: generating new images from a text description, a capability exposed through a dedicated image-generation interface rather than through client.responses.create(). Image generation is a genuinely different kind of model and a genuinely different kind of task from everything else in this course — instead of understanding existing content, the model is producing new pixel content from scratch, guided entirely by a text description (a "prompt," using the term in its more general sense, not this course's specific input/instructions terminology).

The Basic Generation Call

result = client.images.generate(
    model="gpt-image-1",
    prompt="A minimalist logo for a coffee roasting company, featuring a simple line-art coffee bean, black and white, clean and modern.",
    size="1024x1024",
)

image_data = result.data[0]
print(image_data.url)         # a URL to the generated image, if the platform returns one
# or, depending on the requested response format:
print(image_data.b64_json)    # base64-encoded image data directly, if requested instead

Note: The exact model name for image generation, the parameter used to choose between a returned URL and inline base64 data, and the specific set of supported size values are all details that can vary by SDK version and by which image-generation model is current at the time you're building. Confirm the current model name, parameter names, and supported sizes against your installed SDK version's documentation before relying on any of these specifics in production code.

Unlike client.responses.create(), which returns a single unified response object regardless of what content it contains, image generation is exposed through its own dedicated method (client.images.generate() here) — a reflection of the fact that image generation is a genuinely separate capability with its own distinct parameters (image size, style, quality level) that don't map onto the Responses API's parameter set at all.

Saving a Generated Image to Disk

Once you have either a URL or base64 data back from a generation call, getting the actual image onto disk (to serve it, to store it, to display it) is a small, standard piece of code worth having ready.

import base64
import requests

def save_generated_image(image_data, output_path: str) -> None:
    if getattr(image_data, "b64_json", None):
        image_bytes = base64.b64decode(image_data.b64_json)
        with open(output_path, "wb") as f:
            f.write(image_bytes)
    elif getattr(image_data, "url", None):
        response = requests.get(image_data.url)
        response.raise_for_status()
        with open(output_path, "wb") as f:
            f.write(response.content)
    else:
        raise ValueError("Generated image data contains neither a URL nor base64 content")

result = client.images.generate(model="gpt-image-1", prompt="A simple sunrise over mountains, watercolor style.", size="1024x1024")
save_generated_image(result.data[0], "sunrise.png")

This function handles both possible response shapes (a URL that needs a separate fetch, or already-inline base64 data) uniformly, which is worth doing since which shape you get can depend on a parameter you set on the generation call, and code that only handles one shape will break unexpectedly if that parameter's default or your own configuration ever changes.

Prompting for Image Generation Is a Different Skill

Everything Unit 3 covered about prompting a text model — clarity, examples, instructions — applies in spirit to image generation prompts too, but the specific techniques that work well are different, since the "reasoning" happening is fundamentally about translating a description into visual composition rather than about logical or factual reasoning over text.

# Vague prompt — leaves too much to chance
vague_prompt = "a nice logo for a company"

# Specific prompt — describes composition, style, color, and mood explicitly
specific_prompt = (
    "A modern, minimalist logo for a tech startup called 'Nimbus'. "
    "Simple geometric cloud shape in a gradient from light blue to white, "
    "flat design, no text, suitable for use as a small app icon, "
    "centered composition on a transparent background."
)

Effective image-generation prompts tend to be specific about composition (what's where), style (photographic, illustrated, flat design, watercolor, and so on), color palette, mood or lighting, and any hard constraints (no text, transparent background, a specific aspect ratio) — the more of these dimensions a prompt addresses explicitly, the less the result depends on the model's own default assumptions, which may not match what you had in mind.

Generating Multiple Variations

Many image-generation interfaces support requesting several images from a single prompt in one call, letting you review a handful of variations rather than making several separate calls.

result = client.images.generate(
    model="gpt-image-1",
    prompt="A cozy reading nook with a window, soft natural light, illustrated style.",
    size="1024x1024",
    n=4,  # request four variations
)

for i, image_data in enumerate(result.data):
    save_generated_image(image_data, f"reading_nook_variant_{i}.png")

Requesting several variations at once is often a more efficient way to explore a prompt's range of plausible outputs than repeatedly regenerating one image at a time, since the model's output for a given prompt has inherent variability — much as text generation does — and seeing several variations side by side makes it easier to judge whether a prompt reliably produces the intended result or only occasionally does.

Editing an Existing Image

Beyond generating an image from scratch, some image-generation interfaces support editing an existing image — modifying a specific region while leaving the rest intact, guided by a mask and a text description of the desired change.

with open("original_photo.png", "rb") as image_file, open("mask.png", "rb") as mask_file:
    result = client.images.edit(
        model="gpt-image-1",
        image=image_file,
        mask=mask_file,
        prompt="Replace the masked area with a clear blue sky.",
    )
save_generated_image(result.data[0], "edited_photo.png")

The mask argument here is itself an image — typically one where transparent (or otherwise designated) regions mark exactly which part of the original image should be replaced, while the rest of the mask indicates content to preserve unchanged. Producing a good mask is its own small design task, usually done with an image-editing tool rather than generated programmatically, though a mask can also be produced by ordinary image-processing code (drawing a shape, thresholding based on color) when the region to edit follows a predictable, programmatically describable pattern.

Note: The exact parameters, mask format, and supported editing operations for image editing are specific to the image-generation model and SDK version in use, and have evolved meaningfully across model generations. Confirm current capabilities and the exact mask format expected against your installed SDK version's documentation before building a production editing feature around this capability.

Cost Considerations for Image Generation

Image generation is typically priced differently from text generation — often per image rather than per token, with cost varying by resolution and by how many variations are requested in a single call. This has a direct, practical implication for how a feature using image generation should be designed and tested.

def estimate_generation_cost(num_images: int, cost_per_image: float) -> float:
    """A simple illustrative calculation — confirm actual current per-image
    pricing against official documentation, since it varies by resolution
    and by which generation model is used."""
    return num_images * cost_per_image

# Testing a prompt-refinement loop by regenerating many times is a genuinely
# more expensive habit than testing a text prompt by regenerating many times,
# since each image generation call typically costs meaningfully more than a
# short text completion
print(f"Estimated cost for 20 test generations: ${estimate_generation_cost(20, 0.04):.2f}")

This cost structure is worth keeping in mind specifically when iterating on a prompt during development: a workflow that regenerates an image dozens of times while refining wording accumulates cost far faster than the equivalent iteration on a short text prompt would, making it worth investing more upfront care in a detailed, well-considered prompt (following the specificity guidance earlier in this lesson) before generating, rather than treating generation as a cheap trial-and-error loop the way a quick text completion often can be.

Handling Content Policy Rejections

Image-generation requests, like text requests, can be declined for content-policy reasons — a prompt describing content the platform doesn't generate, regardless of intent. This is directly analogous to the refusal handling Unit 6, Lesson 4 covered for structured text outputs, and deserves the same explicit handling rather than an assumption that every generation request will succeed.

def generate_image_safely(prompt: str, size: str = "1024x1024") -> dict:
    try:
        result = client.images.generate(model="gpt-image-1", prompt=prompt, size=size)
        return {"success": True, "data": result.data[0]}
    except Exception as e:
        # Depending on the SDK version, a content-policy rejection may be a
        # specific exception type distinguishable from other request failures —
        # confirm current exception types against your SDK version.
        return {"success": False, "error": str(e)}

outcome = generate_image_safely("A peaceful mountain landscape at sunset.")
if outcome["success"]:
    save_generated_image(outcome["data"], "landscape.png")
else:
    print(f"Generation failed: {outcome['error']}")

For a user-facing feature that generates images from user-supplied prompts, surfacing a clear, non-technical message on rejection (rather than a raw exception message) and logging the underlying reason for later review is worth building in from the start, mirroring the same care Unit 6 argued for around refusal handling in text-based structured extraction.

Combining Generated Images With the Responses API

A generated image can be fed back into client.responses.create() as input_image (Lesson 1), letting a single workflow generate an image and then have the model evaluate, describe, or reason about its own output — useful for a review step, an automated quality check, or a multi-step creative workflow.

result = client.images.generate(model="gpt-image-1", prompt="A friendly cartoon robot mascot, simple flat design.", size="1024x1024")
save_generated_image(result.data[0], "mascot.png")

with open("mascot.png", "rb") as f:
    base64_image = base64.b64encode(f.read()).decode("utf-8")

review_response = client.responses.create(
    model="gpt-5.6-luna",
    input=[{"role": "user", "content": [
        {"type": "input_text", "text": "Does this image look like a friendly, approachable mascot suitable for a children's app? Explain briefly."},
        {"type": "input_image", "image_url": f"data:image/png;base64,{base64_image}"},
    ]}],
)
print(review_response.output_text)

This generate-then-review pattern is a practical way to add an automated quality gate to an image-generation pipeline — while keeping in mind Lesson 1's caution about vision reliability: a model's own self-assessment of subjective qualities like "friendliness" is a genuinely softer signal than a factual visual check (verifying a specific required element is present, say), and shouldn't be trusted as a substitute for human review in a workflow where the final output genuinely matters to get right.

Testing Image-Generation Code Without Incurring Cost

Given the per-image cost structure discussed earlier, testing the logic around image generation — prompt construction, saving, error handling — without actually generating real images on every test run is worth doing deliberately, following this course's established dependency-injection pattern.

class FakeImageData:
    def __init__(self, b64_json=None, url=None):
        self.b64_json = b64_json
        self.url = url

def test_save_generated_image_handles_base64():
    import tempfile, os
    fake_data = FakeImageData(b64_json=base64.b64encode(b"fake image bytes").decode("utf-8"))
    with tempfile.TemporaryDirectory() as tmp_dir:
        path = os.path.join(tmp_dir, "test_image.png")
        save_generated_image(fake_data, path)
        with open(path, "rb") as f:
            assert f.read() == b"fake image bytes"
    print("PASS: save_generated_image correctly writes decoded base64 content")

test_save_generated_image_handles_base64()

This test exercises the base64-decoding and file-writing logic in save_generated_image() without ever calling client.images.generate() — the same cost-avoidance motivation behind every fake-object test in this course, applied here to a capability where the cost of a real call is a more direct, per-invocation dollar amount than the token-based cost of a text completion.

Choosing an Appropriate Size and Quality Level

Image-generation interfaces typically expose both a size parameter (the pixel dimensions of the output) and, in some cases, a separate quality or fidelity setting — and, mirroring Unit 3's reasoning-effort discussion for text models, higher settings on either dimension generally cost more and take longer to generate, without being universally "better" for every use case.

def choose_generation_settings(use_case: str) -> dict:
    """A simple mapping from use case to reasonable generation settings —
    confirm exact supported values and cost differences against current docs."""
    settings = {
        "app_icon": {"size": "512x512"},
        "blog_header": {"size": "1536x1024"},
        "print_poster": {"size": "1792x1024"},
    }
    return settings.get(use_case, {"size": "1024x1024"})

icon_settings = choose_generation_settings("app_icon")
result = client.images.generate(model="gpt-image-1", prompt="A simple weather app icon, sun and cloud.", **icon_settings)

A small app icon rarely benefits from the highest available resolution — the extra detail is invisible at the size it's actually displayed, and generating at an unnecessarily high resolution wastes cost without a corresponding visible improvement, exactly analogous to Unit 5's point that not every request benefits equally from every available capability. Matching generation settings to how the image will actually be used, rather than defaulting to the maximum available size for every request, is a straightforward, low-effort cost optimization worth building into any feature that generates several images for different purposes.

Building a Simple Prompt Template System

For an application that generates images in a consistent style across many different subjects — a set of icons for an app, a series of blog post headers — building a reusable prompt template rather than writing each prompt from scratch keeps the visual style consistent and makes it easy to adjust the whole set by changing one shared piece of wording.

ICON_STYLE_TEMPLATE = (
    "A simple, flat-design icon of {subject}, minimalist style, "
    "single accent color on a white background, no text, centered composition."
)

def generate_icon(subject: str, output_path: str) -> None:
    prompt = ICON_STYLE_TEMPLATE.format(subject=subject)
    result = client.images.generate(model="gpt-image-1", prompt=prompt, size="512x512")
    save_generated_image(result.data[0], output_path)

for subject, filename in [("a coffee cup", "icon_coffee.png"), ("a calendar", "icon_calendar.png"), ("a gear", "icon_settings.png")]:
    generate_icon(subject, filename)

This pattern — a shared template string with a single varying piece of content — is directly analogous to Unit 3's discussion of instructions versus input: the template captures the stable, style-defining part of the prompt (the part that should stay consistent across every generated icon), while subject captures the part that varies per call, keeping the whole icon set visually coherent without needing to retype the full style description for every single image.

Common Mistakes

Iterating on a prompt by repeatedly regenerating images, treating image generation like the cheap, fast iteration loop that's reasonable for short text completions, when the per-image cost structure makes many rounds of trial-and-error meaningfully more expensive.

Writing a vague, underspecified prompt and expecting the model to guess the intended composition, style, and mood, when explicit detail across these dimensions (covered earlier in this lesson) substantially improves how closely a generated image matches what was actually wanted.

Not handling content-policy rejections explicitly for a user-facing generation feature, leaving a rejected request to surface as a raw, confusing exception rather than a clear message and a logged reason for review.

Assuming a generation response always returns either a URL or base64 data specifically, and writing code that only handles one shape, when the actual shape returned can depend on a request parameter that might change.

Best Practices

Invest in a detailed, specific prompt before generating, addressing composition, style, color, mood, and any hard constraints explicitly, rather than relying on many cheap regeneration attempts to arrive at an acceptable result — a cost-conscious habit that also tends to produce more consistent, predictable output.

Request multiple variations in a single call when exploring a prompt's range of outputs, rather than making several separate calls, to get a representative sample of what a given prompt reliably produces.

Handle content-policy rejections explicitly, with a clear user-facing message and a logged underlying reason, exactly as Unit 6 recommended for text-based refusals.

Write code that handles both possible response shapes (URL and base64) uniformly, rather than assuming one specific shape will always be returned.

Test the logic around image generation — prompt construction, saving, error handling — with fake image data, reserving real generation calls for final validation, given the meaningfully higher per-call cost of image generation compared to short text completions.

0 Comments

Reviewed before they appear

No comments yet.

OpenAI SDK
Introduction to the OpenAI SDK Setting Up Python Creating an API Key Your First Call — client.responses.create() and response.output_text Understanding Billing, Credits, and What a Request Costs Why Responses Replaced Chat Completions Anatomy of a Request: model, input, and instructions Anatomy of a Response: The Typed output Array, Not Just Text Roles: User, Assistant, and Developer/System Choosing a Model, and Reading the Models Page Instead of Memorizing Names Instructions vs. Input Writing Prompts That Get Consistent Results Few-Shot Examples Reasoning Models and the reasoning Parameter Debugging a Prompt That Misbehaves Why Streaming Matters for User Experience stream=True and Iterating Over Events Handling the Event Types You Actually Care About Background Mode for Long-Running Jobs Project — Add Live Streaming to Your Chatbot The Problem With Parsing Free Text JSON Schema and Strict Mode Pydantic Models With the SDK's Parse Helpers Handling Refusals and Validation Failures Project — A Resume-to-JSON Extractor Working With input_image input_file, PDFs, and the Files API Image Generation Speech-to-Text and Text-to-Speech Project: A PDF Question-Answering Script What Function Calling Is Defining a Tool Schema The Full Loop Multiple Tools Errors, Timeouts, and Untrusted Arguments Project: A Weather Assistant Web Search File Search and Vector Stores Code Interpreter Remote MCP Servers and Connectors Project: A Research Assistant What an Embedding Is, Without the Maths Generating and Storing Embeddings Similarity Search From Scratch Hosted Vector Stores vs. Rolling Your Own A Small RAG App Over a Folder of Notes Agents vs. a Single API Call — When You Need One pip install openai Giving Agents Tools Handoffs and Multi-Agent Triage Guardrails and Approvals Tracing and Observing What Your Agent Did A Multi-Agent Support Desk Error Codes and What Each One Means Retries, Timeouts, and Backoff Rate Limits and Spend Limits Prompt Caching and Cost Optimisation The Batch API for Bulk Work Async Clients and Concurrency Moderation and Safety Best Practices Designing the App Backend With FastAPI Streaming to a Simple Frontend Deploying and a Cost/Safety Checklist Why Web Search Is Useful for Current Information Using the Web Search Tool with the Responses API Configuring Search Behavior for Application Use Cases Understanding Citations and Source Attribution Where to Go Next Building a Research Assistant with Web Search Combining Web Search with Structured Outputs Handling Conflicting or Low-Quality Web Sources Reducing Unsupported Claims with Grounded Generation Testing Freshness-Sensitive AI Answers Production Considerations for Web-Grounded Applications Understanding File Search and Retrieval-Augmented Generation Creating and Organizing Vector Stores Uploading Documents for Retrieval Connecting Vector Stores to Responses API Requests Designing Document Metadata and Filtering Strategies Building a PDF Question-Answering Application Improving Retrieval Quality With Better Document Preparation Handling Missing Evidence and Retrieval Failures Combining File Search With Web Search Building a Production Knowledge-Base Assistant What the Code Interpreter Tool Is Designed For Running Python-Based Analysis Through the OpenAI SDK Uploading Datasets for Analysis Analyzing CSV and Spreadsheet Data Generating Charts and Data Summaries Handling Generated Files and Downloadable Artifacts Building a Data-Analysis Assistant Combining Code Execution with Structured Outputs Validating Generated Calculations and Results Security and Sandbox Considerations for Code Execution Understanding Multimodal Input with the OpenAI SDK Sending Images to a Model Image Analysis from URLs and Uploaded Files Extracting Text and Information from Screenshots Building an Image-Question-Answering Application Combining Image Input with Structured Output Analyzing Multiple Images in One Request Handling Image Quality and Input Limitations Designing Multimodal Prompts for Reliable Results Building a Practical Vision-Powered Python Application Understanding Speech-to-Text and Text-to-Speech Workflows Transcribing Audio with the OpenAI SDK Working with Uploaded Audio Files Handling Timestamps and Transcription Metadata Building a Meeting Transcription Workflow Generating Spoken Responses from Text Handling Long Audio and Processing Failures Combining Audio with Text and Tool Calling Building an End-to-End Python Voice Application What Embeddings Are and When to Use Them Generating Embeddings With the OpenAI API Preparing Text for Embedding Comparing Vectors With Cosine Similarity Building a Simple Semantic Search Engine in Python Storing Embeddings in a Database Metadata Filtering for Semantic Search Chunking Strategies for Better Retrieval Evaluating Semantic Search Quality Building a Document Similarity Application When Batch Processing Makes Sense Designing Large-Volume AI Processing Pipelines Using Asynchronous Python with the OpenAI SDK Running Concurrent Requests Safely Controlling Concurrency and Avoiding Rate Limits Tracking Batch Job Progress Handling Partial Failures in Bulk Workloads Retrying Failed Items Without Duplicating Successful Work Designing Resumable AI Processing Jobs Building a Production Batch-Processing Pipeline Batch Processing Makes Sense Large-Scale AI Processing Pipelines Async Python with OpenAI SDK Safe Concurrent Requests Concurrency & Rate Limits Batch Progress Tracking Partial Failure Handling Safe Retry Handling Resumable AI Jobs Production Batch Pipeline System–User Data Separation Reusable App Instructions Prompt Templates & Variables Extraction & Classification Prompts Summarization & Transformation Prompts Explicit Output Requirements Prompt Version Management Prompt Testing & Evaluation Reusable Python Prompt Library API Key Security Secure API Key Storage Secure Secret Management Prompt Injection Prevention Trusted vs. Untrusted Content Tool Argument Validation Sensitive Data Handling Secure Logging AI Action Authorization Production AI Security Checklist Why AI Applications Need Evaluation Beyond Unit Tests Unit Testing OpenAI SDK Integration Code Mocking API Responses in Python Tests Testing Structured Outputs Against Schemas Testing Tool-Calling Workflows Building a Small Evaluation Dataset Measuring Accuracy, Consistency, and Failure Rates Regression Testing Prompts and Model Changes Human Evaluation Versus Automated Evaluation Creating a Repeatable Evaluation Pipeline AI Request Monitoring Token Cost Management Usage Metrics Design Reducing Model Calls Prompt & Context Optimization Model Selection & Optimization AI Caching Strategies Interactive Latency Optimization Usage Dashboards & Budget Alerts Performance & Cost Checklist Every API Call Starts Fresh Fixing API Statelessness Server-Side Conversation Memory Limits of Response Chaining What We're Building Conversation Memory Challenges Preparing an OpenAI SDK Application for Deployment Environment-Specific Configuration for Development and Production Deploying a Python AI Service with Docker Container Health Checks and Startup Configuration Managing Secrets in Cloud Deployments Background Workers for Long-Running AI Tasks Queues and Asynchronous Job Architectures Scaling AI Workloads Horizontally Monitoring Production Incidents and Failures Production Deployment Checklist for OpenAI SDK Applications Reusable OpenAI Service Classes AI Client Dependency Injection Typed AI Responses Python Configuration Management AI Request Decorators Centralized AI Error Handling Clean SDK Abstractions Reusable OpenAI Utilities Internal AI Python Libraries SDK Integration Maintenance Production AI Chatbot Document Q&A System Web Research Assistant Customer Support Agent AI Data Analysis Assistant Image Analysis App Meeting Transcription & Summary Semantic Document Search Multi-Tool AI Agent Production OpenAI SDK App Why "It Looked Fine When I Tested It" Isn't Enough Timing Note Status Note Pre-Decision Status Note Current Availability Note
Ask about this post
AI Ask about this post

Ask questions about Image Generation and get answers drawn from it.

Signed-in readers only.