I’m encountering a blocking issue while trying to generate an image using the OpenAI Python SDK.
Here is the relevant part of my code:
import os
# Initialize client with API key stored in environment variable
client = OpenAI(api_key=os.environ.get("OPENAI_API_KEY"))
# Image generation request
generation_response = client.images.generate(
model="dall-e-3",
prompt="A cyberpunk monkey dreaming of a beautiful bunch of bananas, digital art",
n=1,
size="1024x1024",
response_format="url",
)
I do not know if that is the case, but for image/edits endpoint I had the same error for dall-e-2. For gpt-image-1 using SDK from openAI
client.images.(...)
worked fine, unlike dall-e-2. Switching to use requests library in python and use requests.post(…) resolved the problem with dall-e-2. But I used only image.edit
This error message indicates that the extreme, ridiculous (as in subject to my ridicule) moderations blocked the image.
'type': 'image_generation_user_error'
However, I tried a few times, and didn’t get an error. It may have to do with org-specific allowances, or what the prompt rewriter actually changed your prompt into, if it used blocked keywords or similar (you don’t see the rewriting upon failure - an API shortcoming).
(note - you don’t need to include os.environ.get(), as the API library does that for you automatically with the default environment variable)
Have a procedural incremental script that guards against bad parameters being sent, that was successful for my organization with these options. It will save locally to a subdirectory whether you use the boolean download_by_url to get URL or base64. (also a popup preview of the created image)
'''
DALL-E image generation example for openai>1.8.4, saves requested images as files
'''
## START - global user input data
prompt = (
"""
A cyberpunk monkey dreaming of a beautiful bunch of bananas, digital art
""".strip()
)
model_choice = "dall-e-3"
size_choice = 1 # !! see size table
dalle_3_hd = False # quality at 2x the price, defaults to "standard"
dalle_3_natural = False # defaults to "vivid", natural is poor
image1_quality = "medium" # high, medium, or low
download_by_url = True # retrieval method; False = get base64 in response
save_directory = "./images"
log_directory = "./logs" # json of call and response per image
## END - user input data
from io import BytesIO
from datetime import datetime, timezone # for formatting date returned with images
from importlib.metadata import version, PackageNotFoundError # check w/o import
# other imports lazy-loaded as needed
def is_old_package(version, minimum):
version_parts = list(map(int, version.split(".")))
minimum_parts = list(map(int, minimum.split(".")))
return version_parts < minimum_parts
try:
openai_version = version("openai")
except PackageNotFoundError:
raise RuntimeError("The ‘openai’ package is not installed or found. "
"You should run 'pip install --upgrade openai")
if is_old_package(openai_version, "1.8.4"): # the only package needing version check
raise ValueError(f"Error: OpenAI version {openai.__version__}"
" is less than the minimum version 1.8.4\n\n"
">>You should run 'pip install --upgrade openai')")
else:
from openai import OpenAI, RateLimitError, BadRequestError
from openai import APIConnectionError, APIStatusError
sizes = {
"dall-e-2": {1:"256x256", 2: "512x512", 3: "1024x1024"},
"dall-e-3": {1:"1024x1024", 2: "1792x1024", 3: "1024x1792"},
"gpt-image-1": {1:"1024x1024", 2: "1536x1024", 3: "1024x1536"},
}
api_params = {
"model": model_choice, # Defaults to dall-e-2
"size": sizes[model_choice][size_choice],
"prompt": prompt, # DALL-E 3: max 4000 characters, DALL-E 2: max 1000
"n": 1, # Between 2 and 10 is only for DALL-E 2
"user": "myname", # pass a customer ID for safety tracking
}
if model_choice == "gpt-image-1":
api_params.update({
"quality": image1_quality,
"moderation": "low", # or None = stupidly strict
"background": "opaque", # transparent, opaque or auto
"output_format": "png", # or jpg, webp
})
if api_params["output_format"] in ["jpg", "webp"]:
api_params["output_compression"] = "99" # only jpg, webp
if download_by_url:
print("warn: gpt-image-1 can't download via URLs; using base64")
# gpt-image-1 always returns base64; remove untolerated `response_format` key
api_params.pop("response_format", None)
else: # if dall-e
if download_by_url:
api_params["response_format"] = "url"
else:
api_params["response_format"] = "b64_json"
if model_choice == "dall-e-3" and dalle_3_hd:
api_params["quality"] = "hd"
if model_choice == "dall-e-3" and dalle_3_natural:
api_params["style"] = "natural"
def prepare_path(path_str):
"""
Ensure the provided path exists and is writable.
Creates the directory if it doesn't exist. Tests writability by
creating and deleting a temporary file. Returns a pathlib.Path.
"""
from pathlib import Path
path = Path(path_str)
# If path is relative, treat as subdirectory of cwd
if not path.is_absolute():
path = Path.cwd() / path
# Create the directory if it doesn't exist
if not path.exists():
path.mkdir(parents=True, exist_ok=True)
elif not path.is_dir():
raise ValueError(f"'{path}' exists and is not a directory")
# Test writability by writing and deleting a temp file
test_file = path / ".write_test"
try:
with open(test_file, "w") as f:
f.write("") # empty write
test_file.unlink()
except Exception as e:
raise PermissionError(f"Cannot write to directory '{path}': {e}")
return path
# ensure files can be written before calling
files_path = prepare_path(save_directory)
logs_path = prepare_path(log_directory)
# here's the actual request to API and lots of error-catching
client = OpenAI(timeout=150) # will use environment variable "OPENAI_API_KEY"
try:
from time import time
start = time()
images_response = client.images.generate(**api_params)
except APIConnectionError as e:
print("Server connection error: {e.__cause__}") # passed from httpx
raise
except RateLimitError as e:
print(f"OpenAI RATE LIMIT error {e.status_code}: (e.response)")
raise
except APIStatusError as e:
print(f"OpenAI STATUS error {e.status_code}: (e.response)")
raise
except BadRequestError as e:
print(f"OpenAI BAD REQUEST error {e.status_code}: (e.response)")
raise
except Exception as e:
print(f"An unexpected error occurred: {e}")
raise
# Print the elapsed time of the API call
print(f"Elapsed time: {time()-start:.1f}")
# get the prompt used if rewritten by dall-e-3, null if unchanged by AI
revised_prompt = images_response.data[0].revised_prompt
print(f"Revised Prompt:", revised_prompt)
# get out all the images in API return, whether url or base64
# note images_response being pydantic "model.data" and its model_dump() method
image_url_list = []
image_data_list = []
for image in images_response.data:
image_url_list.append(image.model_dump()["url"])
image_data_list.append(image.model_dump()["b64_json"])
# Prepare a list of image file bytes from either URLs or base64 data
image_file_bytes = []
# Download or decode images and append to image_objects (no saving yet)
if image_url_list and all(image_url_list):
import requests # for downloading images from URLs
for url in image_url_list:
while True:
try:
print(f"getting URL: {url}")
response = requests.get(url)
response.raise_for_status() # Raises stored HTTPError, if one occurred.
except requests.HTTPError as e:
print(f"Failed to download image from {url}. Error: {e.response.status_code}")
retry = input("Retry? (y/n): ") # ask script user if image url is bad
if retry.lower() in ["n", "no"]:
raise
else:
continue
break
image_file_bytes.append(response.content)
elif image_data_list and all(image_data_list): # if there is b64 data
import base64 # for decoding images if received in the reply
for data in image_data_list:
image_file_bytes.append(base64.b64decode(data))
else:
raise ValueError("No image data was obtained. Maybe bad code?")
# After obtaining all image objects, produce filenames and save files
def prompt_to_filename(s,max_length=30):
'''make prompt text safe for use within file names'''
import unicodedata,re
t=unicodedata.normalize('NFKD',s).encode('ascii','ignore').decode('ascii')
t=re.sub(r'[^A-Za-z0-9._\- ]+','_',t)
t=re.sub(r'_+','_',t).strip(' .')
if len(t)>max_length: t=t[:max_length].rstrip(' .')
if not t: return 'prompt'
r={'CON','PRN','AUX','NUL','COM1','COM2','COM3','COM4','COM5',
'COM6','COM7','COM8','COM9','LPT1','LPT2','LPT3','LPT4',
'LPT5','LPT6','LPT7','LPT8','LPT9'}
if t.upper().split('.')[0] in r: t='_'+t
return t
# make an auto file name; "created" in response is UNIX epoch time
filename_base = api_params["model"]
epoch_time_int = images_response.created
my_datetime = datetime.fromtimestamp(epoch_time_int, timezone.utc)
if globals().get('i_want_local_time') or True:
my_datetime = my_datetime.astimezone()
file_datetime = my_datetime.strftime('%Y%m%d-%H%M%S')
short_prompt = prompt_to_filename(prompt)
img_filebase = f"{filename_base}-{file_datetime}-{short_prompt}"
extension = "png" # or api_params["output_format"] only accepted with gpt-image-1 model
# Initialize an empty list to store the Image objects
image_objects = []
from PIL import Image
for i, img_bytes in enumerate(image_file_bytes):
img = Image.open(BytesIO(img_bytes))
image_objects.append({"file":img, "filename": f"{img_filebase}-{i}.{extension}"})
# build a Path to the output file
out_file = files_path / f"{img_filebase}-{i}.{extension}"
img.save(out_file)
print(f"{out_file} was saved")
# Log the request body parameters and the full response object to a file
log_obj = {"req": api_params, "resp": images_response.model_dump()}
log_filename = logs_path / f"{img_filebase}.json"
# Write the log_obj as JSON to the log file
import json
with open(log_filename, "w", encoding="utf-8") as log_f:
json.dump(log_obj, log_f, indent=0)
## -- extra fun: pop up some thumbnails in a GUI if you want to see what was saved
from PIL import Image # pillow, for processing image types
import tkinter as tk # for GUI thumbnails of what we got
from PIL import ImageTk # for GUI thumbnails of what we got
def resize_to_max(img, max_w, max_h):
"""
Return a resized copy of `img` so that neither width nor height
exceeds (max_w, max_h), preserving aspect ratio.
"""
w, h = img.size
scale = min(max_w / w, max_h / h)
new_w = int(w * scale)
new_h = int(h * scale)
return img.resize((new_w, new_h), Image.LANCZOS)
if image_objects:
for i, img_dict in enumerate(image_objects):
img = img_dict["file"]
filename = img_dict["filename"]
# Resize image for pop-up
if img.width > 768 or img.height > 768:
img = resize_to_max(img, 768, 768)
window = tk.Tk()
window.title(filename)
tk_image = ImageTk.PhotoImage(img)
label = tk.Label(window, image=tk_image)
label.image = tk_image # keep a reference so it isn’t garbage-collected
label.pack()
window.mainloop()
Thanks so much for this — it’s really helpful. I’m new to coding and calling APIs, and I’ve learned a lot by reading your script. I’ll dive deeper into it now and try to automate the generation of images with 200 different prompts!
I used the script you suggested. Despite that, the problem persists. You can find the traceback below:
OpenAI STATUS error 400: (e.response)
Traceback (most recent call last):
File "/…/site-packages/IPython/core/interactiveshell.py", line XXX, in run_code
exec(code_obj, self.user_global_ns, self.user_ns)
File "<ipython-input-XX-XXXXXXXXXXXX>", line XX, in <module>
images_response = client.images.generate(**api_params)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/…/site-packages/openai/resources/images.py", line XXX, in generate
return self._post(
^^^^^^^^^^^
File "/…/site-packages/openai/_base_client.py", line XXXX, in post
return cast(ResponseT, self.request(cast_to, opts, stream=stream, stream_cls=stream_cls))
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/…/site-packages/openai/_base_client.py", line XXXX, in request
raise self._make_status_error_from_response(err.response) from None
openai.BadRequestError: Error code: 400 - {
'error': {
'message': None,
'type': 'image_generation_user_error',
'param': None,
'code': None
}
}
I’m surprised the issue is coming from moderation— I’ve tried other prompts and nothing works. I wonder if the problem might be due to insufficient credits on my developer account. However, if that were the case, it doesn’t seem to me that the returned error would be a 400.
If you can’t even make cute bunnies, then you can investigate your API platform organization.
Under billing, see the current balance is positive from your prepayment. Then check organization limits, and see that dall-e-3 has the particular rate limit line “xx images per minute” along with RPM and TPM allocated. See that the API key used on the same platform can make a chat completions call, and then that you haven’t in the project that contains API keys limited it to only certain API endpoints.
The useless new error for prompting and content could also mean more useless error status for other types of issues.