promptdojo_promptdojo_promptdojo_promptdojo_promptdojo_promptdojo_promptdojo_promptdojo_promptdojo_promptdojo_promptdojo_

The image model landscape — six families and what each is for — step 7 of 9

The decision tree, ordered

You've now seen the six families and the specialty cases. Here's the routing logic in one place — the order matters.

The rules, in priority order

The reason for priority order: specialty constraints beat general preferences. If the brief needs text-in-image, it doesn't matter that you prefer Midjourney — Ideogram is the answer. So the routing function checks the hard constraints first, then the cheap floor, then style preferences, then defaults.

The order:

  1. Vector required?recraftv4_1. Recraft V4.1. Raster $0.035, vector $0.08.
  2. Text in image required?ideogram-v4. Ideogram 4.0. POST /v1/ideogram-v4/generate.
  3. Cheap floor (budget_per_image < 0.04) → gemini-3.1-flash-lite-image. $0.0336 at 1K. gpt-image-2 quality=low ($0.006) and flux-2-klein (from $0.014) sit on the same floor.
  4. Instruction-heavy composition (specific placements)?gpt-image-2. Google premium is gemini-3-pro-image ($0.134 at 1K/2K, $0.24 at 4K).
  5. Photoreal + art-directed?flux-2-pro. Strongest photoreal output with prompt fidelity.
  6. Default fallthroughgemini-3.1-flash-image. Nano Banana 2. High batch count stays on this family (or Lite if you already hit the cheap floor). The call is generate_content with IMAGE, one image per call.

Midjourney stays a UI pick (V8.x, no public API).

The escape hatches

A few real-world overrides that the priority list above doesn't capture:

  • latency_ms_max < 2000 — drop to a Flash-tier model. nano banana 2 generates faster than Pro. Lite is the other "speed first" pick.
  • self_hosted = True AND volume > 50K/month — run FLUX.2 on a rented GPU. The economics flip when you amortize the hardware.

How this becomes code

The pseudocode for the routing function:

def pick_image_model(task):
    if task.get("needs_vector"):
        return "recraftv4_1"
    if task.get("needs_text_in_image"):
        return "ideogram-v4"
    if task.get("budget_per_image", 1.0) < 0.04:
        return "gemini-3.1-flash-lite-image"
    if task.get("instruction_heavy"):
        return "gpt-image-2"
    if task.get("google_premium"):
        return "gemini-3-pro-image"
    if task.get("photoreal_art_directed"):
        return "flux-2-pro"
    return "gemini-3.1-flash-image"

That's the spec for the write step. Notice the order: specialty constraints (vector, text), then the budget floor, then style preferences (instruction, Google premium, photoreal), then default. High batch_count is not its own branch — it falls through to nano banana 2.

A note on the "default" pick

gemini-3.1-flash-image as the fallthrough is a 2026-specific call. A year ago it would have been Imagen 4. Two years ago it would have been DALL-E 3. The default rotates as the landscape shifts. The shape of the function — specialty → preference → default — doesn't.

If you're writing this for a long-lived production codebase, leave the rule list as a module-level constant (like the RULES tuple in the editor above) so a future maintainer can re-rank without rewriting the function body. That's the same pattern from chapter 26 — keep the registry out of the loop.

What you'll write

Step 8 asks you to implement pick_image_model(task) with the rules above. Step 9 runs it across five tasks and audits a pipeline. Both are pure stdlib, no API calls — just decision logic, exactly like the kind of routing function that sits at the front of a real image-gen feature in production.

The decision tree, ordered

You've now seen the six families and the specialty cases. Here's the routing logic in one place — the order matters.

The rules, in priority order

The reason for priority order: specialty constraints beat general preferences. If the brief needs text-in-image, it doesn't matter that you prefer Midjourney — Ideogram is the answer. So the routing function checks the hard constraints first, then the cheap floor, then style preferences, then defaults.

The order:

  1. Vector required?recraftv4_1. Recraft V4.1. Raster $0.035, vector $0.08.
  2. Text in image required?ideogram-v4. Ideogram 4.0. POST /v1/ideogram-v4/generate.
  3. Cheap floor (budget_per_image < 0.04) → gemini-3.1-flash-lite-image. $0.0336 at 1K. gpt-image-2 quality=low ($0.006) and flux-2-klein (from $0.014) sit on the same floor.
  4. Instruction-heavy composition (specific placements)?gpt-image-2. Google premium is gemini-3-pro-image ($0.134 at 1K/2K, $0.24 at 4K).
  5. Photoreal + art-directed?flux-2-pro. Strongest photoreal output with prompt fidelity.
  6. Default fallthroughgemini-3.1-flash-image. Nano Banana 2. High batch count stays on this family (or Lite if you already hit the cheap floor). The call is generate_content with IMAGE, one image per call.

