Seedance 2.5 is live — 30-second cinematic video with native audio & real-person references
KI-Bildgenerierung für Spiele: Kosten durch Caching senken
2026/09/03

KI-Bildgenerierung für Spiele: Kosten durch Caching senken

Cache-First-Pipeline für Spielbilder mit semantischen Keys, Single-Flight-Anfragen, asynchronem Polling, Budget-Caps und praktischer Kostenrechnung.

Caching kann die durchschnittlichen KI-Bildgenerierungs-Kosten pro Spieleraktion auf $0.002 senken, kann aber ein wirklich neues Bild nicht billiger machen. Bei Z-Images aktuellem reAPI-Preis von $0.005 pro abgeschlossenem Bild ist die Kontrolle: Paid-Miss-Rate × abgeschlossene Versuche pro akzeptiertem Asset ≤ 0.4. Eine 40%-Miss-Rate erreicht die Obergrenze nur, wenn jedes neue Asset beim ersten abgeschlossenen Versuch angenommen wird; um unter $0.002 zu bleiben, musst du unter dieser Grenze liegen.[1]

Cache die Bedeutung eines Gegenstands, nicht nur seinen Prompt. Merge gleichzeitige Misses, generiere nur wahrscheinliche Gegenstände voraus, und miss die Kosten pro akzeptiertem Spielasset statt pro API-Antwort.

TL;DR

  • Bei $0.005 pro abgeschlossenem Z-Image-Generate braucht ein Spiel, das auf durchschnittliche Kosten von $0.002 pro Craft abzielt: Paid-Miss-Rate × abgeschlossene Versuche pro akzeptiertem Asset ≤ 0.4. Mit genau einem Versuch erfüllt 40% die Obergrenze; darunter bedeutet weniger als 40%.[1]
  • Halte einen Recipe-Key für das, was der Spieler kombiniert hat, und einen semantischen Asset-Key für das resultierende Objekt und die Kunstversion.
  • Erzwinge einen aktiven Asset-Job mit einer Datenbank-Uniqueness-Constraint. Eine In-Memory-Promise-Map kann Same-Process-Aufrufer mergen, ist aber nicht die Sperre, die mehrere Worker schützt.
  • Bildgenerierung ist asynchron. Submit einmal, speichere die Task-ID, poll den Task-Endpoint, und kopiere abgeschlossene Ausgabe zu Speicher, den du kontrollierst. [2][3]
  • Ein Smoke-Test mit drei Tasks schloss drei ursprüngliche Kandidaten beim ersten Submit mit 15 Credits oder $0.015 insgesamt ab. Sie wurden nicht visuell überprüft, also sind sie Kandidaten, keine akzeptierten Assets.[4]
  • Wenn 3.000 tägliche Bilder bereits wirklich neue semantische Assets nach Caching sind, kann ein herkömmlicher Cache die Wirtschaft nicht retten. Das Spiel muss mehr Kunst wiederverwenden, die generative Fläche eingrenzen oder höhere Kosten akzeptieren.

Das echte Problem ist Kosten pro Craft, nicht Kosten pro Bild

Angenommen, ein Endlos-Crafting-Spiel kombiniert Gegenstände wie Eisen und Feuer und erzeugt sowohl ein neues Ergebnis als auch das dazugehörige Bild. Wie lassen sich die Medienkosten unter $0.002 pro Craft halten, wenn Spieler täglich 3.000 Craft-Vorgänge auslösen und viele Kombinationen noch nie vorgekommen sind? Genau darin liegt die entscheidende Einschränkung: Das Spiel kann neue Anfragen schneller erzeugen, als seine Einnahmen sie finanzieren können.

Beginne mit dieser Gleichung:

daily generation cost =
  craft events
  x paid miss rate
  x completed attempts per accepted asset
  x price per completed generation

Bei $0.005 pro abgeschlossenem Z-Image-Task ist die Obergrenze:

$0.005 x paid miss rate x completed attempts per accepted asset <= $0.002
paid miss rate x completed attempts per accepted asset <= 0.4

Für 3.000 Craft-Events pro Tag über 30 Tage sehen die Single-Attempt-Szenarien so aus:

Paid-Miss-RateNeue Generierungen/Tag30-Tage-API-Kosten
100%3.000$450
40%1.200$180
20%600$90
10%300$45

Dies sind Szenarien, keine vorhergesagten Hit-Raten, und jede Zeile setzt einen abgeschlossenen Versuch pro akzeptiertem Asset voraus. Bei 1,5 Versuchen zum Beispiel darf die Miss-Rate nicht mehr als 26,7% betragen, um die gleiche $0.002-Obergrenze zu halten. Wenn alle 3.000 Events nach semantischer Deduplizierung einzigartig bleiben, gilt die erste Zeile.

Recipe-Keys und semantische Asset-Keys lösen unterschiedliche Probleme

Ein Recipe-Key stellt die Eingabe des Spielers dar. Falls die Reihenfolge in deinen Regeln nicht wichtig ist, sortiere die exakten kanonischen Item-IDs, bevor du sie hashst. Die unhashierte Identität ist leicht zu inspizieren:

recipe identity: v3 | fire | iron

Das macht iron + fire und fire + iron durch die gleiche Server-gehörende Recipe-Zeile auflösen. Füge eine Rules-Version ein, da ein späterer Balance-Patch das Ergebnis ändern kann. Slugifiziere IDs nicht für den Key: verlustbehaftete Normalisierung kann zwei verschiedene Gegenstände mergen.

Ein semantischer Asset-Key stellt dar, was das Spiel letztendlich anzeigt. Seine unhashierte Identität kann gleich klar bleiben:

asset identity: ember-shield | inventory-icon-v1 | z-image | 1:1 | filtered

Mehrere Rezepte können zu ember-shield führen; Übersetzungen können es auch anders nennen. Diese Wege können immer noch eine genehmigte visuelle Darstellung wiederverwenden.

Rohe Prompts ergeben schlechte Keys: Wording-Änderungen fragmentieren den Cache, und identische Prompts werden nach einer Art-Direktion-Änderung veraltet. Ordne jedes Rezept einer stabilen Ergebnis-ID zu, dann key die Kunst nach Ergebnis-ID, Modell, Seitenverhältnis, Sicherheitsmodus und Stilversion. Erhöhe die Version, wenn du neue Kunst willst.

Ein Cache-First-Request-Flow

Behandle Generierung als Hintergrund-Asset-Produktion, auch wenn der Spieler es startet. Ein praktischer Flow ist:

  1. Löse das eingehende Rezept zu einer semantischen Ergebnis-ID auf.
  2. Schau die semantische Asset-Key in dauerhaftem Speicher nach.
  3. Bei einem Hit, gib die gespeicherte URL sofort zurück.
  4. Bei einem Miss, füge atomar eine gemeinsame Job-Zeile ein, die von dem semantischen Asset keyed wird.
  5. In der gleichen Datenbbanktransaktion reserviere einen Platz im gemeinsamen täglichen Submissions-Budget. Andere Worker geben den vorhandenen Job zurück.
  6. Committe die Zeile als submitting, bevor du einen POST machst, dann speichere die Task-ID sofort nach der Antwort.
  7. Falls Submission mehrdeutig ist, markiere submit_uncertain und stoppe. Poll eine bekannte Task-ID mit GET bis sie completed oder failed wird.
  8. Notiere Terminal-Status und abgerechnete Credits. Kopiere ein abgeschlossenes Bild zu deinen eigenen Speicher und verschiebe es zu pending_review.
  9. Nur eine Akzeptanzprüfung kann pending_review zu ready verschieben.

Nach Schritt sechs gib einen Platzhalter und pending-Status zurück. Lass einen Worker abfragen, während der Client deinen Game-Endpoint prüft. Gib den reAPI-Schlüssel niemals dem Client preis.

Polling ist kostenlos, und der In-Flight-Task-Endpoint ist fünf Sekunden lang gecacht, also poll etwa alle fünf Sekunden.[3] Respektiere Retry-After bei einem 429. Wiederhole den POST nicht blind: reAPI dedupliziert Generierungsanfragen nicht nach Idempotency-Key, also kann eine verlorene Antwort einen akzeptierten Task und eine zweite Belastung verstecken.[5]

