Milvus
Zilliz
  • Home
  • Blog
  • Construire un pipeline Bestseller-to-Image pour le commerce électronique avec Nano Banana 2 + Milvus + Qwen 3.5

Construire un pipeline Bestseller-to-Image pour le commerce électronique avec Nano Banana 2 + Milvus + Qwen 3.5

  • Tutorials
March 03, 2026
Lumina Wang

Si vous créez des outils d'IA pour les vendeurs du commerce électronique, vous avez probablement entendu cette demande un millier de fois : "J'ai un nouveau produit. Donnez-moi une image promotionnelle qui ait l'air de figurer dans une liste de best-sellers. Pas de photographe, pas de studio, et que ce soit bon marché".

Voilà le problème en quelques mots. Les vendeurs disposent de photos à plat et d'un catalogue de best-sellers qui convertissent déjà. Ils veulent faire le lien entre les deux grâce à l'IA, à la fois rapidement et à grande échelle.

Lorsque Google a lancé Nano Banana 2 (Gemini 3.1 Flash Image) le 26 février 2026, nous l'avons testé le jour même et l'avons intégré à notre pipeline de recherche existant basé sur Milvus. Résultat : le coût total de la génération d'images est tombé à environ un tiers de ce qu'il était auparavant, et le débit a doublé. La réduction du prix par image (environ 50% moins cher que Nano Banana Pro) explique en partie cela, mais les économies les plus importantes proviennent de l'élimination totale des cycles de retouche.

Cet article aborde les points positifs de Nano Banana 2 pour le commerce électronique, les points faibles, et propose un tutoriel pratique pour le pipeline complet : Milvus hybrid search pour trouver des best-sellers visuellement similaires, Qwen 3.5 pour l'analyse de style, et Nano Banana 2 pour la génération finale.

Quelles sont les nouveautés de Nano Banana 2 ?

Nano Banana 2 (Gemini 3.1 Flash Image) a été lancé le 26 février 2026. Il apporte la plupart des capacités de Nano Banana Pro à l'architecture Flash, ce qui signifie une génération plus rapide à un prix plus bas. Voici les principales améliorations :

  • Une qualité de niveau professionnel à la vitesse de Flash. Nano Banana 2 offre une connaissance, un raisonnement et une fidélité visuelle de classe mondiale, auparavant exclusifs à Pro, mais avec la latence et le débit de Flash.
  • Sortie de 512 px à 4K. Quatre niveaux de résolution (512 px, 1K, 2K, 4K) avec prise en charge native. Le niveau 512 px est nouveau et unique à Nano Banana 2.
  • 14 rapports d'aspect. Ajoute 4:1, 1:4, 8:1 et 1:8 à l'ensemble existant (1:1, 2:3, 3:2, 3:4, 4:3, 4:5, 5:4, 9:16, 16:9, 21:9).
  • Jusqu'à 14 images de référence. Maintient la ressemblance des personnages pour un maximum de 5 personnages et la fidélité des objets pour un maximum de 14 objets dans un seul flux de travail.
  • Rendu de texte amélioré. Génère un texte lisible et précis dans l'image dans plusieurs langues, avec prise en charge de la traduction et de la localisation au sein d'une même génération.
  • Recherche d'images. Utilise des données web en temps réel et des images issues de Google Search pour générer des représentations plus précises de sujets réels.
  • ~Environ 50 % moins cher par image. Résolution de 1K : 0,,067 contre0 ,134.

Un cas d'utilisation amusant de Nano Banano 2 : Générer un panorama tenant compte de la localisation à partir d'une simple capture d'écran de Google Maps

À partir d'une capture d'écran de Google Maps et d'une invite de style, le modèle reconnaît le contexte géographique et génère un panorama qui préserve les relations spatiales correctes. Utile pour produire des créations publicitaires ciblées par région (une toile de fond de café parisien, un paysage de rue à Tokyo) sans avoir recours à des photographies de stock.

Pour connaître l'ensemble des fonctionnalités, consultez le blog d'annonce de Google et la documentation destinée aux développeurs.

Que signifie cette mise à jour de Nano Banana pour le commerce électronique ?

Le commerce électronique est l'un des secteurs les plus gourmands en images. Listes de produits, annonces sur les places de marché, créations sociales, campagnes de bannières, vitrines localisées : chaque canal exige un flux constant d'actifs visuels, chacun avec ses propres spécifications.

Les principales exigences en matière de génération d'images par IA dans le commerce électronique se résument à ce qui suit :

  • Maintenir des coûts bas - le coût par image doit être adapté à l'échelle d'un catalogue.
  • S'aligner sur l'aspect des best-sellers éprouvés - les nouvelles images doivent s'aligner sur le style visuel des listes qui convertissent déjà.
  • Éviterla contrefaçon - il ne faut pas copier les créations des concurrents ni réutiliser des actifs protégés.

En outre, les vendeurs transfrontaliers ont besoin des éléments suivants

  • Prise en charge de formats multiplateformes - différents rapports d'aspect et spécifications pour les places de marché, les annonces et les vitrines.
  • Rendu de texte multilingue - un texte propre et précis dans l'image dans plusieurs langues.

