Combine image generation with other API tools
On this page
Update November 2, 2026 — Image models.
gpt-image-1, the model this course was originally written for, was discontinued on October 23, 2026.gpt-image-1.5,gpt-image-1-mini, andchatgpt-image-latestwill be retired on December 1, 2026. Examples now usegpt-image-2.5-sunburst, which offers the highest precision for editing;gpt-image-2.5-flareis faster for standard generation. Both models also supportxhighandmaxquality settings. GPT Image models always return images in base64: theresponse_formatparameter from older DALL·E models is not supported and has been removed from the code. Source: Image generation (OpenAI).
Combine image generation with other API tools
In isolation, image generation is a craft tool: you write a prompt, you get a file. Its real power emerges when it becomes one step in an automated chain—a language model that writes the prompt, a vision analysis that describes a reference image, or storage that publishes the result. This lesson shows you how to wire these pieces together.
Have a language model write the prompt
Your users or colleagues don’t write visual prompts; they write briefs. “I need an image for an article on AI in healthcare, futuristic but human” contains no composition, lighting, or palette. Instead of asking them to learn the vocabulary from the previous lesson, insert a language model to handle the translation. Assign it the role of art director in the system message and require it to produce the specific layers of the prompt, with strict instructions to output nothing else. Otherwise, you’ll receive a polite explanation that you’d then have to send to GPT Image.
from openai import OpenAI
import base64
client = OpenAI()
def text_to_image(brief: str, output: str):
"""
Two-step pipeline:
1. An LLM generates the optimal visual prompt
2. GPT Image creates the image
"""
# Step 1: Generate the visual prompt with an LLM
completion = client.chat.completions.create(
model="gpt-5.6-luna",
messages=[
{
"role": "system",
"content": (
"You are an expert art director. "
"Based on a brief, generate a detailed prompt for an "
"image generation model. Include: subject, composition, lighting, "
"style, perspective, color palette. "
"Respond only with the prompt, no explanation."
)
},
{"role": "user", "content": brief}
]
)
visual_prompt = completion.choices[0].message.content
print(f"Generated prompt: {visual_prompt[:100]}...")
# Step 2: Generate the image
response = client.images.generate(
model="gpt-image-2.5-sunburst",
prompt=visual_prompt,
size="1536x1024",
quality="high",
)
data = base64.b64decode(response.data[0].b64_json)
with open(output, "wb") as f:
f.write(data)
print(f"Image generated: {output}")
return visual_prompt
prompt = text_to_image(
"I need an image for an article on artificial intelligence "
"in healthcare. Futuristic but human.",
"ai_health.png"
)
Illustrate an article end-to-end
A feature article needs a cover image and one illustration per section. These images must look cohesive without being identical. The illustrate_article function applies this logic by reinjecting the style into every prompt and repeating the article title each time, giving the full set thematic unity. Two cost-saving details deserve attention: the cover is generated at high quality because everyone sees it and it’s often shared, while section illustrations stay at medium since they display smaller and lower on the page. For the five sections in this example on AI in recruiting, this trade-off cuts the bill without the reader noticing anything.
from openai import OpenAI
import base64
import json
client = OpenAI()
def illustrate_article(title: str, sections: list[str], style: str, output_dir: str = "."):
"""
Generates a header illustration + one per section.
"""
results = []
# Cover image
header_prompt = (
f"Cover image for an article titled \"{title}\", "
f"{style} style, panoramic composition, "
"no text in the image, professional"
)
response = client.images.generate(
model="gpt-image-2.5-sunburst",
prompt=header_prompt,
size="1536x1024",
quality="high",
)
path = f"{output_dir}/header.png"
data = base64.b64decode(response.data[0].b64_json)
with open(path, "wb") as f:
f.write(data)
results.append({"type": "header", "path": path})
print(f"Header: {path}")
# Section illustrations
for i, section in enumerate(sections):
section_prompt = (
f"Illustration for the section \"{section}\" "
f"of an article on \"{title}\", "
f"{style} style, rectangular format, "
"conceptual illustration without text"
)
response = client.images.generate(
model="gpt-image-2.5-sunburst",
prompt=section_prompt,
size="1536x1024",
quality="medium",
)
path = f"{output_dir}/section_{i+1}.png"
data = base64.b64decode(response.data[0].b64_json)
with open(path, "wb") as f:
f.write(data)
results.append({"type": f"section_{i+1}", "section": section, "path": path})
print(f"Section {i+1} ({section[:30]}...) : {path}")
return results
illustrate_article(
title="5 Ways AI Is Transforming Recruitment",
sections=[
"Automatic CV Screening",
"AI-Analyzed Video Interviews",
"Smart Candidate-Job Matching",
"Reducing Recruitment Bias",
"Personalized Onboarding",
],
style="3D isometric illustration, blue and violet tones",
output_dir="."
)
Describe an image to regenerate it differently
How do you get a winter version of a summer landscape when you only have the file, no mask, and no editing tools? Go through language. The model’s vision analyzes the image and produces a description precise enough to serve as a reconstruction prompt—subject, composition, lighting, colors, style, perspective, mood. You then concatenate your modification instruction to this description. The generation model receives a complete text prompt where only the season has changed, and you get an image coherent with the original rather than something unrelated. This is also the most reliable way to reproduce the visual identity of a reference image you cannot edit directly.
from openai import OpenAI
import base64
client = OpenAI()
def analyze_and_vary(image_path: str, instruction: str, output: str):
"""
1. Analyzes an image with GPT Vision
2. Generates a variant based on the analysis
"""
# Step 1: Analyze the image
with open(image_path, "rb") as f:
image_b64 = base64.b64encode(f.read()).decode()
analysis = client.chat.completions.create(
model="gpt-5.6-luna",
messages=[
{
"role": "user",
"content": [
{
"type": "text",
"text": (
"Describe this image in detail so an image "
"generation model can reproduce it faithfully. "
"Include: subject, composition, lighting, colors, style, "
"perspective, mood. Be very precise."
)
},
{
"type": "image_url",
"image_url": {
"url": f"data:image/png;base64,{image_b64}"
}
}
]
}
]
)
description = analysis.choices[0].message.content
print(f"Analysis: {description[:150]}...")
# Step 2: Generate a modified variant
variant_prompt = f"{description}. Requested modification: {instruction}"
response = client.images.generate(
model="gpt-image-2.5-sunburst",
prompt=variant_prompt,
size="1024x1024",
quality="high",
)
data = base64.b64decode(response.data[0].b64_json)
with open(output, "wb") as f:
f.write(data)
print(f"Variant generated: {output}")
analyze_and_vary(
"original_landscape.png",
"Change the season from summer to winter, with snow on roofs and trees",
"winter_landscape.png"
)
Process large volumes in batches
Generating five product visuals in series takes minutes; generating five hundred sequentially takes all night and breaks at the first network hiccup. The setup below solves both problems. Each task is a self-contained dictionary carrying its ID, prompt, output file, and settings. The worker function wraps the call in a try block that returns a status instead of crashing the whole batch: a rejected generation no longer takes down the other four hundred and ninety-nine. The ThreadPoolExecutor then parallelizes execution, but with a deliberately low max_workers—three in this example—because the bottleneck isn’t your machine, it’s the API quotas. The final report counts successes and failures so you know what to retry.
from openai import OpenAI
import base64
import json
import time
from concurrent.futures import ThreadPoolExecutor, as_completed
client = OpenAI()
def generate_one_image(task: dict) -> dict:
"""Generates a single image from a task."""
try:
response = client.images.generate(
model="gpt-image-2.5-sunburst",
prompt=task["prompt"],
size=task.get("size", "1024x1024"),
quality=task.get("quality", "medium"),
)
data = base64.b64decode(response.data[0].b64_json)
with open(task["output"], "wb") as f:
f.write(data)
return {"id": task["id"], "status": "ok", "path": task["output"]}
except Exception as e:
return {"id": task["id"], "status": "error", "error": str(e)}
def batch_generation(tasks: list[dict], max_parallel: int = 3):
"""Executes a batch of generations with controlled parallelism."""
results = []
with ThreadPoolExecutor(max_workers=max_parallel) as executor:
futures = {
executor.submit(generate_one_image, task): task
for task in tasks
}
for future in as_completed(futures):
result = future.result()
results.append(result)
status = result["status"]
print(f" [{status}] {result['id']}")
# Report
ok = sum(1 for r in results if r["status"] == "ok")
errors = sum(1 for r in results if r["status"] == "error")
print(f"\nFinished: {ok} ok, {errors} errors out of {len(tasks)} tasks")
return results
# Define a batch of tasks
tasks = [
{
"id": f"product_{i}",
"prompt": f"E-commerce product photo: {product}, white background, studio",
"output": f"batch_product_{i}.png",
"quality": "high"
}
for i, product in enumerate([
"rose gold watch",
"aviator sunglasses",
"braided leather bracelet",
"solitaire diamond ring",
"cultured pearl necklace",
])
]
results = batch_generation(tasks, max_parallel=3)
Publish directly to storage
Writing the PNG to the disk of the machine running the script only makes sense in development. In production, the image must land where your site will fetch it, and detouring through the local file system becomes an unnecessary point of failure. The b64_json format suits this removal well: decoded bytes are wrapped in a BytesIO object and pushed directly to the bucket, with an explicit ContentType—without which browsers would offer a download instead of displaying the image. The function returns the public URL, which is what your CMS actually needs.
from openai import OpenAI
import base64
import boto3
from io import BytesIO
client = OpenAI()
def generate_and_store_s3(prompt: str, bucket: str, key: str):
"""Generates an image and uploads it directly to S3."""
response = client.images.generate(
model="gpt-image-2.5-sunburst",
prompt=prompt,
size="1024x1024",
quality="high",
)
image_data = base64.b64decode(response.data[0].b64_json)
s3 = boto3.client("s3")
s3.upload_fileobj(
BytesIO(image_data),
bucket,
key,
ExtraArgs={"ContentType": "image/png"}
)
url = f"https://{bucket}.s3.amazonaws.com/{key}"
print(f"Image stored on S3: {url}")
return url
# Example
url = generate_and_store_s3(
"Mobile app icon: open book with stars, flat design",
"my-image-bucket",
"generated/app_icon_v1.png"
)
What keeps a pipeline standing
The five pipelines in this lesson share the same construction hygiene. Each step does one thing, letting you replace prompt writing or storage without touching the rest. Each step handles its own errors; otherwise, a moderation refusal in the middle of a batch brings down the whole chain—that’s exactly what the try in generate_one_image prevents. Parallelism stays throttled to respect rate limits, and on this point, caution costs less than recovery after a temporary ban.
Two less visible but equally decisive habits remain. Log the prompts actually sent, especially when written by a language model: without this log, an image you love becomes unreproducible on the second run. And cache results whenever the same prompt might return, which the next lesson formalizes into a complete system.
Now assemble your own full chain: it takes an article title as input, asks a language model for three descriptions of relevant visuals, generates the three images in parallel, and produces a summary JSON linking each prompt to its file. You’ll then have everything needed to illustrate an entire blog without manual intervention.