Torna al blog

API e SDKGuida

Esegui lo streaming delle risposte chat in Node.js con l'SDK OpenAI

Esegui lo streaming di un completamento chat token per token in Node.js: ciclo for await, interruzione su richiesta, gestione degli errori e utilizzo dei token con l'SDK OpenAI.

Di CheapTokens TeamPubblicato il Aggiornato il 4 min di lettura

Finestra del terminale che stampa una risposta in streaming token per token da uno script Node.js
In questa pagina
  1. Cosa ti serve
  2. Passo 1. Esegui lo streaming della risposta con for await
  3. Passo 2. Interrompi lo stream su richiesta
  4. Passo 3. Gestisci gli errori
  5. Passo 4. Leggi l'utilizzo e stima il costo
  6. Script completo
  7. Prossimi passi

Lo streaming consente alla tua app di mostrare la risposta mentre viene generata invece di attendere la risposta completa. In questa guida creerai un piccolo script Node.js che esegue lo streaming di un completamento chat dall'API CheapTokens token per token, lo interrompe su richiesta e gestisce gli errori. La versione Python di questo argomento è in Streaming responses in Python.

Cosa ti serve#

  • Node.js 22 o successivo (la versione minima indicata nel README del pacchetto openai, verificata il 22 ottobre 2026).
  • Una chiave API CheapTokens. Se non ne hai ancora una, segui Your first API request with CheapTokens.
  • L'SDK ufficiale: npm install openai.

CheapTokens espone un'API compatibile con OpenAI su https://api.cheaptokens.dev/v1, quindi il pacchetto ufficiale openai funziona cambiando solo l'URL di base e la chiave. Mantieni la chiave in una variabile d'ambiente, non nel codice:

bash
export CHEAPTOKENS_API_KEY="your-key"

Passo 1. Esegui lo streaming della risposta con for await#

