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. 代表的なサンプルを1件実行し、すぐにダウンロードして、規模を拡大する前に視覚面と来歴の QA を行います。

現行の API とモデルの対応表

2026-07-09 に確認したドキュメント記載内容:

必要な操作エンドポイントモデルの選択主な制約
Image 5 の新規生成POST /v4/images/generate-asyncヘッダー x-model-version: image5、本文 modelId: firefly_image1 リクエストにつきバリエーションは 1 つ。参照画像は最大 1 枚
Image 5 の自然言語による編集同じ v4 エンドポイント同じヘッダー/本文空でない referenceBlobs 配列を指定すると、操作が指示編集に切り替わります
Image 3/4 の生成と明示的な制御POST /v3/images/generate-asyncimage3、image4_standard、または image4_ultraバリエーションは最大 4 つ。スタイル/構造の制御はここで行います
カスタムモデルによる生成同じ v3 エンドポイントimage3_custom または image4_custom と customModelIdカスタムモデルはプロビジョニングされ、アプリと共有されている必要があります
類似画像POST /v3/images/generate-similar-asyncimage3、image4_standard、または image4_ultra元画像が必要です
マスクによる置き換えPOST /v3/images/fill-asyncImage 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なし7 日間有効なアップロード 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の後で一度だけ再試行してください。Authentication と認証情報の設定を参照してください。

トークンリクエスト:

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:整数を最大1つ指定できます。両方を指定する場合、その個数は numVariations と一致する必要があります。
  • modelSpecificPayload.localeCode:Image 5 で使用するロケール制御。
  • modelSpecificPayload.prompt_reasoner:speed(デフォルト)または quality。Adobe によると、quality は altText を設定し、speed は空で返します。
  • referenceBlobs:最大1つ。空の場合はテキストから画像を生成し、usage: general を指定した空でない値の場合は指示による編集を行います。指定する場合は aspectRatio を省略するか、auto に設定してください。

v3 専用の style、structure、contentClass、visualIntensity、customModelId フィールドを v4 に送信しないでください。v4 の操作スキーマには、ネガティブプロンプトフィールドや任意の出力形式セレクターは記載されていません。JPEG や PNG と決めつけず、実際のレスポンスアセットの Content-Type を検証してください。

v4 の送信は、links.result.href、links.cancel.href、進捗値を含む HTTP 200 を返します。処理は引き続き非同期です。返された結果リンクを、同じ認証ヘッダーを使って終端状態になるまでポーリングし、その後、画像 URL から速やかに画像をダウンロードしてください。

画像3/4:v3 の生成

ドキュメント記載の v3 フィールド:

  • prompt:必須。1~1024文字。
  • negativePrompt:最大1024文字。Image 3 または Image 4 のカスタムモデルではサポートされません。
  • contentClass:photo または art。
  • numVariations:1~4。シードを指定する場合、シード数はバリエーション数と一致する必要があります。
  • promptBiasingLocaleCode:en-US など、BCP-47 に類似した言語・地域の値。
  • style.presets:ドキュメント記載のスネークケースのプリセット。意図を持って組み合わせてください。
  • 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 の送信は、jobId、statusUrl、cancelUrl を含む HTTP 202 を返します。statusUrl をポーリングしてください。実行中のレスポンスには status: running が含まれ、成功時には status: succeeded と、シードおよび画像 URL をそれぞれ含む result.outputs[] が含まれます。failed、cancelled、HTTP の終端エラー、ローカルタイムアウトは終端結果として扱い、サポートに問い合わせる際に備えてレスポンス本文とリクエスト ID を記録してください。

アップロード、マスク、ソース URL

アップロード API はバイナリの画像データを受け付けます。現在の API リファレンスでは、PNG、JPEG、WebP、TIFF、JXL が最大 15 MB で利用できると記載されていますが、古いアップロードガイドでは JPEG、PNG、WebP のみが記載されています。**本番環境での経験則:**マスクや透過には PNG、通常の入力には JPEG または PNG を使用してください。ただし、対象アカウントでより幅広い形式がテスト済みの場合はこの限りではありません。

アップロードの応答は {"images":[{"id":"..."}]} の形式です。API の仕様では、この ID は 7 日間有効とされています。一方、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 が取得できると思い込まないでください。

マスクは注釈ではなく、本番用アセットです。課金対象の呼び出しを行う前に、寸法、アルファ値とグレースケールの意味、反転、ぼかし、位置合わせを確認してください。expand-async では、マスクも適用する場合、ソースに placement を使用できません。出力は最大 3999×3999 になります。局所的なマスク領域の置き換えには Fill、より大きなキャンバスには Expand、参照画像が 1 枚で十分な自然言語による画像全体の調整には Image 5 instruct edit を使用してください。

