feat(blocks): Add Nano Banana 2 to image generator, customizer, and editor blocks (#12218)

Requested by @Torantulino

Add `google/nano-banana-2` (Gemini 3.1 Flash Image) support across all
three image blocks.

### Changes

**`ai_image_customizer.py`**
- Add `NANO_BANANA_2 = "google/nano-banana-2"` to `GeminiImageModel`
enum
- Update block description to reference Nano-Banana models generically

**`ai_image_generator_block.py`**
- Add `NANO_BANANA_2` to `ImageGenModel` enum
- Add generation branch (identical to NBP except model name)

**`flux_kontext.py` (AI Image Editor)**
- Rename `FluxKontextModelName` → `ImageEditorModel` (with
backwards-compatible alias)
- Add `NANO_BANANA_PRO` and `NANO_BANANA_2` to the editor
- Model-aware branching in `run_model()`: NB models use `image_input`
list (not `input_image`), no `seed`, and add `output_format`

**`block_cost_config.py`**
- Add NB2 cost entries for all three blocks (14 credits, matching NBP)
- Add NB Pro cost entry for editor block
- Update editor block refs from `.PRO`/`.MAX` to
`.FLUX_KONTEXT_PRO`/`.FLUX_KONTEXT_MAX`

Resolves SECRT-2047

---------

Co-authored-by: Torantulino <Torantulino@users.noreply.github.com>
Co-authored-by: Abhimanyu Yadav <122007096+Abhi1992002@users.noreply.github.com>
This commit is contained in:
Otto
2026-03-18 09:42:18 +00:00
committed by GitHub
parent 4d00e0f179
commit e657472162
6 changed files with 129 additions and 43 deletions

View File

@@ -27,6 +27,7 @@ from backend.util.file import MediaFileType, store_media_file
class GeminiImageModel(str, Enum):
NANO_BANANA = "google/nano-banana"
NANO_BANANA_PRO = "google/nano-banana-pro"
NANO_BANANA_2 = "google/nano-banana-2"
class AspectRatio(str, Enum):
@@ -77,7 +78,7 @@ class AIImageCustomizerBlock(Block):
)
model: GeminiImageModel = SchemaField(
description="The AI model to use for image generation and editing",
default=GeminiImageModel.NANO_BANANA,
default=GeminiImageModel.NANO_BANANA_2,
title="Model",
)
images: list[MediaFileType] = SchemaField(
@@ -103,7 +104,7 @@ class AIImageCustomizerBlock(Block):
super().__init__(
id="d76bbe4c-930e-4894-8469-b66775511f71",
description=(
"Generate and edit custom images using Google's Nano-Banana model from Gemini 2.5. "
"Generate and edit custom images using Google's Nano-Banana models from Gemini. "
"Provide a prompt and optional reference images to create or modify images."
),
categories={BlockCategory.AI, BlockCategory.MULTIMEDIA},
@@ -111,7 +112,7 @@ class AIImageCustomizerBlock(Block):
output_schema=AIImageCustomizerBlock.Output,
test_input={
"prompt": "Make the scene more vibrant and colorful",
"model": GeminiImageModel.NANO_BANANA,
"model": GeminiImageModel.NANO_BANANA_2,
"images": [],
"aspect_ratio": AspectRatio.MATCH_INPUT_IMAGE,
"output_format": OutputFormat.JPG,

View File

@@ -115,6 +115,7 @@ class ImageGenModel(str, Enum):
RECRAFT = "Recraft v3"
SD3_5 = "Stable Diffusion 3.5 Medium"
NANO_BANANA_PRO = "Nano Banana Pro"
NANO_BANANA_2 = "Nano Banana 2"
class AIImageGeneratorBlock(Block):
@@ -131,7 +132,7 @@ class AIImageGeneratorBlock(Block):
)
model: ImageGenModel = SchemaField(
description="The AI model to use for image generation",
default=ImageGenModel.SD3_5,
default=ImageGenModel.NANO_BANANA_2,
title="Model",
)
size: ImageSize = SchemaField(
@@ -165,7 +166,7 @@ class AIImageGeneratorBlock(Block):
test_input={
"credentials": TEST_CREDENTIALS_INPUT,
"prompt": "An octopus using a laptop in a snowy forest with 'AutoGPT' clearly visible on the screen",
"model": ImageGenModel.RECRAFT,
"model": ImageGenModel.NANO_BANANA_2,
"size": ImageSize.SQUARE,
"style": ImageStyle.REALISTIC,
},
@@ -179,7 +180,9 @@ class AIImageGeneratorBlock(Block):
],
test_mock={
# Return a data URI directly so store_media_file doesn't need to download
"_run_client": lambda *args, **kwargs: "data:image/webp;base64,UklGRiQAAABXRUJQVlA4IBgAAAAwAQCdASoBAAEAAQAcJYgCdAEO"
"_run_client": lambda *args, **kwargs: (
"data:image/webp;base64,UklGRiQAAABXRUJQVlA4IBgAAAAwAQCdASoBAAEAAQAcJYgCdAEO"
)
},
)
@@ -280,17 +283,24 @@ class AIImageGeneratorBlock(Block):
)
return output
elif input_data.model == ImageGenModel.NANO_BANANA_PRO:
# Use Nano Banana Pro (Google Gemini 3 Pro Image)
elif input_data.model in (
ImageGenModel.NANO_BANANA_PRO,
ImageGenModel.NANO_BANANA_2,
):
# Use Nano Banana models (Google Gemini image variants)
model_map = {
ImageGenModel.NANO_BANANA_PRO: "google/nano-banana-pro",
ImageGenModel.NANO_BANANA_2: "google/nano-banana-2",
}
input_params = {
"prompt": modified_prompt,
"aspect_ratio": SIZE_TO_NANO_BANANA_RATIO[input_data.size],
"resolution": "2K", # Default to 2K for good quality/cost balance
"resolution": "2K",
"output_format": "jpg",
"safety_filter_level": "block_only_high", # Most permissive
"safety_filter_level": "block_only_high",
}
output = await self._run_client(
credentials, "google/nano-banana-pro", input_params
credentials, model_map[input_data.model], input_params
)
return output

