mcpField
X
xai-grok-imagine-image

xai-grok-imagine-image

github.com/calesthio创意与媒体219106 次安装15 周前更新
代码仓库

关于 xai-grok-imagine-image

xai-grok-imagine-image 是一项指导通过 xAI 图像 API 生成和编辑图像的技能,面向需要将图像工作流用于生产的开发者与创作者。它说明同步请求、模型选择、自然语言编辑及素材安全管理。

主要功能

  1. 01调用图像生成与编辑接口
  2. 02选择图像模型、比例和分辨率
  3. 03使用单张或多张参考图编辑
  4. 04保存并追踪生成素材
  5. 05处理请求成本、密钥与文件安全

适用场景

  1. 01生成网页主视觉或图标草案
  2. 02基于参考图修改产品或场景细节
  3. 03迭代图像并保留已批准版本
  4. 04将图像生成接入生产工作流

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/calesthio/generative-media-skills --skill xai-grok-imagine-image

安装后在 Claude Code 中输入/xai-grok-imagine-image确认已加载。

附带文件EVAL.md

详细介绍

xAI Grok Imagine Image

将 xAI 的第一方图像 API 作为生产系统来运行,而不是当作提示词玩具。将图像工作流与 Grok Imagine 视频分开:直接 REST API 中的图像生成和编辑是同步的,而视频采用不同的异步请求/轮询契约。

所有易变信息均已于 2026-07-09 根据 xAI 第一方文档核实。部署前请重新确认模型端点、控制台、定价、速率限制、发行说明和法律条款。

证据用语

  • 有文档说明: 已在当前 xAI API 文档、模型页面或条款中说明。
  • 提供方声明: xAI 产品或发布材料中的定性表述;请独立测试。
  • 生产启发式建议: 根据契约和常见媒体实践得出的运维建议,并非 xAI 保证。
  • 未知: 已查阅的第一方来源未能确认。不要凭假设填补空白。

锁定接口和模型

仅使用有文档说明的推理基础 URL 和路由:

POST https://api.x.ai/v1/images/generations
POST https://api.x.ai/v1/images/edits
Authorization: Bearer {XAI_API_KEY}
Content-Type: application/json

创建一个限定团队范围且仅包含所需端点/模型 ACL 的密钥。从密钥管理器或 XAI_API_KEY 加载;切勿将其放入源代码、日志、提示词、资源元数据、浏览器代码或 URL。

当前图像模型
模型当前别名1K 输出2K 输出输入图像用途
grok-imagine-image-qualitygrok-imagine-image-quality-20260403、grok-imagine-image-quality-latest;已退役的 grok-imagine-image-pro 会重定向至此$0.05$0.07每张 $0.01更高质量的生产候选模型;更强的真实感、文本和控制能力属于提供方声明
grok-imagine-imagegrok-imagine-image-2026-03-02$0.02$0.02每张 $0.002成本较低的候选模型,适用于大批量探索

这些价格以美元计,由 xAI 于 2026-07-09 公布。费用按生成的输出以及每张输入图像计算。这些图像模型页面未单独按文本提示词长度收费。请核对账单和当前模型页面。

公开模型页面列出的两个模型均在 us-east-1 和 us-west-2 提供,公开 REST 基础 URL 仍为 api.x.ai。不要根据通用主机名或营销声明推断数据驻留情况。xAI 宣传了面向企业的区域处理/数据驻留选项,但已查阅的公开图像契约未提供自助式区域参数。如对数据驻留有要求,请获取书面区域承诺。

grok-imagine-image-pro 已于 2026-05-15 12:00 PT 退役,现在会重定向至 grok-imagine-image-quality。新代码中不要使用这个已退役的标识,尽管它仍作为别名出现。受控回归测试优先使用带日期的别名;只有在接受提供方更新时才使用不带日期的规范模型名称。在可获取的情况下,记录实际模型/指纹。

上线前,请查询实际密钥可用的模型:

: "${XAI_API_KEY:?set XAI_API_KEY}"
curl --fail-with-body --silent --show-error \
  https://api.x.ai/v1/image-generation-models \
  -H "Authorization: Bearer ${XAI_API_KEY}"

模型列表响应包含 ID、别名、模态、版本/指纹以及 max_prompt_length。当前参考示例显示其值为 1024,但已查阅的架构未说明该单位是字符还是词元。请将实时字段视为由提供方定义,并验证请求,不要擅自标注其单位。

直接请求契约

文生图

发送 prompt 和明确指定的 model。可选字段包括:

  • n:变体数量。Imagine 概览说明每个生成请求最多可生成 10 张图像。每个成功生成的结果都会计费。
  • aspect_ratio:1:1、3:4、4:3、9:16、16:9、2:3、3:2、9:19.5、19.5:9、9:20、20:9、1:2、2:1 或 auto。
  • resolution:小写的 1k 或 2k。
  • response_format:url 或 b64_json。
  • storage_options:将输出保存到 xAI Files API。
  • user:用于滥用监测的唯一终端用户标识符。请发送稳定的假名化值,不要发送原始邮箱、姓名或其他不必要的个人数据。