Nano Banana 2 est proche de remplir toutes les conditions. Les sections ci-dessous décrivent ce que chaque amélioration signifie en pratique : là où elle résout directement un problème de commerce électronique, là où elle n'est pas à la hauteur, et quel est l'impact réel sur les coûts.

Réduction des coûts de production jusqu'à 60

À une résolution de 1K, Nano Banana 2 coûte 0,067par image contre0 pour Pro,134, ce qui représente une réduction de 50 %. Mais le prix par image n'est que la moitié de l'histoire. Ce qui tuait les budgets des utilisateurs, c'était le travail de reprise. Chaque place de marché applique ses propres spécifications d'image (1:1 pour Amazon, 3:4 pour les vitrines Shopify, ultra-large pour les bannières publicitaires), et la production de chaque variante signifiait une passe de génération séparée avec ses propres modes d'échec.

Nano Banana 2 regroupe ces étapes supplémentaires en une seule.

  • Quatre niveaux de résolution native.

  • 512 px (0,045 $)

  • 1K ($0.067)

  • 2K ($0.101)

  • 4K ($0.151).

Le niveau 512px est nouveau et unique à Nano Banana 2. Les utilisateurs peuvent maintenant générer des ébauches de 512 px à faible coût pour l'itération et sortir l'actif final en 2K ou 4K sans étape d'upscaling séparée.

  • 14 rapports d'aspect supportés au total. Voici quelques exemples :

  • 4:1

  • 1:4

  • 8:1

  • 1:8

Ces nouveaux rapports ultra-larges et ultra-grands s'ajoutent à l'ensemble existant. Une session de génération peut produire différents formats tels que : L'image principale d'Amazon (1:1), le héros de la vitrine (3:4) et la bannière publicitaire (ultra-large ou autres ratios).

Aucun recadrage, aucun remplissage, aucun re-promptage n'est nécessaire pour ces 4 rapports. Les 10 autres ratios sont inclus dans le jeu complet, ce qui rend le processus plus flexible sur les différentes plateformes.

L'économie d'environ 50 % par image permettrait à elle seule de réduire la facture de moitié. C'est l'élimination des travaux de retouche pour toutes les résolutions et tous les rapports d'aspect qui a permis de ramener le coût total à environ un tiers de ce qu'il était auparavant.

Prise en charge de 14 images de référence avec le style Bestseller

De toutes les mises à jour de Nano Banana 2, c'est le mélange multiréférences qui a le plus d'impact sur notre pipeline Milvus. Nano Banana 2 accepte jusqu'à 14 images de référence dans une seule requête, en conservant :

  • la ressemblance des caractères pour un maximum de 5 personnages
  • La fidélité des objets pour un maximum de 14 objets

En pratique, nous avons récupéré plusieurs images de best-sellers dans Milvus, nous les avons passées en référence et l'image générée a hérité de la composition de la scène, de l'éclairage, de la pose et du placement des accessoires. Il n'a pas été nécessaire de procéder à une ingénierie rapide pour reconstruire ces modèles à la main.

Les modèles précédents ne prenaient en charge qu'une ou deux références, ce qui obligeait les utilisateurs à choisir un seul best-seller à imiter. Avec 14 emplacements de référence, nous pouvions mélanger les caractéristiques de plusieurs best-sellers et laisser le modèle synthétiser un style composite. C'est cette capacité qui rend possible le pipeline basé sur la recherche dans le tutoriel ci-dessous.

Produire des images de qualité supérieure, prêtes à être commercialisées, sans les coûts de production ou la logistique traditionnels

Pour une génération d'images cohérente et fiable, évitez de déverser toutes vos exigences dans une seule invite. Une approche plus fiable consiste à travailler par étapes : générez d'abord l'arrière-plan, puis le modèle séparément, et enfin composez-les ensemble.

Nous avons testé la génération d'arrière-plan sur les trois modèles de Nano Banana avec la même invite : une image ultra-large 4:1 de l'horizon de Shanghai en temps de pluie, vue à travers une fenêtre, avec la tour de la Perle de l'Orient visible. Cette invite permet de tester la composition, les détails architecturaux et le photoréalisme en un seul passage.

Nano Banana original vs. Nano Banana Pro vs. Nano Banana 2

  • Original Nano Banana. Texture de pluie naturelle avec une distribution de gouttelettes crédible, mais détails des bâtiments trop lissés. L'Oriental Pearl Tower était à peine reconnaissable et la résolution ne répondait pas aux exigences de la production.
  • Nano Banana Pro. Atmosphère cinématographique : l'éclairage intérieur chaud s'oppose de manière convaincante à la pluie froide. Cependant, le cadre de la fenêtre a été complètement omis, ce qui a atténué la sensation de profondeur de l'image. Utilisable en tant qu'image de soutien, pas en tant que héros.
  • Nano Banana 2. Rendu de la scène complète. Le cadre de la fenêtre au premier plan crée de la profondeur. La tour de la Perle de l'Orient est clairement détaillée. Les bateaux apparaissent sur la rivière Huangpu. L'éclairage en couches distingue la chaleur intérieure du couvert extérieur. Les textures de la pluie et des taches d'eau étaient quasi photographiques, et le rapport ultra-large 4:1 maintenait la perspective correcte avec seulement une distorsion mineure sur le bord gauche de la fenêtre.

