Milvus
Zilliz
  • Home
  • Blog
  • 使用 Nano Banana 2 + Milvus + Qwen 3.5 为电子商务建立从畅销书到图片的管道

使用 Nano Banana 2 + Milvus + Qwen 3.5 为电子商务建立从畅销书到图片的管道

  • Tutorials
March 03, 2026
Lumina Wang

如果你为电子商务卖家构建人工智能工具,你可能已经听过无数次这样的请求:"我有一个新产品。给我一张看起来像畅销书的宣传图片。不需要摄影师,不需要工作室,而且要便宜。

这就是问题所在。卖家有平铺的照片,也有已经转化的畅销书目录。他们希望用人工智能在两者之间架起一座桥梁,既快又有规模。

当谷歌在 2026 年 2 月 26 日发布 Nano Banana 2(双子座 3.1 闪存图像)时,我们当天就对其进行了测试,并将其集成到我们现有的基于 Milvus 的检索管道中。结果是:图像生成总成本下降到以前的大约三分之一,吞吐量翻了一番。每个图像的降价(比 Nano Banana Pro 便宜约 50%)是其中的一部分原因,但更大的节省来自于完全消除了返工周期。

本文介绍了 Nano Banana 2 在电子商务方面的优势和不足,然后介绍了完整流水线的实践教程:Milvus混合搜索用于查找视觉相似的畅销书,Qwen3.5 用于风格分析,Nano Banana 2用于最终生成。

Nano Banana 2 有哪些新功能?

Nano Banana 2(双子座 3.1 闪存图像)于 2026 年 2 月 26 日推出。它将 Nano Banana Pro 的大部分功能带到了闪存架构中,这意味着生成速度更快,价格更低。以下是主要升级:

  • 闪存速度下的专业级质量。Nano Banana 2 可提供世界一流的知识、推理和视觉保真度,这在以前只有专业版才能实现,但却具有 Flash 的延迟和吞吐量。
  • 512px 至 4K 输出。四级分辨率(512px、1K、2K、4K),支持原生分辨率。512px 分辨率是 Nano Banana 2 独有的新功能。
  • 14 种宽高比。在现有比例(1:1、2:3、3:2、3:4、4:3、4:5、5:4、9:16、16:9、21:9)基础上增加了 4:1、1:4、8:1 和 1:8。
  • 多达 14 个参考图像。在一个工作流程中,最多可保持 5 个字符的相似性和 14 个对象的保真度。
  • 改进文字渲染。可生成多种语言的清晰、准确的图像内文本,并在一次生成中支持翻译和本地化。
  • 图像搜索基础。从实时网络数据和谷歌搜索中提取图像,生成更准确的真实世界主题描述。
  • ~每张图片的成本降低约 50%。分辨率为 1K:0.067versusPro′s0.067,而 Pro's .134。

Nano Banano 2 的有趣应用案例:根据简单的谷歌地图截图生成位置感知全景图

给定一张谷歌地图截图和一个样式提示,该模型就能识别地理上下文,并生成一个保留正确空间关系的全景图。这对于制作以地区为目标的广告创意(巴黎咖啡馆背景、东京街景)非常有用,而无需寻找图片库。

有关完整的功能集,请参阅谷歌的公告博客开发者文档

Nano Banana 更新对电子商务意味着什么?

电子商务是图像最密集的行业之一。产品列表、市场广告、社交创意、横幅广告、本地化店面:每个渠道都需要源源不断的视觉资产,每个资产都有自己的规格。

电子商务中人工智能图像生成的核心要求可归结为以下几点:

  • 保持低成本--每张图片的成本必须在目录规模内有效。
  • 与经过验证的畅销书外观相匹配--新图片应与已实现转化的列表的视觉风格保持一致。
  • 避免侵权--不得复制竞争对手的创意或重复使用受保护的资产。

除此之外,跨境卖家还需要

  • 多平台格式支持--为市场、广告和店面提供不同的长宽比和规格。
  • 多语言文本渲染--干净、准确的多语言图像内文本。