Crea stream.mjs (l'estensione .mjs ti permette di usare import e await di primo livello):

javascript
import OpenAI from 'openai';

const client = new OpenAI({
  apiKey: process.env.CHEAPTOKENS_API_KEY,
  baseURL: 'https://api.cheaptokens.dev/v1',
});

const stream = await client.chat.completions.create({
  model: 'deepseek-ai/DeepSeek-V4-Flash-0731', // take the id from the CheapTokens docs
  messages: [{ role: 'user', content: 'Explain streaming in two sentences.' }],
  stream: true,
  stream_options: { include_usage: true },
});

let usage = null;
for await (const chunk of stream) {
  const delta = chunk.choices[0]?.delta?.content;
  if (delta) process.stdout.write(delta);
  if (chunk.usage) usage = chunk.usage;
}

console.log('\n\nusage:', usage);

Eseguilo con node stream.mjs. L'SDK legge i Server-Sent Events dall'API e ti fornisce un chunk per ogni iterazione di for await. Tre dettagli sono importanti:

  • Il testo arriva in chunk.choices[0].delta.content. Alcuni chunk non contengono testo (il primo spesso contiene solo il ruolo), quindi verifica che delta non sia vuoto.
  • process.stdout.write stampa senza interruzione di riga, quindi le parole appaiono una dopo l'altra.
  • Con stream_options: { include_usage: true } l'ultimo chunk contiene un oggetto usage con i conteggi dei token di prompt e di completamento, e il suo array choices è vuoto. Ecco perché il codice usa chunk.choices[0]?. con l'optional chaining.

L'id del modello proviene dalla documentazione di CheapTokens: i provider denominano lo stesso modello in modo diverso, quindi copia l'id da lì.

Passo 2. Interrompi lo stream su richiesta#

Gli utenti premono Stop, chiudono una scheda o cambiano chat. Annulla la richiesta per non attendere token che nessuno leggerà. Passa un AbortSignal nelle opzioni della richiesta:

javascript
const controller = new AbortController();

process.on('SIGINT', () => controller.abort()); // Ctrl+C stops the stream

const stream = await client.chat.completions.create(
  {
    model: 'deepseek-ai/DeepSeek-V4-Flash-0731',
    messages: [{ role: 'user', content: 'Write a long story about a lighthouse.' }],
    stream: true,
  },
  { signal: controller.signal },
);

Anche l'oggetto stream ha il proprio stream.controller.abort(), e uscire dal ciclo for await con break chiude comunque la connessione. Usa quello più vicino al tuo codice: un signal quando il pulsante di annullamento si trova altrove, break quando la decisione viene presa all'interno del ciclo (ad esempio, hai trovato una stop word).

Passo 3. Gestisci gli errori#

Uno stream può fallire prima del primo token (chiave errata, saldo vuoto) o a metà (caduta di rete). Avvolgi sia la richiesta che il ciclo in un unico try:

javascript
try {
  for await (const chunk of stream) {
    const delta = chunk.choices[0]?.delta?.content;
    if (delta) process.stdout.write(delta);
  }
} catch (err) {
  if (err instanceof OpenAI.APIUserAbortError) {
    console.log('\n[stopped by user]');
  } else if (err instanceof OpenAI.APIError) {
    console.error(`\nAPI error ${err.status}: ${err.message}`);
  } else {
    throw err;
  }
}
  • APIUserAbortError è ciò che ottieni dopo il tuo abort(). Non è un errore, quindi non mostrarlo come tale.
  • APIError ha l'status HTTP: 401 significa chiave errata o mancante, 429 significa troppe richieste, e un messaggio sui token significa che il saldo deve essere ricaricato. Ulteriori cause e soluzioni sono in Common connection errors and how to fix them e "Not enough tokens" error.
  • Il testo già stampato rimane valido dopo un errore. Conservalo se vuoi mostrare la risposta parziale.

Passo 4. Leggi l'utilizzo e stima il costo#

L'oggetto usage contiene prompt_tokens e completion_tokens. Insieme al prezzo del modello per 1M di token, ti dà il costo esatto di una richiesta. Per una stima approssimativa prima di eseguire qualsiasi cosa, usa la tabella live qui sotto: mostra il costo di 1.000 richieste con 2.000 token di input e 800 di output ciascuna.

Modello Per richiesta 1000 richieste OpenRouter, 1000 richieste
GLM-5.3-Flash $0.000072 $0.072 $0.143
DeepSeek-V4-Flash $0.00011 $0.11 $0.22
MiniMax-M2.7 $0.00022 $0.22 $1.56

2000 token di input e 800 token di output per richiesta. I prezzi per 1M di token sono attuali e cambiano insieme alle nostre tariffe; la colonna OpenRouter mostra i prezzi di riferimento per lo stesso carico di lavoro.

Per le risposte chat in streaming, MiniMax-M2.7 ha un prezzo di $0.05 per 1M di token di input e $0.15 per 1M di token di output, con una finestra di contesto di 180K e fino a 8.192 token di output per risposta. Il prezzo è fisso per modello a qualsiasi ora del giorno, e un unico saldo copre tutti i modelli, quindi cambiare la stringa model è sufficiente per confrontarli sui tuoi prompt.

Script completo#

javascript
import OpenAI from 'openai';

const client = new OpenAI({
  apiKey: process.env.CHEAPTOKENS_API_KEY,
  baseURL: 'https://api.cheaptokens.dev/v1',
});

const controller = new AbortController();
process.on('SIGINT', () => controller.abort());

try {
  const stream = await client.chat.completions.create(
    {
      model: 'deepseek-ai/DeepSeek-V4-Flash-0731',
      messages: [{ role: 'user', content: 'Explain streaming in two sentences.' }],
      stream: true,
      stream_options: { include_usage: true },
    },
    { signal: controller.signal },
  );

  let usage = null;
  for await (const chunk of stream) {
    const delta = chunk.choices[0]?.delta?.content;
    if (delta) process.stdout.write(delta);
    if (chunk.usage) usage = chunk.usage;
  }
  console.log('\n\nusage:', usage);
} catch (err) {
  if (err instanceof OpenAI.APIUserAbortError) {
    console.log('\n[stopped by user]');
  } else if (err instanceof OpenAI.APIError) {
    console.error(`\nAPI error ${err.status}: ${err.message}`);
  } else {
    throw err;
  }
}

Prossimi passi#

  • Invia i chunk a un browser con Server-Sent Events dal tuo endpoint, così la chiave non lascia mai il server.
  • Prova altri modelli cambiando solo la stringa model: MiniMax-M2.7 è disponibile sullo stesso saldo e con lo stesso URL di base.
  • Ottieni una chiave ed esegui lo script: create a CheapTokens API key e ricarica il saldo.

Endpoint, id dei modelli e limiti per ogni modello sono nella CheapTokens docs.