当前 REST 图像架构中没有 seed、负面提示词字段、引导比例、遮罩、输出格式选择器、质量数值或流式传输标记。请勿自行添加。storage_options 中的文件扩展名不会转换生成的字节数据。

自然语言图像编辑

使用 /v1/images/edits,并提供 prompt、model,以及以下两者之一:

  • image:一个源对象;或
  • images:最多包含三个源对象的数组。

每个源对象包含以下其中一项:

  • url:公开的 HTTPS URL 或 base64 数据 URL;或
  • file_id:已完整上传至 xAI Files API 的私有图像。

在同一对象中,url 和 file_id 不能同时使用。支持的编辑输入格式为 JPEG、PNG 和 WebP。当前直接图像架构未公布源文件字节大小上限;请采用保守的应用限制,并针对在线服务进行测试。Batch API 每个请求 25 MB 的负载限制,并不能证明直接路由也有相同限制。

输入多个源图像时,请按请求顺序使用 <IMAGE_0>、<IMAGE_1> 和 <IMAGE_2> 指代它们。多图编辑可以覆盖 aspect_ratio;默认情况下,它会遵循第一个输入图像的比例。单图编辑会保留输入图像的宽高比,因此不要承诺 aspect_ratio 会改变其比例。

尽管编辑架构中存在 n,当前概览明确说明的“最多 10 张”仅适用于图像生成。编辑默认输出一张图像;如需更多张,请先通过功能测试验证,不要假设上限相同。

OpenAI SDK 的 images.edit() 助手不兼容,因为它会发送 multipart 表单数据;xAI 的编辑路由要求使用 JSON。请使用直接 HTTP、xAI SDK 或兼容的 Vercel AI SDK 路径。OpenAI SDK 仍可调用 /images/generations;对于 Python,aspect_ratio 和 resolution 等 xAI 专属字段可能需要通过 extra_body 传入。

将提示词作为生产规格

第一方用例指南建议具体说明主体、风格、光线、构图和氛围。可将其扩展为一份可审查的需求说明:

Deliverable and placement; subject and action; environment and era;
camera/viewpoint and composition; light and palette; materials and rendering medium;
exact visible text and hierarchy; must-preserve details; exclusions; variation intent.
生成实践
  1. 首先说明交付内容:“16:9 网站主视觉”“3:2 编辑类照片”或“1:1 图标探索”。
  2. 先明确一个主要主体及其动作,再说明氛围或风格。
  3. 指定空间关系、相机高度、裁切方式、镜头感和留白。
  4. 用通俗语言描述光线方向与质感,并指定简洁的配色方案;颜色值是目标,不是保证。
  5. 对于产品类作品,描述实体材质及表面效果。
  6. 将必需文案放在引号中,并指定位置、大小写和换行。将 xAI 所称的“更强文本渲染”视为服务提供方的说法;请进行 OCR 并由人工校对。
  7. 如果出现多余文字会构成缺陷,请注明“不要出现可见文字或徽标”。负面约束应以文字描述,而不是作为单独的 API 参数。
编辑实践

使用“操作 + 不变条件”的格式:

Use <IMAGE_0> as the base composition. Replace only the chair fabric with the
navy herringbone material from <IMAGE_1>. Keep camera, crop, room geometry,
person, face, hands, lighting direction, shadows, wall art, and all text unchanged.
Do not add objects.

为每张参考图指定一个用途。说明哪张图决定身份或几何结构,哪张图提供风格或材质,以及哪些元素不得迁移。目前没有文档说明支持遮罩输入,因此不要承诺进行像素级局部修补。如果局部精确性至关重要,请提出具体且范围明确的编辑要求,检查结果,并在需要时使用独立的确定性合成或遮罩工具。

迭代而不偏离

xAI 通过将每次输出作为下一次请求的输入来说明多轮编辑。这是一系列独立的图像调用,而非服务端会话。请持久保存每个已批准的状态及其提示词。

  • 从最后一个已批准的状态开始,而不是自动从最新状态开始。
  • 身份或布局很重要时,每次编辑只做一项语义更改。
  • 每轮都重复不变条件;API 不会将其作为隐藏状态保留。
  • 多次编辑后,请与原始图像对比,检查裁剪、身份、文本、徽标和背景是否偏离。
  • 如果偏离不断累积,请从已批准的基础版本重新开始,并使用整合后的提示词。

进行广泛探索时,请使用成本较低的模型和/或 1K。只有构图稳定后,才将入围的提示词升级到 Quality/2K。这是生产启发式方法,并非质量保证。

响应和产物生命周期