Midjourney stays a UI pick (V8.x, no public API).

The escape hatches

A few real-world overrides that the priority list above doesn't capture:

  • latency_ms_max < 2000 — drop to a Flash-tier model. nano banana 2 generates faster than Pro. Lite is the other "speed first" pick.
  • self_hosted = True AND volume > 50K/month — run FLUX.2 on a rented GPU. The economics flip when you amortize the hardware.

How this becomes code

The pseudocode for the routing function:

def pick_image_model(task):
    if task.get("needs_vector"):
        return "recraftv4_1"
    if task.get("needs_text_in_image"):
        return "ideogram-v4"
    if task.get("budget_per_image", 1.0) < 0.04:
        return "gemini-3.1-flash-lite-image"
    if task.get("instruction_heavy"):
        return "gpt-image-2"
    if task.get("google_premium"):
        return "gemini-3-pro-image"
    if task.get("photoreal_art_directed"):
        return "flux-2-pro"
    return "gemini-3.1-flash-image"

That's the spec for the write step. Notice the order: specialty constraints (vector, text), then the budget floor, then style preferences (instruction, Google premium, photoreal), then default. High batch_count is not its own branch — it falls through to nano banana 2.

A note on the "default" pick

gemini-3.1-flash-image as the fallthrough is a 2026-specific call. A year ago it would have been Imagen 4. Two years ago it would have been DALL-E 3. The default rotates as the landscape shifts. The shape of the function — specialty → preference → default — doesn't.

If you're writing this for a long-lived production codebase, leave the rule list as a module-level constant (like the RULES tuple in the editor above) so a future maintainer can re-rank without rewriting the function body. That's the same pattern from chapter 24 — keep the registry out of the loop.

What you'll write

Step 8 asks you to implement pick_image_model(task) with the rules above. Step 9 runs it across five tasks and audits a pipeline. Both are pure stdlib, no API calls — just decision logic, exactly like the kind of routing function that sits at the front of a real image-gen feature in production.

The decision tree, ordered

You've now seen the six families and the specialty cases. Here's the routing logic in one place — the order matters.

The rules, in priority order

The reason for priority order: specialty constraints beat general preferences. If the brief needs text-in-image, it doesn't matter that you prefer Midjourney — Ideogram is the answer. So the routing function checks the hard constraints first, then the cheap floor, then style preferences, then defaults.

The order:

  1. Vector required?recraftv4_1. Recraft V4.1. Raster $0.035, vector $0.08.
  2. Text in image required?ideogram-v4. Ideogram 4.0. POST /v1/ideogram-v4/generate.
  3. Cheap floor (budget_per_image < 0.04) → gemini-3.1-flash-lite-image. $0.0336 at 1K. gpt-image-2 quality=low ($0.006) and flux-2-klein (from $0.014) sit on the same floor.
  4. Instruction-heavy composition (specific placements)?gpt-image-2. Google premium is gemini-3-pro-image ($0.134 at 1K/2K, $0.24 at 4K).
  5. Photoreal + art-directed?flux-2-pro. Strongest photoreal output with prompt fidelity.
  6. Default fallthroughgemini-3.1-flash-image. Nano Banana 2. High batch count stays on this family (or Lite if you already hit the cheap floor). The call is generate_content with IMAGE, one image per call.

Midjourney stays a UI pick (V8.x, no public API).

The escape hatches

A few real-world overrides that the priority list above doesn't capture:

  • latency_ms_max < 2000 — drop to a Flash-tier model. nano banana 2 generates faster than Pro. Lite is the other "speed first" pick.
  • self_hosted = True AND volume > 50K/month — run FLUX.2 on a rented GPU. The economics flip when you amortize the hardware.

How this becomes code

The pseudocode for the routing function:

def pick_image_model(task):
    if task.get("needs_vector"):
        return "recraftv4_1"
    if task.get("needs_text_in_image"):
        return "ideogram-v4"
    if task.get("budget_per_image", 1.0) < 0.04:
        return "gemini-3.1-flash-lite-image"
    if task.get("instruction_heavy"):
        return "gpt-image-2"
    if task.get("google_premium"):
        return "gemini-3-pro-image"
    if task.get("photoreal_art_directed"):
        return "flux-2-pro"
    return "gemini-3.1-flash-image"

