Stefanus Simandjuntak commited on
Commit
9b4ef96
·
0 Parent(s):

initial commit

Browse files
.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)