mcpField
A
adobe-firefly-image

adobe-firefly-image

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

关于 adobe-firefly-image

面向静态图像制作流程的 Adobe Firefly Services 集成技能,涵盖 API 与模型选择、身份验证、请求校验及质量检查,适用于生成和编辑图像。

主要功能

  1. 01API 与模型选择
  2. 02OAuth Server-to-Server 指南
  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 adobe-firefly-image

安装后在 Claude Code 中输入/adobe-firefly-image确认已加载。

附带文件EVAL.md

详细介绍

Adobe Firefly Image

将其视为静态图像制作与集成技能。除非用户明确要求组合系统,否则不要涉及视频、音频,以及无关的 Photoshop、Lightroom、Express 或 Creative Production 操作。

证据规范

请始终如一地使用以下标签:

  • 已记录表示当前 Adobe API 参考、变更日志、产品条款或安全材料明确说明了该说法。
  • 推断表示结论可根据已记录的字段或行为得出,但 Adobe 并未直接说明。
  • 生产经验法则表示在目标账户和工作流中进行测试的实用建议。

以下易变的 API 信息已于 2026-07-09 核实。正式发布前,请立即重新查阅 API 参考、变更日志和使用说明。Adobe 的一些说明页面中的示例与端点参考相互冲突。如有冲突,请优先采用 API 参考中的操作架构,以及之后明确发布的变更日志条目;不要混用不兼容版本中的字段。

从生产约定开始

生成任何内容之前:

  1. 确认组织有权使用 Firefly Services,且项目已配置 OAuth Server-to-Server 凭据。
  2. 记录交付内容、宽高比或尺寸、对所提供素材可接受的修改范围、权利/同意状态、区域设置、无障碍需求和审核负责人。
  3. 从下方矩阵中选择操作。不要仅因为某个模型是最新版本就选择它。
  4. 在付费批次开始前,明确端点、模型标头、变体数量和计费单位。
  5. 先运行一个有代表性的样例,立即下载,并在扩大规模前完成视觉与来源信息质量检查。

当前 API 与模型对应关系

文档记录,已于 2026-07-09 验证:

需求端点模型选择关键限制
生成新的 Image 5 图像POST /v4/images/generate-async请求头 x-model-version: image5;请求体 modelId: firefly_image每次请求生成一个变体;最多一张参考图像
使用自然语言编辑 Image 5 图像同一 v4 端点相同的请求头和请求体非空的 referenceBlobs 数组会将操作切换为指令式编辑
生成 Image 3/4 图像并使用显式控件POST /v3/images/generate-asyncimage3、image4_standard 或 image4_ultra最多四个变体;样式/结构控件位于此处
使用自定义模型生成图像同一 v3 端点image3_custom 或 image4_custom,以及 customModelId自定义模型必须已配置,并与应用共享
生成相似图像POST /v3/images/generate-similar-asyncimage3、image4_standard 或 image4_ultra必须提供源图像
替换蒙版区域POST /v3/images/fill-async操作架构,而非 Image 5 指令式编辑架构必须提供源图像和蒙版
扩展画布/扩图POST /v3/images/expand-async操作架构必须提供源图像;蒙版与放置位置之间存在互斥限制
在对象周围生成提示词指定的背景POST /v3/images/generate-object-composite-async操作架构没有背景时使用
在现有背景中精确保留对象POST /v3/images/precise-composite精确合成流程必须提供现有背景和放置蒙版
将对象融入现有背景POST /v3/images/adaptive-composite自适应合成流程可能会重新生成/调整对象,使其更加逼真
注重保真度的放大POST /v3/images/upscalex-model-version: precise_upsampler_v1放大倍数为 2、3、4 或 6;异步执行
上传输入图像或蒙版POST /v2/storage/image无返回的上传 ID 有效期为七天

Image 5 是最新记录的图像模型,支持 1 MP、2.4 MP 和 4 MP 分辨率。当前 API 架构中仍包含 v3 模型值;不要仅因 Image 5 已推出就称这些模型已移除。Adobe 于 2025-10-03 移除了已弃用的 v3 同步接口,并于 2025-09-10 移除了 Generate Images v2、Expand v1 和 Fill v1。请仅基于当前异步路由进行开发。请参阅更新日志。

有意处理文档冲突

**文档冲突,已于 2026-07-09 验证:**Image 5 API 参考文档为 /v4/images/generate-async 定义了 aspectRatio、resolutionLevel、modelId、modelSpecificPayload 和 referenceBlobs。与此同时,Image 5 迁移页面将其中一些字段描述为已移除,并给出了一个类似 v3 自定义模型架构的请求载荷。在将该迁移表作为可执行架构使用前,请先对照 API 参考文档解决这一冲突。