直接图像路由会同步返回已完成的 data[] 和 usage;它们不会返回视频式的 request_id。异步 SDK 客户端只是并发运行多个同步调用。服务端排队处理图像任务时,请使用单独的 Batch API。

每个 data 项可以包含:

  • URL 响应中的 url,或不带 data URI 前缀的 b64_json;
  • mime_type;
  • 请求持久化时的 file_output;
  • 生成成功但 Files 持久化失败时的 storage_error。

若私有持久化成功,file_output 会包含 file_id、filename 和可选的过期字段。只有在请求公开链接时,才会包含 public_url 或 public_url_error;普通的仅私有存储不会出现这两项。公开 URL 创建或 Files 持久化可能失败,但已生成的临时产物仍然有效。请先挽救该产物,然后通过 Files API 修复存储/公开链接状态,无需重新生成并再次付费。

usage.cost_in_usd_ticks 是精确的请求费用。xAI 将 100,000,000 ticks 定义为 1 美分,将 10,000,000,000 ticks 定义为 1 美元。

四种不同的生命周期
  1. 默认的 imgen.x.ai URL: 文档仅说明其为临时/短期有效。直接图像响应没有公布确切的有效期。请立即下载。
  2. Batch 图像 URL: 文档说明其会在一小时后过期。请及时获取并复制。
  3. 私有 Files 输出: 未提供 storage_options.expires_after 时,会一直保留到删除;否则会在 3,600–2,592,000 秒(1 小时至 30 天)后过期。
  4. Files 公开 URL: 无需身份验证且可撤销;可以永久有效,也可以在 1 小时至 30 天后过期,但有效期不能超过其文件。每个团队最多可有 1,000 个有效公开 URL。

默认将输出设为私有。迭代工作请使用不含 public_url 的 storage_options: {"filename": "..."}。如果必须分享,请设置较短的有效期,并在交付后撤销。自定义扩展名会影响公开 URL 路径,不会改变存储的 MIME 类型。

产物安全规则
  • 切勿在下载 imgen.x.ai 或 files-cdn.x.ai 内容时附带 Authorization;只有 xAI API/Files 端点才应接收令牌。
  • 将返回的 URL 视为不可信:要求使用 HTTPS,拒绝嵌入的凭据/片段,限制主机,并重新检查重定向。
  • 先流式写入临时文件,并设置严格的字节数上限,再进行缓冲或提升。
  • 验证声明的 MIME 类型、文件魔数、解码后的尺寸/面积,并执行扫描策略;切勿信任文件名。
  • 对最终字节计算哈希,并以原子方式将其提升到受控存储中。
  • 记录请求哈希、提示词版本、参考资料和权利记录、模型/别名、响应 MIME 类型、尺寸、费用、文件 ID/过期时间、产物哈希、审核/审查结果以及发布决定。

示例:严格处理 Base64 的同步生成

此完整 Python 示例仅供参考,并非必须遵循的方案,且未执行,因为执行会产生费用。请先安装 Pillow。默认情况下,它会执行脱敏试运行;只有在明确选择启用后,才会生成两个 2K Quality 候选图像;它会限制响应和解码后的字节数、验证签名/MIME/像素、以原子方式写入,并记录精确的计费金额。

import base64
import binascii
import hashlib
import io
import json
import os
import pathlib
import tempfile
import urllib.error
import urllib.request

from PIL import Image, UnidentifiedImageError

API_URL = "https://api.x.ai/v1/images/generations"
MAX_RESPONSE_BYTES = 80 * 1024 * 1024
MAX_IMAGE_BYTES = 30 * 1024 * 1024
MAX_IMAGE_PIXELS = 20_000_000
OUT = pathlib.Path("grok-imagine-candidates")
Image.MAX_IMAGE_PIXELS = MAX_IMAGE_PIXELS

prompt = (
    "16:9 website hero for a premium outdoor tea brand. A brushed steel kettle "
    "and two matte ceramic cups on a dark slate ledge at dawn in the mountains. "
    "Camera at tabletop height, generous clear negative space on the left, soft "
    "mist, warm sunrise rim light against cool blue shadows, realistic metal and "
    "ceramic textures, restrained editorial photography. No people, logos, labels, "
    "letters, or watermarks. Create distinct composition variations."
)
payload = {
    "model": "grok-imagine-image-quality",
    "prompt": prompt,
    "n": 2,
    "aspect_ratio": "16:9",
    "resolution": "2k",
    "response_format": "b64_json",
    "user": os.getenv("XAI_END_USER_ID", "internal-demo-user"),
}
body = json.dumps(payload, separators=(",", ":")).encode("utf-8")
request = urllib.request.Request(
    API_URL,
    data=body,
    method="POST",
    headers={
        "Authorization": "Bearer " + os.environ.get("XAI_API_KEY", ""),
        "Content-Type": "application/json",
        "Accept": "application/json",
    },
)
print({
    "model": payload["model"], "n": payload["n"],
    "resolution": payload["resolution"],
    "request_sha256": hashlib.sha256(body).hexdigest(),
    "estimated_usd_snapshot_2026_07_09": 0.14,
})
if os.environ.get("SEND_XAI_REQUEST") != "1":
    raise SystemExit("Dry run only; set SEND_XAI_REQUEST=1 after approving the paid call")