Nano Banana 2 几乎满足了所有要求。下文将详细介绍每项升级的实际意义:直接解决电子商务痛点、不足之处以及实际成本影响。

减少高达 60% 的输出生成成本

在 1K 分辨率下,Nano Banana 2的每张图片成本为0.067perimageversusPros0.而 Pro 的 .134,直接降低了 50%。不过,每张图片的价格只是问题的一半。曾经扼杀用户预算的是返工。每个市场都有自己的图片规格(亚马逊为 1:1,Shopify 店面为 3:4,横幅广告为超宽),而生产每种变体都意味着单独的生成过程,有自己的失败模式。

Nano Banana 2 将这些额外的生成过程合并为一个过程。

  • 四种原始分辨率级别。

  • 512px (0.045 美元)

  • 1K ($0.067)

  • 2K ($0.101)

  • 4K ($0.151).

512px 分辨率是 Nano Banana 2 独有的新功能。用户现在可以生成低成本的 512px 草稿进行迭代,并以 2K 或 4K 输出最终资产,而无需单独的升频步骤。

  • 总共支持 14 种宽高比。下面是一些示例:

  • 4:1

  • 1:4

  • 8:1

  • 1:8

这些新的超宽和超高比例加入了现有的比例。一代会话可生成各种格式,如亚马逊主图(1:1)、店面英雄(3:4)和横幅广告(超宽或其他比例。)

这 4 种比例无需裁剪、无需填充、无需重新提示。其余 10 种宽高比包含在全套中,使整个过程在不同平台上更加灵活。

单是每幅图像节省约 50%的费用,就只需支付一半的费用。由于消除了跨分辨率和宽高比的返工,总成本降低了约三分之一。

以畅销书风格支持多达 14 张参考图片

在 Nano Banana 2 的所有更新中,多参考混合对我们的 Milvus 流程影响最大。Nano Banana 2 在单个请求中最多可接受 14 张参考图片,并保持

  • 多达5 个字符的字符相似性
  • 多达14 个对象的对象保真度

在实践中,我们从 Milvus 中检索了多张畅销图片,将它们作为参考传入,生成的图片继承了它们的场景构图、灯光、姿势和道具摆放。不需要任何提示工程来手工重建这些模式。

以前的模型只支持一到两个参考,这迫使用户只能选择单一的畅销书进行模仿。有了 14 个参考位置,我们就可以融合多个绩优榜单的特征,让模型合成一种综合风格。正是这种能力使得下面教程中基于检索的管道成为可能。

无需传统制作成本或物流,即可生成优质、商业化的可视图像

为了生成一致、可靠的图像,应避免将所有需求都集中到一个提示中。更可靠的方法是分阶段进行:首先生成背景,然后分别生成模型,最后将它们合成在一起。

我们用相同的提示测试了所有三个 Nano Banana 模型的背景生成:透过窗户看到的 4:1 超宽雨天上海天际线,东方明珠塔清晰可见。这一提示对构图、架构细节和逼真度进行了一次压力测试。

原始 Nano Banana vs. Nano Banana Pro vs. Nano Banana 2

  • 原始纳米香蕉。自然的雨水纹理,水滴分布真实可信,但建筑细节过度平滑。东方明珠塔几乎无法辨认,分辨率也达不到制作要求。
  • Nano Banana Pro。电影氛围:温暖的室内灯光与冰冷的雨水相映成趣,令人信服。不过,它完全省略了窗框,使画面的层次感变得平淡。可作为辅助画面,而非主角。
  • 纳米香蕉 2。渲染了整个场景。前景的窗框产生了层次感。东方明珠塔细节清晰。黄浦江上出现了船只。分层照明将室内的温暖与室外的阴霾区分开来。雨水和水渍的纹理接近摄影效果,4:1 的超宽比例保持了正确的透视,只有左侧窗户边缘有轻微失真。

对于产品摄影中的大多数背景生成任务,我们发现 Nano Banana 2 的输出无需后期处理即可使用。

跨语言清晰渲染图像内文字

