Claude Opus 5.5 ist auf SeedRouter verfügbar

GPT-Image-2-Anfragen im Batch, ohne Aufgaben zu verlieren

Sende mehrere GPT-Image-2-Prompts, speichere Aufgaben-IDs, setze die Abfrage nach Unterbrechungen fort und lade fertige Bilder herunter.

Als Markdown lesen

Batch-Generierung mit GPT Image 2 bei SeedRouter heißt: gewöhnliche Bildanfragen senden und jede zurückgegebene Aufgaben-ID verfolgen. Diese Anleitung nutzt ein kleines clientseitiges Skript, keine eigene Batch-API und kein Produkt mit Mengenrabatt. Jeder Prompt ist eine eigene Aufgabe; das Skript speichert den Fortschritt, damit du diese Aufgaben nach einer Unterbrechung erneut prüfen kannst.

Es sind zwei getrennte Aufgaben: ein Bild generieren und es herunterladen. Ein fehlgeschlagener Download erfordert keine neue Generierung. Eine Abfragefrist bedeutet nicht, dass die Bildaufgabe fehlgeschlagen ist. Diese Unterscheidungen im Skript zu bewahren, spart Arbeit und verhindert Verwirrung.

Was brauchst du vorab?

Verwende Node.js 22 oder neuer, eine serverseitige Umgebungsvariable SEEDROUTER_API_KEY und genügend Kontoguthaben für deinen Testumfang. Beginne mit zwei Prompts. Prüfe die aktuellen Preise, bevor du die Bildanzahl erhöhst.

Speichere den folgenden Code als images.mjs in einem privaten Arbeitsverzeichnis. Dort entsteht image-tasks.json, und PNG-Dateien landen in images/. Behalte die Zustandsdatei: Sie verbindet deine lokalen Bezeichnungen mit angenommenen Aufgaben. Leg keine Geheimnisse oder kundensensiblen Prompts in ein öffentliches Repository.

Die Beispiele fordern ein Bild je Prompt an. Nutze den Parameter n, wenn du mehrere Ausgaben zum selben Prompt möchtest; verwechsle das nicht mit dem Senden verschiedener Prompts. Den zulässigen Bereich und weitere Vorgaben dokumentiert SeedRouter in der Modellreferenz.

Einmal senden, dann die Aufgaben-IDs speichern

Das Skript schreibt einen Eintrag unconfirmed, bevor es eine Anfrage sendet. Bricht der Prozess ab, bevor die Antwort gespeichert ist, sendet der nächste Lauf diesen Eintrag nicht stillschweigend erneut. Prüfe die Aufgabe in deinem Konto, bevor du über einen unbestätigten Eintrag entscheidest.

import { mkdir, readFile, rename, writeFile } from 'node:fs/promises';

const mode = process.argv[2];
if (!['submit', 'poll'].includes(mode)) {
  throw new Error('Use: node images.mjs submit | poll');
}
const key = process.env.SEEDROUTER_API_KEY;
if (!key) throw new Error('Set SEEDROUTER_API_KEY on your server.');
const base = 'https://api.seedrouter.ai/v1';
const headers = { Authorization: `Bearer ${key}` };
const file = 'image-tasks.json';
let state;
try {
  state = JSON.parse(await readFile(file, 'utf8'));
} catch (error) {
  if (error.code !== 'ENOENT') throw error;
  state = {};
}
const save = async () => {
  await writeFile(`${file}.tmp`, JSON.stringify(state, null, 2), { mode: 0o600 });
  await rename(`${file}.tmp`, file);
};
const pause = () => new Promise((resolve) => setTimeout(resolve, 3000));
const jobs = [
  ['blue-mug', 'A cobalt-blue ceramic mug on a pale gray tabletop.'],
  ['green-bowl', 'A green ceramic bowl on a pale gray tabletop.'],
];