Ein ausführbares Node-20-Modul mit einer gemeinsamen Sperre

Das kleinste Beispiel, das mehrere Worker schützt, braucht gemeinsamen Status. Das Modul unten ist einfach .mjs, nutzt PostgreSQL über das postgres-Paket, und gibt nach Submission zurück statt die Spieler-Anfrage offen zu halten.

npm install postgres

Erstelle die Tabellen einmal. Fülle game_recipes aus den Game-eigenen Rule-Daten; der Browser schickt zwei Item-IDs, niemals eine Ergebnis-ID oder Display-Name.

CREATE TABLE game_recipes (
  recipe_key text PRIMARY KEY,
  result_item_id text NOT NULL,
  result_display_name text NOT NULL
);

CREATE TABLE game_asset_jobs (
  asset_key text PRIMARY KEY,
  origin_recipe_key text NOT NULL REFERENCES game_recipes(recipe_key),
  result_item_id text NOT NULL,
  state text NOT NULL CHECK (state IN (
    'submitting', 'submit_uncertain', 'processing', 'completed',
    'pending_review', 'ready', 'failed', 'rejected'
  )),
  submit_owner uuid NOT NULL,
  prompt text NOT NULL,
  task_id text UNIQUE,
  source_url text,
  durable_url text,
  usage_credits integer,
  error_message text,
  created_at timestamptz NOT NULL DEFAULT now(),
  updated_at timestamptz NOT NULL DEFAULT now()
);

CREATE TABLE game_generation_budgets (
  budget_day date PRIMARY KEY,
  submissions integer NOT NULL CHECK (submissions >= 0),
  submission_limit integer NOT NULL CHECK (submission_limit > 0)
);

Speichere dies als cache-first.mjs. Die vollständigen SHA-256-Keys hashen exakte kanonische IDs; es gibt keinen Slugifizierungsschritt, der zwei verschiedene IDs kollabieren kann.

import { createHash, randomUUID } from 'node:crypto';
import postgres from 'postgres';

const API_BASE = 'https://reapi.ai/api/v1';
const apiKey = process.env.REAPI_API_KEY;
const databaseUrl = process.env.DATABASE_URL;
const dailyLimit = Number(process.env.DAILY_NEW_ASSET_LIMIT ?? '100');

if (!apiKey) throw new Error('Set REAPI_API_KEY on the server');
if (!databaseUrl) throw new Error('Set DATABASE_URL on the server');
if (!Number.isInteger(dailyLimit) || dailyLimit < 1) {
  throw new Error('DAILY_NEW_ASSET_LIMIT must be a positive integer');
}

const sql = postgres(databaseUrl);
const localFlights = new Map(); // L1 only; PostgreSQL owns correctness.

function hashIdentity(value) {
  return createHash('sha256')
    .update(JSON.stringify(value), 'utf8')
    .digest('hex');
}

function canonicalId(value) {
  if (typeof value !== 'string' || value.length === 0 || value.length > 200) {
    throw new Error('Item IDs must be non-empty canonical server IDs');
  }
  return value; // Preserve the exact string; do not trim, slugify, or case-fold.
}

export function recipeKey(leftItemId, rightItemId) {
  const pair = [canonicalId(leftItemId), canonicalId(rightItemId)].sort(
    (a, b) => (a < b ? -1 : a > b ? 1 : 0),
  );
  return `recipe:v3:${hashIdentity({ rulesVersion: 'v3', itemIds: pair })}`;
}

function assetKey(resultItemId) {
  return `asset:${hashIdentity({
    resultItemId,
    styleVersion: 'inventory-icon-v1',
    model: 'z-image',
    aspectRatio: '1:1',
    contentFilter: true,
  })}`;
}

async function resolveRecipe(leftItemId, rightItemId) {
  const key = recipeKey(leftItemId, rightItemId);
  const [recipe] = await sql`
    SELECT result_item_id, result_display_name
    FROM game_recipes
    WHERE recipe_key = ${key}
  `;
  if (!recipe) throw new Error('Unknown recipe');
  if (recipe.result_display_name.length > 200) {
    throw new Error('Canonical display name is too long');
  }
  return { ...recipe, recipeKey: key };
}