That's the spec for the write step. Notice the order: specialty constraints (vector, text), then the budget floor, then style preferences (instruction, Google premium, photoreal), then default. High batch_count is not its own branch — it falls through to nano banana 2.

A note on the "default" pick

gemini-3.1-flash-image as the fallthrough is a 2026-specific call. A year ago it would have been Imagen 4. Two years ago it would have been DALL-E 3. The default rotates as the landscape shifts. The shape of the function — specialty → preference → default — doesn't.

If you're writing this for a long-lived production codebase, leave the rule list as a module-level constant (like the RULES tuple in the editor above) so a future maintainer can re-rank without rewriting the function body. That's the same pattern from the agent harnesses chapter — keep the registry out of the loop.

What you'll write

Step 8 asks you to implement pick_image_model(task) with the rules above. Step 9 runs it across five tasks and audits a pipeline. Both are pure stdlib, no API calls — just decision logic, exactly like the kind of routing function that sits at the front of a real image-gen feature in production.

The decision tree, ordered

You've now seen the six families and the specialty cases. Here's the routing logic in one place — the order matters.

The rules, in priority order

The reason for priority order: specialty constraints beat general preferences. If the brief needs text-in-image, it doesn't matter that you prefer Midjourney — Ideogram is the answer. So the routing function checks the hard constraints first, then the cheap floor, then style preferences, then defaults.

The order:

  1. Vector required?recraftv4_1. Recraft V4.1. Raster $0.035, vector $0.08.
  2. Text in image required?ideogram-v4. Ideogram 4.0. POST /v1/ideogram-v4/generate.
  3. Cheap floor (budget_per_image < 0.04) → gemini-3.1-flash-lite-image. $0.0336 at 1K. gpt-image-2 quality=low ($0.006) and flux-2-klein (from $0.014) sit on the same floor.
  4. Instruction-heavy composition (specific placements)?gpt-image-2. Google premium is gemini-3-pro-image ($0.134 at 1K/2K, $0.24 at 4K).
  5. Photoreal + art-directed?flux-2-pro. Strongest photoreal output with prompt fidelity.
  6. Default fallthroughgemini-3.1-flash-image. Nano Banana 2. High batch count stays on this family (or Lite if you already hit the cheap floor). The call is generate_content with IMAGE, one image per call.

Midjourney stays a UI pick (V8.x, no public API).

The escape hatches

A few real-world overrides that the priority list above doesn't capture:

  • latency_ms_max < 2000 — drop to a Flash-tier model. nano banana 2 generates faster than Pro. Lite is the other "speed first" pick.
  • self_hosted = True AND volume > 50K/month — run FLUX.2 on a rented GPU. The economics flip when you amortize the hardware.

How this becomes code

The pseudocode for the routing function:

def pick_image_model(task):
    if task.get("needs_vector"):
        return "recraftv4_1"
    if task.get("needs_text_in_image"):
        return "ideogram-v4"
    if task.get("budget_per_image", 1.0) < 0.04:
        return "gemini-3.1-flash-lite-image"
    if task.get("instruction_heavy"):
        return "gpt-image-2"
    if task.get("google_premium"):
        return "gemini-3-pro-image"
    if task.get("photoreal_art_directed"):
        return "flux-2-pro"
    return "gemini-3.1-flash-image"

That's the spec for the write step. Notice the order: specialty constraints (vector, text), then the budget floor, then style preferences (instruction, Google premium, photoreal), then default. High batch_count is not its own branch — it falls through to nano banana 2.

A note on the "default" pick

gemini-3.1-flash-image as the fallthrough is a 2026-specific call. A year ago it would have been Imagen 4. Two years ago it would have been DALL-E 3. The default rotates as the landscape shifts. The shape of the function — specialty → preference → default — doesn't.

If you're writing this for a long-lived production codebase, leave the rule list as a module-level constant (like the RULES tuple in the editor above) so a future maintainer can re-rank without rewriting the function body. That's the same pattern from the agent harnesses chapter — keep the registry out of the loop.

What you'll write

Step 8 asks you to implement pick_image_model(task) with the rules above. Step 9 runs it across five tasks and audits a pipeline. Both are pure stdlib, no API calls — just decision logic, exactly like the kind of routing function that sits at the front of a real image-gen feature in production.

The decision tree, ordered

You've now seen the six families and the specialty cases. Here's the routing logic in one place — the order matters.

The rules, in priority order

The reason for priority order: specialty constraints beat general preferences. If the brief needs text-in-image, it doesn't matter that you prefer Midjourney — Ideogram is the answer. So the routing function checks the hard constraints first, then the cheap floor, then style preferences, then defaults.

