AI 开发教程

AI 图片生成接口实战:从提示词到保存图片的完整工作流

构建可复现的 AI 图片生成流程,学习提示词结构、Python 接口调用、Base64 图片保存、失败排查与博客封面质量检查。

光粒子汇聚成玻璃画框中的海岸风景

生成一张图片很容易,持续生成符合用途、尺寸和风格要求的图片却需要一套工作流。提示词只是其中一步:你还要确认接口能力、处理返回数据、检查画面,再把结果变成可发布的资产。

本文以“为 AI 技术博客制作封面底图”为例,介绍从需求到落盘的完整过程。接口示例采用 Images API 的生成路径,且只处理明确返回 Base64 图片的服务。是否支持图片生成、哪些模型可用、参数如何填写,都必须由你所使用的服务文档确认。文本聊天接口可用,不代表图片接口也可用。

一、先把用途写清楚

在写提示词之前,先定义交付物:

项目 本次封面需求
用途 AI API 技术文章封面底图
主体 抽象的请求与响应连接关系
风格 克制的科技插画,画面结构清楚
构图 主体在右侧,左侧留给后续标题
文字 图片中不生成标题、品牌和水印
色彩 深蓝背景,少量青色和暖色强调
输出 保存原图,再制作网站使用的压缩版本

最终宽高比由网站模板决定。提示词写“横向”不等于接口会返回横向像素尺寸,尺寸必须通过模型支持的参数或后期裁切控制。

先做无文字底图,再用排版工具叠加标题,通常更容易保证中文内容准确,也便于修改文章标题。若业务必须直接在图中生成文字,则应单独检查每个字和裁切效果。

二、用六个要素组织提示词

一种便于复用的顺序是:主体、关系、构图、风格、光色、约束。它的作用是把需求拆成可评估的项,而不是保证每个模型都会完全遵循指令。

下面是一份完整封面提示词,可以保存为 UTF-8 编码的 prompt.txt:

为一篇介绍 AI API 调用流程的技术博客制作封面底图。
画面主体是抽象的客户端节点、接口网关与模型节点,
节点之间用清晰的线条与少量发光数据粒子连接,形成有秩序的请求与响应关系。
采用简洁的三维科技插画风格,深蓝色背景,青色边缘光,少量暖橙色强调。
主体安排在画面右侧,左侧保留大面积干净的深色留白,便于后期叠加标题。
背景低细节,视觉焦点明确,避免杂乱密集的电路装饰。
不要生成文字、字母、数字、Logo 或水印;不要描绘真实软件界面或密钥。

“高级、震撼、超清”没有告诉模型如何安排画面。相比堆叠形容词,明确主体位置和留白用途更容易形成可以检查的结果。

不要把所有限制都当成独立的 negative_prompt 参数。很多接口没有这个字段,可以把约束写进正常提示词;只有文档明确支持时才添加专门参数。

三、确认图片接口的四项能力

调用前应检查:接口路径、模型 ID、尺寸与输出格式、同步或异步返回方式。

OpenAI 官方同时提供 Images API 与通过其他 API 使用图片生成能力的方式,不能直接互换请求体。本教程采用单次生成路径,结构可对照 图片生成接口参考。第三方服务若返回任务 ID,就需要按其协议轮询任务状态,不能立即读取 data[0]。

本例要求返回 data[0].b64_json。有些服务或模型返回图片 URL,另一些返回 Base64;请先确认自己的返回契约。不要无条件添加 response_format、quality 或 output_format,这些字段的可用性随模型和实现而变化。

四、Python:请求生成并保存原图

示例需要 Python 3.10 或更新版本。安装依赖:

python -m pip install requests pillow

配置 AI_BASE_URL、AI_API_KEY、AI_IMAGE_MODEL。Base URL 使用服务提供的 API 根路径,例如某些服务采用 https://api.example.com/v1;该地址只是占位说明,不能直接运行。模型 ID 必须替换成实际支持图片生成且当前账户可用的值。

将前面的提示词保存为 prompt.txt,再把下面代码保存为 generate_image.py,从同一目录运行 python generate_image.py。如果需要特定像素尺寸,通过 AI_IMAGE_SIZE 设置服务明确支持的值;不设置时使用服务默认尺寸。

import base64
import binascii
import hashlib
import io
import json
import os
import uuid
from datetime import datetime, timezone
from pathlib import Path

import requests
from PIL import Image