Pour la plupart des tâches de génération d'arrière-plan dans la photographie de produits, nous avons trouvé que la sortie du Nano Banana 2 était utilisable sans post-traitement.

Rendu propre du texte à l'image dans toutes les langues

Les étiquettes de prix, les bannières promotionnelles et les textes multilingues sont inévitables dans les images de commerce électronique, et ils ont toujours été un point de rupture pour la génération d'IA. Nano Banana 2 les gère beaucoup mieux, en prenant en charge le rendu du texte dans l'image dans plusieurs langues, avec traduction et localisation en une seule génération.

Rendu de texte standard. Lors de nos tests, le rendu de texte était sans erreur dans tous les formats de commerce électronique que nous avons essayés : étiquettes de prix, courtes phrases de marketing, et descriptions de produits bilingues.

Continuité de l'écriture manuscrite. Le commerce électronique nécessitant souvent des éléments manuscrits tels que des étiquettes de prix et des cartes personnalisées, nous avons testé si les modèles pouvaient correspondre à un style manuscrit existant et l'étendre - plus précisément, correspondre à une liste de tâches manuscrite et ajouter 5 nouveaux éléments dans le même style. Résultats pour les trois modèles :

  • Original Nano Banana. Numéros de séquence répétés, structure mal comprise.
  • Nano Banana Pro. Mise en page correcte, mais mauvaise reproduction du style de police.
  • Nano Banana 2. Aucune erreur. Le poids des traits et le style de la forme des lettres correspondent suffisamment pour qu'il soit impossible de les distinguer de la source.

Toutefois, la documentation de Google indique que Nano Banana 2 "peut encore éprouver des difficultés avec l'orthographe et les détails fins des images". Nos résultats étaient corrects dans tous les formats que nous avons testés, mais tout flux de production devrait inclure une étape de vérification du texte avant la publication.

Tutoriel étape par étape : Construire un pipeline Bestseller-image avec Milvus, Qwen 3.5 et Nano Banana 2

Avant de commencer : Architecture et configuration du modèle

Pour éviter le caractère aléatoire de la génération d'un seul message, nous avons divisé le processus en trois étapes contrôlables : récupérer ce qui fonctionne déjà avec la recherche hybride Milvus, analyser pourquoi cela fonctionne avec Qwen 3.5, puis générer l'image finale avec ces contraintes intégrées avec Nano Banana 2.

Voici un bref aperçu de chaque outil si vous n'avez jamais travaillé avec eux :

  • Milvus: la base de données vectorielles open-source la plus largement adoptée. Elle stocke votre catalogue de produits sous forme de vecteurs et effectue une recherche hybride (filtres denses + épars + scalaires) pour trouver les images de best-sellers les plus similaires à un nouveau produit.
  • Qwen 3.5: un LLM multimodal populaire. Il prend les images de best-sellers récupérées et extrait les modèles visuels qui les sous-tendent (disposition de la scène, éclairage, pose, humeur) dans un message structuré sur le style.
  • Nano Banana 2: modèle de génération d'images de Google (Gemini 3.1 Flash Image). Prend trois entrées : le nouveau produit flat-lay, une référence de best-seller et l'invite de style de Qwen 3.5. Produit la photo promotionnelle finale.

La logique qui sous-tend cette architecture part d'un constat : l'atout visuel le plus précieux de tout catalogue de commerce électronique est la bibliothèque d'images de best-sellers qui ont déjà été converties. Les poses, les compositions et l'éclairage de ces photos ont été affinés grâce à des dépenses publicitaires réelles. L'extraction directe de ces modèles est un ordre de grandeur plus rapide que la rétro-ingénierie par l'écriture d'un message, et cette étape d'extraction est exactement ce qu'une base de données vectorielles gère.

Voici le flux complet. Nous appelons chaque modèle par l'intermédiaire de l'API OpenRouter, de sorte qu'il n'y a pas besoin de GPU local et qu'il n'y a pas de poids de modèle à télécharger.

New product flat-lay
│
│── Embed → Llama Nemotron Embed VL 1B v2
│
│── Search → Milvus hybrid search
│   ├── Dense vectors (visual similarity)
│   ├── Sparse vectors (keyword matching)
│   └── Scalar filters (category + sales volume)
│
│── Analyze → Qwen 3.5 extracts style from retrieved bestsellers
│   └── scene, lighting, pose, mood → style prompt
│
└── Generate → Nano Banana 2
    ├── Inputs: new product + bestseller reference + style prompt
    └── Output: promotional photo

Nous nous appuyons sur trois capacités de Milvus pour faire fonctionner l'étape de recherche :

  1. Recherche hybride dense + clairsemée. Nous exécutons les intégrations d'images et les vecteurs TF-IDF de texte en tant que requêtes parallèles, puis nous fusionnons les deux ensembles de résultats avec le reranking RRF (Reciprocal Rank Fusion).
  2. Filtrage par champs scalaires. Nous filtrons les champs de métadonnées tels que la catégorie et le nombre de ventes avant la comparaison des vecteurs, de sorte que les résultats ne comprennent que les produits pertinents et performants.
  3. Schéma multi-champs. Nous stockons les vecteurs denses, les vecteurs épars et les métadonnées scalaires dans une seule collection Milvus, ce qui permet de conserver l'ensemble de la logique de recherche dans une seule requête au lieu de la disperser dans plusieurs systèmes.