同样,放大指南的正文仍提及 /v1/images/upsample-async,而当前 API 参考文档和 2026-04-24 更新日志则指出应使用 POST /v3/images/upscale 和 precise_upsampler_v1。请使用后者。creative_upsampler_v1 会返回 422 错误。这是对来源质量的判断,并不意味着 Adobe 所有正文页面都已过时。

从服务端进行身份验证

文档说明: Firefly 请求需要 Adobe IMS 持有者令牌,以及作为 x-api-key 的 Client ID。请使用 OAuth Server-to-Server。切勿在浏览器代码、日志、提示词或生成的产物中暴露客户端密钥或访问令牌。Adobe 文档指出,访问令牌有效期为 24 小时;请主动刷新令牌,并且只有在获取新令牌后,才在收到 401 后重试一次。请参阅身份验证和凭据设置。

令牌请求:

POST https://ims-na1.adobelogin.com/ims/token/v3
Content-Type: application/x-www-form-urlencoded

grant_type=client_credentials&client_id=...&client_secret=...&scope=openid,AdobeID,session,additional_info,read_organizations,firefly_api,ff_apis

之后,每次 Firefly API 调用都要包含:

Authorization: Bearer <access-token>
x-api-key: <client-id>
Content-Type: application/json
Accept: application/json

请使用 Developer Console 中实际配置的作用域和产品配置文件;不要凭猜测扩大作用域。令牌交换成功后出现 403,通常意味着权限、产品配置文件、组织、自定义模型共享、策略或区域访问问题,而非 OAuth 格式错误。

了解请求和响应约定

图像 5:v4 生成和指令编辑

文档说明的 v4 字段:

  • prompt:必需,长度为 1–1500 个字符。
  • aspectRatio:1:1、4:3、3:4、16:9、9:16 或 auto。
  • resolutionLevel:1MP、2.4MP(默认值)或 4MP。
  • modelId:firefly_image。
  • numVariations:最多为 1。
  • seeds:最多包含一个整数;如果两者都出现,其数量必须与 numVariations 相同。
  • modelSpecificPayload.localeCode:Image 5 使用的区域设置控制项。
  • modelSpecificPayload.prompt_reasoner:speed(默认值)或 quality;Adobe 表示,quality 会填充 altText,而 speed 返回的该字段为空。
  • referenceBlobs:最多一个。为空表示文生图;非空且 usage: general 表示指令编辑。如果提供了该字段,请省略 aspectRatio,或将其设为 auto。

不要向 v4 发送仅适用于 v3 的 style、structure、contentClass、visualIntensity 或 customModelId 字段。v4 操作架构未说明负面提示词字段或任意输出格式选择器。请验证实际响应资源的 Content-Type,不要假设它是 JPEG 或 PNG。

v4 提交会返回 HTTP 200,其中包含 links.result.href、links.cancel.href 和进度值。该操作仍是异步的。请使用相同的授权标头轮询返回的结果链接,直到进入终止状态,然后尽快下载图像 URL。

图像 3/4:v3 生成

文档说明的 v3 字段:

  • prompt:必需,长度为 1–1024 个字符。
  • negativePrompt:最多 1024 个字符;Image 3 或 Image 4 自定义模型不支持此字段。
  • contentClass:photo 或 art。
  • numVariations:1–4。如果提供了种子,种子数量必须与变化数量相同。
  • promptBiasingLocaleCode:类似 BCP-47 的语言区域值,例如 en-US。
  • style.presets:文档说明的 snake_case 预设;请谨慎组合。
  • style.strength:1–100,默认值为 50;可与风格参考一起使用。
  • structure.strength:1–100,默认值为 50;控制与结构参考在轮廓/深度上的相似程度。
  • visualIntensity:2–10;image4_custom 不支持此字段。
  • upsamplerType:仅适用于 image4_custom;可设为 default 或 low_creativity。

支持的生成尺寸:

模型文档明确列出的尺寸
Image 42048×2048、2304×1792、1792×2304、2688×1536、1440×2560
Image 32048×2048、1024×1024、2304×1792、1792×2304、2688×1536、2688×1512、1344×768、1344×756、1152×896、896×1152

不要四舍五入到方便的等效宽高比尺寸。服务会验证确切的尺寸组合。

v3 提交会返回 HTTP 202,其中包含 jobId、statusUrl 和 cancelUrl。请轮询 statusUrl。运行中的响应包含 status: running;成功响应包含 status: succeeded 和 result.outputs[],其中每项都包含一个种子和图像 URL。请将 failed、cancelled、HTTP 终止错误和本地超时视为终止结果;为支持请求,请留存响应正文和请求 ID。