def main():
    prompt = Path("prompt.txt").read_text(encoding="utf-8").strip()
    if not prompt:
        raise RuntimeError("prompt.txt 不能为空")
    model = os.environ["AI_IMAGE_MODEL"]
    payload = {"model": model, "prompt": prompt, "n": 1}
    size = os.getenv("AI_IMAGE_SIZE", "").strip()
    if size:
        payload["size"] = size

    url = os.environ["AI_BASE_URL"].rstrip("/") + "/images/generations"
    headers = {"Authorization": "Bearer " + os.environ["AI_API_KEY"]}
    with requests.post(
        url, headers=headers, json=payload, timeout=(10, 180)
    ) as response:
        if not response.ok:
            raise RuntimeError(
                f"生成失败:HTTP {response.status_code};请查看脱敏后的错误信息"
            )
        body = response.json()

    items = body.get("data") if isinstance(body, dict) else None
    if not isinstance(items, list) or not items or not isinstance(items[0], dict):
        raise RuntimeError("未获得预期的 data 数组,请核对同步返回协议")
    encoded = items[0].get("b64_json")
    if not isinstance(encoded, str) or not encoded:
        raise RuntimeError("本例只处理 b64_json;请按服务文档处理 URL 或任务响应")
    try:
        raw = base64.b64decode(encoded, validate=True)
    except (binascii.Error, ValueError) as exc:
        raise RuntimeError("图片 Base64 格式无效") from exc

    with Image.open(io.BytesIO(raw)) as image:
        image.verify()
    with Image.open(io.BytesIO(raw)) as image:
        image_format = image.format
        width, height = image.size
    extension = {"PNG": ".png", "JPEG": ".jpg", "WEBP": ".webp"}.get(image_format)
    if extension is None:
        raise RuntimeError(f"未配置保存规则的图片格式:{image_format}")

    output_dir = Path("generated")
    output_dir.mkdir(exist_ok=True)
    task_id = uuid.uuid4().hex
    image_path = output_dir / (task_id + extension)
    image_path.write_bytes(raw)
    metadata = {
        "task_id": task_id,
        "created_at": datetime.now(timezone.utc).isoformat(),
        "model": model,
        "prompt": prompt,
        "request_options": {k: v for k, v in payload.items() if k != "prompt"},
        "format": image_format,
        "width": width,
        "height": height,
        "sha256": hashlib.sha256(raw).hexdigest(),
        "revised_prompt": items[0].get("revised_prompt"),
    }
    (output_dir / (task_id + ".json")).write_text(
        json.dumps(metadata, ensure_ascii=False, indent=2), encoding="utf-8"
    )
    print(f"已保存:{image_path},尺寸:{width}x{height}")

if __name__ == "__main__":
    main()

代码根据实际图片格式选择扩展名,避免把 JPEG 内容写成 .png。旁边的 JSON 用于记录提示词、请求选项与文件摘要,不保存密钥。图片生成中的返回格式差异可参考 官方图片生成指南。

这里的 180 秒读取等待只是示例配置,不是总时限或性能保证。生成失败时没有自动重发:如果服务已完成任务但响应丢失,盲目重试可能产生重复用量。应先查看任务状态或账户记录,再按服务实际能力决定后续操作。

五、如果返回的是图片 URL

不能把 URL 字符串直接当作图片字节写入文件。应按服务文档及时下载,检查 HTTP 状态、内容类型与实际图片解码结果,再保存到自己可管理的存储。

生产后端还需要限制下载协议、主机和响应大小,避免把模型或服务返回的任意地址当成可信下载目标。这里不提供“下载任何 URL”的通用模板,因为服务返回域名和授权方式需要具体配置。

如果 URL 带有授权查询参数,不要公开贴到文章、日志或共享工单中。临时 URL 也不适合作为长期博客图片地址:发布时应使用自己的静态资源或对象存储地址。

六、提示词如何迭代才有意义?

第一轮先检查结构:是否存在左右布局、留白是否够大、主体关系是否可辨认。结构正确后再调整色彩和质感。

假设第一张图左侧仍然太拥挤,第二轮只改构图要求:

保留节点连接的主体与深蓝青色配色。
将所有主体集中在右侧约三分之二区域。
左侧约三分之一区域只保留平整深色背景,不出现节点、线条或光斑。

这段可以用来重写原提示词的构图部分。若服务支持参考图编辑,则可连同原图一起使用对应编辑接口;单纯发送文字不会自动让接口记住上一次图片。

每轮只改变一个主要维度,记录版本和评估结果。同样提示词也可能生成不同画面,不能把两张图片的差异都归因于一个词。需要稳定比较时保留同一模型和参数,生成多个候选;随机种子仅在接口确实支持时使用。

七、发布前用五项标准验收

  1. 主体明确: 缩小到列表卡片尺寸,仍然能辨认视觉焦点。
  2. 留白可用: 叠加实际中文标题,检查字色和背景对比。
  3. 没有明显瑕疵: 检查节点关系、边缘、无意生成的字母或水印。
  4. 裁切合理: 在桌面和手机模板中预览,避免主体被裁掉。
  5. 文件适合网页: 保留原图,另做压缩版本,比较视觉效果与文件体积。

图片替代文本应说明画面内容,例如“深蓝背景中的抽象 API 节点与数据连接线”,而不是堆叠搜索关键词。涉及真实人物、品牌标识或商用内容时,应查看适用的授权与服务规则,不把“AI 生成”当作权利已解决的证明。

八、常见问题

图片接口返回 400,怎么办?

先去掉未确认支持的尺寸、质量和格式字段,保留文档要求的最小请求,再核对模型 ID。不要因为文本接口能调用,就假定模型具备图片生成能力。

返回 200,却没有 b64_json?

检查是否返回 URL、异步任务 ID,或使用了完全不同的响应协议。解析必须与实际接口一致,不能把另一个 API 的 JSON 路径照搬过来。

同一提示词为什么不能完全复现?

生成过程可能具有随机性,服务实现和模型版本也可能变化。记录配置能够追溯生成条件,但不保证逐像素复现。需要固定资产时,保存经过验收的原图,而不是只保存提示词。

把“需求定义 → 接口确认 → 单次生成 → 图片校验 → 单变量迭代 → 发布验收”串起来,就能让图片生成从临时尝试变成可维护的内容生产流程。