Optimiser les coûts
Optimiser les coûts
La génération d’images via l’API représente un poste de dépense significatif en production. Cette leçon vous donne les techniques concrètes pour réduire vos coûts sans sacrifier la qualité de vos visuels.
Comprendre la structure des coûts
Le coût d’une génération dépend de trois facteurs :
| Facteur | Impact sur le coût |
|---|---|
Qualité (low, medium, high) | Multiplicateur direct |
| Résolution (1024, 1536) | Plus grand = plus cher |
Nombre d’images (n) | Coût linéaire (n x prix unitaire) |
from openai import OpenAI
client = OpenAI()
# Comparer les coûts : même prompt, paramètres différents
configs = [
{"quality": "low", "size": "1024x1024", "label": "Low / 1024"},
{"quality": "medium", "size": "1024x1024", "label": "Medium / 1024"},
{"quality": "high", "size": "1024x1024", "label": "High / 1024"},
{"quality": "medium", "size": "1536x1024", "label": "Medium / 1536"},
{"quality": "high", "size": "1536x1024", "label": "High / 1536"},
]
print("Configuration | Usage (estimé)")
print("-" * 50)
for config in configs:
print(f"{config['label']:22s} | Vérifiez votre dashboard OpenAI")
Stratégie 1 : prototyper en low, livrer en high
La technique la plus efficace : utilisez quality: low pendant toute la phase d’exploration, puis passez en high uniquement pour le rendu final.
from openai import OpenAI
import base64
client = OpenAI()
def workflow_economique(prompt: str, output: str, n_drafts: int = 5):
"""
Workflow en deux phases :
1. Brouillons rapides en low quality
2. Rendu final en high quality
"""
# Phase 1 : exploration rapide
print("Phase 1 : brouillons rapides (low quality)")
drafts = []
for i in range(n_drafts):
response = client.images.generate(
model="gpt-image-1",
prompt=prompt,
size="1024x1024",
quality="low",
response_format="b64_json"
)
path = f"draft_{i+1}.png"
data = base64.b64decode(response.data[0].b64_json)
with open(path, "wb") as f:
f.write(data)
drafts.append(path)
print(f" Brouillon {i+1} : {path}")
# Phase 2 : rendu final (après sélection humaine du meilleur brouillon)
print("\nPhase 2 : rendu final (high quality)")
response = client.images.generate(
model="gpt-image-1",
prompt=prompt,
size="1536x1024",
quality="high",
response_format="b64_json"
)
data = base64.b64decode(response.data[0].b64_json)
with open(output, "wb") as f:
f.write(data)
print(f" Rendu final : {output}")
# Bilan : 5 low + 1 high au lieu de 5 high
print(f"\nBilan : {n_drafts} brouillons low + 1 final high")
print("Économie estimée : ~60% par rapport à tout en high")
workflow_economique(
"Illustration d'un campus technologique vert avec panneaux solaires, style architectural",
"campus_final.png"
)
Stratégie 2 : cache de prompts
Évitez de regénérer des images identiques en maintenant un cache :
import hashlib
import json
import os
from openai import OpenAI
import base64
client = OpenAI()
CACHE_DIR = "image_cache"
CACHE_INDEX = os.path.join(CACHE_DIR, "index.json")
def charger_cache() -> dict:
"""Charge l'index du cache."""
if os.path.exists(CACHE_INDEX):
with open(CACHE_INDEX, "r") as f:
return json.load(f)
return {}
def sauver_cache(cache: dict):
"""Sauvegarde l'index du cache."""
os.makedirs(CACHE_DIR, exist_ok=True)
with open(CACHE_INDEX, "w") as f:
json.dump(cache, f, indent=2, ensure_ascii=False)
def generer_avec_cache(
prompt: str,
size: str = "1024x1024",
quality: str = "medium"
) -> str:
"""Génère une image ou retourne le résultat en cache."""
# Créer une clé unique basée sur les paramètres
key_data = f"{prompt}|{size}|{quality}"
cache_key = hashlib.sha256(key_data.encode()).hexdigest()[:16]
cache = charger_cache()
if cache_key in cache:
cached_path = cache[cache_key]["path"]
if os.path.exists(cached_path):
print(f"Cache hit : {cached_path}")
return cached_path
else:
print("Cache périmé, regénération...")
# Pas en cache : générer
response = client.images.generate(
model="gpt-image-1",
prompt=prompt,
size=size,
quality=quality,
response_format="b64_json"
)
path = os.path.join(CACHE_DIR, f"{cache_key}.png")
os.makedirs(CACHE_DIR, exist_ok=True)
data = base64.b64decode(response.data[0].b64_json)
with open(path, "wb") as f:
f.write(data)
cache[cache_key] = {
"prompt": prompt[:100],
"size": size,
"quality": quality,
"path": path
}
sauver_cache(cache)
print(f"Généré et mis en cache : {path}")
return path
# Première génération : appel API
path1 = generer_avec_cache("Logo minimaliste pour une startup tech, bleu et blanc")
# Deuxième appel identique : servi depuis le cache
path2 = generer_avec_cache("Logo minimaliste pour une startup tech, bleu et blanc")
Stratégie 3 : dimensionner au plus juste
Ne générez pas en haute résolution si le visuel sera affiché en petit :
from openai import OpenAI
import base64
client = OpenAI()
def generer_pour_usage(prompt: str, usage: str, output: str):
"""Choisit automatiquement la résolution et qualité optimales."""
configs = {
# Usage web petit format
"thumbnail": {"size": "1024x1024", "quality": "low"},
"avatar": {"size": "1024x1024", "quality": "low"},
# Usage web standard
"blog_header": {"size": "1536x1024", "quality": "medium"},
"social_post": {"size": "1024x1024", "quality": "medium"},
# Usage nécessitant haute qualité
"hero_banner": {"size": "1536x1024", "quality": "high"},
"print_a4": {"size": "1024x1536", "quality": "high"},
"portfolio": {"size": "1024x1024", "quality": "high"},
}
config = configs.get(usage, {"size": "1024x1024", "quality": "medium"})
response = client.images.generate(
model="gpt-image-1",
prompt=prompt,
size=config["size"],
quality=config["quality"],
response_format="b64_json"
)
data = base64.b64decode(response.data[0].b64_json)
with open(output, "wb") as f:
f.write(data)
print(f"Usage {usage} ({config['quality']}/{config['size']}) → {output}")
generer_pour_usage("Chat mignon en astronaute", "thumbnail", "chat_thumb.png")
generer_pour_usage("Chat mignon en astronaute", "hero_banner", "chat_hero.png")
Stratégie 4 : édition plutôt que régénération complète
Quand vous devez modifier un détail, utilisez images.edit() plutôt que de tout regénérer :
from openai import OpenAI
from PIL import Image, ImageDraw
client = OpenAI()
# Au lieu de regénérer l'image entière pour changer un détail,
# éditez uniquement la zone concernée.
def modifier_detail(image_path: str, zone: tuple, nouveau_contenu: str, output: str):
"""
Modifie un détail dans une image existante.
zone : (x1, y1, x2, y2) de la zone à modifier
"""
source = Image.open(image_path)
mask = Image.new("RGBA", source.size, (0, 0, 0, 255))
draw = ImageDraw.Draw(mask)
draw.rectangle(zone, fill=(0, 0, 0, 0))
mask.save("temp_mask.png")
response = client.images.edit(
model="gpt-image-1",
image=open(image_path, "rb"),
mask=open("temp_mask.png", "rb"),
prompt=nouveau_contenu
)
import urllib.request
urllib.request.urlretrieve(response.data[0].url, output)
print(f"Détail modifié : {output}")
print("Coût : 1 édition au lieu de 1 génération complète")
# Exemple : changer juste le ciel d'une photo
modifier_detail(
"paysage.png",
(0, 0, 1024, 300),
"Ciel bleu avec quelques nuages blancs cotonneux",
"paysage_ciel_modifie.png"
)
Stratégie 5 : suivi des dépenses
Mettez en place un tracker de consommation :
import json
import os
from datetime import datetime
USAGE_FILE = "image_usage_log.json"
def log_generation(prompt: str, size: str, quality: str, n: int = 1):
"""Enregistre chaque génération pour suivre les coûts."""
if os.path.exists(USAGE_FILE):
with open(USAGE_FILE, "r") as f:
log = json.load(f)
else:
log = []
entry = {
"timestamp": datetime.now().isoformat(),
"prompt": prompt[:100],
"size": size,
"quality": quality,
"n_images": n,
}
log.append(entry)
with open(USAGE_FILE, "w") as f:
json.dump(log, f, indent=2, ensure_ascii=False)
def rapport_usage():
"""Affiche un rapport de consommation."""
if not os.path.exists(USAGE_FILE):
print("Aucune donnée de consommation.")
return
with open(USAGE_FILE, "r") as f:
log = json.load(f)
total = len(log)
par_qualite = {}
for entry in log:
q = entry["quality"]
par_qualite[q] = par_qualite.get(q, 0) + entry["n_images"]
print(f"Total des générations : {total}")
print(f"Répartition par qualité :")
for q, count in sorted(par_qualite.items()):
print(f" {q}: {count} images")
rapport_usage()
Tableau récapitulatif des stratégies
| Stratégie | Économie estimée | Effort |
|---|---|---|
| Low pour prototypage | 50-70% | Minimal |
| Cache de prompts | 10-30% (si répétitions) | Modéré |
| Résolution adaptée | 20-40% | Minimal |
| Édition vs régénération | 30-50% (par modification) | Modéré |
| Suivi des dépenses | Indirect (visibilité) | Modéré |
Exercice pratique
Créez un wrapper complet autour de client.images.generate() qui intègre automatiquement : le cache par prompt, l’adaptation qualité/résolution selon l’usage, et le logging de chaque appel. Testez-le avec 10 générations et affichez le rapport de consommation.