การย้ายระบบแอปพลิเคชัน Node.js OpenAI SDK: การตั้งค่า Base URL, Streaming และ Structured Outputs
คู่มือเชิงเทคนิคสำหรับนักพัฒนา Node.js ในการย้ายระบบแอปพลิเคชัน OpenAI SDK: การกำหนดค่า custom base URL, การจัดการระบบยืนยันตัวตน, การจัดการ streaming structured outputs และการวินิจฉัยข้อผิดพลาดของ API
ในหน้านี้
บทนำ
การย้ายระบบแอปพลิเคชัน Node.js เดิมที่ใช้งาน openai SDK ของแท้อยู่นั้น ไม่จำเป็นต้องเขียนโค้ด business logic ขึ้นมาใหม่แต่ประการใด เนื่องจากผู้ให้บริการโมเดลรวมหลายแห่งปฏิบัติตามข้อกำหนด REST ของ OpenAI อย่างเคร่งครัด การปรับเปลี่ยนแอปพลิเคชันที่มีอยู่ให้ทำงานผ่าน routing layer ที่รองรับ OpenAI API อย่างเช่น OneMux จึงต้องการเพียงแค่การอัปเดตพารามิเตอร์ตอนเริ่มต้นไคลเอนต์เท่านั้น
อย่างไรก็ดี pipeline บนระบบ Production มักต้องพึ่งพาฟีเจอร์ระดับสูง ไม่ว่าจะเป็นการบังคับ schema อย่างเคร่งครัดสำหรับการสกัดข้อมูล (data extraction), การทำ streaming ทีละโทเคน และการกู้คืนข้อผิดพลาดอย่างมีเสถียรภาพ คู่มือนี้จะอธิบายวิธีการกำหนดค่า Node.js OpenAI SDK ใหม่, การสกัดข้อมูลด้วย structured JSON schema, การจัดการผลลัพธ์แบบสตรีมอย่างน่าเชื่อถือ และการวินิจฉัยข้อผิดพลาดระหว่างการทำงาน
ขั้นตอนที่ 1: การกำหนดค่า Base URL และการยืนยันตัวตน
OpenAI Node.js SDK ตัวทางการ (openai v4+) รองรับการ override การกำหนดค่าได้โดยตรงผ่าน constructor ของคลาส OpenAI ในการย้าย transport layer ไปยังเอนด์พอยต์ที่เข้ากันได้กับ OpenAI ให้กำหนดค่าพร็อพเพอร์ตี้ baseURL และระบุ API key ของคุณ
การเริ่มต้นการทำงานของ SDK Client
import OpenAI from "openai";
// Initialize the client pointing to an OpenAI-compatible gateway
const client = new OpenAI({
apiKey: process.env.ONEMUX_API_KEY,
baseURL: "https://api.onemux.net/v1",
});
ข้อกำหนดเบื้องต้นก่อนการย้ายระบบ
- Environment Variables: ตรวจสอบให้แน่ใจว่าได้แยกคีย์ของผู้ให้บริการแต่ละรายในไฟล์
.env(ตัวอย่างเช่น แยกONEMUX_API_KEYออกจากOPENAI_API_KEY) - Path Suffix: ตัว SDK ของ OpenAI จะต่อท้าย path ของ route เช่น
/chat/completionsเข้ากับbaseURLให้อัตโนมัติ ดังนั้นโปรดตรวจสอบเสมอว่าbaseURLของคุณลงท้ายด้วย/v1(เช่นhttps://api.onemux.net/v1) เว้นแต่เกตเวย์ของคุณจะจัดการ routing จากรูทโดยตรง - SDK Version: ตรวจสอบว่าคุณใช้งานแพ็กเกจ
openaiเวอร์ชัน^4.0.0ขึ้นไป เนื่องจากเวอร์ชัน v3 เดิมใช้โครงสร้างการกำหนดค่าคนละแบบ (คลาสConfiguration)
หากต้องการดูรายละเอียดเงื่อนไขการกำหนดเส้นทาง (routing) ที่รองรับและเริ่มต้นรับข้อมูลรับรอง API โปรดไปที่ OneMux Quickstart
ขั้นตอนที่ 2: การสกัดข้อมูลแบบมีโครงสร้างด้วย Strict JSON Schema
Structured outputs บังคับให้โมเดลส่งผลลัพธ์กลับมาตรงตามโครงสร้าง JSON Schema ที่กำหนดไว้อย่างแม่นยำ อ้างอิงตามเอกสารประกอบของ OpenAI เกี่ยวกับ structured outputs การตั้งค่า "strict": true มีข้อกำหนดดังนี้:
- ทุกฟิลด์ของออบเจกต์จะต้องถูกระบุไว้ในอาร์เรย์
required - ต้องกำหนด
additionalProperties: falseไว้อย่างชัดเจนในสคีมาของทุกออบเจกต์ - ฟิลด์ที่ไม่บังคับ (optional fields) จะต้องกำหนดชนิดข้อมูลเป็น union type ร่วมกับ
null(เช่น["string", "null"])
การเขียนโค้ด Structured Completion แบบมาตรฐาน
import OpenAI from "openai";
const client = new OpenAI({
apiKey: process.env.ONEMUX_API_KEY,
baseURL: "https://api.onemux.net/v1",
});
// Define strict JSON Schema for invoice extraction
const invoiceExtractionSchema = {
type: "object",
properties: {
vendorName: { type: "string" },
invoiceId: { type: "string" },
totalAmount: { type: "number" },
currency: { type: "string" },
items: {
type: "array",
items: {
type: "object",
properties: {
description: { type: "string" },
quantity: { type: "number" },
unitPrice: { type: "number" },
},
required: ["description", "quantity", "unitPrice"],
additionalProperties: false,
},
},
},
required: ["vendorName", "invoiceId", "totalAmount", "currency", "items"],
additionalProperties: false,
} as const;
async function extractInvoice(rawDocumentText: string) {
const response = await client.chat.completions.create({
model: "gpt-4o",
messages: [
{
role: "system",
content: "Extract normalized invoice records from the provided unstructured input.",
},
{
role: "user",
content: rawDocumentText,
},
],
response_format: {
type: "json_schema",
json_schema: {
name: "invoice_extraction",
strict: true,
schema: invoiceExtractionSchema,
},
},
});
const choice = response.choices[0];
// Check for safety or policy refusals
if (choice.message.refusal) {
throw new Error(`Model refused extraction: ${choice.message.refusal}`);
}
const rawJson = choice.message.content;
if (!rawJson) {
throw new Error("Received empty response payload");
}
return JSON.parse(rawJson);
}
ความเข้ากันได้กับเกตเวย์ปลายทางขึ้นอยู่กับว่าเอนด์พอยต์ของผู้ให้บริการพื้นฐานนั้นรองรับโหมด json_schema ร่วมกับ strict: true หรือไม่ สำหรับรายละเอียดเกี่ยวกับความสามารถของโมเดลบนแต่ละแพลตฟอร์ม สามารถตรวจสอบได้ที่ OneMux Models catalog
ขั้นตอนที่ 3: การจัดการ Streaming ร่วมกับ Structured Outputs
เมื่อเปิดใช้งาน stream: true ควบคู่กับ response_format โมเดลจะสตรีมชิ้นส่วนสตริงย่อยของข้อมูล JSON ออกมา ซึ่งต่างจากการแชตด้วยข้อความทั่วไป ชิ้นส่วนข้อมูล (chunk) แต่ละชิ้นจะไม่สามารถแปลงด้วย JSON.parse() ได้ทันที เพราะเป็นเพียงโทเคน JSON ที่ยังไม่สมบูรณ์
การสะสม JSON Chunks จาก Stream
การสตรีม structured outputs อย่างถูกต้อง ทำได้โดยการสะสมชิ้นส่วน delta จนกระทั่งสตรีมสิ้นสุด หรือส่งต่อ chunk ที่เข้ามาผ่าน parser ที่รองรับการประมวลผล incremental JSON stream
async function streamInvoiceExtraction(rawDocumentText: string) {
const stream = await client.chat.completions.create({
model: "gpt-4o",
stream: true,
messages: [
{
role: "system",
content: "Extract normalized invoice records from the provided text.",
},
{
role: "user",
content: rawDocumentText,
},
],
response_format: {
type: "json_schema",
json_schema: {
name: "invoice_extraction",
strict: true,
schema: invoiceExtractionSchema,
},
},
});
let accumulatedJson = "";
let refusalReason: string | null = null;
for await (const chunk of stream) {
const delta = chunk.choices[0]?.delta;
if (delta?.refusal) {
refusalReason = (refusalReason || "") + delta.refusal;
}
if (delta?.content) {
accumulatedJson += delta.content;
// Emit partial events or log progress if building a reactive UI
}
}
if (refusalReason) {
throw new Error(`Extraction was refused during stream: ${refusalReason}`);
}
try {
return JSON.parse(accumulatedJson);
} catch (err) {
throw new Error(`Failed to parse streamed JSON payload: ${accumulatedJson}`);
}
}
หาก pipeline ของคุณใช้ streaming เพียงเพื่อลดเวลา time-to-first-byte (TTFB) บนหน้าจอผู้ใช้ ให้ตรวจสอบว่าเอนด์พอยต์ปลายทางรองรับการส่ง Server-Sent Events (SSE) อย่างต่อเนื่องผ่านเน็ตเวิร์กพร็อกซีของคุณ
ขั้นตอนที่ 4: การวินิจฉัยและแก้ไขข้อผิดพลาด
เมื่อย้ายการเรียกใช้งาน SDK ไปยัง baseURL แบบกำหนดเอง การตรวจสอบข้อผิดพลาดควรแยกแยะได้ระหว่างปัญหาจากเน็ตเวิร์กพร็อกซี, การตั้งค่า schema ไม่ถูกต้อง, การติดลิมิต rate limit และการปฏิเสธคำขอจากตัวโมเดล
ข้อผิดพลาดที่พบบ่อยและแนวทางการแก้ไข
| รหัสข้อผิดพลาด / ประเภท | สาเหตุ | การวินิจฉัยและวิธีแก้ไข |
|---|---|---|
401 Unauthorized | API key ไม่ถูกต้องหรือไม่มีการส่งคีย์ | ตรวจสอบว่า apiKey มีข้อมูลรับรองที่ถูกต้องซึ่งเกตเวย์ปลายทางยอมรับ (เช่น ตรวจสอบค่า ONEMUX_API_KEY) |
400 BadRequestError | การละเมิดข้อกำหนด Schema (strict: true) | ตรวจสอบข้อความแจ้งเตือนข้อผิดพลาดเพื่อหาคีย์เวิร์ดของ JSON schema ที่ไม่รองรับ (เช่น ลืมใส่ additionalProperties: false หรือระบุฟิลด์ใน required ไม่ครบ) |
404 NotFound | เส้นทาง path หรือ model ID ไม่ถูกต้อง | ตรวจสอบว่า baseURL ลงท้ายด้วย /v1 และยืนยันว่าโมเดลที่เรียกใช้มีอยู่ในรายการโมเดลที่เปิดใช้งานของผู้ให้บริการ |
429 RateLimitError | จำนวนคำขอพร้อมกันหรือวงเงินค่าใช้จ่ายถึงขีดจำกัด | นำเทคนิค exponential backoff มาใช้งาน พร้อมตรวจสอบโควตาผ่านแดชบอร์ดของผู้ให้บริการ |
Refusal (ใน Choice body) | มาตรการความปลอดภัยของโมเดลถูกกระตุ้น | โมเดลสร้างการปฏิเสธคำขออย่างชัดเจน ให้ตรวจสอบค่าที่ choice.message.refusal แทนการอ่านจาก content |
ตัวจัดการข้อผิดพลาดแบบมีโครงสร้าง (Error Handling Wrapper)
import OpenAI from "openai";
async function safeExtractWrapper(fn: () => Promise<any>) {
try {
return await fn();
} catch (error: any) {
if (error instanceof OpenAI.APIError) {
console.error(`OpenAI API Error: status=${error.status}, type=${error.type}`);
console.error(`Message: ${error.message}`);
if (error.status === 400) {
console.error("Verify that your schema complies strictly with JSON Schema constraints.");
} else if (error.status === 401) {
console.error("Authentication failed: Check your bearer token and proxy baseURL.");
} else if (error.status === 429) {
console.warn("Rate limit reached. Queue request for delayed retry.");
}
} else {
console.error("Unexpected application error:", error);
}
throw error;
}
}
ขั้นตอนที่ 5: รวมการจัดการผู้ให้บริการผ่าน OneMux
การส่งคำขอจาก OpenAI SDK ผ่าน OneMux ช่วยให้การทำงานร่วมกับหลากหลายโมเดลเป็นเรื่องง่าย โดยไม่ต้องพึ่งพาไลบรารีภายนอกเพิ่มเติมหรือเขียน adapter ปรับโปรโตคอลเอง
- Unified Endpoint: ใช้งานอินเทอร์เฟซไคลเอนต์เดียวในการกำหนดเส้นทางไปยังโมเดลต่างๆ โดยยังคงใช้รูปแบบคำสั่งมาตรฐานของ OpenAI SDK ได้เหมือนเดิม
- Spend Management & Visibility: รวมศูนย์การเรียกเก็บเงิน ตรวจสอบปริมาณการใช้งานแยกตามโมเดล และกำหนดกฎการใช้จ่ายได้โดยไม่ต้องคอยดูแลใบแจ้งหนี้จากผู้ให้บริการหลายเจ้า
- Flexible Pay-as-You-Go: เติมเครดิตเข้าบัญชีได้อย่างยืดหยุ่น ช่วยป้องกันไม่ให้ระบบหยุดชะงักจากปัญหาบริการขัดข้องกะทันหัน
หากต้องการตรวจสอบอัตราค่าบริการล่าสุดและเปรียบเทียบตัวเลือกการเราต์โมเดลต่างๆ สามารถเข้าไปดูได้ที่ OneMux Pricing Guide และอ่านภาพรวมทางเทคนิคเพิ่มเติมได้ที่ OneMux Documentation
แหล่งอ้างอิง
คำถามที่พบบ่อย
สามารถใช้งาน Zod ร่วมกับ OpenAI SDK เมื่อใช้ custom base URL ได้หรือไม่?
ได้ หากเกตเวย์ของคุณรองรับข้อกำหนด structured outputs ของ `/chat/completions` ครบถ้วน เมธอดตัวช่วยอย่าง `client.beta.chat.completions.parse()` และ `zodResponseFormat()` จะทำงานได้อย่างไร้รอยต่อ โดยจะแปลง Zod schema ไปเป็น strict JSON Schema payload ตามที่ระบบต้องการให้อัตโนมัติ
ทำไมโมเดลถึงส่งข้อผิดพลาด 400 กลับมาเมื่อใช้ 'strict: true'?
กลไก structured outputs ของ OpenAI กำหนดให้ออบเจกต์ทุกตัวต้องระบุ `additionalProperties: false` ไว้อย่างชัดเจน และต้องระบุชื่อพร็อพเพอร์ตี้ทั้งหมดที่ประกาศไว้ลงในอาร์เรย์ `required` หากมีคีย์ใดตกหล่นไปจาก `required` หรือมีการใช้คีย์เวิร์ดของ JSON Schema ที่ไม่รองรับ API จะปฏิเสธคำขอและตอบกลับด้วยข้อผิดพลาด 400 validation error
การเปลี่ยน baseURL ส่งผลต่อการเก็บข้อมูล telemetry หรือ timeout ของ SDK หรือไม่?
การ override ค่า `baseURL` จะเป็นการเปลี่ยนเส้นทางการส่งข้อมูล HTTP ทั้งหมดไปยัง URL ใหม่เท่านั้น ส่วนตัวเลือกเกี่ยวกับ timeout และการลองส่งใหม่ (เช่น `maxRetries` หรือ `timeout` ใน OpenAI constructor) ยังคงทำงานฝั่งไคลเอนต์ตามเดิมทุกประการ
บทความที่เกี่ยวข้อง
Guides
การย้ายระบบ Node.js OpenAI SDK: Base URL, การสตรีม และระบบคัดกรองเคสด้วย Human-in-the-Loop
เรียนรู้วิธีย้ายระบบ Node.js ที่ใช้ OpenAI SDK เดิมไปสู่เกตเวย์ที่รองรับมาตรฐาน OpenAI เช่น OneMux พร้อมจัดการการสตรีม วินิจฉัยข้อผิดพลาด และสร้างเวิร์กโฟลว์คัดกรองเคสสนับสนุนลูกค้าที่มีขั้นตอนอนุมัติโดยมนุษย์
Guides
คู่มือค่าใช้จ่ายและบริบทของ GPT 6.1 Terra สำหรับการดึงข้อมูล JSON ที่เชื่อถือได้
เรียนรู้วิธีประเมินค่าใช้จ่ายเวิร์กโหลด GPT 6.1 Terra และกำหนดขอบเขตบริบท การลองใหม่ และโทเค็นเอาต์พุตสำหรับการดึงข้อมูลแบบมีโครงสร้างด้วยการตรวจสอบ JSON Schema ผ่าน OneMux
Guides
GPT-5.6 Luna API: ราคา ความหน่วง และความน่าเชื่อถือที่ต้องชั่งน้ำหนัก
สำรวจราคา ความหน่วง อัปไทม์ และปริมาณงานของ GPT-5.6 Luna API เรียนรู้วิธีเข้าถึงผ่านเกตเวย์ AI แบบรวมศูนย์ของ OneMux และเมื่อใดควรเลือก Luna แทนโมเดลระดับพรีเมียม
Guides
GPT-5.6 Sol สำหรับฝ่ายสนับสนุนลูกค้า: เลือกโมเดล OpenAI API ให้เหมาะสม
เรียนรู้ว่า GPT-5.6 Sol เหมาะกับงานอัตโนมัติด้านสนับสนุนลูกค้าอย่างไร ราคาเท่าไร และวิธีเข้าถึงผ่าน OneMux API ที่เข้ากันได้กับ OpenAI