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

이 페이지의 내용
스트리밍을 사용하면 전체 응답을 기다리지 않고 생성되는 대로 앱에서 답변을 표시할 수 있습니다. 이 가이드에서는 CheapTokens API에서 채팅 완성을 토큰 단위로 스트리밍하고, 필요할 때 중단하며, 오류를 처리하는 작은 Node.js 스크립트를 작성합니다. 이 주제의 Python 버전은 Python에서의 스트리밍 응답에 있습니다.
필요한 것#
- Node.js 22 이상 (
openai패키지 README에 명시된 최소 버전, 2026년 10월 22일 확인). - CheapTokens API 키. 아직 없다면 CheapTokens로 첫 API 요청하기를 따르세요.
- 공식 SDK:
npm install openai.
CheapTokens는 https:/에서 OpenAI 호환 API를 제공하므로, 공식 openai 패키지는 base URL과 키만 변경하면 동작합니다. 키는 코드가 아니라 환경 변수에 보관하세요:
export CHEAPTOKENS_API_KEY="your-key"1단계. for await로 답변 스트리밍하기#
stream.mjs을 생성하세요 (.mjs 확장자를 사용하면 import와 최상위 await을 사용할 수 있습니다):
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.에 도착합니다. 일부 청크는 텍스트를 포함하지 않으므로(첫 번째 청크는 종종 role만 담고 있음)choices[0]. delta. content delta가 비어 있지 않은지 확인하세요. process.은 줄바꿈 없이 출력하므로 단어가 차례로 나타납니다.stdout. write stream_을 사용하면 마지막 청크에 프롬프트 및 완성 토큰 수를 담은options: { include_ usage: true } usage객체가 포함되고, 해당choices배열은 비어 있습니다. 그래서 코드에서 옵셔널 체이닝과 함께chunk.을 사용합니다.choices[0]?.
모델 id는 CheapTokens 문서에서 가져옵니다. 제공자마다 같은 모델을 다르게 부르므로 거기서 id를 복사하세요.
2단계. 필요할 때 스트림 중단하기#
사용자가 중지 버튼을 누르거나, 탭을 닫거나, 채팅을 전환합니다. 아무도 읽지 않을 토큰을 기다리지 않도록 요청을 취소하세요. 요청 옵션에 AbortSignal을 전달하세요:
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.도 있으며, break로 for await 루프를 빠져나가는 것도 연결을 닫습니다. 코드에 더 가까운 쪽을 사용하세요: 취소 버튼이 다른 곳에 있으면 signal을, 루프 내부에서 결정이 이루어지면(예: 중지 단어를 발견한 경우) break을 사용하세요.
3단계. 오류 처리하기#
스트림은 첫 토큰 전에(잘못된 키, 잔액 부족) 또는 중간에(네트워크 끊김) 실패할 수 있습니다. 요청과 루프를 모두 하나의 try로 감싸세요:
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에는 HTTPstatus가 있습니다: 401은 잘못되었거나 누락된 키, 429는 요청이 너무 많음, 토큰에 관한 메시지는 잔액 충전이 필요함을 의미합니다. 더 많은 원인과 해결 방법은 일반적인 연결 오류와 해결 방법 및 "Not enough tokens" 오류에 있습니다.- 이미 출력된 텍스트는 오류 후에도 유효합니다. 부분 답변을 표시하려면 유지하세요.
4단계. 사용량 읽고 비용 추정하기#
usage 객체에는 prompt_tokens과 completion_이 있습니다. 1M 토큰당 모델 가격과 함께 사용하면 요청의 정확한 비용을 알 수 있습니다. 아무것도 실행하기 전에 대략적인 추정을 하려면 아래의 실시간 표를 사용하세요: 각각 입력 2,000개와 출력 800개 토큰으로 1,000건의 요청 비용을 보여줍니다.
| 모델 | 입력 $/1M | 출력 $/1M | 요청당 | 1,000회 요청 | OpenRouter, 1,000회 요청 |
|---|---|---|---|---|---|
| GLM-5.3-Flash | $0.02 | $0.04 | $0.000072 | $0.072 | $0.143 |
| DeepSeek-V4-Flash | $0.015 | $0.10 | $0.00011 | $0.11 | $0.22 |
| MiniMax-M2.7 | $0.05 | $0.15 | $0.00022 | $0.22 | $1.56 |
요청당 입력 2,000개 및 출력 800개 토큰입니다. 100만 토큰당 가격은 현재 기준이며 당사 요금제에 따라 함께 변경됩니다. OpenRouter 열은 동일한 작업량에 대한 참고 가격을 보여줍니다.
스트리밍 채팅 응답의 경우 MiniMax-M2.7은 1M 입력 토큰당 $0.05, 1M 출력 토큰당 $0.15으로 책정되며, 180K 컨텍스트와 답변당 최대 8,192 출력 토큰을 제공합니다. 가격은 하루 중 언제든 모델별로 고정되며, 하나의 잔액으로 모든 모델을 사용할 수 있으므로 model 문자열만 바꾸면 자신의 프롬프트로 비교할 수 있습니다.
전체 스크립트#
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 문서에 있습니다.