在电子商务图片中,价格标签、促销横幅和多语言文本是不可避免的,而它们历来是人工智能生成的突破点。Nano Banana 2 能更好地处理这些问题,支持多语言图像内文本渲染,并能在一次生成中完成翻译和本地化。

标准文本渲染。在我们的测试中,我们尝试的每种电子商务格式的文本输出都没有错误:价格标签、简短的营销标语和双语产品说明。

手写续写。由于电子商务通常需要价格标签和个性化卡片等手写元素,我们测试了模型是否能匹配现有的手写风格并进行扩展--具体来说,匹配手写待办事项列表并以相同的风格添加 5 个新项目。三种模型的结果:

  • 原始纳米香蕉。重复序列号,结构错误。
  • Nano Banana Pro。布局正确,但字体风格再现不佳。
  • Nano Banana 2。零错误。笔画重量和字形风格与源字体非常接近,无法区分。

不过,谷歌自己的文档指出,Nano Banana 2 "在准确拼写和图像细节方面仍有问题"。我们测试的所有格式的结果都很干净,但任何生产工作流程都应包括发布前的文本验证步骤。

分步教程:使用 Milvus、Qwen 3.5 和 Nano Banana 2 构建从畅销书到图片的流程

开始之前架构和模型设置

为了避免单个提示生成的随机性,我们将整个过程分为三个可控阶段:用Milvus混合搜索检索已经生效的内容,用Qwen 3.5 分析其生效的原因,然后用Nano Banana 2 生成带有这些约束条件的最终图像。

如果你以前没有使用过这些工具,请快速了解一下它们:

  • Milvus最广泛采用的开源向量数据库。它将产品目录存储为向量,并运行混合搜索(密集+稀疏+标量过滤器)来查找与新产品最相似的畅销图片。
  • Qwen 3.5:一种流行的多模态 LLM。利用检索到的畅销图片,提取其背后的视觉模式(场景布局、光线、姿势、情绪),并将其转化为结构化的风格提示。
  • Nano Banana 2:来自谷歌的图像生成模型(Gemini 3.1 Flash Image)。接受三个输入:新产品平面布局、畅销书参考和 Qwen 3.5 的风格提示。输出最终的宣传照片。

这一架构背后的逻辑始于一个观察:任何电子商务目录中最有价值的视觉资产就是已经转换过的畅销书图片库。这些照片中的姿势、构图和光线都是通过实际的广告支出提炼出来的。直接检索这些模式要比通过提示文字进行逆向工程快一个数量级,而这一检索步骤正是向量数据库所要处理的。

以下是整个流程。我们通过 OpenRouter API 调用每个模型,因此不需要本地 GPU,也不需要下载模型权重。

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

我们依靠 Milvus 的三种能力来实现检索阶段:

  1. 密集+稀疏混合搜索。我们将图像 Embeddings 和文本 TF-IDF 向量作为并行查询运行,然后用 RRF(Reciprocal Rank Fusion)重排合并两个结果集。
  2. 标量字段过滤。在向量比较之前,我们会根据类别和销售额等元数据字段进行过滤,因此结果只包括相关的高绩效产品。
  3. 多字段 Schema。我们将密集向量、稀疏向量和标量元数据存储在一个 Milvus Collections 中,这样就能将整个检索逻辑保存在一个查询中,而不是分散在多个系统中。

数据准备

历史产品目录

我们从两个资产开始:一个是现有产品照片的图像/文件夹,另一个是包含元数据的 products.csv 文件。

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

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

新产品数据

对于我们要生成促销图片的产品,我们要准备一个平行结构:new_products/ 文件夹和 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

第 1 步:安装依赖项

!pip install pymilvus openai requests pillow scikit-learn tqdm

第 2 步:导入模块和配置

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

配置所有模型和路径:

# -- 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.”)

实用功能