Préparation des données

Catalogue de produits historique

Nous commençons avec deux ressources : un dossier images/ de photos de produits existants et un fichier products.csv contenant leurs métadonnées.

images/
├── SKU001.jpg
├── SKU002.jpg
├── ...
└── SKU040.jpg

products.csv fields: product_id, image_path, category, color, style, season, sales_count, description, price

Données sur les nouveaux produits

Pour les produits pour lesquels nous voulons générer des images promotionnelles, nous préparons une structure parallèle : un dossier new_products/ et new_products.csv.

new_products/
├── NEW001.jpg    # Blue knit cardigan + grey tulle skirt set
├── NEW002.jpg    # Light green floral ruffle maxi dress
├── NEW003.jpg    # Camel turtleneck knit dress
└── NEW004.jpg    # Dark grey ethnic-style cowl neck top dress

new_products.csv fields: new_id, image_path, category, style, season, prompt_hint

Étape 1 : Installer les dépendances

!pip install pymilvus openai requests pillow scikit-learn tqdm

Étape 2 : Importer les modules et les configurations

import os, io, base64, csv, time
import requests as req
import numpy as np
from PIL import Image
from tqdm.notebook import tqdm
from sklearn.feature_extraction.text import TfidfVectorizer
from IPython.display import display

from openai import OpenAI from pymilvus import MilvusClient, DataType, AnnSearchRequest, RRFRanker

Configurez tous les modèles et chemins d'accès :

# -- Config --
OPENROUTER_API_KEY = os.environ.get(
    "OPENROUTER_API_KEY",
    "<YOUR_OPENROUTER_API_KEY>",
)

# Models (all via OpenRouter, no local download needed) EMBED_MODEL = “nvidia/llama-nemotron-embed-vl-1b-v2” # free, image+text → 2048d EMBED_DIM = 2048 LLM_MODEL = “qwen/qwen3.5-397b-a17b” # style analysis IMAGE_GEN_MODEL = “google/gemini-3.1-flash-image-preview” # Nano Banana 2

# Milvus MILVUS_URI = “./milvus_fashion.db” COLLECTION = “fashion_products” TOP_K = 3

# Paths IMAGE_DIR = “./images” NEW_PRODUCT_DIR = “./new_products” PRODUCT_CSV = “./products.csv” NEW_PRODUCT_CSV = “./new_products.csv”

# OpenRouter client (shared for LLM + image gen) llm = OpenAI(api_key=OPENROUTER_API_KEY, base_url=“https://openrouter.ai/api/v1”)

print(“Config loaded. All models via OpenRouter API.”)

Fonctions utilitaires

Ces fonctions d'aide gèrent l'encodage des images, les appels à l'API et l'analyse des réponses :

  • image_to_uri() : Convertit une image PIL en un URI de données base64 pour le transport de l'API.
  • get_image_embeddings() : Encode par lots des images en vecteurs à 2048 dimensions via l'API d'encodage OpenRouter.
  • get_text_embedding() : Encode le texte dans le même espace vectoriel à 2048 dimensions.
  • sparse_to_dict() : Convertit une ligne de matrice sparse scipy dans le format {index : value} attendu par Milvus pour les vecteurs sparse.
  • extract_images() : Extrait les images générées à partir de la réponse de l'API Nano Banana 2.
# -- Utility functions --

def image_to_uri(img, max_size=1024): “""Convert PIL Image to base64 data URI.""” img = img.copy() w, h = img.size if max(w, h) > max_size: r = max_size / max(w, h) img = img.resize((int(w * r), int(h * r)), Image.LANCZOS) buf = io.BytesIO() img.save(buf, format=“JPEG”, quality=85) return f"data:image/jpeg;base64,{base64.b64encode(buf.getvalue()).decode()}"

