Tutoriale
Costruire un potenziatore di prompt per immagini AI: espansione dei prompt tramite LLM per ideogram-v4 e flux-2
I prompt brevi generano solo immagini generiche. "coffee shop logo" non fornisce quasi nessuna informazione utile al modello: nessun nome da rendere, nessuno stile, nessuna composizione, nessuna palette di colori. Le pipeline moderne di generazione di immagini risolvono questo problema con l'espansione dei prompt: prima che il prompt raggiunga l'API di generazione immagini, una singola chiamata economica a un LLM trasforma un prompt di cinque parole in una descrizione ricca e adatta alle caratteristiche del modello. Alcune piattaforme ormai integrano l'espansore direttamente in modelli specifici, il che di per sé dimostra quanto questo passaggio di pre-elaborazione sia comune.
Questo tutorial costruirà quel passaggio da zero, sotto forma di una piccola pipeline Python: un LLM arricchisce il prompt breve dell'utente, poi il risultato viene inviato a ideogram-v4 o flux-2 tramite l'API unificata per le attività di hiapi. Puoi controllare le regole di espansione per modello: è importante, perché gli elementi che fanno brillare un prompt per Ideogram (testo esatto racchiuso tra virgolette) non sono gli stessi che fanno brillare un prompt per FLUX.2 (scena, illuminazione, fotocamera).
Prerequisiti: una chiave API di hiapi (da creare nella console), Python 3.10+ e requests (pip install requests), e un endpoint LLM compatibile con chat-completions da usare per il passaggio di espansione (un modello piccolo/economico è sufficiente: l'espansore scrive solo circa 100 parole per volta).
Perché espandere i prompt?
Su hiapi ci sono due modelli con un interruttore di espansione integrato: qwen-image-2.0 ha prompt_extend, flux-1.1-pro ha prompt_upsampling. Quando sono attivi, la piattaforma arricchisce il prompt lato server.
Ma ideogram-v4 e flux-2/text-to-image — rispettivamente i due modelli più forti in fatto di tipografia e di aderenza al prompt — non hanno un espansore integrato. Il loro schema di input accetta solo la stringa del prompt e i parametri di generazione, nient'altro. Se vuoi prompt più ricchi su questi modelli, devi portare tu il passaggio di espansione. Il vantaggio: il tuo espansore può essere consapevole del modello, consapevole del brand e soprattutto debuggabile. Vedi esattamente quale stringa viene inviata.
L'espansore: un system prompt per ogni famiglia di modelli
Tutto il trucco sta in una funzione che riceve una stringa e restituisce una stringa. Di seguito la chiamata LLM indipendente dal fornitore: punta LLM_BASE_URL / LLM_API_KEY / LLM_MODEL su qualsiasi endpoint compatibile con chat-completions che stai già eseguendo:
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()
Poiché la pipeline richiede solo str -> str, puoi sostituirlo con qualsiasi espansore: un LLM più grande, un modello messo a punto, o persino un template deterministico per campagne di brand fisse.
Prima e dopo
Input: coffee shop logo
Dopo l'espansione per ideogram-v4:
Un logo a badge circolare minimalista per una torrefazione artigianale chiamata "Driftwood Coffee": la scritta "DRIFTWOOD COFFEE" in un carattere serif maiuscolo pulito circonda una semplice tazza di caffè stilizzata, con tre linee di vapore. Sfondo color crema caldo, testo e tratti in marrone espresso scuro, stile vettoriale piatto, alto contrasto, composizione centrata, ampio spazio bianco intorno.
Dopo l'espansione per flux-2:
Interno accogliente di una torrefazione artigianale durante l'ora d'oro: luce solare calda che filtra attraverso ampie vetrine sul bancone in legno di noce, un barista che crea latte art in una tazza di ceramica, mensole dietro piene di barattoli di vetro ambrato e sacchi di caffè in tela di iuta, vapore delicato che si alza, profondità di campo ridotta, resa fotografica da 35mm, tonalità calde e accoglienti, messa a fuoco nitida sulla tazza.
La stessa intenzione di cinque parole, due prompt completamente diversi — e ciascuno che centra in pieno il proprio obiettivo. La versione per Ideogram mette tra virgolette il testo esatto da rendere (la sua specialità); la versione per FLUX.2 concentra l'attenzione sulla scena e sulla luce (la sua specialità).
Pipeline completa: espansione, creazione del task, polling, download
Tutti i modelli di generazione di hiapi si chiamano tramite un unico endpoint asincrono: POST /v1/tasks crea il task, GET /v1/tasks/<id> interroga lo stato del task, e l'artefatto finale compare in 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"))
Eseguilo con:
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
Attenzione allo schema di input: cambia da modello a modello
L'API per le attività convalida input rigorosamente in base al modello: i campi sconosciuti vengono rifiutati, non ignorati. Se invii a ideogram-v4 un campo che non riconosce:
{
"code": 400,
"error_code": "INVALID_REQUEST",
"message": "invalid input: <root>: additional properties 'size' not allowed"
}
In questa pipeline ci sono tre differenze da notare:
rendering_speedè obbligatorio su ideogram-v4 (TURBO/BALANCED/QUALITY: ogni livello ha un prezzo diverso). flux-2 non ha questo campo.- I menu delle proporzioni non coincidono. ideogram-v4 offre cinque preset da circa 1MP (
1:1,16:9,4:3,9:16,3:4); flux-2 offre un insieme più ampio, piùcustomconwidth/heightespliciti (da 256 a 2048, multipli di 16). Se la tua applicazione lascia che l'utente scelga le proporzioni, mappale per modello prima dell'invio. - flux-2 definisce le dimensioni in livelli di megapixel (
resolution: da'0.5 MP'a'4 MP') e non ha prompt negativo: un altro motivo per cui l'espansore dovrebbe sempre formulare in positivo.
Rafforzamento per la produzione
Ad alto volume preferisci i callback al polling. Aggiungendo un blocco callback alla creazione del task, hiapi ti invierà una notifica POST allo stato terminale, senza che tu debba fare polling:
payload = {
"model": "flux-2/text-to-image",
"input": input_obj,
"callback": {"url": "https://your-domain.com/hiapi/callback", "when": "final"},
}
Il polling è più semplice ed è adatto a script locali, processi cron e volumi bassi; i callback evitano di sprecare richieste su generazioni lunghe e notificano esattamente una volta per task. Uno schema di produzione comune è usare i callback come canale principale e un polling di riconciliazione lento come riserva.
Rendi i retry idempotenti. Invia l'header Idempotency-Key alla creazione del task (massimo 255 byte, conservato per 24 ore). Ripetere con la stessa key restituisce il taskId originale (con l'header di risposta Idempotent-Replay: true) invece di creare — e fatturare — un task duplicato. Riusare la key con un corpo della richiesta diverso restituisce 422 IDEMPOTENCY_KEY_MISMATCH; se un retry entra in conflitto con la richiesta originale ancora in corso, restituisce 409 con Retry-After. Deriva la key dal tuo ID task:
headers = {**HEADERS, "Idempotency-Key": f"expand-pipeline-{job_id}"}
Gestisci esplicitamente gli errori di autenticazione. Una key non valida o senza i permessi necessari fallisce con HTTP 401, con "code": "permission_denied" nel corpo dell'errore: intercettalo separatamente dagli errori di generazione, perché riprovare non serve:
if r.status_code == 401:
raise SystemExit("chiave hiapi non valida o senza accesso a questo modello")
Persisti subito gli output. Gli URL di output dei task hanno una scadenza. Appena un task va a buon fine, scarica i byte e archiviali per conto tuo.
Tieni a freno l'espansore. Limita l'espansione a circa 120 parole, passa temperature ≤ 0.8 e registra nei log sia il prompt breve sia quello espanso insieme al taskId. Quando il risultato della generazione sembra sbagliato, la prima domanda è sempre "cosa ha effettivamente ricevuto il modello?": con un espansore tuo puoi rispondere.
Occhio ai costi cumulativi. Ora paghi due chiamate per ogni immagine: un completion su un LLM piccolo (di solito trascurabile) più la generazione dell'immagine — ideogram-v4 fattura per immagine in base al livello di rendering_speed, flux-2 fattura per immagine in base al livello di risoluzione. Le tariffe attuali di entrambi sono nella pagina dei prezzi.
Cosa fare adesso
- Dettagli del modello e parametri: le pagine dei modelli ideogram-v4 e flux-2
- Dove la piattaforma ha già un espansore integrato, puoi saltare l'espansore fai-da-te: qwen-image-2.0 (
prompt_extend) e flux-1.1-pro (prompt_upsampling) - Riferimento dell'API delle attività e altre guide di integrazione: documentazione
- Confronta il prezzo per immagine dei vari modelli: prezzi
Domande frequenti
Si può inviare lo stesso prompt espanso a entrambi i modelli? Sì, ma a scapito della qualità. Ideogram predilige il testo letterale tra virgolette e il linguaggio del layout; FLUX.2 predilige dettagli di scena, illuminazione e fotocamera. Mantenere un system prompt dell'espansore per ogni famiglia di modelli costa solo qualche riga di codice, ma ripaga a ogni generazione.
hiapi ha un modello che espande il prompt al posto mio?
Sì: qwen-image-2.0 accetta prompt_extend, flux-1.1-pro accetta prompt_upsampling. Per ideogram-v4 e flux-2/text-to-image non c'è un espansore integrato: è proprio lo scenario a cui serve questo tutorial.
Quanto deve essere lungo un prompt espanso? Per questa pipeline le 60-120 parole sono il punto ideale. Oltre si diluisce il segnale, e alcuni modelli economici troncano bruscamente i prompt lunghi (per esempio flux-schnell tronca intorno ai 256 token). La densità batte la lunghezza: ogni frase aggiunta deve vincolare l'immagine.
flux-2 supporta i prompt negativi? No. Esprimi ciò che vuoi, non ciò che non vuoi, e inserisci questa regola nel system prompt dell'espansore, così non produrrà mai formule come "no text, no watermark" che il modello leggerebbe come contenuto.
E se l'LLM cambia l'intenzione dell'utente? Vincolalo nel system prompt ("keep the user's intent exactly"), tieni una temperature moderata e registra il confronto prima/dopo. Per i flussi ad alto rischio, mostra all'utente il prompt espanso prima della generazione, oppure offri un interruttore "usa il mio prompt così com'è" per scavalcare l'espansore.
Perché la mia richiesta fallisce con 400 prima ancora che avvenga qualsiasi generazione?
Validazione dello schema. Ogni modello convalida rigorosamente input: un campo obbligatorio mancante (come rendering_speed su ideogram-v4) o un campo sconosciuto fa fallire subito con error_code: "INVALID_REQUEST" e un messaggio che indica il campo non valido. Questi errori non vengono fatturati; correggi i campi e invia di nuovo.
Articoli correlati
Vedi tutti gli articoli →-
Tutoriale
Come chiamare hailuo-2.3-fast/image-to-video tramite l'API HiAPI: pratica con curl e Python
La prima richiesta funzionante per il livello image-to-video veloce ed economico — dall'ID del modello al download di un MP4
-
Tutoriale
Come chiamare hailuo-2.3/text-to-video tramite l'API HiAPI: curl, Python ed esempi di richiesta eseguibili
-
Tutoriale
Utilizzare Kandinsky 6.0 Pro su HiAPI per la generazione di video da testo: tutorial con curl e Python
Dimostrazione pratica guidata dalla richiesta: costruire la chiamata da prompt a clip video, inviare la richiesta, monitorare lo stato di completamento e scaricare il risultato