กลับไปที่บล็อก

API และ SDKคู่มือ

สตรีมคำตอบแชทใน Node.js ด้วย OpenAI SDK

สตรีม chat completion ทีละโทเคนใน Node.js: ลูป for await หยุดตามต้องการ จัดการข้อผิดพลาด และ token usage ด้วย OpenAI SDK

โดย CheapTokens Teamเผยแพร่เมื่อ อัปเดตเมื่อ ใช้เวลาอ่าน 4 นาที

หน้าต่างเทอร์มินัลที่พิมพ์คำตอบแบบสตรีมทีละโทเคนจากสคริปต์ Node.js
ในหน้านี้
  1. สิ่งที่คุณต้องมี
  2. ขั้นตอนที่ 1 สตรีมคำตอบด้วย for await
  3. ขั้นตอนที่ 2 หยุดสตรีมตามต้องการ
  4. ขั้นตอนที่ 3 จัดการข้อผิดพลาด
  5. ขั้นตอนที่ 4 อ่าน usage และประเมินค่าใช้จ่าย
  6. สคริปต์เต็ม
  7. ขั้นตอนถัดไป

การสตรีมช่วยให้แอปของคุณแสดงคำตอบขณะที่กำลังถูกสร้าง แทนที่จะรอคำตอบทั้งหมด ในคู่มือนี้คุณจะสร้างสคริปต์ Node.js ขนาดเล็กที่สตรีม chat completion จาก CheapTokens API ทีละโทเคน หยุดได้ตามต้องการ และจัดการข้อผิดพลาด เวอร์ชัน Python ของหัวข้อนี้อยู่ใน Streaming responses in Python

สิ่งที่คุณต้องมี#

  • Node.js 22 หรือใหม่กว่า (เวอร์ชันขั้นต่ำที่ระบุใน README ของแพ็กเกจ openai ตรวจสอบเมื่อ 22 ตุลาคม 2026)
  • API key ของ CheapTokens หากยังไม่มี ให้ทำตาม Your first API request with CheapTokens
  • SDK อย่างเป็นทางการ: npm install openai

CheapTokens เปิดให้ใช้ API ที่เข้ากันได้กับ OpenAI ที่ https://api.cheaptokens.dev/v1 ดังนั้นแพ็กเกจ openai อย่างเป็นทางการจึงใช้งานได้โดยเปลี่ยนเพียง base URL และ key เก็บ key ไว้ใน environment variable ไม่ใช่ในโค้ด:

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 จะอ่าน Server-Sent Events จาก API และส่ง chunk หนึ่งให้คุณต่อการวนซ้ำหนึ่งครั้งของ for await มีสามรายละเอียดที่สำคัญ:

  • ข้อความมาใน chunk.choices[0].delta.content บาง chunk ไม่มีข้อความ (chunk แรกมักมีเพียง role) ดังนั้นตรวจสอบว่า delta ไม่ว่าง
  • process.stdout.write พิมพ์โดยไม่ขึ้นบรรทัดใหม่ คำต่าง ๆ จึงปรากฏต่อเนื่องกัน
  • ด้วย stream_options: { include_usage: true } chunk สุดท้ายจะมีออบเจกต์ usage พร้อมจำนวนโทเคน prompt และ completion และอาร์เรย์ choices ของมันว่างเปล่า นั่นคือเหตุผลที่โค้ดใช้ chunk.choices[0]?. กับ optional chaining

model id มาจากเอกสาร CheapTokens: ผู้ให้บริการตั้งชื่อโมเดลเดียวกันต่างกัน ดังนั้นคัดลอก id จากที่นั่น

ขั้นตอนที่ 2 หยุดสตรีมตามต้องการ#

ผู้ใช้กด Stop ปิดแท็บ หรือสลับแชท ยกเลิกคำขอเพื่อไม่ให้คุณรอโทเคนที่ไม่มีใครจะอ่าน ส่ง AbortSignal ใน request options:

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 ยังมี stream.controller.abort() ของตัวเอง และการออกจากลูป for await ด้วย break ก็ปิดการเชื่อมต่อเช่นกัน ใช้อันใดก็ได้ที่ใกล้กับโค้ดของคุณที่สุด: signal เมื่อปุ่มยกเลิกอยู่ที่อื่น, break เมื่อการตัดสินใจเกิดขึ้นภายในลูป (เช่น คุณเจอ stop word)

ขั้นตอนที่ 3 จัดการข้อผิดพลาด#

สตรีมอาจล้มเหลวก่อนโทเคนแรก (key ผิด ยอดเงินว่าง) หรือกลางทาง (เครือข่ายหลุด) ครอบทั้งคำขอและลูปไว้ใน 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 หมายถึง key ผิดหรือไม่มี, 429 หมายถึงคำขอมากเกินไป และข้อความเกี่ยวกับโทเคนหมายถึงยอดเงินต้องเติมเพิ่ม สาเหตุและวิธีแก้เพิ่มเติมอยู่ใน Common connection errors and how to fix them และ "Not enough tokens" error
  • ข้อความที่พิมพ์ออกไปแล้วยังคงใช้ได้หลังเกิดข้อผิดพลาด เก็บไว้หากคุณต้องการแสดงคำตอบบางส่วน

ขั้นตอนที่ 4 อ่าน usage และประเมินค่าใช้จ่าย#

ออบเจกต์ usage เก็บ prompt_tokens และ completion_tokens เมื่อรวมกับราคาโมเดลต่อ 1M โทเคน จะให้ค่าใช้จ่ายที่แน่นอนของคำขอ สำหรับการประเมินคร่าว ๆ ก่อนรันอะไรก็ตาม ใช้ตารางสดด้านล่าง: แสดงค่าใช้จ่ายของ 1,000 คำขอ โดยแต่ละคำขอมี 2,000 โทเคน input และ 800 โทเคน output

โมเดล ต่อคำขอ 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 โทเค็นเอาต์พุตต่อคำขอ ราคาต่อ 1M โทเค็นเป็นราคาปัจจุบันและเปลี่ยนแปลงไปพร้อมกับอัตราค่าบริการของเรา คอลัมน์ OpenRouter แสดงราคาอ้างอิงสำหรับปริมาณงานเดียวกัน

สำหรับคำตอบแชทแบบสตรีม MiniMax-M2.7 ราคา $0.05 ต่อ 1M โทเคน input และ $0.15 ต่อ 1M โทเคน output พร้อม context 180K และ output สูงสุด 8,192 โทเคนต่อคำตอบ ราคาคงที่ต่อโมเดลในทุกช่วงเวลาของวัน และยอดเงินเดียวครอบคลุมทุกโมเดล ดังนั้นการเปลี่ยนสตริง model ก็เพียงพอที่จะเปรียบเทียบโมเดลด้วย prompt ของคุณเอง

สคริปต์เต็ม#

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

ขั้นตอนถัดไป#

  • ส่ง chunk ไปยังเบราว์เซอร์ด้วย Server-Sent Events จาก endpoint ของคุณเอง เพื่อให้ key ไม่ออกจากเซิร์ฟเวอร์เลย
  • ลองโมเดลอื่นโดยเปลี่ยนเพียงสตริง model: MiniMax-M2.7 ใช้ได้บนยอดเงินเดียวกันและ base URL เดียวกัน
  • รับ key และรันสคริปต์: create a CheapTokens API key และเติมยอดเงิน

Endpoint, model id และขีดจำกัดของทุกโมเดลอยู่ใน CheapTokens docs