LLM sinh token lần lượt, nên stream từng mảnh ra client ngay khi có thay vì đợi xong cả câu trả lời.
Được gì: người dùng thấy chữ chạy sau chưa tới một giây, nên tổng thời gian 10 giây vẫn thấy nhanh; và họ dừng được giữa chừng khi thấy model đi sai hướng, tiết kiệm cả thời gian lẫn token. Với chat, trợ lý code hay trợ lý thoại thì đây là yêu cầu bắt buộc, không phải tuỳ chọn.
Giao thức: thực tế gần như luôn là SSE (Server-Sent Events) — một chiều server đẩy về, chạy trên HTTP thường nên qua được CDN và proxy, và đây cũng là định dạng các provider trả về. Chỉ dùng WebSocket khi cần hai chiều thật sự: trợ lý thoại, hoặc cho phép người dùng chen ngang khi model đang nói.
export async function POST(req: Request) {
const stream = await openai.chat.completions.create({ stream: true, ... })
return new Response(new ReadableStream({
async start(ctrl) {
for await (const chunk of stream) {
const delta = chunk.choices[0]?.delta?.content
if (delta) ctrl.enqueue(new TextEncoder().encode(delta))
}
ctrl.close()
},
}), { headers: { "Content-Type": "text/event-stream" } })
}Bốn thứ hay làm hỏng streaming trên production:
- Proxy đệm mất luồng. Nginx và một số CDN gom chunk lại rồi mới trả, thế là stream biến thành một cục. Phải tắt buffer (
X-Accel-Buffering: no). Đây là lỗi hay gặp nhất và triệu chứng dễ đánh lừa, vì code hoàn toàn đúng. - Huỷ không lan lên trên. Người dùng đóng tab mà lời gọi tới provider vẫn chạy tiếp thì vẫn bị tính tiền. Phải truyền
AbortControllerxuống tận SDK. - Lỗi giữa luồng. Đã trả một phần rồi mới hỏng thì không còn status code nào để báo. Cần quy ước một sự kiện lỗi trong chính luồng và client xử lý tử tế.
- Đầu ra có cấu trúc. JSON và tool call về theo từng mảnh, chưa parse được cho tới mảnh cuối. Muốn hiện dần thì cần parser JSON từng phần.
Vài điểm nhỏ về trải nghiệm: hiện chỉ báo đang gõ trước khi token đầu về; gom chunk rồi render theo nhịp khoảng 16ms để chữ không nhấp nháy; và nếu cần quét PII trước khi hiện thì phải đệm lại vài token — đổi một chút độ trễ lấy an toàn.
Đo: theo dõi TTFT thay vì tổng độ trễ, cộng độ trễ giữa các token và tỉ lệ đứt kết nối. Lưu ý số token chỉ có ở sự kiện cuối, muốn tính chi phí thì đọc ở đó.