这些辅助函数用于处理图像编码、API 调用和响应解析:

  • image_to_uri():将 PIL 图像转换为用于 API 传输的 base64 数据 URI。
  • get_image_embeddings():通过 OpenRouter Embedding API 将图像批量编码为 2048 维向量。
  • get_text_embedding():将文本编码到相同的 2048 维向量空间中。
  • sparse_too_dict():将 scipy 稀疏矩阵行转换为 Milvus 期望的稀疏向量的 {index: value} 格式。
  • extract_images()(提取图像从 Nano Banana 2 API 响应中提取生成的图像。
# -- 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.”)

步骤 3:加载产品目录

读取 products.csv 并加载相应的产品图片:

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))))

输出示例:

步骤 4:生成 Embeddings

混合搜索需要为每个产品生成两种向量。

4.1 密集向量:图像嵌入

nvidia/llama-nemotron-embed-vl-1b-v2 模型将每个产品图像编码为 2048 维的密集向量。由于该模型支持共享向量空间中的图像和文本输入,因此相同的嵌入可用于图像到图像和文本到图像检索。

# 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)")

输出:

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

4.2 个稀疏向量:TF-IDF 文本嵌入

使用 scikit-learn 的 TF-IDF 向量器将产品文本描述编码为稀疏向量。这些稀疏向量可以捕捉到密集向量可能忽略的关键字级匹配。

# 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")

输出:

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

为什么同时使用两种向量类型?密集向量和稀疏向量互为补充。密集向量捕捉视觉相似性:色调、服装轮廓、整体风格。稀疏向量捕捉关键词语义:"floral"、"midi "或 "雪纺 "等表示产品属性的术语。将这两种方法结合起来,检索质量明显优于单独使用其中一种方法。

步骤 5:使用混合 Schema 创建一个 Milvus Collections

这一步创建了一个单独的 Milvus Collections,将密集向量、稀疏向量和标量元数据字段存储在一起。这种统一的 Schema 可以在一次查询中实现混合搜索。

字段类型目的
密集矢量FLOAT_VECTOR (2048d)图像嵌入、COSINE 相似性
稀疏向量稀疏浮点型向量TF-IDF 稀疏向量,内积
类别VARCHAR用于筛选的类别标签
销售额INT64用于筛选的历史销售量
颜色、款式、季节VARCHAR附加元数据标签
价格浮动产品价格
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.")

插入产品数据:

# 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.")

输出:

Inserted 40 products into Milvus.

步骤 6:混合搜索以查找类似畅销书

这是核心检索步骤。对于每个新产品,管道会同时运行三个操作符:

  1. 密集搜索:查找具有视觉相似图像 Embeddings 的产品。
  2. 稀疏搜索:通过 TF-IDF 查找具有匹配文本关键词的产品。
  3. 标量过滤:将结果限制为相同类别且 sales_count > 1500 的产品。
  4. RRF 重新排序:使用互易等级融合(Reciprocal Rank Fusion)合并密集和稀疏结果列表。

加载新产品:

# 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))))

输出:

对新产品进行编码:

# 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}")

输出

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

执行混合搜索

这里的关键 API 调用

  • AnnSearchRequest 为密集向量场和稀疏向量场分别创建搜索请求。
  • expr=filter_expr 在每个搜索请求中应用标量过滤。
  • RRFRanker(k=60) 使用互易排名融合算法融合两个排名结果列表。
  • hybrid_search 同时执行两个请求,并返回合并后重新排名的结果。
# 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()

输出:按融合得分排序的前 3 个最相似的畅销书。

步骤 7:使用 Qwen 3.5 分析畅销书风格

我们将检索到的畅销书图片输入 Qwen 3.5,并要求它提取它们共同的视觉 DNA:场景构图、灯光设置、模型姿势和整体氛围。通过分析,我们得到了一个单一的生成提示,并将其交给 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)

输出示例:

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.

步骤 8:使用 Nano Banana 2 生成宣传图片

我们向 Nano Banana 2 传递三个输入:新产品的平铺照片、排名靠前的畅销书图片,以及我们在上一步中提取的风格提示。该模型将这些信息合成一张宣传照片,将新服装与成熟的视觉风格搭配起来。

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!”)