function publicJob(job) {
  return {
    assetKey: job.asset_key,
    state: job.state,
    taskId: job.task_id ?? null,
    url: job.state === 'ready' ? job.durable_url : null,
    usageCredits: job.usage_credits ?? null,
  };
}

async function claimSharedJob(recipe, key, prompt) {
  const owner = randomUUID();

  return sql.begin(async (tx) => {
    const created = await tx`
      INSERT INTO game_asset_jobs (
        asset_key, origin_recipe_key, result_item_id,
        state, submit_owner, prompt
      ) VALUES (
        ${key}, ${recipe.recipeKey}, ${recipe.result_item_id},
        'submitting', ${owner}, ${prompt}
      )
      ON CONFLICT (asset_key) DO NOTHING
      RETURNING *
    `;

    if (created.length === 0) {
      const [existing] = await tx`
        SELECT * FROM game_asset_jobs WHERE asset_key = ${key}
      `;
      if (!existing) throw new Error('Concurrent job was not readable');
      return { ownsSubmit: false, job: existing };
    }

    const budget = await tx`
      INSERT INTO game_generation_budgets (
        budget_day, submissions, submission_limit
      ) VALUES (
        (now() AT TIME ZONE 'UTC')::date, 1, ${dailyLimit}
      )
      ON CONFLICT (budget_day) DO UPDATE SET
        submissions = game_generation_budgets.submissions + 1,
        submission_limit = EXCLUDED.submission_limit
      WHERE game_generation_budgets.submissions < EXCLUDED.submission_limit
      RETURNING submissions
    `;
    if (budget.length === 0) throw new Error('Shared daily budget reached');

    return { ownsSubmit: true, owner, job: created[0] };
  });
}

async function markSubmitUncertain(key, owner, message) {
  const [job] = await sql`
    UPDATE game_asset_jobs
    SET state = 'submit_uncertain', error_message = ${message},
        updated_at = now()
    WHERE asset_key = ${key} AND submit_owner = ${owner}
      AND state = 'submitting'
    RETURNING *
  `;
  return job;
}

async function submitClaimedJob(claim, key, prompt) {
  let response;
  let body = '';

  try {
    response = await fetch(`${API_BASE}/images/generations`, {
      method: 'POST',
      headers: {
        Authorization: `Bearer ${apiKey}`,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        model: 'z-image',
        prompt,
        aspect_ratio: '1:1',
        content_filter: true,
      }),
    });
    body = await response.text();
  } catch (error) {
    const job = await markSubmitUncertain(
      key,
      claim.owner,
      error instanceof Error ? error.message : String(error),
    );
    return publicJob(job ?? claim.job);
  }

  let task;
  try {
    task = JSON.parse(body);
  } catch {
    task = null;
  }

  if (!response.ok || !task?.id) {
    const message = `Unconfirmed submit response: HTTP ${response.status}`;
    const job = await markSubmitUncertain(key, claim.owner, message);
    return publicJob(job ?? claim.job);
  }

  const [saved] = await sql`
    UPDATE game_asset_jobs
    SET state = 'processing', task_id = ${task.id}, updated_at = now()
    WHERE asset_key = ${key} AND submit_owner = ${claim.owner}
      AND state = 'submitting'
    RETURNING *
  `;
  if (!saved) {
    throw new Error(`Task ${task.id} was accepted but its ID was not persisted; do not resubmit`);
  }
  return publicJob(saved);
}

async function startOrRead(recipe, key) {
  const prompt = [
    `A clean 3D inventory icon of ${recipe.result_display_name}.`,
    'One centered object, readable silhouette, plain warm background,',
    'soft studio light, no text, no logo, no existing game character.',
  ].join(' ');

  const claim = await claimSharedJob(recipe, key, prompt);
  if (!claim.ownsSubmit) return publicJob(claim.job);
  return submitClaimedJob(claim, key, prompt);
}

