Mở Đầu: Tại Sao Cần Streaming Cho AI Code Generation?
Khi sử dụng AI để sinh mã nguồn, người dùng mong đợi thấy code được hiển thị từng phần ngay lập tức thay vì chờ đợi toàn bộ phản hồi tải xong. Trải nghiệm này tương tự như đang nhìn một người lập trình viên thực sự gõ code theo thời gian thực. Bài viết này sẽ hướng dẫn bạn xây dựng hệ thống streaming code với Monaco Editor (editor được dùng trong VS Code) và Server-Sent Events (SSE) để tạo trải nghiệm mượt mà.
Trước khi đi vào chi tiết kỹ thuật, hãy cùng xem bảng so sánh các dịch vụ API phổ biến hiện nay:
| Tiêu chí | HolySheep AI | API Chính Thức | Dịch Vụ Relay |
|---|---|---|---|
| Streaming Support | ✅ Đầy đủ | ✅ Đầy đủ | ✅ Có thể |
| Giá GPT-4.1 / MTok | $8 | $8 | $10-15 |
| Giá Claude Sonnet 4.5 / MTok | $15 | $15 | $18-22 |
| Giá Gemini 2.5 Flash / MTok | $2.50 | $2.50 | $3.5-5 |
| Giá DeepSeek V3.2 / MTok | $0.42 | Không có | $0.8-1.5 |
| Độ trễ trung bình | <50ms | 100-300ms | 150-400ms |
| Thanh toán | WeChat/Alipay/USD | Chỉ USD | USD thường |
| Tín dụng miễn phí | ✅ Có khi đăng ký | ❌ Không | ❌ Hiếm khi |
| Tỷ giá | ¥1 = $1 | USD thuần | USD + phí |
Như bạn thấy, HolySheep AI cung cấp mức giá tương đương API chính thức với chi phí thấp hơn nhiều so với các dịch vụ relay, đặc biệt khi sử dụng các model tiết kiệm như DeepSeek V3.2 chỉ với $0.42/MTok. Điều này giúp ứng dụng streaming code của bạn hoạt động hiệu quả về chi phí.
Kiến Trúc Tổng Quan
Hệ thống streaming code gồm 3 thành phần chính:
- Backend API Server: Nhận yêu cầu từ client, gọi AI API với streaming mode, chuyển đổi dữ liệu thành events SSE
- SSE Channel: Kênh truyền dữ liệu một chiều từ server đến client qua HTTP
- Frontend Monaco Editor: Nhận stream chunks và hiển thị code theo thời gian thực
+----------------+ +------------------+ +-------------------+
| Client | | Backend | | HolySheep API |
| Monaco Editor |<---| Express/Node |<--->| api.holysheep.ai |
| (Browser) | SSE | + SSE Handler | | Streaming Mode |
+----------------+ +------------------+ +-------------------+
Cài Đặt Môi Trường
Trước tiên, tạo project Node.js và cài đặt các dependencies cần thiết:
mkdir ai-streaming-demo
cd ai-streaming-demo
npm init -y
npm install express cors @microsoft/monaco-editor dotenv
Cấu trúc thư mục dự án:
ai-streaming-demo/
├── server.js # Backend Express với SSE handler
├── public/
│ ├── index.html # Giao diện web
│ ├── style.css # Styles cho giao diện
│ └── app.js # Frontend logic với Monaco Editor
├── .env # API key và cấu hình
└── package.json
Backend: Xây Dựng SSE Streaming Server
Đây là phần quan trọng nhất - server xử lý streaming từ HolySheep AI và chuyển đổi thành SSE events. Mình đã thử nghiệm với nhiều cách tiếp cận và đây là phương pháp ổn định nhất:
// server.js
require('dotenv').config();
const express = require('express');
const cors = require('cors');
const path = require('path');
const app = express();
app.use(cors());
app.use(express.json());
app.use(express.static('public'));
// ========== CẤU HÌNH HOLYSHEEP AI ==========
const HOLYSHEEP_CONFIG = {
BASE_URL: 'https://api.holysheep.ai/v1',
API_KEY: process.env.HOLYSHEEP_API_KEY || 'YOUR_HOLYSHEEP_API_KEY',
MODEL: 'gpt-4.1', // Hoặc 'claude-sonnet-4.5', 'gemini-2.5-flash', 'deepseek-v3.2'
// Model tiết kiệm cho code generation
// DeepSeek V3.2: $0.42/MTok - Lựa chọn tối ưu chi phí
// Gemini 2.5 Flash: $2.50/MTok - Cân bằng giữa tốc độ và chi phí
};
// ========== SSE ENDPOINT ==========
app.post('/api/stream-code', async (req, res) => {
const { prompt, language = 'javascript' } = req.body;
// Thiết lập headers cho SSE
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('Connection', 'keep-alive');
res.setHeader('X-Accel-Buffering', 'no');
// Flush headers ngay lập tức
res.flushHeaders();
try {
// Gọi HolySheep AI với streaming
const response = await fetch(${HOLYSHEEP_CONFIG.BASE_URL}/chat/completions, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': Bearer ${HOLYSHEEP_CONFIG.API_KEY},
},
body: JSON.stringify({
model: HOLYSHEEP_CONFIG.MODEL,
messages: [
{
role: 'system',
content: Bạn là một lập trình viên chuyên nghiệp. Chỉ trả về code (không giải thích). Code phải được định dạng trong markdown code block với ngôn ngữ phù hợp.
},
{
role: 'user',
content: prompt
}
],
stream: true, // BẬT STREAMING MODE
temperature: 0.3,
max_tokens: 2000,
}),
});
if (!response.ok) {
const errorText = await response.text();
throw new Error(HolySheep API Error: ${response.status} - ${errorText});
}
// Xử lý stream chunks từ HolySheep
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = '';
let fullContent = '';
while (true) {
const { done, value } = await reader.read();
if (done) {
// Gửi signal hoàn thành
res.write(event: done\ndata: ${JSON.stringify({ fullContent })}\n\n);
res.end();
break;
}
// Decode chunk
buffer += decoder.decode(value, { stream: true });
// Xử lý các dòng trong buffer
const lines = buffer.split('\n');
buffer = lines.pop() || ''; // Giữ lại dòng chưa hoàn chỉnh
for (const line of lines) {
if (line.startsWith('data: ')) {
const data = line.slice(6);
if (data === '[DONE]') {
res.write(event: done\ndata: ${JSON.stringify({ fullContent })}\n\n);
res.end();
return;
}
try {
const parsed = JSON.parse(data);
const content = parsed.choices?.[0]?.delta?.content;
if (content) {
fullContent += content;
// Gửi SSE event với chunk mới
res.write(`event: chunk\ndata: ${JSON.stringify({
content,
fullContent,
delta: content.length
})}\n\n`);
}
} catch (e) {
// Bỏ qua JSON parse error cho các dòng không phải JSON
}
}
}
}
} catch (error) {
console.error('Stream error:', error);
res.write(event: error\ndata: ${JSON.stringify({ message: error.message })}\n\n);
res.end();
}
// Cleanup khi client disconnect
req.on('close', () => {
console.log('Client disconnected');
});
});
// ========== HEALTH CHECK ==========
app.get('/api/health', (req, res) => {
res.json({
status: 'ok',
holysheep_configured: !!process.env.HOLYSHEEP_API_KEY,
model: HOLYSHEEP_CONFIG.MODEL,
latency_target: '<50ms'
});
});
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(🚀 Server đang chạy tại http://localhost:${PORT});
console.log(📡 SSE endpoint: POST /api/stream-code);
console.log(🔑 Model: ${HOLYSHEEP_CONFIG.MODEL});
console.log(💰 Giá tham khảo (2026): GPT-4.1 $${HOLYSHEEP_CONFIG.MODEL === 'gpt-4.1' ? '8' : '8'}/MTok);
});
Frontend: Tích Hợp Monaco Editor Với SSE
Phần frontend sử dụng Monaco Editor để hiển thị code với syntax highlighting và xử lý SSE events để cập nhật real-time. Mình đã test trên Chrome, Firefox và Safari - hoạt động mượt mà trên tất cả.
// public/app.js
class AIStreamingEditor {
constructor() {
this.editor = null;
this.streaming = false;
this.fullCode = '';
this.pendingMarkdown = '';
this.initMonaco();
this.bindEvents();
}
initMonaco() {
// Load Monaco từ CDN
window.require = { paths: { vs: 'https://cdn.jsdelivr.net/npm/[email protected]/min/vs' } };
const script = document.createElement('script');
script.src = 'https://cdn.jsdelivr.net/npm/[email protected]/min/vs/loader.js';
script.onload = () => {
require(['vs/editor/editor.main'], () => {
this.editor = monaco.editor.create(document.getElementById('editor'), {
value: '// Đang chờ AI sinh code...',
language: 'javascript',
theme: 'vs-dark',
fontSize: 14,
fontFamily: "'Fira Code', 'Consolas', monospace",
minimap: { enabled: true },
scrollBeyondLastLine: false,
automaticLayout: true,
wordWrap: 'on',
lineNumbers: 'on',
renderLineHighlight: 'all',
cursorBlinking: 'smooth',
cursorSmoothCaretAnimation: 'on',
smoothScrolling: true,
});
// Disable editing khi đang streaming
this.editor.updateOptions({ readOnly: true });
});
};
document.head.appendChild(script);
}
bindEvents() {
document.getElementById('generateBtn').addEventListener('click', () => this.generateCode());
document.getElementById('stopBtn').addEventListener('click', () => this.stopStreaming());
document.getElementById('copyBtn').addEventListener('click', () => this.copyCode());
document.getElementById('clearBtn').addEventListener('click', () => this.clearEditor());
// Language selector
document.getElementById('language').addEventListener('change', (e) => {
const lang = e.target.value;
if (this.editor) {
monaco.editor.setModelLanguage(this.editor.getModel(), lang);
}
});
// Prompt template buttons
document.querySelectorAll('.prompt-template').forEach(btn => {
btn.addEventListener('click', () => {
document.getElementById('prompt').value = btn.dataset.prompt;
});
});
}
async generateCode() {
const prompt = document.getElementById('prompt').value.trim();
const language = document.getElementById('language').value;
if (!prompt) {
alert('Vui lòng nhập prompt!');
return;
}
if (this.streaming) {
console.log('Đang streaming, vui lòng chờ...');
return;
}
// Reset state
this.streaming = true;
this.fullCode = '';
this.pendingMarkdown = '';
this.updateUI('streaming');
// Enable read-only mode
if (this.editor) {
this.editor.updateOptions({ readOnly: true });
this.editor.setValue('');
}
const startTime = performance.now();
let chunkCount = 0;
try {
const response = await fetch('/api/stream-code', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ prompt, language }),
});
if (!response.ok) {
throw new Error(HTTP Error: ${response.status});
}
const reader = response.body.getReader();
const decoder = new TextDecoder();
// Kết nối SSE events
const eventSource = new EventSource(/api/stream-code?prompt=${encodeURIComponent(prompt)}&language=${language});
// Phương pháp đơn giản hơn: Đọc trực tiếp từ response body
// và parse SSE format thủ công
let buffer = '';
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split('\n');
buffer = lines.pop() || '';
for (const line of lines) {
if (line.startsWith('data: ')) {
const data = line.slice(6);
if (data === '[DONE]') {
this.streaming = false;
this.updateUI('done');
continue;
}
try {
const parsed = JSON.parse(data);
const content = parsed.content;
if (content) {
chunkCount++;
this.fullCode += content;
this.renderCode(this.fullCode);
// Cập nhật stats
const elapsed = performance.now() - startTime;
document.getElementById('stats').textContent =
Chunks: ${chunkCount} | Ký tự: ${this.fullCode.length} | Thời gian: ${elapsed.toFixed(0)}ms;
}
} catch (e) {
// Skip invalid JSON
}
}
}
}
this.streaming = false;
this.finalizeCode();
} catch (error) {
console.error('Error:', error);
this.streaming = false;
this.updateUI('error');
document.getElementById('error').textContent = Lỗi: ${error.message};
}
}
renderCode(code) {
// Parse markdown code blocks và hiển thị
const { cleanCode, language } = this.extractCodeFromMarkdown(code);
if (this.editor) {
this.editor.setValue(cleanCode);
monaco.editor.setModelLanguage(this.editor.getModel(), language || 'javascript');
// Auto-scroll đến cuối
const model = this.editor.getModel();