Nano Banana 2 API 调用的关键参数:

  • 模式[文本"、"图像"]:声明响应应包括图像。
  • image_config.aspect_ratio:控制输出的宽高比(3:4 适用于人像/时尚照片)。
  • image_config.image_size:设置分辨率。Nano Banana 2 支持 512px 至 4K。

提取生成的图像:

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())

输出:

步骤 9:并排比较

输出结果大致符合要求:光线柔和、均匀,模型的姿势看起来很自然,气氛也与畅销书的参考相吻合。

不足之处在于服装的融合。开衫看起来是贴在模型上的,而不是穿在身上的,领口的白色标签也渗了出来。单通道生成难以实现这种精细的服装与身体的融合,因此我们在总结中介绍了一些变通方法。

步骤 10:批量生成所有新产品

我们将整个流程打包成一个函数,并在其余新产品中运行。为简洁起见,这里省略了批处理代码;如果您需要完整的实现,请联系我们。

批处理结果中有两点非常突出。我们从Qwen 3.5中获得的风格提示会根据产品进行有意义的调整:夏季连衣裙和冬季针织衫会根据季节、使用案例和配饰获得真正不同的场景描述。而我们从Nano Banana 2 中获得的图片,在光线、质感和构图方面都能与真正的摄影棚摄影相媲美。

总结

在本文中,我们介绍了 Nano Banana 2 为电子商务图像生成带来的好处,将其与原始 Nano Banana 和 Pro 在实际生产任务中进行了比较,并介绍了如何使用 milvus、Qwen 3.5 和 Nano Banana 2 构建畅销书到图像的流程。

这一流程具有四个实际优势:

  • 成本可控,预算可预测。嵌入模型(Llama Nemotron Embed VL 1B v2)在 OpenRouter 上是免费的。Nano Banana 2 的单张图像成本约为 Pro 的一半,而且本机多格式输出消除了返工周期,而返工周期曾使有效账单增加两倍或三倍。对于每季管理数千个 SKU 的电子商务团队来说,这种可预测性意味着图像制作将与目录同步进行,而不是超出预算。
  • 端到端自动化,更快上市。从平铺产品照片到成品宣传图片的流程无需人工干预。新产品从仓库照片到上市图片只需几分钟,而不是几天,这在目录周转率最高的旺季最为重要。
  • 无需本地 GPU,降低了进入门槛。每个模型都通过 OpenRouter API 运行。一个没有 ML 基础设施、也没有专职工程技术人员的团队,只需一台笔记本电脑就能运行这一管道。无需配置,无需维护,也无需前期硬件投资。
  • 检索精度更高,品牌一致性更强。Milvus 在单个查询中结合了密集、稀疏和标量过滤,在产品匹配方面始终优于单向量方法。在实践中,这意味着生成的图像能更可靠地继承品牌既定的视觉语言:照明、构图和造型,这些都是现有畅销产品已经证明的转换方式。生成的图片看起来就像属于你的商店,而不是普通的人工智能图片库。

此外,还有一些限制因素值得直言不讳:

  • 服装与人体的混合。单通道生成可能会使服装看起来是合成的,而不是穿着的。小配饰等细节有时会模糊。解决方法:分阶段生成(先生成背景,再生成模型姿势,最后合成)。这种多通道方法可以缩小每个步骤的范围,并显著提高混合质量。
  • 边缘情况下的细节保真度。配件、图案和文字较多的布局可能会失去清晰度。解决方法:在生成提示中添加明确的限制条件("服装与人体自然贴合,无外露标签,无多余元素,产品细节清晰")。如果特定产品的质量仍然不佳,可改用 Nano Banana Pro 进行最终处理。

Milvus是为混合搜索步骤提供动力的开源向量数据库,如果你想四处打探或尝试换入自己的产品照片,快速入门大约需要十分钟。我们在Discord和 Slack 上有一个非常活跃的社区,我们很乐意看到大家用它创建什么。如果你最终针对不同的垂直产品或更大的目录运行了 Nano Banana 2,请分享结果!我们很乐意听听。

继续阅读

    Try Managed Milvus for Free

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

    Get Started

    Like the article? Spread the word

    扩展阅读