上传、蒙版和源 URL

上传 API 接受二进制图像数据。当前 API 参考列出的格式包括 PNG、JPEG、WebP、TIFF 和 JXL,最大大小为 15 MB;较早的上传指南只列出 JPEG、PNG 和 WebP。**生产经验法则:**蒙版或透明图像使用 PNG;普通输入使用 JPEG 或 PNG,除非已在目标账户中测试过更广泛的格式。

上传响应的格式为 {"images":[{"id":"..."}]}。API 契约说明 ID 的有效期为七天。Adobe 的 Firefly Services 安全说明则另外指出,上传的参考内容会缓存 24 小时;请勿将缓存保留时间等同于 ID 有效期。

对于 v3 源 URL,操作参考通常将 amazonaws.com、windows.net、dropboxusercontent.com 和 storage.googleapis.com 列入允许列表。Image 5 使用说明还列出了 frontdoor.prod.azure.cxp.adobe.com。具体要求因操作而异。请使用有效期较短的 HTTPS 预签名 URL,并限制对象权限,同时确认相应操作的确切允许列表。切勿传入任意公共 URL 并假定 Firefly 会抓取它。

蒙版是生产资产,而非注释。在付费调用前,请检查尺寸、Alpha/灰度语义、反转、羽化和对齐情况。expand-async 禁止在同时应用蒙版时为源使用 placement。其输出最大可达 3999×3999。对于局部蒙版替换,请使用 Fill;对于更大的画布,请使用 Expand;如果只需一个参考图并希望通过自然语言优化整张图像,请使用 Image 5 instruct edit。

根据需要保持不变的内容选择参考图和合成方式

风格和结构

使用 v3 style.imageReference 迁移视觉语言,例如调色板、媒介、光线或氛围。使用 structure.imageReference 引导轮廓和深度。强度范围为 1–100;默认值为 50。这些控制项可以同时使用,但强度都较高时可能会降低对提示词的遵循度。

**生产经验法则:**每次只调整一个控制项,例如依次测试 30、60、90,同时使用相同提示词和种子,然后根据书面评估标准进行选择。种子有助于复现,但无法保证在模型、端点或服务更新后仍能复现。Adobe 的种子指南指出,相同设置会生成相同图像;但仍应记录模型标头、端点、负载和源哈希,因为生命周期变化可能导致跨版本假设失效。

只能使用 风格预设目录中有文档记录的预设值。不要将 UI 标签自行编造为 API 枚举值。以下是一些有用且有文档记录的示例:product_photo、studio_light、golden_hour、flat_design、watercolor、cinematic、bw 和 shallow_depth_of_field;这并非完整目录。

合成方式选择
  • 如果对象已存在,但需要根据提示词生成背景,请使用 Generate Object Composite。请求必须提供可用于合成的输入:更大的目标画布、透明度或蒙版。
  • 如果必须精确保留所提供对象的像素或包装细节,请使用 Precise Composite。它使用背景图像 + 填充区域蒙版 + 对象图像。blend 的范围为 0–1;0 表示完全协调,1 表示保留对象的原始外观。最多可生成三种变体。
  • 如果在现有背景中实现逼真效果比精确保留对象像素更重要,请使用 Adaptive Composite。harmonization 和 shadowIntensity 的范围为 0–1;preserveBackground 会在修改对象区域的同时保留原始背景像素。最多可生成三种变体。

这三种方式都是异步的。不要承诺“precise”就无需进行像素差异和标签可读性审查。对于受监管的包装或产品声明,除非可以接受并审查改动,否则不要使用 Adaptive Composite。

放大

使用当前的 POST /v3/images/upscale,并设置 x-model-version: precise_upsampler_v1、输入图像源、1 到 4 个种子,以及 2、3、4 或 6 的 upscaleFactor。轮询返回的 links.result.href。API 参考文档将输出尺寸描述为输入尺寸乘以放大倍数。请验证服务限制和实际结果;一份文字指南称最大尺寸为 6k,而端点架构主要说明放大倍数,因此处理较大输入前应先检查限制,不要假定每种放大倍数都受支持。

先指导图像,再撰写文案

生产经验法则: 将提示词写成精简的美术指导简报,而不是堆砌形容词。请说明:

  1. 主体及动作;
  2. 环境与时代;
  3. 构图、视角、镜头语言或版式;
  4. 光照与色彩关系;
  5. 材质与表面特征;
  6. 仅在必要时,逐字引用必须显示的文本;
  7. 通过 API 文档规定的负面提示词字段排除不需要的内容(如有)。

