The Problem with Sequential Generation
Each Stable Image Core invocation takes about 15 seconds. Generating 4 images sequentially would add a full minute to the deployment pipeline. Running them in parallel keeps the total time close to the time of a single image.
The Implementation
from concurrent.futures import ThreadPoolExecutor, as_completed
def generate_with_images(site) -> dict:
content = generate_text_only(site)
slug = site.slug
hero_prompt = content.pop("hero_image_prompt", f"hero image for {site.name}")
type_prompts = []
for i, bt in enumerate(content.get("blog_types", [])):
prompt = bt.pop("image_prompt", f"blog category image for {site.name}")
type_prompts.append((f"type_{i}", prompt))
jobs = [("hero", hero_prompt)] + type_prompts
results = {}
with ThreadPoolExecutor(max_workers=4) as pool:
futures = {
pool.submit(_generate_image, prompt, slug, name): name
for name, prompt in jobs
}
for future in as_completed(futures):
img_name = futures[future]
try:
results[img_name] = future.result()
except Exception:
results[img_name] = None
content["hero_image_url"] = results.get("hero")
for i, bt in enumerate(content.get("blog_types", [])):
bt["image_url"] = results.get(f"type_{i}")
return content
Per-Image Upload to S3
Each image is base64-decoded from the Bedrock response and uploaded to S3 under cms-images/{site-slug}/{name}.png.
def _generate_image(prompt: str, slug: str, name: str) -> str:
client = boto3.client("bedrock-runtime", region_name="us-west-2", ...)
response = client.invoke_model(
modelId=IMAGE_MODEL,
body=json.dumps({"prompt": prompt, "aspect_ratio": "16:9", "output_format": "png"}),
contentType="application/json",
accept="application/json",
)
body = json.loads(response["body"].read())
image_bytes = base64.b64decode(body["images"][0])
key = f"cms-images/{slug}/{name}.png"
_boto_s3().put_object(Bucket=S3_BUCKET, Key=key, Body=image_bytes, ContentType="image/png")
return f"https://{S3_BUCKET}.s3.{S3_REGION}.amazonaws.com/{key}"
Tip:
as_completed() returns futures in completion order, not submission order — faster jobs finish first. Use the futures dict to map each future back to its name so you can store results keyed by job name.