Richieste GPT Image 2 in batch senza perdere le attività
Invia un insieme di prompt GPT Image 2, salva gli ID delle attività, riprendi il polling dopo un’interruzione e scarica le immagini senza reinviarle.
Leggi in MarkdownLa generazione in batch con GPT Image 2 su SeedRouter consiste nell’inviare normali richieste di immagini e tenere traccia di ogni identificativo di attività restituito. Questa guida usa un piccolo script lato client, non una Batch API separata né un prodotto scontato per volumi. Ogni prompt è un’attività a sé; lo script salva l’avanzamento così puoi ricontrollare quelle attività dopo un’interruzione.
Ci sono due lavori distinti: generare un’immagine e scaricarla. Un download fallito non richiede una nuova generazione. La scadenza del polling non significa che l’attività di generazione sia fallita. Mantenere queste distinzioni nello script fa risparmiare lavoro e confusione.
Che cosa serve prima di iniziare?
Usa Node.js 22 o versioni successive, una variabile d’ambiente SEEDROUTER_API_KEY lato server e un saldo sufficiente per il tuo insieme di prova. Comincia con due prompt. Controlla i prezzi correnti prima di aumentare il numero di immagini.
Salva il codice seguente come images.mjs in una cartella di lavoro privata. Vi crea image-tasks.json e scarica i file PNG in images/. Conserva il file di stato: è ciò che collega le tue etichette locali alle attività accettate. Non inserire segreti o prompt con dati sensibili dei clienti in un repository pubblico.
Gli esempi chiedono un’immagine per prompt. Usa il parametro n quando vuoi più output per lo stesso prompt; non confonderlo con l’invio di prompt diversi. SeedRouter documenta l’intervallo accettato e gli altri vincoli nella documentazione del modello.
Invia una volta sola, poi salva gli ID delle attività
Lo script scrive una voce unconfirmed prima di inviare una richiesta. Se il processo si interrompe prima di salvare la risposta, l’esecuzione successiva non reinvierà silenziosamente quell’elemento. Esamina l’attività nel tuo account prima di decidere cosa fare di una voce non confermata.
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));Esegui node images.mjs submit, poi node images.mjs poll. Esegui una sola copia dello script alla volta e non rinominare le etichette nel file di stato. Questo è un piccolo esempio locale, non un sistema di archiviazione multiprocesso. Per un servizio con più istanze applicative, usa un’archiviazione durevole con proprietà esclusiva di ogni invio invece di condividere questo file JSON.
Riprendi il polling senza avviare un’altra generazione
Esegui di nuovo node images.mjs poll dopo una scadenza locale o un’interruzione di rete. Legge gli identificativi di attività salvati e fa richieste GET per controllarle. Non invia un’altra richiesta di generazione. Un’attività completata con un download non terminato viene ricontrollata e i suoi file PNG vengono scaricati con gli stessi nomi deterministici.
La scadenza di dieci minuti appartiene a questo script di esempio. Non è un tempo di completamento promesso né un annullamento lato server. Una singola richiesta di rete già in corso può concludersi dopo la scadenza del ciclo. La documentazione sul ciclo di vita delle attività definisce gli stati reali: processing, completed e failed.
Una voce unconfirmed richiede una verifica manuale perché lo script non ha un identificativo di attività salvato. Non cancellare quella voce per rilanciare l’invio solo per vedere se la seconda volta funziona. Controlla prima lo storico delle attività del tuo account. Se recuperi l’identificativo, mettilo nella voce salvata e usa il polling; altrimenti chiarisci l’invio incerto prima di scegliere di generare di nuovo.
Quanta concorrenza conviene usare?
Comincia con invii sequenziali e un piccolo insieme di prova, come fa questo esempio. Ogni attività accettata può proseguire mentre lo script invia la successiva, ma le richieste non partono in una raffica senza limiti. Il codice non implica alcun numero di concorrenza sicuro valido per tutti.
Per un’applicazione più grande, rendi il numero massimo di attività non concluse un’impostazione esplicita. Smetti di accettare nuovo lavoro quando raggiungi quel limite e continua a controllare le attività già accettate. Gestisci i limiti dell’account seguendo le indicazioni sugli errori documentate, non ripetendo alla cieca le richieste POST.
Tieni un registro separato dei risultati che richiedono una revisione creativa. Un’immagine completata che non ti piace non è un’attività API fallita. È una nuova decisione editoriale e, se invii un’altra richiesta, una nuova generazione da mettere a budget.
Controlla l’insieme prima di considerarlo finito
Confronta le etichette di partenza con il file di stato e con i file scaricati. Ogni etichetta deve avere una spiegazione: scaricata, ancora in elaborazione, fallita o in attesa di verifica dell’invio. Contare solo i file può nascondere un’attività non conclusa o un download interrotto a metà.
Apri anche le immagini. Lo script controlla lo stato HTTP e il tipo di contenuto PNG, ma non può decidere se una tazza ha il colore richiesto o se un’etichetta è leggibile. Valuta il brief visivo separatamente dal successo del trasferimento.
Se cambi output_format, aggiorna insieme il tipo di contenuto atteso e l’estensione del file. Rinominare in .png un download JPEG non lo converte. L’esempio fissa volutamente il PNG perché questo ulteriore problema di gestione dei formati non offuschi il recupero delle attività.
Domande frequenti
Questa è la Batch API di OpenAI?
No. Invia da uno script normali attività di generazione immagini di SeedRouter. Non usa un endpoint Batch API e non implica prezzi speciali per i batch.
Posso riprendere solo i download?
Sì. Esegui il comando di polling con lo stato salvato. Rilegge l’output delle attività completate e scarica i risultati mancanti senza creare un’altra attività di generazione.
Un’attività fallita blocca tutte le altre?
Il ciclo di polling registra quell’attività come fallita e continua a controllare le altre. Esamina l’elemento fallito a parte. Un’attività che fallisce non viene addebitata; un timeout locale da solo non dimostra il fallimento.
Conserva il registro delle attività insieme alle immagini
Una generazione in batch affidabile con GPT Image 2 dipende dal mantenere il legame tra ogni prompt e il suo identificativo di attività. Conserva quel registro, riprendi le attività esistenti e ritenta i download separatamente dalla generazione. Quando un piccolo insieme si comporta come previsto, aumenta il carico di lavoro entro un budget che puoi tenere sotto controllo con la guida ai prezzi.



