Images¶
majordomo-llm treats image understanding and image generation as two different capabilities.
Image understanding¶
Image understanding changes the input while retaining the normal text, JSON, structured-output, streaming, retry, cascade, and cost contracts.
from pathlib import Path
from majordomo_llm import ImageInput, get_llm_instance
llm = get_llm_instance("gemini", "gemini-3.8-flash")
response = await llm.get_response(
"What objects are visible?",
images=(ImageInput(Path("photo.webp").read_bytes(), "image/webp"),),
)
Anthropic, OpenAI, and Gemini support image inputs. A model without that
capability raises InputModalityUnsupported before network I/O. In a cascade,
an incompatible member is skipped and the next compatible member is attempted.
Only in-memory bytes are accepted. The library does not fetch image URLs. Valid
media types are image/jpeg, image/png, image/gif, and image/webp.
Hooks continue to inspect and modify the text prompt only. Request logging stores image MIME type, byte length, and SHA-256 but never raw bytes.
Image generation and editing¶
Generated image bytes use a separate interface because output and pricing differ
from LLMResponse.
from pathlib import Path
from majordomo_llm import get_image_instance
model = get_image_instance("gemini", "gemini-3.1-flash-image")
response = await model.generate(
"A clean editorial illustration of a coastal observatory",
aspect_ratio="16:9",
image_size="2K",
)
Path("observatory.jpg").write_bytes(response.images[0].data)
ImageResponse includes generated images, modality-specific token usage, input
and output costs, latency, provider, and model. Provider-specific unsupported
options fail explicitly rather than being silently ignored.
OpenAI supports masks and multiple outputs. Gemini uses conversational reference images, supports one JPEG output per request, and does not expose masks, background, or quality controls through this interface.
Generation failover¶
ImageCascade accepts image provider/model pairs in priority order and implements
the same generate() and edit() interface as ImageModel. A child completes its
own retry policy before the cascade advances. Provider failures, invalid provider
responses, and ImageOptionUnsupported capability mismatches trigger failover.
Caller validation errors and broad configuration errors propagate immediately.
The returned ImageResponse.provider and .model identify the child that actually
succeeded, not the cascade or its primary model.
Generation logging¶
LoggingImageModel wraps any ImageModel, including ImageCascade, and writes
cost, latency, aggregate token usage, status, and errors through the existing
logging adapters without delaying the provider response. On cascade success, the
database row identifies the child provider and model that returned the image.
Optional request/response body storage contains modality-specific usage and image metadata (MIME type, byte length, SHA-256, and revised prompt). Raw reference, mask, and generated image bytes are deliberately never duplicated into logs.
Generation hooks¶
Image generation uses a typed ImageHookPipeline rather than encoding binary
responses into the text-only HookPipeline. Before hooks receive an immutable
ImageHookRequest; after hooks receive the request plus ImageResponse. Outcomes
can pass, warn, block, replace the request or response, or request the next cascade
provider. Hook exceptions are recorded as pass verdicts so faulty policy code does
not break provider calls, matching the text-hook contract.
Three provider-neutral hooks are included:
ImagePromptRegexHookblocks, warns, or redacts prompt matches before generation.ImageRequestLimitsHookenforces output count, reference count, total input bytes, and an optional size allowlist before generation incurs cost.ImageIntegrityHookuses Pillow to decode inputs and outputs, verifies that MIME declarations match decoded formats, and enforces a pixel ceiling. Invalid inputs block; invalid provider outputs request cascade failover.
caller_metadata is available in both hook phases. A cascade runs its before hooks
once, then runs after hooks against each successful provider response until one
passes or the cascade is exhausted. Ordinary hook blocks never trigger failover.