未经授权,不要模仿在世艺术家的风格,也不要使用第三方标志、人脸、受版权保护的角色或参考素材。进行本地化时,设置 API 的区域设置字段,并请母语审校人员检查文化细节;区域设置偏差不等于翻译,也不代表已获得法律许可。

对于准确的排版文字、标志、包装文案、二维码和合规标记,请先生成干净的视觉底图,再叠加已获批准的矢量或栅格图稿。将模型生成的文字视为草稿内容。若用于产品,请要求保留负空间,并采用适合后续叠加图稿的镜头几何。

示例 1 — 完整的 Image 5 生成客户端

示例,并非强制公式。 这个 Python 3.11+ 标准库程序会获取令牌、提交一个 Image 5 任务,通过有界指数退避轮询返回的结果 URL,下载第一张图像,并记录响应元数据。请在安全的服务器环境中设置 FIREFLY_SERVICES_CLIENT_ID 和 FIREFLY_SERVICES_CLIENT_SECRET。

import json
import os
import random
import time
import urllib.error
import urllib.parse
import urllib.request
from datetime import datetime, timezone
from email.utils import parsedate_to_datetime
from pathlib import Path

CLIENT_ID = os.environ["FIREFLY_SERVICES_CLIENT_ID"]
CLIENT_SECRET = os.environ["FIREFLY_SERVICES_CLIENT_SECRET"]
TOKEN_URL = "https://ims-na1.adobelogin.com/ims/token/v3"
GENERATE_URL = "https://firefly-api.adobe.io/v4/images/generate-async"
SCOPES = "openid,AdobeID,session,additional_info,read_organizations,firefly_api,ff_apis"
MIN_API_INTERVAL = 15.0  # Default 4-RPM org entitlement; use a distributed limiter at scale.
_last_api_request = 0.0


def wait_for_org_quota():
    global _last_api_request
    delay = MIN_API_INTERVAL - (time.monotonic() - _last_api_request)
    if delay > 0:
        time.sleep(delay)
    _last_api_request = time.monotonic()


def retry_after_seconds(value, fallback):
    if not value:
        return fallback
    try:
        return max(0.0, float(value))
    except ValueError:
        when = parsedate_to_datetime(value)
        if when.tzinfo is None:
            when = when.replace(tzinfo=timezone.utc)
        return max(0.0, (when - datetime.now(timezone.utc)).total_seconds())


def request_json(url, *, method="GET", headers=None, body=None, attempts=5):
    data = None if body is None else json.dumps(body).encode("utf-8")
    for attempt in range(attempts):
        req = urllib.request.Request(url, data=data, method=method, headers=headers or {})
        wait_for_org_quota()
        try:
            with urllib.request.urlopen(req, timeout=60) as response:
                return response.status, dict(response.headers), json.load(response)
        except urllib.error.HTTPError as exc:
            detail = exc.read().decode("utf-8", "replace")
            retryable = exc.code == 429 or 500 <= exc.code < 600
            if not retryable or attempt == attempts - 1:
                raise RuntimeError(f"HTTP {exc.code} from {url}: {detail}") from exc
            fallback = min(30, 2 ** attempt + random.random())
            time.sleep(max(MIN_API_INTERVAL, retry_after_seconds(exc.headers.get("Retry-After"), fallback)))
        except (urllib.error.URLError, TimeoutError) as exc:
            if attempt == attempts - 1:
                raise RuntimeError(f"Network failure from {url}: {exc}") from exc
            time.sleep(max(MIN_API_INTERVAL, min(30, 2 ** attempt + random.random())))


def get_token():
    form = urllib.parse.urlencode({
        "grant_type": "client_credentials",
        "client_id": CLIENT_ID,
        "client_secret": CLIENT_SECRET,
        "scope": SCOPES,
    }).encode("utf-8")
    req = urllib.request.Request(
        TOKEN_URL,
        data=form,
        method="POST",
        headers={"Content-Type": "application/x-www-form-urlencoded"},
    )
    with urllib.request.urlopen(req, timeout=30) as response:
        return json.load(response)["access_token"]


def find_first_image_url(value):
    if isinstance(value, dict):
        image = value.get("image")
        if isinstance(image, dict) and isinstance(image.get("url"), str):
            return image["url"]
        for child in value.values():
            found = find_first_image_url(child)
            if found:
                return found
    elif isinstance(value, list):
        for child in value:
            found = find_first_image_url(child)
            if found:
                return found
    return None


