Instructions to use Infini-Srr/Ideogram-4 with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Diffusers
How to use Infini-Srr/Ideogram-4 with Diffusers:
pip install -U diffusers transformers accelerate
import torch from diffusers import DiffusionPipeline # switch to "mps" for apple devices pipe = DiffusionPipeline.from_pretrained("Infini-Srr/Ideogram-4", dtype=torch.bfloat16, device_map="cuda") prompt = "Astronaut in a jungle, cold color palette, muted colors, detailed, 8k" image = pipe(prompt).images[0] - Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- Draw Things
- DiffusionBee
Preserve pinned official Ideogram 4 FP8 release, components, original terms and reference code
8ebf922 verified Download reference/official-inference/src/ideogram4/magic_prompt.py from Infini-Srr/Ideogram-4: direct link, hf CLI and curl.
- Browser
- Download file 11.8 kB
-
https://huggingface.co/Infini-Srr/Ideogram-4/resolve/main/reference/official-inference/src/ideogram4/magic_prompt.py
- Command line
-
hf download hf://Infini-Srr/Ideogram-4/reference/official-inference/src/ideogram4/magic_prompt.py
-
curl -L -o magic_prompt.py https://huggingface.co/Infini-Srr/Ideogram-4/resolve/main/reference/official-inference/src/ideogram4/magic_prompt.py
11.8 kB
| from __future__ import annotations | |
| import json | |
| import math | |
| from abc import ABC, abstractmethod | |
| from functools import lru_cache | |
| from pathlib import Path | |
| import requests | |
| from ideogram4.caption_verifier import CaptionVerifier | |
| # Directory holding the system-prompt text files shipped with the package. | |
| SYSTEM_PROMPT_DIR = Path(__file__).resolve().parent / "magic_prompt_system_prompts" | |
| OPENROUTER_URL = "https://openrouter.ai/api/v1/chat/completions" | |
| IDEOGRAM_MAGIC_PROMPT_URL = "https://api.ideogram.ai/v1/ideogram-v4/magic-prompt" | |
| class MagicPrompt(ABC): | |
| """A magic-prompt configuration: rewrites a plain prompt into a caption.""" | |
| def expand(self, prompt: str, aspect_ratio: str = "1:1") -> str: | |
| """Rewrite ``prompt`` into the structured caption JSON string. | |
| Args: | |
| prompt: The user's plain-language idea. | |
| aspect_ratio: Target image aspect ratio as ``"W:H"`` (e.g. ``"16:9"``). | |
| Returns: | |
| The structured caption, expected to be a single-line minified JSON object | |
| matching the caption schema (validate with ``CaptionVerifier``). | |
| """ | |
| # --------------------------------------------------------------------------- # | |
| # Shared helpers (not part of the MagicPrompt interface; subclasses call these). | |
| # --------------------------------------------------------------------------- # | |
| def aspect_ratio_from_size(width: int, height: int) -> str: | |
| """Reduce a pixel ``width``x``height`` to a ``"W:H"`` aspect-ratio string.""" | |
| divisor = math.gcd(width, height) or 1 | |
| return f"{width // divisor}:{height // divisor}" | |
| def _load_sections(filename: str) -> dict[str, str]: | |
| """Parse a system-prompt file into its ``[SECTION]`` blocks. | |
| Files use ``[NAME]`` markers alone on a line (``[META]``, ``[SYSTEM]``, | |
| ``[USER]``). Returns a mapping of lower-cased section name to its text body | |
| with surrounding whitespace stripped. Cached so a file is read at most once. | |
| """ | |
| raw = (SYSTEM_PROMPT_DIR / filename).read_text(encoding="utf-8") | |
| sections: dict[str, str] = {} | |
| current: str | None = None | |
| lines: list[str] = [] | |
| for line in raw.splitlines(): | |
| stripped = line.strip() | |
| if stripped.startswith("[") and stripped.endswith("]") and " " not in stripped: | |
| if current is not None: | |
| sections[current] = "\n".join(lines).strip() | |
| current = stripped[1:-1].strip().lower() | |
| lines = [] | |
| else: | |
| lines.append(line) | |
| if current is not None: | |
| sections[current] = "\n".join(lines).strip() | |
| if "system" not in sections: | |
| raise ValueError(f"{filename} has no [SYSTEM] section") | |
| return sections | |
| def build_messages( | |
| system_prompt_file: str, prompt: str, aspect_ratio: str | |
| ) -> list[dict]: | |
| """Build the chat ``messages`` list from a system-prompt file. | |
| The system message is the file's ``[SYSTEM]`` block. The user message comes | |
| from its ``[USER]`` template, substituting ``{{aspect_ratio}}`` and | |
| ``{{original_prompt}}``. If the template has no ``{{original_prompt}}`` | |
| placeholder (or there is no ``[USER]`` block) the prompt is appended after a | |
| default framing line instead. | |
| """ | |
| sections = _load_sections(system_prompt_file) | |
| template = sections.get("user") | |
| if template is None: | |
| template = "TARGET IMAGE ASPECT RATIO: {{aspect_ratio}} (width:height)." | |
| user = template.replace("{{aspect_ratio}}", aspect_ratio) | |
| if "{{original_prompt}}" in user: | |
| user = user.replace("{{original_prompt}}", prompt) | |
| else: | |
| user = f"{user}\n\n{prompt}" | |
| return [ | |
| {"role": "system", "content": sections["system"]}, | |
| {"role": "user", "content": user}, | |
| ] | |
| def _strip_code_fences(text: str) -> str: | |
| """Drop a surrounding ```` ```json ... ``` ```` fence if a model adds one.""" | |
| text = text.strip() | |
| if not text.startswith("```"): | |
| return text | |
| lines = text.splitlines() | |
| if lines and lines[0].startswith("```"): | |
| lines = lines[1:] | |
| if lines and lines[-1].strip() == "```": | |
| lines = lines[:-1] | |
| return "\n".join(lines).strip() | |
| def openrouter_chat( | |
| model: str, | |
| messages: list[dict], | |
| api_key: str | None, | |
| *, | |
| temperature: float | None = 1.0, | |
| max_tokens: int = 16384, | |
| extra_body: dict | None = None, | |
| timeout: float = 120.0, | |
| ) -> str: | |
| """Run one chat completion through OpenRouter and return its text content. | |
| OpenRouter's API is OpenAI-compatible, so ``model`` is an OpenRouter slug | |
| (https://openrouter.ai/models). ``extra_body`` is merged into the request for | |
| provider-specific knobs. The returned text has markdown code fences stripped. | |
| """ | |
| if not api_key: | |
| raise RuntimeError("No API key. Set MAGIC_PROMPT_API_KEY or pass api_key=...") | |
| body = {"model": model, "messages": messages, "max_tokens": max_tokens} | |
| if temperature is not None: | |
| body["temperature"] = temperature | |
| if extra_body: | |
| body.update(extra_body) | |
| resp = requests.post( | |
| OPENROUTER_URL, | |
| headers={ | |
| "Authorization": f"Bearer {api_key}", | |
| "Content-Type": "application/json", | |
| }, | |
| json=body, | |
| timeout=timeout, | |
| ) | |
| resp.raise_for_status() | |
| data = resp.json() | |
| choices = data.get("choices") | |
| if not choices: | |
| raise RuntimeError(f"OpenRouter returned no choices: {data}") | |
| content = choices[0].get("message", {}).get("content") | |
| if not content: | |
| raise RuntimeError(f"OpenRouter returned an empty message: {choices[0]}") | |
| return _strip_code_fences(content) | |
| def _to_ideogram_aspect_ratio(aspect_ratio: str) -> str: | |
| """Convert a ``"W:H"`` ratio to Ideogram's ``"WxH"`` form (``AUTO`` passes through).""" | |
| if aspect_ratio.upper() == "AUTO": | |
| return "AUTO" | |
| return aspect_ratio.replace(":", "x") | |
| def reorder_caption_keys(caption: dict) -> dict: | |
| """Reorder a caption's object keys to the canonical schema order in place. | |
| JSON object key order is semantically irrelevant, but ``CaptionVerifier`` | |
| enforces a canonical order (e.g. elements must be ``type`` before ``desc``). | |
| The hosted magic-prompt API can return keys in a different order, so we | |
| reorder ``style_description``, ``compositional_deconstruction``, and each | |
| element to match. Unknown keys are kept, appended after the known ones. | |
| """ | |
| verifier = CaptionVerifier() | |
| def _ordered(d: dict, order) -> dict: | |
| known = [k for k in order if k in d] | |
| extra = [k for k in d if k not in order] | |
| return {k: d[k] for k in (*known, *extra)} | |
| if not isinstance(caption, dict): | |
| return caption | |
| sd = caption.get("style_description") | |
| if isinstance(sd, dict): | |
| try: | |
| caption["style_description"] = _ordered( | |
| sd, verifier._style_description_key_order(sd) | |
| ) | |
| except ValueError: | |
| pass # ambiguous photo/art_style; leave order untouched for the verifier to flag | |
| cd = caption.get("compositional_deconstruction") | |
| if isinstance(cd, dict): | |
| cd = _ordered(cd, verifier.compositional_deconstruction_key_order) | |
| elements = cd.get("elements") | |
| if isinstance(elements, list): | |
| reordered = [] | |
| for element in elements: | |
| if isinstance(element, dict): | |
| try: | |
| element = _ordered(element, verifier._element_key_order(element)) | |
| except ValueError: | |
| pass # missing/unknown "type"; leave order for the verifier to flag | |
| reordered.append(element) | |
| cd["elements"] = reordered | |
| caption["compositional_deconstruction"] = cd | |
| return caption | |
| def ideogram_magic_prompt( | |
| prompt: str, | |
| aspect_ratio: str, | |
| api_key: str | None, | |
| *, | |
| timeout: float = 120.0, | |
| ) -> str: | |
| """Expand a plain prompt via Ideogram's hosted magic-prompt API. | |
| Unlike the OpenRouter-based configurations, this is a managed service that | |
| performs the prompt expansion server-side, so no system prompt is sent. | |
| ``aspect_ratio`` is Ideogram's ``"WxH"`` form (or ``"AUTO"``). The endpoint | |
| returns ``{"aspect_ratio": ..., "json_prompt": {...}}``; we return the | |
| ``json_prompt`` object as a minified JSON string. | |
| """ | |
| if not api_key: | |
| raise RuntimeError("No API key. Set IDEOGRAM_API_KEY or pass api_key=...") | |
| resp = requests.post( | |
| IDEOGRAM_MAGIC_PROMPT_URL, | |
| headers={ | |
| "Api-Key": api_key, | |
| "Content-Type": "application/json", | |
| }, | |
| json={"text_prompt": prompt, "aspect_ratio": aspect_ratio}, | |
| timeout=timeout, | |
| ) | |
| resp.raise_for_status() | |
| data = resp.json() | |
| json_prompt = data.get("json_prompt") | |
| if not json_prompt: | |
| raise RuntimeError(f"Ideogram API returned no json_prompt: {data}") | |
| json_prompt = reorder_caption_keys(json_prompt) | |
| return json.dumps(json_prompt, ensure_ascii=False, separators=(",", ":")) | |
| def strip_aspect_ratio_and_bboxes(caption: str, *, strip_bboxes: bool = True) -> str: | |
| data = json.loads(caption) | |
| data.pop("aspect_ratio", None) | |
| if strip_bboxes: | |
| elements = data.get("compositional_deconstruction", {}).get("elements", []) | |
| for element in elements: | |
| if isinstance(element, dict): | |
| element.pop("bbox", None) | |
| return json.dumps(data, ensure_ascii=False, separators=(",", ":")) | |
| # --------------------------------------------------------------------------- # | |
| # Concrete configurations. Each subclass pins a model + system-prompt version. | |
| # --------------------------------------------------------------------------- # | |
| class ClaudeSonnetMagicPromptV1(MagicPrompt): | |
| """Magic prompt v1 on Claude Sonnet 4.6, via OpenRouter.""" | |
| def __init__( | |
| self, | |
| api_key: str | None = None, | |
| *, | |
| timeout: float = 120.0, | |
| strip_bboxes: bool = True, | |
| ) -> None: | |
| self.api_key = api_key | |
| self.timeout = timeout | |
| self.strip_bboxes = strip_bboxes | |
| def expand(self, prompt: str, aspect_ratio: str = "1:1") -> str: | |
| messages = build_messages("v1.txt", prompt, aspect_ratio) | |
| caption = openrouter_chat( | |
| "anthropic/claude-sonnet-4.6", | |
| messages, | |
| self.api_key, | |
| temperature=1.0, | |
| extra_body={"reasoning": {"enabled": False}}, | |
| timeout=self.timeout, | |
| ) | |
| return strip_aspect_ratio_and_bboxes(caption, strip_bboxes=self.strip_bboxes) | |
| class ClaudeOpusMagicPromptV1(MagicPrompt): | |
| """Magic prompt v1 on Claude Opus 4.8, via OpenRouter.""" | |
| def __init__( | |
| self, | |
| api_key: str | None = None, | |
| *, | |
| timeout: float = 120.0, | |
| strip_bboxes: bool = True, | |
| ) -> None: | |
| self.api_key = api_key | |
| self.timeout = timeout | |
| self.strip_bboxes = strip_bboxes | |
| def expand(self, prompt: str, aspect_ratio: str = "1:1") -> str: | |
| messages = build_messages("v1.txt", prompt, aspect_ratio) | |
| caption = openrouter_chat( | |
| "anthropic/claude-opus-4.8", | |
| messages, | |
| self.api_key, | |
| temperature=1.0, | |
| extra_body={"reasoning": {"enabled": False}}, | |
| timeout=self.timeout, | |
| ) | |
| return strip_aspect_ratio_and_bboxes(caption, strip_bboxes=self.strip_bboxes) | |
| class Ideogram4MagicPromptV1(MagicPrompt): | |
| """Magic prompt via Ideogram's hosted ideogram-v4 API. | |
| A free, managed service from Ideogram. The expansion runs server-side, so | |
| unlike the OpenRouter configurations there is no system prompt to ship; the | |
| only input is the user's plain prompt. Authenticate with an Ideogram API key | |
| (``IDEOGRAM_API_KEY``). | |
| """ | |
| def __init__( | |
| self, | |
| api_key: str | None = None, | |
| *, | |
| timeout: float = 120.0, | |
| strip_bboxes: bool = True, | |
| ) -> None: | |
| self.api_key = api_key | |
| self.timeout = timeout | |
| self.strip_bboxes = strip_bboxes | |
| def expand(self, prompt: str, aspect_ratio: str = "1:1") -> str: | |
| caption = ideogram_magic_prompt( | |
| prompt, | |
| _to_ideogram_aspect_ratio(aspect_ratio), | |
| self.api_key, | |
| timeout=self.timeout, | |
| ) | |
| return strip_aspect_ratio_and_bboxes(caption, strip_bboxes=self.strip_bboxes) | |
| MAGIC_PROMPTS: dict[str, type[MagicPrompt]] = { | |
| "claude-sonnet-v1": ClaudeSonnetMagicPromptV1, | |
| "claude-opus-v1": ClaudeOpusMagicPromptV1, | |
| "ideogram-4-v1": Ideogram4MagicPromptV1, | |
| } | |
| DEFAULT_MAGIC_PROMPT = "ideogram-4-v1" | |