チュートリアル
AI画像プロンプトエンハンサーの構築:ideogram-v4とflux-2向けのLLMプロンプト拡張
短いプロンプトでは平凡な画像しか生成できません。「coffee shop logo」はモデルに使える情報をほとんど与えていません——描画すべき名称も、スタイルも、構図も、配色もありません。現代の画像パイプラインではプロンプト拡張でこの問題を解決します。プロンプトが画像APIに渡る前に、低コストなLLMによる処理を1回挟み、5語のプロンプトをリッチでモデル特性に合った記述へと変えるのです。一部のプラットフォームでは現在、特定モデルに拡張機能を組み込んでおり、この前処理ステップがいかに一般的になったかを物語っています。
本チュートリアルではこのステップを自作し、小さなPythonパイプラインに仕上げます。LLMがユーザーの短いプロンプトを豊かにし、その結果をhiapiの統一タスクAPI経由でideogram-v4またはflux-2に送信します。モデルごとに拡張ルールを制御できます——これは重要です。Ideogramのプロンプトを輝かせる要素(引用符で囲んだ正確なテキスト)と、FLUX.2のプロンプトを輝かせる要素(シーン、ライティング、カメラ)は異なるからです。
前提条件:hiapiのAPIキー(コンソールで作成)、Python 3.10+とrequests(pip install requests)、そして拡張ステップに使えるchat-completions互換のLLMエンドポイント(小型/安価なモデルで十分——拡張器は毎回約100語を書くだけです)。
なぜプロンプトを拡張するのか?
hiapiには拡張スイッチを内蔵したモデルが2つあります。qwen-image-2.0にはprompt_extend、flux-1.1-proにはprompt_upsamplingがあります。オンにすると、プラットフォームがサーバー側でプロンプトを豊かにします。
しかしideogram-v4とflux-2/text-to-image——それぞれタイポグラフィとプロンプト追従に最も強い2モデル——には組み込みの拡張器がありません。これらの入力スキーマはプロンプト文字列と生成パラメータのみを受け付け、それ以外は何も受け付けません。これらのモデルでよりリッチなプロンプトを得たいなら、拡張ステップを自分で用意する必要があります。利点は、自作の拡張器がモデル認識・ブランド認識を持ち、デバッグ可能なことです。送信される文字列を正確に確認できます。
拡張器:モデルファミリーごとに1つのシステムプロンプト
コツはすべて、文字列を受け取って文字列を返す関数に集約されます。以下はプロバイダー非依存のLLM呼び出しです——LLM_BASE_URL / LLM_API_KEY / LLM_MODELを、すでに稼働中の任意のchat-completions互換エンドポイントに向けてください:
import os
import requests
LLM_BASE_URL = os.environ["LLM_BASE_URL"] # e.g. https://your-llm-endpoint/v1
LLM_API_KEY = os.environ["LLM_API_KEY"]
LLM_MODEL = os.environ["LLM_MODEL"]
EXPANDER_PROMPTS = {
# ideogram-v4 is a typography specialist: exact strings to render go in
# double quotes, and layout/style language matters more than camera language.
"ideogram-v4": (
"You expand short image prompts for a typography-focused image model. "
"Rules: keep the user's intent exactly; if the image should contain text, "
"put the exact words in double quotes; describe layout (centered, badge, "
"poster), lettering style, palette, and background; flat/vector terms are "
"good; no camera or lens language; output 60-100 words, one paragraph, "
"no preamble."
),
# flux-2 has strong prompt adherence for scenes: subject, setting, lighting,
# mood, camera. It has no negative prompt, so state what you want, not what
# you don't.
"flux-2/text-to-image": (
"You expand short image prompts for a high-fidelity text-to-image model. "
"Rules: keep the user's intent exactly; add subject detail, setting, "
"lighting, mood, composition and camera hints; state desired qualities "
"positively (the model has no negative prompt); output 80-120 words, one "
"paragraph, no preamble."
),
}
def expand_prompt(short_prompt: str, target_model: str) -> str:
"""LLM pass: short user prompt -> enriched, model-appropriate prompt."""
r = requests.post(
f"{LLM_BASE_URL}/chat/completions",
headers={"Authorization": f"Bearer {LLM_API_KEY}"},
json={
"model": LLM_MODEL,
"messages": [
{"role": "system", "content": EXPANDER_PROMPTS[target_model]},
{"role": "user", "content": short_prompt},
],
"temperature": 0.7,
},
timeout=60,
)
r.raise_for_status()
return r.json()["choices"][0]["message"]["content"].strip()
パイプラインはstr -> strだけを必要とするため、任意の拡張器に差し替えられます——より大きなLLM、微調整済みモデル、あるいは固定ブランドキャンペーン向けの決定論的テンプレートでも構いません。
ビフォー・アフター
入力:coffee shop logo
ideogram-v4向けに拡張した後:
「Driftwood Coffee」という名のスペシャルティコーヒーショップのミニマルな円形バッジロゴ。「DRIFTWOOD COFFEE」の文字が、シンプルな線画のコーヒーカップを囲むように、端正な大文字のセリフ体で配置されている。カップからは3本の湯気の線が立つ。温かみのあるクリーム色の背景に、深いエスプレッソブラウンの文字と線。フラットベクタースタイル、高コントラスト、中央揃えの構図、周囲に十分な余白。
flux-2向けに拡張した後:
ゴールデンアワーの、居心地の良いスペシャルティコーヒーショップの店内。温かな日差しが大きな前面の窓からウォールナットのカウンターに降り注ぎ、バリスタが陶器のカップにラテアートを施している。背後には琥珀色のガラス瓶や麻布のコーヒー豆袋が並ぶ棚。柔らかな湯気が立ち上り、浅い被写界深度、35mm写真の質感、温かく心地よい色調、ピントはカップに鮮明に合っている。
同じ5語の意図から、まったく異なる——そしてそれぞれ的確に狙いを定めた——2つのプロンプトが生まれます。Ideogram版は描画すべき正確なテキストを引用符で示し(同モデルの得意分野)、FLUX.2版はシーンと光に筆を尽くします(同モデルの得意分野)。
完全なパイプライン:拡張、タスク作成、ポーリング、ダウンロード
すべてのhiapi生成モデルは1つの非同期エンドポイントで呼び出します:POST /v1/tasksでタスクを作成し、GET /v1/tasks/<id>でタスクをポーリングし、完了した成果物はdata.output[0].urlに現れます。
import json
import os
import time
import requests
HIAPI_KEY = os.environ["HIAPI_API_KEY"]
TASKS = "https://api.hiapi.ai/v1/tasks"
HEADERS = {"Authorization": f"Bearer {HIAPI_KEY}", "Content-Type": "application/json"}
def create_task(model: str, input_obj: dict) -> str:
r = requests.post(TASKS, headers=HEADERS,
json={"model": model, "input": input_obj}, timeout=60)
data = r.json()
task_id = (data.get("data") or {}).get("taskId")
if not task_id:
raise RuntimeError(f"create task failed: {json.dumps(data)[:300]}")
return task_id
def wait_task(task_id: str, timeout_s: int = 300, poll_s: int = 5) -> dict:
deadline = time.time() + timeout_s
while time.time() < deadline:
r = requests.get(f"{TASKS}/{task_id}",
headers={"Authorization": f"Bearer {HIAPI_KEY}"}, timeout=30)
task = r.json().get("data") or {}
if task.get("status") == "success":
return task
if task.get("status") == "fail":
err = task.get("error") or {}
raise RuntimeError(f"task failed: {err.get('code')} {err.get('message')}")
time.sleep(poll_s)
raise TimeoutError(f"task {task_id} still running after {timeout_s}s")
def generate(short_prompt: str, target_model: str) -> str:
expanded = expand_prompt(short_prompt, target_model)
print(f"expanded prompt -> {expanded}\n")
if target_model == "ideogram-v4":
input_obj = {
"prompt": expanded,
"rendering_speed": "BALANCED", # required: TURBO | BALANCED | QUALITY
"aspect_ratio": "1:1", # 1:1, 16:9, 4:3, 9:16, 3:4
}
else: # flux-2/text-to-image
input_obj = {
"prompt": expanded,
"aspect_ratio": "3:2", # 1:1, 16:9, 3:2, 2:3, 4:5, 5:4, 9:16, 3:4, 4:3, custom
"resolution": "1 MP", # '0.5 MP' | '1 MP' | '2 MP' | '4 MP'
}
task_id = create_task(target_model, input_obj)
task = wait_task(task_id)
url = task["output"][0]["url"]
# Output URLs expire — download immediately, don't hot-link them.
img = requests.get(url, timeout=120).content
fname = f"{target_model.replace('/', '-')}.png"
with open(fname, "wb") as f:
f.write(img)
return fname
if __name__ == "__main__":
print(generate("coffee shop logo", "ideogram-v4"))
print(generate("coffee shop logo", "flux-2/text-to-image"))
実行します:
export HIAPI_API_KEY="sk-..."
export LLM_BASE_URL="https://your-llm-endpoint/v1"
export LLM_API_KEY="..."
export LLM_MODEL="your-small-model"
python pipeline.py
入力スキーマに注意——モデルごとに異なる
タスクAPIはモデルに従ってinputを厳密に検証し、未知のフィールドは無視されるのではなく拒否されます。ideogram-v4に認識できないフィールドを送ると:
{
"code": 400,
"error_code": "INVALID_REQUEST",
"message": "invalid input: <root>: additional properties 'size' not allowed"
}
本パイプラインには注目すべき差異が3つあります:
rendering_speedはideogram-v4では必須(TURBO/BALANCED/QUALITY——ティアごとに料金が異なる)。flux-2にはこのフィールドはありません。- アスペクト比のメニューが不揃い。ideogram-v4は約1MPの5つのプリセット(
1:1、16:9、4:3、9:16、3:4)を提供し、flux-2はより広いセットに加えてcustomと明示的なwidth/height(256–2048、16の倍数)を提供します。アプリでユーザーにアスペクト比を選ばせる場合は、ディスパッチ前にモデルごとにマッピングしてください。 - flux-2はメガピクセルティアでサイズを決め(
resolution:'0.5 MP'から'4 MP')、ネガティブプロンプトがありません——これも拡張器が常に肯定的な表現を使うべきもう一つの理由です。
本番環境向けの堅牢化
高負荷時はポーリングよりコールバックを優先。タスク作成時にcallbackブロックを追加すると、hiapiが終了状態でPOST通知してくれ、ポーリング不要になります:
payload = {
"model": "flux-2/text-to-image",
"input": input_obj,
"callback": {"url": "https://your-domain.com/hiapi/callback", "when": "final"},
}
ポーリングはより簡単で、ローカルスクリプト、cronジョブ、低負荷に適しています。コールバックは長時間の生成でリクエストを無駄にせず、各タスクにつき正確に1回通知します。一般的な本番パターンは、コールバックを主経路とし、低速な照合ポーリングをバックアップとすることです。
リトライを冪等に。タスク作成時にIdempotency-Keyヘッダーを送信します(最大255バイト、24時間保持)。同じキーでリトライすると、元のtaskIdが返り(レスポンスヘッダーにIdempotent-Replay: true)、重複タスクを作成——そして課金——することはありません。異なるリクエストボディでキーを再利用すると422 IDEMPOTENCY_KEY_MISMATCHを返し、進行中の元リクエストとリトライが競合すると409とRetry-Afterを返します。キーは自身のタスクIDから導出してください:
headers = {**HEADERS, "Idempotency-Key": f"expand-pipeline-{job_id}"}
認証失敗を明示的に処理。無効または権限不足のキーはHTTP 401で失敗し、エラーボディに"code": "permission_denied"が含まれます——リトライしても無駄なので、生成エラーとは別に捕捉してください:
if r.status_code == 401:
raise SystemExit("hiapi key invalid or lacks access to this model")
出力を即座に永続化。タスクの出力URLには有効期限があります。タスクが成功したらすぐにバイトをダウンロードし、自分で保存してください。
拡張器に手綱を付ける。拡張を約120語以内に制限し、temperatureは0.8以下を渡し、短いプロンプトと拡張後のプロンプトをtaskIdとともにログに記録してください。生成結果がおかしいとき、最初の問いは常に「モデルは実際に何を受け取ったのか?」です——自作の拡張器があれば、それに答えられます。
コストの積み上がりに注意。今や画像1枚につき2回の呼び出しに支払います:小型LLMのcompletion(通常は無視できる程度)と画像生成——ideogram-v4はrendering_speedティアごとに画像単位で課金され、flux-2は解像度ティアごとに画像単位で課金されます。両者の現在の料金は料金ページを参照してください。
次にやるべきこと
- モデル詳細とパラメータ:ideogram-v4とflux-2のモデルページ
- プラットフォームに拡張器が組み込まれている場合はDIY拡張器を省略可能:qwen-image-2.0(
prompt_extend)とflux-1.1-pro(prompt_upsampling) - タスクAPIリファレンスとその他の統合ガイド:ドキュメント
- モデルごとの画像単価を比較:料金
よくある質問
同じ拡張後のプロンプトを2つのモデルに送れますか? はい、できますが品質は犠牲になります。Ideogramは引用符で囲まれたリテラルテキストとレイアウト言語を好み、FLUX.2はシーン、ライティング、カメラの詳細を好みます。モデルファミリーごとに拡張器のシステムプロンプトを1つ用意するコストは数行のコードですが、毎回の生成で元が取れます。
hiapiにプロンプトを代わりに拡張してくれるモデルはありますか?
あります——qwen-image-2.0はprompt_extendを受け付け、flux-1.1-proはprompt_upsamplingを受け付けます。ideogram-v4とflux-2/text-to-imageには組み込みの拡張器がなく、まさに本チュートリアルが当てはまるケースです。
拡張後のプロンプトはどのくらいの長さにすべきですか? 本パイプラインでは60〜120語がスイートスポットです。それを超えるとシグナルが薄まり、一部の低コストモデルは長いプロンプトを強硬に切り詰めます(例えばflux-schnellは約256トークンで切り詰めます)。密度は長さに勝ります。追加されるすべてのフレーズが画像を制約すべきです。
flux-2はネガティブプロンプトをサポートしますか? いいえ。欲しくないものではなく、欲しいものを述べてください——そしてこのルールを拡張器のシステムプロンプトに書き込み、「no text, no watermark」のような、モデルがコンテンツとして読み取ってしまう表現を決して出力させないようにしてください。
LLMがユーザーの意図を変えてしまったら? システムプロンプトで制約し(「keep the user's intent exactly」)、temperatureを適度に保ち、前後の対照を記録してください。高リスクなフローでは、生成前に拡張後のプロンプトをユーザーに表示するか、「自分のプロンプトをそのまま使う」スイッチを提供して拡張器をバイパスできるようにしてください。
なぜ生成が起こる前にリクエストが400で失敗するのですか?
スキーマ検証です。各モデルはinputを厳密に検証します——必須フィールドの欠落(例えばideogram-v4のrendering_speed)や未知のフィールドは即座に失敗し、error_code: "INVALID_REQUEST"と違反フィールドを示すメッセージを返します。これらの失敗は課金されません。フィールドを修正して再送信してください。
関連記事
すべての記事を見る →-
チュートリアル
HiAPI API で hailuo-2.3-fast/image-to-video を呼び出す方法:curl と Python の実践
高速・低コストな画像から動画へのティアにおける最初の実行可能なリクエスト — モデル ID から MP4 のダウンロードまで
-
チュートリアル
HiAPI APIでhailuo-2.3/text-to-videoを呼び出す方法:curl、Python、実行可能なリクエスト例
-
チュートリアル
HiAPI で Kandinsky 6.0 Pro のテキストto動画を使用する:curl と Python チュートリアル
リクエスト優先の実践デモ:プロンプトから動画クリップへの呼び出しを構築し、リクエストを送信し、完了状態をポーリングし、結果をダウンロードする