export async function getOrCreateCraftAsset({ leftItemId, rightItemId }) {
  const recipe = await resolveRecipe(leftItemId, rightItemId);
  const key = assetKey(recipe.result_item_id);
  const existing = localFlights.get(key);
  if (existing) return existing;

  const work = startOrRead(recipe, key).finally(() => {
    if (localFlights.get(key) === work) localFlights.delete(key);
  });
  localFlights.set(key, work);
  return work;
}

export async function pollAsset(key) {
  const [job] = await sql`
    SELECT * FROM game_asset_jobs WHERE asset_key = ${key}
  `;
  if (!job) throw new Error('Unknown asset job');
  if (job.state !== 'processing' || !job.task_id) return publicJob(job);

  const response = await fetch(`${API_BASE}/tasks/${job.task_id}`, {
    headers: { Authorization: `Bearer ${apiKey}` },
  });
  if (response.status === 429) {
    const parsed = Number.parseInt(response.headers.get('retry-after') ?? '', 10);
    return {
      ...publicJob(job),
      retryAfterSeconds: Number.isFinite(parsed) ? Math.max(parsed, 1) : 5,
    };
  }
  if (!response.ok) {
    throw new Error(`GET task failed with HTTP ${response.status}; retry this GET, not the POST`);
  }

  const task = await response.json();
  if (task.status === 'processing') return publicJob(job);
  const credits = Number.isInteger(task.usage?.credits)
    ? task.usage.credits
    : null;

  if (task.status === 'failed') {
    const [failed] = await sql`
      UPDATE game_asset_jobs
      SET state = 'failed', usage_credits = ${credits},
          error_message = ${task.error?.message ?? 'Generation failed'},
          updated_at = now()
      WHERE asset_key = ${key} AND task_id = ${job.task_id}
      RETURNING *
    `;
    return publicJob(failed);
  }

  if (task.status !== 'completed') {
    throw new Error(`Unexpected task status: ${task.status}`);
  }

  const sourceUrl = task.output?.image_urls?.[0] ?? null;
  const [completed] = await sql`
    UPDATE game_asset_jobs
    SET state = 'completed', source_url = ${sourceUrl},
        usage_credits = ${credits},
        error_message = ${sourceUrl ? null : 'Completed task returned no image URL'},
        updated_at = now()
    WHERE asset_key = ${key} AND task_id = ${job.task_id}
    RETURNING *
  `;
  return publicJob(completed);
}

export async function stageForReview(key, durableUrl) {
  const [job] = await sql`
    UPDATE game_asset_jobs
    SET state = 'pending_review', durable_url = ${durableUrl},
        updated_at = now()
    WHERE asset_key = ${key} AND state = 'completed'
      AND source_url IS NOT NULL
    RETURNING *
  `;
  if (!job) throw new Error('Only an archived completed asset can enter review');
  return publicJob(job);
}

export async function reviewAsset(key, accepted, reason = null) {
  const nextState = accepted ? 'ready' : 'rejected';
  const [job] = await sql`
    UPDATE game_asset_jobs
    SET state = ${nextState}, error_message = ${reason}, updated_at = now()
    WHERE asset_key = ${key} AND state = 'pending_review'
    RETURNING *
  `;
  if (!job) throw new Error('Asset is not pending review');
  return publicJob(job);
}

Die Datenbank-Zeile, nicht localFlights, verhindert, dass ein anderer Worker das gleiche Asset submitted. Ein Prozess, der während POST stirbt, lässt submitting; behandle eine veraltete Zeile als submit_uncertain und untersuche sie statt sie zu recyceln. Nach einer bekannten Task-ID, transiente Poll-Fehlversuche retry nur GET.

Der gemeinsame Zähler ist eine konservative Submit-Kappe. Ein unbestätigter oder abgelehnter POST verbraucht immer noch seinen Slot, was sicherer ist als die Kappe lautlos zu überschreiten. Falls ein Terminal-Task fehlschlägt, sein Status und usage.credits bleiben in der Zeile. Dieses Beispiel auto-retry fehlgeschlagene oder abgelehnte Assets nicht; füge ein separates Versuchskontouch und eine explizite, budgetierte Operatoraktion ein, bevor du Neuversuche unterstützt.