The order:

  1. Vector required?recraftv4_1. Recraft V4.1. Raster $0.035, vector $0.08.
  2. Text in image required?ideogram-v4. Ideogram 4.0. POST /v1/ideogram-v4/generate.
  3. Cheap floor (budget_per_image < 0.04) → gemini-3.1-flash-lite-image. $0.0336 at 1K. gpt-image-2 quality=low ($0.006) and flux-2-klein (from $0.014) sit on the same floor.
  4. Instruction-heavy composition (specific placements)?gpt-image-2. Google premium is gemini-3-pro-image ($0.134 at 1K/2K, $0.24 at 4K).
  5. Photoreal + art-directed?flux-2-pro. Strongest photoreal output with prompt fidelity.
  6. Default fallthroughgemini-3.1-flash-image. Nano Banana 2. High batch count stays on this family (or Lite if you already hit the cheap floor). The call is generate_content with IMAGE, one image per call.

Midjourney stays a UI pick (V8.x, no public API).

The escape hatches

A few real-world overrides that the priority list above doesn't capture:

  • latency_ms_max < 2000 — drop to a Flash-tier model. nano banana 2 generates faster than Pro. Lite is the other "speed first" pick.
  • self_hosted = True AND volume > 50K/month — run FLUX.2 on a rented GPU. The economics flip when you amortize the hardware.

How this becomes code

The pseudocode for the routing function:

def pick_image_model(task):
    if task.get("needs_vector"):
        return "recraftv4_1"
    if task.get("needs_text_in_image"):
        return "ideogram-v4"
    if task.get("budget_per_image", 1.0) < 0.04:
        return "gemini-3.1-flash-lite-image"
    if task.get("instruction_heavy"):
        return "gpt-image-2"
    if task.get("google_premium"):
        return "gemini-3-pro-image"
    if task.get("photoreal_art_directed"):
        return "flux-2-pro"
    return "gemini-3.1-flash-image"

That's the spec for the write step. Notice the order: specialty constraints (vector, text), then the budget floor, then style preferences (instruction, Google premium, photoreal), then default. High batch_count is not its own branch — it falls through to nano banana 2.

A note on the "default" pick

gemini-3.1-flash-image as the fallthrough is a 2026-specific call. A year ago it would have been Imagen 4. Two years ago it would have been DALL-E 3. The default rotates as the landscape shifts. The shape of the function — specialty → preference → default — doesn't.

If you're writing this for a long-lived production codebase, leave the rule list as a module-level constant (like the RULES tuple in the editor above) so a future maintainer can re-rank without rewriting the function body. That's the same pattern from the agent harnesses chapter — keep the registry out of the loop.

What you'll write

Step 8 asks you to implement pick_image_model(task) with the rules above. Step 9 runs it across five tasks and audits a pipeline. Both are pure stdlib, no API calls — just decision logic, exactly like the kind of routing function that sits at the front of a real image-gen feature in production.

The decision tree, ordered

You've now seen the six families and the specialty cases. Here's the routing logic in one place — the order matters.

The rules, in priority order

The reason for priority order: specialty constraints beat general preferences. If the brief needs text-in-image, it doesn't matter that you prefer Midjourney — Ideogram is the answer. So the routing function checks the hard constraints first, then the cheap floor, then style preferences, then defaults.

The order:

  1. Vector required?recraftv4_1. Recraft V4.1. Raster $0.035, vector $0.08.
  2. Text in image required?ideogram-v4. Ideogram 4.0. POST /v1/ideogram-v4/generate.
  3. Cheap floor (budget_per_image < 0.04) → gemini-3.1-flash-lite-image. $0.0336 at 1K. gpt-image-2 quality=low ($0.006) and flux-2-klein (from $0.014) sit on the same floor.
  4. Instruction-heavy composition (specific placements)?gpt-image-2. Google premium is gemini-3-pro-image ($0.134 at 1K/2K, $0.24 at 4K).
  5. Photoreal + art-directed?flux-2-pro. Strongest photoreal output with prompt fidelity.
  6. Default fallthroughgemini-3.1-flash-image. Nano Banana 2. High batch count stays on this family (or Lite if you already hit the cheap floor). The call is generate_content with IMAGE, one image per call.

Midjourney stays a UI pick (V8.x, no public API).

The escape hatches