if not os.environ.get("XAI_API_KEY"):
    raise SystemExit("Set XAI_API_KEY before the paid call")
try:
    with urllib.request.urlopen(request, timeout=240) as response:
        zdr = response.headers.get("x-zero-data-retention")
        raw = response.read(MAX_RESPONSE_BYTES + 1)
except urllib.error.HTTPError as exc:
    detail = exc.read(64 * 1024).decode("utf-8", "replace")
    raise SystemExit(f"xAI HTTP {exc.code}: {detail}") from exc
if len(raw) > MAX_RESPONSE_BYTES:
    raise SystemExit("Response exceeded configured cap")

document = json.loads(raw)
items = document.get("data")
if not isinstance(items, list) or len(items) != payload["n"]:
    raise SystemExit(f"Expected {payload['n']} outputs, received {len(items or [])}")
OUT.mkdir(parents=True, exist_ok=True)
artifacts = []
for index, item in enumerate(items):
    encoded = item.get("b64_json")
    if not encoded:
        raise SystemExit(f"Output {index} has no base64 image")
    if len(encoded) > ((MAX_IMAGE_BYTES + 2) // 3) * 4 + 8:
        raise SystemExit(f"Output {index} exceeds encoded-size cap")
    try:
        image = base64.b64decode(encoded, validate=True)
    except (binascii.Error, ValueError) as exc:
        raise SystemExit(f"Output {index} is not strict base64") from exc
    if len(image) > MAX_IMAGE_BYTES:
        raise SystemExit(f"Output {index} exceeds decoded-size cap")
    if image.startswith(b"\xff\xd8\xff"):
        suffix, expected_mime = ".jpg", "image/jpeg"
    elif image.startswith(b"\x89PNG\r\n\x1a\n"):
        suffix, expected_mime = ".png", "image/png"
    elif image.startswith((b"RIFF",)) and image[8:12] == b"WEBP":
        suffix, expected_mime = ".webp", "image/webp"
    else:
        raise SystemExit(f"Output {index} has an unexpected signature")
    declared = item.get("mime_type")
    if declared and declared != expected_mime:
        raise SystemExit(f"Output {index} MIME/signature mismatch")
    try:
        with Image.open(io.BytesIO(image)) as probe:
            dimensions = probe.size
            if dimensions[0] * dimensions[1] > MAX_IMAGE_PIXELS:
                raise SystemExit(f"Output {index} exceeds pixel cap: {dimensions}")
            decoded_format = probe.format
            probe.verify()
        with Image.open(io.BytesIO(image)) as probe:
            probe.load()
    except (UnidentifiedImageError, OSError, Image.DecompressionBombError) as exc:
        raise SystemExit(f"Output {index} failed bounded pixel decode") from exc
    expected_format = {"image/jpeg": "JPEG", "image/png": "PNG", "image/webp": "WEBP"}[expected_mime]
    if decoded_format != expected_format:
        raise SystemExit(f"Output {index} decoder/signature mismatch")
    destination = OUT / f"candidate-{index + 1:02d}{suffix}"
    fd, temporary = tempfile.mkstemp(dir=OUT, prefix=".grok-imagine-")
    try:
        with os.fdopen(fd, "wb") as stream:
            fd = None
            stream.write(image)
            stream.flush()
            os.fsync(stream.fileno())
        os.replace(temporary, destination)
    except Exception:
        if fd is not None:
            os.close(fd)
        try:
            os.unlink(temporary)
        except FileNotFoundError:
            pass
        raise
    artifacts.append({
        "path": str(destination),
        "bytes": len(image),
        "mime_type": expected_mime,
        "dimensions": list(dimensions),
        "sha256": hashlib.sha256(image).hexdigest(),
    })

manifest = {
    "model_requested": payload["model"],
    "request_sha256": hashlib.sha256(body).hexdigest(),
    "zero_data_retention_header": zdr,
    "usage": document.get("usage"),
    "artifacts": artifacts,
    "review_status": "pending",
}
(OUT / "manifest.json").write_text(
    json.dumps(manifest, indent=2, ensure_ascii=False), encoding="utf-8"
)
print(json.dumps(manifest, indent=2))

生产代码还应使用维护中的图像库解码像素,以强制检查尺寸/面积,并运行应用的恶意软件/内容扫描器。

示例:使用私有 Files 持久化进行多参考图像编辑

此完整示例仅供参考,并非必须遵循的方案,且未执行。请先安装 Pillow。默认情况下,它会执行脱敏试运行;需要两个已上传的私有图像文件 ID,使用文档中说明的参考标签,请求私有持久化 24 小时;即使 Files 持久化失败,也会在不转发 API 密钥的情况下保留临时结果。将参考图像存放在 Files 中可避免使用公开源 URL。

import hashlib
import ipaddress
import json
import os
import pathlib
import socket
import tempfile
import urllib.error
import urllib.parse
import urllib.request

from PIL import Image, UnidentifiedImageError

API_URL = "https://api.x.ai/v1/images/edits"
ALLOWED_ASSET_HOSTS = {"imgen.x.ai", "files-cdn.x.ai"}
MAX_DOWNLOAD_BYTES = 30 * 1024 * 1024
MAX_RESPONSE_BYTES = 4 * 1024 * 1024
MAX_IMAGE_PIXELS = 20_000_000
Image.MAX_IMAGE_PIXELS = MAX_IMAGE_PIXELS


class NoRedirect(urllib.request.HTTPRedirectHandler):
    def redirect_request(self, req, fp, code, msg, headers, newurl):
        return None


def validate_asset_url(url):
    parsed = urllib.parse.urlsplit(url)
    if (
        parsed.scheme != "https" or parsed.hostname not in ALLOWED_ASSET_HOSTS
        or parsed.username or parsed.password or parsed.fragment
    ):
        raise ValueError("Unexpected artifact URL")
    addresses = {
        info[4][0] for info in socket.getaddrinfo(parsed.hostname, parsed.port or 443)
    }
    if not addresses or any(not ipaddress.ip_address(value).is_global for value in addresses):
        raise ValueError("Artifact host did not resolve exclusively to public addresses")


def atomic_write(path, data):
    path.parent.mkdir(parents=True, exist_ok=True)
    handle, temporary = tempfile.mkstemp(dir=path.parent, prefix=".manifest-")
    try:
        with os.fdopen(handle, "wb") as output:
            handle = None
            output.write(data)
            output.flush()
            os.fsync(output.fileno())
        os.replace(temporary, path)
    except Exception:
        if handle is not None:
            os.close(handle)
        try:
            os.unlink(temporary)
        except FileNotFoundError:
            pass
        raise

payload = {
    "model": "grok-imagine-image-quality",
    "prompt": (
        "Use <IMAGE_0> as the base product photograph. Replace only its background "
        "with the pale sandstone studio texture and warm side-lighting from "
        "<IMAGE_1>. Preserve the product geometry, camera angle, crop, label layout, "
        "label spelling, colors, reflections, and shadow contact exactly. Do not add "
        "props, text, people, or logos."
    ),
    "images": [
        {"file_id": os.environ.get("XAI_REFERENCE_FILE_ID_0", "<required-file-id-0>")},
        {"file_id": os.environ.get("XAI_REFERENCE_FILE_ID_1", "<required-file-id-1>")},
    ],
    "n": 1,
    "aspect_ratio": "4:3",
    "resolution": "2k",
    "response_format": "url",
    "storage_options": {"filename": "product-sandstone.jpg", "expires_after": 86400},
}
body = json.dumps(payload, separators=(",", ":")).encode("utf-8")
request = urllib.request.Request(
    API_URL,
    data=body,
    method="POST",
    headers={
        "Authorization": "Bearer " + os.environ.get("XAI_API_KEY", ""),
        "Content-Type": "application/json",
        "Accept": "application/json",
    },
)
print({
    "model": payload["model"], "references": len(payload["images"]),
    "resolution": payload["resolution"],
    "request_sha256": hashlib.sha256(body).hexdigest(),
    "estimated_usd_snapshot_2026_07_09": 0.09,
})
if os.environ.get("SEND_XAI_REQUEST") != "1":
    raise SystemExit("Dry run only; set SEND_XAI_REQUEST=1 after approving the paid call")
if not os.environ.get("XAI_API_KEY"):
    raise SystemExit("Set XAI_API_KEY before the paid call")
if any(value["file_id"].startswith("<required-") for value in payload["images"]):
    raise SystemExit("Set both XAI_REFERENCE_FILE_ID environment variables")
try:
    with urllib.request.urlopen(request, timeout=240) as response:
        raw = response.read(MAX_RESPONSE_BYTES + 1)
except urllib.error.HTTPError as exc:
    detail = exc.read(64 * 1024).decode("utf-8", "replace")
    raise SystemExit(f"xAI HTTP {exc.code}: {detail}") from exc
if len(raw) > MAX_RESPONSE_BYTES:
    raise SystemExit("Edit response exceeded configured JSON cap")
try:
    document = json.loads(raw)
except (UnicodeDecodeError, json.JSONDecodeError) as exc:
    raise SystemExit("Edit response was not valid bounded JSON") from exc
items = document.get("data") or []
if len(items) != 1:
    raise SystemExit(f"Expected one edit output, received {len(items)}")
item = items[0]
storage_error = item.get("storage_error")
file_output = item.get("file_output") or {}
if not storage_error and not file_output.get("file_id"):
    raise SystemExit("Missing persisted file_id")

def download_xai_asset(url: str, destination: pathlib.Path) -> dict:
    opener = urllib.request.build_opener(NoRedirect())
    current = url
    response = None
    for _ in range(6):
        validate_asset_url(current)
        # Deliberately omit XAI_API_KEY on every ephemeral/public asset hop.
        asset_request = urllib.request.Request(
            current, headers={"Accept": "image/jpeg,image/png,image/webp"}
        )
        try:
            response = opener.open(asset_request, timeout=90)
            break
        except urllib.error.HTTPError as exc:
            if exc.code not in {301, 302, 303, 307, 308}:
                raise
            location = exc.headers.get("Location")
            exc.close()
            if not location:
                raise ValueError("Artifact redirect omitted Location")
            current = urllib.parse.urljoin(current, location)
    else:
        raise ValueError("Too many artifact redirects")

    total = 0
    prefix = b""
    hasher = hashlib.sha256()
    destination.parent.mkdir(parents=True, exist_ok=True)
    fd = None
    temporary = None
    try:
        with response:
            mime = response.headers.get_content_type()
            if mime not in {"image/jpeg", "image/png", "image/webp"}:
                raise ValueError(f"Unexpected artifact MIME: {mime}")
            declared = response.headers.get("Content-Length")
            if declared and int(declared) > MAX_DOWNLOAD_BYTES:
                raise ValueError("Artifact exceeds configured cap")
            fd, temporary = tempfile.mkstemp(dir=destination.parent, prefix=".download-")
            with os.fdopen(fd, "wb") as output:
                fd = None
                while True:
                    chunk = response.read(64 * 1024)
                    if not chunk:
                        break
                    total += len(chunk)
                    if total > MAX_DOWNLOAD_BYTES:
                        raise ValueError("Artifact exceeded cap while streaming")
                    prefix += chunk[: max(0, 12 - len(prefix))]
                    hasher.update(chunk)
                    output.write(chunk)
                output.flush()
                os.fsync(output.fileno())
        valid = (
            prefix.startswith(b"\xff\xd8\xff")
            or prefix.startswith(b"\x89PNG\r\n\x1a\n")
            or (prefix.startswith(b"RIFF") and prefix[8:12] == b"WEBP")
        )
        if not valid:
            raise ValueError("Unexpected artifact signature")
        expected_mime = (
            "image/jpeg" if prefix.startswith(b"\xff\xd8\xff")
            else "image/png" if prefix.startswith(b"\x89PNG\r\n\x1a\n")
            else "image/webp"
        )
        if mime != expected_mime:
            raise ValueError("Artifact MIME/signature mismatch")
        try:
            with Image.open(temporary) as probe:
                dimensions = probe.size
                if dimensions[0] * dimensions[1] > MAX_IMAGE_PIXELS:
                    raise ValueError(f"Artifact exceeds pixel cap: {dimensions}")
                decoded_format = probe.format
                probe.verify()
            with Image.open(temporary) as probe:
                probe.load()
        except (UnidentifiedImageError, OSError, Image.DecompressionBombError) as exc:
            raise ValueError("Artifact failed bounded pixel decode") from exc
        expected_format = {"image/jpeg": "JPEG", "image/png": "PNG", "image/webp": "WEBP"}[mime]
        if decoded_format != expected_format:
            raise ValueError("Artifact decoder/signature mismatch")
        os.replace(temporary, destination)
        temporary = None
    except Exception:
        if fd is not None:
            os.close(fd)
        try:
            if temporary is not None:
                os.unlink(temporary)
        except FileNotFoundError:
            pass
        raise
    return {"bytes": total, "sha256": hasher.hexdigest(), "mime_type": mime,
            "dimensions": list(dimensions)}

if not item.get("url"):
    raise SystemExit("Missing ephemeral artifact URL")
path = pathlib.Path("grok-imagine-edit") / "product-sandstone.bin"
artifact = download_xai_asset(item["url"], path)
manifest = {
    "request_sha256": hashlib.sha256(body).hexdigest(),
    "file_id": file_output.get("file_id"),
    "file_expires_at": file_output.get("expires_at"),
    "storage_error": storage_error,
    "usage": document.get("usage"),
    "artifact": {"path": str(path), **artifact},
    "review_status": "pending",
}
atomic_write(path.with_suffix(".json"), json.dumps(manifest, indent=2).encode("utf-8"))
print(json.dumps(manifest, indent=2))
if storage_error:
    print("Generation succeeded; retry Files persistence from the saved bytes without regenerating")

生产环境中完成像素解码后,请使用根据 MIME 类型确定的扩展名,而不是 .bin。如果 xAI 更改了其文档中说明的资源主机,请先验证,再更新允许列表。

异步 Batch API

交互式任务请使用直接 REST。排队处理批量任务请使用 Batch API:

  1. 使用名称发送 POST /v1/batches;保留 batch_id。
  2. 发送 POST /v1/batches/{batch_id}/requests,其中包含唯一命名的 batch_request_id 值以及 image_generation 或 image_edit 请求对象。
  3. 轮询 GET /v1/batches/{batch_id},直到 num_pending == 0。
  4. 分页获取 GET /v1/batches/{batch_id}/results;将每项结果与 batch_request_id 匹配,并处理成功、失败和已取消的项目。
  5. 立即下载成功的图像 URL;Batch 媒体 URL 会在一小时后过期。

Batch 处理通常会在 24 小时内完成,但 xAI 表示这仅是尽力而为,并非 SLA。尽管 Batch 概览中通常提到价格降低,Batch 中的图像/视频请求仍按标准费率计费。Batch 请求不会占用常规实时速率限制。单个 Batch 请求负载上限为 25 MB。Batch 可能会被取消或过期;已完成的结果仅在 Batch 过期前可用。

Batch 请求正文示例(提交后会产生费用):

{
  "batch_requests": [
    {
      "batch_request_id": "catalog-hero-0001",
      "batch_request": {
        "image_generation": {
          "model": "grok-imagine-image-quality",
          "prompt": "1:1 catalog hero of a cobalt ceramic vase on warm white paper, soft overhead light, no text or logo",
          "aspect_ratio": "1:1",
          "resolution": "1k",
          "n": 1
        }
      }
    }
  ]
}

为 batch_request_id 使用确定性的业务密钥,并在提交前将其持久化。xAI 说明,这有助于在 Batch 中实现幂等性和结果关联。直接图像路由未说明幂等性标头或密钥;请勿假设客户端重试会被去重。

失败、重试与对账

xAI 记录了以下常见 HTTP 状态码类别:

状态码含义响应
400/422参数无效或字段格式无效;400 也可能表示密钥不正确修正请求;不要原样重试
401/403身份验证缺失或无效、缺少权限、密钥或团队被封禁修正身份、ACL 或账户状态
404/405/415模型或路由不存在、方法错误,或缺少 JSON 内容类型或请求正文修正集成或模型生命周期配置
429速率限制将请求加入队列,并使用设有上限的指数退避和抖动进行重试
5xx/网络临时服务或传输故障仅在有界策略内重试,并核对重复请求风险

目前的模型页面显示,两个图像模型在所有层级的请求速率均为每秒 5 次;尚未公布 TPM 值。团队实际生效的限额以控制台为准;提高 Imagine 限额需联系 xAI 销售或支持团队。将并发量控制在测得的上限以下,并记住一次请求最多可生成 10 个计费输出。

对于 429 或临时故障,如果存在 Retry-After,请遵循该值;否则使用带完整抖动的指数退避,并设置总截止时间。对于内容审核、无效提示词或参考图、授权或存储配置故障,不要将其视为临时故障进行重试。

提交请求前,先创建客户端任务台账,记录请求哈希、预期数量、尝试次数和状态。服务器已接受任务后发生超时,情况并不明确:重复发送直接请求可能会生成并收取第二批费用。手动重放前,请核对任何响应、确切的 usage、工件或文件记录以及账户账单。不要声称可以确定性地重新生成;当前 API 不提供种子值。

分别检查 storage_error、public_url_error、每个 data 项以及预期数量。应通过 Files 端点修复存储或公开链接的部分故障,而不是重新生成图像。

成本控制

每次请求前估算最高费用:

generation = n_outputs × output_price(model, resolution)
edit       = input_image_count × input_price(model) + n_outputs × output_price(model, resolution)

例如,包含 3 张参考图、生成 1 张 2K 输出的 Quality 编辑,目前最高费用为 3 × $0.01 + 1 × $0.07 = $0.10,不含税费或合同调整。生成 10 张 Quality 2K 图像的费用为 $0.70。向客户报价前,请立即重新核实价格。

为每项任务设置输出数量、模型、分辨率、尝试次数和总 cost_in_usd_ticks 上限。若预估费用与实际用量不符,请发出警报。目前批量图像没有折扣。不要隐瞒参考图、n、2K Quality 或重试导致的费用增加。

Files 是单独的计费项:目前公开价格为存储费用 $0.025/GiB/天,下载费用 $0.20/GiB。根据预期字节数 × 保留天数以及检索量编制预算,设置满足需求的最短 TTL,在交付后删除私有文件并撤销公开 URL,并将 Files 费用与推理用量点数分开核对。

质量门禁

检查解码后的像素,而不是缩略图或服务商 URL。发布候选版本必须通过以下检查:

  • **需求:**交付物、宽高比、裁切、安全区域、主体数量及所需变体均正确。
  • **参考图/编辑:**对照每个来源,检查基础几何形状、身份、姿势、标签、Logo、背景、光照及“保持不变”的区域。
  • **文本:**结合 OCR 与人工检查拼写、标点、换行和可读性。
  • **视觉:**检查手部/面部、重复结构、反射、阴影、材质连贯性、边缘光晕、不需要的符号及压缩缺陷。
  • **品牌/权利:**检查配色、标志、许可素材、肖像授权、商标使用场景及禁止的仿冒设计。
  • **技术:**检查真实的 MIME 类型/签名、解码尺寸、色彩模式/配置文件、文件大小、哈希值、存储/公开 URL 生命周期及清单完整性。
  • **安全:**检查审核结果、应用政策及人工审核。

维护固定回归测试套件,涵盖照片级真实感、插画、排版、产品标签、单参考图编辑、三参考图合成、极端宽高比、1K/2K、n=10、base64、临时下载、Files 部分失败、429、超时、审核和 Batch 过期。迁移前,盲测对比规范名称与带日期的别名。

数据、权利、安全与来源

当前的 API 安全常见问题解答指出,未经明确许可,xAI 不会使用 API 输入或输出进行训练。标准 API 请求/响应会保留 30 天,用于审核滥用/不当使用情况,之后删除;企业级 ZDR 可用,响应标头 x-zero-data-retention 会报告其状态。根据当前企业条款,个人数据必须仅通过启用了 ZDR 的 API 提交;PHI 则需要同时具备适用的 BAA 和 ZDR。对于可识别个人身份的输入,应将此视为合同规定的门槛,而非可选的优化措施。条款还规定了涉及法律、安全、安保、审核、滥用防范和调查的例外情况,因此应以已签署的合同为准。

应将用户内容与衍生数据区分开来:除 ZDR 情形外,企业条款允许 xAI 创建服务使用所衍生的去标识化或汇总数据,并将其用于维护/改进服务、开发新产品/功能、研究、基准测试及其他合法用途,同时仍规定未经许可,不会使用用户内容训练 xAI AI 系统。应准确披露这两项规定。

不要将审核保留与有意持久化的 Files 输出混为一谈。没有 TTL 的私有文件可能会一直保留,直到被删除;公开 URL 可能会一直可访问,直到过期/撤销。经审阅的文档未说明 storage_options 与 ZDR 如何交互。如果两者都不可或缺,应取得书面答复,并测试标头/Files 行为,而不是假定二者兼容。

根据当前企业条款,就 xAI 与客户之间的关系而言,客户保留 Input 权利并拥有 Output;在法律允许的最大范围内,xAI 将其对 Output 的权益转让给客户。这并不保证 Output 具备版权保护资格、具有独占性、准确或不侵犯他人权利。不同用户可能获得相似的输出。客户必须拥有必要的 Input 权利/同意,评估 Output,不得将其描述为人工生成,也不得使用 Output 训练自己或其服务提供商的 ML/AI 模型。

对个人数据、人脸/肖像、未成年人、私人场所、受版权保护的作品、商标、凭据、受监管内容以及欺骗性场景实施预检。xAI AUP 禁止的行为包括但不限于侵犯知识产权/隐私权/公开权、未经同意对真实人物进行色情化修改、欺骗性冒充、儿童性剥削、诽谤/造成虚假印象、欺诈/伪造、人肉搜索/跟踪、高风险自动化决策、绕过安全措施,以及移除/规避嵌入的来源元数据或水印。

应取得使用肖像/参考图像的书面同意,披露 AI 的参与情况,保留任何嵌入的来源信息/水印,并添加应用层签名清单。经审阅的图像 API 合同未承诺提供 C2PA 凭证或可见水印,因此不得声称会提供其中任何一项。对于公开、商业、事实、政治、医疗、法律、金融、涉及身份敏感信息或其他可能产生重大影响的用途,必须进行人工审核。

第一方来源

检查日期:2026-07-09:

日期相关的信息缺口和冲突

  • 直接生成的临时图像 URL 仅说明为短期有效;文档中提到的一小时有效期针对 Batch URL,而非直接 URL。
  • 文档未说明直接编辑的源图尺寸限制、编辑参数 n 的最大值、直接请求的幂等性、确定性种子、蒙版/修复输入、输出格式选择,以及有保证的来源凭证。
  • Imagine 概览标题标注 Quality 的价格为 $0.05/image;当前详细定价/模型表中的 Quality 2K 价格为 $0.07。请以当前详细价格为准。
  • Batch 概览笼统提到价格优惠,而当前定价页面明确指出,图像/视频 Batch 请求按标准费率计费。请按标准图像费率计算。
  • 公共页面宣传了企业区域/数据驻留选项,但未公开提供图像请求区域选择器。请通过合同约定数据驻留。
  • Files 与 ZDR 之间的兼容情况尚未明确。未经服务提供商确认,不要承诺两者可同时使用。

评论

支持 Markdown