固定したい要素に応じて参照画像と合成方法を選ぶ

スタイルと構造

パレット、媒体、照明、雰囲気などの視覚的な特徴を転写するには、v3 の style.imageReference を使用します。輪郭や奥行きを導くには、structure.imageReference を使用します。強度は 1〜100 で、準拠度を調整できます。デフォルトは 50 です。これらの設定は併用できますが、両方の強度を高くすると、プロンプトへの準拠度が下がる場合があります。

**本番環境での経験則:**同じプロンプトとシードを使い、たとえば 30、60、90 のように、一度に 1 つの設定だけを変えて比較し、文書化した評価基準で選択してください。シードは再現性を高める手段であり、モデル、エンドポイント、サービスの更新をまたいで結果を保証するものではありません。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 は元のオブジェクトの外観を維持します。最大 3 種類のバリエーションを生成できます。
  • 既存の背景になじむリアリティを、オブジェクトのピクセル単位での正確な保持より優先する場合は、Adaptive Composite を使用します。harmonization と shadowIntensity の範囲は 0〜1 です。preserveBackground を指定すると、オブジェクト領域を変更しながら元の背景ピクセルを保持します。最大 3 種類のバリエーションを生成できます。

3 つとも非同期です。「precise」であっても、ピクセル差分の確認やラベルの判読性チェックが不要になるとは約束しないでください。変更が許容され、レビュー済みでない限り、規制対象のパッケージや製品表示には Adaptive Composite を使用しないでください。

アップスケール

現在の POST /v3/images/upscale を x-model-version: precise_upsampler_v1 とともに使用し、入力画像ソース、1〜4個のシード、upscaleFactor に 2、3、4、または 6 を指定します。返された links.result.href をポーリングします。API リファレンスでは、出力サイズは入力サイズに係数を掛けたものと説明されています。サービスの制限と実際の結果を確認してください。説明文では最大 6k とされていますが、エンドポイントのスキーマでは係数が中心に記載されているため、すべての係数が受け入れられると想定せず、大きな入力は事前に検証してください。

文章を書く前に画像を明確に指示する

制作上のヒューリスティック: プロンプトは形容詞の羅列ではなく、簡潔なアートディレクションの指示として表現します。以下を指定してください。

  1. 被写体と動作;
  2. 環境と時代;
  3. フレーミング、視点、レンズ表現、またはレイアウト;
  4. 照明と色の関係;
  5. 素材と表面の特徴;
  6. 必ず表示する必要があるテキスト。不可欠な場合に限り、正確に引用する;
  7. 除外事項。利用可能な場合は、API で文書化されたネガティブプロンプトフィールドを使用する。

権利を持たない限り、存命のアーティストの作風を模倣したり、サードパーティの商標、顔、著作権で保護されたキャラクター、参照アセットを使用したりしないでください。ローカライズでは、API のロケールフィールドを設定し、ネイティブスピーカーに文化的な詳細を確認してもらいます。ロケールによる偏りは、翻訳でも法的な許諾でもありません。

正確なタイポグラフィ、ロゴ、パッケージのコピー、QR コード、コンプライアンスマークが必要な場合は、クリーンなビジュアルのベースを生成し、その後、承認済みのベクター/ラスターアートワークを合成します。モデルが描画したテキストは下書きとして扱ってください。製品画像では、後からオーバーレイを追加できるよう、ネガティブスペースとカメラの構図を確保するよう指示します。

例 1 — Image 5 生成クライアントの完全な例

必須の形式ではなく、あくまで一例です。 この Python 3.11 以降の標準ライブラリプログラムは、トークンを取得し、Image 5 のジョブを 1 つ送信し、上限付きの指数バックオフで返された結果 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 アセットが 1 つと、監査用 JSON ファイルが生成されます。起こり得る失敗:エンタイトルメントの欠如(403)、v3 スキーマのフィールドを誤って v4 に送信(422)、組織単位のスロットリング(429)、トークンの期限切れ(401)、またはダウンロード前に出力 URL の期限が切れること。

意味のある変更例:レイテンシをテストするには prompt_reasoner を speed に切り替えます。アカウントの料金表で有利な場合は、低コストの事前確認に resolutionLevel: 1MP を使用します。または、許可された referenceBlobs アイテムを 1 つ指定し、アスペクト比を削除するか自動に設定して、編集指示としてプロンプトを作成します。

例 2 — アップロードとマスク付き塗りつぶし