Dein Storage-Worker sollte source_url herunterladen, zu deinem Object-Store hochladen, und diese dauerhafte URL an stageForReview übergeben. Nur reviewAsset(..., true) erstellt ein cacheables ready-Asset.

Was der Smoke-Test mit drei Tasks beweist

Am 3. September 2026 submitten wir drei ursprüngliche 1:1-Inventory-Item-Prompts mit content_filter: true: eine Moos-Laterne, einen Glas-Kompass und einen Glut-Schild. Jeder Task schloss sich beim ersten Submit ab und meldete 5 Credits. [4]

KandidatTask-IDBeobachtete AbschlusszeitCredits
Moss lanterntask_01a065490250739d893a1efcd83f4d1d25.1 s5
Glass compasstask_01a0654995df7265b44b132dce34a91425.0 s5
Ember shieldtask_01a0654995fd73e28e7c68217066407018.2 s5
Insgesamt3 Tasks15 / $0.015

Der öffentliche Smoke-Test-Eintrag enthält die Request-Einstellungen, Task-IDs, verstrichene Zeiten und abgerechnete Credits. Es lässt absichtlich die temporären Output-URLs weg.[4]

Dies ist ein Smoke-Test, kein Latenz-Benchmark; er sagt nichts über p95, Bursts oder langfristige Zuverlässigkeit. Das Protokoll notierte Task-Ergebnisse und Settlement, aber schloss keine Kunstüberprüfung ein. completed bedeutet drei Kandidaten kamen an, nicht dass ein Kunstreviewer sie annahm.

Das ist, warum die nützliche kreative Metrik ist:

cost per accepted asset =
  total settled generation credits x $0.001 / accepted assets

Wenn drei Versuche ein brauchbares Icon ergeben, ist seine Effektivkosten $0.015. Notiere technischen Status und kreative Akzeptanz separat.

Generiere voraus für Latenz, nicht fiktive Einsparungen

Generiere nur voraus vorhersagbare Anfragen, wie die Top-20-Rezepte, die vom Spieler-Inventar erreichbar sind. Nutze die gleiche semantische Cache und Single-Flight-Pfad, mit einer separaten täglichen Kappe.

Generiere nicht den theoretischen Kombinationsraum. Ungenutzte Assets kosten Geld, und offenes Crafting wächst über eine Brute-Force-Queue hinaus. Ranke nach beobachteter Nachfrage und stoppe bei der Kappe.

Das Hybrid fühlt sich normalerweise am besten an:

  • genehmigte semantische Assets geben sofort zurück;
  • hochwahrscheinliche nächste Items werden voraus generiert;
  • Long-Tail-Misses zeigen einen Platzhalter, während eine Background-Task läuft;
  • unpopuläre oder Missbrauchs-Anfragen treffen ein Quota statt eine unbegrenzte Retry-Schleife.

Sicherheit, Speicher und operative Grenzen

Halte content_filter: true für spieler-sichtbare Generierung. Z-Image lässt diese Einstellung standardmäßig aus, also verlasse dich nicht auf den Standard. [2] Moderiere oder beschränke Spieler-Text bevor er ein Prompt wird, blockiere Versuche, geschützte Charaktere oder echte Menschen nachzuahmen, und Rate-Limit nach Account und Gerät. Ein Upstream-Filter ist eine Schicht, nicht deine komplette Spiel-Richtlinie.

Halte den Key Server-seitig. Protokolliere Recipe- und Asset-Keys, Task-ID, Status, Credits, und Akzeptanz ohne unnötige Spieler-Daten zu behalten.

Schließlich kopiere abgeschlossene Dateien zu dauerhaftem Speicher bevor du deren URLs zu Spiel-Clients publizierst. Task-Output-URLs sind kein permanenter Asset-Kontrakt. [3] Speichere einen Checksum und Content-Type neben dem Asset-Eintrag, so dass ein Neuversuch nicht still genehmigte Kunst ersetzen kann.

Fazit: wann Caching nicht genug ist

