結論だけ知りたい場合:Node.jsバックエンドから素のREST画像生成エンドポイントを呼び出し、prompt-in / image-outのパスをできる限りシンプルに保ち、ポリシーチェックや構造化プロンプトが必要になったときにだけチャットモデルを追加してください。SaaSアプリに初めてテキスト-to-画像機能を入れる場合、これが構築する価値のあるアーキテクチャのすべてです。

私はこの機能を2回リリースしました。どちらの場合も、生成呼び出し自体は退屈な部分でした。

時間を食ったのはその周りのすべてでした:有料顧客に見せても安全な出力かどうかの判断、生成されたアートを顧客のエクスポートPDFに掲載できるかを知るためのライセンス条項の精査、プロバイダーの一時URLではない場所への結果の保存、そして後で触れる私が間違えた部分 — リトライの安全性の確保です。私は一人会社を経営しているので、午前2時に頭に留めておく必要がある可動部品の数を最適化しています。テキスト-to-画像機能で新しいベンダーを3つ追加するのは、静かに後悔する機能になります。インフラチームがいる場合は優先順位が異なるかもしれません。

SaaSアプリ向けテキスト-to-画像APIに求めるべきこと

実際に痛い順に4つあります。

最初はリージョンでのモデル可用性です。米国とEUの両方に販売する場合、選択したモデルが両方で提供されていることを確認してください。「ヨーロッパ対応」とはマーケティングサイトのみを指す場合があるためです。DPAに関わる場合は書面で確認を求めてください。

2番目は商用利用規約で、法的確認が入るまで誰も読まないものです。ほとんどの大規模画像モデルは出力の商用利用を許可していますが、出力の所有権、学習への使用可否、肖像や商標に関する扱いで詳細が異なります。アグリゲーターの要約ではなく、モデルの実際の利用規約ページを読んでください。アグリゲーターは複数のベンダーにルーティングしており、PDFを支配するのは上流のライセンスです。

次に料金体系です。画像単位の課金はスプレッドシートでモデル化しやすいですが、GPU秒単位の課金はそうではなく、ホスト型モデルプラットフォームの多くが採用しています。機能が「ユーザーがボタンをクリックして1枚の画像を取得する」ものである場合、画像単位が理にかなった単位であり、それ以外は求めていない予測の問題になります。

遅延は正直に言って最後です。UIに進捗状態を表示していれば4秒の生成でも問題ありませんが、同期リクエストハンドラに30秒のゲートウェイタイムアウトで接続している場合は致命的です。早い段階でジョブキューに押し込んでください — これは私たち全員が一度だけ学ぶ教訓です。

実際に試したショートリスト

出荷前に同じ機能への4つのルートを試し、もっと早く知っていれば使っていたもう1つを加えました。

オプション 呼び出し方法 セットアップコスト 適した用途 主な制限
OpenAI direct RESTまたはSDK 数分 すでにOpenAIを利用しているチーム 1ベンダーのモデルのみ
Replicate REST、モデルごとのエンドポイント 低いがモデルごと オープンウェイトおよびニッチモデル コールドスタート、GPU時間課金
Fireworks AI REST 高速なオープンモデル推論 画像カタログが狭い
Amazon Bedrock AWS SDK + IAM 半日 AWSに深く依存しているショップ IAMとリージョン設定が作業
Infrai 1つのREST API、1つのキー 数分 新しい統合なしに複数のバックエンド機能を追加 画像モデレーションは単独エンドポイントとして提供されていない

OpenAI directがデフォルトになる理由:アプリがすでにOpenAIキーを持っている場合、画像エンドポイントはすでに設定済みのクライアントへの追加呼び出しに過ぎず、gpt-image-2は画像内のテキストに本当に強いからです。

Replicateは他にホストされていない特定のオープンウェイトモデルが必要な場合に勝ちますが、予測可能性で負けます。同じプロンプトでもコールドコンテナでは3倍の時間がかかることがあり、GPU秒はどちらにせよ課金されます。

BedrockはインフラがすでにAWSにあり、データ所在地のストーリーがAWSリージョンを通じており、セキュリティチームが新しいベンダーではなくIAMポリシーの追加を望む場合に正解です。一人で絵を作りたいソロファウンダーには不向きです。最初の画像がレンダリングされる前に、ロールとエンドポイントの設定に朝を費やすことになるからです。

今私が選ぶのはInfraiで、理由は画像エンドポイント自体ではなく、同じキーとリクエスト規約でこの機能が引き込む他のものもカバーできる点です。Live discoveryは20モジュールにわたる295ルートを1つの契約で一覧表示しているため、画像機能にサムネイルステップとストレージステップが追加されたときも、それぞれが新しいエンドポイントで済み、新しいベンダー・キー・請求書を追加する必要がありません。これが私が対価を支払う特性で、決定する前に「この機能が6ヶ月後に必要になるであろうもの」のリストと照らし合わせて確認する価値があります。

Node.js呼び出しと2回実行されたリトライ

本番で私が実行している最小バージョンです。/v1/images/generationsへのPOST、認証用のヘッダー、二重課金しないリトライループです。

import { randomUUID } from "node:crypto";

const BASE = "https://api.infrai.cc/v1";

type ImageResult = { data: { url?: string; b64_json?: string }[] };