View File

@@ -34,17 +34,29 @@ TEST_CREDENTIALS_INPUT = {
"provider": TEST_CREDENTIALS.provider,
"id": TEST_CREDENTIALS.id,
"type": TEST_CREDENTIALS.type,
"title": TEST_CREDENTIALS.type,
"title": TEST_CREDENTIALS.title,
}
class FluxKontextModelName(str, Enum):
PRO = "Flux Kontext Pro"
MAX = "Flux Kontext Max"
class ImageEditorModel(str, Enum):
FLUX_KONTEXT_PRO = "Flux Kontext Pro"
FLUX_KONTEXT_MAX = "Flux Kontext Max"
NANO_BANANA_PRO = "Nano Banana Pro"
NANO_BANANA_2 = "Nano Banana 2"
@property
def api_name(self) -> str:
return f"black-forest-labs/flux-kontext-{self.name.lower()}"
_map = {
"FLUX_KONTEXT_PRO": "black-forest-labs/flux-kontext-pro",
"FLUX_KONTEXT_MAX": "black-forest-labs/flux-kontext-max",
"NANO_BANANA_PRO": "google/nano-banana-pro",
"NANO_BANANA_2": "google/nano-banana-2",
}
return _map[self.name]
# Keep old name as alias for backwards compatibility
FluxKontextModelName = ImageEditorModel
class AspectRatio(str, Enum):
@@ -69,7 +81,7 @@ class AIImageEditorBlock(Block):
credentials: CredentialsMetaInput[
Literal[ProviderName.REPLICATE], Literal["api_key"]
] = CredentialsField(
description="Replicate API key with permissions for Flux Kontext models",
description="Replicate API key with permissions for Flux Kontext and Nano Banana models",
)
prompt: str = SchemaField(
description="Text instruction describing the desired edit",
@@ -87,14 +99,14 @@ class AIImageEditorBlock(Block):
advanced=False,
)
seed: Optional[int] = SchemaField(
description="Random seed. Set for reproducible generation",
description="Random seed. Set for reproducible generation (Flux Kontext only; ignored by Nano Banana models)",
default=None,
title="Seed",
advanced=True,
)
model: FluxKontextModelName = SchemaField(
model: ImageEditorModel = SchemaField(
description="Model variant to use",
default=FluxKontextModelName.PRO,
default=ImageEditorModel.NANO_BANANA_2,
title="Model",
)
@@ -107,7 +119,7 @@ class AIImageEditorBlock(Block):
super().__init__(
id="3fd9c73d-4370-4925-a1ff-1b86b99fabfa",
description=(
"Edit images using BlackForest Labs' Flux Kontext models. Provide a prompt "
"Edit images using Flux Kontext or Google Nano Banana models. Provide a prompt "
"and optional reference image to generate a modified image."
),
categories={BlockCategory.AI, BlockCategory.MULTIMEDIA},
@@ -118,7 +130,7 @@ class AIImageEditorBlock(Block):
"input_image": "data:image/png;base64,MQ==",
"aspect_ratio": AspectRatio.MATCH_INPUT_IMAGE,
"seed": None,
"model": FluxKontextModelName.PRO,
"model": ImageEditorModel.NANO_BANANA_2,
"credentials": TEST_CREDENTIALS_INPUT,
},
test_output=[
@@ -127,7 +139,9 @@ class AIImageEditorBlock(Block):
],
test_mock={
# Use data URI to avoid HTTP requests during tests
"run_model": lambda *args, **kwargs: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==",
"run_model": lambda *args, **kwargs: (
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg=="
),
},
test_credentials=TEST_CREDENTIALS,
)
@@ -142,7 +156,7 @@ class AIImageEditorBlock(Block):
) -> BlockOutput:
result = await self.run_model(
api_key=credentials.api_key,
model_name=input_data.model.api_name,
model=input_data.model,
prompt=input_data.prompt,
input_image_b64=(
await store_media_file(
@@ -169,7 +183,7 @@ class AIImageEditorBlock(Block):
async def run_model(
self,
api_key: SecretStr,
model_name: str,
model: ImageEditorModel,
prompt: str,
input_image_b64: Optional[str],
aspect_ratio: str,
@@ -178,12 +192,29 @@ class AIImageEditorBlock(Block):
graph_exec_id: str,
) -> MediaFileType:
client = ReplicateClient(api_token=api_key.get_secret_value())
input_params = {
"prompt": prompt,
"input_image": input_image_b64,
"aspect_ratio": aspect_ratio,
**({"seed": seed} if seed is not None else {}),
}
model_name = model.api_name
is_nano_banana = model in (
ImageEditorModel.NANO_BANANA_PRO,
ImageEditorModel.NANO_BANANA_2,
)
if is_nano_banana:
input_params: dict = {
"prompt": prompt,
"aspect_ratio": aspect_ratio,
"output_format": "jpg",
"safety_filter_level": "block_only_high",
}
# NB API expects "image_input" as a list, unlike Flux's single "input_image"
if input_image_b64:
input_params["image_input"] = [input_image_b64]
else:
input_params = {
"prompt": prompt,
"input_image": input_image_b64,
"aspect_ratio": aspect_ratio,
**({"seed": seed} if seed is not None else {}),
}
try:
output: FileOutput | list[FileOutput] = await client.async_run( # type: ignore

View File

@@ -423,7 +423,7 @@ BLOCK_COSTS: dict[Type[Block], list[BlockCost]] = {
BlockCost(
cost_amount=10,
cost_filter={
"model": FluxKontextModelName.PRO.api_name,
"model": FluxKontextModelName.FLUX_KONTEXT_PRO,
"credentials": {
"id": replicate_credentials.id,
"provider": replicate_credentials.provider,
@@ -434,7 +434,29 @@ BLOCK_COSTS: dict[Type[Block], list[BlockCost]] = {
BlockCost(
cost_amount=20,
cost_filter={
"model": FluxKontextModelName.MAX.api_name,
"model": FluxKontextModelName.FLUX_KONTEXT_MAX,
"credentials": {
"id": replicate_credentials.id,
"provider": replicate_credentials.provider,
"type": replicate_credentials.type,
},
},
),
BlockCost(
cost_amount=14, # Nano Banana Pro
cost_filter={
"model": FluxKontextModelName.NANO_BANANA_PRO,
"credentials": {
"id": replicate_credentials.id,
"provider": replicate_credentials.provider,
"type": replicate_credentials.type,
},
},
),
BlockCost(
cost_amount=14, # Nano Banana 2
cost_filter={
"model": FluxKontextModelName.NANO_BANANA_2,
"credentials": {
"id": replicate_credentials.id,
"provider": replicate_credentials.provider,
@@ -632,6 +654,17 @@ BLOCK_COSTS: dict[Type[Block], list[BlockCost]] = {
},
},
),
BlockCost(
cost_amount=14, # Nano Banana 2: same pricing tier as Pro
cost_filter={
"model": ImageGenModel.NANO_BANANA_2,
"credentials": {
"id": replicate_credentials.id,
"provider": replicate_credentials.provider,
"type": replicate_credentials.type,
},
},
),
],
AIImageCustomizerBlock: [
BlockCost(
@@ -656,6 +689,17 @@ BLOCK_COSTS: dict[Type[Block], list[BlockCost]] = {
},
},
),
BlockCost(
cost_amount=14, # Nano Banana 2: same pricing tier as Pro
cost_filter={
"model": GeminiImageModel.NANO_BANANA_2,
"credentials": {
"id": replicate_credentials.id,
"provider": replicate_credentials.provider,
"type": replicate_credentials.type,
},
},
),
],
VideoNarrationBlock: [
BlockCost(

View File

@@ -209,8 +209,8 @@ Below is a comprehensive list of all available blocks, categorized by their prim
| [AI Ad Maker Video Creator](block-integrations/llm.md#ai-ad-maker-video-creator) | Creates an AIgenerated 30second advert (text + images) |
| [AI Condition](block-integrations/llm.md#ai-condition) | Uses AI to evaluate natural language conditions and provide conditional outputs |
| [AI Conversation](block-integrations/llm.md#ai-conversation) | A block that facilitates multi-turn conversations with a Large Language Model (LLM), maintaining context across message exchanges |
| [AI Image Customizer](block-integrations/llm.md#ai-image-customizer) | Generate and edit custom images using Google's Nano-Banana model from Gemini 2 |
| [AI Image Editor](block-integrations/llm.md#ai-image-editor) | Edit images using BlackForest Labs' Flux Kontext models |
| [AI Image Customizer](block-integrations/llm.md#ai-image-customizer) | Generate and edit custom images using Google's Nano-Banana models from Gemini |
| [AI Image Editor](block-integrations/llm.md#ai-image-editor) | Edit images using Flux Kontext or Google Nano Banana models |
| [AI Image Generator](block-integrations/llm.md#ai-image-generator) | Generate images using various AI models through a unified interface |
| [AI List Generator](block-integrations/llm.md#ai-list-generator) | A block that creates lists of items based on prompts using a Large Language Model (LLM), with optional source data for context |
| [AI Music Generator](block-integrations/llm.md#ai-music-generator) | This block generates music using Meta's MusicGen model on Replicate |

View File

@@ -125,7 +125,7 @@ Creating an interactive chatbot that can maintain context over multiple exchange
## AI Image Customizer
### What it is
Generate and edit custom images using Google's Nano-Banana model from Gemini 2.5. Provide a prompt and optional reference images to create or modify images.
Generate and edit custom images using Google's Nano-Banana models from Gemini. Provide a prompt and optional reference images to create or modify images.
### How it works
<!-- MANUAL: how_it_works -->
@@ -139,7 +139,7 @@ Configure aspect ratio to match your needs and choose between JPG or PNG output
| Input | Description | Type | Required |
|-------|-------------|------|----------|
| prompt | A text description of the image you want to generate | str | Yes |
| model | The AI model to use for image generation and editing | "google/nano-banana" \| "google/nano-banana-pro" | No |
| model | The AI model to use for image generation and editing | "google/nano-banana" \| "google/nano-banana-pro" \| "google/nano-banana-2" | No |
| images | Optional list of input images to reference or modify | List[str (file)] | No |
| aspect_ratio | Aspect ratio of the generated image | "match_input_image" \| "1:1" \| "2:3" \| "3:2" \| "3:4" \| "4:3" \| "4:5" \| "5:4" \| "9:16" \| "16:9" \| "21:9" | No |
| output_format | Format of the output image | "jpg" \| "png" | No |
@@ -165,13 +165,13 @@ Configure aspect ratio to match your needs and choose between JPG or PNG output
## AI Image Editor
### What it is
Edit images using BlackForest Labs' Flux Kontext models. Provide a prompt and optional reference image to generate a modified image.
Edit images using Flux Kontext or Google Nano Banana models. Provide a prompt and optional reference image to generate a modified image.
### How it works
<!-- MANUAL: how_it_works -->
This block uses BlackForest Labs' Flux Kontext models for context-aware image editing. Describe the desired edit in the prompt, and optionally provide an input image to modify.
This block uses BlackForest Labs' Flux Kontext or Google's Nano Banana models for context-aware image editing. Describe the desired edit in the prompt, and optionally provide an input image to modify.
Choose between Flux Kontext Pro or Max for different quality/speed tradeoffs. Set a seed for reproducible results across multiple runs.
Choose between Flux Kontext Pro, Max, or Nano Banana models for different quality/speed tradeoffs. Set a seed for reproducible results across multiple runs (Flux Kontext only).
<!-- END MANUAL -->
### Inputs
@@ -181,8 +181,8 @@ Choose between Flux Kontext Pro or Max for different quality/speed tradeoffs. Se
| prompt | Text instruction describing the desired edit | str | Yes |
| input_image | Reference image URI (jpeg, png, gif, webp) | str (file) | No |
| aspect_ratio | Aspect ratio of the generated image | "match_input_image" \| "1:1" \| "16:9" \| "9:16" \| "4:3" \| "3:4" \| "3:2" \| "2:3" \| "4:5" \| "5:4" \| "21:9" \| "9:21" \| "2:1" \| "1:2" | No |
| seed | Random seed. Set for reproducible generation | int | No |
| model | Model variant to use | "Flux Kontext Pro" \| "Flux Kontext Max" | No |
| seed | Random seed. Set for reproducible generation (Flux Kontext only; ignored by Nano Banana models) | int | No |
| model | Model variant to use | "Flux Kontext Pro" \| "Flux Kontext Max" \| "Nano Banana Pro" \| "Nano Banana 2" | No |
### Outputs
@@ -219,7 +219,7 @@ The unified interface allows switching between models without changing your work
| Input | Description | Type | Required |
|-------|-------------|------|----------|
| prompt | Text prompt for image generation | str | Yes |
| model | The AI model to use for image generation | "Flux 1.1 Pro" \| "Flux 1.1 Pro Ultra" \| "Recraft v3" \| "Stable Diffusion 3.5 Medium" \| "Nano Banana Pro" | No |
| model | The AI model to use for image generation | "Flux 1.1 Pro" \| "Flux 1.1 Pro Ultra" \| "Recraft v3" \| "Stable Diffusion 3.5 Medium" \| "Nano Banana Pro" \| "Nano Banana 2" | No |
| size | Format of the generated image: - Square: Perfect for profile pictures, icons - Landscape: Traditional photo format - Portrait: Vertical photos, portraits - Wide: Cinematic format, desktop wallpapers - Tall: Mobile wallpapers, social media stories | "square" \| "landscape" \| "portrait" \| "wide" \| "tall" | No |
| style | Visual style for the generated image | "any" \| "realistic_image" \| "realistic_image/b_and_w" \| "realistic_image/hdr" \| "realistic_image/natural_light" \| "realistic_image/studio_portrait" \| "realistic_image/enterprise" \| "realistic_image/hard_flash" \| "realistic_image/motion_blur" \| "digital_illustration" \| "digital_illustration/pixel_art" \| "digital_illustration/hand_drawn" \| "digital_illustration/grain" \| "digital_illustration/infantile_sketch" \| "digital_illustration/2d_art_poster" \| "digital_illustration/2d_art_poster_2" \| "digital_illustration/handmade_3d" \| "digital_illustration/hand_drawn_outline" \| "digital_illustration/engraving_color" | No |