Spaces:
Build error
Build error
Stefanus Simandjuntak
commited on
Commit
·
9b4ef96
0
Parent(s):
initial commit
Browse files- .gitignore +86 -0
- README.md +243 -0
- api_server.py +323 -0
- configs/training_config.yaml +28 -0
- convert_dataset.py +138 -0
- deploy_to_novita.py +254 -0
- docker-compose.yml +0 -0
- novita_chat_app.py +187 -0
- novita_rag_chat.py +302 -0
- requirements.txt +38 -0
- run_all.sh +82 -0
- run_alternative_models.sh +90 -0
- run_complete_workflow.py +208 -0
- run_novita.sh +60 -0
- scripts/create_sample_dataset.py +195 -0
- scripts/download_alternative_models.py +186 -0
- scripts/download_model.py +120 -0
- scripts/download_open_models.py +163 -0
- scripts/finetune_lora.py +251 -0
- scripts/local_training_setup.py +273 -0
- scripts/novita_ai_setup.py +256 -0
- scripts/novita_ai_setup_v2.py +376 -0
- scripts/run_novita_finetuning.py +117 -0
- scripts/test_model.py +201 -0
- scripts/test_novita_connection.py +158 -0
- scripts/train_with_monitoring.py +228 -0
- setup.bat +54 -0
- setup.sh +52 -0
- setup_novita.sh +56 -0
- templates/chat.html +350 -0
- test_novita_simple.py +112 -0
- web_app.py +161 -0
.gitignore
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Virtual Environment
|
| 2 |
+
venv/
|
| 3 |
+
env/
|
| 4 |
+
ENV/
|
| 5 |
+
|
| 6 |
+
# Python
|
| 7 |
+
__pycache__/
|
| 8 |
+
*.py[cod]
|
| 9 |
+
*$py.class
|
| 10 |
+
*.so
|
| 11 |
+
.Python
|
| 12 |
+
build/
|
| 13 |
+
develop-eggs/
|
| 14 |
+
dist/
|
| 15 |
+
downloads/
|
| 16 |
+
eggs/
|
| 17 |
+
.eggs/
|
| 18 |
+
lib/
|
| 19 |
+
lib64/
|
| 20 |
+
parts/
|
| 21 |
+
sdist/
|
| 22 |
+
var/
|
| 23 |
+
wheels/
|
| 24 |
+
*.egg-info/
|
| 25 |
+
.installed.cfg
|
| 26 |
+
*.egg
|
| 27 |
+
MANIFEST
|
| 28 |
+
|
| 29 |
+
# Model files (too large for git)
|
| 30 |
+
models/
|
| 31 |
+
*.bin
|
| 32 |
+
*.safetensors
|
| 33 |
+
*.ckpt
|
| 34 |
+
*.pt
|
| 35 |
+
*.pth
|
| 36 |
+
|
| 37 |
+
# Data files
|
| 38 |
+
data/*.jsonl
|
| 39 |
+
data/*.json
|
| 40 |
+
data/*.csv
|
| 41 |
+
data/*.txt
|
| 42 |
+
|
| 43 |
+
# Logs
|
| 44 |
+
logs/
|
| 45 |
+
*.log
|
| 46 |
+
*.out
|
| 47 |
+
|
| 48 |
+
# Environment variables
|
| 49 |
+
.env
|
| 50 |
+
.env.local
|
| 51 |
+
.env.production
|
| 52 |
+
|
| 53 |
+
# IDE
|
| 54 |
+
.vscode/
|
| 55 |
+
.idea/
|
| 56 |
+
*.swp
|
| 57 |
+
*.swo
|
| 58 |
+
*~
|
| 59 |
+
|
| 60 |
+
# OS
|
| 61 |
+
.DS_Store
|
| 62 |
+
Thumbs.db
|
| 63 |
+
|
| 64 |
+
# Jupyter Notebook
|
| 65 |
+
.ipynb_checkpoints
|
| 66 |
+
|
| 67 |
+
# PyTorch
|
| 68 |
+
*.pkl
|
| 69 |
+
*.pickle
|
| 70 |
+
|
| 71 |
+
# HuggingFace
|
| 72 |
+
.cache/
|
| 73 |
+
huggingface/
|
| 74 |
+
|
| 75 |
+
# Docker
|
| 76 |
+
.dockerignore
|
| 77 |
+
|
| 78 |
+
# Temporary files
|
| 79 |
+
tmp/
|
| 80 |
+
temp/
|
| 81 |
+
*.tmp
|
| 82 |
+
*.temp
|
| 83 |
+
|
| 84 |
+
|
| 85 |
+
|
| 86 |
+
|
README.md
ADDED
|
@@ -0,0 +1,243 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Base LLM Setup - Llama 3.1 8B dengan LoRA
|
| 2 |
+
|
| 3 |
+
Setup lengkap untuk fine-tuning model Llama 3.1 8B menggunakan LoRA (Low-Rank Adaptation).
|
| 4 |
+
|
| 5 |
+
## 🚀 Fitur
|
| 6 |
+
|
| 7 |
+
- **Base Model**: Llama 3.1 8B Instruct
|
| 8 |
+
- **Fine-tuning**: LoRA untuk efisiensi memory
|
| 9 |
+
- **Format Data**: JSONL (JSON Lines)
|
| 10 |
+
- **Environment**: Virtual environment dengan Python
|
| 11 |
+
- **Inference**: vLLM untuk serving model
|
| 12 |
+
- **Monitoring**: Logs dan metrics
|
| 13 |
+
|
| 14 |
+
## 📁 Struktur Direktori
|
| 15 |
+
|
| 16 |
+
```
|
| 17 |
+
base-llm-setup/
|
| 18 |
+
├── models/ # Model weights
|
| 19 |
+
├── data/ # Training datasets (JSONL)
|
| 20 |
+
├── scripts/ # Python scripts
|
| 21 |
+
│ ├── download_model.py # Download base model
|
| 22 |
+
│ ├── finetune_lora.py # LoRA fine-tuning
|
| 23 |
+
│ ├── test_model.py # Test fine-tuned model
|
| 24 |
+
│ └── create_sample_dataset.py # Create sample data
|
| 25 |
+
├── configs/ # Configuration files
|
| 26 |
+
├── logs/ # Training logs
|
| 27 |
+
├── venv/ # Virtual environment
|
| 28 |
+
├── requirements.txt # Python dependencies
|
| 29 |
+
├── setup.sh # Setup script
|
| 30 |
+
├── docker-compose.yml # Docker services
|
| 31 |
+
└── README.md # This file
|
| 32 |
+
```
|
| 33 |
+
|
| 34 |
+
## 🛠️ Prerequisites
|
| 35 |
+
|
| 36 |
+
- Python 3.8+
|
| 37 |
+
- CUDA-compatible GPU (untuk training)
|
| 38 |
+
- Docker & Docker Compose
|
| 39 |
+
- HuggingFace account dan token
|
| 40 |
+
|
| 41 |
+
## ⚡ Quick Start
|
| 42 |
+
|
| 43 |
+
### 1. Setup Environment
|
| 44 |
+
|
| 45 |
+
```bash
|
| 46 |
+
# Clone atau buat folder
|
| 47 |
+
cd base-llm-setup
|
| 48 |
+
|
| 49 |
+
# Jalankan setup script
|
| 50 |
+
chmod +x setup.sh
|
| 51 |
+
./setup.sh
|
| 52 |
+
```
|
| 53 |
+
|
| 54 |
+
### 2. Aktifkan Virtual Environment
|
| 55 |
+
|
| 56 |
+
```bash
|
| 57 |
+
source venv/bin/activate
|
| 58 |
+
```
|
| 59 |
+
|
| 60 |
+
### 3. Set HuggingFace Token
|
| 61 |
+
|
| 62 |
+
```bash
|
| 63 |
+
export HUGGINGFACE_TOKEN="your_token_here"
|
| 64 |
+
```
|
| 65 |
+
|
| 66 |
+
### 4. Download Base Model
|
| 67 |
+
|
| 68 |
+
```bash
|
| 69 |
+
python scripts/download_model.py
|
| 70 |
+
```
|
| 71 |
+
|
| 72 |
+
### 5. Buat Dataset (JSONL)
|
| 73 |
+
|
| 74 |
+
```bash
|
| 75 |
+
python scripts/create_sample_dataset.py
|
| 76 |
+
```
|
| 77 |
+
|
| 78 |
+
### 6. Fine-tuning dengan LoRA
|
| 79 |
+
|
| 80 |
+
```bash
|
| 81 |
+
python scripts/finetune_lora.py
|
| 82 |
+
```
|
| 83 |
+
|
| 84 |
+
### 7. Test Model
|
| 85 |
+
|
| 86 |
+
```bash
|
| 87 |
+
python scripts/test_model.py
|
| 88 |
+
```
|
| 89 |
+
|
| 90 |
+
## 📊 Format Dataset JSONL
|
| 91 |
+
|
| 92 |
+
Dataset harus dalam format JSONL (JSON Lines) dengan struktur:
|
| 93 |
+
|
| 94 |
+
```jsonl
|
| 95 |
+
{"text": "Apa itu machine learning?", "category": "education", "language": "id"}
|
| 96 |
+
{"text": "Jelaskan tentang deep learning", "category": "education", "language": "id"}
|
| 97 |
+
{"text": "Bagaimana cara kerja neural network?", "category": "education", "language": "id"}
|
| 98 |
+
```
|
| 99 |
+
|
| 100 |
+
**Field yang diperlukan:**
|
| 101 |
+
- `text`: Teks untuk training (wajib)
|
| 102 |
+
- `category`: Kategori data (opsional)
|
| 103 |
+
- `language`: Bahasa (opsional, default: "id")
|
| 104 |
+
|
| 105 |
+
## 🔧 Konfigurasi
|
| 106 |
+
|
| 107 |
+
### Model Configuration (`configs/llama_config.yaml`)
|
| 108 |
+
|
| 109 |
+
```yaml
|
| 110 |
+
model_name: "meta-llama/Llama-3.1-8B-Instruct"
|
| 111 |
+
model_path: "./models/llama-3.1-8b-instruct"
|
| 112 |
+
max_length: 8192
|
| 113 |
+
temperature: 0.7
|
| 114 |
+
top_p: 0.9
|
| 115 |
+
top_k: 40
|
| 116 |
+
repetition_penalty: 1.1
|
| 117 |
+
|
| 118 |
+
# LoRA Configuration
|
| 119 |
+
lora_config:
|
| 120 |
+
r: 16
|
| 121 |
+
lora_alpha: 32
|
| 122 |
+
lora_dropout: 0.1
|
| 123 |
+
target_modules: ["q_proj", "v_proj", "k_proj", "o_proj", "gate_proj", "up_proj", "down_proj"]
|
| 124 |
+
|
| 125 |
+
# Training Configuration
|
| 126 |
+
training_config:
|
| 127 |
+
learning_rate: 2e-4
|
| 128 |
+
batch_size: 4
|
| 129 |
+
gradient_accumulation_steps: 4
|
| 130 |
+
num_epochs: 3
|
| 131 |
+
warmup_steps: 100
|
| 132 |
+
save_steps: 500
|
| 133 |
+
eval_steps: 500
|
| 134 |
+
```
|
| 135 |
+
|
| 136 |
+
### Docker Configuration
|
| 137 |
+
|
| 138 |
+
```bash
|
| 139 |
+
# Start vLLM service
|
| 140 |
+
docker-compose up -d vllm
|
| 141 |
+
|
| 142 |
+
# Check status
|
| 143 |
+
docker-compose ps
|
| 144 |
+
|
| 145 |
+
# View logs
|
| 146 |
+
docker-compose logs -f vllm
|
| 147 |
+
```
|
| 148 |
+
|
| 149 |
+
## 🧪 Testing
|
| 150 |
+
|
| 151 |
+
### Interactive Mode
|
| 152 |
+
```bash
|
| 153 |
+
python scripts/test_model.py
|
| 154 |
+
# Pilih opsi 1 untuk interactive chat
|
| 155 |
+
```
|
| 156 |
+
|
| 157 |
+
### Batch Testing
|
| 158 |
+
```bash
|
| 159 |
+
python scripts/test_model.py
|
| 160 |
+
# Pilih opsi 2 untuk batch testing
|
| 161 |
+
```
|
| 162 |
+
|
| 163 |
+
### Custom Prompt
|
| 164 |
+
```bash
|
| 165 |
+
python scripts/test_model.py
|
| 166 |
+
# Pilih opsi 3 untuk custom prompt
|
| 167 |
+
```
|
| 168 |
+
|
| 169 |
+
## 📈 Monitoring
|
| 170 |
+
|
| 171 |
+
### Training Logs
|
| 172 |
+
- Logs tersimpan di folder `logs/`
|
| 173 |
+
- Monitor GPU usage dengan `nvidia-smi`
|
| 174 |
+
- Check training progress di console
|
| 175 |
+
|
| 176 |
+
### Model Performance
|
| 177 |
+
- Loss metrics selama training
|
| 178 |
+
- Model checkpoints tersimpan setiap `save_steps`
|
| 179 |
+
- Evaluation metrics setiap `eval_steps`
|
| 180 |
+
|
| 181 |
+
## 🔍 Troubleshooting
|
| 182 |
+
|
| 183 |
+
### Common Issues
|
| 184 |
+
|
| 185 |
+
1. **CUDA Out of Memory**
|
| 186 |
+
- Kurangi `batch_size`
|
| 187 |
+
- Kurangi `max_length`
|
| 188 |
+
- Gunakan gradient accumulation
|
| 189 |
+
|
| 190 |
+
2. **Model Download Failed**
|
| 191 |
+
- Check HuggingFace token
|
| 192 |
+
- Verify internet connection
|
| 193 |
+
- Check disk space
|
| 194 |
+
|
| 195 |
+
3. **Training Slow**
|
| 196 |
+
- Increase `batch_size` jika memory cukup
|
| 197 |
+
- Optimize data loading
|
| 198 |
+
- Use mixed precision training
|
| 199 |
+
|
| 200 |
+
### Performance Tips
|
| 201 |
+
|
| 202 |
+
- Gunakan SSD untuk dataset besar
|
| 203 |
+
- Monitor GPU temperature
|
| 204 |
+
- Use appropriate learning rate scheduling
|
| 205 |
+
- Regular checkpointing untuk recovery
|
| 206 |
+
|
| 207 |
+
## 📚 Dependencies
|
| 208 |
+
|
| 209 |
+
Lihat `requirements.txt` untuk daftar lengkap dependencies:
|
| 210 |
+
|
| 211 |
+
- **Core**: torch, transformers, peft, datasets
|
| 212 |
+
- **Inference**: vllm, openai
|
| 213 |
+
- **Utils**: numpy, pandas, pyyaml
|
| 214 |
+
- **Dev**: pytest, black, flake8
|
| 215 |
+
|
| 216 |
+
## 🤝 Contributing
|
| 217 |
+
|
| 218 |
+
1. Fork repository
|
| 219 |
+
2. Create feature branch
|
| 220 |
+
3. Commit changes
|
| 221 |
+
4. Push to branch
|
| 222 |
+
5. Create Pull Request
|
| 223 |
+
|
| 224 |
+
## 📄 License
|
| 225 |
+
|
| 226 |
+
MIT License - lihat LICENSE file untuk detail.
|
| 227 |
+
|
| 228 |
+
## 🆘 Support
|
| 229 |
+
|
| 230 |
+
Jika ada masalah atau pertanyaan:
|
| 231 |
+
|
| 232 |
+
1. Check troubleshooting section
|
| 233 |
+
2. Review logs di folder `logs/`
|
| 234 |
+
3. Open issue di repository
|
| 235 |
+
4. Contact maintainer
|
| 236 |
+
|
| 237 |
+
---
|
| 238 |
+
|
| 239 |
+
**Happy Fine-tuning! 🚀**
|
| 240 |
+
|
| 241 |
+
|
| 242 |
+
|
| 243 |
+
|
api_server.py
ADDED
|
@@ -0,0 +1,323 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
Textilindo AI API Server
|
| 4 |
+
Clean API-only implementation
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
from flask import Flask, request, jsonify
|
| 8 |
+
from flask_cors import CORS
|
| 9 |
+
import os
|
| 10 |
+
import json
|
| 11 |
+
import requests
|
| 12 |
+
from difflib import SequenceMatcher
|
| 13 |
+
import logging
|
| 14 |
+
|
| 15 |
+
# Setup logging
|
| 16 |
+
logging.basicConfig(level=logging.INFO)
|
| 17 |
+
logger = logging.getLogger(__name__)
|
| 18 |
+
|
| 19 |
+
app = Flask(__name__)
|
| 20 |
+
CORS(app) # Enable CORS for all routes
|
| 21 |
+
|
| 22 |
+
class TextilindoAI:
|
| 23 |
+
def __init__(self, api_key):
|
| 24 |
+
self.api_key = api_key
|
| 25 |
+
self.base_url = "https://api.novita.ai/openai"
|
| 26 |
+
self.headers = {
|
| 27 |
+
"Authorization": f"Bearer {api_key}",
|
| 28 |
+
"Content-Type": "application/json"
|
| 29 |
+
}
|
| 30 |
+
self.model = "qwen/qwen3-235b-a22b-instruct-2507"
|
| 31 |
+
self.dataset = self.load_dataset()
|
| 32 |
+
|
| 33 |
+
def load_dataset(self):
|
| 34 |
+
"""Load the training dataset"""
|
| 35 |
+
dataset = []
|
| 36 |
+
dataset_path = "data/textilindo_training_data.jsonl"
|
| 37 |
+
|
| 38 |
+
if os.path.exists(dataset_path):
|
| 39 |
+
try:
|
| 40 |
+
with open(dataset_path, 'r', encoding='utf-8') as f:
|
| 41 |
+
for line in f:
|
| 42 |
+
line = line.strip()
|
| 43 |
+
if line:
|
| 44 |
+
data = json.loads(line)
|
| 45 |
+
dataset.append(data)
|
| 46 |
+
logger.info(f"Loaded {len(dataset)} examples from dataset")
|
| 47 |
+
except Exception as e:
|
| 48 |
+
logger.error(f"Error loading dataset: {e}")
|
| 49 |
+
|
| 50 |
+
return dataset
|
| 51 |
+
|
| 52 |
+
def find_relevant_context(self, user_query, top_k=3):
|
| 53 |
+
"""Find most relevant examples from dataset"""
|
| 54 |
+
if not self.dataset:
|
| 55 |
+
return []
|
| 56 |
+
|
| 57 |
+
scores = []
|
| 58 |
+
for i, example in enumerate(self.dataset):
|
| 59 |
+
instruction = example.get('instruction', '').lower()
|
| 60 |
+
output = example.get('output', '').lower()
|
| 61 |
+
query = user_query.lower()
|
| 62 |
+
|
| 63 |
+
instruction_score = SequenceMatcher(None, query, instruction).ratio()
|
| 64 |
+
output_score = SequenceMatcher(None, query, output).ratio()
|
| 65 |
+
combined_score = (instruction_score * 0.7) + (output_score * 0.3)
|
| 66 |
+
scores.append((combined_score, i))
|
| 67 |
+
|
| 68 |
+
scores.sort(reverse=True)
|
| 69 |
+
relevant_examples = []
|
| 70 |
+
|
| 71 |
+
for score, idx in scores[:top_k]:
|
| 72 |
+
if score > 0.1:
|
| 73 |
+
relevant_examples.append(self.dataset[idx])
|
| 74 |
+
|
| 75 |
+
return relevant_examples
|
| 76 |
+
|
| 77 |
+
def create_context_prompt(self, user_query, relevant_examples):
|
| 78 |
+
"""Create a prompt with relevant context"""
|
| 79 |
+
if not relevant_examples:
|
| 80 |
+
return user_query
|
| 81 |
+
|
| 82 |
+
context_parts = []
|
| 83 |
+
context_parts.append("Berikut adalah beberapa contoh pertanyaan dan jawaban tentang Textilindo:")
|
| 84 |
+
context_parts.append("")
|
| 85 |
+
|
| 86 |
+
for i, example in enumerate(relevant_examples, 1):
|
| 87 |
+
instruction = example.get('instruction', '')
|
| 88 |
+
output = example.get('output', '')
|
| 89 |
+
context_parts.append(f"Contoh {i}:")
|
| 90 |
+
context_parts.append(f"Pertanyaan: {instruction}")
|
| 91 |
+
context_parts.append(f"Jawaban: {output}")
|
| 92 |
+
context_parts.append("")
|
| 93 |
+
|
| 94 |
+
context_parts.append("Berdasarkan contoh di atas, jawab pertanyaan berikut:")
|
| 95 |
+
context_parts.append(f"Pertanyaan: {user_query}")
|
| 96 |
+
context_parts.append("Jawaban:")
|
| 97 |
+
|
| 98 |
+
return "\n".join(context_parts)
|
| 99 |
+
|
| 100 |
+
def chat(self, message, max_tokens=300, temperature=0.7):
|
| 101 |
+
"""Send message to Novita AI with RAG context"""
|
| 102 |
+
relevant_examples = self.find_relevant_context(message, 3)
|
| 103 |
+
|
| 104 |
+
if relevant_examples:
|
| 105 |
+
enhanced_prompt = self.create_context_prompt(message, relevant_examples)
|
| 106 |
+
context_used = True
|
| 107 |
+
else:
|
| 108 |
+
enhanced_prompt = message
|
| 109 |
+
context_used = False
|
| 110 |
+
|
| 111 |
+
payload = {
|
| 112 |
+
"model": self.model,
|
| 113 |
+
"messages": [{"role": "user", "content": enhanced_prompt}],
|
| 114 |
+
"max_tokens": max_tokens,
|
| 115 |
+
"temperature": temperature,
|
| 116 |
+
"top_p": 0.9
|
| 117 |
+
}
|
| 118 |
+
|
| 119 |
+
try:
|
| 120 |
+
response = requests.post(
|
| 121 |
+
f"{self.base_url}/chat/completions",
|
| 122 |
+
headers=self.headers,
|
| 123 |
+
json=payload,
|
| 124 |
+
timeout=30
|
| 125 |
+
)
|
| 126 |
+
|
| 127 |
+
if response.status_code == 200:
|
| 128 |
+
result = response.json()
|
| 129 |
+
ai_response = result.get('choices', [{}])[0].get('message', {}).get('content', '')
|
| 130 |
+
|
| 131 |
+
return {
|
| 132 |
+
"success": True,
|
| 133 |
+
"response": ai_response,
|
| 134 |
+
"context_used": context_used,
|
| 135 |
+
"relevant_examples_count": len(relevant_examples),
|
| 136 |
+
"model": self.model,
|
| 137 |
+
"tokens_used": result.get('usage', {}).get('total_tokens', 0)
|
| 138 |
+
}
|
| 139 |
+
else:
|
| 140 |
+
return {
|
| 141 |
+
"success": False,
|
| 142 |
+
"error": f"API Error: {response.status_code}",
|
| 143 |
+
"details": response.text
|
| 144 |
+
}
|
| 145 |
+
|
| 146 |
+
except Exception as e:
|
| 147 |
+
return {
|
| 148 |
+
"success": False,
|
| 149 |
+
"error": f"Request Error: {str(e)}"
|
| 150 |
+
}
|
| 151 |
+
|
| 152 |
+
# Initialize AI
|
| 153 |
+
api_key = os.getenv('NOVITA_API_KEY')
|
| 154 |
+
if not api_key:
|
| 155 |
+
logger.error("NOVITA_API_KEY not found in environment variables")
|
| 156 |
+
exit(1)
|
| 157 |
+
|
| 158 |
+
ai = TextilindoAI(api_key)
|
| 159 |
+
|
| 160 |
+
@app.route('/health', methods=['GET'])
|
| 161 |
+
def health_check():
|
| 162 |
+
"""Health check endpoint"""
|
| 163 |
+
return jsonify({
|
| 164 |
+
"status": "healthy",
|
| 165 |
+
"service": "Textilindo AI API",
|
| 166 |
+
"model": ai.model,
|
| 167 |
+
"dataset_loaded": len(ai.dataset) > 0,
|
| 168 |
+
"dataset_size": len(ai.dataset)
|
| 169 |
+
})
|
| 170 |
+
|
| 171 |
+
@app.route('/chat', methods=['POST'])
|
| 172 |
+
def chat():
|
| 173 |
+
"""Main chat endpoint"""
|
| 174 |
+
try:
|
| 175 |
+
data = request.get_json()
|
| 176 |
+
|
| 177 |
+
if not data:
|
| 178 |
+
return jsonify({
|
| 179 |
+
"success": False,
|
| 180 |
+
"error": "No JSON data provided"
|
| 181 |
+
}), 400
|
| 182 |
+
|
| 183 |
+
message = data.get('message', '').strip()
|
| 184 |
+
if not message:
|
| 185 |
+
return jsonify({
|
| 186 |
+
"success": False,
|
| 187 |
+
"error": "Message is required"
|
| 188 |
+
}), 400
|
| 189 |
+
|
| 190 |
+
# Optional parameters
|
| 191 |
+
max_tokens = data.get('max_tokens', 300)
|
| 192 |
+
temperature = data.get('temperature', 0.7)
|
| 193 |
+
|
| 194 |
+
# Validate parameters
|
| 195 |
+
if not isinstance(max_tokens, int) or max_tokens < 1 or max_tokens > 1000:
|
| 196 |
+
return jsonify({
|
| 197 |
+
"success": False,
|
| 198 |
+
"error": "max_tokens must be between 1 and 1000"
|
| 199 |
+
}), 400
|
| 200 |
+
|
| 201 |
+
if not isinstance(temperature, (int, float)) or temperature < 0 or temperature > 2:
|
| 202 |
+
return jsonify({
|
| 203 |
+
"success": False,
|
| 204 |
+
"error": "temperature must be between 0 and 2"
|
| 205 |
+
}), 400
|
| 206 |
+
|
| 207 |
+
# Process chat
|
| 208 |
+
result = ai.chat(message, max_tokens, temperature)
|
| 209 |
+
|
| 210 |
+
if result["success"]:
|
| 211 |
+
return jsonify(result)
|
| 212 |
+
else:
|
| 213 |
+
return jsonify(result), 500
|
| 214 |
+
|
| 215 |
+
except Exception as e:
|
| 216 |
+
logger.error(f"Error in chat endpoint: {e}")
|
| 217 |
+
return jsonify({
|
| 218 |
+
"success": False,
|
| 219 |
+
"error": f"Internal server error: {str(e)}"
|
| 220 |
+
}), 500
|
| 221 |
+
|
| 222 |
+
@app.route('/stats', methods=['GET'])
|
| 223 |
+
def get_stats():
|
| 224 |
+
"""Get dataset and system statistics"""
|
| 225 |
+
try:
|
| 226 |
+
topics = {}
|
| 227 |
+
for example in ai.dataset:
|
| 228 |
+
metadata = example.get('metadata', {})
|
| 229 |
+
topic = metadata.get('topic', 'unknown')
|
| 230 |
+
topics[topic] = topics.get(topic, 0) + 1
|
| 231 |
+
|
| 232 |
+
return jsonify({
|
| 233 |
+
"success": True,
|
| 234 |
+
"dataset": {
|
| 235 |
+
"total_examples": len(ai.dataset),
|
| 236 |
+
"topics": topics,
|
| 237 |
+
"topics_count": len(topics)
|
| 238 |
+
},
|
| 239 |
+
"model": {
|
| 240 |
+
"name": ai.model,
|
| 241 |
+
"provider": "Novita AI"
|
| 242 |
+
},
|
| 243 |
+
"system": {
|
| 244 |
+
"api_version": "1.0.0",
|
| 245 |
+
"status": "operational"
|
| 246 |
+
}
|
| 247 |
+
})
|
| 248 |
+
|
| 249 |
+
except Exception as e:
|
| 250 |
+
logger.error(f"Error in stats endpoint: {e}")
|
| 251 |
+
return jsonify({
|
| 252 |
+
"success": False,
|
| 253 |
+
"error": f"Internal server error: {str(e)}"
|
| 254 |
+
}), 500
|
| 255 |
+
|
| 256 |
+
@app.route('/examples', methods=['GET'])
|
| 257 |
+
def get_examples():
|
| 258 |
+
"""Get sample questions from dataset"""
|
| 259 |
+
try:
|
| 260 |
+
limit = request.args.get('limit', 10, type=int)
|
| 261 |
+
limit = min(limit, 50) # Max 50 examples
|
| 262 |
+
|
| 263 |
+
examples = []
|
| 264 |
+
for example in ai.dataset[:limit]:
|
| 265 |
+
examples.append({
|
| 266 |
+
"instruction": example.get('instruction', ''),
|
| 267 |
+
"output": example.get('output', ''),
|
| 268 |
+
"topic": example.get('metadata', {}).get('topic', 'unknown')
|
| 269 |
+
})
|
| 270 |
+
|
| 271 |
+
return jsonify({
|
| 272 |
+
"success": True,
|
| 273 |
+
"examples": examples,
|
| 274 |
+
"total_returned": len(examples),
|
| 275 |
+
"total_available": len(ai.dataset)
|
| 276 |
+
})
|
| 277 |
+
|
| 278 |
+
except Exception as e:
|
| 279 |
+
logger.error(f"Error in examples endpoint: {e}")
|
| 280 |
+
return jsonify({
|
| 281 |
+
"success": False,
|
| 282 |
+
"error": f"Internal server error: {str(e)}"
|
| 283 |
+
}), 500
|
| 284 |
+
|
| 285 |
+
@app.route('/', methods=['GET'])
|
| 286 |
+
def root():
|
| 287 |
+
"""API root endpoint with documentation"""
|
| 288 |
+
return jsonify({
|
| 289 |
+
"service": "Textilindo AI API",
|
| 290 |
+
"version": "1.0.0",
|
| 291 |
+
"description": "AI-powered customer service for Textilindo",
|
| 292 |
+
"endpoints": {
|
| 293 |
+
"GET /": "API documentation (this endpoint)",
|
| 294 |
+
"GET /health": "Health check",
|
| 295 |
+
"POST /chat": "Chat with AI",
|
| 296 |
+
"GET /stats": "Dataset and system statistics",
|
| 297 |
+
"GET /examples": "Sample questions from dataset"
|
| 298 |
+
},
|
| 299 |
+
"usage": {
|
| 300 |
+
"chat": {
|
| 301 |
+
"method": "POST",
|
| 302 |
+
"url": "/chat",
|
| 303 |
+
"body": {
|
| 304 |
+
"message": "string (required)",
|
| 305 |
+
"max_tokens": "integer (optional, default: 300)",
|
| 306 |
+
"temperature": "float (optional, default: 0.7)"
|
| 307 |
+
}
|
| 308 |
+
}
|
| 309 |
+
},
|
| 310 |
+
"model": ai.model,
|
| 311 |
+
"dataset_size": len(ai.dataset)
|
| 312 |
+
})
|
| 313 |
+
|
| 314 |
+
if __name__ == '__main__':
|
| 315 |
+
logger.info("Starting Textilindo AI API Server...")
|
| 316 |
+
logger.info(f"Model: {ai.model}")
|
| 317 |
+
logger.info(f"Dataset loaded: {len(ai.dataset)} examples")
|
| 318 |
+
|
| 319 |
+
app.run(
|
| 320 |
+
debug=False, # Set to False for production
|
| 321 |
+
host='0.0.0.0',
|
| 322 |
+
port=5001
|
| 323 |
+
)
|
configs/training_config.yaml
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
dataset_path: data/textilindo_training_data.jsonl
|
| 2 |
+
lora_config:
|
| 3 |
+
lora_alpha: 32
|
| 4 |
+
lora_dropout: 0.1
|
| 5 |
+
r: 16
|
| 6 |
+
target_modules:
|
| 7 |
+
- q_proj
|
| 8 |
+
- v_proj
|
| 9 |
+
- k_proj
|
| 10 |
+
- o_proj
|
| 11 |
+
- gate_proj
|
| 12 |
+
- up_proj
|
| 13 |
+
- down_proj
|
| 14 |
+
max_length: 2048
|
| 15 |
+
model_name: meta-llama/llama-3.2-1b-instruct
|
| 16 |
+
model_path: ./models/llama-3.2-1b-instruct
|
| 17 |
+
repetition_penalty: 1.1
|
| 18 |
+
temperature: 0.7
|
| 19 |
+
top_k: 40
|
| 20 |
+
top_p: 0.9
|
| 21 |
+
training_config:
|
| 22 |
+
batch_size: 4
|
| 23 |
+
eval_steps: 500
|
| 24 |
+
gradient_accumulation_steps: 4
|
| 25 |
+
learning_rate: 0.0002
|
| 26 |
+
num_epochs: 3
|
| 27 |
+
save_steps: 500
|
| 28 |
+
warmup_steps: 100
|
convert_dataset.py
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
Convert dataset from instruction/input/output format to text format for fine-tuning
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
import json
|
| 7 |
+
import os
|
| 8 |
+
from pathlib import Path
|
| 9 |
+
|
| 10 |
+
def convert_dataset(input_file, output_file):
|
| 11 |
+
"""Convert dataset from instruction format to text format"""
|
| 12 |
+
|
| 13 |
+
print(f"🔄 Converting dataset from {input_file} to {output_file}")
|
| 14 |
+
|
| 15 |
+
# Read input dataset
|
| 16 |
+
with open(input_file, 'r', encoding='utf-8') as f:
|
| 17 |
+
lines = f.readlines()
|
| 18 |
+
|
| 19 |
+
converted_data = []
|
| 20 |
+
|
| 21 |
+
for i, line in enumerate(lines, 1):
|
| 22 |
+
try:
|
| 23 |
+
data = json.loads(line.strip())
|
| 24 |
+
|
| 25 |
+
# Extract fields
|
| 26 |
+
instruction = data.get('instruction', '')
|
| 27 |
+
input_text = data.get('input', '')
|
| 28 |
+
output = data.get('output', '')
|
| 29 |
+
metadata = data.get('metadata', {})
|
| 30 |
+
|
| 31 |
+
# Create training text in instruction-following format
|
| 32 |
+
if input_text.strip():
|
| 33 |
+
# If there's input, use instruction + input format
|
| 34 |
+
training_text = f"### Instruction:\n{instruction}\n\n### Input:\n{input_text}\n\n### Response:\n{output}"
|
| 35 |
+
else:
|
| 36 |
+
# If no input, use simple instruction format
|
| 37 |
+
training_text = f"### Instruction:\n{instruction}\n\n### Response:\n{output}"
|
| 38 |
+
|
| 39 |
+
# Add to converted data
|
| 40 |
+
converted_data.append({
|
| 41 |
+
"text": training_text,
|
| 42 |
+
"instruction": instruction,
|
| 43 |
+
"input": input_text,
|
| 44 |
+
"output": output,
|
| 45 |
+
"metadata": metadata
|
| 46 |
+
})
|
| 47 |
+
|
| 48 |
+
except json.JSONDecodeError as e:
|
| 49 |
+
print(f"⚠️ Warning: Invalid JSON at line {i}: {e}")
|
| 50 |
+
continue
|
| 51 |
+
|
| 52 |
+
# Save converted dataset
|
| 53 |
+
with open(output_file, 'w', encoding='utf-8') as f:
|
| 54 |
+
for item in converted_data:
|
| 55 |
+
f.write(json.dumps(item, ensure_ascii=False) + '\n')
|
| 56 |
+
|
| 57 |
+
print(f"✅ Converted {len(converted_data)} samples")
|
| 58 |
+
print(f"📁 Saved to: {output_file}")
|
| 59 |
+
|
| 60 |
+
return output_file
|
| 61 |
+
|
| 62 |
+
def create_training_config(model_name, dataset_path):
|
| 63 |
+
"""Create training configuration file"""
|
| 64 |
+
|
| 65 |
+
config = {
|
| 66 |
+
"model_name": model_name,
|
| 67 |
+
"model_path": f"./models/{model_name.split('/')[-1]}",
|
| 68 |
+
"dataset_path": dataset_path,
|
| 69 |
+
"max_length": 2048,
|
| 70 |
+
"temperature": 0.7,
|
| 71 |
+
"top_p": 0.9,
|
| 72 |
+
"top_k": 40,
|
| 73 |
+
"repetition_penalty": 1.1,
|
| 74 |
+
|
| 75 |
+
"lora_config": {
|
| 76 |
+
"r": 16,
|
| 77 |
+
"lora_alpha": 32,
|
| 78 |
+
"lora_dropout": 0.1,
|
| 79 |
+
"target_modules": ["q_proj", "v_proj", "k_proj", "o_proj", "gate_proj", "up_proj", "down_proj"]
|
| 80 |
+
},
|
| 81 |
+
|
| 82 |
+
"training_config": {
|
| 83 |
+
"learning_rate": 2e-4,
|
| 84 |
+
"batch_size": 4,
|
| 85 |
+
"gradient_accumulation_steps": 4,
|
| 86 |
+
"num_epochs": 3,
|
| 87 |
+
"warmup_steps": 100,
|
| 88 |
+
"save_steps": 500,
|
| 89 |
+
"eval_steps": 500
|
| 90 |
+
}
|
| 91 |
+
}
|
| 92 |
+
|
| 93 |
+
config_path = "configs/training_config.yaml"
|
| 94 |
+
os.makedirs("configs", exist_ok=True)
|
| 95 |
+
|
| 96 |
+
import yaml
|
| 97 |
+
with open(config_path, 'w', encoding='utf-8') as f:
|
| 98 |
+
yaml.dump(config, f, default_flow_style=False, allow_unicode=True)
|
| 99 |
+
|
| 100 |
+
print(f"✅ Created training config: {config_path}")
|
| 101 |
+
return config_path
|
| 102 |
+
|
| 103 |
+
def main():
|
| 104 |
+
print("🚀 Dataset Converter for Textilindo AI")
|
| 105 |
+
print("=" * 50)
|
| 106 |
+
|
| 107 |
+
# Input and output files
|
| 108 |
+
input_file = "data/lora_dataset_20250829_113330.jsonl"
|
| 109 |
+
output_file = "data/textilindo_training_data.jsonl"
|
| 110 |
+
|
| 111 |
+
# Check if input file exists
|
| 112 |
+
if not os.path.exists(input_file):
|
| 113 |
+
print(f"❌ Input file not found: {input_file}")
|
| 114 |
+
return
|
| 115 |
+
|
| 116 |
+
# Convert dataset
|
| 117 |
+
converted_file = convert_dataset(input_file, output_file)
|
| 118 |
+
|
| 119 |
+
# Create training config
|
| 120 |
+
model_name = "meta-llama/llama-3.2-1b-instruct" # Lightweight model for testing
|
| 121 |
+
config_path = create_training_config(model_name, converted_file)
|
| 122 |
+
|
| 123 |
+
print("\n🎉 Dataset conversion complete!")
|
| 124 |
+
print("\n📋 Next steps:")
|
| 125 |
+
print("1. Run fine-tuning: python scripts/finetune_lora.py")
|
| 126 |
+
print("2. Test the model: python scripts/test_model.py")
|
| 127 |
+
print("3. Deploy to Novita AI (manual process for now)")
|
| 128 |
+
|
| 129 |
+
# Show sample of converted data
|
| 130 |
+
print(f"\n📄 Sample converted data:")
|
| 131 |
+
with open(output_file, 'r', encoding='utf-8') as f:
|
| 132 |
+
sample = json.loads(f.readline())
|
| 133 |
+
print(f"Text length: {len(sample['text'])} characters")
|
| 134 |
+
print(f"Instruction: {sample['instruction'][:100]}...")
|
| 135 |
+
print(f"Output: {sample['output'][:100]}...")
|
| 136 |
+
|
| 137 |
+
if __name__ == "__main__":
|
| 138 |
+
main()
|
deploy_to_novita.py
ADDED
|
@@ -0,0 +1,254 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
Deploy fine-tuned model to Novita AI serverless GPU
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
import os
|
| 7 |
+
import json
|
| 8 |
+
import requests
|
| 9 |
+
import time
|
| 10 |
+
from pathlib import Path
|
| 11 |
+
|
| 12 |
+
class NovitaAIDeployer:
|
| 13 |
+
def __init__(self, api_key):
|
| 14 |
+
self.api_key = api_key
|
| 15 |
+
self.base_url = "https://api.novita.ai/openai"
|
| 16 |
+
self.headers = {
|
| 17 |
+
"Authorization": f"Bearer {api_key}",
|
| 18 |
+
"Content-Type": "application/json"
|
| 19 |
+
}
|
| 20 |
+
|
| 21 |
+
def test_connection(self):
|
| 22 |
+
"""Test connection to Novita AI"""
|
| 23 |
+
try:
|
| 24 |
+
response = requests.get(f"{self.base_url}/models", headers=self.headers, timeout=10)
|
| 25 |
+
return response.status_code == 200
|
| 26 |
+
except Exception as e:
|
| 27 |
+
print(f"❌ Connection error: {e}")
|
| 28 |
+
return False
|
| 29 |
+
|
| 30 |
+
def get_available_models(self):
|
| 31 |
+
"""Get list of available models"""
|
| 32 |
+
try:
|
| 33 |
+
response = requests.get(f"{self.base_url}/models", headers=self.headers, timeout=10)
|
| 34 |
+
if response.status_code == 200:
|
| 35 |
+
return response.json().get('data', [])
|
| 36 |
+
return []
|
| 37 |
+
except Exception as e:
|
| 38 |
+
print(f"❌ Error getting models: {e}")
|
| 39 |
+
return []
|
| 40 |
+
|
| 41 |
+
def create_deployment(self, model_name, deployment_name=None):
|
| 42 |
+
"""Create a deployment for the model"""
|
| 43 |
+
if not deployment_name:
|
| 44 |
+
deployment_name = f"textilindo-{model_name.split('/')[-1]}"
|
| 45 |
+
|
| 46 |
+
# Note: This is a placeholder for the actual deployment API
|
| 47 |
+
# Novita AI might not have a public deployment API yet
|
| 48 |
+
print(f"🔧 Creating deployment: {deployment_name}")
|
| 49 |
+
print(f"📋 Model: {model_name}")
|
| 50 |
+
|
| 51 |
+
# For now, we'll create a configuration file for manual deployment
|
| 52 |
+
deployment_config = {
|
| 53 |
+
"deployment_name": deployment_name,
|
| 54 |
+
"model_name": model_name,
|
| 55 |
+
"base_url": self.base_url,
|
| 56 |
+
"api_key": self.api_key[:10] + "..." + self.api_key[-10:],
|
| 57 |
+
"created_at": time.strftime("%Y-%m-%d %H:%M:%S"),
|
| 58 |
+
"status": "ready_for_deployment"
|
| 59 |
+
}
|
| 60 |
+
|
| 61 |
+
config_path = f"configs/{deployment_name}_deployment.json"
|
| 62 |
+
os.makedirs("configs", exist_ok=True)
|
| 63 |
+
|
| 64 |
+
with open(config_path, 'w', encoding='utf-8') as f:
|
| 65 |
+
json.dump(deployment_config, f, indent=2, ensure_ascii=False)
|
| 66 |
+
|
| 67 |
+
print(f"✅ Deployment config saved: {config_path}")
|
| 68 |
+
return config_path
|
| 69 |
+
|
| 70 |
+
def test_model_inference(self, model_name, test_prompt="Halo, apa kabar?"):
|
| 71 |
+
"""Test model inference"""
|
| 72 |
+
print(f"🧪 Testing inference with model: {model_name}")
|
| 73 |
+
|
| 74 |
+
payload = {
|
| 75 |
+
"model": model_name,
|
| 76 |
+
"messages": [
|
| 77 |
+
{"role": "user", "content": test_prompt}
|
| 78 |
+
],
|
| 79 |
+
"max_tokens": 100,
|
| 80 |
+
"temperature": 0.7
|
| 81 |
+
}
|
| 82 |
+
|
| 83 |
+
try:
|
| 84 |
+
response = requests.post(
|
| 85 |
+
f"{self.base_url}/chat/completions",
|
| 86 |
+
headers=self.headers,
|
| 87 |
+
json=payload,
|
| 88 |
+
timeout=30
|
| 89 |
+
)
|
| 90 |
+
|
| 91 |
+
if response.status_code == 200:
|
| 92 |
+
result = response.json()
|
| 93 |
+
assistant_message = result.get('choices', [{}])[0].get('message', {}).get('content', '')
|
| 94 |
+
print(f"✅ Inference successful!")
|
| 95 |
+
print(f"📝 Response: {assistant_message}")
|
| 96 |
+
return True
|
| 97 |
+
else:
|
| 98 |
+
print(f"❌ Inference failed: {response.status_code} - {response.text}")
|
| 99 |
+
return False
|
| 100 |
+
|
| 101 |
+
except Exception as e:
|
| 102 |
+
print(f"❌ Inference error: {e}")
|
| 103 |
+
return False
|
| 104 |
+
|
| 105 |
+
def create_deployment_guide():
|
| 106 |
+
"""Create a deployment guide for Novita AI"""
|
| 107 |
+
guide_content = """
|
| 108 |
+
# Novita AI Deployment Guide
|
| 109 |
+
|
| 110 |
+
## Current Status
|
| 111 |
+
Your fine-tuned model is ready for deployment to Novita AI serverless GPU.
|
| 112 |
+
|
| 113 |
+
## Manual Deployment Steps
|
| 114 |
+
|
| 115 |
+
### 1. Prepare Your Model
|
| 116 |
+
- Ensure your fine-tuned model is saved in the `models/` directory
|
| 117 |
+
- Verify the model weights and configuration files are complete
|
| 118 |
+
|
| 119 |
+
### 2. Upload to Novita AI
|
| 120 |
+
1. Log in to your Novita AI dashboard: https://novita.ai/
|
| 121 |
+
2. Navigate to "Custom Models" or "Model Library"
|
| 122 |
+
3. Click "Upload Model" or "Deploy Custom Model"
|
| 123 |
+
4. Upload your model files (weights, config, tokenizer)
|
| 124 |
+
5. Set the model name (e.g., "textilindo-llama-3.2-1b")
|
| 125 |
+
6. Configure serverless GPU settings
|
| 126 |
+
|
| 127 |
+
### 3. Configure API Access
|
| 128 |
+
1. Get your deployment API endpoint
|
| 129 |
+
2. Update your application to use the new endpoint
|
| 130 |
+
3. Test the deployment with sample queries
|
| 131 |
+
|
| 132 |
+
### 4. Monitor Usage
|
| 133 |
+
- Track API calls and costs in the Novita AI dashboard
|
| 134 |
+
- Monitor model performance and response times
|
| 135 |
+
- Set up alerts for any issues
|
| 136 |
+
|
| 137 |
+
## API Usage Example
|
| 138 |
+
|
| 139 |
+
```python
|
| 140 |
+
import requests
|
| 141 |
+
|
| 142 |
+
# Your deployment endpoint
|
| 143 |
+
endpoint = "https://api.novita.ai/openai"
|
| 144 |
+
api_key = "your_api_key"
|
| 145 |
+
|
| 146 |
+
headers = {
|
| 147 |
+
"Authorization": f"Bearer {api_key}",
|
| 148 |
+
"Content-Type": "application/json"
|
| 149 |
+
}
|
| 150 |
+
|
| 151 |
+
payload = {
|
| 152 |
+
"model": "your-deployed-model-name",
|
| 153 |
+
"messages": [
|
| 154 |
+
{"role": "user", "content": "dimana lokasi textilindo?"}
|
| 155 |
+
],
|
| 156 |
+
"max_tokens": 200,
|
| 157 |
+
"temperature": 0.7
|
| 158 |
+
}
|
| 159 |
+
|
| 160 |
+
response = requests.post(f"{endpoint}/chat/completions", headers=headers, json=payload)
|
| 161 |
+
result = response.json()
|
| 162 |
+
print(result['choices'][0]['message']['content'])
|
| 163 |
+
```
|
| 164 |
+
|
| 165 |
+
## Next Steps
|
| 166 |
+
1. Contact Novita AI support for custom model deployment
|
| 167 |
+
2. Consider using their Model API for easier integration
|
| 168 |
+
3. Set up monitoring and logging for production use
|
| 169 |
+
"""
|
| 170 |
+
|
| 171 |
+
guide_path = "DEPLOYMENT_GUIDE.md"
|
| 172 |
+
with open(guide_path, 'w', encoding='utf-8') as f:
|
| 173 |
+
f.write(guide_content)
|
| 174 |
+
|
| 175 |
+
print(f"✅ Deployment guide created: {guide_path}")
|
| 176 |
+
|
| 177 |
+
def main():
|
| 178 |
+
print("🚀 Novita AI Deployment Setup")
|
| 179 |
+
print("=" * 50)
|
| 180 |
+
|
| 181 |
+
# Check API key
|
| 182 |
+
api_key = os.getenv('NOVITA_API_KEY')
|
| 183 |
+
if not api_key:
|
| 184 |
+
print("❌ NOVITA_API_KEY not found")
|
| 185 |
+
api_key = input("Enter your Novita AI API key: ").strip()
|
| 186 |
+
if not api_key:
|
| 187 |
+
print("❌ API key required")
|
| 188 |
+
return
|
| 189 |
+
os.environ['NOVITA_API_KEY'] = api_key
|
| 190 |
+
|
| 191 |
+
# Initialize deployer
|
| 192 |
+
deployer = NovitaAIDeployer(api_key)
|
| 193 |
+
|
| 194 |
+
# Test connection
|
| 195 |
+
print("🔍 Testing connection...")
|
| 196 |
+
if not deployer.test_connection():
|
| 197 |
+
print("❌ Could not connect to Novita AI")
|
| 198 |
+
return
|
| 199 |
+
|
| 200 |
+
print("✅ Connected to Novita AI!")
|
| 201 |
+
|
| 202 |
+
# Get available models
|
| 203 |
+
models = deployer.get_available_models()
|
| 204 |
+
print(f"📋 Found {len(models)} available models")
|
| 205 |
+
|
| 206 |
+
# Select model for deployment
|
| 207 |
+
print("\n🎯 Select model for deployment:")
|
| 208 |
+
lightweight_models = [
|
| 209 |
+
"meta-llama/llama-3.2-1b-instruct",
|
| 210 |
+
"meta-llama/llama-3.2-3b-instruct",
|
| 211 |
+
"qwen/qwen3-4b-fp8",
|
| 212 |
+
"qwen/qwen3-8b-fp8"
|
| 213 |
+
]
|
| 214 |
+
|
| 215 |
+
for i, model in enumerate(lightweight_models, 1):
|
| 216 |
+
print(f"{i}. {model}")
|
| 217 |
+
|
| 218 |
+
try:
|
| 219 |
+
choice = int(input("\nSelect model (1-4): ").strip())
|
| 220 |
+
if 1 <= choice <= len(lightweight_models):
|
| 221 |
+
selected_model = lightweight_models[choice - 1]
|
| 222 |
+
else:
|
| 223 |
+
print("❌ Invalid choice, using default")
|
| 224 |
+
selected_model = lightweight_models[0]
|
| 225 |
+
except ValueError:
|
| 226 |
+
print("❌ Invalid input, using default")
|
| 227 |
+
selected_model = lightweight_models[0]
|
| 228 |
+
|
| 229 |
+
print(f"✅ Selected: {selected_model}")
|
| 230 |
+
|
| 231 |
+
# Test model inference
|
| 232 |
+
print(f"\n🧪 Testing model inference...")
|
| 233 |
+
if deployer.test_model_inference(selected_model):
|
| 234 |
+
print("✅ Model inference working!")
|
| 235 |
+
else:
|
| 236 |
+
print("❌ Model inference failed")
|
| 237 |
+
return
|
| 238 |
+
|
| 239 |
+
# Create deployment config
|
| 240 |
+
print(f"\n🔧 Creating deployment configuration...")
|
| 241 |
+
config_path = deployer.create_deployment(selected_model)
|
| 242 |
+
|
| 243 |
+
# Create deployment guide
|
| 244 |
+
create_deployment_guide()
|
| 245 |
+
|
| 246 |
+
print(f"\n🎉 Deployment setup complete!")
|
| 247 |
+
print(f"\n📋 Next steps:")
|
| 248 |
+
print(f"1. Check deployment config: {config_path}")
|
| 249 |
+
print(f"2. Read deployment guide: DEPLOYMENT_GUIDE.md")
|
| 250 |
+
print(f"3. Contact Novita AI support for custom model deployment")
|
| 251 |
+
print(f"4. Monitor your usage in the Novita AI dashboard")
|
| 252 |
+
|
| 253 |
+
if __name__ == "__main__":
|
| 254 |
+
main()
|
docker-compose.yml
ADDED
|
File without changes
|
novita_chat_app.py
ADDED
|
@@ -0,0 +1,187 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
Simple Novita AI Chat Application
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
import os
|
| 7 |
+
import requests
|
| 8 |
+
import json
|
| 9 |
+
import time
|
| 10 |
+
|
| 11 |
+
class NovitaAIChat:
|
| 12 |
+
def __init__(self, api_key):
|
| 13 |
+
self.api_key = api_key
|
| 14 |
+
self.base_url = "https://api.novita.ai/openai"
|
| 15 |
+
self.headers = {
|
| 16 |
+
"Authorization": f"Bearer {api_key}",
|
| 17 |
+
"Content-Type": "application/json"
|
| 18 |
+
}
|
| 19 |
+
self.conversation_history = []
|
| 20 |
+
self.current_model = "meta-llama/llama-3.2-1b-instruct"
|
| 21 |
+
|
| 22 |
+
def get_available_models(self):
|
| 23 |
+
"""Get list of available models"""
|
| 24 |
+
try:
|
| 25 |
+
response = requests.get(f"{self.base_url}/models", headers=self.headers, timeout=10)
|
| 26 |
+
if response.status_code == 200:
|
| 27 |
+
models = response.json()
|
| 28 |
+
return models.get('data', [])
|
| 29 |
+
else:
|
| 30 |
+
print(f"❌ Error getting models: {response.status_code}")
|
| 31 |
+
return []
|
| 32 |
+
except Exception as e:
|
| 33 |
+
print(f"❌ Error: {e}")
|
| 34 |
+
return []
|
| 35 |
+
|
| 36 |
+
def chat_completion(self, message, model=None):
|
| 37 |
+
"""Send message to Novita AI and get response"""
|
| 38 |
+
if model is None:
|
| 39 |
+
model = self.current_model
|
| 40 |
+
|
| 41 |
+
# Add user message to history
|
| 42 |
+
self.conversation_history.append({"role": "user", "content": message})
|
| 43 |
+
|
| 44 |
+
# Prepare payload
|
| 45 |
+
payload = {
|
| 46 |
+
"model": model,
|
| 47 |
+
"messages": self.conversation_history,
|
| 48 |
+
"max_tokens": 500,
|
| 49 |
+
"temperature": 0.7,
|
| 50 |
+
"top_p": 0.9
|
| 51 |
+
}
|
| 52 |
+
|
| 53 |
+
try:
|
| 54 |
+
print("🤖 Thinking...", end="", flush=True)
|
| 55 |
+
response = requests.post(
|
| 56 |
+
f"{self.base_url}/chat/completions",
|
| 57 |
+
headers=self.headers,
|
| 58 |
+
json=payload,
|
| 59 |
+
timeout=60
|
| 60 |
+
)
|
| 61 |
+
|
| 62 |
+
if response.status_code == 200:
|
| 63 |
+
result = response.json()
|
| 64 |
+
assistant_message = result.get('choices', [{}])[0].get('message', {}).get('content', '')
|
| 65 |
+
|
| 66 |
+
# Add assistant response to history
|
| 67 |
+
self.conversation_history.append({"role": "assistant", "content": assistant_message})
|
| 68 |
+
|
| 69 |
+
print("\r" + " " * 20 + "\r", end="") # Clear "Thinking..." message
|
| 70 |
+
return assistant_message
|
| 71 |
+
else:
|
| 72 |
+
print(f"\r❌ Error: {response.status_code} - {response.text}")
|
| 73 |
+
return None
|
| 74 |
+
|
| 75 |
+
except Exception as e:
|
| 76 |
+
print(f"\r❌ Error: {e}")
|
| 77 |
+
return None
|
| 78 |
+
|
| 79 |
+
def change_model(self, model_id):
|
| 80 |
+
"""Change the current model"""
|
| 81 |
+
self.current_model = model_id
|
| 82 |
+
print(f"✅ Model changed to: {model_id}")
|
| 83 |
+
|
| 84 |
+
def clear_history(self):
|
| 85 |
+
"""Clear conversation history"""
|
| 86 |
+
self.conversation_history = []
|
| 87 |
+
print("✅ Conversation history cleared")
|
| 88 |
+
|
| 89 |
+
def show_models(self):
|
| 90 |
+
"""Show available models"""
|
| 91 |
+
models = self.get_available_models()
|
| 92 |
+
if models:
|
| 93 |
+
print("\n📋 Available Models:")
|
| 94 |
+
print("-" * 50)
|
| 95 |
+
for i, model in enumerate(models[:20], 1): # Show first 20 models
|
| 96 |
+
model_id = model.get('id', 'Unknown')
|
| 97 |
+
print(f"{i:2d}. {model_id}")
|
| 98 |
+
print("-" * 50)
|
| 99 |
+
print(f"Current model: {self.current_model}")
|
| 100 |
+
else:
|
| 101 |
+
print("❌ Could not fetch models")
|
| 102 |
+
|
| 103 |
+
def main():
|
| 104 |
+
print("🚀 Novita AI Chat Application")
|
| 105 |
+
print("=" * 50)
|
| 106 |
+
|
| 107 |
+
# Check API key
|
| 108 |
+
api_key = os.getenv('NOVITA_API_KEY')
|
| 109 |
+
if not api_key:
|
| 110 |
+
print("❌ NOVITA_API_KEY not found")
|
| 111 |
+
api_key = input("Enter your Novita AI API key: ").strip()
|
| 112 |
+
if not api_key:
|
| 113 |
+
print("❌ API key required")
|
| 114 |
+
return
|
| 115 |
+
os.environ['NOVITA_API_KEY'] = api_key
|
| 116 |
+
|
| 117 |
+
# Initialize chat
|
| 118 |
+
chat = NovitaAIChat(api_key)
|
| 119 |
+
|
| 120 |
+
# Test connection
|
| 121 |
+
print("🔍 Testing connection...")
|
| 122 |
+
models = chat.get_available_models()
|
| 123 |
+
if not models:
|
| 124 |
+
print("❌ Could not connect to Novita AI")
|
| 125 |
+
return
|
| 126 |
+
|
| 127 |
+
print(f"✅ Connected! Found {len(models)} models")
|
| 128 |
+
|
| 129 |
+
# Show current model
|
| 130 |
+
print(f"🤖 Current model: {chat.current_model}")
|
| 131 |
+
|
| 132 |
+
# Main chat loop
|
| 133 |
+
print("\n💬 Start chatting! Type 'help' for commands, 'quit' to exit")
|
| 134 |
+
print("-" * 50)
|
| 135 |
+
|
| 136 |
+
while True:
|
| 137 |
+
try:
|
| 138 |
+
user_input = input("\n👤 You: ").strip()
|
| 139 |
+
|
| 140 |
+
if not user_input:
|
| 141 |
+
continue
|
| 142 |
+
|
| 143 |
+
# Handle commands
|
| 144 |
+
if user_input.lower() in ['quit', 'exit', 'q']:
|
| 145 |
+
print("👋 Goodbye!")
|
| 146 |
+
break
|
| 147 |
+
elif user_input.lower() == 'help':
|
| 148 |
+
print("\n📋 Available Commands:")
|
| 149 |
+
print(" help - Show this help")
|
| 150 |
+
print(" models - Show available models")
|
| 151 |
+
print(" change <id> - Change model (e.g., change 5)")
|
| 152 |
+
print(" clear - Clear conversation history")
|
| 153 |
+
print(" quit/exit/q - Exit the application")
|
| 154 |
+
print(" <any text> - Send message to AI")
|
| 155 |
+
continue
|
| 156 |
+
elif user_input.lower() == 'models':
|
| 157 |
+
chat.show_models()
|
| 158 |
+
continue
|
| 159 |
+
elif user_input.lower() == 'clear':
|
| 160 |
+
chat.clear_history()
|
| 161 |
+
continue
|
| 162 |
+
elif user_input.lower().startswith('change '):
|
| 163 |
+
try:
|
| 164 |
+
model_num = int(user_input.split()[1])
|
| 165 |
+
models = chat.get_available_models()
|
| 166 |
+
if 1 <= model_num <= len(models):
|
| 167 |
+
new_model = models[model_num - 1].get('id')
|
| 168 |
+
chat.change_model(new_model)
|
| 169 |
+
else:
|
| 170 |
+
print(f"❌ Invalid model number. Use 1-{len(models)}")
|
| 171 |
+
except (ValueError, IndexError):
|
| 172 |
+
print("❌ Usage: change <number>")
|
| 173 |
+
continue
|
| 174 |
+
|
| 175 |
+
# Send message to AI
|
| 176 |
+
response = chat.chat_completion(user_input)
|
| 177 |
+
if response:
|
| 178 |
+
print(f"\n🤖 Assistant: {response}")
|
| 179 |
+
|
| 180 |
+
except KeyboardInterrupt:
|
| 181 |
+
print("\n👋 Goodbye!")
|
| 182 |
+
break
|
| 183 |
+
except Exception as e:
|
| 184 |
+
print(f"❌ Error: {e}")
|
| 185 |
+
|
| 186 |
+
if __name__ == "__main__":
|
| 187 |
+
main()
|
novita_rag_chat.py
ADDED
|
@@ -0,0 +1,302 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
Novita AI RAG Chat Application - Uses your dataset as context
|
| 4 |
+
No fine-tuning required!
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
import os
|
| 8 |
+
import json
|
| 9 |
+
import requests
|
| 10 |
+
import time
|
| 11 |
+
from pathlib import Path
|
| 12 |
+
from difflib import SequenceMatcher
|
| 13 |
+
|
| 14 |
+
class NovitaAIRAGChat:
|
| 15 |
+
def __init__(self, api_key, dataset_path="data/textilindo_training_data.jsonl"):
|
| 16 |
+
self.api_key = api_key
|
| 17 |
+
self.base_url = "https://api.novita.ai/openai"
|
| 18 |
+
self.headers = {
|
| 19 |
+
"Authorization": f"Bearer {api_key}",
|
| 20 |
+
"Content-Type": "application/json"
|
| 21 |
+
}
|
| 22 |
+
self.conversation_history = []
|
| 23 |
+
self.current_model = "qwen/qwen3-235b-a22b-instruct-2507" # High-quality model
|
| 24 |
+
self.dataset = self.load_dataset(dataset_path)
|
| 25 |
+
self.context_window = 5 # Number of most relevant examples to include
|
| 26 |
+
|
| 27 |
+
def load_dataset(self, dataset_path):
|
| 28 |
+
"""Load the training dataset"""
|
| 29 |
+
print(f"📚 Loading dataset from {dataset_path}...")
|
| 30 |
+
dataset = []
|
| 31 |
+
|
| 32 |
+
if not os.path.exists(dataset_path):
|
| 33 |
+
print(f"⚠️ Dataset not found: {dataset_path}")
|
| 34 |
+
return dataset
|
| 35 |
+
|
| 36 |
+
try:
|
| 37 |
+
with open(dataset_path, 'r', encoding='utf-8') as f:
|
| 38 |
+
for line in f:
|
| 39 |
+
line = line.strip()
|
| 40 |
+
if line:
|
| 41 |
+
data = json.loads(line)
|
| 42 |
+
dataset.append(data)
|
| 43 |
+
print(f"✅ Loaded {len(dataset)} examples from dataset")
|
| 44 |
+
except Exception as e:
|
| 45 |
+
print(f"❌ Error loading dataset: {e}")
|
| 46 |
+
|
| 47 |
+
return dataset
|
| 48 |
+
|
| 49 |
+
def find_relevant_context(self, user_query, top_k=5):
|
| 50 |
+
"""Find most relevant examples from dataset"""
|
| 51 |
+
if not self.dataset:
|
| 52 |
+
return []
|
| 53 |
+
|
| 54 |
+
# Simple similarity scoring
|
| 55 |
+
scores = []
|
| 56 |
+
for i, example in enumerate(self.dataset):
|
| 57 |
+
instruction = example.get('instruction', '').lower()
|
| 58 |
+
output = example.get('output', '').lower()
|
| 59 |
+
query = user_query.lower()
|
| 60 |
+
|
| 61 |
+
# Calculate similarity scores
|
| 62 |
+
instruction_score = SequenceMatcher(None, query, instruction).ratio()
|
| 63 |
+
output_score = SequenceMatcher(None, query, output).ratio()
|
| 64 |
+
|
| 65 |
+
# Combined score (weight instruction more heavily)
|
| 66 |
+
combined_score = (instruction_score * 0.7) + (output_score * 0.3)
|
| 67 |
+
scores.append((combined_score, i))
|
| 68 |
+
|
| 69 |
+
# Sort by score and get top_k
|
| 70 |
+
scores.sort(reverse=True)
|
| 71 |
+
relevant_examples = []
|
| 72 |
+
|
| 73 |
+
for score, idx in scores[:top_k]:
|
| 74 |
+
if score > 0.1: # Only include if similarity > 10%
|
| 75 |
+
relevant_examples.append(self.dataset[idx])
|
| 76 |
+
|
| 77 |
+
return relevant_examples
|
| 78 |
+
|
| 79 |
+
def create_context_prompt(self, user_query, relevant_examples):
|
| 80 |
+
"""Create a prompt with relevant context"""
|
| 81 |
+
if not relevant_examples:
|
| 82 |
+
return user_query
|
| 83 |
+
|
| 84 |
+
context_parts = []
|
| 85 |
+
context_parts.append("Berikut adalah beberapa contoh pertanyaan dan jawaban tentang Textilindo:")
|
| 86 |
+
context_parts.append("")
|
| 87 |
+
|
| 88 |
+
for i, example in enumerate(relevant_examples, 1):
|
| 89 |
+
instruction = example.get('instruction', '')
|
| 90 |
+
output = example.get('output', '')
|
| 91 |
+
context_parts.append(f"Contoh {i}:")
|
| 92 |
+
context_parts.append(f"Pertanyaan: {instruction}")
|
| 93 |
+
context_parts.append(f"Jawaban: {output}")
|
| 94 |
+
context_parts.append("")
|
| 95 |
+
|
| 96 |
+
context_parts.append("Berdasarkan contoh di atas, jawab pertanyaan berikut:")
|
| 97 |
+
context_parts.append(f"Pertanyaan: {user_query}")
|
| 98 |
+
context_parts.append("Jawaban:")
|
| 99 |
+
|
| 100 |
+
return "\n".join(context_parts)
|
| 101 |
+
|
| 102 |
+
def chat_completion(self, message, model=None):
|
| 103 |
+
"""Send message to Novita AI with RAG context"""
|
| 104 |
+
if model is None:
|
| 105 |
+
model = self.current_model
|
| 106 |
+
|
| 107 |
+
# Find relevant context
|
| 108 |
+
relevant_examples = self.find_relevant_context(message, self.context_window)
|
| 109 |
+
|
| 110 |
+
# Create context-aware prompt
|
| 111 |
+
if relevant_examples:
|
| 112 |
+
enhanced_prompt = self.create_context_prompt(message, relevant_examples)
|
| 113 |
+
print(f"🔍 Found {len(relevant_examples)} relevant examples from dataset")
|
| 114 |
+
else:
|
| 115 |
+
enhanced_prompt = message
|
| 116 |
+
print("🔍 No relevant examples found, using direct query")
|
| 117 |
+
|
| 118 |
+
# Add to conversation history
|
| 119 |
+
self.conversation_history.append({"role": "user", "content": enhanced_prompt})
|
| 120 |
+
|
| 121 |
+
# Prepare payload
|
| 122 |
+
payload = {
|
| 123 |
+
"model": model,
|
| 124 |
+
"messages": self.conversation_history,
|
| 125 |
+
"max_tokens": 500,
|
| 126 |
+
"temperature": 0.7,
|
| 127 |
+
"top_p": 0.9
|
| 128 |
+
}
|
| 129 |
+
|
| 130 |
+
try:
|
| 131 |
+
print("🤖 Thinking...", end="", flush=True)
|
| 132 |
+
response = requests.post(
|
| 133 |
+
f"{self.base_url}/chat/completions",
|
| 134 |
+
headers=self.headers,
|
| 135 |
+
json=payload,
|
| 136 |
+
timeout=60
|
| 137 |
+
)
|
| 138 |
+
|
| 139 |
+
if response.status_code == 200:
|
| 140 |
+
result = response.json()
|
| 141 |
+
assistant_message = result.get('choices', [{}])[0].get('message', {}).get('content', '')
|
| 142 |
+
|
| 143 |
+
# Add assistant response to history
|
| 144 |
+
self.conversation_history.append({"role": "assistant", "content": assistant_message})
|
| 145 |
+
|
| 146 |
+
print("\r" + " " * 20 + "\r", end="") # Clear "Thinking..." message
|
| 147 |
+
return assistant_message
|
| 148 |
+
else:
|
| 149 |
+
print(f"\r❌ Error: {response.status_code} - {response.text}")
|
| 150 |
+
return None
|
| 151 |
+
|
| 152 |
+
except Exception as e:
|
| 153 |
+
print(f"\r❌ Error: {e}")
|
| 154 |
+
return None
|
| 155 |
+
|
| 156 |
+
def change_model(self, model_id):
|
| 157 |
+
"""Change the current model"""
|
| 158 |
+
self.current_model = model_id
|
| 159 |
+
print(f"✅ Model changed to: {model_id}")
|
| 160 |
+
|
| 161 |
+
def clear_history(self):
|
| 162 |
+
"""Clear conversation history"""
|
| 163 |
+
self.conversation_history = []
|
| 164 |
+
print("✅ Conversation history cleared")
|
| 165 |
+
|
| 166 |
+
def show_models(self):
|
| 167 |
+
"""Show available models"""
|
| 168 |
+
try:
|
| 169 |
+
response = requests.get(f"{self.base_url}/models", headers=self.headers, timeout=10)
|
| 170 |
+
if response.status_code == 200:
|
| 171 |
+
models = response.json().get('data', [])
|
| 172 |
+
print("\n📋 Available Models:")
|
| 173 |
+
print("-" * 50)
|
| 174 |
+
for i, model in enumerate(models[:20], 1): # Show first 20 models
|
| 175 |
+
model_id = model.get('id', 'Unknown')
|
| 176 |
+
print(f"{i:2d}. {model_id}")
|
| 177 |
+
print("-" * 50)
|
| 178 |
+
print(f"Current model: {self.current_model}")
|
| 179 |
+
else:
|
| 180 |
+
print("❌ Could not fetch models")
|
| 181 |
+
except Exception as e:
|
| 182 |
+
print(f"❌ Error: {e}")
|
| 183 |
+
|
| 184 |
+
def show_dataset_stats(self):
|
| 185 |
+
"""Show dataset statistics"""
|
| 186 |
+
if not self.dataset:
|
| 187 |
+
print("❌ No dataset loaded")
|
| 188 |
+
return
|
| 189 |
+
|
| 190 |
+
print(f"\n📊 Dataset Statistics:")
|
| 191 |
+
print(f"Total examples: {len(self.dataset)}")
|
| 192 |
+
|
| 193 |
+
# Count by topic
|
| 194 |
+
topics = {}
|
| 195 |
+
for example in self.dataset:
|
| 196 |
+
metadata = example.get('metadata', {})
|
| 197 |
+
topic = metadata.get('topic', 'unknown')
|
| 198 |
+
topics[topic] = topics.get(topic, 0) + 1
|
| 199 |
+
|
| 200 |
+
print(f"Topics: {dict(topics)}")
|
| 201 |
+
|
| 202 |
+
# Show sample questions
|
| 203 |
+
print(f"\n📝 Sample questions:")
|
| 204 |
+
for i, example in enumerate(self.dataset[:5], 1):
|
| 205 |
+
instruction = example.get('instruction', '')
|
| 206 |
+
print(f"{i}. {instruction}")
|
| 207 |
+
|
| 208 |
+
def main():
|
| 209 |
+
print("🚀 Novita AI RAG Chat - Textilindo AI")
|
| 210 |
+
print("=" * 60)
|
| 211 |
+
print("This application uses your dataset as context with Novita AI models")
|
| 212 |
+
print("No fine-tuning required - RAG approach!")
|
| 213 |
+
print("=" * 60)
|
| 214 |
+
|
| 215 |
+
# Check API key
|
| 216 |
+
api_key = os.getenv('NOVITA_API_KEY')
|
| 217 |
+
if not api_key:
|
| 218 |
+
print("❌ NOVITA_API_KEY not found")
|
| 219 |
+
api_key = input("Enter your Novita AI API key: ").strip()
|
| 220 |
+
if not api_key:
|
| 221 |
+
print("❌ API key required")
|
| 222 |
+
return
|
| 223 |
+
os.environ['NOVITA_API_KEY'] = api_key
|
| 224 |
+
|
| 225 |
+
# Initialize RAG chat
|
| 226 |
+
chat = NovitaAIRAGChat(api_key)
|
| 227 |
+
|
| 228 |
+
# Test connection
|
| 229 |
+
print("🔍 Testing connection...")
|
| 230 |
+
try:
|
| 231 |
+
response = requests.get(f"{chat.base_url}/models", headers=chat.headers, timeout=10)
|
| 232 |
+
if response.status_code != 200:
|
| 233 |
+
print("❌ Could not connect to Novita AI")
|
| 234 |
+
return
|
| 235 |
+
except Exception as e:
|
| 236 |
+
print(f"❌ Connection error: {e}")
|
| 237 |
+
return
|
| 238 |
+
|
| 239 |
+
print("✅ Connected to Novita AI!")
|
| 240 |
+
|
| 241 |
+
# Show dataset stats
|
| 242 |
+
chat.show_dataset_stats()
|
| 243 |
+
|
| 244 |
+
# Show current model
|
| 245 |
+
print(f"\n🤖 Current model: {chat.current_model}")
|
| 246 |
+
|
| 247 |
+
# Main chat loop
|
| 248 |
+
print("\n💬 Start chatting! Type 'help' for commands, 'quit' to exit")
|
| 249 |
+
print("-" * 60)
|
| 250 |
+
|
| 251 |
+
while True:
|
| 252 |
+
try:
|
| 253 |
+
user_input = input("\n👤 You: ").strip()
|
| 254 |
+
|
| 255 |
+
if not user_input:
|
| 256 |
+
continue
|
| 257 |
+
|
| 258 |
+
# Handle commands
|
| 259 |
+
if user_input.lower() in ['quit', 'exit', 'q']:
|
| 260 |
+
print("👋 Goodbye!")
|
| 261 |
+
break
|
| 262 |
+
elif user_input.lower() == 'help':
|
| 263 |
+
print("\n📋 Available Commands:")
|
| 264 |
+
print(" help - Show this help")
|
| 265 |
+
print(" models - Show available models")
|
| 266 |
+
print(" change <id> - Change model (e.g., change 5)")
|
| 267 |
+
print(" clear - Clear conversation history")
|
| 268 |
+
print(" stats - Show dataset statistics")
|
| 269 |
+
print(" quit/exit/q - Exit the application")
|
| 270 |
+
print(" <any text> - Send message to AI (with RAG context)")
|
| 271 |
+
continue
|
| 272 |
+
elif user_input.lower() == 'models':
|
| 273 |
+
chat.show_models()
|
| 274 |
+
continue
|
| 275 |
+
elif user_input.lower() == 'clear':
|
| 276 |
+
chat.clear_history()
|
| 277 |
+
continue
|
| 278 |
+
elif user_input.lower() == 'stats':
|
| 279 |
+
chat.show_dataset_stats()
|
| 280 |
+
continue
|
| 281 |
+
elif user_input.lower().startswith('change '):
|
| 282 |
+
try:
|
| 283 |
+
model_num = int(user_input.split()[1])
|
| 284 |
+
# This would need to be implemented to get model list
|
| 285 |
+
print("⚠️ Model changing not implemented yet")
|
| 286 |
+
except (ValueError, IndexError):
|
| 287 |
+
print("❌ Usage: change <number>")
|
| 288 |
+
continue
|
| 289 |
+
|
| 290 |
+
# Send message to AI with RAG context
|
| 291 |
+
response = chat.chat_completion(user_input)
|
| 292 |
+
if response:
|
| 293 |
+
print(f"\n🤖 Assistant: {response}")
|
| 294 |
+
|
| 295 |
+
except KeyboardInterrupt:
|
| 296 |
+
print("\n👋 Goodbye!")
|
| 297 |
+
break
|
| 298 |
+
except Exception as e:
|
| 299 |
+
print(f"❌ Error: {e}")
|
| 300 |
+
|
| 301 |
+
if __name__ == "__main__":
|
| 302 |
+
main()
|
requirements.txt
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Core ML libraries
|
| 2 |
+
torch>=2.0.0
|
| 3 |
+
transformers>=4.35.0
|
| 4 |
+
accelerate>=0.24.0
|
| 5 |
+
peft>=0.6.0
|
| 6 |
+
datasets>=2.14.0
|
| 7 |
+
|
| 8 |
+
# vLLM and inference
|
| 9 |
+
vllm>=0.2.0
|
| 10 |
+
openai>=1.0.0
|
| 11 |
+
|
| 12 |
+
# Data processing
|
| 13 |
+
numpy>=1.24.0
|
| 14 |
+
pandas>=2.0.0
|
| 15 |
+
pyyaml>=6.0
|
| 16 |
+
|
| 17 |
+
# HuggingFace tools
|
| 18 |
+
huggingface-hub>=0.17.0
|
| 19 |
+
tokenizers>=0.14.0
|
| 20 |
+
|
| 21 |
+
# Utilities
|
| 22 |
+
tqdm>=4.65.0
|
| 23 |
+
requests>=2.31.0
|
| 24 |
+
python-dotenv>=1.0.0
|
| 25 |
+
|
| 26 |
+
# Monitoring and system info
|
| 27 |
+
psutil>=5.9.0
|
| 28 |
+
GPUtil>=1.4.0
|
| 29 |
+
|
| 30 |
+
# Optional: For better performance
|
| 31 |
+
bitsandbytes>=0.41.0
|
| 32 |
+
scipy>=1.10.0
|
| 33 |
+
scikit-learn>=1.3.0
|
| 34 |
+
|
| 35 |
+
# Development and testing
|
| 36 |
+
pytest>=7.4.0
|
| 37 |
+
black>=23.0.0
|
| 38 |
+
flake8>=6.0.0
|
run_all.sh
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/bash
|
| 2 |
+
|
| 3 |
+
echo "🚀 Complete Base LLM Setup"
|
| 4 |
+
echo "=========================="
|
| 5 |
+
|
| 6 |
+
# Check if virtual environment exists
|
| 7 |
+
if [ ! -d "venv" ]; then
|
| 8 |
+
echo "📦 Virtual environment not found. Creating..."
|
| 9 |
+
chmod +x setup.sh
|
| 10 |
+
./setup.sh
|
| 11 |
+
else
|
| 12 |
+
echo "✅ Virtual environment found"
|
| 13 |
+
fi
|
| 14 |
+
|
| 15 |
+
# Activate virtual environment
|
| 16 |
+
echo "🔧 Activating virtual environment..."
|
| 17 |
+
source venv/bin/activate
|
| 18 |
+
|
| 19 |
+
# Check HuggingFace token
|
| 20 |
+
if [ -z "$HUGGINGFACE_TOKEN" ]; then
|
| 21 |
+
echo "⚠️ HUGGINGFACE_TOKEN not set"
|
| 22 |
+
echo "Please set your token:"
|
| 23 |
+
echo "export HUGGINGFACE_TOKEN='your_token_here'"
|
| 24 |
+
echo ""
|
| 25 |
+
read -p "Enter your HuggingFace token: " token
|
| 26 |
+
if [ ! -z "$token" ]; then
|
| 27 |
+
export HUGGINGFACE_TOKEN="$token"
|
| 28 |
+
echo "✅ Token set"
|
| 29 |
+
else
|
| 30 |
+
echo "❌ No token provided. Exiting..."
|
| 31 |
+
exit 1
|
| 32 |
+
fi
|
| 33 |
+
else
|
| 34 |
+
echo "✅ HuggingFace token found"
|
| 35 |
+
fi
|
| 36 |
+
|
| 37 |
+
# Check if model exists
|
| 38 |
+
if [ ! -d "models/llama-3.1-8b-instruct" ]; then
|
| 39 |
+
echo "📥 Downloading base model..."
|
| 40 |
+
python scripts/download_model.py
|
| 41 |
+
else
|
| 42 |
+
echo "✅ Base model found"
|
| 43 |
+
fi
|
| 44 |
+
|
| 45 |
+
# Check if dataset exists
|
| 46 |
+
if [ ! -f "data/training_data.jsonl" ]; then
|
| 47 |
+
echo "📊 Creating sample dataset..."
|
| 48 |
+
python scripts/create_sample_dataset.py
|
| 49 |
+
# Choose option 1 (create sample dataset)
|
| 50 |
+
echo "1" | python scripts/create_sample_dataset.py
|
| 51 |
+
else
|
| 52 |
+
echo "✅ Training dataset found"
|
| 53 |
+
fi
|
| 54 |
+
|
| 55 |
+
# Check if config exists
|
| 56 |
+
if [ ! -f "configs/llama_config.yaml" ]; then
|
| 57 |
+
echo "⚙️ Creating model configuration..."
|
| 58 |
+
python scripts/download_model.py
|
| 59 |
+
else
|
| 60 |
+
echo "✅ Model configuration found"
|
| 61 |
+
fi
|
| 62 |
+
|
| 63 |
+
echo ""
|
| 64 |
+
echo "🎉 Setup Complete!"
|
| 65 |
+
echo "=================="
|
| 66 |
+
echo ""
|
| 67 |
+
echo "📋 Next steps:"
|
| 68 |
+
echo "1. Review configuration: cat configs/llama_config.yaml"
|
| 69 |
+
echo "2. Start fine-tuning: python scripts/finetune_lora.py"
|
| 70 |
+
echo "3. Test model: python scripts/test_model.py"
|
| 71 |
+
echo "4. Start vLLM service: docker-compose up -d vllm"
|
| 72 |
+
echo ""
|
| 73 |
+
echo "💡 Tips:"
|
| 74 |
+
echo "- Always activate venv: source venv/bin/activate"
|
| 75 |
+
echo "- Monitor GPU usage: nvidia-smi"
|
| 76 |
+
echo "- Check logs: tail -f logs/training.log"
|
| 77 |
+
echo ""
|
| 78 |
+
echo "🚀 Ready to start fine-tuning!"
|
| 79 |
+
|
| 80 |
+
|
| 81 |
+
|
| 82 |
+
|
run_alternative_models.sh
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/bash
|
| 2 |
+
|
| 3 |
+
echo "🚀 Alternative Models Setup"
|
| 4 |
+
echo "==========================="
|
| 5 |
+
|
| 6 |
+
# Check if virtual environment exists
|
| 7 |
+
if [ ! -d "venv" ]; then
|
| 8 |
+
echo "📦 Virtual environment not found. Creating..."
|
| 9 |
+
chmod +x setup.sh
|
| 10 |
+
./setup.sh
|
| 11 |
+
else
|
| 12 |
+
echo "✅ Virtual environment found"
|
| 13 |
+
fi
|
| 14 |
+
|
| 15 |
+
# Activate virtual environment
|
| 16 |
+
echo "🔧 Activating virtual environment..."
|
| 17 |
+
source venv/bin/activate
|
| 18 |
+
|
| 19 |
+
# Check HuggingFace token
|
| 20 |
+
if [ -z "$HUGGINGFACE_TOKEN" ]; then
|
| 21 |
+
echo "⚠️ HUGGINGFACE_TOKEN not set"
|
| 22 |
+
echo "Please set your token:"
|
| 23 |
+
echo "export HUGGINGFACE_TOKEN='your_token_here'"
|
| 24 |
+
echo ""
|
| 25 |
+
read -p "Enter your HuggingFace token: " token
|
| 26 |
+
if [ ! -z "$token" ]; then
|
| 27 |
+
export HUGGINGFACE_TOKEN="$token"
|
| 28 |
+
echo "✅ Token set"
|
| 29 |
+
else
|
| 30 |
+
echo "❌ No token provided. Exiting..."
|
| 31 |
+
exit 1
|
| 32 |
+
fi
|
| 33 |
+
else
|
| 34 |
+
echo "✅ HuggingFace token found"
|
| 35 |
+
fi
|
| 36 |
+
|
| 37 |
+
# Create directories
|
| 38 |
+
echo "📁 Creating directories..."
|
| 39 |
+
mkdir -p models data configs logs
|
| 40 |
+
|
| 41 |
+
# Show model options
|
| 42 |
+
echo ""
|
| 43 |
+
echo "📋 Model Options Available:"
|
| 44 |
+
echo "1. Llama 3.2 1B Instruct - Lightweight and fast"
|
| 45 |
+
echo "2. Qwen3 4B Instruct - Good performance, reasonable size"
|
| 46 |
+
echo "3. DialoGPT Medium - Conversational AI model"
|
| 47 |
+
echo ""
|
| 48 |
+
|
| 49 |
+
# Ask user preference
|
| 50 |
+
read -p "Which model would you like to use? (1-3): " model_choice
|
| 51 |
+
|
| 52 |
+
case $model_choice in
|
| 53 |
+
1)
|
| 54 |
+
echo "🎯 Selected: Llama 3.2 1B Instruct"
|
| 55 |
+
;;
|
| 56 |
+
2)
|
| 57 |
+
echo "🎯 Selected: Qwen3 4B Instruct"
|
| 58 |
+
;;
|
| 59 |
+
3)
|
| 60 |
+
echo "🎯 Selected: DialoGPT Medium"
|
| 61 |
+
;;
|
| 62 |
+
*)
|
| 63 |
+
echo "❌ Invalid choice. Using default: Llama 3.2 1B Instruct"
|
| 64 |
+
model_choice=1
|
| 65 |
+
;;
|
| 66 |
+
esac
|
| 67 |
+
|
| 68 |
+
echo ""
|
| 69 |
+
echo "🚀 Starting model download..."
|
| 70 |
+
python scripts/download_alternative_models.py
|
| 71 |
+
|
| 72 |
+
echo ""
|
| 73 |
+
echo "🎉 Setup Complete!"
|
| 74 |
+
echo "=================="
|
| 75 |
+
echo ""
|
| 76 |
+
echo "📋 Next steps:"
|
| 77 |
+
echo "1. Review configuration: ls configs/"
|
| 78 |
+
echo "2. Start fine-tuning: python scripts/finetune_lora.py"
|
| 79 |
+
echo "3. Test model: python scripts/test_model.py"
|
| 80 |
+
echo "4. Or use Novita AI: python scripts/novita_ai_setup.py"
|
| 81 |
+
echo ""
|
| 82 |
+
echo "💡 Tips:"
|
| 83 |
+
echo "- Always activate venv: source venv/bin/activate"
|
| 84 |
+
echo "- Monitor GPU usage: nvidia-smi"
|
| 85 |
+
echo "- Check logs: tail -f logs/training.log"
|
| 86 |
+
echo ""
|
| 87 |
+
echo "🚀 Ready to start fine-tuning!"
|
| 88 |
+
|
| 89 |
+
|
| 90 |
+
|
run_complete_workflow.py
ADDED
|
@@ -0,0 +1,208 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
Complete workflow for Textilindo AI: Dataset → Fine-tuning → Deployment
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
import os
|
| 7 |
+
import sys
|
| 8 |
+
import subprocess
|
| 9 |
+
from pathlib import Path
|
| 10 |
+
|
| 11 |
+
def run_command(command, description):
|
| 12 |
+
"""Run a command and handle errors"""
|
| 13 |
+
print(f"\n🔄 {description}")
|
| 14 |
+
print(f"Command: {command}")
|
| 15 |
+
|
| 16 |
+
try:
|
| 17 |
+
result = subprocess.run(command, shell=True, check=True, capture_output=True, text=True)
|
| 18 |
+
print(f"✅ {description} completed successfully")
|
| 19 |
+
return True
|
| 20 |
+
except subprocess.CalledProcessError as e:
|
| 21 |
+
print(f"❌ {description} failed")
|
| 22 |
+
print(f"Error: {e.stderr}")
|
| 23 |
+
return False
|
| 24 |
+
|
| 25 |
+
def check_requirements():
|
| 26 |
+
"""Check if all requirements are met"""
|
| 27 |
+
print("🔍 Checking requirements...")
|
| 28 |
+
|
| 29 |
+
# Check if virtual environment is activated
|
| 30 |
+
if not hasattr(sys, 'real_prefix') and not (hasattr(sys, 'base_prefix') and sys.base_prefix != sys.prefix):
|
| 31 |
+
print("⚠️ Virtual environment not detected")
|
| 32 |
+
print("Please activate the virtual environment first:")
|
| 33 |
+
print("source venv/bin/activate")
|
| 34 |
+
return False
|
| 35 |
+
|
| 36 |
+
# Check if API key is set
|
| 37 |
+
api_key = os.getenv('NOVITA_API_KEY')
|
| 38 |
+
if not api_key:
|
| 39 |
+
print("⚠️ NOVITA_API_KEY not set")
|
| 40 |
+
print("Please set your Novita AI API key:")
|
| 41 |
+
print("export NOVITA_API_KEY='your_api_key'")
|
| 42 |
+
return False
|
| 43 |
+
|
| 44 |
+
# Check if dataset exists
|
| 45 |
+
dataset_path = "data/lora_dataset_20250829_113330.jsonl"
|
| 46 |
+
if not os.path.exists(dataset_path):
|
| 47 |
+
print(f"❌ Dataset not found: {dataset_path}")
|
| 48 |
+
return False
|
| 49 |
+
|
| 50 |
+
print("✅ All requirements met")
|
| 51 |
+
return True
|
| 52 |
+
|
| 53 |
+
def step1_convert_dataset():
|
| 54 |
+
"""Step 1: Convert dataset format"""
|
| 55 |
+
print("\n" + "="*60)
|
| 56 |
+
print("STEP 1: CONVERT DATASET FORMAT")
|
| 57 |
+
print("="*60)
|
| 58 |
+
|
| 59 |
+
return run_command(
|
| 60 |
+
"python convert_dataset.py",
|
| 61 |
+
"Converting dataset from instruction format to training format"
|
| 62 |
+
)
|
| 63 |
+
|
| 64 |
+
def step2_download_model():
|
| 65 |
+
"""Step 2: Download base model"""
|
| 66 |
+
print("\n" + "="*60)
|
| 67 |
+
print("STEP 2: DOWNLOAD BASE MODEL")
|
| 68 |
+
print("="*60)
|
| 69 |
+
|
| 70 |
+
# Check if model already exists
|
| 71 |
+
model_path = "models/llama-3.2-1b-instruct"
|
| 72 |
+
if os.path.exists(model_path):
|
| 73 |
+
print(f"✅ Model already exists: {model_path}")
|
| 74 |
+
return True
|
| 75 |
+
|
| 76 |
+
return run_command(
|
| 77 |
+
"python scripts/download_open_models.py",
|
| 78 |
+
"Downloading base model (Llama 3.2 1B Instruct)"
|
| 79 |
+
)
|
| 80 |
+
|
| 81 |
+
def step3_fine_tune():
|
| 82 |
+
"""Step 3: Fine-tune the model"""
|
| 83 |
+
print("\n" + "="*60)
|
| 84 |
+
print("STEP 3: FINE-TUNE MODEL")
|
| 85 |
+
print("="*60)
|
| 86 |
+
|
| 87 |
+
# Check if training config exists
|
| 88 |
+
config_path = "configs/training_config.yaml"
|
| 89 |
+
if not os.path.exists(config_path):
|
| 90 |
+
print(f"❌ Training config not found: {config_path}")
|
| 91 |
+
print("Please run Step 1 first to create the config")
|
| 92 |
+
return False
|
| 93 |
+
|
| 94 |
+
return run_command(
|
| 95 |
+
"python scripts/finetune_lora.py",
|
| 96 |
+
"Fine-tuning model with LoRA"
|
| 97 |
+
)
|
| 98 |
+
|
| 99 |
+
def step4_test_model():
|
| 100 |
+
"""Step 4: Test the fine-tuned model"""
|
| 101 |
+
print("\n" + "="*60)
|
| 102 |
+
print("STEP 4: TEST FINE-TUNED MODEL")
|
| 103 |
+
print("="*60)
|
| 104 |
+
|
| 105 |
+
# Check if fine-tuned model exists
|
| 106 |
+
lora_path = "models/textilindo-lora-weights"
|
| 107 |
+
if not os.path.exists(lora_path):
|
| 108 |
+
print(f"⚠️ Fine-tuned model not found: {lora_path}")
|
| 109 |
+
print("This step will be skipped")
|
| 110 |
+
return True
|
| 111 |
+
|
| 112 |
+
return run_command(
|
| 113 |
+
"python scripts/test_model.py",
|
| 114 |
+
"Testing fine-tuned model"
|
| 115 |
+
)
|
| 116 |
+
|
| 117 |
+
def step5_deploy_preparation():
|
| 118 |
+
"""Step 5: Prepare for deployment"""
|
| 119 |
+
print("\n" + "="*60)
|
| 120 |
+
print("STEP 5: PREPARE FOR DEPLOYMENT")
|
| 121 |
+
print("="*60)
|
| 122 |
+
|
| 123 |
+
return run_command(
|
| 124 |
+
"python deploy_to_novita.py",
|
| 125 |
+
"Preparing deployment configuration"
|
| 126 |
+
)
|
| 127 |
+
|
| 128 |
+
def main():
|
| 129 |
+
print("🚀 Textilindo AI Complete Workflow")
|
| 130 |
+
print("="*60)
|
| 131 |
+
print("This script will:")
|
| 132 |
+
print("1. Convert your dataset to training format")
|
| 133 |
+
print("2. Download a base model")
|
| 134 |
+
print("3. Fine-tune the model with your data")
|
| 135 |
+
print("4. Test the fine-tuned model")
|
| 136 |
+
print("5. Prepare for deployment to Novita AI")
|
| 137 |
+
print("="*60)
|
| 138 |
+
|
| 139 |
+
# Check requirements
|
| 140 |
+
if not check_requirements():
|
| 141 |
+
print("\n❌ Requirements not met. Please fix the issues above.")
|
| 142 |
+
return
|
| 143 |
+
|
| 144 |
+
# Ask for confirmation
|
| 145 |
+
response = input("\nDo you want to continue? (y/n): ").strip().lower()
|
| 146 |
+
if response not in ['y', 'yes']:
|
| 147 |
+
print("👋 Workflow cancelled")
|
| 148 |
+
return
|
| 149 |
+
|
| 150 |
+
# Execute steps
|
| 151 |
+
steps = [
|
| 152 |
+
("Dataset Conversion", step1_convert_dataset),
|
| 153 |
+
("Model Download", step2_download_model),
|
| 154 |
+
("Fine-tuning", step3_fine_tune),
|
| 155 |
+
("Model Testing", step4_test_model),
|
| 156 |
+
("Deployment Preparation", step5_deploy_preparation)
|
| 157 |
+
]
|
| 158 |
+
|
| 159 |
+
successful_steps = 0
|
| 160 |
+
total_steps = len(steps)
|
| 161 |
+
|
| 162 |
+
for step_name, step_func in steps:
|
| 163 |
+
print(f"\n🎯 Starting: {step_name}")
|
| 164 |
+
if step_func():
|
| 165 |
+
successful_steps += 1
|
| 166 |
+
else:
|
| 167 |
+
print(f"❌ {step_name} failed. You can:")
|
| 168 |
+
print("1. Fix the issue and run this step manually")
|
| 169 |
+
print("2. Continue with the next step")
|
| 170 |
+
print("3. Stop the workflow")
|
| 171 |
+
|
| 172 |
+
response = input("Continue to next step? (y/n): ").strip().lower()
|
| 173 |
+
if response not in ['y', 'yes']:
|
| 174 |
+
break
|
| 175 |
+
|
| 176 |
+
# Summary
|
| 177 |
+
print("\n" + "="*60)
|
| 178 |
+
print("WORKFLOW SUMMARY")
|
| 179 |
+
print("="*60)
|
| 180 |
+
print(f"✅ Completed: {successful_steps}/{total_steps} steps")
|
| 181 |
+
|
| 182 |
+
if successful_steps == total_steps:
|
| 183 |
+
print("\n🎉 All steps completed successfully!")
|
| 184 |
+
print("\n📋 Next steps:")
|
| 185 |
+
print("1. Check your fine-tuned model in the models/ directory")
|
| 186 |
+
print("2. Read DEPLOYMENT_GUIDE.md for deployment instructions")
|
| 187 |
+
print("3. Contact Novita AI support for custom model deployment")
|
| 188 |
+
print("4. Test your deployed model with the chat application")
|
| 189 |
+
else:
|
| 190 |
+
print(f"\n⚠️ {total_steps - successful_steps} steps failed")
|
| 191 |
+
print("Please check the error messages above and run the failed steps manually")
|
| 192 |
+
|
| 193 |
+
print("\n📁 Generated files:")
|
| 194 |
+
files_to_check = [
|
| 195 |
+
"data/textilindo_training_data.jsonl",
|
| 196 |
+
"configs/training_config.yaml",
|
| 197 |
+
"models/textilindo-lora-weights/",
|
| 198 |
+
"DEPLOYMENT_GUIDE.md"
|
| 199 |
+
]
|
| 200 |
+
|
| 201 |
+
for file_path in files_to_check:
|
| 202 |
+
if os.path.exists(file_path):
|
| 203 |
+
print(f" ✅ {file_path}")
|
| 204 |
+
else:
|
| 205 |
+
print(f" ❌ {file_path} (not found)")
|
| 206 |
+
|
| 207 |
+
if __name__ == "__main__":
|
| 208 |
+
main()
|
run_novita.sh
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/bash
|
| 2 |
+
|
| 3 |
+
echo "🚀 Complete Novita AI Setup"
|
| 4 |
+
echo "==========================="
|
| 5 |
+
|
| 6 |
+
# Check if virtual environment exists
|
| 7 |
+
if [ ! -d "venv" ]; then
|
| 8 |
+
echo "📦 Virtual environment not found. Creating..."
|
| 9 |
+
chmod +x setup_novita.sh
|
| 10 |
+
./setup_novita.sh
|
| 11 |
+
else
|
| 12 |
+
echo "✅ Virtual environment found"
|
| 13 |
+
fi
|
| 14 |
+
|
| 15 |
+
# Activate virtual environment
|
| 16 |
+
echo "🔧 Activating virtual environment..."
|
| 17 |
+
source venv/bin/activate
|
| 18 |
+
|
| 19 |
+
# Check Novita AI API key
|
| 20 |
+
if [ -z "$NOVITA_API_KEY" ]; then
|
| 21 |
+
echo "⚠️ NOVITA_API_KEY not set"
|
| 22 |
+
echo "Please set your key:"
|
| 23 |
+
echo "export NOVITA_API_KEY='your_key_here'"
|
| 24 |
+
echo ""
|
| 25 |
+
read -p "Enter your Novita AI API key: " key
|
| 26 |
+
if [ ! -z "$key" ]; then
|
| 27 |
+
export NOVITA_API_KEY="$key"
|
| 28 |
+
echo "✅ API key set"
|
| 29 |
+
else
|
| 30 |
+
echo "❌ No API key provided. Exiting..."
|
| 31 |
+
exit 1
|
| 32 |
+
fi
|
| 33 |
+
else
|
| 34 |
+
echo "✅ Novita AI API key found"
|
| 35 |
+
fi
|
| 36 |
+
|
| 37 |
+
# Create data directory if not exists
|
| 38 |
+
if [ ! -d "data" ]; then
|
| 39 |
+
echo "📁 Creating data directory..."
|
| 40 |
+
mkdir -p data
|
| 41 |
+
fi
|
| 42 |
+
|
| 43 |
+
echo ""
|
| 44 |
+
echo "🎉 Setup Complete!"
|
| 45 |
+
echo "=================="
|
| 46 |
+
echo ""
|
| 47 |
+
echo "📋 Next steps:"
|
| 48 |
+
echo "1. Review available models: python scripts/novita_ai_setup.py"
|
| 49 |
+
echo "2. Create fine-tuning job"
|
| 50 |
+
echo "3. Monitor training progress"
|
| 51 |
+
echo ""
|
| 52 |
+
echo "💡 Tips:"
|
| 53 |
+
echo "- Always activate venv: source venv/bin/activate"
|
| 54 |
+
echo "- Check API documentation: https://docs.novita.ai"
|
| 55 |
+
echo "- Monitor your usage in Novita AI dashboard"
|
| 56 |
+
echo ""
|
| 57 |
+
echo "🚀 Ready to start with Novita AI!"
|
| 58 |
+
|
| 59 |
+
|
| 60 |
+
|
scripts/create_sample_dataset.py
ADDED
|
@@ -0,0 +1,195 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
Script untuk membuat sample dataset JSONL untuk training
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
import json
|
| 7 |
+
import os
|
| 8 |
+
from pathlib import Path
|
| 9 |
+
|
| 10 |
+
def create_sample_dataset():
|
| 11 |
+
"""Create sample JSONL dataset"""
|
| 12 |
+
|
| 13 |
+
# Sample training data
|
| 14 |
+
sample_data = [
|
| 15 |
+
{
|
| 16 |
+
"text": "Apa itu machine learning? Machine learning adalah cabang dari artificial intelligence yang memungkinkan komputer belajar dari data tanpa diprogram secara eksplisit.",
|
| 17 |
+
"category": "education",
|
| 18 |
+
"language": "id"
|
| 19 |
+
},
|
| 20 |
+
{
|
| 21 |
+
"text": "Jelaskan tentang deep learning. Deep learning adalah subset dari machine learning yang menggunakan neural network dengan banyak layer untuk memproses data kompleks.",
|
| 22 |
+
"category": "education",
|
| 23 |
+
"language": "id"
|
| 24 |
+
},
|
| 25 |
+
{
|
| 26 |
+
"text": "Bagaimana cara kerja neural network? Neural network bekerja dengan menerima input, memproses melalui hidden layers, dan menghasilkan output berdasarkan weights yang telah dilatih.",
|
| 27 |
+
"category": "education",
|
| 28 |
+
"language": "id"
|
| 29 |
+
},
|
| 30 |
+
{
|
| 31 |
+
"text": "Apa keuntungan menggunakan Python untuk AI? Python memiliki library yang lengkap seperti TensorFlow, PyTorch, dan scikit-learn yang memudahkan development AI.",
|
| 32 |
+
"category": "programming",
|
| 33 |
+
"language": "id"
|
| 34 |
+
},
|
| 35 |
+
{
|
| 36 |
+
"text": "Jelaskan tentang transfer learning. Transfer learning adalah teknik menggunakan model yang sudah dilatih pada dataset besar dan mengadaptasinya untuk task yang lebih spesifik.",
|
| 37 |
+
"category": "education",
|
| 38 |
+
"language": "id"
|
| 39 |
+
},
|
| 40 |
+
{
|
| 41 |
+
"text": "Bagaimana cara optimize model machine learning? Optimasi dapat dilakukan dengan hyperparameter tuning, feature engineering, dan menggunakan teknik seperti cross-validation.",
|
| 42 |
+
"category": "optimization",
|
| 43 |
+
"language": "id"
|
| 44 |
+
},
|
| 45 |
+
{
|
| 46 |
+
"text": "Apa itu overfitting? Overfitting terjadi ketika model belajar terlalu detail dari training data sehingga performa pada data baru menurun.",
|
| 47 |
+
"category": "education",
|
| 48 |
+
"language": "id"
|
| 49 |
+
},
|
| 50 |
+
{
|
| 51 |
+
"text": "Jelaskan tentang regularization. Regularization adalah teknik untuk mencegah overfitting dengan menambahkan penalty pada model complexity.",
|
| 52 |
+
"category": "education",
|
| 53 |
+
"language": "id"
|
| 54 |
+
},
|
| 55 |
+
{
|
| 56 |
+
"text": "Bagaimana cara handle imbalanced dataset? Dataset tidak seimbang dapat diatasi dengan teknik sampling, class weights, atau menggunakan metrics yang tepat seperti F1-score.",
|
| 57 |
+
"category": "data_handling",
|
| 58 |
+
"language": "id"
|
| 59 |
+
},
|
| 60 |
+
{
|
| 61 |
+
"text": "Apa itu ensemble learning? Ensemble learning menggabungkan multiple model untuk meningkatkan performa prediksi dan mengurangi variance.",
|
| 62 |
+
"category": "education",
|
| 63 |
+
"language": "id"
|
| 64 |
+
}
|
| 65 |
+
]
|
| 66 |
+
|
| 67 |
+
# Create data directory
|
| 68 |
+
data_dir = Path("data")
|
| 69 |
+
data_dir.mkdir(exist_ok=True)
|
| 70 |
+
|
| 71 |
+
# Write to JSONL file
|
| 72 |
+
output_file = data_dir / "training_data.jsonl"
|
| 73 |
+
|
| 74 |
+
with open(output_file, 'w', encoding='utf-8') as f:
|
| 75 |
+
for item in sample_data:
|
| 76 |
+
json.dump(item, f, ensure_ascii=False)
|
| 77 |
+
f.write('\n')
|
| 78 |
+
|
| 79 |
+
print(f"✅ Sample dataset created: {output_file}")
|
| 80 |
+
print(f"📊 Total samples: {len(sample_data)}")
|
| 81 |
+
print(f"📁 File size: {output_file.stat().st_size / 1024:.2f} KB")
|
| 82 |
+
|
| 83 |
+
# Show sample content
|
| 84 |
+
print("\n📝 Sample content:")
|
| 85 |
+
print("-" * 50)
|
| 86 |
+
for i, item in enumerate(sample_data[:3], 1):
|
| 87 |
+
print(f"Sample {i}:")
|
| 88 |
+
print(f" Text: {item['text'][:100]}...")
|
| 89 |
+
print(f" Category: {item['category']}")
|
| 90 |
+
print(f" Language: {item['language']}")
|
| 91 |
+
print()
|
| 92 |
+
|
| 93 |
+
def create_custom_dataset():
|
| 94 |
+
"""Create custom dataset from user input"""
|
| 95 |
+
|
| 96 |
+
print("🔧 Create Custom Dataset")
|
| 97 |
+
print("=" * 40)
|
| 98 |
+
|
| 99 |
+
# Get dataset info
|
| 100 |
+
dataset_name = input("Dataset name (without extension): ").strip()
|
| 101 |
+
if not dataset_name:
|
| 102 |
+
dataset_name = "custom_dataset"
|
| 103 |
+
|
| 104 |
+
num_samples = input("Number of samples (default 10): ").strip()
|
| 105 |
+
try:
|
| 106 |
+
num_samples = int(num_samples) if num_samples else 10
|
| 107 |
+
except ValueError:
|
| 108 |
+
num_samples = 10
|
| 109 |
+
|
| 110 |
+
print(f"\n📝 Creating {num_samples} samples...")
|
| 111 |
+
print("Format: Enter text for each sample (empty line to finish early)")
|
| 112 |
+
|
| 113 |
+
custom_data = []
|
| 114 |
+
|
| 115 |
+
for i in range(num_samples):
|
| 116 |
+
print(f"\nSample {i+1}/{num_samples}:")
|
| 117 |
+
text = input("Text: ").strip()
|
| 118 |
+
|
| 119 |
+
if not text:
|
| 120 |
+
print("Empty text, finishing...")
|
| 121 |
+
break
|
| 122 |
+
|
| 123 |
+
category = input("Category (optional): ").strip() or "general"
|
| 124 |
+
language = input("Language (optional, default 'id'): ").strip() or "id"
|
| 125 |
+
|
| 126 |
+
sample = {
|
| 127 |
+
"text": text,
|
| 128 |
+
"category": category,
|
| 129 |
+
"language": language
|
| 130 |
+
}
|
| 131 |
+
|
| 132 |
+
custom_data.append(sample)
|
| 133 |
+
|
| 134 |
+
# Ask if user wants to continue
|
| 135 |
+
if i < num_samples - 1:
|
| 136 |
+
continue_input = input("Continue? (y/n, default y): ").strip().lower()
|
| 137 |
+
if continue_input in ['n', 'no']:
|
| 138 |
+
break
|
| 139 |
+
|
| 140 |
+
if not custom_data:
|
| 141 |
+
print("❌ No data entered, dataset not created")
|
| 142 |
+
return
|
| 143 |
+
|
| 144 |
+
# Create data directory
|
| 145 |
+
data_dir = Path("data")
|
| 146 |
+
data_dir.mkdir(exist_ok=True)
|
| 147 |
+
|
| 148 |
+
# Write to JSONL file
|
| 149 |
+
output_file = data_dir / f"{dataset_name}.jsonl"
|
| 150 |
+
|
| 151 |
+
with open(output_file, 'w', encoding='utf-8') as f:
|
| 152 |
+
for item in custom_data:
|
| 153 |
+
json.dump(item, f, ensure_ascii=False)
|
| 154 |
+
f.write('\n')
|
| 155 |
+
|
| 156 |
+
print(f"\n✅ Custom dataset created: {output_file}")
|
| 157 |
+
print(f"📊 Total samples: {len(custom_data)}")
|
| 158 |
+
|
| 159 |
+
def main():
|
| 160 |
+
print("📊 Dataset Creator for LLM Training")
|
| 161 |
+
print("=" * 50)
|
| 162 |
+
|
| 163 |
+
print("Pilih opsi:")
|
| 164 |
+
print("1. Create sample dataset (10 samples)")
|
| 165 |
+
print("2. Create custom dataset")
|
| 166 |
+
print("3. View existing datasets")
|
| 167 |
+
|
| 168 |
+
choice = input("\nPilihan (1-3): ").strip()
|
| 169 |
+
|
| 170 |
+
if choice == "1":
|
| 171 |
+
create_sample_dataset()
|
| 172 |
+
elif choice == "2":
|
| 173 |
+
create_custom_dataset()
|
| 174 |
+
elif choice == "3":
|
| 175 |
+
data_dir = Path("data")
|
| 176 |
+
if data_dir.exists():
|
| 177 |
+
jsonl_files = list(data_dir.glob("*.jsonl"))
|
| 178 |
+
if jsonl_files:
|
| 179 |
+
print(f"\n📁 Found {len(jsonl_files)} JSONL files:")
|
| 180 |
+
for file in jsonl_files:
|
| 181 |
+
size = file.stat().st_size / 1024
|
| 182 |
+
print(f" - {file.name} ({size:.2f} KB)")
|
| 183 |
+
else:
|
| 184 |
+
print("\n📁 No JSONL files found in data/ directory")
|
| 185 |
+
else:
|
| 186 |
+
print("\n📁 Data directory does not exist")
|
| 187 |
+
else:
|
| 188 |
+
print("❌ Pilihan tidak valid")
|
| 189 |
+
|
| 190 |
+
if __name__ == "__main__":
|
| 191 |
+
main()
|
| 192 |
+
|
| 193 |
+
|
| 194 |
+
|
| 195 |
+
|
scripts/download_alternative_models.py
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
Script untuk download model alternatif yang lebih mudah diakses
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
import os
|
| 7 |
+
import sys
|
| 8 |
+
import subprocess
|
| 9 |
+
from pathlib import Path
|
| 10 |
+
|
| 11 |
+
def check_huggingface_token():
|
| 12 |
+
"""Check if HuggingFace token is available"""
|
| 13 |
+
token = os.getenv('HUGGINGFACE_TOKEN')
|
| 14 |
+
if not token:
|
| 15 |
+
print("❌ HUGGINGFACE_TOKEN tidak ditemukan!")
|
| 16 |
+
print("Silakan set environment variable:")
|
| 17 |
+
print("export HUGGINGFACE_TOKEN='your_token_here'")
|
| 18 |
+
return False
|
| 19 |
+
return True
|
| 20 |
+
|
| 21 |
+
def download_model(model_name, model_path):
|
| 22 |
+
"""Download model menggunakan huggingface-cli"""
|
| 23 |
+
print(f"📥 Downloading model: {model_name}")
|
| 24 |
+
print(f"📁 Target directory: {model_path}")
|
| 25 |
+
|
| 26 |
+
try:
|
| 27 |
+
cmd = [
|
| 28 |
+
"huggingface-cli", "download",
|
| 29 |
+
model_name,
|
| 30 |
+
"--local-dir", str(model_path),
|
| 31 |
+
"--local-dir-use-symlinks", "False"
|
| 32 |
+
]
|
| 33 |
+
|
| 34 |
+
result = subprocess.run(cmd, capture_output=True, text=True)
|
| 35 |
+
|
| 36 |
+
if result.returncode == 0:
|
| 37 |
+
print("✅ Model berhasil didownload!")
|
| 38 |
+
return True
|
| 39 |
+
else:
|
| 40 |
+
print(f"❌ Error downloading model: {result.stderr}")
|
| 41 |
+
return False
|
| 42 |
+
|
| 43 |
+
except FileNotFoundError:
|
| 44 |
+
print("❌ huggingface-cli tidak ditemukan!")
|
| 45 |
+
print("Silakan install dengan: pip install huggingface_hub")
|
| 46 |
+
return False
|
| 47 |
+
|
| 48 |
+
def create_model_config(model_name, model_path):
|
| 49 |
+
"""Create model configuration file"""
|
| 50 |
+
config_dir = Path("configs")
|
| 51 |
+
config_dir.mkdir(exist_ok=True)
|
| 52 |
+
|
| 53 |
+
if "llama" in model_name.lower():
|
| 54 |
+
config_content = f"""# Model Configuration for {model_name}
|
| 55 |
+
model_name: "{model_name}"
|
| 56 |
+
model_path: "{model_path}"
|
| 57 |
+
max_length: 4096
|
| 58 |
+
temperature: 0.7
|
| 59 |
+
top_p: 0.9
|
| 60 |
+
top_k: 40
|
| 61 |
+
repetition_penalty: 1.1
|
| 62 |
+
|
| 63 |
+
# LoRA Configuration
|
| 64 |
+
lora_config:
|
| 65 |
+
r: 16
|
| 66 |
+
lora_alpha: 32
|
| 67 |
+
lora_dropout: 0.1
|
| 68 |
+
target_modules: ["q_proj", "v_proj", "k_proj", "o_proj", "gate_proj", "up_proj", "down_proj"]
|
| 69 |
+
|
| 70 |
+
# Training Configuration
|
| 71 |
+
training_config:
|
| 72 |
+
learning_rate: 2e-4
|
| 73 |
+
batch_size: 4
|
| 74 |
+
gradient_accumulation_steps: 4
|
| 75 |
+
num_epochs: 3
|
| 76 |
+
warmup_steps: 100
|
| 77 |
+
save_steps: 500
|
| 78 |
+
eval_steps: 500
|
| 79 |
+
"""
|
| 80 |
+
else:
|
| 81 |
+
config_content = f"""# Model Configuration for {model_name}
|
| 82 |
+
model_name: "{model_name}"
|
| 83 |
+
model_path: "{model_path}"
|
| 84 |
+
max_length: 4096
|
| 85 |
+
temperature: 0.7
|
| 86 |
+
top_p: 0.9
|
| 87 |
+
top_k: 40
|
| 88 |
+
repetition_penalty: 1.1
|
| 89 |
+
|
| 90 |
+
# LoRA Configuration
|
| 91 |
+
lora_config:
|
| 92 |
+
r: 16
|
| 93 |
+
lora_alpha: 32
|
| 94 |
+
lora_dropout: 0.1
|
| 95 |
+
target_modules: ["q_proj", "v_proj", "k_proj", "o_proj", "gate_proj", "up_proj", "down_proj"]
|
| 96 |
+
|
| 97 |
+
# Training Configuration
|
| 98 |
+
training_config:
|
| 99 |
+
learning_rate: 2e-4
|
| 100 |
+
batch_size: 4
|
| 101 |
+
gradient_accumulation_steps: 4
|
| 102 |
+
num_epochs: 3
|
| 103 |
+
warmup_steps: 100
|
| 104 |
+
save_steps: 500
|
| 105 |
+
eval_steps: 500
|
| 106 |
+
"""
|
| 107 |
+
|
| 108 |
+
config_file = config_dir / f"{model_name.split('/')[-1].lower().replace('-', '_')}_config.yaml"
|
| 109 |
+
with open(config_file, 'w') as f:
|
| 110 |
+
f.write(config_content)
|
| 111 |
+
|
| 112 |
+
print(f"✅ Model config created: {config_file}")
|
| 113 |
+
return str(config_file)
|
| 114 |
+
|
| 115 |
+
def main():
|
| 116 |
+
print("🚀 Download Alternative Models")
|
| 117 |
+
print("=" * 50)
|
| 118 |
+
|
| 119 |
+
if not check_huggingface_token():
|
| 120 |
+
sys.exit(1)
|
| 121 |
+
|
| 122 |
+
# Model options
|
| 123 |
+
models = [
|
| 124 |
+
{
|
| 125 |
+
"name": "meta-llama/Llama-3.2-1B-Instruct",
|
| 126 |
+
"path": "models/llama-3.2-1b-instruct",
|
| 127 |
+
"description": "Llama 3.2 1B Instruct - Lightweight and fast"
|
| 128 |
+
},
|
| 129 |
+
{
|
| 130 |
+
"name": "Qwen/Qwen3-4B-Instruct",
|
| 131 |
+
"path": "models/qwen3-4b-instruct",
|
| 132 |
+
"description": "Qwen3 4B Instruct - Good performance, reasonable size"
|
| 133 |
+
},
|
| 134 |
+
{
|
| 135 |
+
"name": "microsoft/DialoGPT-medium",
|
| 136 |
+
"path": "models/dialogpt-medium",
|
| 137 |
+
"description": "DialoGPT Medium - Conversational AI model"
|
| 138 |
+
}
|
| 139 |
+
]
|
| 140 |
+
|
| 141 |
+
print("📋 Pilih model yang ingin didownload:")
|
| 142 |
+
for i, model in enumerate(models, 1):
|
| 143 |
+
print(f"{i}. {model['name']}")
|
| 144 |
+
print(f" {model['description']}")
|
| 145 |
+
print()
|
| 146 |
+
|
| 147 |
+
try:
|
| 148 |
+
choice = int(input("Pilihan (1-3): ").strip())
|
| 149 |
+
if choice < 1 or choice > len(models):
|
| 150 |
+
print("❌ Pilihan tidak valid")
|
| 151 |
+
return
|
| 152 |
+
|
| 153 |
+
selected_model = models[choice - 1]
|
| 154 |
+
|
| 155 |
+
print(f"\n🎯 Model yang dipilih: {selected_model['name']}")
|
| 156 |
+
print(f"📝 Deskripsi: {selected_model['description']}")
|
| 157 |
+
|
| 158 |
+
# Confirm download
|
| 159 |
+
confirm = input("\nLanjutkan download? (y/n): ").strip().lower()
|
| 160 |
+
if confirm not in ['y', 'yes']:
|
| 161 |
+
print("❌ Download dibatalkan")
|
| 162 |
+
return
|
| 163 |
+
|
| 164 |
+
# Download model
|
| 165 |
+
print(f"\n1️⃣ Downloading model...")
|
| 166 |
+
if download_model(selected_model['name'], selected_model['path']):
|
| 167 |
+
print(f"\n2️⃣ Creating model configuration...")
|
| 168 |
+
config_file = create_model_config(selected_model['name'], selected_model['path'])
|
| 169 |
+
|
| 170 |
+
print("\n3️�� Setup selesai!")
|
| 171 |
+
print(f"\n📋 Langkah selanjutnya:")
|
| 172 |
+
print(f"1. Model tersimpan di: {selected_model['path']}")
|
| 173 |
+
print(f"2. Config tersimpan di: {config_file}")
|
| 174 |
+
print("3. Jalankan: python scripts/finetune_lora.py")
|
| 175 |
+
print("4. Atau gunakan Novita AI: python scripts/novita_ai_setup.py")
|
| 176 |
+
|
| 177 |
+
except ValueError:
|
| 178 |
+
print("❌ Input tidak valid")
|
| 179 |
+
except KeyboardInterrupt:
|
| 180 |
+
print("\n👋 Download dibatalkan")
|
| 181 |
+
|
| 182 |
+
if __name__ == "__main__":
|
| 183 |
+
main()
|
| 184 |
+
|
| 185 |
+
|
| 186 |
+
|
scripts/download_model.py
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
Script untuk download dan setup model Llama 3.1 8B
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
import os
|
| 7 |
+
import sys
|
| 8 |
+
import subprocess
|
| 9 |
+
from pathlib import Path
|
| 10 |
+
|
| 11 |
+
def check_huggingface_token():
|
| 12 |
+
"""Check if HuggingFace token is available"""
|
| 13 |
+
token = os.getenv('HUGGINGFACE_TOKEN')
|
| 14 |
+
if not token:
|
| 15 |
+
print("❌ HUGGINGFACE_TOKEN tidak ditemukan!")
|
| 16 |
+
print("Silakan set environment variable:")
|
| 17 |
+
print("export HUGGINGFACE_TOKEN='your_token_here'")
|
| 18 |
+
print("\nAtau buat file .env dengan isi:")
|
| 19 |
+
print("HUGGINGFACE_TOKEN=your_token_here")
|
| 20 |
+
return False
|
| 21 |
+
return True
|
| 22 |
+
|
| 23 |
+
def download_model():
|
| 24 |
+
"""Download model menggunakan huggingface-cli"""
|
| 25 |
+
model_name = "meta-llama/Llama-3.1-8B-Instruct"
|
| 26 |
+
models_dir = Path("models")
|
| 27 |
+
|
| 28 |
+
if not models_dir.exists():
|
| 29 |
+
models_dir.mkdir(parents=True)
|
| 30 |
+
|
| 31 |
+
print(f"📥 Downloading model: {model_name}")
|
| 32 |
+
print(f"📁 Target directory: {models_dir.absolute()}")
|
| 33 |
+
|
| 34 |
+
try:
|
| 35 |
+
cmd = [
|
| 36 |
+
"huggingface-cli", "download",
|
| 37 |
+
model_name,
|
| 38 |
+
"--local-dir", str(models_dir / "llama-3.1-8b-instruct"),
|
| 39 |
+
"--local-dir-use-symlinks", "False"
|
| 40 |
+
]
|
| 41 |
+
|
| 42 |
+
result = subprocess.run(cmd, capture_output=True, text=True)
|
| 43 |
+
|
| 44 |
+
if result.returncode == 0:
|
| 45 |
+
print("✅ Model berhasil didownload!")
|
| 46 |
+
else:
|
| 47 |
+
print(f"❌ Error downloading model: {result.stderr}")
|
| 48 |
+
return False
|
| 49 |
+
|
| 50 |
+
except FileNotFoundError:
|
| 51 |
+
print("❌ huggingface-cli tidak ditemukan!")
|
| 52 |
+
print("Silakan install dengan: pip install huggingface_hub")
|
| 53 |
+
return False
|
| 54 |
+
|
| 55 |
+
return True
|
| 56 |
+
|
| 57 |
+
def create_model_config():
|
| 58 |
+
"""Create model configuration file"""
|
| 59 |
+
config_dir = Path("configs")
|
| 60 |
+
config_dir.mkdir(exist_ok=True)
|
| 61 |
+
|
| 62 |
+
config_content = """# Model Configuration for Llama 3.1 8B
|
| 63 |
+
model_name: "meta-llama/Llama-3.1-8B-Instruct"
|
| 64 |
+
model_path: "./models/llama-3.1-8b-instruct"
|
| 65 |
+
max_length: 8192
|
| 66 |
+
temperature: 0.7
|
| 67 |
+
top_p: 0.9
|
| 68 |
+
top_k: 40
|
| 69 |
+
repetition_penalty: 1.1
|
| 70 |
+
|
| 71 |
+
# LoRA Configuration
|
| 72 |
+
lora_config:
|
| 73 |
+
r: 16
|
| 74 |
+
lora_alpha: 32
|
| 75 |
+
lora_dropout: 0.1
|
| 76 |
+
target_modules: ["q_proj", "v_proj", "k_proj", "o_proj", "gate_proj", "up_proj", "down_proj"]
|
| 77 |
+
|
| 78 |
+
# Training Configuration
|
| 79 |
+
training_config:
|
| 80 |
+
learning_rate: 2e-4
|
| 81 |
+
batch_size: 4
|
| 82 |
+
gradient_accumulation_steps: 4
|
| 83 |
+
num_epochs: 3
|
| 84 |
+
warmup_steps: 100
|
| 85 |
+
save_steps: 500
|
| 86 |
+
eval_steps: 500
|
| 87 |
+
"""
|
| 88 |
+
|
| 89 |
+
config_file = config_dir / "llama_config.yaml"
|
| 90 |
+
with open(config_file, 'w') as f:
|
| 91 |
+
f.write(config_content)
|
| 92 |
+
|
| 93 |
+
print(f"✅ Model config created: {config_file}")
|
| 94 |
+
|
| 95 |
+
def main():
|
| 96 |
+
print("🚀 Setup Base LLM - Llama 3.1 8B")
|
| 97 |
+
print("=" * 50)
|
| 98 |
+
|
| 99 |
+
if not check_huggingface_token():
|
| 100 |
+
sys.exit(1)
|
| 101 |
+
|
| 102 |
+
print("\n1️⃣ Downloading model...")
|
| 103 |
+
if not download_model():
|
| 104 |
+
sys.exit(1)
|
| 105 |
+
|
| 106 |
+
print("\n2️⃣ Creating model configuration...")
|
| 107 |
+
create_model_config()
|
| 108 |
+
|
| 109 |
+
print("\n3️⃣ Setup selesai!")
|
| 110 |
+
print("\n📋 Langkah selanjutnya:")
|
| 111 |
+
print("1. Jalankan: docker-compose up -d")
|
| 112 |
+
print("2. Test API: curl http://localhost:8000/health")
|
| 113 |
+
print("3. Mulai fine-tuning dengan LoRA")
|
| 114 |
+
|
| 115 |
+
if __name__ == "__main__":
|
| 116 |
+
main()
|
| 117 |
+
|
| 118 |
+
|
| 119 |
+
|
| 120 |
+
|
scripts/download_open_models.py
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
Script untuk download model yang benar-benar open source dan mudah diakses
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
import os
|
| 7 |
+
import sys
|
| 8 |
+
import subprocess
|
| 9 |
+
from pathlib import Path
|
| 10 |
+
|
| 11 |
+
def check_huggingface_token():
|
| 12 |
+
"""Check if HuggingFace token is available"""
|
| 13 |
+
token = os.getenv('HUGGINGFACE_TOKEN')
|
| 14 |
+
if not token:
|
| 15 |
+
print("❌ HUGGINGFACE_TOKEN tidak ditemukan!")
|
| 16 |
+
print("Silakan set environment variable:")
|
| 17 |
+
print("export HUGGINGFACE_TOKEN='your_token_here'")
|
| 18 |
+
return False
|
| 19 |
+
return True
|
| 20 |
+
|
| 21 |
+
def download_model(model_name, model_path):
|
| 22 |
+
"""Download model menggunakan huggingface-cli"""
|
| 23 |
+
print(f"📥 Downloading model: {model_name}")
|
| 24 |
+
print(f"📁 Target directory: {model_path}")
|
| 25 |
+
|
| 26 |
+
try:
|
| 27 |
+
cmd = [
|
| 28 |
+
"huggingface-cli", "download",
|
| 29 |
+
model_name,
|
| 30 |
+
"--local-dir", str(model_path),
|
| 31 |
+
"--local-dir-use-symlinks", "False"
|
| 32 |
+
]
|
| 33 |
+
|
| 34 |
+
result = subprocess.run(cmd, capture_output=True, text=True)
|
| 35 |
+
|
| 36 |
+
if result.returncode == 0:
|
| 37 |
+
print("✅ Model berhasil didownload!")
|
| 38 |
+
return True
|
| 39 |
+
else:
|
| 40 |
+
print(f"❌ Error downloading model: {result.stderr}")
|
| 41 |
+
return False
|
| 42 |
+
|
| 43 |
+
except FileNotFoundError:
|
| 44 |
+
print("❌ huggingface-cli tidak ditemukan!")
|
| 45 |
+
print("Silakan install dengan: pip install huggingface_hub")
|
| 46 |
+
return False
|
| 47 |
+
|
| 48 |
+
def create_model_config(model_name, model_path):
|
| 49 |
+
"""Create model configuration file"""
|
| 50 |
+
config_dir = Path("configs")
|
| 51 |
+
config_dir.mkdir(exist_ok=True)
|
| 52 |
+
|
| 53 |
+
config_content = f"""# Model Configuration for {model_name}
|
| 54 |
+
model_name: "{model_name}"
|
| 55 |
+
model_path: "{model_path}"
|
| 56 |
+
max_length: 2048
|
| 57 |
+
temperature: 0.7
|
| 58 |
+
top_p: 0.9
|
| 59 |
+
top_k: 40
|
| 60 |
+
repetition_penalty: 1.1
|
| 61 |
+
|
| 62 |
+
# LoRA Configuration
|
| 63 |
+
lora_config:
|
| 64 |
+
r: 16
|
| 65 |
+
lora_alpha: 32
|
| 66 |
+
lora_dropout: 0.1
|
| 67 |
+
target_modules: ["q_proj", "v_proj", "k_proj", "o_proj", "gate_proj", "up_proj", "down_proj"]
|
| 68 |
+
|
| 69 |
+
# Training Configuration
|
| 70 |
+
training_config:
|
| 71 |
+
learning_rate: 2e-4
|
| 72 |
+
batch_size: 4
|
| 73 |
+
gradient_accumulation_steps: 4
|
| 74 |
+
num_epochs: 3
|
| 75 |
+
warmup_steps: 100
|
| 76 |
+
save_steps: 500
|
| 77 |
+
eval_steps: 500
|
| 78 |
+
"""
|
| 79 |
+
|
| 80 |
+
config_file = config_dir / f"{model_name.split('/')[-1].lower().replace('-', '_')}_config.yaml"
|
| 81 |
+
with open(config_file, 'w') as f:
|
| 82 |
+
f.write(config_content)
|
| 83 |
+
|
| 84 |
+
print(f"✅ Model config created: {config_file}")
|
| 85 |
+
return str(config_file)
|
| 86 |
+
|
| 87 |
+
def main():
|
| 88 |
+
print("🚀 Download Open Source Models")
|
| 89 |
+
print("=" * 50)
|
| 90 |
+
|
| 91 |
+
if not check_huggingface_token():
|
| 92 |
+
sys.exit(1)
|
| 93 |
+
|
| 94 |
+
# Model options - truly open source
|
| 95 |
+
models = [
|
| 96 |
+
{
|
| 97 |
+
"name": "microsoft/DialoGPT-medium",
|
| 98 |
+
"path": "models/dialogpt-medium",
|
| 99 |
+
"description": "DialoGPT Medium - Conversational AI model (355M parameters)"
|
| 100 |
+
},
|
| 101 |
+
{
|
| 102 |
+
"name": "distilgpt2",
|
| 103 |
+
"path": "models/distilgpt2",
|
| 104 |
+
"description": "DistilGPT2 - Lightweight GPT-2 model (82M parameters)"
|
| 105 |
+
},
|
| 106 |
+
{
|
| 107 |
+
"name": "gpt2",
|
| 108 |
+
"path": "models/gpt2",
|
| 109 |
+
"description": "GPT-2 - Original GPT-2 model (124M parameters)"
|
| 110 |
+
},
|
| 111 |
+
{
|
| 112 |
+
"name": "EleutherAI/gpt-neo-125M",
|
| 113 |
+
"path": "models/gpt-neo-125m",
|
| 114 |
+
"description": "GPT-Neo 125M - Small but capable model (125M parameters)"
|
| 115 |
+
}
|
| 116 |
+
]
|
| 117 |
+
|
| 118 |
+
print("📋 Pilih model yang ingin didownload:")
|
| 119 |
+
for i, model in enumerate(models, 1):
|
| 120 |
+
print(f"{i}. {model['name']}")
|
| 121 |
+
print(f" {model['description']}")
|
| 122 |
+
print()
|
| 123 |
+
|
| 124 |
+
try:
|
| 125 |
+
choice = int(input("Pilihan (1-4): ").strip())
|
| 126 |
+
if choice < 1 or choice > len(models):
|
| 127 |
+
print("❌ Pilihan tidak valid")
|
| 128 |
+
return
|
| 129 |
+
|
| 130 |
+
selected_model = models[choice - 1]
|
| 131 |
+
|
| 132 |
+
print(f"\n🎯 Model yang dipilih: {selected_model['name']}")
|
| 133 |
+
print(f"📝 Deskripsi: {selected_model['description']}")
|
| 134 |
+
|
| 135 |
+
# Confirm download
|
| 136 |
+
confirm = input("\nLanjutkan download? (y/n): ").strip().lower()
|
| 137 |
+
if confirm not in ['y', 'yes']:
|
| 138 |
+
print("❌ Download dibatalkan")
|
| 139 |
+
return
|
| 140 |
+
|
| 141 |
+
# Download model
|
| 142 |
+
print(f"\n1️⃣ Downloading model...")
|
| 143 |
+
if download_model(selected_model['name'], selected_model['path']):
|
| 144 |
+
print(f"\n2️⃣ Creating model configuration...")
|
| 145 |
+
config_file = create_model_config(selected_model['name'], selected_model['path'])
|
| 146 |
+
|
| 147 |
+
print("\n3️⃣ Setup selesai!")
|
| 148 |
+
print(f"\n📋 Langkah selanjutnya:")
|
| 149 |
+
print(f"1. Model tersimpan di: {selected_model['path']}")
|
| 150 |
+
print(f"2. Config tersimpan di: {config_file}")
|
| 151 |
+
print("3. Jalankan: python scripts/finetune_lora.py")
|
| 152 |
+
print("4. Atau gunakan Novita AI: python scripts/novita_ai_setup.py")
|
| 153 |
+
|
| 154 |
+
except ValueError:
|
| 155 |
+
print("❌ Input tidak valid")
|
| 156 |
+
except KeyboardInterrupt:
|
| 157 |
+
print("\n👋 Download dibatalkan")
|
| 158 |
+
|
| 159 |
+
if __name__ == "__main__":
|
| 160 |
+
main()
|
| 161 |
+
|
| 162 |
+
|
| 163 |
+
|
scripts/finetune_lora.py
ADDED
|
@@ -0,0 +1,251 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
Script untuk fine-tuning model Llama 3.1 8B dengan LoRA
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
import os
|
| 7 |
+
import sys
|
| 8 |
+
import yaml
|
| 9 |
+
import json
|
| 10 |
+
import torch
|
| 11 |
+
from pathlib import Path
|
| 12 |
+
from transformers import (
|
| 13 |
+
AutoTokenizer,
|
| 14 |
+
AutoModelForCausalLM,
|
| 15 |
+
TrainingArguments,
|
| 16 |
+
Trainer,
|
| 17 |
+
DataCollatorForLanguageModeling
|
| 18 |
+
)
|
| 19 |
+
from peft import (
|
| 20 |
+
LoraConfig,
|
| 21 |
+
get_peft_model,
|
| 22 |
+
TaskType,
|
| 23 |
+
prepare_model_for_kbit_training
|
| 24 |
+
)
|
| 25 |
+
from datasets import Dataset
|
| 26 |
+
import logging
|
| 27 |
+
|
| 28 |
+
# Setup logging
|
| 29 |
+
logging.basicConfig(level=logging.INFO)
|
| 30 |
+
logger = logging.getLogger(__name__)
|
| 31 |
+
|
| 32 |
+
def load_config(config_path):
|
| 33 |
+
"""Load configuration from YAML file"""
|
| 34 |
+
try:
|
| 35 |
+
with open(config_path, 'r') as f:
|
| 36 |
+
config = yaml.safe_load(f)
|
| 37 |
+
return config
|
| 38 |
+
except Exception as e:
|
| 39 |
+
logger.error(f"Error loading config: {e}")
|
| 40 |
+
return None
|
| 41 |
+
|
| 42 |
+
def load_model_and_tokenizer(config):
|
| 43 |
+
"""Load base model and tokenizer"""
|
| 44 |
+
model_path = config['model_path']
|
| 45 |
+
|
| 46 |
+
logger.info(f"Loading model from: {model_path}")
|
| 47 |
+
|
| 48 |
+
# Load tokenizer
|
| 49 |
+
tokenizer = AutoTokenizer.from_pretrained(
|
| 50 |
+
model_path,
|
| 51 |
+
trust_remote_code=True,
|
| 52 |
+
padding_side="right"
|
| 53 |
+
)
|
| 54 |
+
|
| 55 |
+
if tokenizer.pad_token is None:
|
| 56 |
+
tokenizer.pad_token = tokenizer.eos_token
|
| 57 |
+
|
| 58 |
+
# Load model
|
| 59 |
+
model = AutoModelForCausalLM.from_pretrained(
|
| 60 |
+
model_path,
|
| 61 |
+
torch_dtype=torch.float16,
|
| 62 |
+
device_map="auto",
|
| 63 |
+
trust_remote_code=True
|
| 64 |
+
)
|
| 65 |
+
|
| 66 |
+
# Prepare model for k-bit training
|
| 67 |
+
model = prepare_model_for_kbit_training(model)
|
| 68 |
+
|
| 69 |
+
return model, tokenizer
|
| 70 |
+
|
| 71 |
+
def setup_lora_config(config):
|
| 72 |
+
"""Setup LoRA configuration"""
|
| 73 |
+
lora_config = config['lora_config']
|
| 74 |
+
|
| 75 |
+
peft_config = LoraConfig(
|
| 76 |
+
task_type=TaskType.CAUSAL_LM,
|
| 77 |
+
r=lora_config['r'],
|
| 78 |
+
lora_alpha=lora_config['lora_alpha'],
|
| 79 |
+
lora_dropout=lora_config['lora_dropout'],
|
| 80 |
+
target_modules=lora_config['target_modules'],
|
| 81 |
+
bias="none",
|
| 82 |
+
)
|
| 83 |
+
|
| 84 |
+
return peft_config
|
| 85 |
+
|
| 86 |
+
def prepare_dataset(data_path, tokenizer, max_length=512):
|
| 87 |
+
"""Prepare dataset for training"""
|
| 88 |
+
logger.info(f"Loading dataset from: {data_path}")
|
| 89 |
+
|
| 90 |
+
# Load your dataset here
|
| 91 |
+
# Support for JSONL format (one JSON object per line)
|
| 92 |
+
if data_path.endswith('.jsonl'):
|
| 93 |
+
# Read JSONL file line by line
|
| 94 |
+
data = []
|
| 95 |
+
with open(data_path, 'r', encoding='utf-8') as f:
|
| 96 |
+
for line_num, line in enumerate(f, 1):
|
| 97 |
+
line = line.strip()
|
| 98 |
+
if line:
|
| 99 |
+
try:
|
| 100 |
+
json_obj = json.loads(line)
|
| 101 |
+
data.append(json_obj)
|
| 102 |
+
except json.JSONDecodeError as e:
|
| 103 |
+
logger.warning(f"Invalid JSON at line {line_num}: {e}")
|
| 104 |
+
continue
|
| 105 |
+
|
| 106 |
+
if not data:
|
| 107 |
+
raise ValueError("No valid JSON objects found in JSONL file")
|
| 108 |
+
|
| 109 |
+
# Convert to Dataset
|
| 110 |
+
dataset = Dataset.from_list(data)
|
| 111 |
+
logger.info(f"Loaded {len(dataset)} samples from JSONL file")
|
| 112 |
+
|
| 113 |
+
elif data_path.endswith('.json'):
|
| 114 |
+
dataset = Dataset.from_json(data_path)
|
| 115 |
+
elif data_path.endswith('.csv'):
|
| 116 |
+
dataset = Dataset.from_csv(data_path)
|
| 117 |
+
else:
|
| 118 |
+
raise ValueError("Unsupported data format. Use .jsonl, .json, or .csv")
|
| 119 |
+
|
| 120 |
+
# Validate dataset structure
|
| 121 |
+
if 'text' not in dataset.column_names:
|
| 122 |
+
logger.warning("Column 'text' not found in dataset")
|
| 123 |
+
logger.info(f"Available columns: {dataset.column_names}")
|
| 124 |
+
# Try to find alternative text column
|
| 125 |
+
text_columns = [col for col in dataset.column_names if 'text' in col.lower() or 'content' in col.lower()]
|
| 126 |
+
if text_columns:
|
| 127 |
+
logger.info(f"Found potential text columns: {text_columns}")
|
| 128 |
+
# Use first found text column
|
| 129 |
+
text_column = text_columns[0]
|
| 130 |
+
else:
|
| 131 |
+
raise ValueError("No text column found. Dataset must contain a 'text' column or similar")
|
| 132 |
+
else:
|
| 133 |
+
text_column = 'text'
|
| 134 |
+
|
| 135 |
+
def tokenize_function(examples):
|
| 136 |
+
# Tokenize the texts
|
| 137 |
+
tokenized = tokenizer(
|
| 138 |
+
examples[text_column],
|
| 139 |
+
truncation=True,
|
| 140 |
+
padding=True,
|
| 141 |
+
max_length=max_length,
|
| 142 |
+
return_tensors="pt"
|
| 143 |
+
)
|
| 144 |
+
return tokenized
|
| 145 |
+
|
| 146 |
+
# Tokenize dataset
|
| 147 |
+
tokenized_dataset = dataset.map(
|
| 148 |
+
tokenize_function,
|
| 149 |
+
batched=True,
|
| 150 |
+
remove_columns=dataset.column_names
|
| 151 |
+
)
|
| 152 |
+
|
| 153 |
+
return tokenized_dataset
|
| 154 |
+
|
| 155 |
+
def train_model(model, tokenizer, dataset, config, output_dir):
|
| 156 |
+
"""Train the model with LoRA"""
|
| 157 |
+
training_config = config['training_config']
|
| 158 |
+
|
| 159 |
+
# Setup training arguments
|
| 160 |
+
training_args = TrainingArguments(
|
| 161 |
+
output_dir=output_dir,
|
| 162 |
+
num_train_epochs=training_config['num_epochs'],
|
| 163 |
+
per_device_train_batch_size=training_config['batch_size'],
|
| 164 |
+
gradient_accumulation_steps=training_config['gradient_accumulation_steps'],
|
| 165 |
+
learning_rate=training_config['learning_rate'],
|
| 166 |
+
warmup_steps=training_config['warmup_steps'],
|
| 167 |
+
save_steps=training_config['save_steps'],
|
| 168 |
+
eval_steps=training_config['eval_steps'],
|
| 169 |
+
logging_steps=10,
|
| 170 |
+
save_total_limit=3,
|
| 171 |
+
prediction_loss_only=True,
|
| 172 |
+
remove_unused_columns=False,
|
| 173 |
+
push_to_hub=False,
|
| 174 |
+
report_to=None,
|
| 175 |
+
)
|
| 176 |
+
|
| 177 |
+
# Setup data collator
|
| 178 |
+
data_collator = DataCollatorForLanguageModeling(
|
| 179 |
+
tokenizer=tokenizer,
|
| 180 |
+
mlm=False,
|
| 181 |
+
)
|
| 182 |
+
|
| 183 |
+
# Setup trainer
|
| 184 |
+
trainer = Trainer(
|
| 185 |
+
model=model,
|
| 186 |
+
args=training_args,
|
| 187 |
+
train_dataset=dataset,
|
| 188 |
+
data_collator=data_collator,
|
| 189 |
+
tokenizer=tokenizer,
|
| 190 |
+
)
|
| 191 |
+
|
| 192 |
+
# Start training
|
| 193 |
+
logger.info("Starting training...")
|
| 194 |
+
trainer.train()
|
| 195 |
+
|
| 196 |
+
# Save the model
|
| 197 |
+
trainer.save_model()
|
| 198 |
+
logger.info(f"Model saved to: {output_dir}")
|
| 199 |
+
|
| 200 |
+
def main():
|
| 201 |
+
print("🚀 LoRA Fine-tuning - Llama 3.1 8B")
|
| 202 |
+
print("=" * 50)
|
| 203 |
+
|
| 204 |
+
# Load configuration
|
| 205 |
+
config_path = "configs/llama_config.yaml"
|
| 206 |
+
if not os.path.exists(config_path):
|
| 207 |
+
print(f"❌ Config file tidak ditemukan: {config_path}")
|
| 208 |
+
print("Jalankan download_model.py terlebih dahulu")
|
| 209 |
+
sys.exit(1)
|
| 210 |
+
|
| 211 |
+
config = load_config(config_path)
|
| 212 |
+
if not config:
|
| 213 |
+
sys.exit(1)
|
| 214 |
+
|
| 215 |
+
# Setup paths
|
| 216 |
+
output_dir = Path("models/finetuned-llama-lora")
|
| 217 |
+
output_dir.mkdir(parents=True, exist_ok=True)
|
| 218 |
+
|
| 219 |
+
# Load model and tokenizer
|
| 220 |
+
print("1️⃣ Loading model and tokenizer...")
|
| 221 |
+
model, tokenizer = load_model_and_tokenizer(config)
|
| 222 |
+
|
| 223 |
+
# Setup LoRA
|
| 224 |
+
print("2️⃣ Setting up LoRA configuration...")
|
| 225 |
+
peft_config = setup_lora_config(config)
|
| 226 |
+
model = get_peft_model(model, peft_config)
|
| 227 |
+
|
| 228 |
+
# Print trainable parameters
|
| 229 |
+
model.print_trainable_parameters()
|
| 230 |
+
|
| 231 |
+
# Prepare dataset (placeholder - replace with your data)
|
| 232 |
+
print("3️⃣ Preparing dataset...")
|
| 233 |
+
data_path = "data/training_data.jsonl" # Default to JSONL format
|
| 234 |
+
|
| 235 |
+
if not os.path.exists(data_path):
|
| 236 |
+
print(f"⚠️ Data file tidak ditemukan: {data_path}")
|
| 237 |
+
print("Buat dataset terlebih dahulu atau update path di script")
|
| 238 |
+
print("Skipping training...")
|
| 239 |
+
return
|
| 240 |
+
|
| 241 |
+
dataset = prepare_dataset(data_path, tokenizer)
|
| 242 |
+
|
| 243 |
+
# Train model
|
| 244 |
+
print("4️⃣ Starting training...")
|
| 245 |
+
train_model(model, tokenizer, dataset, config, output_dir)
|
| 246 |
+
|
| 247 |
+
print("✅ Training selesai!")
|
| 248 |
+
print(f"📁 Model tersimpan di: {output_dir}")
|
| 249 |
+
|
| 250 |
+
if __name__ == "__main__":
|
| 251 |
+
main()
|
scripts/local_training_setup.py
ADDED
|
@@ -0,0 +1,273 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
Script untuk setup training lokal dengan model yang lebih kecil
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
import os
|
| 7 |
+
import sys
|
| 8 |
+
import subprocess
|
| 9 |
+
from pathlib import Path
|
| 10 |
+
import logging
|
| 11 |
+
|
| 12 |
+
logging.basicConfig(level=logging.INFO)
|
| 13 |
+
logger = logging.getLogger(__name__)
|
| 14 |
+
|
| 15 |
+
def check_system_requirements():
|
| 16 |
+
"""Check system requirements untuk training lokal"""
|
| 17 |
+
print("🔍 Checking System Requirements...")
|
| 18 |
+
print("=" * 50)
|
| 19 |
+
|
| 20 |
+
# Check Python version
|
| 21 |
+
python_version = sys.version_info
|
| 22 |
+
print(f"🐍 Python: {python_version.major}.{python_version.minor}.{python_version.micro}")
|
| 23 |
+
|
| 24 |
+
if python_version < (3, 8):
|
| 25 |
+
print("❌ Python 3.8+ required")
|
| 26 |
+
return False
|
| 27 |
+
else:
|
| 28 |
+
print("✅ Python version OK")
|
| 29 |
+
|
| 30 |
+
# Check available memory
|
| 31 |
+
try:
|
| 32 |
+
import psutil
|
| 33 |
+
memory = psutil.virtual_memory()
|
| 34 |
+
memory_gb = memory.total / (1024**3)
|
| 35 |
+
print(f"💾 RAM: {memory_gb:.1f} GB")
|
| 36 |
+
|
| 37 |
+
if memory_gb < 8:
|
| 38 |
+
print("⚠️ Warning: Less than 8GB RAM may cause issues")
|
| 39 |
+
else:
|
| 40 |
+
print("✅ RAM sufficient")
|
| 41 |
+
except ImportError:
|
| 42 |
+
print("⚠️ psutil not available, cannot check memory")
|
| 43 |
+
|
| 44 |
+
# Check disk space
|
| 45 |
+
try:
|
| 46 |
+
disk = psutil.disk_usage('.')
|
| 47 |
+
disk_gb = disk.free / (1024**3)
|
| 48 |
+
print(f"💿 Free Disk: {disk_gb:.1f} GB")
|
| 49 |
+
|
| 50 |
+
if disk_gb < 10:
|
| 51 |
+
print("⚠️ Warning: Less than 10GB free space")
|
| 52 |
+
else:
|
| 53 |
+
print("✅ Disk space sufficient")
|
| 54 |
+
except:
|
| 55 |
+
print("⚠️ Cannot check disk space")
|
| 56 |
+
|
| 57 |
+
# Check CUDA (optional)
|
| 58 |
+
try:
|
| 59 |
+
import torch
|
| 60 |
+
if torch.cuda.is_available():
|
| 61 |
+
gpu_count = torch.cuda.device_count()
|
| 62 |
+
print(f"🎮 CUDA GPUs: {gpu_count}")
|
| 63 |
+
for i in range(gpu_count):
|
| 64 |
+
gpu_name = torch.cuda.get_device_name(i)
|
| 65 |
+
gpu_memory = torch.cuda.get_device_properties(i).total_memory / (1024**3)
|
| 66 |
+
print(f" GPU {i}: {gpu_name} ({gpu_memory:.1f} GB)")
|
| 67 |
+
print("✅ CUDA available - Fast training possible")
|
| 68 |
+
else:
|
| 69 |
+
print("⚠️ CUDA not available - Training will be slower (CPU only)")
|
| 70 |
+
except ImportError:
|
| 71 |
+
print("⚠️ PyTorch not available")
|
| 72 |
+
|
| 73 |
+
return True
|
| 74 |
+
|
| 75 |
+
def download_small_model():
|
| 76 |
+
"""Download model yang cocok untuk training lokal"""
|
| 77 |
+
print("\n📥 Downloading Small Model for Local Training...")
|
| 78 |
+
print("=" * 50)
|
| 79 |
+
|
| 80 |
+
# Model options yang cocok untuk training lokal
|
| 81 |
+
small_models = [
|
| 82 |
+
{
|
| 83 |
+
"name": "distilgpt2",
|
| 84 |
+
"path": "models/distilgpt2",
|
| 85 |
+
"size_mb": 82,
|
| 86 |
+
"description": "DistilGPT2 - Very lightweight (82M parameters)"
|
| 87 |
+
},
|
| 88 |
+
{
|
| 89 |
+
"name": "microsoft/DialoGPT-small",
|
| 90 |
+
"path": "models/dialogpt-small",
|
| 91 |
+
"size_mb": 117,
|
| 92 |
+
"description": "DialoGPT Small - Conversational (117M parameters)"
|
| 93 |
+
},
|
| 94 |
+
{
|
| 95 |
+
"name": "EleutherAI/gpt-neo-125M",
|
| 96 |
+
"path": "models/gpt-neo-125m",
|
| 97 |
+
"size_mb": 125,
|
| 98 |
+
"description": "GPT-Neo 125M - Good balance (125M parameters)"
|
| 99 |
+
},
|
| 100 |
+
{
|
| 101 |
+
"name": "gpt2",
|
| 102 |
+
"path": "models/gpt2",
|
| 103 |
+
"size_mb": 124,
|
| 104 |
+
"description": "GPT-2 - Original but small (124M parameters)"
|
| 105 |
+
}
|
| 106 |
+
]
|
| 107 |
+
|
| 108 |
+
print("📋 Available small models:")
|
| 109 |
+
for i, model in enumerate(small_models, 1):
|
| 110 |
+
print(f"{i}. {model['name']}")
|
| 111 |
+
print(f" {model['description']}")
|
| 112 |
+
print(f" Size: ~{model['size_mb']} MB")
|
| 113 |
+
print()
|
| 114 |
+
|
| 115 |
+
try:
|
| 116 |
+
choice = int(input("Pilih model (1-4): ").strip())
|
| 117 |
+
if choice < 1 or choice > len(small_models):
|
| 118 |
+
print("❌ Pilihan tidak valid, menggunakan default: distilgpt2")
|
| 119 |
+
choice = 1
|
| 120 |
+
|
| 121 |
+
selected_model = small_models[choice - 1]
|
| 122 |
+
print(f"\n🎯 Selected: {selected_model['name']}")
|
| 123 |
+
|
| 124 |
+
# Download model
|
| 125 |
+
print(f"\n📥 Downloading {selected_model['name']}...")
|
| 126 |
+
if download_model_with_transformers(selected_model['name'], selected_model['path']):
|
| 127 |
+
print(f"✅ Model downloaded successfully!")
|
| 128 |
+
return selected_model
|
| 129 |
+
else:
|
| 130 |
+
print("❌ Download failed")
|
| 131 |
+
return None
|
| 132 |
+
|
| 133 |
+
except (ValueError, KeyboardInterrupt):
|
| 134 |
+
print("\n❌ Download cancelled")
|
| 135 |
+
return None
|
| 136 |
+
|
| 137 |
+
def download_model_with_transformers(model_name, model_path):
|
| 138 |
+
"""Download model menggunakan transformers library"""
|
| 139 |
+
try:
|
| 140 |
+
from transformers import AutoTokenizer, AutoModelForCausalLM
|
| 141 |
+
|
| 142 |
+
print(f"Downloading tokenizer...")
|
| 143 |
+
tokenizer = AutoTokenizer.from_pretrained(model_name)
|
| 144 |
+
tokenizer.save_pretrained(model_path)
|
| 145 |
+
|
| 146 |
+
print(f"Downloading model...")
|
| 147 |
+
model = AutoModelForCausalLM.from_pretrained(model_name)
|
| 148 |
+
model.save_pretrained(model_path)
|
| 149 |
+
|
| 150 |
+
return True
|
| 151 |
+
|
| 152 |
+
except Exception as e:
|
| 153 |
+
logger.error(f"Error downloading model: {e}")
|
| 154 |
+
return False
|
| 155 |
+
|
| 156 |
+
def create_local_training_config(model_info):
|
| 157 |
+
"""Create configuration untuk training lokal"""
|
| 158 |
+
config_dir = Path("configs")
|
| 159 |
+
config_dir.mkdir(exist_ok=True)
|
| 160 |
+
|
| 161 |
+
config_content = f"""# Local Training Configuration for {model_info['name']}
|
| 162 |
+
model_name: "{model_info['name']}"
|
| 163 |
+
model_path: "{model_info['path']}"
|
| 164 |
+
max_length: 512
|
| 165 |
+
temperature: 0.7
|
| 166 |
+
top_p: 0.9
|
| 167 |
+
top_k: 40
|
| 168 |
+
repetition_penalty: 1.1
|
| 169 |
+
|
| 170 |
+
# LoRA Configuration (for memory efficiency)
|
| 171 |
+
lora_config:
|
| 172 |
+
r: 8 # Reduced for smaller models
|
| 173 |
+
lora_alpha: 16
|
| 174 |
+
lora_dropout: 0.1
|
| 175 |
+
target_modules: ["q_proj", "v_proj", "k_proj", "o_proj"]
|
| 176 |
+
|
| 177 |
+
# Training Configuration (optimized for local training)
|
| 178 |
+
training_config:
|
| 179 |
+
learning_rate: 1e-4 # Lower learning rate for stability
|
| 180 |
+
batch_size: 2 # Smaller batch size for memory
|
| 181 |
+
gradient_accumulation_steps: 8 # Accumulate gradients
|
| 182 |
+
num_epochs: 3
|
| 183 |
+
warmup_steps: 50
|
| 184 |
+
save_steps: 100
|
| 185 |
+
eval_steps: 100
|
| 186 |
+
max_grad_norm: 1.0
|
| 187 |
+
weight_decay: 0.01
|
| 188 |
+
|
| 189 |
+
# Hardware Configuration
|
| 190 |
+
hardware_config:
|
| 191 |
+
device: "auto" # Will use GPU if available
|
| 192 |
+
mixed_precision: true # Use mixed precision for memory efficiency
|
| 193 |
+
gradient_checkpointing: true # Save memory during training
|
| 194 |
+
"""
|
| 195 |
+
|
| 196 |
+
config_file = config_dir / f"local_training_{model_info['name'].split('/')[-1].lower().replace('-', '_')}.yaml"
|
| 197 |
+
with open(config_file, 'w') as f:
|
| 198 |
+
f.write(config_content)
|
| 199 |
+
|
| 200 |
+
print(f"✅ Local training config created: {config_file}")
|
| 201 |
+
return str(config_file)
|
| 202 |
+
|
| 203 |
+
def setup_local_training_environment():
|
| 204 |
+
"""Setup environment untuk training lokal"""
|
| 205 |
+
print("\n🔧 Setting up Local Training Environment...")
|
| 206 |
+
print("=" * 50)
|
| 207 |
+
|
| 208 |
+
# Install required packages
|
| 209 |
+
packages = [
|
| 210 |
+
"torch",
|
| 211 |
+
"transformers",
|
| 212 |
+
"datasets",
|
| 213 |
+
"accelerate",
|
| 214 |
+
"peft",
|
| 215 |
+
"bitsandbytes",
|
| 216 |
+
"scipy",
|
| 217 |
+
"scikit-learn"
|
| 218 |
+
]
|
| 219 |
+
|
| 220 |
+
print("📦 Installing required packages...")
|
| 221 |
+
for package in packages:
|
| 222 |
+
try:
|
| 223 |
+
subprocess.run([sys.executable, "-m", "pip", "install", package],
|
| 224 |
+
check=True, capture_output=True)
|
| 225 |
+
print(f"✅ {package} installed")
|
| 226 |
+
except subprocess.CalledProcessError:
|
| 227 |
+
print(f"⚠️ Failed to install {package}")
|
| 228 |
+
|
| 229 |
+
print("\n✅ Local training environment setup complete!")
|
| 230 |
+
|
| 231 |
+
def main():
|
| 232 |
+
print("🚀 Local Training Setup")
|
| 233 |
+
print("=" * 50)
|
| 234 |
+
|
| 235 |
+
# Check system requirements
|
| 236 |
+
if not check_system_requirements():
|
| 237 |
+
print("❌ System requirements not met")
|
| 238 |
+
return
|
| 239 |
+
|
| 240 |
+
# Setup training environment
|
| 241 |
+
setup_local_training_environment()
|
| 242 |
+
|
| 243 |
+
# Download small model
|
| 244 |
+
model_info = download_small_model()
|
| 245 |
+
if not model_info:
|
| 246 |
+
print("❌ Model download failed")
|
| 247 |
+
return
|
| 248 |
+
|
| 249 |
+
# Create training config
|
| 250 |
+
config_file = create_local_training_config(model_info)
|
| 251 |
+
|
| 252 |
+
print(f"\n🎉 Local Training Setup Complete!")
|
| 253 |
+
print("=" * 50)
|
| 254 |
+
print(f"📁 Model: {model_info['path']}")
|
| 255 |
+
print(f"⚙️ Config: {config_file}")
|
| 256 |
+
print(f"📊 Dataset: data/lora_dataset_20250829_113330.jsonl")
|
| 257 |
+
|
| 258 |
+
print(f"\n📋 Next steps:")
|
| 259 |
+
print("1. Review configuration: cat configs/local_training_*.yaml")
|
| 260 |
+
print("2. Start training: python scripts/finetune_lora.py")
|
| 261 |
+
print("3. Monitor training: tail -f logs/training.log")
|
| 262 |
+
|
| 263 |
+
print(f"\n💡 Tips for local training:")
|
| 264 |
+
print("- Use smaller batch sizes if you run out of memory")
|
| 265 |
+
print("- Enable gradient checkpointing for memory efficiency")
|
| 266 |
+
print("- Monitor GPU memory usage with nvidia-smi")
|
| 267 |
+
print("- Consider using mixed precision training")
|
| 268 |
+
|
| 269 |
+
if __name__ == "__main__":
|
| 270 |
+
main()
|
| 271 |
+
|
| 272 |
+
|
| 273 |
+
|
scripts/novita_ai_setup.py
ADDED
|
@@ -0,0 +1,256 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
Script untuk setup dan menggunakan Novita AI
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
import os
|
| 7 |
+
import sys
|
| 8 |
+
import requests
|
| 9 |
+
import json
|
| 10 |
+
from pathlib import Path
|
| 11 |
+
import logging
|
| 12 |
+
|
| 13 |
+
logging.basicConfig(level=logging.INFO)
|
| 14 |
+
logger = logging.getLogger(__name__)
|
| 15 |
+
|
| 16 |
+
class NovitaAIClient:
|
| 17 |
+
def __init__(self, api_key):
|
| 18 |
+
self.api_key = api_key
|
| 19 |
+
self.base_url = "https://api.novita.ai"
|
| 20 |
+
self.headers = {
|
| 21 |
+
"Authorization": f"Bearer {api_key}",
|
| 22 |
+
"Content-Type": "application/json"
|
| 23 |
+
}
|
| 24 |
+
|
| 25 |
+
def test_connection(self):
|
| 26 |
+
"""Test koneksi ke Novita AI API"""
|
| 27 |
+
try:
|
| 28 |
+
response = requests.get(
|
| 29 |
+
f"{self.base_url}/v1/models",
|
| 30 |
+
headers=self.headers
|
| 31 |
+
)
|
| 32 |
+
if response.status_code == 200:
|
| 33 |
+
logger.info("✅ Koneksi ke Novita AI berhasil!")
|
| 34 |
+
return True
|
| 35 |
+
else:
|
| 36 |
+
logger.error(f"❌ Error: {response.status_code} - {response.text}")
|
| 37 |
+
return False
|
| 38 |
+
except Exception as e:
|
| 39 |
+
logger.error(f"❌ Error koneksi: {e}")
|
| 40 |
+
return False
|
| 41 |
+
|
| 42 |
+
def get_available_models(self):
|
| 43 |
+
"""Dapatkan daftar model yang tersedia"""
|
| 44 |
+
try:
|
| 45 |
+
response = requests.get(
|
| 46 |
+
f"{self.base_url}/v1/models",
|
| 47 |
+
headers=self.headers
|
| 48 |
+
)
|
| 49 |
+
if response.status_code == 200:
|
| 50 |
+
models = response.json()
|
| 51 |
+
logger.info("📋 Model yang tersedia:")
|
| 52 |
+
for model in models.get('data', []):
|
| 53 |
+
logger.info(f" - {model.get('id', 'Unknown')}: {model.get('name', 'Unknown')}")
|
| 54 |
+
return models
|
| 55 |
+
else:
|
| 56 |
+
logger.error(f"❌ Error: {response.status_code}")
|
| 57 |
+
return None
|
| 58 |
+
except Exception as e:
|
| 59 |
+
logger.error(f"❌ Error: {e}")
|
| 60 |
+
return None
|
| 61 |
+
|
| 62 |
+
def create_fine_tuning_job(self, model_name, training_file, validation_file=None):
|
| 63 |
+
"""Buat fine-tuning job"""
|
| 64 |
+
try:
|
| 65 |
+
payload = {
|
| 66 |
+
"model": model_name,
|
| 67 |
+
"training_file": training_file,
|
| 68 |
+
"validation_file": validation_file,
|
| 69 |
+
"hyperparameters": {
|
| 70 |
+
"n_epochs": 3,
|
| 71 |
+
"batch_size": 4,
|
| 72 |
+
"learning_rate_multiplier": 1.0
|
| 73 |
+
}
|
| 74 |
+
}
|
| 75 |
+
|
| 76 |
+
response = requests.post(
|
| 77 |
+
f"{self.base_url}/v1/fine_tuning/jobs",
|
| 78 |
+
headers=self.headers,
|
| 79 |
+
json=payload
|
| 80 |
+
)
|
| 81 |
+
|
| 82 |
+
if response.status_code == 200:
|
| 83 |
+
job = response.json()
|
| 84 |
+
logger.info(f"✅ Fine-tuning job created: {job.get('id')}")
|
| 85 |
+
return job
|
| 86 |
+
else:
|
| 87 |
+
logger.error(f"❌ Error: {response.status_code} - {response.text}")
|
| 88 |
+
return None
|
| 89 |
+
|
| 90 |
+
except Exception as e:
|
| 91 |
+
logger.error(f"❌ Error: {e}")
|
| 92 |
+
return None
|
| 93 |
+
|
| 94 |
+
def list_fine_tuning_jobs(self):
|
| 95 |
+
"""List semua fine-tuning jobs"""
|
| 96 |
+
try:
|
| 97 |
+
response = requests.get(
|
| 98 |
+
f"{self.base_url}/v1/fine_tuning/jobs",
|
| 99 |
+
headers=self.headers
|
| 100 |
+
)
|
| 101 |
+
|
| 102 |
+
if response.status_code == 200:
|
| 103 |
+
jobs = response.json()
|
| 104 |
+
logger.info("📋 Fine-tuning jobs:")
|
| 105 |
+
for job in jobs.get('data', []):
|
| 106 |
+
status = job.get('status', 'unknown')
|
| 107 |
+
model = job.get('model', 'unknown')
|
| 108 |
+
job_id = job.get('id', 'unknown')
|
| 109 |
+
logger.info(f" - {job_id}: {model} ({status})")
|
| 110 |
+
return jobs
|
| 111 |
+
else:
|
| 112 |
+
logger.error(f"❌ Error: {response.status_code}")
|
| 113 |
+
return None
|
| 114 |
+
|
| 115 |
+
except Exception as e:
|
| 116 |
+
logger.error(f"❌ Error: {e}")
|
| 117 |
+
return None
|
| 118 |
+
|
| 119 |
+
def get_fine_tuning_job(self, job_id):
|
| 120 |
+
"""Dapatkan detail fine-tuning job"""
|
| 121 |
+
try:
|
| 122 |
+
response = requests.get(
|
| 123 |
+
f"{self.base_url}/v1/fine_tuning/jobs/{job_id}",
|
| 124 |
+
headers=self.headers
|
| 125 |
+
)
|
| 126 |
+
|
| 127 |
+
if response.status_code == 200:
|
| 128 |
+
job = response.json()
|
| 129 |
+
logger.info(f"📋 Job {job_id}:")
|
| 130 |
+
logger.info(f" Status: {job.get('status')}")
|
| 131 |
+
logger.info(f" Model: {job.get('model')}")
|
| 132 |
+
logger.info(f" Created: {job.get('created_at')}")
|
| 133 |
+
return job
|
| 134 |
+
else:
|
| 135 |
+
logger.error(f"❌ Error: {response.status_code}")
|
| 136 |
+
return None
|
| 137 |
+
|
| 138 |
+
except Exception as e:
|
| 139 |
+
logger.error(f"❌ Error: {e}")
|
| 140 |
+
return None
|
| 141 |
+
|
| 142 |
+
def setup_novita_environment():
|
| 143 |
+
"""Setup environment untuk Novita AI"""
|
| 144 |
+
print("🚀 Setup Novita AI Environment")
|
| 145 |
+
print("=" * 40)
|
| 146 |
+
|
| 147 |
+
# Check API key
|
| 148 |
+
api_key = os.getenv('NOVITA_API_KEY')
|
| 149 |
+
if not api_key:
|
| 150 |
+
print("⚠️ NOVITA_API_KEY tidak ditemukan")
|
| 151 |
+
api_key = input("Masukkan Novita AI API key: ").strip()
|
| 152 |
+
if api_key:
|
| 153 |
+
os.environ['NOVITA_API_KEY'] = api_key
|
| 154 |
+
else:
|
| 155 |
+
print("❌ API key diperlukan untuk melanjutkan")
|
| 156 |
+
return None
|
| 157 |
+
|
| 158 |
+
# Test connection
|
| 159 |
+
client = NovitaAIClient(api_key)
|
| 160 |
+
if not client.test_connection():
|
| 161 |
+
print("❌ Gagal koneksi ke Novita AI")
|
| 162 |
+
return None
|
| 163 |
+
|
| 164 |
+
return client
|
| 165 |
+
|
| 166 |
+
def create_sample_dataset():
|
| 167 |
+
"""Buat sample dataset untuk fine-tuning"""
|
| 168 |
+
data_dir = Path("data")
|
| 169 |
+
data_dir.mkdir(exist_ok=True)
|
| 170 |
+
|
| 171 |
+
# Sample data untuk fine-tuning
|
| 172 |
+
sample_data = [
|
| 173 |
+
{
|
| 174 |
+
"messages": [
|
| 175 |
+
{"role": "system", "content": "Anda adalah asisten AI yang membantu dengan pertanyaan dalam bahasa Indonesia."},
|
| 176 |
+
{"role": "user", "content": "Apa itu machine learning?"},
|
| 177 |
+
{"role": "assistant", "content": "Machine learning adalah cabang dari artificial intelligence yang memungkinkan komputer belajar dari data tanpa diprogram secara eksplisit."}
|
| 178 |
+
]
|
| 179 |
+
},
|
| 180 |
+
{
|
| 181 |
+
"messages": [
|
| 182 |
+
{"role": "system", "content": "Anda adalah asisten AI yang membantu dengan pertanyaan dalam bahasa Indonesia."},
|
| 183 |
+
{"role": "user", "content": "Jelaskan tentang deep learning"},
|
| 184 |
+
{"role": "assistant", "content": "Deep learning adalah subset dari machine learning yang menggunakan neural network dengan banyak layer untuk memproses data kompleks."}
|
| 185 |
+
]
|
| 186 |
+
},
|
| 187 |
+
{
|
| 188 |
+
"messages": [
|
| 189 |
+
{"role": "system", "content": "Anda adalah asisten AI yang membantu dengan pertanyaan dalam bahasa Indonesia."},
|
| 190 |
+
{"role": "user", "content": "Bagaimana cara kerja neural network?"},
|
| 191 |
+
{"role": "assistant", "content": "Neural network bekerja dengan menerima input, memproses melalui hidden layers, dan menghasilkan output berdasarkan weights yang telah dilatih."}
|
| 192 |
+
]
|
| 193 |
+
}
|
| 194 |
+
]
|
| 195 |
+
|
| 196 |
+
# Save sebagai JSONL
|
| 197 |
+
output_file = data_dir / "training_data.jsonl"
|
| 198 |
+
with open(output_file, 'w', encoding='utf-8') as f:
|
| 199 |
+
for item in sample_data:
|
| 200 |
+
json.dump(item, f, ensure_ascii=False)
|
| 201 |
+
f.write('\n')
|
| 202 |
+
|
| 203 |
+
print(f"✅ Sample dataset created: {output_file}")
|
| 204 |
+
return str(output_file)
|
| 205 |
+
|
| 206 |
+
def main():
|
| 207 |
+
print("🤖 Novita AI Setup & Fine-tuning")
|
| 208 |
+
print("=" * 50)
|
| 209 |
+
|
| 210 |
+
# Setup environment
|
| 211 |
+
client = setup_novita_environment()
|
| 212 |
+
if not client:
|
| 213 |
+
return
|
| 214 |
+
|
| 215 |
+
# Get available models
|
| 216 |
+
print("\n1️⃣ Getting available models...")
|
| 217 |
+
models = client.get_available_models()
|
| 218 |
+
|
| 219 |
+
# Create sample dataset
|
| 220 |
+
print("\n2️⃣ Creating sample dataset...")
|
| 221 |
+
training_file = create_sample_dataset()
|
| 222 |
+
|
| 223 |
+
# Show menu
|
| 224 |
+
while True:
|
| 225 |
+
print("\n📋 Menu:")
|
| 226 |
+
print("1. List fine-tuning jobs")
|
| 227 |
+
print("2. Create fine-tuning job")
|
| 228 |
+
print("3. Check job status")
|
| 229 |
+
print("4. Exit")
|
| 230 |
+
|
| 231 |
+
choice = input("\nPilihan (1-4): ").strip()
|
| 232 |
+
|
| 233 |
+
if choice == "1":
|
| 234 |
+
client.list_fine_tuning_jobs()
|
| 235 |
+
elif choice == "2":
|
| 236 |
+
if models and models.get('data'):
|
| 237 |
+
model_id = input("Masukkan model ID: ").strip()
|
| 238 |
+
job = client.create_fine_tuning_job(model_id, training_file)
|
| 239 |
+
if job:
|
| 240 |
+
print(f"✅ Job created: {job.get('id')}")
|
| 241 |
+
else:
|
| 242 |
+
print("❌ Tidak ada model tersedia")
|
| 243 |
+
elif choice == "3":
|
| 244 |
+
job_id = input("Masukkan job ID: ").strip()
|
| 245 |
+
client.get_fine_tuning_job(job_id)
|
| 246 |
+
elif choice == "4":
|
| 247 |
+
print("👋 Goodbye!")
|
| 248 |
+
break
|
| 249 |
+
else:
|
| 250 |
+
print("❌ Pilihan tidak valid")
|
| 251 |
+
|
| 252 |
+
if __name__ == "__main__":
|
| 253 |
+
main()
|
| 254 |
+
|
| 255 |
+
|
| 256 |
+
|
scripts/novita_ai_setup_v2.py
ADDED
|
@@ -0,0 +1,376 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
Script untuk setup dan menggunakan Novita AI (Updated Version)
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
import os
|
| 7 |
+
import sys
|
| 8 |
+
import requests
|
| 9 |
+
import json
|
| 10 |
+
from pathlib import Path
|
| 11 |
+
import logging
|
| 12 |
+
|
| 13 |
+
logging.basicConfig(level=logging.INFO)
|
| 14 |
+
logger = logging.getLogger(__name__)
|
| 15 |
+
|
| 16 |
+
class NovitaAIClient:
|
| 17 |
+
def __init__(self, api_key):
|
| 18 |
+
self.api_key = api_key
|
| 19 |
+
# Use correct Novita AI endpoint
|
| 20 |
+
self.possible_endpoints = [
|
| 21 |
+
"https://api.novita.ai/openai",
|
| 22 |
+
"https://api.novita.ai",
|
| 23 |
+
"https://api.novita.com/openai"
|
| 24 |
+
]
|
| 25 |
+
self.base_url = None
|
| 26 |
+
self.headers = {
|
| 27 |
+
"Authorization": f"Bearer {api_key}",
|
| 28 |
+
"Content-Type": "application/json"
|
| 29 |
+
}
|
| 30 |
+
|
| 31 |
+
def find_working_endpoint(self):
|
| 32 |
+
"""Find working API endpoint"""
|
| 33 |
+
for endpoint in self.possible_endpoints:
|
| 34 |
+
try:
|
| 35 |
+
logger.info(f"🔍 Testing endpoint: {endpoint}")
|
| 36 |
+
response = requests.get(
|
| 37 |
+
f"{endpoint}/v1/models",
|
| 38 |
+
headers=self.headers,
|
| 39 |
+
timeout=10
|
| 40 |
+
)
|
| 41 |
+
if response.status_code == 200:
|
| 42 |
+
self.base_url = endpoint
|
| 43 |
+
logger.info(f"✅ Working endpoint found: {endpoint}")
|
| 44 |
+
return True
|
| 45 |
+
else:
|
| 46 |
+
logger.info(f"⚠️ Endpoint {endpoint} returned {response.status_code}")
|
| 47 |
+
except Exception as e:
|
| 48 |
+
logger.info(f"❌ Endpoint {endpoint} failed: {e}")
|
| 49 |
+
continue
|
| 50 |
+
|
| 51 |
+
return False
|
| 52 |
+
|
| 53 |
+
def test_connection(self):
|
| 54 |
+
"""Test koneksi ke Novita AI API"""
|
| 55 |
+
if not self.find_working_endpoint():
|
| 56 |
+
logger.error("❌ Tidak ada endpoint yang berfungsi")
|
| 57 |
+
return False
|
| 58 |
+
|
| 59 |
+
try:
|
| 60 |
+
# Use OpenAI-compatible paths
|
| 61 |
+
test_paths = [
|
| 62 |
+
"/models",
|
| 63 |
+
"/v1/models",
|
| 64 |
+
"/chat/completions"
|
| 65 |
+
]
|
| 66 |
+
|
| 67 |
+
for path in test_paths:
|
| 68 |
+
try:
|
| 69 |
+
response = requests.get(
|
| 70 |
+
f"{self.base_url}{path}",
|
| 71 |
+
headers=self.headers,
|
| 72 |
+
timeout=10
|
| 73 |
+
)
|
| 74 |
+
if response.status_code == 200:
|
| 75 |
+
logger.info(f"✅ Koneksi ke Novita AI berhasil! Endpoint: {self.base_url}{path}")
|
| 76 |
+
return True
|
| 77 |
+
elif response.status_code == 401:
|
| 78 |
+
logger.error("❌ Unauthorized - API key mungkin salah")
|
| 79 |
+
return False
|
| 80 |
+
elif response.status_code == 404:
|
| 81 |
+
logger.info(f"⚠️ Path {path} tidak ditemukan, mencoba yang lain...")
|
| 82 |
+
continue
|
| 83 |
+
else:
|
| 84 |
+
logger.info(f"⚠️ Endpoint {path} returned {response.status_code}")
|
| 85 |
+
except Exception as e:
|
| 86 |
+
logger.info(f"⚠️ Path {path} failed: {e}")
|
| 87 |
+
continue
|
| 88 |
+
|
| 89 |
+
logger.error("❌ Tidak ada endpoint yang berfungsi")
|
| 90 |
+
return False
|
| 91 |
+
|
| 92 |
+
except Exception as e:
|
| 93 |
+
logger.error(f"❌ Error koneksi: {e}")
|
| 94 |
+
return False
|
| 95 |
+
|
| 96 |
+
def get_available_models(self):
|
| 97 |
+
"""Dapatkan daftar model yang tersedia"""
|
| 98 |
+
if not self.base_url:
|
| 99 |
+
logger.error("❌ Base URL belum diset")
|
| 100 |
+
return None
|
| 101 |
+
|
| 102 |
+
try:
|
| 103 |
+
# Use OpenAI-compatible model endpoints
|
| 104 |
+
model_paths = [
|
| 105 |
+
"/models",
|
| 106 |
+
"/v1/models"
|
| 107 |
+
]
|
| 108 |
+
|
| 109 |
+
for path in model_paths:
|
| 110 |
+
try:
|
| 111 |
+
response = requests.get(
|
| 112 |
+
f"{self.base_url}{path}",
|
| 113 |
+
headers=self.headers,
|
| 114 |
+
timeout=10
|
| 115 |
+
)
|
| 116 |
+
if response.status_code == 200:
|
| 117 |
+
models = response.json()
|
| 118 |
+
logger.info("📋 Model yang tersedia:")
|
| 119 |
+
if isinstance(models, dict) and 'data' in models:
|
| 120 |
+
for model in models['data']:
|
| 121 |
+
logger.info(f" - {model.get('id', 'Unknown')}: {model.get('name', 'Unknown')}")
|
| 122 |
+
elif isinstance(models, list):
|
| 123 |
+
for model in models:
|
| 124 |
+
logger.info(f" - {model.get('id', 'Unknown')}: {model.get('name', 'Unknown')}")
|
| 125 |
+
else:
|
| 126 |
+
logger.info(f" Response format: {type(models)}")
|
| 127 |
+
logger.info(f" Content: {models}")
|
| 128 |
+
return models
|
| 129 |
+
else:
|
| 130 |
+
logger.info(f"⚠️ Path {path} returned {response.status_code}")
|
| 131 |
+
except Exception as e:
|
| 132 |
+
logger.info(f"⚠️ Path {path} failed: {e}")
|
| 133 |
+
continue
|
| 134 |
+
|
| 135 |
+
logger.error("❌ Tidak bisa mendapatkan daftar model")
|
| 136 |
+
return None
|
| 137 |
+
|
| 138 |
+
except Exception as e:
|
| 139 |
+
logger.error(f"❌ Error: {e}")
|
| 140 |
+
return None
|
| 141 |
+
|
| 142 |
+
def create_fine_tuning_job(self, model_name, training_file, validation_file=None):
|
| 143 |
+
"""Buat fine-tuning job"""
|
| 144 |
+
if not self.base_url:
|
| 145 |
+
logger.error("❌ Base URL belum diset")
|
| 146 |
+
return None
|
| 147 |
+
|
| 148 |
+
try:
|
| 149 |
+
payload = {
|
| 150 |
+
"model": model_name,
|
| 151 |
+
"training_file": training_file,
|
| 152 |
+
"validation_file": validation_file,
|
| 153 |
+
"hyperparameters": {
|
| 154 |
+
"n_epochs": 3,
|
| 155 |
+
"batch_size": 4,
|
| 156 |
+
"learning_rate_multiplier": 1.0
|
| 157 |
+
}
|
| 158 |
+
}
|
| 159 |
+
|
| 160 |
+
# Use OpenAI-compatible fine-tuning endpoints
|
| 161 |
+
ft_paths = [
|
| 162 |
+
"/fine_tuning/jobs",
|
| 163 |
+
"/v1/fine_tuning/jobs"
|
| 164 |
+
]
|
| 165 |
+
|
| 166 |
+
for path in ft_paths:
|
| 167 |
+
try:
|
| 168 |
+
response = requests.post(
|
| 169 |
+
f"{self.base_url}{path}",
|
| 170 |
+
headers=self.headers,
|
| 171 |
+
json=payload,
|
| 172 |
+
timeout=30
|
| 173 |
+
)
|
| 174 |
+
|
| 175 |
+
if response.status_code == 200:
|
| 176 |
+
job = response.json()
|
| 177 |
+
logger.info(f"✅ Fine-tuning job created: {job.get('id')}")
|
| 178 |
+
return job
|
| 179 |
+
elif response.status_code == 404:
|
| 180 |
+
logger.info(f"⚠️ Path {path} tidak ditemukan, mencoba yang lain...")
|
| 181 |
+
continue
|
| 182 |
+
else:
|
| 183 |
+
logger.error(f"❌ Error: {response.status_code} - {response.text}")
|
| 184 |
+
continue
|
| 185 |
+
|
| 186 |
+
except Exception as e:
|
| 187 |
+
logger.info(f"⚠️ Path {path} failed: {e}")
|
| 188 |
+
continue
|
| 189 |
+
|
| 190 |
+
logger.error("❌ Tidak bisa membuat fine-tuning job")
|
| 191 |
+
return None
|
| 192 |
+
|
| 193 |
+
except Exception as e:
|
| 194 |
+
logger.error(f"❌ Error: {e}")
|
| 195 |
+
return None
|
| 196 |
+
|
| 197 |
+
def list_fine_tuning_jobs(self):
|
| 198 |
+
"""List semua fine-tuning jobs"""
|
| 199 |
+
if not self.base_url:
|
| 200 |
+
logger.error("❌ Base URL belum diset")
|
| 201 |
+
return None
|
| 202 |
+
|
| 203 |
+
try:
|
| 204 |
+
# Use OpenAI-compatible job listing endpoints
|
| 205 |
+
job_paths = [
|
| 206 |
+
"/fine_tuning/jobs",
|
| 207 |
+
"/v1/fine_tuning/jobs"
|
| 208 |
+
]
|
| 209 |
+
|
| 210 |
+
for path in job_paths:
|
| 211 |
+
try:
|
| 212 |
+
response = requests.get(
|
| 213 |
+
f"{self.base_url}{path}",
|
| 214 |
+
headers=self.headers,
|
| 215 |
+
timeout=10
|
| 216 |
+
)
|
| 217 |
+
|
| 218 |
+
if response.status_code == 200:
|
| 219 |
+
jobs = response.json()
|
| 220 |
+
logger.info("📋 Fine-tuning jobs:")
|
| 221 |
+
if isinstance(jobs, dict) and 'data' in jobs:
|
| 222 |
+
for job in jobs['data']:
|
| 223 |
+
status = job.get('status', 'unknown')
|
| 224 |
+
model = job.get('model', 'unknown')
|
| 225 |
+
job_id = job.get('id', 'unknown')
|
| 226 |
+
logger.info(f" - {job_id}: {model} ({status})")
|
| 227 |
+
elif isinstance(jobs, list):
|
| 228 |
+
for job in jobs:
|
| 229 |
+
status = job.get('status', 'unknown')
|
| 230 |
+
model = job.get('model', 'unknown')
|
| 231 |
+
job_id = job.get('id', 'unknown')
|
| 232 |
+
logger.info(f" - {job_id}: {model} ({status})")
|
| 233 |
+
else:
|
| 234 |
+
logger.info(f" Response format: {type(jobs)}")
|
| 235 |
+
logger.info(f" Content: {jobs}")
|
| 236 |
+
return jobs
|
| 237 |
+
elif response.status_code == 404:
|
| 238 |
+
logger.info(f"⚠️ Path {path} tidak ditemukan, mencoba yang lain...")
|
| 239 |
+
continue
|
| 240 |
+
else:
|
| 241 |
+
logger.error(f"❌ Error: {response.status_code}")
|
| 242 |
+
continue
|
| 243 |
+
|
| 244 |
+
except Exception as e:
|
| 245 |
+
logger.info(f"⚠️ Path {path} failed: {e}")
|
| 246 |
+
continue
|
| 247 |
+
|
| 248 |
+
logger.error("❌ Tidak bisa mendapatkan daftar jobs")
|
| 249 |
+
return None
|
| 250 |
+
|
| 251 |
+
except Exception as e:
|
| 252 |
+
logger.error(f"❌ Error: {e}")
|
| 253 |
+
return None
|
| 254 |
+
|
| 255 |
+
def setup_novita_environment():
|
| 256 |
+
"""Setup environment untuk Novita AI"""
|
| 257 |
+
print("🚀 Setup Novita AI Environment")
|
| 258 |
+
print("=" * 40)
|
| 259 |
+
|
| 260 |
+
# Check API key
|
| 261 |
+
api_key = os.getenv('NOVITA_API_KEY')
|
| 262 |
+
if not api_key:
|
| 263 |
+
print("⚠️ NOVITA_API_KEY tidak ditemukan")
|
| 264 |
+
api_key = input("Masukkan Novita AI API key: ").strip()
|
| 265 |
+
if api_key:
|
| 266 |
+
os.environ['NOVITA_API_KEY'] = api_key
|
| 267 |
+
else:
|
| 268 |
+
print("❌ API key diperlukan untuk melanjutkan")
|
| 269 |
+
return None
|
| 270 |
+
|
| 271 |
+
# Test connection
|
| 272 |
+
client = NovitaAIClient(api_key)
|
| 273 |
+
if not client.test_connection():
|
| 274 |
+
print("❌ Gagal koneksi ke Novita AI")
|
| 275 |
+
print("💡 Tips:")
|
| 276 |
+
print("- Pastikan API key benar")
|
| 277 |
+
print("- Cek koneksi internet")
|
| 278 |
+
print("- Cek dokumentasi Novita AI untuk endpoint yang benar")
|
| 279 |
+
return None
|
| 280 |
+
|
| 281 |
+
return client
|
| 282 |
+
|
| 283 |
+
def create_sample_dataset():
|
| 284 |
+
"""Gunakan dataset yang sudah ada atau buat yang baru jika tidak ada"""
|
| 285 |
+
data_dir = Path("data")
|
| 286 |
+
data_dir.mkdir(exist_ok=True)
|
| 287 |
+
|
| 288 |
+
# Cek apakah dataset sudah ada
|
| 289 |
+
existing_dataset = data_dir / "lora_dataset_20250829_113330.jsonl"
|
| 290 |
+
if existing_dataset.exists():
|
| 291 |
+
print(f"✅ Dataset sudah ada: {existing_dataset}")
|
| 292 |
+
print(f"📊 File size: {existing_dataset.stat().st_size / 1024:.2f} KB")
|
| 293 |
+
return str(existing_dataset)
|
| 294 |
+
|
| 295 |
+
# Jika tidak ada, buat sample dataset
|
| 296 |
+
print("⚠️ Dataset tidak ditemukan, membuat sample dataset...")
|
| 297 |
+
sample_data = [
|
| 298 |
+
{
|
| 299 |
+
"messages": [
|
| 300 |
+
{"role": "system", "content": "Anda adalah asisten AI yang membantu dengan pertanyaan dalam bahasa Indonesia."},
|
| 301 |
+
{"role": "user", "content": "Apa itu machine learning?"},
|
| 302 |
+
{"role": "assistant", "content": "Machine learning adalah cabang dari artificial intelligence yang memungkinkan komputer belajar dari data tanpa diprogram secara eksplisit."}
|
| 303 |
+
]
|
| 304 |
+
},
|
| 305 |
+
{
|
| 306 |
+
"messages": [
|
| 307 |
+
{"role": "system", "content": "Anda adalah asisten AI yang membantu dengan pertanyaan dalam bahasa Indonesia."},
|
| 308 |
+
{"role": "user", "content": "Jelaskan tentang deep learning"},
|
| 309 |
+
{"role": "assistant", "content": "Deep learning adalah subset dari machine learning yang menggunakan neural network dengan banyak layer untuk memproses data kompleks."}
|
| 310 |
+
]
|
| 311 |
+
}
|
| 312 |
+
]
|
| 313 |
+
|
| 314 |
+
# Save sebagai JSONL
|
| 315 |
+
output_file = data_dir / "training_data.jsonl"
|
| 316 |
+
with open(output_file, 'w', encoding='utf-8') as f:
|
| 317 |
+
for item in sample_data:
|
| 318 |
+
json.dump(item, f, ensure_ascii=False)
|
| 319 |
+
f.write('\n')
|
| 320 |
+
|
| 321 |
+
print(f"✅ Sample dataset created: {output_file}")
|
| 322 |
+
return str(output_file)
|
| 323 |
+
|
| 324 |
+
def main():
|
| 325 |
+
print("🤖 Novita AI Setup & Fine-tuning (Updated)")
|
| 326 |
+
print("=" * 50)
|
| 327 |
+
|
| 328 |
+
# Setup environment
|
| 329 |
+
client = setup_novita_environment()
|
| 330 |
+
if not client:
|
| 331 |
+
return
|
| 332 |
+
|
| 333 |
+
# Get available models
|
| 334 |
+
print("\n1️⃣ Getting available models...")
|
| 335 |
+
models = client.get_available_models()
|
| 336 |
+
|
| 337 |
+
# Create sample dataset
|
| 338 |
+
print("\n2️⃣ Creating sample dataset...")
|
| 339 |
+
training_file = create_sample_dataset()
|
| 340 |
+
|
| 341 |
+
# Show menu
|
| 342 |
+
while True:
|
| 343 |
+
print("\n📋 Menu:")
|
| 344 |
+
print("1. List fine-tuning jobs")
|
| 345 |
+
print("2. Create fine-tuning job")
|
| 346 |
+
print("3. Check job status")
|
| 347 |
+
print("4. Test API endpoints")
|
| 348 |
+
print("5. Exit")
|
| 349 |
+
|
| 350 |
+
choice = input("\nPilihan (1-5): ").strip()
|
| 351 |
+
|
| 352 |
+
if choice == "1":
|
| 353 |
+
client.list_fine_tuning_jobs()
|
| 354 |
+
elif choice == "2":
|
| 355 |
+
if models:
|
| 356 |
+
model_id = input("Masukkan model ID: ").strip()
|
| 357 |
+
job = client.create_fine_tuning_job(model_id, training_file)
|
| 358 |
+
if job:
|
| 359 |
+
print(f"✅ Job created: {job.get('id')}")
|
| 360 |
+
else:
|
| 361 |
+
print("❌ Tidak ada model tersedia")
|
| 362 |
+
elif choice == "3":
|
| 363 |
+
job_id = input("Masukkan job ID: ").strip()
|
| 364 |
+
# This would need to be implemented based on actual API
|
| 365 |
+
print("⚠️ Check job status belum diimplementasikan")
|
| 366 |
+
elif choice == "4":
|
| 367 |
+
print("🔍 Testing API endpoints...")
|
| 368 |
+
client.test_connection()
|
| 369 |
+
elif choice == "5":
|
| 370 |
+
print("👋 Goodbye!")
|
| 371 |
+
break
|
| 372 |
+
else:
|
| 373 |
+
print("❌ Pilihan tidak valid")
|
| 374 |
+
|
| 375 |
+
if __name__ == "__main__":
|
| 376 |
+
main()
|
scripts/run_novita_finetuning.py
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
Script sederhana untuk menjalankan fine-tuning Novita AI
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
import os
|
| 7 |
+
import sys
|
| 8 |
+
from pathlib import Path
|
| 9 |
+
|
| 10 |
+
# Import NovitaAIClient dari script yang sudah ada
|
| 11 |
+
sys.path.append('scripts')
|
| 12 |
+
from novita_ai_setup_v2 import NovitaAIClient, create_sample_dataset
|
| 13 |
+
|
| 14 |
+
def main():
|
| 15 |
+
print("🚀 Novita AI Fine-tuning - Auto Run")
|
| 16 |
+
print("=" * 50)
|
| 17 |
+
|
| 18 |
+
# Check environment variables
|
| 19 |
+
api_key = os.getenv('NOVITA_API_KEY')
|
| 20 |
+
if not api_key:
|
| 21 |
+
print("❌ NOVITA_API_KEY tidak ditemukan")
|
| 22 |
+
print("Silakan set: export NOVITA_API_KEY='your_key'")
|
| 23 |
+
return
|
| 24 |
+
|
| 25 |
+
base_url = os.getenv('NOVITA_BASE_URL', 'https://api.novita.ai/openai')
|
| 26 |
+
print(f"🔑 API Key: {api_key[:10]}...{api_key[-10:]}")
|
| 27 |
+
print(f"🌐 Base URL: {base_url}")
|
| 28 |
+
|
| 29 |
+
# Create client
|
| 30 |
+
client = NovitaAIClient(api_key)
|
| 31 |
+
client.base_url = base_url
|
| 32 |
+
|
| 33 |
+
# Test connection
|
| 34 |
+
print("\n1️⃣ Testing connection...")
|
| 35 |
+
if not client.test_connection():
|
| 36 |
+
print("❌ Koneksi gagal")
|
| 37 |
+
return
|
| 38 |
+
|
| 39 |
+
# Get available models
|
| 40 |
+
print("\n2️⃣ Getting available models...")
|
| 41 |
+
models = client.get_available_models()
|
| 42 |
+
|
| 43 |
+
if not models:
|
| 44 |
+
print("❌ Tidak bisa mendapatkan daftar model")
|
| 45 |
+
return
|
| 46 |
+
|
| 47 |
+
# Select model automatically (Llama 3.2 1B Instruct if available)
|
| 48 |
+
selected_model = None
|
| 49 |
+
preferred_models = [
|
| 50 |
+
"meta-llama/llama-3.2-1b-instruct",
|
| 51 |
+
"meta-llama/llama-3.2-3b-instruct",
|
| 52 |
+
"qwen/qwen3-4b-fp8",
|
| 53 |
+
"qwen/qwen3-8b-fp8"
|
| 54 |
+
]
|
| 55 |
+
|
| 56 |
+
print("\n🎯 Selecting model...")
|
| 57 |
+
for preferred in preferred_models:
|
| 58 |
+
if isinstance(models, dict) and 'data' in models:
|
| 59 |
+
for model in models['data']:
|
| 60 |
+
if model.get('id') == preferred:
|
| 61 |
+
selected_model = preferred
|
| 62 |
+
print(f"✅ Selected: {preferred}")
|
| 63 |
+
break
|
| 64 |
+
elif isinstance(models, list):
|
| 65 |
+
for model in models:
|
| 66 |
+
if model.get('id') == preferred:
|
| 67 |
+
selected_model = preferred
|
| 68 |
+
print(f"✅ Selected: {preferred}")
|
| 69 |
+
break
|
| 70 |
+
|
| 71 |
+
if selected_model:
|
| 72 |
+
break
|
| 73 |
+
|
| 74 |
+
if not selected_model:
|
| 75 |
+
# Fallback to first available model
|
| 76 |
+
if isinstance(models, dict) and 'data' in models and models['data']:
|
| 77 |
+
selected_model = models['data'][0].get('id')
|
| 78 |
+
elif isinstance(models, list) and models:
|
| 79 |
+
selected_model = models[0].get('id')
|
| 80 |
+
|
| 81 |
+
if selected_model:
|
| 82 |
+
print(f"⚠️ Fallback to: {selected_model}")
|
| 83 |
+
else:
|
| 84 |
+
print("❌ Tidak ada model yang tersedia")
|
| 85 |
+
return
|
| 86 |
+
|
| 87 |
+
# Create dataset
|
| 88 |
+
print("\n3️⃣ Preparing dataset...")
|
| 89 |
+
training_file = create_sample_dataset()
|
| 90 |
+
|
| 91 |
+
# Create fine-tuning job
|
| 92 |
+
print(f"\n4️⃣ Creating fine-tuning job...")
|
| 93 |
+
print(f" Model: {selected_model}")
|
| 94 |
+
print(f" Training file: {training_file}")
|
| 95 |
+
|
| 96 |
+
job = client.create_fine_tuning_job(selected_model, training_file)
|
| 97 |
+
|
| 98 |
+
if job:
|
| 99 |
+
print(f"\n✅ Fine-tuning job created successfully!")
|
| 100 |
+
print(f" Job ID: {job.get('id')}")
|
| 101 |
+
print(f" Status: {job.get('status', 'unknown')}")
|
| 102 |
+
print(f" Model: {job.get('model', 'unknown')}")
|
| 103 |
+
|
| 104 |
+
print(f"\n📋 Next steps:")
|
| 105 |
+
print(f"1. Monitor job status")
|
| 106 |
+
print(f"2. Check logs for progress")
|
| 107 |
+
print(f"3. Download fine-tuned model when complete")
|
| 108 |
+
|
| 109 |
+
else:
|
| 110 |
+
print("\n❌ Failed to create fine-tuning job")
|
| 111 |
+
print("💡 Check the error messages above")
|
| 112 |
+
|
| 113 |
+
if __name__ == "__main__":
|
| 114 |
+
main()
|
| 115 |
+
|
| 116 |
+
|
| 117 |
+
|
scripts/test_model.py
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
Script untuk testing model yang sudah di-fine-tune
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
import os
|
| 7 |
+
import sys
|
| 8 |
+
import yaml
|
| 9 |
+
import torch
|
| 10 |
+
from pathlib import Path
|
| 11 |
+
from transformers import AutoTokenizer, AutoModelForCausalLM
|
| 12 |
+
from peft import PeftModel
|
| 13 |
+
import logging
|
| 14 |
+
|
| 15 |
+
logging.basicConfig(level=logging.INFO)
|
| 16 |
+
logger = logging.getLogger(__name__)
|
| 17 |
+
|
| 18 |
+
def load_finetuned_model(model_path, lora_weights_path):
|
| 19 |
+
"""Load fine-tuned model with LoRA weights"""
|
| 20 |
+
logger.info(f"Loading base model from: {model_path}")
|
| 21 |
+
|
| 22 |
+
# Load base model
|
| 23 |
+
model = AutoModelForCausalLM.from_pretrained(
|
| 24 |
+
model_path,
|
| 25 |
+
torch_dtype=torch.float16,
|
| 26 |
+
device_map="auto",
|
| 27 |
+
trust_remote_code=True
|
| 28 |
+
)
|
| 29 |
+
|
| 30 |
+
# Load LoRA weights
|
| 31 |
+
logger.info(f"Loading LoRA weights from: {lora_weights_path}")
|
| 32 |
+
model = PeftModel.from_pretrained(model, lora_weights_path)
|
| 33 |
+
|
| 34 |
+
# Load tokenizer
|
| 35 |
+
tokenizer = AutoTokenizer.from_pretrained(
|
| 36 |
+
model_path,
|
| 37 |
+
trust_remote_code=True
|
| 38 |
+
)
|
| 39 |
+
|
| 40 |
+
if tokenizer.pad_token is None:
|
| 41 |
+
tokenizer.pad_token = tokenizer.eos_token
|
| 42 |
+
|
| 43 |
+
return model, tokenizer
|
| 44 |
+
|
| 45 |
+
def generate_response(model, tokenizer, prompt, max_length=512):
|
| 46 |
+
"""Generate response from the model"""
|
| 47 |
+
inputs = tokenizer(prompt, return_tensors="pt").to(model.device)
|
| 48 |
+
|
| 49 |
+
with torch.no_grad():
|
| 50 |
+
outputs = model.generate(
|
| 51 |
+
**inputs,
|
| 52 |
+
max_length=max_length,
|
| 53 |
+
temperature=0.7,
|
| 54 |
+
top_p=0.9,
|
| 55 |
+
top_k=40,
|
| 56 |
+
repetition_penalty=1.1,
|
| 57 |
+
do_sample=True,
|
| 58 |
+
pad_token_id=tokenizer.eos_token_id
|
| 59 |
+
)
|
| 60 |
+
|
| 61 |
+
response = tokenizer.decode(outputs[0], skip_special_tokens=True)
|
| 62 |
+
return response
|
| 63 |
+
|
| 64 |
+
def interactive_test(model, tokenizer):
|
| 65 |
+
"""Interactive testing mode"""
|
| 66 |
+
print("🤖 Interactive Testing Mode")
|
| 67 |
+
print("Type 'quit' to exit")
|
| 68 |
+
print("-" * 50)
|
| 69 |
+
|
| 70 |
+
while True:
|
| 71 |
+
try:
|
| 72 |
+
user_input = input("\n👤 You: ").strip()
|
| 73 |
+
|
| 74 |
+
if user_input.lower() in ['quit', 'exit', 'q']:
|
| 75 |
+
print("👋 Goodbye!")
|
| 76 |
+
break
|
| 77 |
+
|
| 78 |
+
if not user_input:
|
| 79 |
+
continue
|
| 80 |
+
|
| 81 |
+
print("\n🤖 Assistant: ", end="")
|
| 82 |
+
response = generate_response(model, tokenizer, user_input)
|
| 83 |
+
|
| 84 |
+
# Extract only the generated part (remove input)
|
| 85 |
+
if user_input in response:
|
| 86 |
+
generated_part = response.split(user_input)[-1].strip()
|
| 87 |
+
print(generated_part)
|
| 88 |
+
else:
|
| 89 |
+
print(response)
|
| 90 |
+
|
| 91 |
+
except KeyboardInterrupt:
|
| 92 |
+
print("\n👋 Goodbye!")
|
| 93 |
+
break
|
| 94 |
+
except Exception as e:
|
| 95 |
+
logger.error(f"Error generating response: {e}")
|
| 96 |
+
print(f"❌ Error: {e}")
|
| 97 |
+
|
| 98 |
+
def batch_test(model, tokenizer, test_cases):
|
| 99 |
+
"""Batch testing with predefined test cases"""
|
| 100 |
+
print("🧪 Batch Testing Mode")
|
| 101 |
+
print("=" * 50)
|
| 102 |
+
|
| 103 |
+
for i, test_case in enumerate(test_cases, 1):
|
| 104 |
+
print(f"\n📝 Test Case {i}: {test_case['prompt']}")
|
| 105 |
+
print("-" * 40)
|
| 106 |
+
|
| 107 |
+
try:
|
| 108 |
+
response = generate_response(model, tokenizer, test_case['prompt'])
|
| 109 |
+
print(f"🤖 Response: {response}")
|
| 110 |
+
|
| 111 |
+
if 'expected' in test_case:
|
| 112 |
+
print(f"🎯 Expected: {test_case['expected']}")
|
| 113 |
+
|
| 114 |
+
except Exception as e:
|
| 115 |
+
logger.error(f"Error in test case {i}: {e}")
|
| 116 |
+
print(f"❌ Error: {e}")
|
| 117 |
+
|
| 118 |
+
def main():
|
| 119 |
+
print("🧪 Model Testing - Fine-tuned Llama 3.1 8B")
|
| 120 |
+
print("=" * 50)
|
| 121 |
+
|
| 122 |
+
# Check if model exists
|
| 123 |
+
base_model_path = "models/llama-3.1-8b-instruct"
|
| 124 |
+
lora_weights_path = "models/finetuned-llama-lora"
|
| 125 |
+
|
| 126 |
+
if not os.path.exists(base_model_path):
|
| 127 |
+
print(f"❌ Base model tidak ditemukan: {base_model_path}")
|
| 128 |
+
print("Jalankan download_model.py terlebih dahulu")
|
| 129 |
+
sys.exit(1)
|
| 130 |
+
|
| 131 |
+
if not os.path.exists(lora_weights_path):
|
| 132 |
+
print(f"⚠️ LoRA weights tidak ditemukan: {lora_weights_path}")
|
| 133 |
+
print("Model akan menggunakan base model tanpa fine-tuning")
|
| 134 |
+
lora_weights_path = None
|
| 135 |
+
|
| 136 |
+
try:
|
| 137 |
+
# Load model
|
| 138 |
+
print("1️⃣ Loading model...")
|
| 139 |
+
if lora_weights_path:
|
| 140 |
+
model, tokenizer = load_finetuned_model(base_model_path, lora_weights_path)
|
| 141 |
+
else:
|
| 142 |
+
from transformers import AutoTokenizer, AutoModelForCausalLM
|
| 143 |
+
model = AutoModelForCausalLM.from_pretrained(
|
| 144 |
+
base_model_path,
|
| 145 |
+
torch_dtype=torch.float16,
|
| 146 |
+
device_map="auto",
|
| 147 |
+
trust_remote_code=True
|
| 148 |
+
)
|
| 149 |
+
tokenizer = AutoTokenizer.from_pretrained(
|
| 150 |
+
base_model_path,
|
| 151 |
+
trust_remote_code=True
|
| 152 |
+
)
|
| 153 |
+
|
| 154 |
+
print("✅ Model loaded successfully!")
|
| 155 |
+
|
| 156 |
+
# Test cases
|
| 157 |
+
test_cases = [
|
| 158 |
+
{
|
| 159 |
+
"prompt": "Apa itu machine learning?",
|
| 160 |
+
"expected": "Penjelasan tentang machine learning"
|
| 161 |
+
},
|
| 162 |
+
{
|
| 163 |
+
"prompt": "Jelaskan tentang deep learning dalam bahasa Indonesia",
|
| 164 |
+
"expected": "Penjelasan tentang deep learning"
|
| 165 |
+
},
|
| 166 |
+
{
|
| 167 |
+
"prompt": "Buat puisi tentang teknologi",
|
| 168 |
+
"expected": "Puisi tentang teknologi"
|
| 169 |
+
}
|
| 170 |
+
]
|
| 171 |
+
|
| 172 |
+
# Choose testing mode
|
| 173 |
+
print("\n2️⃣ Pilih mode testing:")
|
| 174 |
+
print("1. Interactive mode (chat)")
|
| 175 |
+
print("2. Batch testing")
|
| 176 |
+
print("3. Custom prompt")
|
| 177 |
+
|
| 178 |
+
choice = input("\nPilihan (1-3): ").strip()
|
| 179 |
+
|
| 180 |
+
if choice == "1":
|
| 181 |
+
interactive_test(model, tokenizer)
|
| 182 |
+
elif choice == "2":
|
| 183 |
+
batch_test(model, tokenizer, test_cases)
|
| 184 |
+
elif choice == "3":
|
| 185 |
+
custom_prompt = input("Masukkan prompt custom: ").strip()
|
| 186 |
+
if custom_prompt:
|
| 187 |
+
response = generate_response(model, tokenizer, custom_prompt)
|
| 188 |
+
print(f"\n🤖 Response: {response}")
|
| 189 |
+
else:
|
| 190 |
+
print("❌ Pilihan tidak valid")
|
| 191 |
+
|
| 192 |
+
except Exception as e:
|
| 193 |
+
logger.error(f"Error: {e}")
|
| 194 |
+
print(f"❌ Error loading model: {e}")
|
| 195 |
+
|
| 196 |
+
if __name__ == "__main__":
|
| 197 |
+
main()
|
| 198 |
+
|
| 199 |
+
|
| 200 |
+
|
| 201 |
+
|
scripts/test_novita_connection.py
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
Simple script untuk test koneksi Novita AI
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
import os
|
| 7 |
+
import requests
|
| 8 |
+
import json
|
| 9 |
+
|
| 10 |
+
def test_novita_connection():
|
| 11 |
+
"""Test koneksi ke Novita AI dengan berbagai cara"""
|
| 12 |
+
|
| 13 |
+
api_key = os.getenv('NOVITA_API_KEY')
|
| 14 |
+
if not api_key:
|
| 15 |
+
print("❌ NOVITA_API_KEY tidak ditemukan")
|
| 16 |
+
return
|
| 17 |
+
|
| 18 |
+
print(f"🔑 API Key: {api_key[:10]}...{api_key[-10:]}")
|
| 19 |
+
print("🔍 Testing koneksi ke Novita AI...")
|
| 20 |
+
|
| 21 |
+
# Test different possible endpoints
|
| 22 |
+
endpoints_to_test = [
|
| 23 |
+
"https://api.novita.ai",
|
| 24 |
+
"https://api.novita.com",
|
| 25 |
+
"https://novita.ai/api",
|
| 26 |
+
"https://novita.com/api",
|
| 27 |
+
"https://api.novita.ai/v1",
|
| 28 |
+
"https://api.novita.com/v1",
|
| 29 |
+
"https://novita.ai/api/v1",
|
| 30 |
+
"https://novita.com/api/v1"
|
| 31 |
+
]
|
| 32 |
+
|
| 33 |
+
headers = {
|
| 34 |
+
"Authorization": f"Bearer {api_key}",
|
| 35 |
+
"Content-Type": "application/json"
|
| 36 |
+
}
|
| 37 |
+
|
| 38 |
+
working_endpoints = []
|
| 39 |
+
|
| 40 |
+
for endpoint in endpoints_to_test:
|
| 41 |
+
print(f"\n🔍 Testing: {endpoint}")
|
| 42 |
+
|
| 43 |
+
# Test basic connectivity
|
| 44 |
+
try:
|
| 45 |
+
# Test GET request
|
| 46 |
+
response = requests.get(f"{endpoint}/models", headers=headers, timeout=10)
|
| 47 |
+
print(f" GET /models: {response.status_code}")
|
| 48 |
+
|
| 49 |
+
if response.status_code == 200:
|
| 50 |
+
print(f" ✅ Success! Response: {response.text[:200]}...")
|
| 51 |
+
working_endpoints.append(endpoint)
|
| 52 |
+
elif response.status_code == 401:
|
| 53 |
+
print(f" ⚠️ Unauthorized - API key mungkin salah")
|
| 54 |
+
elif response.status_code == 404:
|
| 55 |
+
print(f" ⚠️ Not Found - Endpoint tidak ada")
|
| 56 |
+
else:
|
| 57 |
+
print(f" ⚠️ Status: {response.status_code}")
|
| 58 |
+
|
| 59 |
+
except requests.exceptions.ConnectionError as e:
|
| 60 |
+
print(f" ❌ Connection Error: {e}")
|
| 61 |
+
except requests.exceptions.Timeout as e:
|
| 62 |
+
print(f" ⏰ Timeout: {e}")
|
| 63 |
+
except Exception as e:
|
| 64 |
+
print(f" ❌ Error: {e}")
|
| 65 |
+
|
| 66 |
+
# Test POST request
|
| 67 |
+
try:
|
| 68 |
+
test_data = {"test": "connection"}
|
| 69 |
+
response = requests.post(f"{endpoint}/test", headers=headers, json=test_data, timeout=10)
|
| 70 |
+
print(f" POST /test: {response.status_code}")
|
| 71 |
+
except Exception as e:
|
| 72 |
+
print(f" ❌ POST Error: {e}")
|
| 73 |
+
|
| 74 |
+
print(f"\n📊 Summary:")
|
| 75 |
+
if working_endpoints:
|
| 76 |
+
print(f"✅ Working endpoints: {len(working_endpoints)}")
|
| 77 |
+
for endpoint in working_endpoints:
|
| 78 |
+
print(f" - {endpoint}")
|
| 79 |
+
else:
|
| 80 |
+
print("❌ No working endpoints found")
|
| 81 |
+
print("\n💡 Suggestions:")
|
| 82 |
+
print("1. Check if the API key is correct")
|
| 83 |
+
print("2. Check Novita AI documentation for correct endpoints")
|
| 84 |
+
print("3. Try using a different API key")
|
| 85 |
+
print("4. Check if there are any IP restrictions")
|
| 86 |
+
|
| 87 |
+
return working_endpoints
|
| 88 |
+
|
| 89 |
+
def test_openai_compatible():
|
| 90 |
+
"""Test if Novita AI is OpenAI compatible"""
|
| 91 |
+
print("\n🤖 Testing OpenAI compatibility...")
|
| 92 |
+
|
| 93 |
+
api_key = os.getenv('NOVITA_API_KEY')
|
| 94 |
+
if not api_key:
|
| 95 |
+
print("❌ NOVITA_API_KEY tidak ditemukan")
|
| 96 |
+
return
|
| 97 |
+
|
| 98 |
+
# Try OpenAI-compatible endpoints
|
| 99 |
+
openai_endpoints = [
|
| 100 |
+
"https://api.novita.ai/v1",
|
| 101 |
+
"https://api.novita.com/v1",
|
| 102 |
+
"https://novita.ai/api/v1",
|
| 103 |
+
"https://novita.com/api/v1"
|
| 104 |
+
]
|
| 105 |
+
|
| 106 |
+
headers = {
|
| 107 |
+
"Authorization": f"Bearer {api_key}",
|
| 108 |
+
"Content-Type": "application/json"
|
| 109 |
+
}
|
| 110 |
+
|
| 111 |
+
for endpoint in openai_endpoints:
|
| 112 |
+
print(f"\n🔍 Testing OpenAI endpoint: {endpoint}")
|
| 113 |
+
|
| 114 |
+
try:
|
| 115 |
+
# Test models endpoint
|
| 116 |
+
response = requests.get(f"{endpoint}/models", headers=headers, timeout=10)
|
| 117 |
+
print(f" GET /models: {response.status_code}")
|
| 118 |
+
|
| 119 |
+
if response.status_code == 200:
|
| 120 |
+
print(f" ✅ Success!")
|
| 121 |
+
try:
|
| 122 |
+
models = response.json()
|
| 123 |
+
print(f" 📋 Models: {json.dumps(models, indent=2)[:300]}...")
|
| 124 |
+
except:
|
| 125 |
+
print(f" 📋 Response: {response.text[:200]}...")
|
| 126 |
+
elif response.status_code == 401:
|
| 127 |
+
print(f" ⚠️ Unauthorized")
|
| 128 |
+
elif response.status_code == 404:
|
| 129 |
+
print(f" ⚠️ Not Found")
|
| 130 |
+
else:
|
| 131 |
+
print(f" ⚠️ Status: {response.status_code}")
|
| 132 |
+
|
| 133 |
+
except Exception as e:
|
| 134 |
+
print(f" ❌ Error: {e}")
|
| 135 |
+
|
| 136 |
+
def main():
|
| 137 |
+
print("🔍 Novita AI Connection Tester")
|
| 138 |
+
print("=" * 40)
|
| 139 |
+
|
| 140 |
+
# Test basic connection
|
| 141 |
+
working_endpoints = test_novita_connection()
|
| 142 |
+
|
| 143 |
+
# Test OpenAI compatibility
|
| 144 |
+
test_openai_compatible()
|
| 145 |
+
|
| 146 |
+
print(f"\n🎯 Next Steps:")
|
| 147 |
+
if working_endpoints:
|
| 148 |
+
print("✅ Koneksi berhasil! Anda bisa melanjutkan dengan fine-tuning")
|
| 149 |
+
print("💡 Gunakan endpoint yang berfungsi untuk setup selanjutnya")
|
| 150 |
+
else:
|
| 151 |
+
print("❌ Koneksi gagal. Cek dokumentasi Novita AI")
|
| 152 |
+
print("💡 Atau gunakan alternatif lain seperti local models")
|
| 153 |
+
|
| 154 |
+
if __name__ == "__main__":
|
| 155 |
+
main()
|
| 156 |
+
|
| 157 |
+
|
| 158 |
+
|
scripts/train_with_monitoring.py
ADDED
|
@@ -0,0 +1,228 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
Script untuk training dengan monitoring GPU dan logging yang lengkap
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
import os
|
| 7 |
+
import sys
|
| 8 |
+
import time
|
| 9 |
+
import json
|
| 10 |
+
import psutil
|
| 11 |
+
import GPUtil
|
| 12 |
+
from pathlib import Path
|
| 13 |
+
from datetime import datetime
|
| 14 |
+
import logging
|
| 15 |
+
from finetune_lora import main as finetune_main
|
| 16 |
+
|
| 17 |
+
def setup_logging():
|
| 18 |
+
"""Setup logging dengan format yang lengkap"""
|
| 19 |
+
log_dir = Path("logs")
|
| 20 |
+
log_dir.mkdir(exist_ok=True)
|
| 21 |
+
|
| 22 |
+
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
| 23 |
+
log_file = log_dir / f"training_{timestamp}.log"
|
| 24 |
+
|
| 25 |
+
# Setup logging format
|
| 26 |
+
logging.basicConfig(
|
| 27 |
+
level=logging.INFO,
|
| 28 |
+
format='%(asctime)s - %(levelname)s - %(message)s',
|
| 29 |
+
handlers=[
|
| 30 |
+
logging.FileHandler(log_file, encoding='utf-8'),
|
| 31 |
+
logging.StreamHandler(sys.stdout)
|
| 32 |
+
]
|
| 33 |
+
)
|
| 34 |
+
|
| 35 |
+
return logging.getLogger(__name__)
|
| 36 |
+
|
| 37 |
+
def get_system_info():
|
| 38 |
+
"""Get system information"""
|
| 39 |
+
info = {
|
| 40 |
+
"timestamp": datetime.now().isoformat(),
|
| 41 |
+
"cpu_count": psutil.cpu_count(),
|
| 42 |
+
"memory_total_gb": round(psutil.virtual_memory().total / (1024**3), 2),
|
| 43 |
+
"memory_available_gb": round(psutil.virtual_memory().available / (1024**3), 2),
|
| 44 |
+
"disk_usage": {}
|
| 45 |
+
}
|
| 46 |
+
|
| 47 |
+
# Disk usage
|
| 48 |
+
for partition in psutil.disk_partitions():
|
| 49 |
+
try:
|
| 50 |
+
usage = psutil.disk_usage(partition.mountpoint)
|
| 51 |
+
info["disk_usage"][partition.mountpoint] = {
|
| 52 |
+
"total_gb": round(usage.total / (1024**3), 2),
|
| 53 |
+
"used_gb": round(usage.used / (1024**3), 2),
|
| 54 |
+
"free_gb": round(usage.free / (1024**3), 2),
|
| 55 |
+
"percent": usage.percent
|
| 56 |
+
}
|
| 57 |
+
except PermissionError:
|
| 58 |
+
continue
|
| 59 |
+
|
| 60 |
+
return info
|
| 61 |
+
|
| 62 |
+
def get_gpu_info():
|
| 63 |
+
"""Get GPU information"""
|
| 64 |
+
try:
|
| 65 |
+
gpus = GPUtil.getGPUs()
|
| 66 |
+
gpu_info = []
|
| 67 |
+
|
| 68 |
+
for gpu in gpus:
|
| 69 |
+
gpu_info.append({
|
| 70 |
+
"id": gpu.id,
|
| 71 |
+
"name": gpu.name,
|
| 72 |
+
"memory_total_mb": gpu.memoryTotal,
|
| 73 |
+
"memory_used_mb": gpu.memoryUsed,
|
| 74 |
+
"memory_free_mb": gpu.memoryFree,
|
| 75 |
+
"memory_utilization_percent": gpu.memoryUtil * 100,
|
| 76 |
+
"gpu_utilization_percent": gpu.load * 100,
|
| 77 |
+
"temperature_celsius": gpu.temperature
|
| 78 |
+
})
|
| 79 |
+
|
| 80 |
+
return gpu_info
|
| 81 |
+
except Exception as e:
|
| 82 |
+
logging.warning(f"Could not get GPU info: {e}")
|
| 83 |
+
return []
|
| 84 |
+
|
| 85 |
+
def monitor_resources(logger, interval=30):
|
| 86 |
+
"""Monitor system resources during training"""
|
| 87 |
+
logger.info("🔍 Starting resource monitoring...")
|
| 88 |
+
|
| 89 |
+
start_time = time.time()
|
| 90 |
+
monitoring_data = []
|
| 91 |
+
|
| 92 |
+
try:
|
| 93 |
+
while True:
|
| 94 |
+
# Get current resource usage
|
| 95 |
+
current_time = time.time()
|
| 96 |
+
elapsed_time = current_time - start_time
|
| 97 |
+
|
| 98 |
+
# System info
|
| 99 |
+
system_info = get_system_info()
|
| 100 |
+
system_info["elapsed_time_seconds"] = elapsed_time
|
| 101 |
+
|
| 102 |
+
# GPU info
|
| 103 |
+
gpu_info = get_gpu_info()
|
| 104 |
+
|
| 105 |
+
# Memory usage
|
| 106 |
+
memory = psutil.virtual_memory()
|
| 107 |
+
system_info["memory_used_gb"] = round(memory.used / (1024**3), 2)
|
| 108 |
+
system_info["memory_percent"] = memory.percent
|
| 109 |
+
|
| 110 |
+
# CPU usage
|
| 111 |
+
system_info["cpu_percent"] = psutil.cpu_percent(interval=1)
|
| 112 |
+
|
| 113 |
+
# Combine all info
|
| 114 |
+
monitoring_entry = {
|
| 115 |
+
"timestamp": datetime.now().isoformat(),
|
| 116 |
+
"elapsed_time_seconds": elapsed_time,
|
| 117 |
+
"system": system_info,
|
| 118 |
+
"gpu": gpu_info
|
| 119 |
+
}
|
| 120 |
+
|
| 121 |
+
monitoring_data.append(monitoring_entry)
|
| 122 |
+
|
| 123 |
+
# Log summary
|
| 124 |
+
logger.info(f"⏱️ Elapsed: {elapsed_time/60:.1f}min | "
|
| 125 |
+
f"CPU: {system_info['cpu_percent']:.1f}% | "
|
| 126 |
+
f"RAM: {system_info['memory_percent']:.1f}%")
|
| 127 |
+
|
| 128 |
+
if gpu_info:
|
| 129 |
+
for gpu in gpu_info:
|
| 130 |
+
logger.info(f"🎮 GPU {gpu['id']}: "
|
| 131 |
+
f"Util: {gpu['gpu_utilization_percent']:.1f}% | "
|
| 132 |
+
f"Memory: {gpu['memory_utilization_percent']:.1f}% | "
|
| 133 |
+
f"Temp: {gpu['temperature_celsius']:.1f}°C")
|
| 134 |
+
|
| 135 |
+
# Save monitoring data periodically
|
| 136 |
+
if len(monitoring_data) % 10 == 0: # Every 10 entries
|
| 137 |
+
monitoring_file = Path("logs") / f"monitoring_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json"
|
| 138 |
+
with open(monitoring_file, 'w') as f:
|
| 139 |
+
json.dump(monitoring_data, f, indent=2)
|
| 140 |
+
logger.info(f"💾 Monitoring data saved: {monitoring_file}")
|
| 141 |
+
|
| 142 |
+
time.sleep(interval)
|
| 143 |
+
|
| 144 |
+
except KeyboardInterrupt:
|
| 145 |
+
logger.info("⏹️ Resource monitoring stopped by user")
|
| 146 |
+
|
| 147 |
+
return monitoring_data
|
| 148 |
+
|
| 149 |
+
def main():
|
| 150 |
+
"""Main function untuk training dengan monitoring"""
|
| 151 |
+
print("🚀 Training dengan Monitoring - Llama 3.1 8B LoRA")
|
| 152 |
+
print("=" * 60)
|
| 153 |
+
|
| 154 |
+
# Setup logging
|
| 155 |
+
logger = setup_logging()
|
| 156 |
+
|
| 157 |
+
# Log system information
|
| 158 |
+
logger.info("🖥️ System Information:")
|
| 159 |
+
system_info = get_system_info()
|
| 160 |
+
for key, value in system_info.items():
|
| 161 |
+
if key != "disk_usage":
|
| 162 |
+
logger.info(f" {key}: {value}")
|
| 163 |
+
|
| 164 |
+
# Log GPU information
|
| 165 |
+
gpu_info = get_gpu_info()
|
| 166 |
+
if gpu_info:
|
| 167 |
+
logger.info("🎮 GPU Information:")
|
| 168 |
+
for gpu in gpu_info:
|
| 169 |
+
logger.info(f" GPU {gpu['id']}: {gpu['name']}")
|
| 170 |
+
logger.info(f" Memory: {gpu['memory_total_mb']}MB total")
|
| 171 |
+
logger.info(f" Temperature: {gpu['temperature_celsius']}°C")
|
| 172 |
+
else:
|
| 173 |
+
logger.warning("⚠️ No GPU detected. Training will be very slow on CPU!")
|
| 174 |
+
|
| 175 |
+
# Check prerequisites
|
| 176 |
+
logger.info("🔍 Checking prerequisites...")
|
| 177 |
+
|
| 178 |
+
# Check if model exists
|
| 179 |
+
model_path = Path("models/llama-3.1-8b-instruct")
|
| 180 |
+
if not model_path.exists():
|
| 181 |
+
logger.error("❌ Base model not found. Please run download_model.py first!")
|
| 182 |
+
return
|
| 183 |
+
|
| 184 |
+
# Check if dataset exists
|
| 185 |
+
data_path = Path("data/training_data.jsonl")
|
| 186 |
+
if not data_path.exists():
|
| 187 |
+
logger.error("❌ Training dataset not found. Please run create_sample_dataset.py first!")
|
| 188 |
+
return
|
| 189 |
+
|
| 190 |
+
# Check if config exists
|
| 191 |
+
config_path = Path("configs/llama_config.yaml")
|
| 192 |
+
if not config_path.exists():
|
| 193 |
+
logger.error("❌ Model configuration not found. Please run download_model.py first!")
|
| 194 |
+
return
|
| 195 |
+
|
| 196 |
+
logger.info("✅ All prerequisites met!")
|
| 197 |
+
|
| 198 |
+
# Start resource monitoring in background
|
| 199 |
+
import threading
|
| 200 |
+
monitoring_thread = threading.Thread(
|
| 201 |
+
target=monitor_resources,
|
| 202 |
+
args=(logger, 30), # Monitor every 30 seconds
|
| 203 |
+
daemon=True
|
| 204 |
+
)
|
| 205 |
+
monitoring_thread.start()
|
| 206 |
+
|
| 207 |
+
# Start training
|
| 208 |
+
logger.info("🚀 Starting LoRA fine-tuning...")
|
| 209 |
+
try:
|
| 210 |
+
finetune_main()
|
| 211 |
+
logger.info("✅ Training completed successfully!")
|
| 212 |
+
except Exception as e:
|
| 213 |
+
logger.error(f"❌ Training failed: {e}")
|
| 214 |
+
raise
|
| 215 |
+
finally:
|
| 216 |
+
logger.info("📊 Training session ended")
|
| 217 |
+
|
| 218 |
+
# Save final monitoring data
|
| 219 |
+
monitoring_file = Path("logs") / f"final_monitoring_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json"
|
| 220 |
+
# Note: In a real implementation, you'd want to capture the monitoring data
|
| 221 |
+
logger.info(f"💾 Final monitoring data saved: {monitoring_file}")
|
| 222 |
+
|
| 223 |
+
if __name__ == "__main__":
|
| 224 |
+
main()
|
| 225 |
+
|
| 226 |
+
|
| 227 |
+
|
| 228 |
+
|
setup.bat
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
@echo off
|
| 2 |
+
echo 🚀 Setup Base LLM Environment (Windows)
|
| 3 |
+
echo ======================================
|
| 4 |
+
|
| 5 |
+
REM Check if Python is available
|
| 6 |
+
python --version >nul 2>&1
|
| 7 |
+
if errorlevel 1 (
|
| 8 |
+
echo ❌ Python tidak ditemukan!
|
| 9 |
+
echo Silakan install Python 3.8+ terlebih dahulu
|
| 10 |
+
pause
|
| 11 |
+
exit /b 1
|
| 12 |
+
)
|
| 13 |
+
|
| 14 |
+
echo ✅ Python ditemukan
|
| 15 |
+
|
| 16 |
+
REM Create virtual environment
|
| 17 |
+
echo 📦 Creating virtual environment...
|
| 18 |
+
python -m venv venv
|
| 19 |
+
|
| 20 |
+
REM Activate virtual environment
|
| 21 |
+
echo 🔧 Activating virtual environment...
|
| 22 |
+
call venv\Scripts\activate.bat
|
| 23 |
+
|
| 24 |
+
REM Upgrade pip
|
| 25 |
+
echo ⬆️ Upgrading pip...
|
| 26 |
+
python -m pip install --upgrade pip
|
| 27 |
+
|
| 28 |
+
REM Install requirements
|
| 29 |
+
echo 📚 Installing requirements...
|
| 30 |
+
pip install -r requirements.txt
|
| 31 |
+
|
| 32 |
+
REM Install additional tools
|
| 33 |
+
echo 🛠️ Installing additional tools...
|
| 34 |
+
pip install huggingface-cli
|
| 35 |
+
|
| 36 |
+
echo.
|
| 37 |
+
echo ✅ Setup selesai!
|
| 38 |
+
echo.
|
| 39 |
+
echo 📋 Langkah selanjutnya:
|
| 40 |
+
echo 1. Aktifkan virtual environment: venv\Scripts\activate.bat
|
| 41 |
+
echo 2. Set HuggingFace token: set HUGGINGFACE_TOKEN=your_token
|
| 42 |
+
echo 3. Jalankan: python scripts\download_model.py
|
| 43 |
+
echo 4. Jalankan: python scripts\finetune_lora.py
|
| 44 |
+
echo.
|
| 45 |
+
echo 💡 Tips:
|
| 46 |
+
echo - Selalu aktifkan venv sebelum menjalankan script
|
| 47 |
+
echo - Gunakan 'deactivate' untuk keluar dari venv
|
| 48 |
+
echo - Pastikan GPU tersedia untuk training
|
| 49 |
+
echo.
|
| 50 |
+
pause
|
| 51 |
+
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
|
setup.sh
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/bash
|
| 2 |
+
|
| 3 |
+
echo "🚀 Setup Base LLM Environment"
|
| 4 |
+
echo "=============================="
|
| 5 |
+
|
| 6 |
+
# Check if Python 3.8+ is available
|
| 7 |
+
python_version=$(python3 --version 2>&1 | grep -oP '\d+\.\d+' | head -1)
|
| 8 |
+
if [[ -z "$python_version" ]]; then
|
| 9 |
+
echo "❌ Python 3 tidak ditemukan!"
|
| 10 |
+
echo "Silakan install Python 3.8+ terlebih dahulu"
|
| 11 |
+
exit 1
|
| 12 |
+
fi
|
| 13 |
+
|
| 14 |
+
echo "✅ Python version: $python_version"
|
| 15 |
+
|
| 16 |
+
# Create virtual environment
|
| 17 |
+
echo "📦 Creating virtual environment..."
|
| 18 |
+
python3 -m venv venv
|
| 19 |
+
|
| 20 |
+
# Activate virtual environment
|
| 21 |
+
echo "🔧 Activating virtual environment..."
|
| 22 |
+
source venv/bin/activate
|
| 23 |
+
|
| 24 |
+
# Upgrade pip
|
| 25 |
+
echo "⬆️ Upgrading pip..."
|
| 26 |
+
pip install --upgrade pip
|
| 27 |
+
|
| 28 |
+
# Install requirements
|
| 29 |
+
echo "📚 Installing requirements..."
|
| 30 |
+
pip install -r requirements.txt
|
| 31 |
+
|
| 32 |
+
# Install additional tools
|
| 33 |
+
echo "🛠️ Installing additional tools..."
|
| 34 |
+
pip install huggingface-cli
|
| 35 |
+
|
| 36 |
+
echo ""
|
| 37 |
+
echo "✅ Setup selesai!"
|
| 38 |
+
echo ""
|
| 39 |
+
echo "📋 Langkah selanjutnya:"
|
| 40 |
+
echo "1. Aktifkan virtual environment: source venv/bin/activate"
|
| 41 |
+
echo "2. Set HuggingFace token: export HUGGINGFACE_TOKEN='your_token'"
|
| 42 |
+
echo "3. Jalankan: python scripts/download_model.py"
|
| 43 |
+
echo "4. Jalankan: python scripts/finetune_lora.py"
|
| 44 |
+
echo ""
|
| 45 |
+
echo "💡 Tips:"
|
| 46 |
+
echo "- Selalu aktifkan venv sebelum menjalankan script"
|
| 47 |
+
echo "- Gunakan 'deactivate' untuk keluar dari venv"
|
| 48 |
+
echo "- Pastikan GPU tersedia untuk training"
|
| 49 |
+
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
|
setup_novita.sh
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/bash
|
| 2 |
+
|
| 3 |
+
echo "🚀 Setup Novita AI Environment"
|
| 4 |
+
echo "=============================="
|
| 5 |
+
|
| 6 |
+
# Check if Python 3.8+ is available
|
| 7 |
+
python_version=$(python3 --version 2>&1 | grep -oP '\d+\.\d+' | head -1)
|
| 8 |
+
if [[ -z "$python_version" ]]; then
|
| 9 |
+
echo "❌ Python 3 tidak ditemukan!"
|
| 10 |
+
echo "Silakan install Python 3.8+ terlebih dahulu"
|
| 11 |
+
exit 1
|
| 12 |
+
fi
|
| 13 |
+
|
| 14 |
+
echo "✅ Python version: $python_version"
|
| 15 |
+
|
| 16 |
+
# Create virtual environment
|
| 17 |
+
echo "📦 Creating virtual environment..."
|
| 18 |
+
python3 -m venv venv
|
| 19 |
+
|
| 20 |
+
# Activate virtual environment
|
| 21 |
+
echo "🔧 Activating virtual environment..."
|
| 22 |
+
source venv/bin/activate
|
| 23 |
+
|
| 24 |
+
# Upgrade pip
|
| 25 |
+
echo "⬆️ Upgrading pip..."
|
| 26 |
+
pip install --upgrade pip
|
| 27 |
+
|
| 28 |
+
# Install requirements for Novita AI
|
| 29 |
+
echo "📚 Installing requirements..."
|
| 30 |
+
pip install requests openai python-dotenv
|
| 31 |
+
|
| 32 |
+
# Install additional tools
|
| 33 |
+
echo "🛠️ Installing additional tools..."
|
| 34 |
+
pip install huggingface-hub
|
| 35 |
+
|
| 36 |
+
echo ""
|
| 37 |
+
echo "✅ Setup selesai!"
|
| 38 |
+
echo ""
|
| 39 |
+
echo "📋 Langkah selanjutnya:"
|
| 40 |
+
echo "1. Aktifkan virtual environment: source venv/bin/activate"
|
| 41 |
+
echo "2. Set Novita AI API key: export NOVITA_API_KEY='your_key'"
|
| 42 |
+
echo "3. Jalankan: python scripts/novita_ai_setup.py"
|
| 43 |
+
echo ""
|
| 44 |
+
echo "💡 Tips:"
|
| 45 |
+
echo "- Selalu aktifkan venv sebelum menjalankan script"
|
| 46 |
+
echo "- Gunakan 'deactivate' untuk keluar dari venv"
|
| 47 |
+
echo "- Pastikan API key Novita AI valid"
|
| 48 |
+
echo ""
|
| 49 |
+
echo "🔑 Untuk mendapatkan API key:"
|
| 50 |
+
echo "1. Kunjungi https://novita.ai"
|
| 51 |
+
echo "2. Buat account atau login"
|
| 52 |
+
echo "3. Buka dashboard dan cari API keys"
|
| 53 |
+
echo "4. Copy API key dan set sebagai environment variable"
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
|
templates/chat.html
ADDED
|
@@ -0,0 +1,350 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="id">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8">
|
| 5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
+
<title>Textilindo AI Assistant</title>
|
| 7 |
+
<style>
|
| 8 |
+
* {
|
| 9 |
+
margin: 0;
|
| 10 |
+
padding: 0;
|
| 11 |
+
box-sizing: border-box;
|
| 12 |
+
}
|
| 13 |
+
|
| 14 |
+
body {
|
| 15 |
+
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
| 16 |
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
| 17 |
+
height: 100vh;
|
| 18 |
+
display: flex;
|
| 19 |
+
justify-content: center;
|
| 20 |
+
align-items: center;
|
| 21 |
+
}
|
| 22 |
+
|
| 23 |
+
.chat-container {
|
| 24 |
+
width: 90%;
|
| 25 |
+
max-width: 800px;
|
| 26 |
+
height: 80vh;
|
| 27 |
+
background: white;
|
| 28 |
+
border-radius: 20px;
|
| 29 |
+
box-shadow: 0 20px 40px rgba(0,0,0,0.1);
|
| 30 |
+
display: flex;
|
| 31 |
+
flex-direction: column;
|
| 32 |
+
overflow: hidden;
|
| 33 |
+
}
|
| 34 |
+
|
| 35 |
+
.header {
|
| 36 |
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
| 37 |
+
color: white;
|
| 38 |
+
padding: 20px;
|
| 39 |
+
text-align: center;
|
| 40 |
+
}
|
| 41 |
+
|
| 42 |
+
.header h1 {
|
| 43 |
+
font-size: 24px;
|
| 44 |
+
margin-bottom: 5px;
|
| 45 |
+
}
|
| 46 |
+
|
| 47 |
+
.header p {
|
| 48 |
+
opacity: 0.9;
|
| 49 |
+
font-size: 14px;
|
| 50 |
+
}
|
| 51 |
+
|
| 52 |
+
.chat-messages {
|
| 53 |
+
flex: 1;
|
| 54 |
+
padding: 20px;
|
| 55 |
+
overflow-y: auto;
|
| 56 |
+
background: #f8f9fa;
|
| 57 |
+
}
|
| 58 |
+
|
| 59 |
+
.message {
|
| 60 |
+
margin-bottom: 15px;
|
| 61 |
+
display: flex;
|
| 62 |
+
align-items: flex-start;
|
| 63 |
+
}
|
| 64 |
+
|
| 65 |
+
.message.user {
|
| 66 |
+
justify-content: flex-end;
|
| 67 |
+
}
|
| 68 |
+
|
| 69 |
+
.message-content {
|
| 70 |
+
max-width: 70%;
|
| 71 |
+
padding: 12px 16px;
|
| 72 |
+
border-radius: 18px;
|
| 73 |
+
word-wrap: break-word;
|
| 74 |
+
}
|
| 75 |
+
|
| 76 |
+
.message.user .message-content {
|
| 77 |
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
| 78 |
+
color: white;
|
| 79 |
+
border-bottom-right-radius: 4px;
|
| 80 |
+
}
|
| 81 |
+
|
| 82 |
+
.message.assistant .message-content {
|
| 83 |
+
background: white;
|
| 84 |
+
color: #333;
|
| 85 |
+
border: 1px solid #e1e5e9;
|
| 86 |
+
border-bottom-left-radius: 4px;
|
| 87 |
+
}
|
| 88 |
+
|
| 89 |
+
.avatar {
|
| 90 |
+
width: 32px;
|
| 91 |
+
height: 32px;
|
| 92 |
+
border-radius: 50%;
|
| 93 |
+
margin: 0 8px;
|
| 94 |
+
display: flex;
|
| 95 |
+
align-items: center;
|
| 96 |
+
justify-content: center;
|
| 97 |
+
font-weight: bold;
|
| 98 |
+
font-size: 14px;
|
| 99 |
+
}
|
| 100 |
+
|
| 101 |
+
.user .avatar {
|
| 102 |
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
| 103 |
+
color: white;
|
| 104 |
+
}
|
| 105 |
+
|
| 106 |
+
.assistant .avatar {
|
| 107 |
+
background: #28a745;
|
| 108 |
+
color: white;
|
| 109 |
+
}
|
| 110 |
+
|
| 111 |
+
.input-container {
|
| 112 |
+
padding: 20px;
|
| 113 |
+
background: white;
|
| 114 |
+
border-top: 1px solid #e1e5e9;
|
| 115 |
+
}
|
| 116 |
+
|
| 117 |
+
.input-form {
|
| 118 |
+
display: flex;
|
| 119 |
+
gap: 10px;
|
| 120 |
+
}
|
| 121 |
+
|
| 122 |
+
.message-input {
|
| 123 |
+
flex: 1;
|
| 124 |
+
padding: 12px 16px;
|
| 125 |
+
border: 2px solid #e1e5e9;
|
| 126 |
+
border-radius: 25px;
|
| 127 |
+
font-size: 14px;
|
| 128 |
+
outline: none;
|
| 129 |
+
transition: border-color 0.3s;
|
| 130 |
+
}
|
| 131 |
+
|
| 132 |
+
.message-input:focus {
|
| 133 |
+
border-color: #667eea;
|
| 134 |
+
}
|
| 135 |
+
|
| 136 |
+
.send-button {
|
| 137 |
+
padding: 12px 24px;
|
| 138 |
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
| 139 |
+
color: white;
|
| 140 |
+
border: none;
|
| 141 |
+
border-radius: 25px;
|
| 142 |
+
cursor: pointer;
|
| 143 |
+
font-weight: bold;
|
| 144 |
+
transition: transform 0.2s;
|
| 145 |
+
}
|
| 146 |
+
|
| 147 |
+
.send-button:hover {
|
| 148 |
+
transform: translateY(-2px);
|
| 149 |
+
}
|
| 150 |
+
|
| 151 |
+
.send-button:disabled {
|
| 152 |
+
opacity: 0.6;
|
| 153 |
+
cursor: not-allowed;
|
| 154 |
+
transform: none;
|
| 155 |
+
}
|
| 156 |
+
|
| 157 |
+
.typing-indicator {
|
| 158 |
+
display: none;
|
| 159 |
+
padding: 12px 16px;
|
| 160 |
+
background: white;
|
| 161 |
+
border: 1px solid #e1e5e9;
|
| 162 |
+
border-radius: 18px;
|
| 163 |
+
border-bottom-left-radius: 4px;
|
| 164 |
+
color: #666;
|
| 165 |
+
font-style: italic;
|
| 166 |
+
}
|
| 167 |
+
|
| 168 |
+
.stats {
|
| 169 |
+
position: fixed;
|
| 170 |
+
top: 20px;
|
| 171 |
+
right: 20px;
|
| 172 |
+
background: white;
|
| 173 |
+
padding: 15px;
|
| 174 |
+
border-radius: 10px;
|
| 175 |
+
box-shadow: 0 5px 15px rgba(0,0,0,0.1);
|
| 176 |
+
font-size: 12px;
|
| 177 |
+
max-width: 200px;
|
| 178 |
+
}
|
| 179 |
+
|
| 180 |
+
.stats h3 {
|
| 181 |
+
margin-bottom: 10px;
|
| 182 |
+
color: #667eea;
|
| 183 |
+
}
|
| 184 |
+
|
| 185 |
+
.stats p {
|
| 186 |
+
margin-bottom: 5px;
|
| 187 |
+
}
|
| 188 |
+
|
| 189 |
+
@media (max-width: 768px) {
|
| 190 |
+
.chat-container {
|
| 191 |
+
width: 95%;
|
| 192 |
+
height: 90vh;
|
| 193 |
+
}
|
| 194 |
+
|
| 195 |
+
.stats {
|
| 196 |
+
position: static;
|
| 197 |
+
margin: 10px;
|
| 198 |
+
max-width: none;
|
| 199 |
+
}
|
| 200 |
+
}
|
| 201 |
+
</style>
|
| 202 |
+
</head>
|
| 203 |
+
<body>
|
| 204 |
+
<div class="stats" id="stats">
|
| 205 |
+
<h3>📊 Stats</h3>
|
| 206 |
+
<p>Loading...</p>
|
| 207 |
+
</div>
|
| 208 |
+
|
| 209 |
+
<div class="chat-container">
|
| 210 |
+
<div class="header">
|
| 211 |
+
<h1>🤖 Textilindo AI Assistant</h1>
|
| 212 |
+
<p>Powered by Novita AI • Ask me anything about Textilindo!</p>
|
| 213 |
+
</div>
|
| 214 |
+
|
| 215 |
+
<div class="chat-messages" id="chatMessages">
|
| 216 |
+
<div class="message assistant">
|
| 217 |
+
<div class="avatar">AI</div>
|
| 218 |
+
<div class="message-content">
|
| 219 |
+
Halo! Saya adalah AI Assistant Textilindo. Ada yang bisa saya bantu? 😊
|
| 220 |
+
</div>
|
| 221 |
+
</div>
|
| 222 |
+
</div>
|
| 223 |
+
|
| 224 |
+
<div class="typing-indicator" id="typingIndicator">
|
| 225 |
+
AI sedang mengetik...
|
| 226 |
+
</div>
|
| 227 |
+
|
| 228 |
+
<div class="input-container">
|
| 229 |
+
<form class="input-form" id="chatForm">
|
| 230 |
+
<input type="text" class="message-input" id="messageInput"
|
| 231 |
+
placeholder="Ketik pertanyaan Anda di sini..." autocomplete="off">
|
| 232 |
+
<button type="submit" class="send-button" id="sendButton">
|
| 233 |
+
Kirim
|
| 234 |
+
</button>
|
| 235 |
+
</form>
|
| 236 |
+
</div>
|
| 237 |
+
</div>
|
| 238 |
+
|
| 239 |
+
<script>
|
| 240 |
+
const chatMessages = document.getElementById('chatMessages');
|
| 241 |
+
const messageInput = document.getElementById('messageInput');
|
| 242 |
+
const sendButton = document.getElementById('sendButton');
|
| 243 |
+
const chatForm = document.getElementById('chatForm');
|
| 244 |
+
const typingIndicator = document.getElementById('typingIndicator');
|
| 245 |
+
const stats = document.getElementById('stats');
|
| 246 |
+
|
| 247 |
+
// Load stats
|
| 248 |
+
fetch('/stats')
|
| 249 |
+
.then(response => response.json())
|
| 250 |
+
.then(data => {
|
| 251 |
+
if (data.error) {
|
| 252 |
+
stats.innerHTML = '<h3>📊 Stats</h3><p>Error loading stats</p>';
|
| 253 |
+
} else {
|
| 254 |
+
stats.innerHTML = `
|
| 255 |
+
<h3>📊 Stats</h3>
|
| 256 |
+
<p>📝 ${data.total_examples} examples</p>
|
| 257 |
+
<p>🤖 ${data.model.split('/').pop()}</p>
|
| 258 |
+
<p>📂 ${Object.keys(data.topics).length} topics</p>
|
| 259 |
+
`;
|
| 260 |
+
}
|
| 261 |
+
})
|
| 262 |
+
.catch(error => {
|
| 263 |
+
stats.innerHTML = '<h3>📊 Stats</h3><p>Error loading stats</p>';
|
| 264 |
+
});
|
| 265 |
+
|
| 266 |
+
function addMessage(content, isUser = false) {
|
| 267 |
+
const messageDiv = document.createElement('div');
|
| 268 |
+
messageDiv.className = `message ${isUser ? 'user' : 'assistant'}`;
|
| 269 |
+
|
| 270 |
+
const avatar = document.createElement('div');
|
| 271 |
+
avatar.className = 'avatar';
|
| 272 |
+
avatar.textContent = isUser ? 'U' : 'AI';
|
| 273 |
+
|
| 274 |
+
const messageContent = document.createElement('div');
|
| 275 |
+
messageContent.className = 'message-content';
|
| 276 |
+
messageContent.textContent = content;
|
| 277 |
+
|
| 278 |
+
messageDiv.appendChild(avatar);
|
| 279 |
+
messageDiv.appendChild(messageContent);
|
| 280 |
+
|
| 281 |
+
chatMessages.appendChild(messageDiv);
|
| 282 |
+
chatMessages.scrollTop = chatMessages.scrollHeight;
|
| 283 |
+
}
|
| 284 |
+
|
| 285 |
+
function showTyping() {
|
| 286 |
+
typingIndicator.style.display = 'block';
|
| 287 |
+
chatMessages.scrollTop = chatMessages.scrollHeight;
|
| 288 |
+
}
|
| 289 |
+
|
| 290 |
+
function hideTyping() {
|
| 291 |
+
typingIndicator.style.display = 'none';
|
| 292 |
+
}
|
| 293 |
+
|
| 294 |
+
async function sendMessage(message) {
|
| 295 |
+
if (!message.trim()) return;
|
| 296 |
+
|
| 297 |
+
// Add user message
|
| 298 |
+
addMessage(message, true);
|
| 299 |
+
messageInput.value = '';
|
| 300 |
+
|
| 301 |
+
// Show typing indicator
|
| 302 |
+
showTyping();
|
| 303 |
+
|
| 304 |
+
try {
|
| 305 |
+
const response = await fetch('/chat', {
|
| 306 |
+
method: 'POST',
|
| 307 |
+
headers: {
|
| 308 |
+
'Content-Type': 'application/json',
|
| 309 |
+
},
|
| 310 |
+
body: JSON.stringify({ message: message })
|
| 311 |
+
});
|
| 312 |
+
|
| 313 |
+
const data = await response.json();
|
| 314 |
+
|
| 315 |
+
// Hide typing indicator
|
| 316 |
+
hideTyping();
|
| 317 |
+
|
| 318 |
+
// Add AI response
|
| 319 |
+
addMessage(data.response);
|
| 320 |
+
|
| 321 |
+
} catch (error) {
|
| 322 |
+
hideTyping();
|
| 323 |
+
addMessage('Maaf, terjadi kesalahan. Silakan coba lagi.');
|
| 324 |
+
console.error('Error:', error);
|
| 325 |
+
}
|
| 326 |
+
}
|
| 327 |
+
|
| 328 |
+
chatForm.addEventListener('submit', (e) => {
|
| 329 |
+
e.preventDefault();
|
| 330 |
+
const message = messageInput.value.trim();
|
| 331 |
+
if (message) {
|
| 332 |
+
sendMessage(message);
|
| 333 |
+
}
|
| 334 |
+
});
|
| 335 |
+
|
| 336 |
+
messageInput.addEventListener('keypress', (e) => {
|
| 337 |
+
if (e.key === 'Enter' && !e.shiftKey) {
|
| 338 |
+
e.preventDefault();
|
| 339 |
+
const message = messageInput.value.trim();
|
| 340 |
+
if (message) {
|
| 341 |
+
sendMessage(message);
|
| 342 |
+
}
|
| 343 |
+
}
|
| 344 |
+
});
|
| 345 |
+
|
| 346 |
+
// Focus input on load
|
| 347 |
+
messageInput.focus();
|
| 348 |
+
</script>
|
| 349 |
+
</body>
|
| 350 |
+
</html>
|
test_novita_simple.py
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
Simple script untuk test koneksi Novita AI dengan endpoint yang benar
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
import os
|
| 7 |
+
import requests
|
| 8 |
+
import json
|
| 9 |
+
|
| 10 |
+
def test_novita_connection():
|
| 11 |
+
"""Test koneksi ke Novita AI dengan endpoint yang benar"""
|
| 12 |
+
|
| 13 |
+
api_key = os.getenv('NOVITA_API_KEY')
|
| 14 |
+
if not api_key:
|
| 15 |
+
print("❌ NOVITA_API_KEY tidak ditemukan")
|
| 16 |
+
return False
|
| 17 |
+
|
| 18 |
+
print(f"🔑 API Key: {api_key[:10]}...{api_key[-10:]}")
|
| 19 |
+
print("🔍 Testing koneksi ke Novita AI...")
|
| 20 |
+
|
| 21 |
+
# Use the correct endpoint
|
| 22 |
+
base_url = "https://api.novita.ai/openai"
|
| 23 |
+
|
| 24 |
+
headers = {
|
| 25 |
+
"Authorization": f"Bearer {api_key}",
|
| 26 |
+
"Content-Type": "application/json"
|
| 27 |
+
}
|
| 28 |
+
|
| 29 |
+
try:
|
| 30 |
+
# Test models endpoint
|
| 31 |
+
print(f"🔍 Testing: {base_url}/models")
|
| 32 |
+
response = requests.get(f"{base_url}/models", headers=headers, timeout=10)
|
| 33 |
+
print(f" Status: {response.status_code}")
|
| 34 |
+
|
| 35 |
+
if response.status_code == 200:
|
| 36 |
+
print("✅ Koneksi berhasil!")
|
| 37 |
+
models = response.json()
|
| 38 |
+
print(f"📋 Found {len(models.get('data', []))} models")
|
| 39 |
+
return True
|
| 40 |
+
else:
|
| 41 |
+
print(f"❌ Error: {response.status_code} - {response.text}")
|
| 42 |
+
return False
|
| 43 |
+
|
| 44 |
+
except Exception as e:
|
| 45 |
+
print(f"❌ Error: {e}")
|
| 46 |
+
return False
|
| 47 |
+
|
| 48 |
+
def test_chat_completion():
|
| 49 |
+
"""Test chat completion dengan model sederhana"""
|
| 50 |
+
|
| 51 |
+
api_key = os.getenv('NOVITA_API_KEY')
|
| 52 |
+
if not api_key:
|
| 53 |
+
print("❌ NOVITA_API_KEY tidak ditemukan")
|
| 54 |
+
return False
|
| 55 |
+
|
| 56 |
+
base_url = "https://api.novita.ai/openai"
|
| 57 |
+
|
| 58 |
+
headers = {
|
| 59 |
+
"Authorization": f"Bearer {api_key}",
|
| 60 |
+
"Content-Type": "application/json"
|
| 61 |
+
}
|
| 62 |
+
|
| 63 |
+
# Test dengan model yang ringan
|
| 64 |
+
payload = {
|
| 65 |
+
"model": "meta-llama/llama-3.2-1b-instruct",
|
| 66 |
+
"messages": [
|
| 67 |
+
{"role": "user", "content": "Hello! How are you today?"}
|
| 68 |
+
],
|
| 69 |
+
"max_tokens": 50,
|
| 70 |
+
"temperature": 0.7
|
| 71 |
+
}
|
| 72 |
+
|
| 73 |
+
try:
|
| 74 |
+
print(f"🔍 Testing chat completion...")
|
| 75 |
+
response = requests.post(f"{base_url}/chat/completions", headers=headers, json=payload, timeout=30)
|
| 76 |
+
print(f" Status: {response.status_code}")
|
| 77 |
+
|
| 78 |
+
if response.status_code == 200:
|
| 79 |
+
result = response.json()
|
| 80 |
+
print("✅ Chat completion berhasil!")
|
| 81 |
+
print(f"📝 Response: {result.get('choices', [{}])[0].get('message', {}).get('content', 'No content')}")
|
| 82 |
+
return True
|
| 83 |
+
else:
|
| 84 |
+
print(f"❌ Error: {response.status_code} - {response.text}")
|
| 85 |
+
return False
|
| 86 |
+
|
| 87 |
+
except Exception as e:
|
| 88 |
+
print(f"❌ Error: {e}")
|
| 89 |
+
return False
|
| 90 |
+
|
| 91 |
+
def main():
|
| 92 |
+
print("🚀 Novita AI Simple Test")
|
| 93 |
+
print("=" * 40)
|
| 94 |
+
|
| 95 |
+
# Test connection
|
| 96 |
+
if test_novita_connection():
|
| 97 |
+
print("\n🎉 Koneksi berhasil! Sekarang test chat completion...")
|
| 98 |
+
|
| 99 |
+
# Test chat completion
|
| 100 |
+
if test_chat_completion():
|
| 101 |
+
print("\n🎉 Semua test berhasil! Novita AI siap digunakan.")
|
| 102 |
+
print("\n📋 Next steps:")
|
| 103 |
+
print("1. Gunakan script novita_ai_setup_v2.py untuk fine-tuning")
|
| 104 |
+
print("2. Atau gunakan script test_model.py untuk testing")
|
| 105 |
+
print("3. Monitor usage di dashboard Novita AI")
|
| 106 |
+
else:
|
| 107 |
+
print("\n⚠️ Chat completion gagal, tapi koneksi OK")
|
| 108 |
+
else:
|
| 109 |
+
print("\n❌ Koneksi gagal. Cek API key dan endpoint")
|
| 110 |
+
|
| 111 |
+
if __name__ == "__main__":
|
| 112 |
+
main()
|
web_app.py
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
Web interface for Textilindo AI Chat
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
from flask import Flask, render_template, request, jsonify
|
| 7 |
+
import os
|
| 8 |
+
import json
|
| 9 |
+
import requests
|
| 10 |
+
from difflib import SequenceMatcher
|
| 11 |
+
|
| 12 |
+
app = Flask(__name__)
|
| 13 |
+
|
| 14 |
+
class TextilindoAI:
|
| 15 |
+
def __init__(self, api_key):
|
| 16 |
+
self.api_key = api_key
|
| 17 |
+
self.base_url = "https://api.novita.ai/openai"
|
| 18 |
+
self.headers = {
|
| 19 |
+
"Authorization": f"Bearer {api_key}",
|
| 20 |
+
"Content-Type": "application/json"
|
| 21 |
+
}
|
| 22 |
+
self.model = "qwen/qwen3-235b-a22b-instruct-2507"
|
| 23 |
+
self.dataset = self.load_dataset()
|
| 24 |
+
|
| 25 |
+
def load_dataset(self):
|
| 26 |
+
"""Load the training dataset"""
|
| 27 |
+
dataset = []
|
| 28 |
+
dataset_path = "data/textilindo_training_data.jsonl"
|
| 29 |
+
|
| 30 |
+
if os.path.exists(dataset_path):
|
| 31 |
+
try:
|
| 32 |
+
with open(dataset_path, 'r', encoding='utf-8') as f:
|
| 33 |
+
for line in f:
|
| 34 |
+
line = line.strip()
|
| 35 |
+
if line:
|
| 36 |
+
data = json.loads(line)
|
| 37 |
+
dataset.append(data)
|
| 38 |
+
except Exception as e:
|
| 39 |
+
print(f"Error loading dataset: {e}")
|
| 40 |
+
|
| 41 |
+
return dataset
|
| 42 |
+
|
| 43 |
+
def find_relevant_context(self, user_query, top_k=3):
|
| 44 |
+
"""Find most relevant examples from dataset"""
|
| 45 |
+
if not self.dataset:
|
| 46 |
+
return []
|
| 47 |
+
|
| 48 |
+
scores = []
|
| 49 |
+
for i, example in enumerate(self.dataset):
|
| 50 |
+
instruction = example.get('instruction', '').lower()
|
| 51 |
+
output = example.get('output', '').lower()
|
| 52 |
+
query = user_query.lower()
|
| 53 |
+
|
| 54 |
+
instruction_score = SequenceMatcher(None, query, instruction).ratio()
|
| 55 |
+
output_score = SequenceMatcher(None, query, output).ratio()
|
| 56 |
+
combined_score = (instruction_score * 0.7) + (output_score * 0.3)
|
| 57 |
+
scores.append((combined_score, i))
|
| 58 |
+
|
| 59 |
+
scores.sort(reverse=True)
|
| 60 |
+
relevant_examples = []
|
| 61 |
+
|
| 62 |
+
for score, idx in scores[:top_k]:
|
| 63 |
+
if score > 0.1:
|
| 64 |
+
relevant_examples.append(self.dataset[idx])
|
| 65 |
+
|
| 66 |
+
return relevant_examples
|
| 67 |
+
|
| 68 |
+
def create_context_prompt(self, user_query, relevant_examples):
|
| 69 |
+
"""Create a prompt with relevant context"""
|
| 70 |
+
if not relevant_examples:
|
| 71 |
+
return user_query
|
| 72 |
+
|
| 73 |
+
context_parts = []
|
| 74 |
+
context_parts.append("Berikut adalah beberapa contoh pertanyaan dan jawaban tentang Textilindo:")
|
| 75 |
+
context_parts.append("")
|
| 76 |
+
|
| 77 |
+
for i, example in enumerate(relevant_examples, 1):
|
| 78 |
+
instruction = example.get('instruction', '')
|
| 79 |
+
output = example.get('output', '')
|
| 80 |
+
context_parts.append(f"Contoh {i}:")
|
| 81 |
+
context_parts.append(f"Pertanyaan: {instruction}")
|
| 82 |
+
context_parts.append(f"Jawaban: {output}")
|
| 83 |
+
context_parts.append("")
|
| 84 |
+
|
| 85 |
+
context_parts.append("Berdasarkan contoh di atas, jawab pertanyaan berikut:")
|
| 86 |
+
context_parts.append(f"Pertanyaan: {user_query}")
|
| 87 |
+
context_parts.append("Jawaban:")
|
| 88 |
+
|
| 89 |
+
return "\n".join(context_parts)
|
| 90 |
+
|
| 91 |
+
def chat(self, message):
|
| 92 |
+
"""Send message to Novita AI with RAG context"""
|
| 93 |
+
relevant_examples = self.find_relevant_context(message, 3)
|
| 94 |
+
|
| 95 |
+
if relevant_examples:
|
| 96 |
+
enhanced_prompt = self.create_context_prompt(message, relevant_examples)
|
| 97 |
+
else:
|
| 98 |
+
enhanced_prompt = message
|
| 99 |
+
|
| 100 |
+
payload = {
|
| 101 |
+
"model": self.model,
|
| 102 |
+
"messages": [{"role": "user", "content": enhanced_prompt}],
|
| 103 |
+
"max_tokens": 300,
|
| 104 |
+
"temperature": 0.7,
|
| 105 |
+
"top_p": 0.9
|
| 106 |
+
}
|
| 107 |
+
|
| 108 |
+
try:
|
| 109 |
+
response = requests.post(
|
| 110 |
+
f"{self.base_url}/chat/completions",
|
| 111 |
+
headers=self.headers,
|
| 112 |
+
json=payload,
|
| 113 |
+
timeout=30
|
| 114 |
+
)
|
| 115 |
+
|
| 116 |
+
if response.status_code == 200:
|
| 117 |
+
result = response.json()
|
| 118 |
+
return result.get('choices', [{}])[0].get('message', {}).get('content', '')
|
| 119 |
+
else:
|
| 120 |
+
return f"Error: {response.status_code}"
|
| 121 |
+
|
| 122 |
+
except Exception as e:
|
| 123 |
+
return f"Error: {str(e)}"
|
| 124 |
+
|
| 125 |
+
# Initialize AI
|
| 126 |
+
ai = TextilindoAI(os.getenv('NOVITA_API_KEY', ''))
|
| 127 |
+
|
| 128 |
+
@app.route('/')
|
| 129 |
+
def home():
|
| 130 |
+
return render_template('chat.html')
|
| 131 |
+
|
| 132 |
+
@app.route('/chat', methods=['POST'])
|
| 133 |
+
def chat():
|
| 134 |
+
data = request.get_json()
|
| 135 |
+
message = data.get('message', '')
|
| 136 |
+
|
| 137 |
+
if not message:
|
| 138 |
+
return jsonify({'response': 'Please enter a message'})
|
| 139 |
+
|
| 140 |
+
response = ai.chat(message)
|
| 141 |
+
return jsonify({'response': response})
|
| 142 |
+
|
| 143 |
+
@app.route('/stats')
|
| 144 |
+
def stats():
|
| 145 |
+
if not ai.dataset:
|
| 146 |
+
return jsonify({'error': 'No dataset loaded'})
|
| 147 |
+
|
| 148 |
+
topics = {}
|
| 149 |
+
for example in ai.dataset:
|
| 150 |
+
metadata = example.get('metadata', {})
|
| 151 |
+
topic = metadata.get('topic', 'unknown')
|
| 152 |
+
topics[topic] = topics.get(topic, 0) + 1
|
| 153 |
+
|
| 154 |
+
return jsonify({
|
| 155 |
+
'total_examples': len(ai.dataset),
|
| 156 |
+
'topics': topics,
|
| 157 |
+
'model': ai.model
|
| 158 |
+
})
|
| 159 |
+
|
| 160 |
+
if __name__ == '__main__':
|
| 161 |
+
app.run(debug=True, host='0.0.0.0', port=5000)
|