A hall visual that stays on the general branch uses the Efficient model from the 2026-10-08 intelligence-radar tree. render-hall writes only portraits whose files are still absent. Assistant: grok Assistant-Session: 01a11ab6-1534-7d63-8606-662b92a80ed6
157 lines
6.1 KiB
Python
157 lines
6.1 KiB
Python
"""OpenRouter image-model decision tree.
|
|
|
|
The tree and the prices come from the intelligence-radar digest dated
|
|
2026-10-08 (``2026-10-08-1527_Image_Generation_with_Generative_AI_Digest``),
|
|
recorded for callers in ``docs/image-model-selection.md``. Ranks and prices
|
|
move. This module is the machine-readable copy of that snapshot, not a live
|
|
leaderboard.
|
|
|
|
Hall of Helix portraits are general generation: square technical illustration,
|
|
no logos, no readable text, and many of them. Quality still matters, so the
|
|
hall tier is Efficient, not Frontier and not bulk.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import re
|
|
from dataclasses import dataclass
|
|
from typing import Any
|
|
|
|
from llm_connect.exceptions import LLMConfigurationError
|
|
|
|
# branch, tier -> model, approximate USD, quote USD, per-call cap USD
|
|
_CHOICES: dict[tuple[str, str], tuple[str, float, float, float]] = {
|
|
("vector", "styles"): ("recraft/recraft-v4-styles-vector", 0.05, 0.06, 0.10),
|
|
("vector", "default"): ("recraft/recraft-v4.1-vector", 0.08, 0.09, 0.15),
|
|
("vector", "pro"): ("recraft/recraft-v4.1-pro-vector", 0.30, 0.32, 0.40),
|
|
("editing", "frontier"): ("openai/gpt-image-2.5-sunburst", 0.21, 0.25, 0.30),
|
|
("editing", "efficient"): ("microsoft/mai-image-2.6", 0.039, 0.045, 0.08),
|
|
("editing", "budget"): ("meta/muse-image", 0.010, 0.012, 0.03),
|
|
("text_heavy", "frontier"): ("openai/gpt-image-2.5-sunburst", 0.21, 0.25, 0.30),
|
|
("text_heavy", "grounded"): ("google/gemini-nano-banana-2.1", 0.034, 0.04, 0.08),
|
|
("brand", "default"): ("recraft/recraft-v4-styles", 0.035, 0.04, 0.08),
|
|
("brand", "pro"): ("recraft/recraft-v4-styles-pro", 0.10, 0.12, 0.16),
|
|
("general", "frontier"): ("openai/gpt-image-2.5-sunburst", 0.21, 0.25, 0.30),
|
|
("general", "frontier_fast"): ("openai/gpt-image-2.5-flare", 0.21, 0.25, 0.30),
|
|
("general", "efficient"): ("google/gemini-nano-banana-2.1", 0.034, 0.04, 0.08),
|
|
("general", "efficient_fallback"): ("microsoft/mai-image-2.6", 0.039, 0.045, 0.08),
|
|
("general", "budget"): ("meta/muse-image", 0.010, 0.012, 0.03),
|
|
("general", "bulk"): ("recraft/recraft-v4.1-flash", 0.007, 0.008, 0.02),
|
|
}
|
|
|
|
_DEFAULT_TIER = {
|
|
"vector": "default",
|
|
"editing": "frontier",
|
|
"text_heavy": "frontier",
|
|
"brand": "default",
|
|
"general": "efficient",
|
|
}
|
|
|
|
# Many hall portraits, and the portrait is what the public post shows.
|
|
HALL_GENERAL_TIER = "efficient"
|
|
|
|
_SELF_HOST_RE = re.compile(r"\b(self-host(?:ed|ing)?|open weights|on our own hardware)\b", re.I)
|
|
_VECTOR_RE = re.compile(r"\b(svg|vector output|vector-style|as a vector)\b", re.I)
|
|
_EDIT_RE = re.compile(r"\b(edit the existing|inpaint|image-to-image|change the attached image)\b", re.I)
|
|
_BRAND_RE = re.compile(r"\b(brand kit|brand style|style system)\b", re.I)
|
|
_NEGATED_TEXT_RE = re.compile(
|
|
r"\b(?:no|not|without|zero)\b(?:\s+\w+){0,5}\s+(?:readable\s+)?(?:text|lettering|glyphs?)\b",
|
|
re.I,
|
|
)
|
|
_TEXT_RE = re.compile(
|
|
r"\b(readable text|typography|caption|headline|lettering|signage|the words)\b",
|
|
re.I,
|
|
)
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class ImageSelection:
|
|
"""One branch of the OpenRouter image tree."""
|
|
|
|
branch: str
|
|
tier: str
|
|
model: str
|
|
approx_usd: float
|
|
quote_usd: float
|
|
max_cost_usd: float
|
|
reason: str
|
|
|
|
def to_dict(self) -> dict[str, Any]:
|
|
return {
|
|
"branch": self.branch,
|
|
"tier": self.tier,
|
|
"model": self.model,
|
|
"approx_usd": self.approx_usd,
|
|
"quote_usd": self.quote_usd,
|
|
"max_cost_usd": self.max_cost_usd,
|
|
"reason": self.reason,
|
|
}
|
|
|
|
|
|
def classify_image_branch(prompt: str) -> str:
|
|
"""Walk the digest tree in order. The first match wins."""
|
|
if _SELF_HOST_RE.search(prompt):
|
|
return "self_host"
|
|
if _VECTOR_RE.search(prompt):
|
|
return "vector"
|
|
if _EDIT_RE.search(prompt):
|
|
return "editing"
|
|
without_negations = _NEGATED_TEXT_RE.sub(" ", prompt)
|
|
# A prompt that forbids readable text is not the text-heavy branch, even
|
|
# when it mentions words as something the picture must not contain.
|
|
forbids_text = _NEGATED_TEXT_RE.search(prompt) is not None
|
|
if _TEXT_RE.search(without_negations) and not forbids_text:
|
|
return "text_heavy"
|
|
if _BRAND_RE.search(prompt):
|
|
return "brand"
|
|
return "general"
|
|
|
|
|
|
def choose_image_model(branch: str, tier: str | None = None) -> ImageSelection:
|
|
"""Return the priced OpenRouter model for one branch and tier."""
|
|
if branch == "self_host":
|
|
raise LLMConfigurationError(
|
|
"Self-hosted or open-weight image generation is not available through OpenRouter",
|
|
context={"branch": branch},
|
|
)
|
|
if branch not in _DEFAULT_TIER:
|
|
known = ", ".join(sorted(_DEFAULT_TIER))
|
|
raise LLMConfigurationError(
|
|
f"Unknown image branch {branch!r}. Choose from: {known}",
|
|
context={"branch": branch},
|
|
)
|
|
selected_tier = tier or _DEFAULT_TIER[branch]
|
|
choice = _CHOICES.get((branch, selected_tier))
|
|
if choice is None:
|
|
known = ", ".join(sorted(item[1] for item in _CHOICES if item[0] == branch))
|
|
raise LLMConfigurationError(
|
|
f"Unknown tier {selected_tier!r} for branch {branch!r}. Choose from: {known}",
|
|
context={"branch": branch, "tier": selected_tier},
|
|
)
|
|
model, approx, quote, cap = choice
|
|
reason = (
|
|
f"{branch}/{selected_tier} from the 2026-10-08 OpenRouter image tree; "
|
|
f"quote {quote:.3f} USD against about {approx:.3f} USD per 1024 square image"
|
|
)
|
|
return ImageSelection(
|
|
branch=branch,
|
|
tier=selected_tier,
|
|
model=model,
|
|
approx_usd=approx,
|
|
quote_usd=quote,
|
|
max_cost_usd=cap,
|
|
reason=reason,
|
|
)
|
|
|
|
|
|
def selection_for_prompt(prompt: str, *, tier: str | None = None) -> ImageSelection:
|
|
"""Classify a prompt, then price it.
|
|
|
|
``tier`` overrides only the general branch. Specialist branches keep the
|
|
tree's own default, because a hall portrait that asks for readable text
|
|
is no longer an Efficient general image.
|
|
"""
|
|
branch = classify_image_branch(prompt)
|
|
if branch == "general":
|
|
return choose_image_model(branch, tier or HALL_GENERAL_TIER)
|
|
return choose_image_model(branch, None)
|