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:
- Vector required? →
recraftv4_1. Recraft V4.1. Raster $0.035, vector $0.08. - Text in image required? →
ideogram-v4. Ideogram 4.0. POST/v1/ideogram-v4/generate. - Cheap floor (
budget_per_image < 0.04) →gemini-3.1-flash-lite-image. $0.0336 at 1K. gpt-image-2 quality=low ($0.006) andflux-2-klein(from $0.014) sit on the same floor. - Instruction-heavy composition (specific placements)? →
gpt-image-2. Google premium isgemini-3-pro-image($0.134 at 1K/2K, $0.24 at 4K). - Photoreal + art-directed? →
flux-2-pro. Strongest photoreal output with prompt fidelity. - Default fallthrough →
gemini-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 isgenerate_contentwith 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 = TrueAND 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:
- Vector required? →
recraftv4_1. Recraft V4.1. Raster $0.035, vector $0.08. - Text in image required? →
ideogram-v4. Ideogram 4.0. POST/v1/ideogram-v4/generate. - Cheap floor (
budget_per_image < 0.04) →gemini-3.1-flash-lite-image. $0.0336 at 1K. gpt-image-2 quality=low ($0.006) andflux-2-klein(from $0.014) sit on the same floor. - Instruction-heavy composition (specific placements)? →
gpt-image-2. Google premium isgemini-3-pro-image($0.134 at 1K/2K, $0.24 at 4K). - Photoreal + art-directed? →
flux-2-pro. Strongest photoreal output with prompt fidelity. - Default fallthrough →
gemini-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 isgenerate_contentwith 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 = TrueAND 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:
- Vector required? →
recraftv4_1. Recraft V4.1. Raster $0.035, vector $0.08. - Text in image required? →
ideogram-v4. Ideogram 4.0. POST/v1/ideogram-v4/generate. - Cheap floor (
budget_per_image < 0.04) →gemini-3.1-flash-lite-image. $0.0336 at 1K. gpt-image-2 quality=low ($0.006) andflux-2-klein(from $0.014) sit on the same floor. - Instruction-heavy composition (specific placements)? →
gpt-image-2. Google premium isgemini-3-pro-image($0.134 at 1K/2K, $0.24 at 4K). - Photoreal + art-directed? →
flux-2-pro. Strongest photoreal output with prompt fidelity. - Default fallthrough →
gemini-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 isgenerate_contentwith 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 = TrueAND 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:
- Vector required? →
recraftv4_1. Recraft V4.1. Raster $0.035, vector $0.08. - Text in image required? →
ideogram-v4. Ideogram 4.0. POST/v1/ideogram-v4/generate. - Cheap floor (
budget_per_image < 0.04) →gemini-3.1-flash-lite-image. $0.0336 at 1K. gpt-image-2 quality=low ($0.006) andflux-2-klein(from $0.014) sit on the same floor. - Instruction-heavy composition (specific placements)? →
gpt-image-2. Google premium isgemini-3-pro-image($0.134 at 1K/2K, $0.24 at 4K). - Photoreal + art-directed? →
flux-2-pro. Strongest photoreal output with prompt fidelity. - Default fallthrough →
gemini-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 isgenerate_contentwith 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 = TrueAND 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:
- Vector required? →
recraftv4_1. Recraft V4.1. Raster $0.035, vector $0.08. - Text in image required? →
ideogram-v4. Ideogram 4.0. POST/v1/ideogram-v4/generate. - Cheap floor (
budget_per_image < 0.04) →gemini-3.1-flash-lite-image. $0.0336 at 1K. gpt-image-2 quality=low ($0.006) andflux-2-klein(from $0.014) sit on the same floor. - Instruction-heavy composition (specific placements)? →
gpt-image-2. Google premium isgemini-3-pro-image($0.134 at 1K/2K, $0.24 at 4K). - Photoreal + art-directed? →
flux-2-pro. Strongest photoreal output with prompt fidelity. - Default fallthrough →
gemini-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 isgenerate_contentwith 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 = TrueAND 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:
- Vector required? →
recraftv4_1. Recraft V4.1. Raster $0.035, vector $0.08. - Text in image required? →
ideogram-v4. Ideogram 4.0. POST/v1/ideogram-v4/generate. - Cheap floor (
budget_per_image < 0.04) →gemini-3.1-flash-lite-image. $0.0336 at 1K. gpt-image-2 quality=low ($0.006) andflux-2-klein(from $0.014) sit on the same floor. - Instruction-heavy composition (specific placements)? →
gpt-image-2. Google premium isgemini-3-pro-image($0.134 at 1K/2K, $0.24 at 4K). - Photoreal + art-directed? →
flux-2-pro. Strongest photoreal output with prompt fidelity. - Default fallthrough →
gemini-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 isgenerate_contentwith 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 = TrueAND 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:
- Vector required? →
recraftv4_1. Recraft V4.1. Raster $0.035, vector $0.08. - Text in image required? →
ideogram-v4. Ideogram 4.0. POST/v1/ideogram-v4/generate. - Cheap floor (
budget_per_image < 0.04) →gemini-3.1-flash-lite-image. $0.0336 at 1K. gpt-image-2 quality=low ($0.006) andflux-2-klein(from $0.014) sit on the same floor. - Instruction-heavy composition (specific placements)? →
gpt-image-2. Google premium isgemini-3-pro-image($0.134 at 1K/2K, $0.24 at 4K). - Photoreal + art-directed? →
flux-2-pro. Strongest photoreal output with prompt fidelity. - Default fallthrough →
gemini-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 isgenerate_contentwith 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 = TrueAND 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:
- Vector required? →
recraftv4_1. Recraft V4.1. Raster $0.035, vector $0.08. - Text in image required? →
ideogram-v4. Ideogram 4.0. POST/v1/ideogram-v4/generate. - Cheap floor (
budget_per_image < 0.04) →gemini-3.1-flash-lite-image. $0.0336 at 1K. gpt-image-2 quality=low ($0.006) andflux-2-klein(from $0.014) sit on the same floor. - Instruction-heavy composition (specific placements)? →
gpt-image-2. Google premium isgemini-3-pro-image($0.134 at 1K/2K, $0.24 at 4K). - Photoreal + art-directed? →
flux-2-pro. Strongest photoreal output with prompt fidelity. - Default fallthrough →
gemini-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 isgenerate_contentwith 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 = TrueAND 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:
- Vector required? →
recraftv4_1. Recraft V4.1. Raster $0.035, vector $0.08. - Text in image required? →
ideogram-v4. Ideogram 4.0. POST/v1/ideogram-v4/generate. - Cheap floor (
budget_per_image < 0.04) →gemini-3.1-flash-lite-image. $0.0336 at 1K. gpt-image-2 quality=low ($0.006) andflux-2-klein(from $0.014) sit on the same floor. - Instruction-heavy composition (specific placements)? →
gpt-image-2. Google premium isgemini-3-pro-image($0.134 at 1K/2K, $0.24 at 4K). - Photoreal + art-directed? →
flux-2-pro. Strongest photoreal output with prompt fidelity. - Default fallthrough →
gemini-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 isgenerate_contentwith 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 = TrueAND 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:
- Vector required? →
recraftv4_1. Recraft V4.1. Raster $0.035, vector $0.08. - Text in image required? →
ideogram-v4. Ideogram 4.0. POST/v1/ideogram-v4/generate. - Cheap floor (
budget_per_image < 0.04) →gemini-3.1-flash-lite-image. $0.0336 at 1K. gpt-image-2 quality=low ($0.006) andflux-2-klein(from $0.014) sit on the same floor. - Instruction-heavy composition (specific placements)? →
gpt-image-2. Google premium isgemini-3-pro-image($0.134 at 1K/2K, $0.24 at 4K). - Photoreal + art-directed? →
flux-2-pro. Strongest photoreal output with prompt fidelity. - Default fallthrough →
gemini-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 isgenerate_contentwith 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 = TrueAND 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:
- Vector required? →
recraftv4_1. Recraft V4.1. Raster $0.035, vector $0.08. - Text in image required? →
ideogram-v4. Ideogram 4.0. POST/v1/ideogram-v4/generate. - Cheap floor (
budget_per_image < 0.04) →gemini-3.1-flash-lite-image. $0.0336 at 1K. gpt-image-2 quality=low ($0.006) andflux-2-klein(from $0.014) sit on the same floor. - Instruction-heavy composition (specific placements)? →
gpt-image-2. Google premium isgemini-3-pro-image($0.134 at 1K/2K, $0.24 at 4K). - Photoreal + art-directed? →
flux-2-pro. Strongest photoreal output with prompt fidelity. - Default fallthrough →
gemini-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 isgenerate_contentwith 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 = TrueAND 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.