def main():
    token = get_token()
    headers = {
        "Authorization": f"Bearer {token}",
        "x-api-key": CLIENT_ID,
        "Content-Type": "application/json",
        "Accept": "application/json",
        "x-model-version": "image5",
    }
    payload = {
        "prompt": (
            "Editorial product photograph of a matte cobalt reusable bottle on pale limestone, "
            "three-quarter view, soft north-window light, restrained blue and warm-gray palette, "
            "clean negative space at upper left, realistic condensation, no logo or lettering"
        ),
        "aspectRatio": "4:3",
        "resolutionLevel": "4MP",
        "modelId": "firefly_image",
        "numVariations": 1,
        "seeds": [42345],
        "referenceBlobs": [],
        "modelSpecificPayload": {"localeCode": "en-US", "prompt_reasoner": "quality"},
    }
    status, response_headers, accepted = request_json(
        GENERATE_URL, method="POST", headers=headers, body=payload
    )
    result_url = accepted["links"]["result"]["href"]
    cancel_url = accepted["links"]["cancel"]["href"]
    deadline = time.monotonic() + 300
    final = accepted
    result_headers = {}
    while time.monotonic() < deadline:
        _, result_headers, final = request_json(result_url, headers=headers)
        state = str(final.get("status", "")).lower()
        if state in {"succeeded", "failed", "cancelled", "canceled"}:
            break
        if find_first_image_url(final):
            break
    else:
        cancel_result = None
        try:
            cancel_status, _, cancel_body = request_json(cancel_url, method="PUT", headers=headers)
            cancel_result = {"status": cancel_status, "body": cancel_body}
        except Exception as exc:
            cancel_result = {"error": str(exc)}
        Path("firefly-response.json").write_text(
            json.dumps({
                "request": payload,
                "accept_status": status,
                "accept_headers": response_headers,
                "accepted": accepted,
                "cancel_attempt": cancel_result,
            }, indent=2),
            encoding="utf-8",
        )
        raise TimeoutError("Firefly job did not finish within 300 seconds")

    if str(final.get("status", "")).lower() in {"failed", "cancelled", "canceled"}:
        raise RuntimeError(f"Firefly job ended unsuccessfully: {json.dumps(final)}")
    image_url = find_first_image_url(final)
    if not image_url:
        raise RuntimeError(f"No image URL in terminal response: {json.dumps(final)}")
    with urllib.request.urlopen(image_url, timeout=120) as image_response:
        content_type = image_response.headers.get_content_type()
        suffix = {"image/png": ".png", "image/jpeg": ".jpg", "image/webp": ".webp"}.get(
            content_type, ".bin"
        )
        Path("firefly-output" + suffix).write_bytes(image_response.read())
    Path("firefly-response.json").write_text(
        json.dumps({
            "request": payload,
            "accept_status": status,
            "accept_headers": response_headers,
            "accepted": accepted,
            "result_headers": result_headers,
            "result": final,
        }, indent=2),
        encoding="utf-8",
    )


if __name__ == "__main__":
    main()

预期结果:生成一个 4:3 的 Image 5 资源,以实际媒体类型对应的扩展名下载到本地,并附带一个审计 JSON。可能的失败原因:缺少使用权限(403)、误将 v3 架构中的字段发送到 v4(422)、组织级限流(429)、令牌过期(401),或输出 URL 在下载前过期。

有意义的变体:将 prompt_reasoner 切换为 speed 以测试延迟;如果账户费率卡更有利,可使用 resolutionLevel: 1MP 进行低成本预检;或者提供一个获准的 referenceBlobs 项目,移除或自动设置宽高比,并将提示词写成编辑指令。

示例 2 — 上传并进行蒙版填充

示例,并非强制公式。 这个 Python 3 程序会上传一个 PNG 源图和蒙版,提交 4 个带种子的 v3 填充变体,然后轮询返回的状态 URL。它假设源图和蒙版尺寸相同,且已通过目视确认蒙版语义无误。

import json
import os
import random
import time
import urllib.error
import urllib.request
from datetime import datetime, timezone
from email.utils import parsedate_to_datetime
from pathlib import Path

CLIENT_ID = os.environ["FIREFLY_SERVICES_CLIENT_ID"]
TOKEN = os.environ["FIREFLY_SERVICES_ACCESS_TOKEN"]
BASE = "https://firefly-api.adobe.io"
AUTH = {"Authorization": f"Bearer {TOKEN}", "x-api-key": CLIENT_ID}
MIN_API_INTERVAL = 15.0  # Default 4-RPM org entitlement; replace with shared storage at scale.
_last_api_request = 0.0


