harismlnaslm commited on
Commit
e207dc8
·
1 Parent(s): 81a2146

Add complete scripts directory with training, testing, and deployment tools

Browse files
DEPLOYMENT.md ADDED
@@ -0,0 +1,150 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Textilindo AI Assistant - Hugging Face Spaces Deployment Guide
2
+
3
+ ## 🚀 Quick Setup for Hugging Face Spaces
4
+
5
+ ### 1. Environment Variables Required
6
+
7
+ Set these environment variables in your Hugging Face Space settings:
8
+
9
+ ```bash
10
+ # Required: Hugging Face API Key
11
+ HUGGINGFACE_API_KEY=your_huggingface_api_key_here
12
+
13
+ # Optional: Default model (defaults to meta-llama/Llama-3.1-8B-Instruct)
14
+ DEFAULT_MODEL=meta-llama/Llama-3.1-8B-Instruct
15
+
16
+ # Optional: Alternative lightweight models
17
+ # DEFAULT_MODEL=meta-llama/Llama-3.2-1B-Instruct
18
+ # DEFAULT_MODEL=microsoft/DialoGPT-medium
19
+ ```
20
+
21
+ ### 2. Files Structure
22
+
23
+ Your Hugging Face Space should contain:
24
+
25
+ ```
26
+ ├── app.py # Main FastAPI application
27
+ ├── Dockerfile # Docker configuration
28
+ ├── requirements.txt # Python dependencies
29
+ ├── README.md # Space description
30
+ ├── configs/
31
+ │ ├── system_prompt.md # AI system prompt
32
+ │ └── training_config.yaml # Training configuration
33
+ ├── data/
34
+ │ └── lora_dataset_*.jsonl # Training datasets
35
+ └── templates/
36
+ └── chat.html # Chat interface (optional)
37
+ ```
38
+
39
+ ### 3. Deployment Steps
40
+
41
+ 1. **Create a new Hugging Face Space:**
42
+ - Go to https://huggingface.co/new-space
43
+ - Choose "Docker" as the SDK
44
+ - Name your space (e.g., "textilindo-ai-assistant")
45
+
46
+ 2. **Upload files:**
47
+ - Clone your space repository
48
+ - Copy all files from this project
49
+ - Commit and push to your space
50
+
51
+ 3. **Set environment variables:**
52
+ - Go to your space settings
53
+ - Add the required environment variables
54
+ - Make sure to set `HUGGINGFACE_API_KEY`
55
+
56
+ 4. **Deploy:**
57
+ - Your space will automatically build and deploy
58
+ - Check the logs for any issues
59
+
60
+ ### 4. API Endpoints
61
+
62
+ Once deployed, your space will have these endpoints:
63
+
64
+ - `GET /` - Main chat interface
65
+ - `POST /chat` - Chat API endpoint
66
+ - `GET /health` - Health check
67
+ - `GET /info` - Application information
68
+
69
+ ### 5. Usage Examples
70
+
71
+ #### Chat API
72
+ ```bash
73
+ curl -X POST "https://your-space-name.hf.space/chat" \
74
+ -H "Content-Type: application/json" \
75
+ -d '{"message": "dimana lokasi textilindo?"}'
76
+ ```
77
+
78
+ #### Health Check
79
+ ```bash
80
+ curl "https://your-space-name.hf.space/health"
81
+ ```
82
+
83
+ ### 6. Troubleshooting
84
+
85
+ #### Common Issues:
86
+
87
+ 1. **"HUGGINGFACE_API_KEY not found"**
88
+ - Make sure you've set the environment variable in your space settings
89
+ - The app will use mock responses if no API key is provided
90
+
91
+ 2. **Model loading errors**
92
+ - Check if the model name is correct
93
+ - Try using a lighter model like `meta-llama/Llama-3.2-1B-Instruct`
94
+
95
+ 3. **Memory issues**
96
+ - Hugging Face Spaces have limited memory
97
+ - Use smaller models or reduce batch sizes
98
+
99
+ 4. **Build failures**
100
+ - Check the build logs in your space
101
+ - Ensure all dependencies are in requirements.txt
102
+
103
+ ### 7. Customization
104
+
105
+ #### Change the Model:
106
+ Update the `DEFAULT_MODEL` environment variable:
107
+ ```bash
108
+ DEFAULT_MODEL=meta-llama/Llama-3.2-1B-Instruct
109
+ ```
110
+
111
+ #### Update System Prompt:
112
+ Edit `configs/system_prompt.md` and redeploy.
113
+
114
+ #### Add More Training Data:
115
+ Add more JSONL files to the `data/` directory.
116
+
117
+ ### 8. Performance Optimization
118
+
119
+ For better performance on Hugging Face Spaces:
120
+
121
+ 1. **Use smaller models:**
122
+ - `meta-llama/Llama-3.2-1B-Instruct` (1B parameters)
123
+ - `microsoft/DialoGPT-medium` (355M parameters)
124
+
125
+ 2. **Optimize system prompt:**
126
+ - Keep it concise
127
+ - Remove unnecessary instructions
128
+
129
+ 3. **Monitor resource usage:**
130
+ - Check the space logs
131
+ - Use the `/health` endpoint
132
+
133
+ ### 9. Security Notes
134
+
135
+ - Never commit API keys to your repository
136
+ - Use environment variables for sensitive data
137
+ - The app includes CORS middleware for web access
138
+ - All user inputs are logged (check logs for debugging)
139
+
140
+ ### 10. Support
141
+
142
+ If you encounter issues:
143
+
144
+ 1. Check the space logs
145
+ 2. Verify environment variables
146
+ 3. Test with the `/health` endpoint
147
+ 4. Try the mock responses (without API key)
148
+
149
+ For more help, check the Hugging Face Spaces documentation:
150
+ https://huggingface.co/docs/hub/spaces
scripts/check_training_ready.py ADDED
@@ -0,0 +1,206 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Check if everything is ready for Textilindo AI training
4
+ """
5
+
6
+ import os
7
+ import sys
8
+ import yaml
9
+ from pathlib import Path
10
+
11
+ def check_file_exists(file_path, description):
12
+ """Check if a file exists and print status"""
13
+ if os.path.exists(file_path):
14
+ print(f"✅ {description}: {file_path}")
15
+ return True
16
+ else:
17
+ print(f"❌ {description}: {file_path}")
18
+ return False
19
+
20
+ def check_config():
21
+ """Check configuration files"""
22
+ print("🔍 Checking configuration files...")
23
+
24
+ config_path = "configs/training_config.yaml"
25
+ if not os.path.exists(config_path):
26
+ print(f"❌ Training config not found: {config_path}")
27
+ return False
28
+
29
+ try:
30
+ with open(config_path, 'r') as f:
31
+ config = yaml.safe_load(f)
32
+
33
+ # Check required fields
34
+ required_fields = ['model_name', 'model_path', 'dataset_path', 'lora_config', 'training_config']
35
+ for field in required_fields:
36
+ if field not in config:
37
+ print(f"❌ Missing field in config: {field}")
38
+ return False
39
+
40
+ print("✅ Training configuration is valid")
41
+ return True
42
+
43
+ except Exception as e:
44
+ print(f"❌ Error reading config: {e}")
45
+ return False
46
+
47
+ def check_dataset():
48
+ """Check dataset file"""
49
+ print("\n🔍 Checking dataset...")
50
+
51
+ config_path = "configs/training_config.yaml"
52
+ with open(config_path, 'r') as f:
53
+ config = yaml.safe_load(f)
54
+
55
+ dataset_path = config['dataset_path']
56
+
57
+ if not os.path.exists(dataset_path):
58
+ print(f"❌ Dataset not found: {dataset_path}")
59
+ return False
60
+
61
+ # Check if it's a valid JSONL file
62
+ try:
63
+ import json
64
+ with open(dataset_path, 'r', encoding='utf-8') as f:
65
+ lines = f.readlines()
66
+
67
+ if not lines:
68
+ print("❌ Dataset is empty")
69
+ return False
70
+
71
+ # Check first few lines
72
+ valid_lines = 0
73
+ for i, line in enumerate(lines[:5]): # Check first 5 lines
74
+ line = line.strip()
75
+ if line:
76
+ try:
77
+ json.loads(line)
78
+ valid_lines += 1
79
+ except json.JSONDecodeError:
80
+ print(f"❌ Invalid JSON at line {i+1}")
81
+ return False
82
+
83
+ print(f"✅ Dataset found: {dataset_path}")
84
+ print(f" Total lines: {len(lines)}")
85
+ print(f" Valid JSON lines checked: {valid_lines}")
86
+ return True
87
+
88
+ except Exception as e:
89
+ print(f"❌ Error reading dataset: {e}")
90
+ return False
91
+
92
+ def check_model():
93
+ """Check if base model exists"""
94
+ print("\n🔍 Checking base model...")
95
+
96
+ config_path = "configs/training_config.yaml"
97
+ with open(config_path, 'r') as f:
98
+ config = yaml.safe_load(f)
99
+
100
+ model_path = config['model_path']
101
+
102
+ if not os.path.exists(model_path):
103
+ print(f"❌ Base model not found: {model_path}")
104
+ print(" Run: python scripts/setup_textilindo_training.py")
105
+ return False
106
+
107
+ # Check if it's a valid model directory
108
+ required_files = ['config.json']
109
+ optional_files = ['tokenizer.json', 'tokenizer_config.json']
110
+
111
+ for file in required_files:
112
+ if not os.path.exists(os.path.join(model_path, file)):
113
+ print(f"❌ Model file missing: {file}")
114
+ return False
115
+
116
+ # Check for at least one tokenizer file
117
+ tokenizer_found = any(os.path.exists(os.path.join(model_path, file)) for file in optional_files)
118
+ if not tokenizer_found:
119
+ print("❌ No tokenizer files found")
120
+ return False
121
+
122
+ print(f"✅ Base model found: {model_path}")
123
+ return True
124
+
125
+ def check_system_prompt():
126
+ """Check system prompt file"""
127
+ print("\n🔍 Checking system prompt...")
128
+
129
+ system_prompt_path = "configs/system_prompt.md"
130
+
131
+ if not os.path.exists(system_prompt_path):
132
+ print(f"❌ System prompt not found: {system_prompt_path}")
133
+ return False
134
+
135
+ try:
136
+ with open(system_prompt_path, 'r', encoding='utf-8') as f:
137
+ content = f.read()
138
+
139
+ if 'SYSTEM_PROMPT' not in content:
140
+ print("❌ SYSTEM_PROMPT not found in file")
141
+ return False
142
+
143
+ print(f"✅ System prompt found: {system_prompt_path}")
144
+ return True
145
+
146
+ except Exception as e:
147
+ print(f"❌ Error reading system prompt: {e}")
148
+ return False
149
+
150
+ def check_requirements():
151
+ """Check Python requirements"""
152
+ print("\n🔍 Checking Python requirements...")
153
+
154
+ required_packages = [
155
+ 'torch',
156
+ 'transformers',
157
+ 'peft',
158
+ 'datasets',
159
+ 'accelerate',
160
+ 'bitsandbytes',
161
+ 'yaml'
162
+ ]
163
+
164
+ missing_packages = []
165
+ for package in required_packages:
166
+ try:
167
+ __import__(package)
168
+ print(f"✅ {package}")
169
+ except ImportError:
170
+ missing_packages.append(package)
171
+ print(f"❌ {package}")
172
+
173
+ if missing_packages:
174
+ print(f"\n❌ Missing packages: {', '.join(missing_packages)}")
175
+ print("Install with: pip install " + " ".join(missing_packages))
176
+ return False
177
+
178
+ return True
179
+
180
+ def main():
181
+ print("🔍 Textilindo AI Training - Readiness Check")
182
+ print("=" * 50)
183
+
184
+ all_ready = True
185
+
186
+ # Check all components
187
+ all_ready &= check_config()
188
+ all_ready &= check_dataset()
189
+ all_ready &= check_model()
190
+ all_ready &= check_system_prompt()
191
+ all_ready &= check_requirements()
192
+
193
+ print("\n" + "=" * 50)
194
+
195
+ if all_ready:
196
+ print("✅ Everything is ready for training!")
197
+ print("\n📋 Next steps:")
198
+ print("1. Run training: python scripts/train_textilindo_ai.py")
199
+ print("2. Or use runner: ./run_textilindo_training.sh")
200
+ else:
201
+ print("❌ Some components are missing or invalid")
202
+ print("Please fix the issues above before training")
203
+ sys.exit(1)
204
+
205
+ if __name__ == "__main__":
206
+ main()
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/inference_textilindo_ai.py ADDED
@@ -0,0 +1,178 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Inference script untuk Textilindo AI Assistant
4
+ Menggunakan model yang sudah di-fine-tune dengan LoRA
5
+ """
6
+
7
+ import os
8
+ import sys
9
+ import torch
10
+ import argparse
11
+ from pathlib import Path
12
+ from transformers import AutoTokenizer, AutoModelForCausalLM
13
+ from peft import PeftModel
14
+ import logging
15
+
16
+ logging.basicConfig(level=logging.INFO)
17
+ logger = logging.getLogger(__name__)
18
+
19
+ def load_system_prompt(system_prompt_path):
20
+ """Load system prompt from markdown file"""
21
+ try:
22
+ with open(system_prompt_path, 'r', encoding='utf-8') as f:
23
+ content = f.read()
24
+
25
+ # Extract SYSTEM_PROMPT from markdown
26
+ if 'SYSTEM_PROMPT = """' in content:
27
+ start = content.find('SYSTEM_PROMPT = """') + len('SYSTEM_PROMPT = """')
28
+ end = content.find('"""', start)
29
+ system_prompt = content[start:end].strip()
30
+ else:
31
+ # Fallback: use entire content
32
+ system_prompt = content.strip()
33
+
34
+ return system_prompt
35
+ except Exception as e:
36
+ logger.error(f"Error loading system prompt: {e}")
37
+ return None
38
+
39
+ def load_model(model_path, lora_path=None):
40
+ """Load model with optional LoRA weights"""
41
+ logger.info(f"Loading base model from: {model_path}")
42
+
43
+ # Load tokenizer
44
+ tokenizer = AutoTokenizer.from_pretrained(
45
+ model_path,
46
+ trust_remote_code=True
47
+ )
48
+
49
+ if tokenizer.pad_token is None:
50
+ tokenizer.pad_token = tokenizer.eos_token
51
+
52
+ # Load base model
53
+ model = AutoModelForCausalLM.from_pretrained(
54
+ model_path,
55
+ torch_dtype=torch.float16,
56
+ device_map="auto",
57
+ trust_remote_code=True
58
+ )
59
+
60
+ # Load LoRA weights if provided
61
+ if lora_path and os.path.exists(lora_path):
62
+ logger.info(f"Loading LoRA weights from: {lora_path}")
63
+ model = PeftModel.from_pretrained(model, lora_path)
64
+ else:
65
+ logger.warning("No LoRA weights found, using base model")
66
+
67
+ return model, tokenizer
68
+
69
+ def generate_response(model, tokenizer, user_input, system_prompt, max_length=512):
70
+ """Generate response from the model"""
71
+ # Create full prompt with system prompt
72
+ full_prompt = f"<|system|>\n{system_prompt}\n<|user|>\n{user_input}\n<|assistant|>\n"
73
+
74
+ inputs = tokenizer(full_prompt, return_tensors="pt").to(model.device)
75
+
76
+ with torch.no_grad():
77
+ outputs = model.generate(
78
+ **inputs,
79
+ max_length=max_length,
80
+ temperature=0.7,
81
+ top_p=0.9,
82
+ top_k=40,
83
+ repetition_penalty=1.1,
84
+ do_sample=True,
85
+ pad_token_id=tokenizer.eos_token_id,
86
+ eos_token_id=tokenizer.eos_token_id,
87
+ stop_strings=["<|end|>", "<|user|>"]
88
+ )
89
+
90
+ response = tokenizer.decode(outputs[0], skip_special_tokens=True)
91
+
92
+ # Extract only the assistant's response
93
+ if "<|assistant|>" in response:
94
+ assistant_response = response.split("<|assistant|>")[-1].strip()
95
+ # Remove any remaining special tokens
96
+ assistant_response = assistant_response.replace("<|end|>", "").strip()
97
+ return assistant_response
98
+ else:
99
+ return response
100
+
101
+ def interactive_chat(model, tokenizer, system_prompt):
102
+ """Interactive chat mode"""
103
+ print("🤖 Textilindo AI Assistant - Chat Mode")
104
+ print("=" * 60)
105
+ print("Type 'quit' to exit")
106
+ print("-" * 60)
107
+
108
+ while True:
109
+ try:
110
+ user_input = input("\n👤 Customer: ").strip()
111
+
112
+ if user_input.lower() in ['quit', 'exit', 'q']:
113
+ print("👋 Terima kasih! Sampai jumpa!")
114
+ break
115
+
116
+ if not user_input:
117
+ continue
118
+
119
+ print("\n🤖 Textilindo AI: ", end="", flush=True)
120
+ response = generate_response(model, tokenizer, user_input, system_prompt)
121
+ print(response)
122
+
123
+ except KeyboardInterrupt:
124
+ print("\n👋 Terima kasih! Sampai jumpa!")
125
+ break
126
+ except Exception as e:
127
+ logger.error(f"Error generating response: {e}")
128
+ print(f"❌ Error: {e}")
129
+
130
+ def main():
131
+ parser = argparse.ArgumentParser(description='Textilindo AI Assistant Inference')
132
+ parser.add_argument('--model_path', type=str, default='./models/llama-3.1-8b-instruct',
133
+ help='Path to base model')
134
+ parser.add_argument('--lora_path', type=str, default=None,
135
+ help='Path to LoRA weights')
136
+ parser.add_argument('--system_prompt', type=str, default='configs/system_prompt.md',
137
+ help='Path to system prompt file')
138
+ parser.add_argument('--prompt', type=str, default=None,
139
+ help='Single prompt to process')
140
+
141
+ args = parser.parse_args()
142
+
143
+ print("🤖 Textilindo AI Assistant - Inference")
144
+ print("=" * 60)
145
+
146
+ # Load system prompt
147
+ system_prompt = load_system_prompt(args.system_prompt)
148
+ if not system_prompt:
149
+ print(f"❌ System prompt tidak ditemukan: {args.system_prompt}")
150
+ sys.exit(1)
151
+
152
+ # Check if model exists
153
+ if not os.path.exists(args.model_path):
154
+ print(f"❌ Base model tidak ditemukan: {args.model_path}")
155
+ print("Jalankan setup_textilindo_training.py terlebih dahulu")
156
+ sys.exit(1)
157
+
158
+ try:
159
+ # Load model
160
+ print("1️⃣ Loading model...")
161
+ model, tokenizer = load_model(args.model_path, args.lora_path)
162
+ print("✅ Model loaded successfully!")
163
+
164
+ if args.prompt:
165
+ # Single prompt mode
166
+ print(f"\n📝 Processing prompt: {args.prompt}")
167
+ response = generate_response(model, tokenizer, args.prompt, system_prompt)
168
+ print(f"\n🤖 Response: {response}")
169
+ else:
170
+ # Interactive mode
171
+ interactive_chat(model, tokenizer, system_prompt)
172
+
173
+ except Exception as e:
174
+ logger.error(f"Error: {e}")
175
+ print(f"❌ Error loading model: {e}")
176
+
177
+ if __name__ == "__main__":
178
+ 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/setup_textilindo_training.py ADDED
@@ -0,0 +1,175 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Setup script untuk Textilindo AI Assistant training
4
+ Download model dan prepare environment
5
+ """
6
+
7
+ import os
8
+ import sys
9
+ import yaml
10
+ import torch
11
+ from pathlib import Path
12
+ from transformers import AutoTokenizer, AutoModelForCausalLM
13
+ import logging
14
+
15
+ logging.basicConfig(level=logging.INFO)
16
+ logger = logging.getLogger(__name__)
17
+
18
+ def load_config(config_path):
19
+ """Load configuration from YAML file"""
20
+ try:
21
+ with open(config_path, 'r') as f:
22
+ config = yaml.safe_load(f)
23
+ return config
24
+ except Exception as e:
25
+ logger.error(f"Error loading config: {e}")
26
+ return None
27
+
28
+ def download_model(config):
29
+ """Download base model"""
30
+ model_name = config['model_name']
31
+ model_path = config['model_path']
32
+
33
+ logger.info(f"Downloading model: {model_name}")
34
+ logger.info(f"Target path: {model_path}")
35
+
36
+ # Create models directory
37
+ Path(model_path).mkdir(parents=True, exist_ok=True)
38
+
39
+ try:
40
+ # Download tokenizer
41
+ logger.info("Downloading tokenizer...")
42
+ tokenizer = AutoTokenizer.from_pretrained(
43
+ model_name,
44
+ trust_remote_code=True,
45
+ cache_dir=model_path
46
+ )
47
+
48
+ # Download model with memory optimization
49
+ logger.info("Downloading model...")
50
+ model = AutoModelForCausalLM.from_pretrained(
51
+ model_name,
52
+ torch_dtype=torch.float16,
53
+ trust_remote_code=True,
54
+ cache_dir=model_path,
55
+ low_cpu_mem_usage=True,
56
+ load_in_8bit=True # Use 8-bit quantization for memory efficiency
57
+ )
58
+
59
+ # Save to local path
60
+ logger.info(f"Saving model to: {model_path}")
61
+ tokenizer.save_pretrained(model_path)
62
+ model.save_pretrained(model_path)
63
+
64
+ logger.info("✅ Model downloaded successfully!")
65
+ return True
66
+
67
+ except Exception as e:
68
+ logger.error(f"Error downloading model: {e}")
69
+ return False
70
+
71
+ def check_requirements():
72
+ """Check if all requirements are met"""
73
+ print("🔍 Checking requirements...")
74
+
75
+ # Check Python version
76
+ if sys.version_info < (3, 8):
77
+ print("❌ Python 3.8+ required")
78
+ return False
79
+
80
+ # Check PyTorch
81
+ try:
82
+ import torch
83
+ print(f"✅ PyTorch {torch.__version__}")
84
+ except ImportError:
85
+ print("❌ PyTorch not installed")
86
+ return False
87
+
88
+ # Check CUDA availability
89
+ if torch.cuda.is_available():
90
+ print(f"✅ CUDA available: {torch.cuda.get_device_name(0)}")
91
+ print(f" GPU Memory: {torch.cuda.get_device_properties(0).total_memory / 1024**3:.1f} GB")
92
+ else:
93
+ print("⚠️ CUDA not available - training will be slower on CPU")
94
+
95
+ # Check required packages
96
+ required_packages = [
97
+ 'transformers',
98
+ 'peft',
99
+ 'datasets',
100
+ 'accelerate',
101
+ 'bitsandbytes'
102
+ ]
103
+
104
+ missing_packages = []
105
+ for package in required_packages:
106
+ try:
107
+ __import__(package)
108
+ print(f"✅ {package}")
109
+ except ImportError:
110
+ missing_packages.append(package)
111
+ print(f"❌ {package}")
112
+
113
+ if missing_packages:
114
+ print(f"\n❌ Missing packages: {', '.join(missing_packages)}")
115
+ print("Install with: pip install " + " ".join(missing_packages))
116
+ return False
117
+
118
+ return True
119
+
120
+ def main():
121
+ print("🚀 Textilindo AI Assistant - Setup")
122
+ print("=" * 50)
123
+
124
+ # Check requirements
125
+ if not check_requirements():
126
+ print("\n❌ Requirements not met. Please install missing packages.")
127
+ sys.exit(1)
128
+
129
+ # Load configuration
130
+ config_path = "configs/training_config.yaml"
131
+ if not os.path.exists(config_path):
132
+ print(f"❌ Config file tidak ditemukan: {config_path}")
133
+ sys.exit(1)
134
+
135
+ config = load_config(config_path)
136
+ if not config:
137
+ sys.exit(1)
138
+
139
+ # Check if model already exists
140
+ model_path = config['model_path']
141
+ if os.path.exists(model_path) and os.path.exists(os.path.join(model_path, "config.json")):
142
+ print(f"✅ Model already exists: {model_path}")
143
+ print("Skipping download...")
144
+ else:
145
+ # Download model
146
+ print("1️⃣ Downloading base model...")
147
+ if not download_model(config):
148
+ print("❌ Failed to download model")
149
+ sys.exit(1)
150
+
151
+ # Check dataset
152
+ dataset_path = config['dataset_path']
153
+ if not os.path.exists(dataset_path):
154
+ print(f"❌ Dataset tidak ditemukan: {dataset_path}")
155
+ print("Please ensure your dataset is in the correct location")
156
+ sys.exit(1)
157
+ else:
158
+ print(f"✅ Dataset found: {dataset_path}")
159
+
160
+ # Check system prompt
161
+ system_prompt_path = "configs/system_prompt.md"
162
+ if not os.path.exists(system_prompt_path):
163
+ print(f"❌ System prompt tidak ditemukan: {system_prompt_path}")
164
+ sys.exit(1)
165
+ else:
166
+ print(f"✅ System prompt found: {system_prompt_path}")
167
+
168
+ print("\n✅ Setup completed successfully!")
169
+ print("\n📋 Next steps:")
170
+ print("1. Run training: python scripts/train_textilindo_ai.py")
171
+ print("2. Test model: python scripts/test_textilindo_ai.py")
172
+ print("3. Test with LoRA: python scripts/test_textilindo_ai.py --lora_path models/textilindo-ai-lora-YYYYMMDD_HHMMSS")
173
+
174
+ if __name__ == "__main__":
175
+ main()
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/test_textilindo_ai.py ADDED
@@ -0,0 +1,235 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Script untuk testing Textilindo AI Assistant yang sudah di-fine-tune
4
+ """
5
+
6
+ import os
7
+ import sys
8
+ import yaml
9
+ import torch
10
+ import argparse
11
+ from pathlib import Path
12
+ from transformers import AutoTokenizer, AutoModelForCausalLM
13
+ from peft import PeftModel
14
+ import logging
15
+
16
+ logging.basicConfig(level=logging.INFO)
17
+ logger = logging.getLogger(__name__)
18
+
19
+ def load_system_prompt(system_prompt_path):
20
+ """Load system prompt from markdown file"""
21
+ try:
22
+ with open(system_prompt_path, 'r', encoding='utf-8') as f:
23
+ content = f.read()
24
+
25
+ # Extract SYSTEM_PROMPT from markdown
26
+ if 'SYSTEM_PROMPT = """' in content:
27
+ start = content.find('SYSTEM_PROMPT = """') + len('SYSTEM_PROMPT = """')
28
+ end = content.find('"""', start)
29
+ system_prompt = content[start:end].strip()
30
+ else:
31
+ # Fallback: use entire content
32
+ system_prompt = content.strip()
33
+
34
+ return system_prompt
35
+ except Exception as e:
36
+ logger.error(f"Error loading system prompt: {e}")
37
+ return None
38
+
39
+ def load_finetuned_model(model_path, lora_weights_path, system_prompt):
40
+ """Load fine-tuned model with LoRA weights"""
41
+ logger.info(f"Loading base model from: {model_path}")
42
+
43
+ # Load base model
44
+ model = AutoModelForCausalLM.from_pretrained(
45
+ model_path,
46
+ torch_dtype=torch.float16,
47
+ device_map="auto",
48
+ trust_remote_code=True
49
+ )
50
+
51
+ # Load LoRA weights if available
52
+ if lora_weights_path and os.path.exists(lora_weights_path):
53
+ logger.info(f"Loading LoRA weights from: {lora_weights_path}")
54
+ model = PeftModel.from_pretrained(model, lora_weights_path)
55
+ else:
56
+ logger.warning("No LoRA weights found, using base model")
57
+
58
+ # Load tokenizer
59
+ tokenizer = AutoTokenizer.from_pretrained(
60
+ model_path,
61
+ trust_remote_code=True
62
+ )
63
+
64
+ if tokenizer.pad_token is None:
65
+ tokenizer.pad_token = tokenizer.eos_token
66
+
67
+ return model, tokenizer
68
+
69
+ def generate_response(model, tokenizer, user_input, system_prompt, max_length=512):
70
+ """Generate response from the model"""
71
+ # Create full prompt with system prompt
72
+ full_prompt = f"<|system|>\n{system_prompt}\n<|user|>\n{user_input}\n<|assistant|>\n"
73
+
74
+ inputs = tokenizer(full_prompt, return_tensors="pt").to(model.device)
75
+
76
+ with torch.no_grad():
77
+ outputs = model.generate(
78
+ **inputs,
79
+ max_length=max_length,
80
+ temperature=0.7,
81
+ top_p=0.9,
82
+ top_k=40,
83
+ repetition_penalty=1.1,
84
+ do_sample=True,
85
+ pad_token_id=tokenizer.eos_token_id,
86
+ eos_token_id=tokenizer.eos_token_id,
87
+ stop_strings=["<|end|>", "<|user|>"]
88
+ )
89
+
90
+ response = tokenizer.decode(outputs[0], skip_special_tokens=True)
91
+
92
+ # Extract only the assistant's response
93
+ if "<|assistant|>" in response:
94
+ assistant_response = response.split("<|assistant|>")[-1].strip()
95
+ # Remove any remaining special tokens
96
+ assistant_response = assistant_response.replace("<|end|>", "").strip()
97
+ return assistant_response
98
+ else:
99
+ return response
100
+
101
+ def interactive_test(model, tokenizer, system_prompt):
102
+ """Interactive testing mode"""
103
+ print("🤖 Textilindo AI Assistant - Interactive Mode")
104
+ print("=" * 60)
105
+ print("Type 'quit' to exit")
106
+ print("-" * 60)
107
+
108
+ while True:
109
+ try:
110
+ user_input = input("\n👤 Customer: ").strip()
111
+
112
+ if user_input.lower() in ['quit', 'exit', 'q']:
113
+ print("👋 Terima kasih! Sampai jumpa!")
114
+ break
115
+
116
+ if not user_input:
117
+ continue
118
+
119
+ print("\n🤖 Textilindo AI: ", end="", flush=True)
120
+ response = generate_response(model, tokenizer, user_input, system_prompt)
121
+ print(response)
122
+
123
+ except KeyboardInterrupt:
124
+ print("\n👋 Terima kasih! Sampai jumpa!")
125
+ break
126
+ except Exception as e:
127
+ logger.error(f"Error generating response: {e}")
128
+ print(f"❌ Error: {e}")
129
+
130
+ def batch_test(model, tokenizer, system_prompt, test_cases):
131
+ """Batch testing with predefined test cases"""
132
+ print("🧪 Textilindo AI Assistant - Batch Testing")
133
+ print("=" * 60)
134
+
135
+ for i, test_case in enumerate(test_cases, 1):
136
+ print(f"\n📝 Test Case {i}: {test_case['prompt']}")
137
+ print("-" * 40)
138
+
139
+ try:
140
+ response = generate_response(model, tokenizer, test_case['prompt'], system_prompt)
141
+ print(f"🤖 Response: {response}")
142
+
143
+ if 'expected' in test_case:
144
+ print(f"🎯 Expected: {test_case['expected']}")
145
+
146
+ except Exception as e:
147
+ logger.error(f"Error in test case {i}: {e}")
148
+ print(f"❌ Error: {e}")
149
+
150
+ def main():
151
+ parser = argparse.ArgumentParser(description='Test Textilindo AI Assistant')
152
+ parser.add_argument('--model_path', type=str, default='./models/llama-3.1-8b-instruct',
153
+ help='Path to base model')
154
+ parser.add_argument('--lora_path', type=str, default=None,
155
+ help='Path to LoRA weights')
156
+ parser.add_argument('--system_prompt', type=str, default='configs/system_prompt.md',
157
+ help='Path to system prompt file')
158
+
159
+ args = parser.parse_args()
160
+
161
+ print("🧪 Textilindo AI Assistant Testing")
162
+ print("=" * 60)
163
+
164
+ # Load system prompt
165
+ system_prompt = load_system_prompt(args.system_prompt)
166
+ if not system_prompt:
167
+ print(f"❌ System prompt tidak ditemukan: {args.system_prompt}")
168
+ sys.exit(1)
169
+
170
+ # Check if model exists
171
+ if not os.path.exists(args.model_path):
172
+ print(f"❌ Base model tidak ditemukan: {args.model_path}")
173
+ print("Jalankan download_model.py terlebih dahulu")
174
+ sys.exit(1)
175
+
176
+ try:
177
+ # Load model
178
+ print("1️⃣ Loading model...")
179
+ model, tokenizer = load_finetuned_model(args.model_path, args.lora_path, system_prompt)
180
+ print("✅ Model loaded successfully!")
181
+
182
+ # Test cases specific to Textilindo
183
+ test_cases = [
184
+ {
185
+ "prompt": "dimana lokasi textilindo?",
186
+ "expected": "Textilindo berkantor pusat di Jl. Raya Prancis No.39, Kosambi Tim., Kec. Kosambi, Kabupaten Tangerang, Banten 15213"
187
+ },
188
+ {
189
+ "prompt": "Jam berapa textilindo beroperasional?",
190
+ "expected": "Jam operasional Senin-Jumat 08:00-17:00, Sabtu 08:00-12:00."
191
+ },
192
+ {
193
+ "prompt": "Berapa ketentuan pembelian?",
194
+ "expected": "Minimal order 1 roll per jenis kain"
195
+ },
196
+ {
197
+ "prompt": "bagimana dengan pembayarannya?",
198
+ "expected": "Pembayaran dapat dilakukan via transfer bank atau cash on delivery"
199
+ },
200
+ {
201
+ "prompt": "apa ada gratis ongkir?",
202
+ "expected": "Gratis ongkir untuk order minimal 5 roll."
203
+ },
204
+ {
205
+ "prompt": "Apa bisa dikirimkan sample? apa gratis?",
206
+ "expected": "hallo kak untuk sampel kita bisa kirimkan gratis ya kak 😊"
207
+ }
208
+ ]
209
+
210
+ # Choose testing mode
211
+ print("\n2️⃣ Pilih mode testing:")
212
+ print("1. Interactive mode (chat)")
213
+ print("2. Batch testing")
214
+ print("3. Custom prompt")
215
+
216
+ choice = input("\nPilihan (1-3): ").strip()
217
+
218
+ if choice == "1":
219
+ interactive_test(model, tokenizer, system_prompt)
220
+ elif choice == "2":
221
+ batch_test(model, tokenizer, system_prompt, test_cases)
222
+ elif choice == "3":
223
+ custom_prompt = input("Masukkan prompt custom: ").strip()
224
+ if custom_prompt:
225
+ response = generate_response(model, tokenizer, custom_prompt, system_prompt)
226
+ print(f"\n🤖 Response: {response}")
227
+ else:
228
+ print("❌ Pilihan tidak valid")
229
+
230
+ except Exception as e:
231
+ logger.error(f"Error: {e}")
232
+ print(f"❌ Error loading model: {e}")
233
+
234
+ if __name__ == "__main__":
235
+ main()
scripts/train_textilindo_ai.py ADDED
@@ -0,0 +1,282 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Script untuk fine-tuning Llama 3.2 1B dengan LoRA untuk Textilindo AI Assistant
4
+ Menggunakan system prompt dan dataset khusus Textilindo
5
+ """
6
+
7
+ import os
8
+ import sys
9
+ import yaml
10
+ import json
11
+ import torch
12
+ from pathlib import Path
13
+ from transformers import (
14
+ AutoTokenizer,
15
+ AutoModelForCausalLM,
16
+ TrainingArguments,
17
+ Trainer,
18
+ DataCollatorForLanguageModeling
19
+ )
20
+ from peft import (
21
+ LoraConfig,
22
+ get_peft_model,
23
+ TaskType,
24
+ prepare_model_for_kbit_training
25
+ )
26
+ from datasets import Dataset
27
+ import logging
28
+ from datetime import datetime
29
+
30
+ # Setup logging
31
+ logging.basicConfig(level=logging.INFO)
32
+ logger = logging.getLogger(__name__)
33
+
34
+ def load_config(config_path):
35
+ """Load configuration from YAML file"""
36
+ try:
37
+ with open(config_path, 'r') as f:
38
+ config = yaml.safe_load(f)
39
+ return config
40
+ except Exception as e:
41
+ logger.error(f"Error loading config: {e}")
42
+ return None
43
+
44
+ def load_system_prompt(system_prompt_path):
45
+ """Load system prompt from markdown file"""
46
+ try:
47
+ with open(system_prompt_path, 'r', encoding='utf-8') as f:
48
+ content = f.read()
49
+
50
+ # Extract SYSTEM_PROMPT from markdown
51
+ if 'SYSTEM_PROMPT = """' in content:
52
+ start = content.find('SYSTEM_PROMPT = """') + len('SYSTEM_PROMPT = """')
53
+ end = content.find('"""', start)
54
+ system_prompt = content[start:end].strip()
55
+ else:
56
+ # Fallback: use entire content
57
+ system_prompt = content.strip()
58
+
59
+ return system_prompt
60
+ except Exception as e:
61
+ logger.error(f"Error loading system prompt: {e}")
62
+ return None
63
+
64
+ def load_model_and_tokenizer(config):
65
+ """Load base model and tokenizer"""
66
+ model_path = config['model_path']
67
+
68
+ logger.info(f"Loading model from: {model_path}")
69
+
70
+ # Load tokenizer
71
+ tokenizer = AutoTokenizer.from_pretrained(
72
+ model_path,
73
+ trust_remote_code=True,
74
+ padding_side="right"
75
+ )
76
+
77
+ if tokenizer.pad_token is None:
78
+ tokenizer.pad_token = tokenizer.eos_token
79
+
80
+ # Load model with memory optimization
81
+ model = AutoModelForCausalLM.from_pretrained(
82
+ model_path,
83
+ torch_dtype=torch.float16,
84
+ device_map="auto",
85
+ trust_remote_code=True,
86
+ low_cpu_mem_usage=True,
87
+ load_in_8bit=True # Use 8-bit quantization for memory efficiency
88
+ )
89
+
90
+ # Prepare model for k-bit training
91
+ model = prepare_model_for_kbit_training(model)
92
+
93
+ return model, tokenizer
94
+
95
+ def setup_lora_config(config):
96
+ """Setup LoRA configuration"""
97
+ lora_config = config['lora_config']
98
+
99
+ peft_config = LoraConfig(
100
+ task_type=TaskType.CAUSAL_LM,
101
+ r=lora_config['r'],
102
+ lora_alpha=lora_config['lora_alpha'],
103
+ lora_dropout=lora_config['lora_dropout'],
104
+ target_modules=lora_config['target_modules'],
105
+ bias="none",
106
+ )
107
+
108
+ return peft_config
109
+
110
+ def prepare_textilindo_dataset(data_path, tokenizer, system_prompt, max_length=2048):
111
+ """Prepare Textilindo dataset for training with system prompt"""
112
+ logger.info(f"Loading dataset from: {data_path}")
113
+
114
+ # Load JSONL dataset
115
+ data = []
116
+ with open(data_path, 'r', encoding='utf-8') as f:
117
+ for line_num, line in enumerate(f, 1):
118
+ line = line.strip()
119
+ if line:
120
+ try:
121
+ json_obj = json.loads(line)
122
+ data.append(json_obj)
123
+ except json.JSONDecodeError as e:
124
+ logger.warning(f"Invalid JSON at line {line_num}: {e}")
125
+ continue
126
+
127
+ if not data:
128
+ raise ValueError("No valid JSON objects found in JSONL file")
129
+
130
+ logger.info(f"Loaded {len(data)} samples from JSONL file")
131
+
132
+ # Convert to training format with system prompt
133
+ training_data = []
134
+ for item in data:
135
+ # Extract instruction and output
136
+ instruction = item.get('instruction', '')
137
+ output = item.get('output', '')
138
+
139
+ if not instruction or not output:
140
+ continue
141
+
142
+ # Create training text with system prompt
143
+ training_text = f"<|system|>\n{system_prompt}\n<|user|>\n{instruction}\n<|assistant|>\n{output}<|end|>"
144
+
145
+ training_data.append({
146
+ 'text': training_text,
147
+ 'instruction': instruction,
148
+ 'output': output
149
+ })
150
+
151
+ # Convert to Dataset
152
+ dataset = Dataset.from_list(training_data)
153
+ logger.info(f"Prepared {len(dataset)} training samples")
154
+
155
+ def tokenize_function(examples):
156
+ # Tokenize the texts
157
+ tokenized = tokenizer(
158
+ examples['text'],
159
+ truncation=True,
160
+ padding=True,
161
+ max_length=max_length,
162
+ return_tensors="pt"
163
+ )
164
+ return tokenized
165
+
166
+ # Tokenize dataset
167
+ tokenized_dataset = dataset.map(
168
+ tokenize_function,
169
+ batched=True,
170
+ remove_columns=dataset.column_names
171
+ )
172
+
173
+ return tokenized_dataset
174
+
175
+ def train_model(model, tokenizer, dataset, config, output_dir):
176
+ """Train the model with LoRA"""
177
+ training_config = config['training_config']
178
+
179
+ # Setup training arguments
180
+ training_args = TrainingArguments(
181
+ output_dir=output_dir,
182
+ num_train_epochs=training_config['num_epochs'],
183
+ per_device_train_batch_size=training_config['batch_size'],
184
+ gradient_accumulation_steps=training_config['gradient_accumulation_steps'],
185
+ learning_rate=training_config['learning_rate'],
186
+ warmup_steps=training_config['warmup_steps'],
187
+ save_steps=training_config['save_steps'],
188
+ eval_steps=training_config['eval_steps'],
189
+ logging_steps=training_config.get('logging_steps', 10),
190
+ save_total_limit=training_config.get('save_total_limit', 3),
191
+ prediction_loss_only=training_config.get('prediction_loss_only', True),
192
+ remove_unused_columns=training_config.get('remove_unused_columns', False),
193
+ push_to_hub=training_config.get('push_to_hub', False),
194
+ report_to=training_config.get('report_to', None),
195
+ fp16=True, # Enable mixed precision training
196
+ dataloader_pin_memory=False, # Reduce memory usage
197
+ )
198
+
199
+ # Setup data collator
200
+ data_collator = DataCollatorForLanguageModeling(
201
+ tokenizer=tokenizer,
202
+ mlm=False,
203
+ )
204
+
205
+ # Setup trainer
206
+ trainer = Trainer(
207
+ model=model,
208
+ args=training_args,
209
+ train_dataset=dataset,
210
+ data_collator=data_collator,
211
+ tokenizer=tokenizer,
212
+ )
213
+
214
+ # Start training
215
+ logger.info("Starting training...")
216
+ trainer.train()
217
+
218
+ # Save the model
219
+ trainer.save_model()
220
+ logger.info(f"Model saved to: {output_dir}")
221
+
222
+ def main():
223
+ print("🚀 Textilindo AI Assistant - LoRA Fine-tuning")
224
+ print("=" * 60)
225
+
226
+ # Load configuration
227
+ config_path = "configs/training_config.yaml"
228
+ if not os.path.exists(config_path):
229
+ print(f"❌ Config file tidak ditemukan: {config_path}")
230
+ sys.exit(1)
231
+
232
+ config = load_config(config_path)
233
+ if not config:
234
+ sys.exit(1)
235
+
236
+ # Load system prompt
237
+ system_prompt_path = "configs/system_prompt.md"
238
+ if not os.path.exists(system_prompt_path):
239
+ print(f"❌ System prompt tidak ditemukan: {system_prompt_path}")
240
+ sys.exit(1)
241
+
242
+ system_prompt = load_system_prompt(system_prompt_path)
243
+ if not system_prompt:
244
+ sys.exit(1)
245
+
246
+ # Setup paths
247
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
248
+ output_dir = Path(f"models/textilindo-ai-lora-{timestamp}")
249
+ output_dir.mkdir(parents=True, exist_ok=True)
250
+
251
+ # Check if dataset exists
252
+ data_path = config['dataset_path']
253
+ if not os.path.exists(data_path):
254
+ print(f"❌ Dataset tidak ditemukan: {data_path}")
255
+ sys.exit(1)
256
+
257
+ # Load model and tokenizer
258
+ print("1️⃣ Loading model and tokenizer...")
259
+ model, tokenizer = load_model_and_tokenizer(config)
260
+
261
+ # Setup LoRA
262
+ print("2️⃣ Setting up LoRA configuration...")
263
+ peft_config = setup_lora_config(config)
264
+ model = get_peft_model(model, peft_config)
265
+
266
+ # Print trainable parameters
267
+ model.print_trainable_parameters()
268
+
269
+ # Prepare dataset
270
+ print("3️⃣ Preparing Textilindo dataset...")
271
+ dataset = prepare_textilindo_dataset(data_path, tokenizer, system_prompt, config['max_length'])
272
+
273
+ # Train model
274
+ print("4️⃣ Starting training...")
275
+ train_model(model, tokenizer, dataset, config, output_dir)
276
+
277
+ print("✅ Training selesai!")
278
+ print(f"📁 Model tersimpan di: {output_dir}")
279
+ print(f"🔧 Untuk testing, jalankan: python scripts/test_textilindo_ai.py --model_path {output_dir}")
280
+
281
+ if __name__ == "__main__":
282
+ main()
scripts/train_textilindo_ai_optimized.py ADDED
@@ -0,0 +1,296 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Memory-optimized training script untuk Textilindo AI Assistant
4
+ Optimized untuk Llama 3.1 8B dengan LoRA pada laptop
5
+ """
6
+
7
+ import os
8
+ import sys
9
+ import yaml
10
+ import json
11
+ import torch
12
+ from pathlib import Path
13
+ from transformers import (
14
+ AutoTokenizer,
15
+ AutoModelForCausalLM,
16
+ TrainingArguments,
17
+ Trainer,
18
+ DataCollatorForLanguageModeling,
19
+ BitsAndBytesConfig
20
+ )
21
+ from peft import (
22
+ LoraConfig,
23
+ get_peft_model,
24
+ TaskType,
25
+ prepare_model_for_kbit_training
26
+ )
27
+ from datasets import Dataset
28
+ import logging
29
+ from datetime import datetime
30
+ import gc
31
+
32
+ # Setup logging
33
+ logging.basicConfig(level=logging.INFO)
34
+ logger = logging.getLogger(__name__)
35
+
36
+ def load_config(config_path):
37
+ """Load configuration from YAML file"""
38
+ try:
39
+ with open(config_path, 'r') as f:
40
+ config = yaml.safe_load(f)
41
+ return config
42
+ except Exception as e:
43
+ logger.error(f"Error loading config: {e}")
44
+ return None
45
+
46
+ def load_system_prompt(system_prompt_path):
47
+ """Load system prompt from markdown file"""
48
+ try:
49
+ with open(system_prompt_path, 'r', encoding='utf-8') as f:
50
+ content = f.read()
51
+
52
+ # Extract SYSTEM_PROMPT from markdown
53
+ if 'SYSTEM_PROMPT = """' in content:
54
+ start = content.find('SYSTEM_PROMPT = """') + len('SYSTEM_PROMPT = """')
55
+ end = content.find('"""', start)
56
+ system_prompt = content[start:end].strip()
57
+ else:
58
+ # Fallback: use entire content
59
+ system_prompt = content.strip()
60
+
61
+ return system_prompt
62
+ except Exception as e:
63
+ logger.error(f"Error loading system prompt: {e}")
64
+ return None
65
+
66
+ def load_model_and_tokenizer(config):
67
+ """Load base model and tokenizer with memory optimization"""
68
+ model_path = config['model_path']
69
+
70
+ logger.info(f"Loading model from: {model_path}")
71
+
72
+ # Configure quantization for memory efficiency
73
+ bnb_config = BitsAndBytesConfig(
74
+ load_in_4bit=True,
75
+ bnb_4bit_use_double_quant=True,
76
+ bnb_4bit_quant_type="nf4",
77
+ bnb_4bit_compute_dtype=torch.float16
78
+ )
79
+
80
+ # Load tokenizer
81
+ tokenizer = AutoTokenizer.from_pretrained(
82
+ model_path,
83
+ trust_remote_code=True,
84
+ padding_side="right"
85
+ )
86
+
87
+ if tokenizer.pad_token is None:
88
+ tokenizer.pad_token = tokenizer.eos_token
89
+
90
+ # Load model with 4-bit quantization
91
+ model = AutoModelForCausalLM.from_pretrained(
92
+ model_path,
93
+ quantization_config=bnb_config,
94
+ device_map="auto",
95
+ trust_remote_code=True,
96
+ low_cpu_mem_usage=True
97
+ )
98
+
99
+ # Prepare model for k-bit training
100
+ model = prepare_model_for_kbit_training(model)
101
+
102
+ return model, tokenizer
103
+
104
+ def setup_lora_config(config):
105
+ """Setup LoRA configuration optimized for 8B model"""
106
+ lora_config = config['lora_config']
107
+
108
+ peft_config = LoraConfig(
109
+ task_type=TaskType.CAUSAL_LM,
110
+ r=lora_config['r'],
111
+ lora_alpha=lora_config['lora_alpha'],
112
+ lora_dropout=lora_config['lora_dropout'],
113
+ target_modules=lora_config['target_modules'],
114
+ bias="none",
115
+ )
116
+
117
+ return peft_config
118
+
119
+ def prepare_textilindo_dataset(data_path, tokenizer, system_prompt, max_length=1024):
120
+ """Prepare Textilindo dataset for training with system prompt"""
121
+ logger.info(f"Loading dataset from: {data_path}")
122
+
123
+ # Load JSONL dataset
124
+ data = []
125
+ with open(data_path, 'r', encoding='utf-8') as f:
126
+ for line_num, line in enumerate(f, 1):
127
+ line = line.strip()
128
+ if line:
129
+ try:
130
+ json_obj = json.loads(line)
131
+ data.append(json_obj)
132
+ except json.JSONDecodeError as e:
133
+ logger.warning(f"Invalid JSON at line {line_num}: {e}")
134
+ continue
135
+
136
+ if not data:
137
+ raise ValueError("No valid JSON objects found in JSONL file")
138
+
139
+ logger.info(f"Loaded {len(data)} samples from JSONL file")
140
+
141
+ # Convert to training format with system prompt
142
+ training_data = []
143
+ for item in data:
144
+ # Extract instruction and output
145
+ instruction = item.get('instruction', '')
146
+ output = item.get('output', '')
147
+
148
+ if not instruction or not output:
149
+ continue
150
+
151
+ # Create training text with system prompt (shorter for memory efficiency)
152
+ training_text = f"<|system|>\n{system_prompt[:500]}...\n<|user|>\n{instruction}\n<|assistant|>\n{output}<|end|>"
153
+
154
+ training_data.append({
155
+ 'text': training_text,
156
+ 'instruction': instruction,
157
+ 'output': output
158
+ })
159
+
160
+ # Convert to Dataset
161
+ dataset = Dataset.from_list(training_data)
162
+ logger.info(f"Prepared {len(dataset)} training samples")
163
+
164
+ def tokenize_function(examples):
165
+ # Tokenize the texts
166
+ tokenized = tokenizer(
167
+ examples['text'],
168
+ truncation=True,
169
+ padding=True,
170
+ max_length=max_length,
171
+ return_tensors="pt"
172
+ )
173
+ return tokenized
174
+
175
+ # Tokenize dataset
176
+ tokenized_dataset = dataset.map(
177
+ tokenize_function,
178
+ batched=True,
179
+ remove_columns=dataset.column_names
180
+ )
181
+
182
+ return tokenized_dataset
183
+
184
+ def train_model(model, tokenizer, dataset, config, output_dir):
185
+ """Train the model with LoRA - memory optimized"""
186
+ training_config = config['training_config']
187
+
188
+ # Setup training arguments optimized for memory
189
+ training_args = TrainingArguments(
190
+ output_dir=output_dir,
191
+ num_train_epochs=training_config['num_epochs'],
192
+ per_device_train_batch_size=training_config['batch_size'],
193
+ gradient_accumulation_steps=training_config['gradient_accumulation_steps'],
194
+ learning_rate=training_config['learning_rate'],
195
+ warmup_steps=training_config['warmup_steps'],
196
+ save_steps=training_config['save_steps'],
197
+ eval_steps=training_config['eval_steps'],
198
+ logging_steps=training_config.get('logging_steps', 5),
199
+ save_total_limit=training_config.get('save_total_limit', 2),
200
+ prediction_loss_only=training_config.get('prediction_loss_only', True),
201
+ remove_unused_columns=training_config.get('remove_unused_columns', False),
202
+ push_to_hub=training_config.get('push_to_hub', False),
203
+ report_to=training_config.get('report_to', None),
204
+ fp16=True, # Enable mixed precision training
205
+ dataloader_pin_memory=False, # Reduce memory usage
206
+ dataloader_num_workers=0, # Reduce memory usage
207
+ gradient_checkpointing=True, # Enable gradient checkpointing for memory
208
+ optim="adamw_torch", # Use AdamW optimizer
209
+ max_grad_norm=1.0, # Gradient clipping
210
+ )
211
+
212
+ # Setup data collator
213
+ data_collator = DataCollatorForLanguageModeling(
214
+ tokenizer=tokenizer,
215
+ mlm=False,
216
+ )
217
+
218
+ # Setup trainer
219
+ trainer = Trainer(
220
+ model=model,
221
+ args=training_args,
222
+ train_dataset=dataset,
223
+ data_collator=data_collator,
224
+ tokenizer=tokenizer,
225
+ )
226
+
227
+ # Start training
228
+ logger.info("Starting training...")
229
+ trainer.train()
230
+
231
+ # Save the model
232
+ trainer.save_model()
233
+ logger.info(f"Model saved to: {output_dir}")
234
+
235
+ def main():
236
+ print("🚀 Textilindo AI Assistant - Memory Optimized LoRA Training")
237
+ print("=" * 70)
238
+
239
+ # Load configuration
240
+ config_path = "configs/training_config.yaml"
241
+ if not os.path.exists(config_path):
242
+ print(f"❌ Config file tidak ditemukan: {config_path}")
243
+ sys.exit(1)
244
+
245
+ config = load_config(config_path)
246
+ if not config:
247
+ sys.exit(1)
248
+
249
+ # Load system prompt
250
+ system_prompt_path = "configs/system_prompt.md"
251
+ if not os.path.exists(system_prompt_path):
252
+ print(f"❌ System prompt tidak ditemukan: {system_prompt_path}")
253
+ sys.exit(1)
254
+
255
+ system_prompt = load_system_prompt(system_prompt_path)
256
+ if not system_prompt:
257
+ sys.exit(1)
258
+
259
+ # Setup paths
260
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
261
+ output_dir = Path(f"models/textilindo-ai-lora-8b-{timestamp}")
262
+ output_dir.mkdir(parents=True, exist_ok=True)
263
+
264
+ # Check if dataset exists
265
+ data_path = config['dataset_path']
266
+ if not os.path.exists(data_path):
267
+ print(f"❌ Dataset tidak ditemukan: {data_path}")
268
+ sys.exit(1)
269
+
270
+ # Load model and tokenizer
271
+ print("1️⃣ Loading model and tokenizer (4-bit quantized)...")
272
+ model, tokenizer = load_model_and_tokenizer(config)
273
+
274
+ # Setup LoRA
275
+ print("2️⃣ Setting up LoRA configuration...")
276
+ peft_config = setup_lora_config(config)
277
+ model = get_peft_model(model, peft_config)
278
+
279
+ # Print trainable parameters
280
+ model.print_trainable_parameters()
281
+
282
+ # Prepare dataset
283
+ print("3️⃣ Preparing Textilindo dataset...")
284
+ dataset = prepare_textilindo_dataset(data_path, tokenizer, system_prompt, config['max_length'])
285
+
286
+ # Train model
287
+ print("4️⃣ Starting training...")
288
+ print("⚠️ This may take several hours. Monitor GPU memory usage.")
289
+ train_model(model, tokenizer, dataset, config, output_dir)
290
+
291
+ print("✅ Training selesai!")
292
+ print(f"📁 Model tersimpan di: {output_dir}")
293
+ print(f"🔧 Untuk testing, jalankan: python scripts/test_textilindo_ai.py --model_path {output_dir}")
294
+
295
+ if __name__ == "__main__":
296
+ main()
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
+
test_deployment.py ADDED
@@ -0,0 +1,266 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Test script for Textilindo AI Assistant deployment
4
+ Run this to verify your setup before deploying to Hugging Face Spaces
5
+ """
6
+
7
+ import os
8
+ import sys
9
+ import json
10
+ import requests
11
+ from pathlib import Path
12
+
13
+ def test_file_structure():
14
+ """Test if all required files exist"""
15
+ print("🔍 Testing file structure...")
16
+
17
+ required_files = [
18
+ "app.py",
19
+ "Dockerfile",
20
+ "requirements.txt",
21
+ "configs/system_prompt.md",
22
+ "configs/training_config.yaml",
23
+ "templates/chat.html"
24
+ ]
25
+
26
+ required_dirs = [
27
+ "data",
28
+ "configs",
29
+ "templates"
30
+ ]
31
+
32
+ missing_files = []
33
+ missing_dirs = []
34
+
35
+ for file in required_files:
36
+ if not Path(file).exists():
37
+ missing_files.append(file)
38
+
39
+ for dir in required_dirs:
40
+ if not Path(dir).exists():
41
+ missing_dirs.append(dir)
42
+
43
+ if missing_files:
44
+ print(f"❌ Missing files: {missing_files}")
45
+ return False
46
+
47
+ if missing_dirs:
48
+ print(f"❌ Missing directories: {missing_dirs}")
49
+ return False
50
+
51
+ print("✅ All required files and directories exist")
52
+ return True
53
+
54
+ def test_data_files():
55
+ """Test if data files exist and are valid"""
56
+ print("🔍 Testing data files...")
57
+
58
+ data_dir = Path("data")
59
+ if not data_dir.exists():
60
+ print("❌ Data directory not found")
61
+ return False
62
+
63
+ jsonl_files = list(data_dir.glob("*.jsonl"))
64
+ if not jsonl_files:
65
+ print("❌ No JSONL files found in data directory")
66
+ return False
67
+
68
+ print(f"✅ Found {len(jsonl_files)} JSONL files:")
69
+ for file in jsonl_files:
70
+ print(f" - {file.name}")
71
+
72
+ # Test one JSONL file
73
+ test_file = jsonl_files[0]
74
+ try:
75
+ with open(test_file, 'r', encoding='utf-8') as f:
76
+ lines = f.readlines()
77
+
78
+ if not lines:
79
+ print(f"❌ {test_file.name} is empty")
80
+ return False
81
+
82
+ # Test first line
83
+ first_line = lines[0].strip()
84
+ if first_line:
85
+ json.loads(first_line)
86
+ print(f"✅ {test_file.name} contains valid JSON")
87
+
88
+ print(f"✅ {test_file.name} has {len(lines)} lines")
89
+ return True
90
+
91
+ except json.JSONDecodeError as e:
92
+ print(f"❌ {test_file.name} contains invalid JSON: {e}")
93
+ return False
94
+ except Exception as e:
95
+ print(f"❌ Error reading {test_file.name}: {e}")
96
+ return False
97
+
98
+ def test_config_files():
99
+ """Test if configuration files are valid"""
100
+ print("🔍 Testing configuration files...")
101
+
102
+ # Test system prompt
103
+ prompt_file = Path("configs/system_prompt.md")
104
+ if not prompt_file.exists():
105
+ print("❌ System prompt file not found")
106
+ return False
107
+
108
+ try:
109
+ with open(prompt_file, 'r', encoding='utf-8') as f:
110
+ content = f.read()
111
+
112
+ if 'SYSTEM_PROMPT' not in content:
113
+ print("⚠️ SYSTEM_PROMPT not found in system_prompt.md")
114
+ else:
115
+ print("✅ System prompt file is valid")
116
+
117
+ except Exception as e:
118
+ print(f"❌ Error reading system prompt: {e}")
119
+ return False
120
+
121
+ # Test training config
122
+ config_file = Path("configs/training_config.yaml")
123
+ if not config_file.exists():
124
+ print("❌ Training config file not found")
125
+ return False
126
+
127
+ try:
128
+ import yaml
129
+ with open(config_file, 'r') as f:
130
+ config = yaml.safe_load(f)
131
+
132
+ required_fields = ['model_name', 'model_path', 'dataset_path']
133
+ for field in required_fields:
134
+ if field not in config:
135
+ print(f"❌ Missing field in config: {field}")
136
+ return False
137
+
138
+ print("✅ Training configuration is valid")
139
+ return True
140
+
141
+ except Exception as e:
142
+ print(f"❌ Error reading training config: {e}")
143
+ return False
144
+
145
+ def test_app_import():
146
+ """Test if the app can be imported"""
147
+ print("🔍 Testing app import...")
148
+
149
+ try:
150
+ # Add current directory to path
151
+ sys.path.insert(0, '.')
152
+
153
+ # Try to import the app
154
+ import app
155
+ print("✅ App module imported successfully")
156
+
157
+ # Test if FastAPI app exists
158
+ if hasattr(app, 'app'):
159
+ print("✅ FastAPI app found")
160
+ else:
161
+ print("❌ FastAPI app not found")
162
+ return False
163
+
164
+ return True
165
+
166
+ except ImportError as e:
167
+ print(f"❌ Error importing app: {e}")
168
+ return False
169
+ except Exception as e:
170
+ print(f"❌ Unexpected error: {e}")
171
+ return False
172
+
173
+ def test_environment():
174
+ """Test environment variables"""
175
+ print("🔍 Testing environment...")
176
+
177
+ # Check if HUGGINGFACE_API_KEY is set
178
+ api_key = os.getenv('HUGGINGFACE_API_KEY')
179
+ if api_key:
180
+ print("✅ HUGGINGFACE_API_KEY is set")
181
+ else:
182
+ print("⚠️ HUGGINGFACE_API_KEY not set (will use mock responses)")
183
+
184
+ # Check Python version
185
+ python_version = sys.version_info
186
+ if python_version >= (3, 8):
187
+ print(f"✅ Python version {python_version.major}.{python_version.minor} is compatible")
188
+ else:
189
+ print(f"❌ Python version {python_version.major}.{python_version.minor} is too old (need 3.8+)")
190
+ return False
191
+
192
+ return True
193
+
194
+ def test_dependencies():
195
+ """Test if required dependencies can be imported"""
196
+ print("🔍 Testing dependencies...")
197
+
198
+ required_modules = [
199
+ 'fastapi',
200
+ 'uvicorn',
201
+ 'pydantic',
202
+ 'requests',
203
+ 'huggingface_hub'
204
+ ]
205
+
206
+ missing_modules = []
207
+
208
+ for module in required_modules:
209
+ try:
210
+ __import__(module)
211
+ print(f"✅ {module}")
212
+ except ImportError:
213
+ missing_modules.append(module)
214
+ print(f"❌ {module}")
215
+
216
+ if missing_modules:
217
+ print(f"❌ Missing modules: {missing_modules}")
218
+ print("Install with: pip install " + " ".join(missing_modules))
219
+ return False
220
+
221
+ return True
222
+
223
+ def main():
224
+ """Run all tests"""
225
+ print("🧪 Textilindo AI Assistant - Deployment Test")
226
+ print("=" * 50)
227
+
228
+ tests = [
229
+ test_file_structure,
230
+ test_data_files,
231
+ test_config_files,
232
+ test_environment,
233
+ test_dependencies,
234
+ test_app_import
235
+ ]
236
+
237
+ passed = 0
238
+ total = len(tests)
239
+
240
+ for test in tests:
241
+ try:
242
+ if test():
243
+ passed += 1
244
+ print()
245
+ except Exception as e:
246
+ print(f"❌ Test failed with error: {e}")
247
+ print()
248
+
249
+ print("=" * 50)
250
+ print(f"📊 Test Results: {passed}/{total} tests passed")
251
+
252
+ if passed == total:
253
+ print("🎉 All tests passed! Your setup is ready for deployment.")
254
+ print("\n📋 Next steps:")
255
+ print("1. Run: ./deploy_to_hf_space.sh")
256
+ print("2. Or manually deploy to Hugging Face Spaces")
257
+ print("3. Set environment variables in your space settings")
258
+ print("4. Test your deployed application")
259
+ else:
260
+ print("❌ Some tests failed. Please fix the issues above before deploying.")
261
+ return 1
262
+
263
+ return 0
264
+
265
+ if __name__ == "__main__":
266
+ sys.exit(main())