Voltar ao blog

API e SDKGuia

Faça streaming de respostas de chat em Node.js com o SDK da OpenAI

Faça streaming de uma conclusão de chat token por token em Node.js: loop for await, interrupção sob demanda, tratamento de erros e uso de tokens com o SDK da OpenAI.

Por CheapTokens TeamPublicado em Atualizado em 4 min de leitura

Janela de terminal imprimindo uma resposta em streaming token por token a partir de um script Node.js
Nesta página
  1. O que você precisa
  2. Passo 1. Faça streaming da resposta com for await
  3. Passo 2. Interrompa o stream sob demanda
  4. Passo 3. Trate erros
  5. Passo 4. Leia o uso e estime o custo
  6. Script completo
  7. Próximos passos

O streaming permite que seu app mostre a resposta conforme ela é gerada, em vez de esperar pela resposta completa. Neste guia você vai construir um pequeno script Node.js que faz streaming de uma conclusão de chat da API CheapTokens token por token, interrompe sob demanda e trata erros. A versão em Python deste tópico está em Streaming responses in Python.

O que você precisa#

  • Node.js 22 ou mais recente (a versão mínima indicada no README do pacote openai, verificada em 22 de outubro de 2026).
  • Uma chave de API CheapTokens. Se você ainda não tem uma, siga Your first API request with CheapTokens.
  • O SDK oficial: npm install openai.

O CheapTokens expõe uma API compatível com OpenAI em https://api.cheaptokens.dev/v1, então o pacote oficial openai funciona alterando apenas a URL base e a chave. Mantenha a chave em uma variável de ambiente, não no código:

bash
export CHEAPTOKENS_API_KEY="your-key"

Passo 1. Faça streaming da resposta com for await#

Crie stream.mjs (a extensão .mjs permite usar import e await de nível superior):

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);

Execute com node stream.mjs. O SDK lê os Server-Sent Events da API e entrega um chunk por iteração de for await. Três detalhes importam:

  • O texto chega em chunk.choices[0].delta.content. Alguns chunks não trazem texto (o primeiro geralmente contém apenas o role), então verifique se delta não está vazio.
  • process.stdout.write imprime sem quebra de linha, então as palavras aparecem uma após a outra.
  • Com stream_options: { include_usage: true } o último chunk traz um objeto usage com as contagens de tokens de prompt e de conclusão, e seu array choices está vazio. É por isso que o código usa chunk.choices[0]?. com optional chaining.

O id do modelo vem da documentação do CheapTokens: os provedores nomeiam o mesmo modelo de formas diferentes, então copie o id de lá.

Passo 2. Interrompa o stream sob demanda#

Usuários pressionam Stop, fecham uma aba ou trocam de chat. Cancele a requisição para não esperar por tokens que ninguém vai ler. Passe um AbortSignal nas opções da requisição:

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 },
);

O objeto de stream também tem seu próprio stream.controller.abort(), e sair do loop for await com break também fecha a conexão. Use o que estiver mais próximo do seu código: um signal quando o botão de cancelar fica em outro lugar, break quando a decisão é tomada dentro do loop (por exemplo, você encontrou uma stop word).

Passo 3. Trate erros#

Um stream pode falhar antes do primeiro token (chave errada, saldo vazio) ou no meio (queda de rede). Envolva tanto a requisição quanto o loop em um único 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 é o que você recebe após seu próprio abort(). Não é uma falha, então não mostre como tal.
  • APIError tem o status HTTP: 401 significa chave errada ou ausente, 429 significa requisições demais, e uma mensagem sobre tokens significa que o saldo precisa de recarga. Mais causas e correções estão em Common connection errors and how to fix them e "Not enough tokens" error.
  • O texto já impresso continua válido após um erro. Mantenha-o se quiser mostrar a resposta parcial.

Passo 4. Leia o uso e estime o custo#

O objeto usage contém prompt_tokens e completion_tokens. Junto com o preço do modelo por 1M de tokens, ele dá o custo exato de uma requisição. Para uma estimativa aproximada antes de executar qualquer coisa, use a tabela ao vivo abaixo: ela mostra o custo de 1.000 requisições com 2.000 tokens de entrada e 800 de saída cada.

Modelo Por solicitação 1.000 solicitações OpenRouter, 1.000 solicitações
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

2.000 tokens de entrada e 800 tokens de saída por solicitação. Os preços por 1M de tokens são atuais e mudam junto com nossas tarifas; a coluna OpenRouter mostra preços de referência para a mesma carga de trabalho.

Para respostas de chat em streaming, o MiniMax-M2.7 custa $0.05 por 1M de tokens de entrada e $0.15 por 1M de tokens de saída, com contexto de 180K e até 8.192 tokens de saída por resposta. O preço é fixo por modelo em qualquer horário do dia, e um saldo cobre todos os modelos, então trocar a string model é suficiente para compará-los com seus próprios prompts.

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;
  }
}

Próximos passos#

  • Envie os chunks para um navegador com Server-Sent Events a partir do seu próprio endpoint, para que a chave nunca saia do servidor.
  • Experimente outros modelos alterando apenas a string model: o MiniMax-M2.7 está disponível no mesmo saldo e na mesma URL base.
  • Obtenha uma chave e execute o script: create a CheapTokens API key e recarregue o saldo.

Endpoints, ids de modelos e limites de cada modelo estão na CheapTokens docs.