def get_image_embeddings(images, batch_size=5): “""Encode images via OpenRouter embedding API.""” all_embs = [] for i in tqdm(range(0, len(images), batch_size), desc=“Encoding images”): batch = images[i : i + batch_size] inputs = [ {“content”: [{“type”: “image_url”, “image_url”: {“url”: image_to_uri(img, max_size=512)}}]} for img in batch ] resp = req.post( “https://openrouter.ai/api/v1/embeddings”, headers={“Authorization”: f"Bearer {OPENROUTER_API_KEY}"}, json={“model”: EMBED_MODEL, “input”: inputs}, timeout=120, ) data = resp.json() if “data” not in data: print(f"API error: {data}") continue for item in sorted(data[“data”], key=lambda x: x[“index”]): all_embs.append(item[“embedding”]) time.sleep(0.5) # rate limit friendly return np.array(all_embs, dtype=np.float32)

def get_text_embedding(text): “""Encode text via OpenRouter embedding API.""” resp = req.post( “https://openrouter.ai/api/v1/embeddings”, headers={“Authorization”: f"Bearer {OPENROUTER_API_KEY}"}, json={“model”: EMBED_MODEL, “input”: text}, timeout=60, ) return np.array(resp.json()[“data”][0][“embedding”], dtype=np.float32)

def sparse_to_dict(sparse_row): “""Convert scipy sparse row to Milvus sparse vector format {index: value}.""” coo = sparse_row.tocoo() return {int(i): float(v) for i, v in zip(coo.col, coo.data)}

def extract_images(response): “""Extract generated images from OpenRouter response.""” images = [] raw = response.model_dump() msg = raw[“choices”][0][“message”] # Method 1: images field (OpenRouter extension) if “images” in msg and msg[“images”]: for img_data in msg[“images”]: url = img_data[“image_url”][“url”] b64 = url.split(“,”, 1)[1] images.append(Image.open(io.BytesIO(base64.b64decode(b64)))) # Method 2: inline base64 in content parts if not images and isinstance(msg.get(“content”), list): for part in msg[“content”]: if isinstance(part, dict) and part.get(“type”) == “image_url”: url = part[“image_url”][“url”] if url.startswith(“data:image”): b64 = url.split(“,”, 1)[1] images.append(Image.open(io.BytesIO(base64.b64decode(b64)))) return images

print(“Utility functions ready.”)

Étape 3 : Chargement du catalogue de produits

Lire products.csv et charger les images de produits correspondantes :

with open(PRODUCT_CSV, newline="", encoding="utf-8") as f:
    products = list(csv.DictReader(f))

product_images = [] for p in products: img = Image.open(os.path.join(IMAGE_DIR, p[“image_path”])).convert(“RGB”) product_images.append(img)

print(f"Loaded {len(products)} products.") for i in range(3): p = products[i] print(f"{p[‘product_id’]} | {p[‘category’]} | {p[‘color’]} | {p[‘style’]} | sales: {p[‘sales_count’]}") display(product_images[i].resize((180, int(180 * product_images[i].height / product_images[i].width))))

Exemple de sortie :

Étape 4 : Générer des liens (Embeddings)

La recherche hybride nécessite deux types de vecteurs pour chaque produit.

4.1 Vecteurs denses : encastrements d'images

Le modèle nvidia/llama-nemotron-embed-vl-1b-v2 encode chaque image de produit dans un vecteur dense à 2048 dimensions. Comme ce modèle prend en charge les entrées image et texte dans un espace vectoriel partagé, les mêmes encastrements fonctionnent pour la recherche d'image à image et de texte à image.

# Dense embeddings: image → 2048-dim vector via OpenRouter API
dense_vectors = get_image_embeddings(product_images, batch_size=5)
print(f"Dense vectors: {dense_vectors.shape}  (products x {EMBED_DIM}d)")

Résultats :

Dense vectors: (40, 2048)  (products x 2048d)

4,2 vecteurs denses : Encastrements de texte TF-IDF

Les descriptions textuelles des produits sont encodées dans des vecteurs denses à l'aide du vecteur TF-IDF de scikit-learn. Ces vecteurs capturent la correspondance au niveau des mots-clés que les vecteurs denses peuvent manquer.

# Sparse embeddings: TF-IDF on product descriptions
descriptions = [p["description"] for p in products]
tfidf = TfidfVectorizer(stop_words="english", max_features=500)
tfidf_matrix = tfidf.fit_transform(descriptions)

sparse_vectors = [sparse_to_dict(tfidf_matrix[i]) for i in range(len(products))] print(f"Sparse vectors: {len(sparse_vectors)} products, vocab size: {len(tfidf.vocabulary_)}") print(f"Sample sparse vector (SKU001): {len(sparse_vectors[0])} non-zero terms")

Sortie :

Sparse vectors: 40 products, vocab size: 179
Sample sparse vector (SKU001): 11 non-zero terms

Pourquoi les deux types de vecteurs ? Les vecteurs denses et peu denses se complètent. Les vecteurs denses capturent la similarité visuelle : palette de couleurs, silhouette du vêtement, style général. Les vecteurs peu denses capturent la sémantique des mots-clés : des termes tels que "floral", "midi" ou "mousseline" qui signalent les attributs du produit. La combinaison de ces deux approches permet d'obtenir une qualité d'extraction nettement supérieure à celle de l'une ou l'autre approche prise isolément.

Étape 5 : Création d'une collection Milvus avec un schéma hybride

Cette étape permet de créer une collection Milvus unique qui stocke ensemble les vecteurs denses, les vecteurs épars et les champs de métadonnées scalaires. Ce schéma unifié permet d'effectuer une recherche hybride en une seule requête.

ChampType de champObjectif
vecteur_denseFLOAT_VECTOR (2048d)Incrustation d'image, similarité COSINE
vecteur_denseVECTEUR_FLOAT_SPAREVecteur clair TF-IDF, produit intérieur
catégorieVARCHARÉtiquette de la catégorie pour le filtrage
sales_countINT64Volume historique des ventes pour le filtrage
couleur, style, saisonVARCHARÉtiquettes de métadonnées supplémentaires
prixFLOATPrix du produit
milvus_client = MilvusClient(uri=MILVUS_URI)

if milvus_client.has_collection(COLLECTION): milvus_client.drop_collection(COLLECTION)

schema = milvus_client.create_schema(auto_id=True, enable_dynamic_field=True) schema.add_field(“id”, DataType.INT64, is_primary=True) schema.add_field(“product_id”, DataType.VARCHAR, max_length=20) schema.add_field(“category”, DataType.VARCHAR, max_length=50) schema.add_field(“color”, DataType.VARCHAR, max_length=50) schema.add_field(“style”, DataType.VARCHAR, max_length=50) schema.add_field(“season”, DataType.VARCHAR, max_length=50) schema.add_field(“sales_count”, DataType.INT64) schema.add_field(“description”, DataType.VARCHAR, max_length=500) schema.add_field(“price”, DataType.FLOAT) schema.add_field(“dense_vector”, DataType.FLOAT_VECTOR, dim=EMBED_DIM) schema.add_field(“sparse_vector”, DataType.SPARSE_FLOAT_VECTOR)

index_params = milvus_client.prepare_index_params() index_params.add_index(field_name=“dense_vector”, index_type=“FLAT”, metric_type=“COSINE”) index_params.add_index(field_name=“sparse_vector”, index_type=“SPARSE_INVERTED_INDEX”, metric_type=“IP”)

milvus_client.create_collection(COLLECTION, schema=schema, index_params=index_params) print(f"Milvus collection '{COLLECTION}' created with hybrid schema.")

Insérer les données relatives au produit :

# Insert all products
rows = []
for i, p in enumerate(products):
    rows.append({
        "product_id": p["product_id"],
        "category": p["category"],
        "color": p["color"],
        "style": p["style"],
        "season": p["season"],
        "sales_count": int(p["sales_count"]),
        "description": p["description"],
        "price": float(p["price"]),
        "dense_vector": dense_vectors[i].tolist(),
        "sparse_vector": sparse_vectors[i],
    })

milvus_client.insert(COLLECTION, rows) stats = milvus_client.get_collection_stats(COLLECTION) print(f"Inserted {stats[‘row_count’]} products into Milvus.")

Sortie :

Inserted 40 products into Milvus.

Étape 6 : Recherche hybride pour trouver des best-sellers similaires

Il s'agit de l'étape principale de recherche. Pour chaque nouveau produit, le pipeline exécute trois opérations simultanément :

  1. Recherche dense: recherche de produits dont les images sont visuellement similaires.
  2. Recherche éparse: recherche des produits dont les mots-clés correspondent au texte via TF-IDF.
  3. Filtrage scalaire: restreint les résultats à la même catégorie et aux produits dont le nombre de ventes est supérieur à 1500.
  4. RRF reranking: fusionne les listes de résultats denses et éparses à l'aide de la fusion réciproque des rangs (Reciprocal Rank Fusion).

Chargez le nouveau produit :

# Load new products
with open(NEW_PRODUCT_CSV, newline="", encoding="utf-8") as f:
    new_products = list(csv.DictReader(f))

# Pick the first new product for demo new_prod = new_products[0] new_img = Image.open(os.path.join(NEW_PRODUCT_DIR, new_prod[“image_path”])).convert(“RGB”)

print(f"New product: {new_prod[‘new_id’]}") print(f"Category: {new_prod[‘category’]} | Style: {new_prod[‘style’]} | Season: {new_prod[‘season’]}") print(f"Prompt hint: {new_prod[‘prompt_hint’]}") display(new_img.resize((300, int(300 * new_img.height / new_img.width))))

Sortie :

Encodage du nouveau produit :

# Encode new product
# Dense: image embedding via API
query_dense = get_image_embeddings([new_img], batch_size=1)[0]

# Sparse: TF-IDF from text query query_text = f"{new_prod[‘category’]} {new_prod[‘style’]} {new_prod[‘season’]} {new_prod[‘prompt_hint’]}" query_sparse = sparse_to_dict(tfidf.transform([query_text])[0])

# Scalar filter filter_expr = f’category == "{new_prod[“category”]}" and sales_count > 1500’

print(f"Dense query: {query_dense.shape}") print(f"Sparse query: {len(query_sparse)} non-zero terms") print(f"Filter: {filter_expr}")

Sortie : Encoder le nouveau produit : Sortie : Encoder le nouveau produit : Sortie :

Dense query: (2048,)
Sparse query: 6 non-zero terms
Filter: category == "midi_dress" and sales_count > 1500

Exécuter la recherche hybride

Voici les principaux appels API :

  • AnnSearchRequest crée des requêtes de recherche distinctes pour les champs vectoriels denses et épars.
  • expr=filter_expr applique un filtrage scalaire dans chaque requête de recherche.
  • RRFRanker(k=60) fusionne les deux listes de résultats classés à l'aide de l'algorithme Reciprocal Rank Fusion.
  • hybrid_search exécute les deux requêtes et renvoie les résultats fusionnés et reclassés.
# Hybrid search: dense + sparse + scalar filter + RRF reranking
dense_req = AnnSearchRequest(
    data=[query_dense.tolist()],
    anns_field="dense_vector",
    param={"metric_type": "COSINE"},
    limit=20,
    expr=filter_expr,
)
sparse_req = AnnSearchRequest(
    data=[query_sparse],
    anns_field="sparse_vector",
    param={"metric_type": "IP"},
    limit=20,
    expr=filter_expr,
)

results = milvus_client.hybrid_search( collection_name=COLLECTION, reqs=[dense_req, sparse_req], ranker=RRFRanker(k=60), limit=TOP_K, output_fields=[“product_id”, “category”, “color”, “style”, “season”, “sales_count”, “description”, “price”], )

# Display retrieved bestsellers retrieved_products = [] retrieved_images = [] print(f"Top-{TOP_K} similar bestsellers:\n") for hit in results[0]: entity = hit[“entity”] pid = entity[“product_id”] img = Image.open(os.path.join(IMAGE_DIR, f"{pid}.jpg")).convert(“RGB”) retrieved_products.append(entity) retrieved_images.append(img) print(f"{pid} | {entity[‘category’]} | {entity[‘color’]} | {entity[‘style’]} " f"| sales: {entity[‘sales_count’]} | ${entity[‘price’]:.1f} | score: {hit[‘distance’]:.4f}") print(f" {entity[‘description’]}") display(img.resize((250, int(250 * img.height / img.width)))) print()

Résultat : les 3 best-sellers les plus similaires, classés par score fusionné.

Étape 7 : Analyse du style des best-sellers avec Qwen 3.5

Nous introduisons les images de best-sellers récupérées dans Qwen 3.5 et lui demandons d'extraire leur ADN visuel commun : la composition de la scène, la configuration de l'éclairage, la pose du modèle et l'ambiance générale. De cette analyse, nous obtenons une génération unique prête à être transmise à Nano Banana 2.

content = [
    {"type": "image_url", "image_url": {"url": image_to_uri(img)}}
    for img in retrieved_images
]
content.append({
    "type": "text",
    "text": (
        "These are our top-selling fashion product photos.\n\n"
        "Analyze their common visual style in these dimensions:\n"
        "1. Scene / background setting\n"
        "2. Lighting and color tone\n"
        "3. Model pose and framing\n"
        "4. Overall mood and aesthetic\n\n"
        "Then, based on this analysis, write ONE concise image generation prompt "
        "(under 100 words) that captures this style. The prompt should describe "
        "a scene for a model wearing a new clothing item. "
        "Output ONLY the prompt, nothing else."
    ),
})

response = llm.chat.completions.create( model=LLM_MODEL, messages=[{“role”: “user”, “content”: content}], max_tokens=512, temperature=0.7, ) style_prompt = response.choices[0].message.content.strip() print(“Style prompt from Qwen3.5:\n”) print(style_prompt)

Exemple de résultat :

Style prompt from Qwen3.5:

Professional full-body fashion photograph of a model wearing a stylish new dress. Bright, soft high-key lighting that illuminates the subject evenly. Clean, uncluttered background, either stark white or a softly blurred bright outdoor setting. The model stands in a relaxed, natural pose to showcase the garment’s silhouette and drape. Sharp focus, vibrant colors, fresh and elegant commercial aesthetic.

Étape 8 : Générer l'image promotionnelle avec Nano Banana 2

Nous transmettons trois entrées à Nano Banana 2 : la photo du nouveau produit, l'image du best-seller le mieux classé et l'invite de style que nous avons extraite à l'étape précédente. Le modèle les compose en une photo promotionnelle qui associe le nouveau vêtement à un style visuel éprouvé.

gen_prompt = (
    f"I have a new clothing product (Image 1: flat-lay photo) and a reference "
    f"promotional photo from our bestselling catalog (Image 2).\n\n"
    f"Generate a professional e-commerce promotional photograph of a female model "
    f"wearing the clothing from Image 1.\n\n"
    f"Style guidance: {style_prompt}\n\n"
    f"Scene hint: {new_prod['prompt_hint']}\n\n"
    f"Requirements:\n"
    f"- Full body shot, photorealistic, high quality\n"
    f"- The clothing should match Image 1 exactly\n"
    f"- The photo style and mood should match Image 2"
)

gen_content = [ {“type”: “image_url”, “image_url”: {“url”: image_to_uri(new_img)}}, {“type”: “image_url”, “image_url”: {“url”: image_to_uri(retrieved_images[0])}}, {“type”: “text”, “text”: gen_prompt}, ]

print(“Generating promotional photo with Nano Banana 2…”) gen_response = llm.chat.completions.create( model=IMAGE_GEN_MODEL, messages=[{“role”: “user”, “content”: gen_content}], extra_body={ “modalities”: [“text”, “image”], “image_config”: {“aspect_ratio”: “3:4”, “image_size”: “2K”}, }, ) print(“Done!”)

Paramètres clés de l'appel API Nano Banana 2 :

  • modalités : ["text", "image"] : déclare que la réponse doit inclure une image.
  • image_config.aspect_ratio : contrôle le rapport d'aspect de la sortie (3:4 fonctionne bien pour les portraits et les photos de mode).
  • image_config.image_size : définit la résolution. Nano Banana 2 prend en charge les images de 512 px à 4K.

Extraire l'image générée :

generated_images = extract_images(gen_response)

text_content = gen_response.choices[0].message.content if text_content: print(f"Model response: {text_content[:300]}\n")

if generated_images: for i, img in enumerate(generated_images): print(f"— Generated promo photo {i+1} —") display(img) img.save(f"promo_{new_prod[‘new_id’]}{i+1}.png") print(f"Saved: promo{new_prod[‘new_id’]}_{i+1}.png") else: print(“No image generated. Raw response:”) print(gen_response.model_dump())

Sortie :

Étape 9 : Comparaison côte à côte

L'image générée respecte les grandes lignes : l'éclairage est doux et uniforme, la pose du modèle semble naturelle et l'ambiance correspond à la référence du best-seller.

Là où le résultat n'est pas à la hauteur, c'est au niveau du mélange des vêtements. Le cardigan semble collé sur le modèle plutôt que porté, et l'étiquette blanche de l'encolure transparaît. La génération en un seul passage a du mal à intégrer finement les vêtements au corps, c'est pourquoi nous proposons des solutions de contournement dans le résumé.

Étape 10 : Génération de lots pour tous les nouveaux produits

Nous regroupons l'ensemble du pipeline dans une seule fonction et l'exécutons pour les nouveaux produits restants. Le code des lots est omis ici par souci de concision ; contactez-nous si vous avez besoin de l'implémentation complète.

Deux choses ressortent des résultats des lots. Les invites de style que nous obtenons de Qwen 3.5 s'adaptent de manière significative à chaque produit : une robe d'été et un tricot d'hiver reçoivent des descriptions de scène véritablement différentes, adaptées à la saison, au cas d'utilisation et aux accessoires. Les images obtenues par Nano Banana 2, quant à elles, sont comparables à de véritables photographies de studio en termes d'éclairage, de texture et de composition.

Conclusion

Dans cet article, nous avons présenté ce que Nano Banana 2 apporte à la génération d'images pour le commerce électronique, nous l'avons comparé à la version originale de Nano Banana et à la version Pro dans le cadre de tâches de production réelles, et nous avons expliqué comment construire un pipeline bestseller-image avec Milvus, Qwen 3.5 et Nano Banana 2.

Ce pipeline présente quatre avantages pratiques :

  • Coût contrôlé, budgets prévisibles. Le modèle d'intégration (Llama Nemotron Embed VL 1B v2) est gratuit sur OpenRouter. Nano Banana 2 fonctionne à peu près à la moitié du coût par image de Pro, et la sortie multiformat native élimine les cycles de retouche qui doublaient ou triplaient la facture effective. Pour les équipes de commerce électronique qui gèrent des milliers de références par saison, cette prévisibilité signifie que la production d'images s'adapte au catalogue au lieu de dépasser le budget.
  • Automatisation de bout en bout, temps de référencement plus rapide. Le flux qui va de la photo de produit à plat à l'image promotionnelle finie se déroule sans intervention manuelle. Un nouveau produit peut passer de la photo d'entrepôt à l'image de référencement prête pour le marché en quelques minutes plutôt qu'en quelques jours, ce qui est particulièrement important pendant les saisons de pointe, lorsque le taux de rotation du catalogue est le plus élevé.
  • Aucun GPU local n'est nécessaire, ce qui réduit la barrière à l'entrée. Chaque modèle est exécuté via l'API OpenRouter. Une équipe ne disposant pas d'infrastructure ML ni de personnel d'ingénierie dédié peut exécuter ce pipeline à partir d'un ordinateur portable. Il n'y a rien à provisionner, rien à maintenir et aucun investissement matériel initial.
  • Une plus grande précision d'extraction, une plus grande cohérence de la marque. Milvus combine le filtrage dense, clairsemé et scalaire en une seule requête, ce qui lui permet de surpasser systématiquement les approches à vecteur unique pour la correspondance des produits. En pratique, cela signifie que les images générées héritent de manière plus fiable du langage visuel établi de votre marque : l'éclairage, la composition et le style que vos best-sellers existants ont déjà prouvé convertir. Les images générées ont l'air d'avoir leur place dans votre magasin, et non pas d'être des images génériques d'IA.

Il existe également des limites qu'il convient de connaître :

  • Le mélange entre le vêtement et le corps. La génération d'un seul passage peut donner l'impression que les vêtements sont composés plutôt que portés. Les détails fins tels que les petits accessoires sont parfois flous. Solution : générer par étapes (d'abord l'arrière-plan, puis la pose du modèle, puis le composite). Cette approche multi-passages donne à chaque étape un champ d'application plus étroit et améliore considérablement la qualité du mélange.
  • Fidélité des détails dans les cas extrêmes. Les accessoires, les motifs et les mises en page comportant beaucoup de texte peuvent perdre de leur netteté. Solution : ajouter des contraintes explicites à l'invite de génération ("les vêtements s'adaptent naturellement au corps, pas d'étiquettes apparentes, pas d'éléments supplémentaires, les détails du produit sont nets"). Si la qualité n'est toujours pas au rendez-vous pour un produit spécifique, passez à Nano Banana Pro pour la version finale.

Milvus est la base de données vectorielles open-source qui alimente l'étape de recherche hybride, et si vous souhaitez y jeter un coup d'œil ou essayer d'y insérer vos propres photos de produits, lequickstart prend environ dix minutes. Nous avons une communauté assez active sur Discord et Slack, et nous serions ravis de voir ce que les gens construisent avec cela. Et si vous finissez par faire fonctionner Nano Banana 2 avec un produit vertical différent ou un catalogue plus important, n'hésitez pas à nous faire part des résultats ! Nous serions ravis de les connaître.

Lire la suite

    Try Managed Milvus for Free

    Zilliz Cloud is hassle-free, powered by Milvus and 10x faster.

    Get Started

    Like the article? Spread the word

    Continuer à Lire