必須の形式ではなく、あくまで一例です。 この Python 3 プログラムは PNG のソース画像とマスクをアップロードし、シード値を指定した v3 の塗りつぶしバリエーションを 4 つ送信してから、返されたステータス 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. アップロード、送信、ポーリングは、組織全体で共有する 1 つのリミッターを経由させます。デフォルトの 4 RPM の利用枠では、すべてのワーカーからの Firefly API リクエストの間隔を平均 15 秒以上にしてください。適応型ポーリングを使用し、Retry-After に従い、組織の契約上の上限が対応している場合にのみ、より短い間隔でポーリングします。以前の 1 秒間隔の例は、現在の利用上の注意に優先しません。
  4. ローカルの期限を設定し、キャンセル機能を提供します。現在のジョブをキャンセルするには、返されたキャンセルリンクに PUT を送信します。
  5. 429 と一時的な 5xx には、ジッターを加えた指数バックオフで再試行します。変更していないリクエストを 400/403/413/415/422 で再試行しないでください。
  6. 401 はトークンを更新してから 1 回再試行します。401 が繰り返される場合は、設定に問題があります。
  7. 出力はすぐに管理下のストレージにダウンロードします。署名付き URL は配信手段であり、永続的なアセット管理手段ではありません。
  8. エンドポイント、モデルヘッダー、シークレットを削除したペイロード、シード、アップロード ID、ソースハッシュ、ジョブ ID、Adobe のリクエスト/相関ヘッダー、出力ハッシュ、メディアタイプ、寸法、レビュー状況を保存します。

現在のリファレンスに記載されている一般的なレスポンスの意味:

ステータス対応
400不正なリクエストを修正します。JSON とルートを検証してください
401ベアラートークンを更新/確認します
403利用資格、製品プロファイル、組織、ポリシー、カスタムモデルへのアクセス、リージョンを確認します
404/410ジョブまたは一時リソースを利用不可/期限切れとして扱い、繰り返し試行しないでください
408/499リクエスト/クライアントの中断です。再送信する前に、ジョブが受け付けられたか確認します
409ジョブ状態の競合です。多くの場合、完了後のキャンセルが原因です。ステータスを取得してください
413アップロード/リクエストのサイズを縮小します
415Content-Type または画像のエンコーディングを修正します
422フィールド、列挙値、サイズ、マスク、シード数、モデルヘッダー、または互換性のないスキーマを修正します
429Retry-After に従い、バックオフし、組織全体でリクエストの受付を制御します
451停止して、法的制限/提供制限としてエスカレーションします。回避しないでください
500/503上限を設けて再試行します。問題が続く場合は Adobe のステータス/サポートを確認してください

安全フィルターを回避するために、拒否されたプロンプトを自動的に変更しないでください。失敗のカテゴリを提示し、準拠した内容への修正を求めてください。

容量、アクセス、費用

記載内容の確認日:2026-07-09: Firefly API のデフォルトの制限は、組織あたり 1 分につき 4 リクエスト、1 日につき 9,000 リクエストです。アカウントマネージャーが RPD を変更せずに RPM を引き上げた場合、1 日あたりの制限が関係してきます。より高い制限については、アカウントマネージャーにお問い合わせください。ワーカー間で共有するリミッターを構築してください。契約に別途記載がない限り、ポーリングもリクエスト数に含まれます。利用上の注意を参照してください。

Firefly Services は、エンタープライズ向けにプロビジョニングされるサービスです。参照されている開発者向けスキーマには、すべてのお客様に適用される公開の画像あたり料金はありません。Adobe は API の処理をオペレーション数で計測します。API アクションまたはワークフローが複数のオペレーションを消費することがあり、適用される料金表は Admin Console/契約で確認できます。金額を推測して提示しないでください。一括処理の見積もりを提示する前に、組織の利用資格、料金表、含まれるオペレーション数、超過料金の条件、モデル/機能の提供状況、地域制限を確認してください。Shared Credit の製品説明を参照してください。

権利、安全性、プライバシー、来歴

入力と出力

プロンプト、参照画像、マスク、人物/肖像、ブランド、カスタムモデルのトレーニングデータ、および予定している配布について、必要な権利を確保してください。Adobeの現行の2026v2エンタープライズAPI規約では、入力と出力はCustomer Contentとされ、出力が独自のものとは限らないと警告しています。また、Developer Toolsやそれらから派生したコンテンツ/データ/出力を使用して、AIシステムを作成、トレーニング、テスト、または改善することを制限し、Sales Orderで別の用途が明示的に許可されていない限り、Customer Softwareを社内利用に限定しています。これにより、著作権の成立、独占性、権利侵害がないこと、適合性、または外部向けの連携に関する許可が保証されるわけではありません。弁護士とともに、現行のAdobe Creative API製品規約のランディングページ、2026v2 PSLT、および顧客のSales Orderを確認してください。