def wait_for_org_quota():
    global _last_api_request
    delay = MIN_API_INTERVAL - (time.monotonic() - _last_api_request)
    if delay > 0:
        time.sleep(delay)
    _last_api_request = time.monotonic()


def retry_after_seconds(value, fallback):
    if not value:
        return fallback
    try:
        return max(0.0, float(value))
    except ValueError:
        when = parsedate_to_datetime(value)
        if when.tzinfo is None:
            when = when.replace(tzinfo=timezone.utc)
        return max(0.0, (when - datetime.now(timezone.utc)).total_seconds())


def open_bytes(req, *, attempts=5):
    for attempt in range(attempts):
        wait_for_org_quota()
        try:
            with urllib.request.urlopen(req, timeout=60) as response:
                return response.status, dict(response.headers), response.read()
        except urllib.error.HTTPError as exc:
            detail = exc.read().decode("utf-8", "replace")
            retryable = exc.code == 429 or 500 <= exc.code < 600
            if not retryable or attempt == attempts - 1:
                raise RuntimeError(f"HTTP {exc.code}: {detail}") from exc
            fallback = min(30, 2 ** attempt + random.random())
            time.sleep(max(MIN_API_INTERVAL, retry_after_seconds(exc.headers.get("Retry-After"), fallback)))
        except (urllib.error.URLError, TimeoutError) as exc:
            if attempt == attempts - 1:
                raise RuntimeError(f"Network failure: {exc}") from exc
            time.sleep(max(MIN_API_INTERVAL, min(30, 2 ** attempt + random.random())))
    raise AssertionError("unreachable")


def upload_png(path):
    req = urllib.request.Request(
        f"{BASE}/v2/storage/image",
        data=Path(path).read_bytes(),
        method="POST",
        headers={**AUTH, "Content-Type": "image/png", "Accept": "application/json"},
    )
    _, _, raw = open_bytes(req)
    return json.loads(raw)["images"][0]["id"]


def json_request(url, *, method="GET", payload=None):
    data = None if payload is None else json.dumps(payload).encode("utf-8")
    req = urllib.request.Request(
        url,
        data=data,
        method=method,
        headers={**AUTH, "Content-Type": "application/json", "Accept": "application/json"},
    )
    _, _, raw = open_bytes(req)
    return json.loads(raw)


source_id = upload_png("source.png")
mask_id = upload_png("mask.png")
job = json_request(
    f"{BASE}/v3/images/fill-async",
    method="POST",
    payload={
        "image": {"source": {"uploadId": source_id}},
        "mask": {"source": {"uploadId": mask_id}, "invert": False},
        "prompt": (
            "replace only the masked vase with a handmade cobalt ceramic vase, "
            "preserve table and shadows"
        ),
        "negativePrompt": "lettering, logo, extra objects",
        "promptBiasingLocaleCode": "en-US",
        "numVariations": 4,
        "seeds": [101, 202, 303, 404],
        "size": {"width": 2048, "height": 2048},
    },
)
Path("fill-accepted.json").write_text(json.dumps(job, indent=2), encoding="utf-8")

deadline = time.monotonic() + 300
while time.monotonic() < deadline:
    result = json_request(job["statusUrl"])
    state = result.get("status", "")
    if state == "succeeded":
        Path("fill-result.json").write_text(json.dumps(result, indent=2), encoding="utf-8")
        break
    if state in {"failed", "cancelled", "canceled"}:
        raise RuntimeError(json.dumps(result))
else:
    cancel_result = None
    try:
        cancel_result = json_request(job["cancelUrl"], method="PUT")
    except Exception as exc:
        cancel_result = {"error": str(exc)}
    Path("fill-accepted.json").write_text(
        json.dumps({"accepted": job, "cancel_attempt": cancel_result}, indent=2),
        encoding="utf-8",
    )
    raise TimeoutError("Fill job did not finish within 300 seconds")

outputs = result.get("result", {}).get("outputs", [])
if len(outputs) != 4:
    raise RuntimeError(f"Expected four outputs, received {len(outputs)}")
suffixes = {"image/png": ".png", "image/jpeg": ".jpg", "image/webp": ".webp"}
for index, output in enumerate(outputs, start=1):
    with urllib.request.urlopen(output["image"]["url"], timeout=120) as response:
        content_type = response.headers.get_content_type()
        suffix = suffixes.get(content_type)
        if suffix is None:
            raise RuntimeError(f"Unexpected output Content-Type: {content_type}")
        Path(f"fill-output-{index}{suffix}").write_bytes(response.read())