A few real-world overrides that the priority list above doesn't capture:

  • latency_ms_max < 2000 — drop to a Flash-tier model. nano banana 2 generates faster than Pro. Lite is the other "speed first" pick.
  • self_hosted = True AND volume > 50K/month — run FLUX.2 on a rented GPU. The economics flip when you amortize the hardware.

How this becomes code

The pseudocode for the routing function:

def pick_image_model(task):
    if task.get("needs_vector"):
        return "recraftv4_1"
    if task.get("needs_text_in_image"):
        return "ideogram-v4"
    if task.get("budget_per_image", 1.0) < 0.04:
        return "gemini-3.1-flash-lite-image"
    if task.get("instruction_heavy"):
        return "gpt-image-2"
    if task.get("google_premium"):
        return "gemini-3-pro-image"
    if task.get("photoreal_art_directed"):
        return "flux-2-pro"
    return "gemini-3.1-flash-image"

That's the spec for the write step. Notice the order: specialty constraints (vector, text), then the budget floor, then style preferences (instruction, Google premium, photoreal), then default. High batch_count is not its own branch — it falls through to nano banana 2.

A note on the "default" pick

gemini-3.1-flash-image as the fallthrough is a 2026-specific call. A year ago it would have been Imagen 4. Two years ago it would have been DALL-E 3. The default rotates as the landscape shifts. The shape of the function — specialty → preference → default — doesn't.

If you're writing this for a long-lived production codebase, leave the rule list as a module-level constant (like the RULES tuple in the editor above) so a future maintainer can re-rank without rewriting the function body. That's the same pattern from the agent harnesses chapter — keep the registry out of the loop.

What you'll write

Step 8 asks you to implement pick_image_model(task) with the rules above. Step 9 runs it across five tasks and audits a pipeline. Both are pure stdlib, no API calls — just decision logic, exactly like the kind of routing function that sits at the front of a real image-gen feature in production.

The decision tree, ordered

You've now seen the six families and the specialty cases. Here's the routing logic in one place — the order matters.

The rules, in priority order

The reason for priority order: specialty constraints beat general preferences. If the brief needs text-in-image, it doesn't matter that you prefer Midjourney — Ideogram is the answer. So the routing function checks the hard constraints first, then the cheap floor, then style preferences, then defaults.

The order:

  1. Vector required?recraftv4_1. Recraft V4.1. Raster $0.035, vector $0.08.
  2. Text in image required?ideogram-v4. Ideogram 4.0. POST /v1/ideogram-v4/generate.
  3. Cheap floor (budget_per_image < 0.04) → gemini-3.1-flash-lite-image. $0.0336 at 1K. gpt-image-2 quality=low ($0.006) and flux-2-klein (from $0.014) sit on the same floor.
  4. Instruction-heavy composition (specific placements)?gpt-image-2. Google premium is gemini-3-pro-image ($0.134 at 1K/2K, $0.24 at 4K).
  5. Photoreal + art-directed?flux-2-pro. Strongest photoreal output with prompt fidelity.
  6. Default fallthroughgemini-3.1-flash-image. Nano Banana 2. High batch count stays on this family (or Lite if you already hit the cheap floor). The call is generate_content with IMAGE, one image per call.

Midjourney stays a UI pick (V8.x, no public API).

The escape hatches

A few real-world overrides that the priority list above doesn't capture:

  • latency_ms_max < 2000 — drop to a Flash-tier model. nano banana 2 generates faster than Pro. Lite is the other "speed first" pick.
  • self_hosted = True AND volume > 50K/month — run FLUX.2 on a rented GPU. The economics flip when you amortize the hardware.

How this becomes code

The pseudocode for the routing function:

def pick_image_model(task):
    if task.get("needs_vector"):
        return "recraftv4_1"
    if task.get("needs_text_in_image"):
        return "ideogram-v4"
    if task.get("budget_per_image", 1.0) < 0.04:
        return "gemini-3.1-flash-lite-image"
    if task.get("instruction_heavy"):
        return "gpt-image-2"
    if task.get("google_premium"):
        return "gemini-3-pro-image"
    if task.get("photoreal_art_directed"):
        return "flux-2-pro"
    return "gemini-3.1-flash-image"

That's the spec for the write step. Notice the order: specialty constraints (vector, text), then the budget floor, then style preferences (instruction, Google premium, photoreal), then default. High batch_count is not its own branch — it falls through to nano banana 2.

A note on the "default" pick

gemini-3.1-flash-image as the fallthrough is a 2026-specific call. A year ago it would have been Imagen 4. Two years ago it would have been DALL-E 3. The default rotates as the landscape shifts. The shape of the function — specialty → preference → default — doesn't.

