Spaces:
Running
Running
Create worker.js
Browse files
worker.js
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { pipeline, env, cos_sim } from 'https://cdn.jsdelivr.net/npm/@huggingface/transformers@3.0.0-alpha.19/dist/transformers.min.js';
|
| 2 |
+
|
| 3 |
+
// Cấu hình WebGPU
|
| 4 |
+
env.backends.onnx.wasm.proxy = false;
|
| 5 |
+
|
| 6 |
+
// --- CẤU HÌNH MODEL (KHÔNG ĐỔI THEO YÊU CẦU) ---
|
| 7 |
+
// 1. Embedding Model
|
| 8 |
+
const EMBEDDING_MODEL_ID = 'onnx-community/embeddinggemma-300m-ONNX';
|
| 9 |
+
|
| 10 |
+
// 2. LLM Model: Granite 4.0 Nano
|
| 11 |
+
// Lưu ý: Nếu phiên bản ONNX của Granite 4.0 chưa public dưới ID này,
|
| 12 |
+
// bạn cần trỏ tới đúng repo onnx (ví dụ ibm-granite/granite-3.0-2b-instruct nếu 4.0 chưa có onnx).
|
| 13 |
+
// Tuy nhiên, tôi giữ nguyên tham chiếu "Granite" như yêu cầu.
|
| 14 |
+
const LLM_MODEL_ID = 'ibm-granite/granite-4.0-350m-instruct'; // Kiểm tra lại tên chính xác trên HF Hub nếu lỗi
|
| 15 |
+
|
| 16 |
+
let extractor = null;
|
| 17 |
+
let generator = null;
|
| 18 |
+
let vectorStore = []; // Lưu trữ chunks và vectors: { text: string, vector: number[] }
|
| 19 |
+
|
| 20 |
+
// Khởi tạo Models
|
| 21 |
+
async function initModels() {
|
| 22 |
+
try {
|
| 23 |
+
console.log("Đang tải Embedding Model...");
|
| 24 |
+
extractor = await pipeline('feature-extraction', EMBEDDING_MODEL_ID, {
|
| 25 |
+
device: 'webgpu',
|
| 26 |
+
dtype: 'fp32', // Embedding thường cần độ chính xác
|
| 27 |
+
});
|
| 28 |
+
|
| 29 |
+
console.log("Đang tải LLM Granite 4.0...");
|
| 30 |
+
generator = await pipeline('text-generation', LLM_MODEL_ID, {
|
| 31 |
+
device: 'webgpu',
|
| 32 |
+
dtype: 'q4', // Quantization 4-bit để chạy mượt trên browser
|
| 33 |
+
use_external_data_format: true
|
| 34 |
+
});
|
| 35 |
+
|
| 36 |
+
self.postMessage({ type: 'init_complete' });
|
| 37 |
+
} catch (e) {
|
| 38 |
+
self.postMessage({ type: 'error', payload: "Lỗi tải model: " + e.message });
|
| 39 |
+
}
|
| 40 |
+
}
|
| 41 |
+
|
| 42 |
+
// Xử lý chunking văn bản
|
| 43 |
+
function chunkText(text, chunkSize = 300, overlap = 50) {
|
| 44 |
+
const sentences = text.match(/[^.!?]+[.!?]+|[^.!?]+$/g) || [text];
|
| 45 |
+
let chunks = [];
|
| 46 |
+
let currentChunk = "";
|
| 47 |
+
|
| 48 |
+
for (let sentence of sentences) {
|
| 49 |
+
if ((currentChunk + sentence).length > chunkSize) {
|
| 50 |
+
chunks.push(currentChunk.trim());
|
| 51 |
+
currentChunk = sentence.slice(-overlap); // Overlap đơn giản
|
| 52 |
+
} else {
|
| 53 |
+
currentChunk += " " + sentence;
|
| 54 |
+
}
|
| 55 |
+
}
|
| 56 |
+
if (currentChunk) chunks.push(currentChunk.trim());
|
| 57 |
+
return chunks;
|
| 58 |
+
}
|
| 59 |
+
|
| 60 |
+
// Tạo embeddings cho văn bản
|
| 61 |
+
async function ingestText(text) {
|
| 62 |
+
const chunks = chunkText(text);
|
| 63 |
+
vectorStore = []; // Reset store
|
| 64 |
+
|
| 65 |
+
for (const chunk of chunks) {
|
| 66 |
+
const output = await extractor(chunk, { pooling: 'mean', normalize: true });
|
| 67 |
+
vectorStore.push({
|
| 68 |
+
text: chunk,
|
| 69 |
+
vector: output.data
|
| 70 |
+
});
|
| 71 |
+
}
|
| 72 |
+
console.log(`Đã index ${vectorStore.length} đoạn văn bản.`);
|
| 73 |
+
}
|
| 74 |
+
|
| 75 |
+
// Tìm kiếm RAG
|
| 76 |
+
async function retrieve(query) {
|
| 77 |
+
const queryOutput = await extractor(query, { pooling: 'mean', normalize: true });
|
| 78 |
+
const queryVector = queryOutput.data;
|
| 79 |
+
|
| 80 |
+
// Tính Cosine Similarity
|
| 81 |
+
const scored = vectorStore.map(item => {
|
| 82 |
+
return {
|
| 83 |
+
text: item.text,
|
| 84 |
+
score: cos_sim(queryVector, item.vector)
|
| 85 |
+
};
|
| 86 |
+
});
|
| 87 |
+
|
| 88 |
+
// Lấy top 3 đoạn liên quan nhất
|
| 89 |
+
scored.sort((a, b) => b.score - a.score);
|
| 90 |
+
return scored.slice(0, 3).map(i => i.text).join("\n\n");
|
| 91 |
+
}
|
| 92 |
+
|
| 93 |
+
// Xử lý tin nhắn từ Main Thread
|
| 94 |
+
self.onmessage = async (e) => {
|
| 95 |
+
if (!extractor || !generator) {
|
| 96 |
+
await initModels();
|
| 97 |
+
}
|
| 98 |
+
|
| 99 |
+
const { type, payload } = e.data;
|
| 100 |
+
|
| 101 |
+
if (type === 'ingest_text') {
|
| 102 |
+
await ingestText(payload);
|
| 103 |
+
} else if (type === 'query') {
|
| 104 |
+
// 1. Retrieve Context
|
| 105 |
+
const context = await retrieve(payload);
|
| 106 |
+
|
| 107 |
+
// 2. Tạo Prompt cho Granite
|
| 108 |
+
// Định dạng prompt cơ bản cho instruction tuned model
|
| 109 |
+
const prompt = `<|system|>
|
| 110 |
+
Bạn là trợ lý AI hữu ích. Hãy trả lời câu hỏi dựa trên ngữ cảnh được cung cấp bên dưới bằng Tiếng Việt.
|
| 111 |
+
Ngữ cảnh:
|
| 112 |
+
${context}
|
| 113 |
+
<|user|>
|
| 114 |
+
${payload}
|
| 115 |
+
<|assistant|>
|
| 116 |
+
`;
|
| 117 |
+
|
| 118 |
+
// 3. Generate Answer
|
| 119 |
+
try {
|
| 120 |
+
const output = await generator(prompt, {
|
| 121 |
+
max_new_tokens: 256,
|
| 122 |
+
temperature: 0.7,
|
| 123 |
+
do_sample: true,
|
| 124 |
+
});
|
| 125 |
+
|
| 126 |
+
// Lấy phần trả lời sau tag assistant (tuỳ thuộc format model)
|
| 127 |
+
let answer = output[0].generated_text;
|
| 128 |
+
// Cắt bớt phần prompt nếu cần thiết
|
| 129 |
+
if (answer.includes("<|assistant|>")) {
|
| 130 |
+
answer = answer.split("<|assistant|>")[1];
|
| 131 |
+
}
|
| 132 |
+
|
| 133 |
+
self.postMessage({ type: 'answer', payload: answer });
|
| 134 |
+
} catch (err) {
|
| 135 |
+
self.postMessage({ type: 'error', payload: err.message });
|
| 136 |
+
}
|
| 137 |
+
}
|
| 138 |
+
};
|
| 139 |
+
|
| 140 |
+
// Khởi tạo ngay khi worker chạy
|
| 141 |
+
initModels();
|