HiAPI
博客

教程

构建 AI 图像提示词增强器:面向 ideogram-v4 和 flux-2 的 LLM 提示词扩写

2026年11月3日
构建 AI 图像提示词增强器:面向 ideogram-v4 和 flux-2 的 LLM 提示词扩写

简短的提示词只能生成泛泛的图像。“coffee shop logo” 几乎没给模型任何可用的信息——没有要渲染的名称,没有风格,没有构图,没有配色。现代图像流水线用提示词扩写解决这个问题:在提示词进入图像 API 之前,先用一次成本低廉的 LLM 处理,把五个词的提示词变成一段丰富的、符合模型特性的描述。有些平台现在已把扩写器内置到特定模型中,这本身就说明这一预处理步骤有多普遍。

本教程将自行构建这一步,做成一个小型 Python 流水线:由 LLM 丰富用户的简短提示词,再通过 hiapi 的统一任务 API 把结果发给 ideogram-v4 或 flux-2。你可以按模型控制扩写规则——这很重要,因为让 Ideogram 提示词出彩的要素(用引号包裹的精确文字)与让 FLUX.2 提示词出彩的要素(场景、光照、镜头)并不相同。

前置条件:一个 hiapi API key(在控制台创建)、Python 3.10+ 及 requests(pip install requests),以及一个可用于扩写步骤的、兼容 chat-completions 的 LLM 端点(小模型/便宜模型就足够——扩写器每次只写约 100 词)。

为什么要扩写提示词?

hiapi 上有两个模型自带扩写开关:qwen-image-2.0 有 prompt_extend,flux-1.1-pro 有 prompt_upsampling。打开后平台会在服务端丰富提示词。

但 ideogram-v4 和 flux-2/text-to-image——分别是排版和提示词遵循方面最强的两个模型——没有内置扩写器。它们的输入 schema 只接受提示词字符串和生成参数,别无其他。如果要在这些模型上获得更丰富的提示词,你需要自带扩写步骤。好处是:你自己的扩写器能感知模型、感知品牌,而且可调试。你能确切看到发送的是哪个字符串。

扩写器:每个模型家族一个系统提示词

整个技巧就是一个字符串进、字符串出的函数。下面是与服务商无关的 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" 字样以干净的大写衬线字体环绕一个简单的线条咖啡杯,杯上有三条蒸汽线。温暖的奶油色背景,深浓缩咖啡棕色的文字和线条,扁平矢量风格,高对比度,居中构图,四周留白充足。

为 flux-2 扩写后:

黄金时刻下一家温馨的特色咖啡店内景,温暖的阳光透过宽大的前窗洒在胡桃木吧台上,一位咖啡师正在陶瓷杯中拉花,身后架子上摆满琥珀色玻璃罐和麻布咖啡豆袋,柔和的蒸汽升腾,浅景深,35mm 摄影质感,温暖宜人的色调,焦点清晰落在杯子上。

同样的五个字符的意图,两个完全不同——且各自精准命中——的提示词。Ideogram 版本用引号标出要渲染的精确文字(它的专长);FLUX.2 版本则把笔墨花在场景和光线(它的专长)上。

完整流水线:扩写、创建任务、轮询、下载

所有 hiapi 生成模型都通过一个异步端点调用: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

注意输入 schema——每个模型都不同

任务 API 严格按模型校验 input,未知字段会被拒绝,而不是忽略。如果你给 ideogram-v4 发送它不认识的字段:

{
  "code": 400,
  "error_code": "INVALID_REQUEST",
  "message": "invalid input: <root>: additional properties 'size' not allowed"
}

本流水线中有三处差异值得注意:

  • rendering_speed 在 ideogram-v4 上是必填的(TURBO/BALANCED/QUALITY——不同档位定价不同)。flux-2 没有这个字段。
  • 宽高比菜单不一致。 ideogram-v4 提供五档约 1MP 预设(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 任务和低并发量;回调则避免在长时间生成上浪费请求,并且每个任务恰好通知一次。常见的生产模式是以回调为主通道,以慢速对账轮询为备份。

让重试幂等。创建任务时发送 Idempotency-Key 头(最多 255 字节,保留 24 小时)。用相同 key 重试会返回原始 taskId(响应头带 Idempotent-Replay: true),而不会创建——并计费——一个重复任务。用不同的请求体复用 key 会返回 422 IDEMPOTENCY_KEY_MISMATCH;重试与进行中的原始请求发生竞态则返回 409 及 Retry-After。从你自己的任务 ID 派生 key:

headers = {**HEADERS, "Idempotency-Key": f"expand-pipeline-{job_id}"}

显式处理认证失败。无效或权限不足的 key 会以 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 一起记录日志。当生成结果看起来不对时,第一个问题永远是“模型实际收到了什么?”——有了自己的扩写器,你就能回答它。

留意成本叠加。现在每张图你都要为两次调用付费:一次小型 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 参考和更多集成指南:文档
  • 比较各模型的每图定价:定价

常见问题

可以把同一个扩写后的提示词发给两个模型吗? 可以,但会牺牲质量。Ideogram 青睐用引号包裹的字面文字和布局语言;FLUX.2 青睐场景、光照和镜头细节。为每个模型家族保留一个扩写器系统提示词,成本只有几行代码,却在每次生成时都有回报。

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 token 处截断)。密度胜过长度:每一个新增短语都应约束图像。

flux-2 支持负面提示词吗? 不支持。表述你想要什么,而不是你不想要什么——并把这条规则写进扩写器的系统提示词,使它永远不会输出 “no text, no watermark” 这类会被模型当作内容读取的措辞。

如果 LLM 改变了用户的意图怎么办? 在系统提示词中加以约束(“keep the user's intent exactly”),保持适中的 temperature,并记录前后对照。对于高风险流程,在生成前向用户展示扩写后的提示词,或提供一个“原样使用我的提示词”开关以绕过扩写器。

为什么我的请求在任何生成发生之前就以 400 失败? Schema 校验。每个模型严格校验 input——缺少必填字段(如 ideogram-v4 上的 rendering_speed)或出现未知字段都会快速失败,返回 error_code: "INVALID_REQUEST" 以及指明违规字段的消息。这些失败不收费;修正字段后重新提交即可。

立即开始构建持久 AI 产物

获取 API 密钥,通过一个基于任务(task)的 API 调用图像、视频和音频模型。

阅读 API 文档