预期结果:fill-result.json 中包含 4 个 URL 及其种子,另有 4 个下载到本地的图像文件。可能的失败原因:源图与蒙版尺寸不匹配、蒙版意图颠倒、不支持的尺寸、种子数量不匹配、返回的媒体类型异常,或提示词要求填充端点更改蒙版未覆盖的像素。

安全地操作异步任务

  1. 轮询前先持久化提交响应。
  2. 遵循返回的 statusUrl 或 links.result.href;不要根据任务 ID 重构主机名。
  3. 将上传、提交和轮询请求统一纳入组织级限流器。在默认每分钟 4 次请求的额度下,所有工作进程发出的 Firefly API 请求之间平均至少间隔 15 秒;使用自适应轮询,遵守 Retry-After,只有在组织的合同额度允许时才加快轮询。较早的一秒间隔示例不能取代当前使用说明。
  4. 设置本地截止时间并提供取消功能。当前任务的取消方式是向返回的取消链接发送 PUT 请求。
  5. 对 429 和暂时性 5xx 错误,使用带随机抖动的指数退避重试。对未修改的 400/403/413/415/422 错误不要重试。
  6. 将 401 视为需要刷新令牌,然后仅重试一次。重复出现 401 表示配置错误。
  7. 立即将输出下载到受控存储中;预签名 URL 是交付机制,不是持久化资产管理方案。
  8. 存储端点、模型标头、移除机密信息后的载荷、种子、上传 ID、源文件哈希、任务 ID、Adobe 请求/关联标头、输出哈希、媒体类型、尺寸和审核状态。

当前参考文档中常见响应的含义:

状态操作
400修正格式错误的请求;验证 JSON 和路由
401刷新/验证 Bearer 令牌
403检查授权、产品配置文件、组织、策略、自定义模型访问权限和区域
404/410将任务或临时资源视为不可用/已过期;不要循环重试
408/499请求/客户端中断;重新提交前确认任务是否已被接受
409任务状态冲突,通常是终止后取消;获取状态
413减少上传/请求大小
415修正 Content-Type 或图像编码
422修正字段、枚举值、大小、蒙版、种子数量、模型标头或不兼容的架构
429遵守 Retry-After,执行退避,并实施组织级准入控制
451停止并上报法律/可用性限制;不要规避限制
500/503设置重试上限;若问题持续,查看 Adobe 状态/联系支持

切勿自动修改被拒绝的提示词以绕过安全过滤器。请说明失败类别,并请求用户提供符合要求的修订内容。

容量、访问权限和成本

已记录,已于 2026-07-09 验证:Firefly API 的默认限制为每个组织每分钟 4 次请求,每天 9,000 次请求。如果账户经理提高每分钟请求数,但未更改每日请求数,每日限制就会变得重要。更高的限制需联系账户经理。请在各工作进程之间构建共享限流器;除非合同另有说明,轮询也会消耗请求额度。请参阅使用说明。

Firefly Services 是面向企业提供的服务。引用的开发者架构中没有通用的公开单张图像价格。Adobe 按 API 工作量以 Operations 计量;一个 API 操作或工作流可能消耗多个 Operation,适用费率可在 Admin Console/合同中查询。不要编造美元估算。在报价批量任务前,请查询组织的授权额度、费率卡、包含的 Operations、超额费用条款、模型/功能可用性和区域限制。请参阅 Shared Credit 产品说明。

权利、安全、隐私和来源信息

输入和输出

确保拥有提示词、参考图像、蒙版、人物/肖像、品牌、自定义模型训练数据及预期分发方式的相关权利。Adobe 当前的 2026v2 企业 API 条款规定,输入和输出属于客户内容,并提醒输出可能并非独一无二。这些条款还限制使用 Developer Tools 或其衍生内容/数据/输出来创建、训练、测试或改进 AI 系统,并限制客户软件仅供内部使用,除非销售订单明确允许其他用途。这并不保证内容可获得版权保护、具有独占性、不构成侵权、适合预期用途,或可用于面向外部的集成。请与法律顾问一起审阅当前的 Adobe Creative API 产品条款页面、2026v2 PSLT 和客户的销售订单。

Adobe 表示,Firefly 基础模型使用授权内容(如 Adobe Stock)和公有领域内容进行训练,并称不会使用客户/用户内容训练这些模型。Firefly Services 安全说明书指出,企业输入/输出不会纳入基础模型训练,但用户自行控制的反馈/改进计划或服务本身即为 AI 训练的功能属于有限例外。应将其表述为 Adobe 记录在案的政策,而非经过独立审计的保证。请参阅 Adobe 的方法 和 Firefly Services 安全事实说明书。

赔偿

