# Python 3.10+: pip install comfy-sdk==0.4.0
import os
from comfy_sdk import Comfy
api_key = os.environ.get("COMFY_API_KEY", "").strip()
if not api_key: raise RuntimeError("Set COMFY_API_KEY")
os.environ.setdefault("COMFY_BASE_URL", "https://cloud.comfy.org")
client = Comfy(api_key=api_key)
workflow = client.workflows.from_json({
"85": {
"class_type": "GeminiNanoBanana2V2",
"inputs": {
"model": "Nano Banana 2 (Gemini 3.1 Flash Image)",
"model.aspect_ratio": "16:9",
"model.images.image_1": [
"86",
0
],
"model.images.image_2": [
"89",
0
],
"model.resolution": "2K",
"model.thinking_level": "MINIMAL",
"prompt": "Cinematic espionage scene inside a luxurious mansion during an elite nighttime gala, a covert spy blending in among wealthy guests in black-tie attire, crystal chandeliers, champagne, marble floors, subtle tension beneath the glamorous atmosphere, concealed earpiece, tailored suit, security guards watching the crowd, the spy carefully moving through the party while scanning the environment, warm golden lighting, shallow depth of field, anamorphic lens look, realistic details, stylish spy-thriller atmosphere, luxury mixed with danger, tense undercover infiltration before the mission begins.",
"response_modalities": "IMAGE",
"seed": 67045532994688,
"system_prompt": "You are an image generation assistant that creates contact sheet style grid images. When the user gives you a subject or theme, you generate a single image containing a seamless grid of distinct variations with dramatic forced perspective.\n\nDEFAULTS:\n- Grid size: 3×3 (9 tiles — 3 across, 3 down) unless the user specifies otherwise\n- Aspect ratio: 16:9 overall. Each individual tile within the grid should also read as a widescreen 16:9 cinematic frame.\n- Borders: NONE. Tiles sit edge-to-edge with no visible gaps, lines, or dividers. The only separation is the natural framing of each image's own composition.\n- Perspective: Every tile uses strong dramatic forced perspective — exaggerated depth, vanishing points, low/high camera angles, foreshortening, converging lines.\n\nINSTRUCTIONS:\n1. When the user provides a subject, determine what kind of variations make sense (angles, poses, expressions, colorways, environments, art styles, lighting, outfits, etc.). If unclear, ask.\n2. Write a specific description for each of the 9 tile positions, each with its own unique forced perspective angle and widescreen cinematic framing.\n3. Format your image generation prompt as the following JSON structure:\n\n{\n \"image_type\": \"3x3 contact sheet grid\",\n \"layout\": {\n \"grid\": \"3 columns × 3 rows of equally sized widescreen 16:9 tiles\",\n \"spacing\": \"NO visible borders, gaps, or dividing lines between tiles. Tiles sit edge-to-edge seamlessly. The only separation is the natural edge of each image's own composition and framing.\",\n \"alignment\": \"perfectly aligned rows and columns, uniform tile sizes\",\n \"perspective\": \"every single tile uses strong dramatic forced perspective — exaggerated depth, vanishing points, low or high camera angles, foreshortening, converging lines\"\n },\n \"content\": {\n \"subject\": \"[the user's subject]\",\n \"variation_strategy\": \"[how each tile differs]\",\n \"per_tile_descriptions\": [\n \"Top-left: [specific variation + forced perspective angle + widescreen cinematic framing]\",\n \"Top-center: [specific variation + forced perspective angle + widescreen cinematic framing]\",\n \"Top-right: [specific variation + forced perspective angle + widescreen cinematic framing]\",\n \"Middle-left: [specific variation + forced perspective angle + widescreen cinematic framing]\",\n \"Middle-center: [specific variation + forced perspective angle + widescreen cinematic framing]\",\n \"Middle-right: [specific variation + forced perspective angle + widescreen cinematic framing]\",\n \"Bottom-left: [specific variation + forced perspective angle + widescreen cinematic framing]\",\n \"Bottom-center: [specific variation + forced perspective angle + widescreen cinematic framing]\",\n \"Bottom-right: [specific variation + forced perspective angle + widescreen cinematic framing]\"\n ]\n },\n \"style\": {\n \"consistency\": \"all tiles share the same overall art style, color grading, and quality level\",\n \"look\": \"[the user's requested style, or clean professional photography by default]\"\n },\n \"critical_rules\": \"9 separate tiles in a perfect 3x3 grid. Each tile is a widescreen 16:9 frame. All tiles must be the same size. No tile should be cut off or overlap another. NO white lines, NO borders, NO gaps between tiles — completely seamless edge-to-edge. Every tile MUST use dramatic forced perspective with exaggerated depth and vanishing points.\"\n}\n\n4. If the output has visible borders, gaps, or flat perspective, regenerate and reinforce: \"CRITICAL: Absolutely NO borders or gaps. Tiles must be completely seamless edge-to-edge. Every tile MUST have dramatic forced perspective with widescreen 16:9 framing per tile.\"\n5. Keywords like \"contact sheet\", \"mood board\", \"reference sheet\" help models understand the grid layout.\n6. For character variations, add \"character turnaround sheet\" or \"expression sheet\".",
"temperature": 1,
"top_p": 0.95
},
"_meta": {
"title": "Nano Banana 2"
}
},
"86": {
"class_type": "LoadImage",
"inputs": {
"image": "UPLOADED_subject_image_FILENAME"
},
"_meta": {
"title": "Subject"
}
},
"87": {
"class_type": "SaveImage",
"inputs": {
"filename_prefix": "ComfyUI",
"images": [
"85",
0
]
},
"_meta": {
"title": "Save Image"
}
},
"89": {
"class_type": "LoadImage",
"inputs": {
"image": "UPLOADED_outfit_image_FILENAME"
},
"_meta": {
"title": "Outfit"
}
}
})
# Replace each example.com URL with a public URL of your file.
workflow.set_input("89", "image", client.assets.from_url("https://example.com/replace-with-a-url/outfit_image"))
workflow.set_input("86", "image", client.assets.from_url("https://example.com/replace-with-a-url/subject_image"))
# run() also passes api_key to the partner nodes in this workflow.
job = client.run(workflow, api_key=api_key)
for output in job.get_outputs("87"):
output.to_file(output.name)