Guide complet de l'API Z-Image-Base : Application pratique du CFG et des Negative Prompts
Tutoriel complet sur l'API Z-Image-Base : réglage du paramètre CFG, utilisation de negative_prompt, guidage par image de référence, contrôle de la force. Inclut des exemples de code Python/cURL.
Bonjour, je m’appelle Dora. C’est incroyable qu’une petite contrariété m’ait poussée vers l’API Z-Image-Base. J’avais un lot d’images de référence, du contenu qui nécessitait un aspect visuel cohérent, et pas assez de temps pour surveiller les prompts. Je voyais sans cesse des gens ajuster manuellement les paramètres et publier des résultats ponctuels. C’est bien pour des démos. Mon travail avait besoin de quelque chose que je pouvais lancer deux fois par semaine sans mauvaises surprises. Alors, au fil de quelques sessions entre janvier et février 2026, j’ai configuré Z-Image-Base, j’ai prêté attention aux résistances qu’il m’opposait, et j’ai pris des notes sur ce qui le rendait vraiment fiable.
Ce n’est pas un tour d’horizon de tous les paramètres. Ce sont les parties que je continuais à toucher, ainsi que de petits ajustements qui ont réduit l’effort mental. Si vous êtes entouré d’outils IA et que vous voulez simplement en trouver un qui se comporte correctement, cela pourrait vous aider.
Configuration de base de l’API
Obtenir une clé API WaveSpeed
Je me suis inscrite et j’ai récupéré une clé API depuis le tableau de bord WaveSpeed. Rien de surprenant. Une petite remarque : les permissions de la clé étaient délimitées par projet. Utile dans les workflows partagés, mais cela signifie que vous voudrez nommer vos projets clairement dès le départ. J’ai créé un projet uniquement pour les expériences d’images afin que les journaux et les quotas ne se mélangent pas avec la production.
Si vous faites tourner vos clés (ce que je fais, mensuellement), Z-Image-Base l’a géré proprement. J’ai remplacé le secret dans mes variables d’environnement, relancé le script, et n’ai constaté aucun état d’authentification persistant. C’est comme ça que ça devrait fonctionner, mais j’ai quand même ressenti un peu de soulagement quand ça a marché du premier coup.
Structure de l’URL du point de terminaison
La base ressemblait à une configuration REST standard avec un sélecteur de modèle :
- Base :
https://api.wavespeed.ai - Chemin versionné :
/v1/images - Sélecteur de modèle :
model: "Z-Image-Base"
En pratique, j’envoyais des requêtes POST à /v1/images/generations pour les exécutions basées uniquement sur le prompt, et à /v1/images/edits lorsque je transmettais une image de référence. Je préfère des chemins clairs aux chaînes de requête surchargées, et cela correspondait à ma façon de penser. Si votre routage diffère, le nom du modèle reste important : Z-Image-Base semblait un peu plus conservateur que les modèles plus tape-à-l’œil, ce que j’appréciais.
Paramètres d’en-tête d’authentification
Rien de compliqué ici, mais l’orthographe exacte compte :
Authorization: Bearer YOUR_WAVESPEED_API_KEYContent-Type: application/jsonpour les prompts uniquementContent-Type: multipart/form-datalors du téléchargement d’une image
Je conserve les clés dans .env et les charge au moment de l’exécution. C’est banal, et ça évite les commits accidentels. Aussi : si vous scriptez depuis un CI, définissez la clé comme secret masqué et évitez de journaliser les requêtes complètes. Évident, mais ça vaut la peine d’être dit. Une seule ligne de journal égarée peut vous hanter. Selon les meilleures pratiques de sécurité de WaveSpeed, les clés API doivent être stockées dans des variables d’environnement, jamais exposées dans le code frontend, et renouvelées régulièrement.
Explication détaillée des paramètres principaux
prompt - Méthode de rédaction du prompt direct
Je n’ai pas compliqué le style de prompt. Avec Z-Image-Base, les prompts directs simples fonctionnaient mieux que la prose ornée. J’écrivais l’intention en premier, les contraintes en second :
- « Photo de produit de style éditorial d’une tasse en céramique sur une table en lin, lumière matinale douce. »
- Puis les détails : « aspect 35mm, étalonnage neutre des couleurs, sans texte, arrière-plan épuré. »
J’évitais les verbes sauf si j’avais besoin de mouvement. Le modèle était stable quand je restais concret : matériaux, lumière, rendu d’objectif, type d’arrière-plan. Il dérivait quand j’ajoutais des impressions. Si j’écrivais « minimalisme cosy », j’obtenais un chaos de bougies. Quand j’écrivais « lumière douce, une seule source diffuse, pas de bougies », il s’exécutait.
negative_prompt - Exclure les éléments indésirables
J’ai résisté aux prompts négatifs au début, mais ils sont devenus mon garde-fou. Je conservais une courte base de référence que je réutilisais :
- « no text, no watermark, no extra hands, no extra limbs, no logo, no signature, no border »
L’ajout de ceci a réduit le temps de nettoyage. Ça ne corrigeait pas tous les cas limites (les anneaux se glissent encore parfois sur les tasses), mais ça abaissait suffisamment le taux d’anomalies pour que j’arrête de relancer le rendu pour les petits lots. Quand j’avais besoin d’angles de produit stricts, j’ajoutais « no dramatic perspective, no tilt » et j’obtenais des prises de vue plus horizontales.
guidance_scale (CFG) - Contrôler le degré de conformité au prompt
Celui-ci comptait plus que je ne l’aurais cru. Mes notes :
- 5–6 : détendu, un peu plus de texture et d’improvisation
- 7–8 : fiable : ce que j’utilisais pour la plupart des exécutions
- 9+ : conformité littérale, mais parfois un éclairage fragile et des couleurs plus plates
Quand j’avais une bonne image de référence, je baissais le CFG à 6–7 et obtenais un meilleur micro-contraste. Sans référence, 7,5 était ma médiane. Selon la documentation technique de Z-Image, le modèle prend en charge le Classifier-Free Guidance (CFG) complet, offrant une précision pour l’ingénierie de prompts complexes. Au-dessus de 9, c’était comme demander à un ami minutieux d’être encore plus minutieux : pas faux, juste légèrement sans vie.
image + strength - Guidage par image de référence
Ce duo faisait le gros du travail. Je joignais un JPG de référence unique et ajustais strength pour contrôler l’adhérence du modèle :
- 0,25–0,35 : touche légère : conserve la palette et l’ambiance
- 0,45–0,6 : adhérence solide : la composition reste proche
- 0,7+ : quasi-copie : utile pour les variations avec de petits changements stylistiques
Je ne m’attendais pas à ce que 0,5 soit le point idéal, mais c’était le cas. Assez de structure pour conserver la mise en page, assez de liberté pour les ajustements d’éclairage. Des forces plus élevées le rendaient têtu : si la source avait une ombre au mauvais endroit, le modèle s’y accrochait. J’ai commencé à affiner le prompt avec « shadow on camera-left » et à réduire la force à la place. Moins de nouvelles tentatives de cette façon.
Une note pratique : je redimensionnais les entrées de référence à 1024 sur le côté long avant le téléchargement. Des dimensions cohérentes m’ont donné des recadrages et des vitesses plus prévisibles. Cela a également économisé un peu de bande passante sur de nombreuses itérations.
Implémentation du code
Test rapide avec cURL
Quand je configure un nouvel endpoint, je commence par cURL juste pour voir la forme de la réponse et les codes d’erreur.
curl -X POST \
https://api.wavespeed.ai/v1/images/generations \
-H "Authorization: Bearer $WAVESPEED_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "Z-Image-Base",
"prompt": "Editorial-style product photo of a ceramic mug on a linen table, soft morning light. 35mm look, neutral color grading, no text, uncluttered background.",
"negative_prompt": "no text, no watermark, no extra hands, no extra limbs, no logo, no signature, no border",
"guidance_scale": 7.5,
"size": "1024x1024"
}'
J’ai reçu en retour un payload JSON avec un tableau d’images (base64 ou URLs, selon les options). Si vous préférez des URLs directes, définissez une cible de sortie ou un indicateur de stockage si l’API le prend en charge. Je me suis contentée de base64 pendant les tests et j’ai décodé localement.
Exemple Python complet
C’est proche de ce que j’ai exécuté lors d’un traitement par lot un mardi matin. C’est simple, avec juste assez de structure pour les nouvelles tentatives.
import os
import base64
import requests
API_KEY = os.getenv("WAVESPEED_API_KEY")
BASE_URL = "https://api.wavespeed.ai/v1"
headers = {
"Authorization": f"Bearer {API_KEY}",
}
def generate_image(prompt, negative_prompt, guidance_scale=7.5, size="1024x1024"):
url = f"{BASE_URL}/images/generations"
payload = {
"model": "Z-Image-Base",
"prompt": prompt,
"negative_prompt": negative_prompt,
"guidance_scale": guidance_scale,
"size": size,
}
response = requests.post(
url,
headers={**headers, "Content-Type": "application/json"},
json=payload,
timeout=60
)
response.raise_for_status()
data = response.json()
b64_string = data["data"][0]["b64_json"]
return base64.b64decode(b64_string)
if __name__ == "__main__":
prompt = (
"Editorial-style product photo of a ceramic mug on a linen table, soft morning light. "
"35mm look, neutral color grading, no text, uncluttered background."
)
negative_prompt = (
"no text, no watermark, no extra hands, no extra limbs, "
"no logo, no signature, no border"
)
try:
image_bytes = generate_image(prompt, negative_prompt)
with open("output.png", "wb") as f:
f.write(image_bytes)
print("Saved output.png")
except requests.HTTPError as e:
print("HTTP error:", e.response.text if e.response else str(e))
except Exception as e:
print("Error:", str(e))
J’ai ajouté un délai d’attente de 60 secondes après qu’un job s’est bloqué sur un Wi-Fi instable. Le bloc d’exception est rudimentaire, mais en production je mappe les codes de statut (429, 503) sur des délais d’attente progressifs.
Exemple JavaScript/Node.js
Pour Node, j’ai utilisé fetch avec un petit helper. Même idée : garder l’authentification simple, faire remonter les erreurs clairement.
import fs from 'node:fs/promises';
import fetch from 'node-fetch';
const API_KEY = process.env.WAVESPEED_API_KEY;
const BASE_URL = 'https://api.wavespeed.ai/v1';
async function generateImage({
prompt,
negativePrompt,
guidanceScale = 7.5,
size = '1024x1024',
}) {
const response = await fetch(`${BASE_URL}/images/generations`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
model: 'Z-Image-Base',
prompt,
negative_prompt: negativePrompt,
guidance_scale: guidanceScale,
size,
}),
});
if (!response.ok) {
const errorText = await response.text();
throw new Error(`HTTP ${response.status}: ${errorText}`);
}
const json = await response.json();
const base64String = json.data[0].b64_json;
return Buffer.from(base64String, 'base64');
}
(async () => {
try {
const prompt =
'Editorial-style product photo of a ceramic mug on a linen table, soft morning light. ' +
'35mm look, neutral color grading, no text, uncluttered background.';
const negativePrompt =
'no text, no watermark, no extra hands, no extra limbs, ' +
'no logo, no signature, no border';
const imageBuffer = await generateImage({ prompt, negativePrompt });
await fs.writeFile('node-output.png', imageBuffer);
console.log('Saved node-output.png');
} catch (err) {
console.error('Error:', err.message);
}
})();
Une fois que ça fonctionnait, j’ai arrêté de penser à la plomberie, ce qui est l’objectif. L’API Z-Image-Base ne s’est pas mise en travers du chemin.
Mode asynchrone vs synchrone
J’ai essayé les deux modes. Le mode synchrone convenait bien pour les images uniques ou les vérifications rapides. La latence se situait en moyenne dans les basses secondes pour des sorties carrées de 1024. Quand je mettais en file d’attente 8 à 12 rendus, je passais en asynchrone pour que mon script ne se bloque pas sur chacun d’eux.
Le schéma async était prévisible : envoyer un job, obtenir un id, interroger un endpoint de statut, puis récupérer le résultat quand state: "succeeded". J’ai défini un intervalle d’interrogation de 2 à 3 secondes et un plafond de 2 minutes. Sur des réseaux instables, l’async m’a sauvée des timeouts parce que je pouvais reprendre l’interrogation sans soumettre à nouveau le job. Petit détail, grande différence sur le Wi-Fi en déplacement.
Si votre workflow est interactif (ajuster, voir, ajuster), le sync est plus agréable. S’il est par lots ou planifié, l’async maintient le pipeline plus fluide et échoue plus gracieusement.
Gestion des erreurs
Deux schémas m’ont aidée à maintenir les erreurs banales.
- Limites de débit (429) : j’ai utilisé un backoff exponentiel avec gigue. Commencer à ~500ms, plafonner à ~20s, et s’arrêter après 5 à 6 tentatives. Avec les jobs async, je réessayais simplement l’appel de statut plutôt que de remettre le rendu en file d’attente, à moins que l’API n’indique que le job était manquant.
- Erreurs de validation (400/422) : elles provenaient généralement de types de contenu non concordants ou de tailles non prises en charge. Quand je voyais un 422 sur les modifications d’image, c’était presque toujours le nom de mon champ form-data qui était erroné (
imagevsreference_image). J’ai ajouté une petite vérification de schéma avant les requêtes pour éviter ça.
Une dernière note : je journalisais model, guidance_scale, size, et un hash du prompt. Quand quelque chose d’étrange se glissait, je pouvais le reproduire exactement. C’est la différence entre hausser les épaules et corriger.
Est-ce que Z-Image-Base échoue moins que les autres modèles ? Difficile à dire. Ce que j’ai remarqué, c’est que quand il échouait, les messages étaient suffisamment précis pour que je ne perde pas de temps à deviner. C’est une bonne ergonomie.