If you're writing this for a long-lived production codebase, leave the rule list as a module-level constant (like the RULES tuple in the editor above) so a future maintainer can re-rank without rewriting the function body. That's the same pattern from the agent harnesses chapter — keep the registry out of the loop.

What you'll write

Step 8 asks you to implement pick_image_model(task) with the rules above. Step 9 runs it across five tasks and audits a pipeline. Both are pure stdlib, no API calls — just decision logic, exactly like the kind of routing function that sits at the front of a real image-gen feature in production.

The decision tree, ordered

You've now seen the six families and the specialty cases. Here's the routing logic in one place — the order matters.

The rules, in priority order

The reason for priority order: specialty constraints beat general preferences. If the brief needs text-in-image, it doesn't matter that you prefer Midjourney — Ideogram is the answer. So the routing function checks the hard constraints first, then the cheap floor, then style preferences, then defaults.

The order:

  1. Vector required?recraftv4_1. Recraft V4.1. Raster $0.035, vector $0.08.
  2. Text in image required?ideogram-v4. Ideogram 4.0. POST /v1/ideogram-v4/generate.
  3. Cheap floor (budget_per_image < 0.04) → gemini-3.1-flash-lite-image. $0.0336 at 1K. gpt-image-2 quality=low ($0.006) and flux-2-klein (from $0.014) sit on the same floor.
  4. Instruction-heavy composition (specific placements)?gpt-image-2. Google premium is gemini-3-pro-image ($0.134 at 1K/2K, $0.24 at 4K).
  5. Photoreal + art-directed?flux-2-pro. Strongest photoreal output with prompt fidelity.
  6. Default fallthroughgemini-3.1-flash-image. Nano Banana 2. High batch count stays on this family (or Lite if you already hit the cheap floor). The call is generate_content with IMAGE, one image per call.

Midjourney stays a UI pick (V8.x, no public API).

The escape hatches

A few real-world overrides that the priority list above doesn't capture:

  • latency_ms_max < 2000 — drop to a Flash-tier model. nano banana 2 generates faster than Pro. Lite is the other "speed first" pick.
  • self_hosted = True AND volume > 50K/month — run FLUX.2 on a rented GPU. The economics flip when you amortize the hardware.

How this becomes code

The pseudocode for the routing function:

def pick_image_model(task):
    if task.get("needs_vector"):
        return "recraftv4_1"
    if task.get("needs_text_in_image"):
        return "ideogram-v4"
    if task.get("budget_per_image", 1.0) < 0.04:
        return "gemini-3.1-flash-lite-image"
    if task.get("instruction_heavy"):
        return "gpt-image-2"
    if task.get("google_premium"):
        return "gemini-3-pro-image"
    if task.get("photoreal_art_directed"):
        return "flux-2-pro"
    return "gemini-3.1-flash-image"

That's the spec for the write step. Notice the order: specialty constraints (vector, text), then the budget floor, then style preferences (instruction, Google premium, photoreal), then default. High batch_count is not its own branch — it falls through to nano banana 2.

A note on the "default" pick

gemini-3.1-flash-image as the fallthrough is a 2026-specific call. A year ago it would have been Imagen 4. Two years ago it would have been DALL-E 3. The default rotates as the landscape shifts. The shape of the function — specialty → preference → default — doesn't.

If you're writing this for a long-lived production codebase, leave the rule list as a module-level constant (like the RULES tuple in the editor above) so a future maintainer can re-rank without rewriting the function body. That's the same pattern from the agent harnesses chapter — keep the registry out of the loop.

What you'll write

Step 8 asks you to implement pick_image_model(task) with the rules above. Step 9 runs it across five tasks and audits a pipeline. Both are pure stdlib, no API calls — just decision logic, exactly like the kind of routing function that sits at the front of a real image-gen feature in production.

The decision tree, ordered

You've now seen the six families and the specialty cases. Here's the routing logic in one place — the order matters.

The rules, in priority order

The reason for priority order: specialty constraints beat general preferences. If the brief needs text-in-image, it doesn't matter that you prefer Midjourney — Ideogram is the answer. So the routing function checks the hard constraints first, then the cheap floor, then style preferences, then defaults.