Adobeは、Fireflyの基盤モデルのトレーニングに、Adobe Stockなどのライセンス済みコンテンツやパブリックドメインのコンテンツを使用していると説明し、顧客/ユーザーのコンテンツをそれらのモデルのトレーニングに使用していないと述べています。Firefly Servicesのセキュリティシートには、ユーザーが管理するフィードバック/改善プログラムや、サービスがAIトレーニングである機能に関する限定的な例外を除き、エンタープライズの入力/出力は基盤モデルのトレーニングに含まれないと記載されています。これはAdobeが文書で公表しているポリシーとして提示し、独立監査済みの保証として扱わないでください。AdobeのアプローチおよびFirefly Servicesのセキュリティファクトシートを参照してください。

補償

条件を付けずに「Fireflyの出力は補償される」と言わないでください。Adobeの契約上のFirefly Output IP補償は、Sales Order/対象となるオファーに具体的に含まれている場合に限り、記載された対象Firefly機能およびサーフェスに適用され、定義されたExport Eventの後に有効となり、適用される契約に定められた除外事項、義務、および責任上限に従います。現行規約では、特にFirefly Outputの変更または組み合わせから生じる請求が除外されています。これは合成、再エンコード、その他の後処理に直接関係します。製品説明では、対象となる顧客がOperationsを利用して出力をダウンロードした場合、Firefly APIはサーフェスとして記載されていますが、すべての機能があらゆるサーフェスで対象となるとは限りません。現行のFirefly製品説明、Sales Order、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が自動的に付与されます。Content CredentialsはAdobeを発行者として示し、生成による制作過程を改ざん検知可能な形で記録しますが、事実の正確性、所有権、同意、安全性を証明するものではありません。納品された元ファイルとそのマニフェストを保持してください。通常の最適化として来歴情報を削除しないでください。パイプラインでアセットを再エンコード、合成、またはエクスポートする場合は、Content Credentialsが維持されるかをテストし、組織のツールが対応している場合は、新しい署名付き来歴レコードを作成してください。AdobeのContent Credentialsの概要と、一次資料であるC2PA仕様を参照してください。

品質保証ゲート

該当するチェック項目に不合格があれば、アセットを却下するか修正してください。

  • プロンプトの意図:被写体、動作、数、ロケール、除外事項が満たされています。
  • 構図:トリミング、安全領域、ネガティブスペース、地平線、遠近法、目標アスペクト比が適切です。
  • 人体と形状:手、顔、反射、影、反復する構造、物体の接触が自然です。
  • 製品/ブランド:シルエット、比率、色、素材、ラベル、ロゴ、訴求内容が承認済みの参照と一致しています。保持が必要な場合はピクセル差分を確認してください。
  • 編集範囲の分離:変更していない領域を比較し、100%と200%の表示倍率でマスクの境界、ハロー、テクスチャの反復、照明の変化を確認してください。
  • テキスト:すべての文字が読みやすく、正確です。そうでない場合は後処理で置き換えてください。
  • 技術要件:デコードに成功し、メディアタイプ、ピクセル寸法、カラープロファイル、アルファ、ファイルサイズ、チェックサムが納品条件を満たしています。
  • アクセシビリティ:生成されたaltTextを確認してください。prompt_reasoner: qualityによって入力されていても、無条件に信頼しないでください。
  • 安全性/権利:関連するリスクについて人間によるレビューが完了しており、参照素材と肖像の使用許諾が記録されています。
  • 来歴:元ファイルとContent Credentialsが保持されているか、文書化された変換チェーンが存在します。
  • 再現性:エンドポイント、モデルヘッダー、リクエスト、シード、ソースハッシュ、ジョブID、レビュー結果が、シークレットを含めずに保存されています。

バッチ処理では、生成前に数値による受け入れ基準を定めてください。たとえば、ラベルの変更がゼロであること、マスク外の変更が測定許容範囲を超えないこと、寸法が正確であること、手動却下率が上限以下であることなどです。さまざまなプロンプト、ロケール、シード、参照タイプをサンプリングしてください。見栄えのよいヒーロー画像が1枚できただけで、パイプラインを承認しないでください。

確認した情報源

変動するすべての情報は、2026-07-09 に以下の一次情報源で確認しました。

コメント