if (mode === 'submit') {
  for (const [name, prompt] of jobs) {
    if (state[name]) continue;
    state[name] = { status: 'unconfirmed' };
    await save();
    try {
      const response = await fetch(`${base}/images/generations`, {
        method: 'POST',
        headers: { ...headers, 'Content-Type': 'application/json' },
        body: JSON.stringify({
          model: 'gpt-image-2', prompt,
          size: '1024x1024', quality: 'low', output_format: 'png', n: 1,
        }),
        signal: AbortSignal.timeout(60000),
      });
      const task = await response.json();
      if (typeof task.id === 'string' && task.id) {
        state[name] = { id: task.id, status: 'processing' };
        await save();
      }
      if (!response.ok || !state[name].id) {
        throw new Error('Submission needs review.');
      }
    } catch {
      console.error(`${name}: stopped; inspect the saved state before continuing.`);
      process.exitCode = 1;
      break;
    }
  }
} else {
  await mkdir('images', { recursive: true });
  const deadline = Date.now() + 600000;
  do {
    for (const [name, entry] of Object.entries(state)) {
      if (!entry.id || entry.status === 'failed' || entry.downloaded) continue;
      try {
        const response = await fetch(`${base}/tasks/${encodeURIComponent(entry.id)}`, {
          headers, signal: AbortSignal.timeout(30000),
        });
        if (!response.ok) throw new Error('Task check failed.');
        const task = await response.json();
        if (!['processing', 'completed', 'failed'].includes(task.status)) {
          throw new Error('Unexpected task status.');
        }
        entry.status = task.status;
        await save();
        if (task.status !== 'completed') continue;
        const images = task.output?.data;
        if (!Array.isArray(images) || !images.length) {
          throw new Error('Completed task has no image URLs.');
        }
        for (const [index, image] of images.entries()) {
          const url = new URL(image.url);
          if (url.protocol !== 'https:') throw new Error('Expected an HTTPS image URL.');
          // The download is a separate request: never attach the API key.
          const result = await fetch(url, { signal: AbortSignal.timeout(60000) });
          if (!result.ok || !result.headers.get('content-type')?.startsWith('image/png')) {
            throw new Error('PNG download failed.');
          }
          await writeFile(`images/${name}-${index}.png`, Buffer.from(await result.arrayBuffer()));
        }
        entry.downloaded = true;
        await save();
      } catch {
        console.error(`${name}: check or download incomplete; task ID retained.`);
      }
    }
    if (!Object.values(state).some((entry) => entry.id && entry.status !== 'failed' && !entry.downloaded)) break;
    await pause();
  } while (Date.now() < deadline);
}
console.log(JSON.stringify(state, null, 2));

Führe node images.mjs submit aus, danach node images.mjs poll. Lass immer nur eine Kopie des Skripts laufen und benenne Bezeichnungen in der Zustandsdatei nicht um. Dies ist ein kleines lokales Beispiel, kein Speichersystem für mehrere Prozesse. Bei einem Dienst mit mehreren Anwendungsinstanzen nutzt du dauerhaften Speicher mit eindeutiger Zuordnung jeder Sendung, statt diese JSON-Datei zu teilen.

Abfrage fortsetzen, ohne neu zu generieren

Führe node images.mjs poll nach einer lokalen Frist oder Netzunterbrechung erneut aus. Es liest gespeicherte Aufgaben-IDs und prüft sie per GET-Anfrage. Es sendet keine weitere Generierungsanfrage. Eine abgeschlossene Aufgabe mit unvollständigem Download wird erneut geprüft, und ihre PNG-Dateien werden unter denselben festgelegten Dateinamen heruntergeladen.

Die Zehn-Minuten-Frist gehört zu diesem Beispielskript. Sie ist weder eine zugesagte Fertigstellungszeit noch ein serverseitiger Abbruch. Eine laufende Netzanfrage kann nach dieser Schleifenfrist enden. Die tatsächlichen Zustände definiert die Referenz zum Aufgabenlebenszyklus: processing, completed und failed.

Ein Eintrag unconfirmed braucht eine manuelle Prüfung, weil das Skript keine Aufgaben-ID gespeichert hat. Lösch diesen Eintrag nicht und starte das Senden nicht erneut, nur um zu sehen, ob es beim zweiten Mal klappt. Prüfe zuerst den Aufgabenverlauf deines Kontos. Findest du die ID wieder, trag sie in den gespeicherten Eintrag ein und nutze die Abfrage; andernfalls kläre die unsichere Sendung, bevor du erneut generierst.