The order:

  1. Vector required?recraftv4_1. Recraft V4.1. Raster $0.035, vector $0.08.
  2. Text in image required?ideogram-v4. Ideogram 4.0. POST /v1/ideogram-v4/generate.
  3. Cheap floor (budget_per_image < 0.04) → gemini-3.1-flash-lite-image. $0.0336 at 1K. gpt-image-2 quality=low ($0.006) and flux-2-klein (from $0.014) sit on the same floor.
  4. Instruction-heavy composition (specific placements)?gpt-image-2. Google premium is gemini-3-pro-image ($0.134 at 1K/2K, $0.24 at 4K).
  5. Photoreal + art-directed?flux-2-pro. Strongest photoreal output with prompt fidelity.
  6. Default fallthroughgemini-3.1-flash-image. Nano Banana 2. High batch count stays on this family (or Lite if you already hit the cheap floor). The call is generate_content with IMAGE, one image per call.

Midjourney stays a UI pick (V8.x, no public API).

The escape hatches

A few real-world overrides that the priority list above doesn't capture:

  • latency_ms_max < 2000 — drop to a Flash-tier model. nano banana 2 generates faster than Pro. Lite is the other "speed first" pick.
  • self_hosted = True AND volume > 50K/month — run FLUX.2 on a rented GPU. The economics flip when you amortize the hardware.

How this becomes code

The pseudocode for the routing function:

def pick_image_model(task):
    if task.get("needs_vector"):
        return "recraftv4_1"
    if task.get("needs_text_in_image"):
        return "ideogram-v4"
    if task.get("budget_per_image", 1.0) < 0.04:
        return "gemini-3.1-flash-lite-image"
    if task.get("instruction_heavy"):
        return "gpt-image-2"
    if task.get("google_premium"):
        return "gemini-3-pro-image"
    if task.get("photoreal_art_directed"):
        return "flux-2-pro"
    return "gemini-3.1-flash-image"

That's the spec for the write step. Notice the order: specialty constraints (vector, text), then the budget floor, then style preferences (instruction, Google premium, photoreal), then default. High batch_count is not its own branch — it falls through to nano banana 2.

A note on the "default" pick

gemini-3.1-flash-image as the fallthrough is a 2026-specific call. A year ago it would have been Imagen 4. Two years ago it would have been DALL-E 3. The default rotates as the landscape shifts. The shape of the function — specialty → preference → default — doesn't.

If you're writing this for a long-lived production codebase, leave the rule list as a module-level constant (like the RULES tuple in the editor above) so a future maintainer can re-rank without rewriting the function body. That's the same pattern from the agent harnesses chapter — keep the registry out of the loop.

What you'll write

Step 8 asks you to implement pick_image_model(task) with the rules above. Step 9 runs it across five tasks and audits a pipeline. Both are pure stdlib, no API calls — just decision logic, exactly like the kind of routing function that sits at the front of a real image-gen feature in production.

The decision tree, ordered

You've now seen the six families and the specialty cases. Here's the routing logic in one place — the order matters.

The rules, in priority order

The reason for priority order: specialty constraints beat general preferences. If the brief needs text-in-image, it doesn't matter that you prefer Midjourney — Ideogram is the answer. So the routing function checks the hard constraints first, then the cheap floor, then style preferences, then defaults.

The order:

  1. Vector required?recraftv4_1. Recraft V4.1. Raster $0.035, vector $0.08.
  2. Text in image required?ideogram-v4. Ideogram 4.0. POST /v1/ideogram-v4/generate.
  3. Cheap floor (budget_per_image < 0.04) → gemini-3.1-flash-lite-image. $0.0336 at 1K. gpt-image-2 quality=low ($0.006) and flux-2-klein (from $0.014) sit on the same floor.
  4. Instruction-heavy composition (specific placements)?gpt-image-2. Google premium is gemini-3-pro-image ($0.134 at 1K/2K, $0.24 at 4K).
  5. Photoreal + art-directed?flux-2-pro. Strongest photoreal output with prompt fidelity.
  6. Default fallthroughgemini-3.1-flash-image. Nano Banana 2. High batch count stays on this family (or Lite if you already hit the cheap floor). The call is generate_content with IMAGE, one image per call.

Midjourney stays a UI pick (V8.x, no public API).

The escape hatches

A few real-world overrides that the priority list above doesn't capture:

  • latency_ms_max < 2000 — drop to a Flash-tier model. nano banana 2 generates faster than Pro. Lite is the other "speed first" pick.
  • self_hosted = True AND volume > 50K/month — run FLUX.2 on a rented GPU. The economics flip when you amortize the hardware.

How this becomes code

The pseudocode for the routing function:

def pick_image_model(task):
    if task.get("needs_vector"):
        return "recraftv4_1"
    if task.get("needs_text_in_image"):
        return "ideogram-v4"
    if task.get("budget_per_image", 1.0) < 0.04:
        return "gemini-3.1-flash-lite-image"
    if task.get("instruction_heavy"):
        return "gpt-image-2"
    if task.get("google_premium"):
        return "gemini-3-pro-image"
    if task.get("photoreal_art_directed"):
        return "flux-2-pro"
    return "gemini-3.1-flash-image"

That's the spec for the write step. Notice the order: specialty constraints (vector, text), then the budget floor, then style preferences (instruction, Google premium, photoreal), then default. High batch_count is not its own branch — it falls through to nano banana 2.

A note on the "default" pick

gemini-3.1-flash-image as the fallthrough is a 2026-specific call. A year ago it would have been Imagen 4. Two years ago it would have been DALL-E 3. The default rotates as the landscape shifts. The shape of the function — specialty → preference → default — doesn't.

If you're writing this for a long-lived production codebase, leave the rule list as a module-level constant (like the RULES tuple in the editor above) so a future maintainer can re-rank without rewriting the function body. That's the same pattern from chapter 14 — keep the registry out of the loop.

What you'll write

Step 8 asks you to implement pick_image_model(task) with the rules above. Step 9 runs it across five tasks and audits a pipeline. Both are pure stdlib, no API calls — just decision logic, exactly like the kind of routing function that sits at the front of a real image-gen feature in production.

The decision tree, ordered

You've now seen the six families and the specialty cases. Here's the routing logic in one place — the order matters.

The rules, in priority order

The reason for priority order: specialty constraints beat general preferences. If the brief needs text-in-image, it doesn't matter that you prefer Midjourney — Ideogram is the answer. So the routing function checks the hard constraints first, then the cheap floor, then style preferences, then defaults.

The order:

  1. Vector required?recraftv4_1. Recraft V4.1. Raster $0.035, vector $0.08.
  2. Text in image required?ideogram-v4. Ideogram 4.0. POST /v1/ideogram-v4/generate.
  3. Cheap floor (budget_per_image < 0.04) → gemini-3.1-flash-lite-image. $0.0336 at 1K. gpt-image-2 quality=low ($0.006) and flux-2-klein (from $0.014) sit on the same floor.
  4. Instruction-heavy composition (specific placements)?gpt-image-2. Google premium is gemini-3-pro-image ($0.134 at 1K/2K, $0.24 at 4K).
  5. Photoreal + art-directed?flux-2-pro. Strongest photoreal output with prompt fidelity.
  6. Default fallthroughgemini-3.1-flash-image. Nano Banana 2. High batch count stays on this family (or Lite if you already hit the cheap floor). The call is generate_content with IMAGE, one image per call.

Midjourney stays a UI pick (V8.x, no public API).

The escape hatches

A few real-world overrides that the priority list above doesn't capture:

  • latency_ms_max < 2000 — drop to a Flash-tier model. nano banana 2 generates faster than Pro. Lite is the other "speed first" pick.
  • self_hosted = True AND volume > 50K/month — run FLUX.2 on a rented GPU. The economics flip when you amortize the hardware.

How this becomes code

The pseudocode for the routing function:

def pick_image_model(task):
    if task.get("needs_vector"):
        return "recraftv4_1"
    if task.get("needs_text_in_image"):
        return "ideogram-v4"
    if task.get("budget_per_image", 1.0) < 0.04:
        return "gemini-3.1-flash-lite-image"
    if task.get("instruction_heavy"):
        return "gpt-image-2"
    if task.get("google_premium"):
        return "gemini-3-pro-image"
    if task.get("photoreal_art_directed"):
        return "flux-2-pro"
    return "gemini-3.1-flash-image"

That's the spec for the write step. Notice the order: specialty constraints (vector, text), then the budget floor, then style preferences (instruction, Google premium, photoreal), then default. High batch_count is not its own branch — it falls through to nano banana 2.

A note on the "default" pick

gemini-3.1-flash-image as the fallthrough is a 2026-specific call. A year ago it would have been Imagen 4. Two years ago it would have been DALL-E 3. The default rotates as the landscape shifts. The shape of the function — specialty → preference → default — doesn't.

If you're writing this for a long-lived production codebase, leave the rule list as a module-level constant (like the RULES tuple in the editor above) so a future maintainer can re-rank without rewriting the function body. That's the same pattern from the agent harnesses chapter — keep the registry out of the loop.

What you'll write

Step 8 asks you to implement pick_image_model(task) with the rules above. Step 9 runs it across five tasks and audits a pipeline. Both are pure stdlib, no API calls — just decision logic, exactly like the kind of routing function that sits at the front of a real image-gen feature in production.