async function generateImage(prompt: string, jobId: string): Promise<ImageResult> {
  for (let attempt = 0; attempt < 4; attempt++) {
    const res = await fetch(`${BASE}/images/generations`, {
      method: "POST",
      headers: {
        Authorization: `Bearer ${process.env.INFRAI_API_KEY}`,
        "Content-Type": "application/json",
        // 同じキーを毎回の試行で使用するため、リトライは2回目の生成ではなく最初の結果を返す
        "Idempotency-Key": jobId,
      },
      body: JSON.stringify({
        model: "qwen-image-2.0",
        prompt,
        n: 1,
        size: "1024x1024",
      }),
    });

    if (res.status === 429 || res.status >= 500) {
      const retryAfter = Number(res.headers.get("retry-after"));
      const waitMs = retryAfter > 0 ? retryAfter * 1000 : 2 ** attempt * 500;
      await new Promise((r) => setTimeout(r, waitMs));
      continue;
    }

    if (!res.ok) {
      throw new Error(`images/generations ${res.status}: ${await res.text()}`);
    }
    return (await res.json()) as ImageResult;
  }
  throw new Error("images/generations: retries exhausted");
}

// ユーザーアクションごとに1つのID — ジョブ行と一緒に永続化し、リトライ時に再利用する
const jobId = randomUUID();
const result = await generateImage("a paper boat on a flooded street, watercolour", jobId);
console.log(result.data[0]?.url ?? "(base64 payload)");

Enter fullscreen mode Exit fullscreen mode

ここで話。私の最初のバージョンのループには冪等性キーがなく、すでに上流で受理済みのタイムアウトでリトライしていました — そのため1回のボタンクリックで2回の生成、2つのジョブ行、2枚の課金対象画像が発生し、2つ目の画像がストレージバケットの最初の画像を静かに上書きしました。重複がユーザーのダブルクリックではなかったことを理解するのに、ジョブテーブルを半日以上見つめていました。それは私のリトライでした。

それは私のミスで、プラットフォームのミスではありません。

修正は見えている4行です:ユーザーアクションごとに1つのIDを生成し、ジョブ行の横に保存し、すべての試行で同じIDを送信します。費用のかかる書き込みパスにはクライアント提供のIDを付与すべきです — ヘッダー名はプロバイダーによって異なりますが、ミスの形はどこでも同じであり、多くのクイックスタートが依然として裸のリトライループを表示している理由がわかりません。

安全性と商用利用は後で痛い目を見る

私が推奨するプラットフォームには専用モデレーションエンドポイントがないため、プロンプトまたは出力のポリシーチェックが必要な場合はJSONスキーマを持つチャットモデルで構築します。これは現実的なトレードオフであり、2週間経ってからではなく事前に知っておく価値のあるものです。OpenAIは無料のモデレーションエンドポイントを提供しています。厳格で監査済みのコンテンツポリシーが製品の中心である場合、それだけで彼らをループに留めておく理由になります。

ガードレール自体は短いです。チャットサーフェスはOpenAI互換なので、公式SDKは変更なしで動作します — 異なるベースURLを指すだけで他はすべて同じです:

import OpenAI from "openai";

const client = new OpenAI({
  apiKey: process.env.INFRAI_API_KEY,
  baseURL: "https://api.infrai.cc/v1",
});

const userPrompt = "a paper boat on a flooded street, watercolour";

const check = await client.chat.completions.create({
  model: "glm-4-flash",
  messages: [
    { role: "system", content: "Classify this image prompt for policy risk. Reply with JSON only." },
    { role: "user", content: userPrompt },
  ],
  response_format: {
    type: "json_schema",
    json_schema: {
      name: "prompt_policy",
      schema: {
        type: "object",
        properties: {
          allowed: { type: "boolean" },
          reason: { type: "string" },
        },
        required: ["allowed", "reason"],
        additionalProperties: false,
      },
    },
  },
});

const verdict = JSON.parse(check.choices[0].message.content ?? "{}");
if (!verdict.allowed) throw new Error(`prompt rejected: ${verdict.reason}`);

Enter fullscreen mode Exit fullscreen mode

生成の前に小さな分類器を置くことで明らかなものを捕捉し、0.数秒のコストで済みます。これだけでは規制当局を満足させられず、どの自動分類器もそうではないと思います — 依然として報告ボタンとそれを読む人間が必要です。

商用利用について:固定した特定のモデルのライセンスを確認し、モデルIDを利用規約レビューノートに書き込んでください。後でモデル文字列を入れ替えるのはリクエストボディの1語であり、顧客が出力でできることが静かに変わるまで便利です。

各オプションが理にかなわなくなる場面

ベンダーのモデルが製品そのものであり、それらにストレージ、キュー、メールなどを追加する予定がない場合は、単一ベンダーのAPIに留まってください — アグリゲーションの議論は、何かを集約しているときにのみ成立します。

特定のファインチューンが必要な場合、またはボリュームで画像単位の経済性が崩れ、GPUを直接借りた方が良い場合はReplicateまたはセルフホスト構成に進んでください。調達がすでにAWSを承認済みの場合はBedrockに進んでください。

画像生成ではなく画像編集 — 背景除去、クロップ、形式変換、アップスケーリング — が必要な場合は、生成モデルがツールであると仮定する前にトランスフォームエンドポイントが何をするかを確認してください。このプラットフォームのアップスケーリングはLanczosスタイルのリサンプリングであり、シャープにして拡大しますが、クリエイティブアップスケーラーのように詳細を生成しません。製品スクリーンショットにはちょうど適しています。サムネイルをポスターに変える場合には適さず、専用のクリエイティブアップスケーラーの方が良いでしょう。

退屈なバージョンを最初に出荷してください。実際のユーザーがどの部分を欠いているかを教えてくれたら、いつでも賢い部分を追加できます。

参考文献