Wie viel Nebenläufigkeit ist sinnvoll?

Beginne mit sequenziellem Senden und einem kleinen Testumfang, so wie dieses Beispiel. Jede angenommene Aufgabe läuft weiter, während das Skript die nächste sendet, doch Anfragen werden nicht in unbegrenzten Schüben abgefeuert. Der Code impliziert keine allgemein sichere Zahl für Nebenläufigkeit.

Mach in einer größeren Anwendung die maximale Zahl offener Aufgaben zu einer ausdrücklichen Einstellung. Nimm keine neue Arbeit mehr an, wenn dieses Limit erreicht ist, und prüfe bereits angenommene Aufgaben weiter. Behandle Kontolimits nach den dokumentierten Fehlerhinweisen und nicht durch blindes Wiederholen von POST-Anfragen.

Führe eine eigene Liste der Ergebnisse, die eine gestalterische Überarbeitung brauchen. Ein fertiges Bild, das dir nicht gefällt, ist keine fehlgeschlagene API-Aufgabe. Es ist eine neue redaktionelle Entscheidung und, wenn du erneut sendest, eine weitere Generierung, die ins Budget gehört.

Prüfe den Satz, bevor du ihn als fertig bezeichnest

Vergleiche deine Eingabebezeichnungen mit der Zustandsdatei und den heruntergeladenen Dateien. Zu jeder Bezeichnung sollte eine Erklärung gehören: heruntergeladen, noch in Bearbeitung, fehlgeschlagen oder wartet auf Prüfung der Sendung. Nur Dateien zu zählen, kann eine offene Aufgabe oder einen auf halbem Weg abgebrochenen Download verbergen.

Öffne außerdem die Bilder. Das Skript prüft HTTP-Status und PNG-Inhaltstyp, kann aber nicht entscheiden, ob eine Tasse die gewünschte Farbe hat oder ein Etikett lesbar ist. Prüfe die gestalterische Anforderung getrennt vom erfolgreichen Transport.

Änderst du output_format, pass den erwarteten Inhaltstyp und die Dateiendung gemeinsam an. Ein heruntergeladenes JPEG in .png umzubenennen, wandelt es nicht um. Das Beispiel legt PNG bewusst fest, damit dieses zusätzliche Formatproblem die Wiederaufnahme von Aufgaben nicht verdeckt.

Häufige Fragen

Ist das die Batch-API von OpenAI?

Nein. Es sendet gewöhnliche SeedRouter-Bildaufgaben aus einem Skript. Es nutzt keinen Batch-API-Endpunkt und bedeutet keine besonderen Batch-Preise.

Kann ich nur die Downloads fortsetzen?

Ja. Führe den Abfragebefehl mit dem gespeicherten Zustand aus. Er holt die Ausgabe abgeschlossener Aufgaben erneut und lädt fehlende Ergebnisse herunter, ohne eine weitere Bildaufgabe zu erzeugen.

Stoppt eine fehlgeschlagene Aufgabe alle anderen?

Die Abfrageschleife vermerkt diese Aufgabe als fehlgeschlagen und prüft die übrigen weiter. Sieh dir den fehlgeschlagenen Eintrag gesondert an. Eine fehlgeschlagene Aufgabe wird nicht berechnet; eine lokale Zeitüberschreitung allein belegt keinen Fehlschlag.

Bewahre die Aufgabenliste bei den Bildern auf

Verlässliche Batch-Generierung mit GPT Image 2 hängt daran, die Verbindung zwischen jedem Prompt und seiner Aufgaben-ID zu bewahren. Erhalte diese Aufzeichnung, setze vorhandene Aufgaben fort und wiederhole Downloads getrennt von der Generierung. Verhält sich ein kleiner Satz wie erwartet, erhöhe die Last innerhalb eines Budgets, das du mit dem Preisleitfaden überwachen kannst.

Verwandte Leitfäden