切勿不加限定地声称“Firefly 输出享有赔偿保障”。Adobe 合同中的 Firefly 输出知识产权赔偿仅在销售订单/符合条件的优惠中明确包含该赔偿时适用;适用范围仅限列明的符合条件的 Firefly 功能和界面,并须在定义的导出事件发生后,且受管辖协议中的排除条款、义务和责任上限约束。当前条款排除的情形包括因修改或组合 Firefly 输出而产生的索赔,这与合成、重新编码或其他后期处理直接相关。产品说明将 Firefly API 列为一种界面,但前提是符合条件的客户使用 Operations 并下载输出;并非所有功能都必然适用于每种界面。请核实当前的 Firefly 产品说明、销售订单、2026v2 PSLT、符合条件的功能、导出证据及下游转换。法律解释问题请咨询法律顾问。

安全

遵循当前的 Adobe 生成式 AI 用户指南。该指南禁止非法或有害用途、露骨色情内容、性化未成年人、仇恨/贬损内容、美化血腥暴力、宣扬自残或暴力极端主义、有害欺骗、侵犯隐私、不合规的受监管活动、欺骗性冒充以及侵犯第三方权利。Adobe 表示,提示词、输入和输出可能会经过自动和人工审核,以防止滥用并进行过滤。

在适用情况下,添加应用级别的同意、年龄、权利、隐私、冒充和受监管用途把关机制。对于公开声明、新闻类图像、真实人物描绘、儿童、健康/金融/法律场景、选举及高影响决策,应保留人工审核。API 成功响应并不代表已通过安全或权利审核。

隐私与保留期限

Adobe 安全事实表中记录的企业服务默认设置: Firefly 生成式 API 的提示词文本、配置设置和假名化用户 ID 会保留 90 天;通过 API 上传的参考内容会缓存 24 小时;生成的 Firefly API 图像会存储 24 小时,并通过预签名 URL 提供访问;Content Credentials 的加密哈希和清单信息可能会保留在 Content Credentials 云端。请确认当前协议和区域部署情况,因为产品界面和合同可能会影响数据处理方式。

尽量减少个人或机密数据,使用短时有效的源 URL,避免在提示词中嵌入机密,将结果下载到客户受管控的存储空间,并按照政策删除本地暂存副本。公开的 v4 请求架构不提供由客户端控制的 storeInputs 开关;不要凭空编造这样的开关。

Content Credentials

Adobe 表示,对于像 Text to Image 这类像素完全由 Firefly 生成的素材,会自动附加 Content Credentials。凭证会标明 Adobe 为签发方,并添加可检测篡改的生成来源信息;但无法证明内容真实、所有权归属、已获同意或安全。请保留交付的原始文件及其清单。不要把剥离来源信息作为常规优化。如果流水线对素材进行重新编码、合成或导出,请测试凭证是否保留;如果组织的工具支持,请创建新的签名来源记录。请参阅 Adobe 的 Content Credentials 概述和主要的 C2PA 规范。

质量保障关卡

任何适用检查未通过时,都应拒绝或修改素材:

  • 提示词意图:主体、动作、数量、本地化要求和排除项均符合要求。
  • 构图:裁剪、安全区域、留白、地平线、透视和目标纵横比均正确。
  • 解剖与几何:手部、面部、倒影、阴影、重复结构和物体接触关系均协调合理。
  • 产品/品牌:轮廓、比例、颜色、材质、标签、徽标和声明均与获批参考一致;需要保留原样时,执行像素差异比较。
  • 编辑隔离:比较未更改区域;以 100% 和 200% 缩放检查蒙版接缝、光晕、纹理重复和光照过渡。
  • 文本:每个字符都清晰且正确;否则在后期替换。
  • 技术:能够成功解码;媒体类型、像素尺寸、色彩配置文件、Alpha 通道、文件大小和校验和均符合交付要求。
  • 无障碍:检查生成的 altText;不要仅仅因为 prompt_reasoner: quality 填充了它就予以信任。
  • 安全/权利:已完成人工审查,评估相关风险;已记录参考素材和肖像使用许可。
  • 来源信息:已保留原始文件和 Content Credentials,或存在有记录的转换链。
  • 可复现性:已存储端点、模型标头、请求、种子、源文件哈希、作业 ID 和审核决定,且不含机密。

对于批量生成,应在生成前设定数值验收阈值,例如标签零改动、蒙版外区域的改动不超过经测量的容差、尺寸完全一致,以及人工拒绝率上限。应针对不同提示词、本地化设置、种子和参考类型进行抽样;不要仅凭一张出色的主视觉图就批准整条流水线。

已核查的来源

所有时效性信息均于 2026-07-09 根据第一方来源核查:

评论

支持 Markdown