Caching funktioniert nur, wenn das Spiel Wiederverwendung erlaubt. Falls Paid-Miss-Rate × abgeschlossene Versuche pro akzeptiertem Asset 0.4 übersteigt, kann die $0.002-Obergrenze nicht bei einem $0.005-Unit-Preis halten. Falls die 3.000 täglichen Anfragen im ursprünglichen Beispiel bereits einzigartige semantische Assets nach Caching sind, wird Cache-Software-Wechsel das Problem nicht lösen.

Dann ändere eine Produkt-Einschränkung: teile Kunst zwischen ähnlichen Items, nutze prozedurales Umfärben oder Overlays, generiere nur beliebte Items, Deckel-Discoveries, berechne für Long-Tail-Erstellung, oder akzeptiere ein größeres Media-Budget.

Überprüfe die Live-Z-Image-Modell-Seite bevor du ein Budget setzt, dann nutze den Z-Image-API-Leitfaden und Tasks-API-Referenz um Submission und Polling zu implementieren. Preise und Modell-Verhalten können sich ändern; deine eigenen abgerechneten Credits bleiben die Quelle der Wahrheit für eine Produktionsrechnung.

FAQ

Reduziert Caching den Preis eines neuen KI-Bildes?

Nein. Es reduziert, wie oft du eines kaufst. Die aktuelle Z-Image-Belastung bleibt fünf Credits pro abgeschlossenem Generate; ein Cache-Hit kostet keine neuen Generierungs-Credits.

Sollte der Prompt selbst der Cache-Key sein?

Normalerweise nicht. Löse Rezepte zu einer stabilen Ergebnis-ID auf, dann versione seine Kunst separat.

Was, wenn Item-Reihenfolge wichtig ist?

Sortiere die Recipe-Eingaben nicht. water + fire und fire + water sollten verschiedene Recipe-Keys haben wann immer deine Regeln ihnen verschiedene Ergebnisse geben.

Sollte ein Spiel Assets voraus generieren oder sie in Echtzeit generieren?

Nutze beides: generiere ein wahrscheinliches, budgetiertes Set voraus, diene Hits sofort, und generiere unprediktkible Long-Tail-Items im Hintergrund.

Wie oft sollte ich einen reAPI-Image-Task abfragen?

Etwa alle fünf Sekunden ist ein praktischer Ausgangspunkt. Schnelleres Polling macht Generierung nicht schneller fertig und kann Rate-Limit-Budget verschwenden.

Werden fehlergeschlagene Generierungen berechnet?

Die Z-Image-Dokumentation sagt, fehlergeschlagene und abgelehnte Anfragen werden nicht berechnet. Überprüfe trotzdem status zusammen mit usage.credits: ein abgeschlossenes aber visuell unbrauchbares Ergebnis ist anders als ein fehlergeschlagener Task und zählt zur kreativen Kosten.[2][3]

Kann ich einen Durchschnitt unter $0.002 pro Craft versprechen?

Nur nach Messung deiner Paid-Miss-Rate und abgeschlossener Versuche pro akzeptiertem Asset. Bei $0.005 pro Bild, um streng unter $0.002 zu bleiben, braucht ihr Produkt unter 0.4 zu bleiben. Eine 40%-Miss-Rate erfüllt nur gerade die Obergrenze, wenn jedes Asset seinen ersten abgeschlossenen Versuch passiert.

Kann der Game-Client den Image-Endpoint direkt aufrufen?

Er sollte nicht. Ein Client-Side-Credential kann extrahiert und missbraucht werden. Route Generierung über dein Backend, erzwinge Quotas dort, und lass Clients eine Spiel-gehörende ausstehende Job abfragen.

Referenzen

  1. reAPI. Z-Image model page and live pricing. Retrieved September 3, 2026 from reapi.ai/models/z-image
  2. reAPI. Z-Image API documentation: inputs, asynchronous response, pricing, filters, and output retention. Retrieved September 3, 2026 from reapi.ai/docs/z-image
  3. reAPI. Tasks API: status, settled usage, polling, rate-limit behavior, and output storage. Retrieved September 3, 2026 from reapi.ai/docs/api/tasks
  4. reAPI. Three-task Z-Image smoke-test record. Run September 3, 2026. Temporary output URLs are omitted; no visual acceptance review was performed.
  5. reAPI. API overview: idempotency behavior. Retrieved September 3, 2026.