블로그로 돌아가기

API & SDK가이드

OpenAI SDK로 Node.js에서 채팅 응답 스트리밍하기

Node.js에서 채팅 완성을 토큰 단위로 스트리밍하기: for await 루프, 필요 시 중단, 오류 처리 및 OpenAI SDK로 토큰 사용량 확인.

작성자: CheapTokens Team 게시됨 업데이트됨읽는 시간 4분

Node.js 스크립트에서 스트리밍된 답변을 토큰 단위로 출력하는 터미널 창
이 페이지의 내용
  1. 필요한 것
  2. 1단계. for await로 답변 스트리밍하기
  3. 2단계. 필요할 때 스트림 중단하기
  4. 3단계. 오류 처리하기
  5. 4단계. 사용량 읽고 비용 추정하기
  6. 전체 스크립트
  7. 다음 단계

스트리밍을 사용하면 전체 응답을 기다리지 않고 생성되는 대로 앱에서 답변을 표시할 수 있습니다. 이 가이드에서는 CheapTokens API에서 채팅 완성을 토큰 단위로 스트리밍하고, 필요할 때 중단하며, 오류를 처리하는 작은 Node.js 스크립트를 작성합니다. 이 주제의 Python 버전은 Python에서의 스트리밍 응답에 있습니다.

필요한 것#

  • Node.js 22 이상 (openai 패키지 README에 명시된 최소 버전, 2026년 10월 22일 확인).
  • CheapTokens API 키. 아직 없다면 CheapTokens로 첫 API 요청하기를 따르세요.
  • 공식 SDK: npm install openai.

CheapTokens는 https://api.cheaptokens.dev/v1에서 OpenAI 호환 API를 제공하므로, 공식 openai 패키지는 base URL과 키만 변경하면 동작합니다. 키는 코드가 아니라 환경 변수에 보관하세요:

bash
export CHEAPTOKENS_API_KEY="your-key"

1단계. for await로 답변 스트리밍하기#

stream.mjs을 생성하세요 (.mjs 확장자를 사용하면 import와 최상위 await을 사용할 수 있습니다):

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

node stream.mjs로 실행하세요. SDK는 API에서 Server-Sent Events를 읽고 for await의 각 반복마다 청크 하나를 전달합니다. 세 가지 세부 사항이 중요합니다:

  • 텍스트는 chunk.choices[0].delta.content에 도착합니다. 일부 청크는 텍스트를 포함하지 않으므로(첫 번째 청크는 종종 role만 담고 있음) delta가 비어 있지 않은지 확인하세요.
  • process.stdout.write은 줄바꿈 없이 출력하므로 단어가 차례로 나타납니다.
  • stream_options: { include_usage: true }을 사용하면 마지막 청크에 프롬프트 및 완성 토큰 수를 담은 usage 객체가 포함되고, 해당 choices 배열은 비어 있습니다. 그래서 코드에서 옵셔널 체이닝과 함께 chunk.choices[0]?.을 사용합니다.

모델 id는 CheapTokens 문서에서 가져옵니다. 제공자마다 같은 모델을 다르게 부르므로 거기서 id를 복사하세요.

2단계. 필요할 때 스트림 중단하기#

사용자가 중지 버튼을 누르거나, 탭을 닫거나, 채팅을 전환합니다. 아무도 읽지 않을 토큰을 기다리지 않도록 요청을 취소하세요. 요청 옵션에 AbortSignal을 전달하세요:

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

스트림 객체에는 자체 stream.controller.abort()도 있으며, break로 for await 루프를 빠져나가는 것도 연결을 닫습니다. 코드에 더 가까운 쪽을 사용하세요: 취소 버튼이 다른 곳에 있으면 signal을, 루프 내부에서 결정이 이루어지면(예: 중지 단어를 발견한 경우) break을 사용하세요.

3단계. 오류 처리하기#

스트림은 첫 토큰 전에(잘못된 키, 잔액 부족) 또는 중간에(네트워크 끊김) 실패할 수 있습니다. 요청과 루프를 모두 하나의 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는 자신의 abort() 이후에 얻는 것입니다. 실패가 아니므로 실패로 표시하지 마세요.
  • APIError에는 HTTP status가 있습니다: 401은 잘못되었거나 누락된 키, 429는 요청이 너무 많음, 토큰에 관한 메시지는 잔액 충전이 필요함을 의미합니다. 더 많은 원인과 해결 방법은 일반적인 연결 오류와 해결 방법 및 "Not enough tokens" 오류에 있습니다.
  • 이미 출력된 텍스트는 오류 후에도 유효합니다. 부분 답변을 표시하려면 유지하세요.

4단계. 사용량 읽고 비용 추정하기#

usage 객체에는 prompt_tokens과 completion_tokens이 있습니다. 1M 토큰당 모델 가격과 함께 사용하면 요청의 정확한 비용을 알 수 있습니다. 아무것도 실행하기 전에 대략적인 추정을 하려면 아래의 실시간 표를 사용하세요: 각각 입력 2,000개와 출력 800개 토큰으로 1,000건의 요청 비용을 보여줍니다.

모델 요청당 1,000회 요청 OpenRouter, 1,000회 요청
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개 및 출력 800개 토큰입니다. 100만 토큰당 가격은 현재 기준이며 당사 요금제에 따라 함께 변경됩니다. OpenRouter 열은 동일한 작업량에 대한 참고 가격을 보여줍니다.

스트리밍 채팅 응답의 경우 MiniMax-M2.7은 1M 입력 토큰당 $0.05, 1M 출력 토큰당 $0.15으로 책정되며, 180K 컨텍스트와 답변당 최대 8,192 출력 토큰을 제공합니다. 가격은 하루 중 언제든 모델별로 고정되며, 하나의 잔액으로 모든 모델을 사용할 수 있으므로 model 문자열만 바꾸면 자신의 프롬프트로 비교할 수 있습니다.

전체 스크립트#

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

다음 단계#

  • 자체 엔드포인트에서 Server-Sent Events로 청크를 브라우저에 전송하여 키가 서버를 떠나지 않도록 하세요.
  • model 문자열만 변경하여 다른 모델을 사용해 보세요: MiniMax-M2.7은 동일한 잔액과 동일한 base URL에서 사용할 수 있습니다.
  • 키를 발급받고 스크립트를 실행하세요: CheapTokens API 키 생성하기 및 잔액 충전.

모든 모델의 엔드포인트, 모델 id 및 제한은 CheapTokens 문서에 있습니다.