Di chuyển ứng dụng Node.js dùng OpenAI SDK: Base URL, Streaming và Structured Outputs
Hướng dẫn kỹ thuật dành cho lập trình viên Node.js khi di chuyển ứng dụng dùng OpenAI SDK: cấu hình base URL tùy chỉnh, quản lý xác thực, xử lý structured outputs dạng streaming và chẩn đoán lỗi API.
Mục lục
Giới thiệu
Việc di chuyển một ứng dụng Node.js hiện có đang sử dụng SDK chính thức openai không đòi hỏi bạn phải viết lại logic nghiệp vụ. Vì nhiều nhà cung cấp mô hình hợp nhất tuân thủ nghiêm ngặt các đặc tả REST của OpenAI, việc điều chỉnh ứng dụng hiện có để chạy qua một lớp định tuyến tương thích với OpenAI—chẳng hạn như OneMux—chỉ yêu cầu cập nhật các tham số khởi tạo client.
Tuy nhiên, các pipeline trong môi trường production thường phụ thuộc vào các tính năng nâng cao: tính tuân thủ schema nghiêm ngặt khi trích xuất dữ liệu, streaming từng token và khả năng khôi phục lỗi mạnh mẽ. Hướng dẫn này sẽ hướng dẫn cách cấu hình lại Node.js OpenAI SDK, thực thi trích xuất JSON schema có cấu trúc, xử lý phản hồi streaming đáng tin cậy và chẩn đoán lỗi trong quá trình thực thi.
Bước 1: Cấu hình Base URL và xác thực
Node.js SDK chính thức của OpenAI (openai v4+) cho phép ghi đè cấu hình trực tiếp bên trong constructor của lớp OpenAI. Để di chuyển tầng truyền tải sang một endpoint tương thích với OpenAI, hãy định cấu hình thuộc tính baseURL và thiết lập API key của bạn.
Khởi tạo 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",
});
Điều kiện tiên quyết trước khi di chuyển
- Biến môi trường: Đảm bảo phân tách các khóa API của từng nhà cung cấp trong cấu hình
.envcủa bạn (ví dụ:ONEMUX_API_KEYthay vìOPENAI_API_KEY). - Hậu tố đường dẫn: OpenAI SDK tự động nối thêm các đường dẫn định tuyến như
/chat/completionsvàobaseURL. Luôn đảm bảo rằngbaseURLcủa bạn có chứa hậu tố/v1(ví dụ:https://api.onemux.net/v1) trừ khi gateway của bạn xử lý định tuyến gốc. - Phiên bản SDK: Xác minh rằng bạn đang chạy phiên bản
openaitừ^4.0.0trở lên, vì các phiên bản v3 cũ hơn sử dụng cấu trúc cấu hình khác (lớpConfiguration).
Để xem lại các quy tắc định tuyến được hỗ trợ và bắt đầu với thông tin xác thực API, hãy truy cập OneMux Quickstart.
Bước 2: Trích xuất có cấu trúc với JSON Schema nghiêm ngặt
Structured outputs buộc các mô hình phải trả về kết quả tuân thủ chính xác theo một JSON Schema được cung cấp. Theo tài liệu của OpenAI về structured outputs, việc đặt "strict": true đòi hỏi:
- Tất cả các trường của object phải được liệt kê trong mảng
required. additionalProperties: falsephải được xác định rõ ràng trên tất cả schema của object.- Các trường tùy chọn phải được mô hình hóa dưới dạng union types với
null(ví dụ:["string", "null"]).
Triển khai Structured Completion tiêu chuẩn
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);
}
Khả năng tương thích với các gateway hạ nguồn phụ thuộc vào việc endpoint của nhà cung cấp cơ bản có hỗ trợ chế độ json_schema với strict: true hay không. Để biết chi tiết về khả năng hỗ trợ mô hình trên các nền tảng, hãy xem Danh mục mô hình OneMux.
Bước 3: Xử lý Streaming với Structured Outputs
Khi kích hoạt stream: true cùng với response_format, mô hình sẽ stream các đoạn chuỗi phân đoạn của payload JSON. Khác với chat dạng văn bản thông thường, các chunk riêng lẻ không thể được parse trực tiếp bằng JSON.parse() vì chúng đại diện cho các token JSON chưa hoàn chỉnh.
Tích lũy các JSON chunk trong luồng Stream
Để stream structured outputs đúng cách, hãy tích lũy các đoạn delta cho đến khi luồng stream hoàn tất, hoặc chuyển tiếp các chunk nhận được qua một bộ parser luồng JSON tăng dần.
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}`);
}
}
Nếu pipeline của bạn chỉ sử dụng streaming nhằm mục đích tối ưu hóa thời gian nhận byte đầu tiên (TTFB) trên giao diện người dùng, hãy xác minh rằng endpoint hạ nguồn có hỗ trợ Server-Sent Events (SSE) liên tục thông qua network proxy của bạn.
Bước 4: Chẩn đoán và xử lý sự cố lỗi
Khi di chuyển các lệnh gọi SDK sang một baseURL tùy chỉnh, việc chẩn đoán lỗi cần phân biệt rõ ràng giữa lỗi network proxy, cấu hình schema sai, giới hạn tốc độ (rate limit) và việc mô hình từ chối phản hồi (refusals).
Các lỗi phổ biến và chiến lược xử lý
| Mã lỗi / Phân loại | Nguyên nhân gốc rễ | Chẩn đoán & Cách khắc phục |
|---|---|---|
401 Unauthorized | API key không hợp lệ hoặc bị thiếu | Xác nhận rằng apiKey của bạn chứa thông tin xác thực hợp lệ được gateway mục tiêu công nhận (ví dụ: kiểm tra ONEMUX_API_KEY). |
400 BadRequestError | Vi phạm schema (strict: true) | Kiểm tra thông báo lỗi để tìm các từ khóa JSON schema không được hỗ trợ (ví dụ: thiếu additionalProperties: false hoặc thiếu các trường trong required). |
404 NotFound | Sai đường dẫn hoặc sai Model ID | Đảm bảo baseURL kết thúc bằng /v1. Xác nhận mô hình được yêu cầu có tồn tại trong danh sách mô hình đang hoạt động của nhà cung cấp. |
429 RateLimitError | Chạm ngưỡng đồng thời hoặc hạn mức chi tiêu | Triển khai cơ chế backoff theo cấp số nhân (exponential backoff). Giám sát hạn mức quota thông qua dashboard của nhà cung cấp. |
Refusal (Body của Choice) | Kích hoạt rào cản an toàn (safety guardrails) của mô hình | Mô hình đã tạo ra phản hồi từ chối rõ ràng. Hãy kiểm tra choice.message.refusal thay vì tìm trong content. |
Trình bọc (wrapper) xử lý lỗi có cấu trúc
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;
}
}
Bước 5: Hợp nhất các nhà cung cấp thông qua OneMux
Định tuyến các yêu cầu OpenAI SDK qua OneMux giúp đơn giản hóa các hoạt động đa mô hình mà không yêu cầu thêm thư viện bên thứ ba hoặc các bộ điều hợp giao thức tùy chỉnh.
- Endpoint hợp nhất: Duy trì một giao diện client duy nhất trong khi vẫn định tuyến qua nhiều mô hình bằng ngữ nghĩa tiêu chuẩn của OpenAI SDK.
- Quản lý & Giám sát chi tiêu: Tập trung hóa việc thanh toán, xem xét mức sử dụng theo từng mô hình và thiết lập các quy tắc chi tiêu mà không cần quản lý hóa đơn từ nhiều nhà cung cấp khác nhau.
- Trả phí linh hoạt (Pay-as-You-Go): Nạp tiền vào tài khoản thông qua các gói tín dụng linh hoạt, giúp ngăn ngừa sự cố gián đoạn dịch vụ ngoài ý muốn.
Để xem bảng giá sử dụng hiện tại và so sánh các tùy chọn định tuyến giữa các mô hình, hãy tham khảo Bảng giá OneMux và đọc tài liệu tổng quan kỹ thuật trong Tài liệu OneMux.
Tài liệu tham khảo
FAQ
Tôi có thể sử dụng Zod với OpenAI SDK khi dùng base URL tùy chỉnh không?
Có. Nếu gateway của bạn triển khai đầy đủ đặc tả structured outputs cho `/chat/completions`, các phương thức tiện ích như `client.beta.chat.completions.parse()` và `zodResponseFormat()` sẽ hoạt động bình thường bằng cách chuyển đổi Zod schema thành payload JSON Schema nghiêm ngặt theo yêu cầu.
Tại sao mô hình trả về lỗi 400 khi sử dụng 'strict: true'?
Cơ chế structured outputs của OpenAI yêu cầu mọi định nghĩa object phải khai báo rõ ràng `additionalProperties: false` và liệt kê tất cả các thuộc tính đã khai báo bên trong mảng `required`. Nếu thiếu bất kỳ khóa nào trong `required`, hoặc nếu xuất hiện các từ khóa JSON Schema không được hỗ trợ, API sẽ từ chối yêu cầu với lỗi xác thực 400.
Việc thay đổi baseURL có ảnh hưởng đến khả năng đo lường (telemetry) hoặc cơ chế timeout của SDK không?
Việc ghi đè `baseURL` sẽ chuyển hướng toàn bộ luồng truyền tải HTTP đến URL mới. Các tùy chọn cấu hình timeout và thử lại (chẳng hạn như `maxRetries` hoặc `timeout` trong hàm khởi tạo của OpenAI) vẫn hoạt động hoàn toàn bình thường ở phía client.
Bài viết liên quan
Guides
Di chuyển ứng dụng Node.js OpenAI SDK: Base URL, Streaming và Phân loại hỗ trợ có con người can thiệp (Human-in-the-Loop)
Tìm hiểu cách chuyển đổi ứng dụng Node.js OpenAI SDK hiện có sang một gateway tương thích OpenAI như OneMux, xử lý streaming, chẩn đoán lỗi và xây dựng quy trình phân loại hỗ trợ khách hàng có con người phê duyệt.
Guides
Hướng dẫn chi phí và ngữ cảnh GPT 6.1 Terra cho trích xuất JSON đáng tin cậy
Tìm hiểu cách ước tính chi phí workload GPT 6.1 Terra và giới hạn ngữ cảnh, số lần thử lại cũng như số token đầu ra cho việc trích xuất có cấu trúc với xác thực JSON Schema thông qua OneMux.
Guides
API GPT-5.6 Luna: Chi phí, Độ trễ và Đánh đổi Độ tin cậy
Khám phá chi phí, độ trễ, uptime và thông lượng của API GPT-5.6 Luna. Tìm hiểu cách truy cập qua cổng AI hợp nhất OneMux và khi nào nên chọn Luna thay vì các mô hình cao cấp.
Guides
GPT-5.6 Sol cho hỗ trợ khách hàng: Chọn đúng mô hình OpenAI API
Tìm hiểu cách GPT-5.6 Sol phù hợp với tự động hóa hỗ trợ khách hàng, giá của nó và cách truy cập thông qua API tương thích OpenAI hợp nhất của OneMux.