Commit ·
327cd3e
1
Parent(s): 5060d16
feat: Migrate the default LLM from ChatGLM3 to Qwen2-0.5B-Instruct, updating model loading, generation logic, and documentation.
Browse files- .env.example +1 -1
- QUICKSTART.md +1 -1
- README.md +6 -6
- app.py +3 -3
- config/model_config.py +1 -1
- requirements.txt +1 -0
- utils/rag_pipeline.py +122 -22
.env.example
CHANGED
|
@@ -1,5 +1,5 @@
|
|
| 1 |
# Model Configuration
|
| 2 |
-
MODEL_NAME=
|
| 3 |
EMBEDDING_MODEL=sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2
|
| 4 |
|
| 5 |
# Device Configuration (auto/cuda/cpu)
|
|
|
|
| 1 |
# Model Configuration
|
| 2 |
+
MODEL_NAME=Qwen/Qwen2-0.5B-Instruct
|
| 3 |
EMBEDDING_MODEL=sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2
|
| 4 |
|
| 5 |
# Device Configuration (auto/cuda/cpu)
|
QUICKSTART.md
CHANGED
|
@@ -22,7 +22,7 @@ Aplikasi akan:
|
|
| 22 |
2. Inisialisasi vector database
|
| 23 |
3. Launch Gradio interface di `http://localhost:7860`
|
| 24 |
|
| 25 |
-
**Catatan**: Model
|
| 26 |
|
| 27 |
## 📚 Workflow Penggunaan
|
| 28 |
|
|
|
|
| 22 |
2. Inisialisasi vector database
|
| 23 |
3. Launch Gradio interface di `http://localhost:7860`
|
| 24 |
|
| 25 |
+
**Catatan**: Model Qwen2 akan di-download otomatis saat pertama kali digunakan (ukuran ~1GB, jauh lebih kecil dari model sebelumnya).
|
| 26 |
|
| 27 |
## 📚 Workflow Penggunaan
|
| 28 |
|
README.md
CHANGED
|
@@ -32,7 +32,7 @@ short_description: Chat dengan dokumen PDF menggunakan RAG dan GLM Model
|
|
| 32 |
|
| 33 |
RAG ChatBot adalah aplikasi AI yang memungkinkan Anda untuk mengupload dokumen PDF dan melakukan tanya jawab interaktif tentang isi dokumen tersebut. Sistem menggunakan:
|
| 34 |
|
| 35 |
-
- **
|
| 36 |
- **RAG (Retrieval-Augmented Generation)**: Teknik untuk mencari informasi relevan dari dokumen
|
| 37 |
- **ChromaDB**: Vector database untuk penyimpanan dan pencarian semantic
|
| 38 |
- **Gradio**: Interface web yang modern dan interaktif
|
|
@@ -145,7 +145,7 @@ Aplikasi akan berjalan di `http://localhost:7860`
|
|
| 145 |
└────┬─────┘
|
| 146 |
│
|
| 147 |
┌────▼─────┐
|
| 148 |
-
│
|
| 149 |
└──────────┘
|
| 150 |
```
|
| 151 |
|
|
@@ -183,7 +183,7 @@ Edit file `.env` untuk mengatur konfigurasi:
|
|
| 183 |
|
| 184 |
```bash
|
| 185 |
# Model
|
| 186 |
-
MODEL_NAME=
|
| 187 |
EMBEDDING_MODEL=sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2
|
| 188 |
|
| 189 |
# Device (auto/cuda/cpu)
|
|
@@ -227,8 +227,8 @@ Berikut dependencies utama yang digunakan:
|
|
| 227 |
|
| 228 |
### Model Loading Error
|
| 229 |
```bash
|
| 230 |
-
#
|
| 231 |
-
|
| 232 |
```
|
| 233 |
|
| 234 |
### PDF Extraction Error
|
|
@@ -254,5 +254,5 @@ Untuk pertanyaan dan support, silakan buat issue di repository ini.
|
|
| 254 |
---
|
| 255 |
|
| 256 |
<div align="center">
|
| 257 |
-
Made with ❤️ using Gradio and
|
| 258 |
</div>
|
|
|
|
| 32 |
|
| 33 |
RAG ChatBot adalah aplikasi AI yang memungkinkan Anda untuk mengupload dokumen PDF dan melakukan tanya jawab interaktif tentang isi dokumen tersebut. Sistem menggunakan:
|
| 34 |
|
| 35 |
+
- **Qwen2-0.5B-Instruct**: Model bahasa generatif yang ringan untuk menghasilkan jawaban
|
| 36 |
- **RAG (Retrieval-Augmented Generation)**: Teknik untuk mencari informasi relevan dari dokumen
|
| 37 |
- **ChromaDB**: Vector database untuk penyimpanan dan pencarian semantic
|
| 38 |
- **Gradio**: Interface web yang modern dan interaktif
|
|
|
|
| 145 |
└────┬─────┘
|
| 146 |
│
|
| 147 |
┌────▼─────┐
|
| 148 |
+
│ Qwen2 │ (Response Generation)
|
| 149 |
└──────────┘
|
| 150 |
```
|
| 151 |
|
|
|
|
| 183 |
|
| 184 |
```bash
|
| 185 |
# Model
|
| 186 |
+
MODEL_NAME=Qwen/Qwen2-0.5B-Instruct
|
| 187 |
EMBEDDING_MODEL=sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2
|
| 188 |
|
| 189 |
# Device (auto/cuda/cpu)
|
|
|
|
| 227 |
|
| 228 |
### Model Loading Error
|
| 229 |
```bash
|
| 230 |
+
# Model saat ini sudah sangat ringan (~1GB)
|
| 231 |
+
# Jika masih ada masalah, pastikan koneksi internet stabil untuk download
|
| 232 |
```
|
| 233 |
|
| 234 |
### PDF Extraction Error
|
|
|
|
| 254 |
---
|
| 255 |
|
| 256 |
<div align="center">
|
| 257 |
+
Made with ❤️ using Gradio and Qwen2
|
| 258 |
</div>
|
app.py
CHANGED
|
@@ -375,7 +375,7 @@ with gr.Blocks(css=CUSTOM_CSS, theme=gr.themes.Soft(), title="RAG ChatBot - GLM"
|
|
| 375 |
|
| 376 |
**Tech Stack:**
|
| 377 |
- Framework: Gradio
|
| 378 |
-
- LLM:
|
| 379 |
- Embeddings: Sentence Transformers
|
| 380 |
- Vector DB: ChromaDB
|
| 381 |
- PDF Processing: PyPDF2 + pdfplumber
|
|
@@ -385,7 +385,7 @@ with gr.Blocks(css=CUSTOM_CSS, theme=gr.themes.Soft(), title="RAG ChatBot - GLM"
|
|
| 385 |
gr.Markdown("""
|
| 386 |
Untuk mengubah konfigurasi model, edit file `.env`:
|
| 387 |
```bash
|
| 388 |
-
MODEL_NAME=
|
| 389 |
DEVICE=auto
|
| 390 |
CHUNK_SIZE=500
|
| 391 |
CHUNK_OVERLAP=50
|
|
@@ -401,7 +401,7 @@ if __name__ == "__main__":
|
|
| 401 |
os.makedirs(config.VECTOR_DB_DIR, exist_ok=True)
|
| 402 |
|
| 403 |
print("=" * 60)
|
| 404 |
-
print("🚀 Launching RAG ChatBot dengan
|
| 405 |
print("=" * 60)
|
| 406 |
print(f"Model: {config.MODEL_NAME}")
|
| 407 |
print(f"Device: {config.DEVICE}")
|
|
|
|
| 375 |
|
| 376 |
**Tech Stack:**
|
| 377 |
- Framework: Gradio
|
| 378 |
+
- LLM: Qwen2 (Transformers)
|
| 379 |
- Embeddings: Sentence Transformers
|
| 380 |
- Vector DB: ChromaDB
|
| 381 |
- PDF Processing: PyPDF2 + pdfplumber
|
|
|
|
| 385 |
gr.Markdown("""
|
| 386 |
Untuk mengubah konfigurasi model, edit file `.env`:
|
| 387 |
```bash
|
| 388 |
+
MODEL_NAME=Qwen/Qwen2-0.5B-Instruct
|
| 389 |
DEVICE=auto
|
| 390 |
CHUNK_SIZE=500
|
| 391 |
CHUNK_OVERLAP=50
|
|
|
|
| 401 |
os.makedirs(config.VECTOR_DB_DIR, exist_ok=True)
|
| 402 |
|
| 403 |
print("=" * 60)
|
| 404 |
+
print("🚀 Launching RAG ChatBot dengan Qwen2")
|
| 405 |
print("=" * 60)
|
| 406 |
print(f"Model: {config.MODEL_NAME}")
|
| 407 |
print(f"Device: {config.DEVICE}")
|
config/model_config.py
CHANGED
|
@@ -11,7 +11,7 @@ class Config:
|
|
| 11 |
"""Configuration class for RAG ChatBot"""
|
| 12 |
|
| 13 |
# Model Settings
|
| 14 |
-
MODEL_NAME = os.getenv("MODEL_NAME", "
|
| 15 |
EMBEDDING_MODEL = os.getenv("EMBEDDING_MODEL", "sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2")
|
| 16 |
|
| 17 |
# Device Configuration
|
|
|
|
| 11 |
"""Configuration class for RAG ChatBot"""
|
| 12 |
|
| 13 |
# Model Settings
|
| 14 |
+
MODEL_NAME = os.getenv("MODEL_NAME", "Qwen/Qwen2-0.5B-Instruct")
|
| 15 |
EMBEDDING_MODEL = os.getenv("EMBEDDING_MODEL", "sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2")
|
| 16 |
|
| 17 |
# Device Configuration
|
requirements.txt
CHANGED
|
@@ -11,6 +11,7 @@ langchain>=0.1.0
|
|
| 11 |
langchain-community>=0.0.20
|
| 12 |
langchain-text-splitters>=0.0.1
|
| 13 |
sentencepiece
|
|
|
|
| 14 |
|
| 15 |
# PDF Processing
|
| 16 |
PyPDF2>=3.0.0
|
|
|
|
| 11 |
langchain-community>=0.0.20
|
| 12 |
langchain-text-splitters>=0.0.1
|
| 13 |
sentencepiece
|
| 14 |
+
dontenv
|
| 15 |
|
| 16 |
# PDF Processing
|
| 17 |
PyPDF2>=3.0.0
|
utils/rag_pipeline.py
CHANGED
|
@@ -3,7 +3,7 @@ RAG Pipeline for retrieving relevant context and generating responses
|
|
| 3 |
"""
|
| 4 |
from typing import List, Dict, Optional
|
| 5 |
import torch
|
| 6 |
-
from transformers import AutoTokenizer,
|
| 7 |
from config.model_config import config
|
| 8 |
from utils.vector_store import VectorStore
|
| 9 |
|
|
@@ -29,7 +29,7 @@ class RAGPipeline:
|
|
| 29 |
return config.DEVICE
|
| 30 |
|
| 31 |
def load_model(self):
|
| 32 |
-
"""Load
|
| 33 |
if self.model is not None:
|
| 34 |
print("Model already loaded")
|
| 35 |
return
|
|
@@ -38,16 +38,23 @@ class RAGPipeline:
|
|
| 38 |
print(f"Using device: {self.device}")
|
| 39 |
|
| 40 |
try:
|
|
|
|
|
|
|
| 41 |
self.tokenizer = AutoTokenizer.from_pretrained(
|
| 42 |
config.MODEL_NAME,
|
| 43 |
trust_remote_code=True
|
| 44 |
)
|
| 45 |
|
| 46 |
-
self.model =
|
| 47 |
config.MODEL_NAME,
|
| 48 |
trust_remote_code=True,
|
| 49 |
-
torch_dtype=torch.float16 if self.device == "cuda" else torch.float32
|
| 50 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 51 |
|
| 52 |
# Set to evaluation mode
|
| 53 |
self.model = self.model.eval()
|
|
@@ -113,7 +120,7 @@ class RAGPipeline:
|
|
| 113 |
|
| 114 |
Args:
|
| 115 |
query: User query
|
| 116 |
-
history: Chat history (
|
| 117 |
use_rag: Whether to use RAG retrieval
|
| 118 |
max_length: Maximum response length
|
| 119 |
temperature: Sampling temperature
|
|
@@ -146,17 +153,58 @@ class RAGPipeline:
|
|
| 146 |
else:
|
| 147 |
prompt = query
|
| 148 |
|
| 149 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 150 |
try:
|
| 151 |
-
|
| 152 |
-
|
| 153 |
-
|
| 154 |
-
|
| 155 |
-
|
| 156 |
-
temperature=temperature,
|
| 157 |
-
top_p=top_p
|
| 158 |
)
|
| 159 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 160 |
return response, sources
|
| 161 |
|
| 162 |
except Exception as e:
|
|
@@ -211,17 +259,69 @@ class RAGPipeline:
|
|
| 211 |
else:
|
| 212 |
prompt = query
|
| 213 |
|
| 214 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 215 |
try:
|
| 216 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 217 |
self.tokenizer,
|
| 218 |
-
|
| 219 |
-
|
| 220 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 221 |
temperature=temperature,
|
| 222 |
-
top_p=top_p
|
| 223 |
-
|
| 224 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 225 |
|
| 226 |
except Exception as e:
|
| 227 |
print(f"Error streaming response: {e}")
|
|
|
|
| 3 |
"""
|
| 4 |
from typing import List, Dict, Optional
|
| 5 |
import torch
|
| 6 |
+
from transformers import AutoTokenizer, AutoModelForCausalLM
|
| 7 |
from config.model_config import config
|
| 8 |
from utils.vector_store import VectorStore
|
| 9 |
|
|
|
|
| 29 |
return config.DEVICE
|
| 30 |
|
| 31 |
def load_model(self):
|
| 32 |
+
"""Load Qwen2 model and tokenizer"""
|
| 33 |
if self.model is not None:
|
| 34 |
print("Model already loaded")
|
| 35 |
return
|
|
|
|
| 38 |
print(f"Using device: {self.device}")
|
| 39 |
|
| 40 |
try:
|
| 41 |
+
from transformers import AutoModelForCausalLM
|
| 42 |
+
|
| 43 |
self.tokenizer = AutoTokenizer.from_pretrained(
|
| 44 |
config.MODEL_NAME,
|
| 45 |
trust_remote_code=True
|
| 46 |
)
|
| 47 |
|
| 48 |
+
self.model = AutoModelForCausalLM.from_pretrained(
|
| 49 |
config.MODEL_NAME,
|
| 50 |
trust_remote_code=True,
|
| 51 |
+
torch_dtype=torch.float16 if self.device == "cuda" else torch.float32,
|
| 52 |
+
device_map=self.device if self.device == "cuda" else None
|
| 53 |
+
)
|
| 54 |
+
|
| 55 |
+
# Move to device if CPU
|
| 56 |
+
if self.device == "cpu":
|
| 57 |
+
self.model = self.model.to(self.device)
|
| 58 |
|
| 59 |
# Set to evaluation mode
|
| 60 |
self.model = self.model.eval()
|
|
|
|
| 120 |
|
| 121 |
Args:
|
| 122 |
query: User query
|
| 123 |
+
history: Chat history (list of [user_msg, assistant_msg] pairs)
|
| 124 |
use_rag: Whether to use RAG retrieval
|
| 125 |
max_length: Maximum response length
|
| 126 |
temperature: Sampling temperature
|
|
|
|
| 153 |
else:
|
| 154 |
prompt = query
|
| 155 |
|
| 156 |
+
# Build messages for Qwen2 chat format
|
| 157 |
+
messages = []
|
| 158 |
+
|
| 159 |
+
# Add system message
|
| 160 |
+
messages.append({
|
| 161 |
+
"role": "system",
|
| 162 |
+
"content": config.SYSTEM_PROMPT
|
| 163 |
+
})
|
| 164 |
+
|
| 165 |
+
# Add chat history
|
| 166 |
+
if history:
|
| 167 |
+
for user_msg, assistant_msg in history:
|
| 168 |
+
if user_msg:
|
| 169 |
+
messages.append({"role": "user", "content": user_msg})
|
| 170 |
+
if assistant_msg:
|
| 171 |
+
messages.append({"role": "assistant", "content": assistant_msg})
|
| 172 |
+
|
| 173 |
+
# Add current query
|
| 174 |
+
messages.append({"role": "user", "content": prompt})
|
| 175 |
+
|
| 176 |
+
# Generate response using Qwen2
|
| 177 |
try:
|
| 178 |
+
# Apply chat template
|
| 179 |
+
text = self.tokenizer.apply_chat_template(
|
| 180 |
+
messages,
|
| 181 |
+
tokenize=False,
|
| 182 |
+
add_generation_prompt=True
|
|
|
|
|
|
|
| 183 |
)
|
| 184 |
|
| 185 |
+
# Tokenize
|
| 186 |
+
model_inputs = self.tokenizer([text], return_tensors="pt").to(self.device)
|
| 187 |
+
|
| 188 |
+
# Generate
|
| 189 |
+
with torch.no_grad():
|
| 190 |
+
generated_ids = self.model.generate(
|
| 191 |
+
model_inputs.input_ids,
|
| 192 |
+
max_new_tokens=max_length,
|
| 193 |
+
temperature=temperature,
|
| 194 |
+
top_p=top_p,
|
| 195 |
+
do_sample=True,
|
| 196 |
+
pad_token_id=self.tokenizer.pad_token_id,
|
| 197 |
+
eos_token_id=self.tokenizer.eos_token_id
|
| 198 |
+
)
|
| 199 |
+
|
| 200 |
+
# Decode response
|
| 201 |
+
generated_ids = [
|
| 202 |
+
output_ids[len(input_ids):]
|
| 203 |
+
for input_ids, output_ids in zip(model_inputs.input_ids, generated_ids)
|
| 204 |
+
]
|
| 205 |
+
|
| 206 |
+
response = self.tokenizer.batch_decode(generated_ids, skip_special_tokens=True)[0]
|
| 207 |
+
|
| 208 |
return response, sources
|
| 209 |
|
| 210 |
except Exception as e:
|
|
|
|
| 259 |
else:
|
| 260 |
prompt = query
|
| 261 |
|
| 262 |
+
# Build messages for Qwen2 chat format
|
| 263 |
+
messages = []
|
| 264 |
+
|
| 265 |
+
# Add system message
|
| 266 |
+
messages.append({
|
| 267 |
+
"role": "system",
|
| 268 |
+
"content": config.SYSTEM_PROMPT
|
| 269 |
+
})
|
| 270 |
+
|
| 271 |
+
# Add chat history
|
| 272 |
+
if history:
|
| 273 |
+
for user_msg, assistant_msg in history:
|
| 274 |
+
if user_msg:
|
| 275 |
+
messages.append({"role": "user", "content": user_msg})
|
| 276 |
+
if assistant_msg:
|
| 277 |
+
messages.append({"role": "assistant", "content": assistant_msg})
|
| 278 |
+
|
| 279 |
+
# Add current query
|
| 280 |
+
messages.append({"role": "user", "content": prompt})
|
| 281 |
+
|
| 282 |
+
# Stream response using Qwen2
|
| 283 |
try:
|
| 284 |
+
from transformers import TextIteratorStreamer
|
| 285 |
+
from threading import Thread
|
| 286 |
+
|
| 287 |
+
# Apply chat template
|
| 288 |
+
text = self.tokenizer.apply_chat_template(
|
| 289 |
+
messages,
|
| 290 |
+
tokenize=False,
|
| 291 |
+
add_generation_prompt=True
|
| 292 |
+
)
|
| 293 |
+
|
| 294 |
+
# Tokenize
|
| 295 |
+
model_inputs = self.tokenizer([text], return_tensors="pt").to(self.device)
|
| 296 |
+
|
| 297 |
+
# Setup streamer
|
| 298 |
+
streamer = TextIteratorStreamer(
|
| 299 |
self.tokenizer,
|
| 300 |
+
skip_prompt=True,
|
| 301 |
+
skip_special_tokens=True
|
| 302 |
+
)
|
| 303 |
+
|
| 304 |
+
# Generation kwargs
|
| 305 |
+
generation_kwargs = dict(
|
| 306 |
+
model_inputs,
|
| 307 |
+
streamer=streamer,
|
| 308 |
+
max_new_tokens=max_length,
|
| 309 |
temperature=temperature,
|
| 310 |
+
top_p=top_p,
|
| 311 |
+
do_sample=True,
|
| 312 |
+
pad_token_id=self.tokenizer.pad_token_id,
|
| 313 |
+
eos_token_id=self.tokenizer.eos_token_id
|
| 314 |
+
)
|
| 315 |
+
|
| 316 |
+
# Start generation in separate thread
|
| 317 |
+
thread = Thread(target=self.model.generate, kwargs=generation_kwargs)
|
| 318 |
+
thread.start()
|
| 319 |
+
|
| 320 |
+
# Stream output
|
| 321 |
+
full_response = ""
|
| 322 |
+
for new_text in streamer:
|
| 323 |
+
full_response += new_text
|
| 324 |
+
yield full_response, sources
|
| 325 |
|
| 326 |
except Exception as e:
|
| 327 |
print(f"Error streaming response: {e}")
|