Text Generation
MLX
Safetensors
English
qwen2
laravel
php
code-generation
lora
fine-tuned
laravel-boost
conversational
4-bit precision
Instructions to use fchis/Laravel-13x-Qwen2.5-Coder-7B-Instruct-LoRA with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- MLX
How to use fchis/Laravel-13x-Qwen2.5-Coder-7B-Instruct-LoRA with MLX:
# Make sure mlx-lm is installed # pip install --upgrade mlx-lm # Generate text with mlx-lm from mlx_lm import load, generate model, tokenizer = load("fchis/Laravel-13x-Qwen2.5-Coder-7B-Instruct-LoRA") prompt = "Write a story about Einstein" messages = [{"role": "user", "content": prompt}] prompt = tokenizer.apply_chat_template( messages, add_generation_prompt=True ) text = generate(model, tokenizer, prompt=prompt, verbose=True) - Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- LM Studio
- Pi
How to use fchis/Laravel-13x-Qwen2.5-Coder-7B-Instruct-LoRA with Pi:
Start the MLX server
# Install MLX LM: uv tool install mlx-lm # Start a local OpenAI-compatible server: mlx_lm.server --model "fchis/Laravel-13x-Qwen2.5-Coder-7B-Instruct-LoRA"
Configure the model in Pi
# Install Pi: npm install -g @earendil-works/pi-coding-agent # Add to ~/.pi/agent/models.json: { "providers": { "mlx-lm": { "baseUrl": "http://localhost:8080/v1", "api": "openai-completions", "apiKey": "none", "models": [ { "id": "fchis/Laravel-13x-Qwen2.5-Coder-7B-Instruct-LoRA" } ] } } }Run Pi
# Start Pi in your project directory: pi
- MLX LM
How to use fchis/Laravel-13x-Qwen2.5-Coder-7B-Instruct-LoRA with MLX LM:
Generate or start a chat session
# Install MLX LM uv tool install mlx-lm # Interactive chat REPL mlx_lm.chat --model "fchis/Laravel-13x-Qwen2.5-Coder-7B-Instruct-LoRA"
Run an OpenAI-compatible server
# Install MLX LM uv tool install mlx-lm # Start the server mlx_lm.server --model "fchis/Laravel-13x-Qwen2.5-Coder-7B-Instruct-LoRA" # Calling the OpenAI-compatible server with curl curl -X POST "http://localhost:8000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "fchis/Laravel-13x-Qwen2.5-Coder-7B-Instruct-LoRA", "messages": [ {"role": "user", "content": "Hello"} ] }' - Hermes Agent
How to use fchis/Laravel-13x-Qwen2.5-Coder-7B-Instruct-LoRA with Hermes Agent:
Start the MLX server
# Install MLX LM: uv tool install mlx-lm # Start a local OpenAI-compatible server: mlx_lm.server --model "fchis/Laravel-13x-Qwen2.5-Coder-7B-Instruct-LoRA"
Configure Hermes
# Install Hermes: curl -fsSL https://hermes-agent.nousresearch.com/install.sh | bash hermes setup # Point Hermes at the local server: hermes config set model.provider custom hermes config set model.base_url http://127.0.0.1:8080/v1 hermes config set model.default fchis/Laravel-13x-Qwen2.5-Coder-7B-Instruct-LoRA
Run Hermes
hermes
- Atomic Chat
- OpenClaw
How to use fchis/Laravel-13x-Qwen2.5-Coder-7B-Instruct-LoRA with OpenClaw:
Start the MLX server
# Install MLX LM: uv tool install mlx-lm # Start a local OpenAI-compatible server: mlx_lm.server --model "fchis/Laravel-13x-Qwen2.5-Coder-7B-Instruct-LoRA"
Configure OpenClaw
# Install OpenClaw: npm install -g openclaw@latest # Register the local server and set it as the default model: openclaw onboard --non-interactive --mode local \ --auth-choice custom-api-key \ --custom-base-url http://127.0.0.1:8080/v1 \ --custom-model-id "fchis/Laravel-13x-Qwen2.5-Coder-7B-Instruct-LoRA" \ --custom-provider-id mlx-lm \ --custom-compatibility openai \ --custom-text-input \ --accept-risk \ --skip-health
Run OpenClaw
openclaw agent --local --agent main --message "Hello from Hugging Face"
Upload README.md with huggingface_hub
Browse files
README.md
CHANGED
|
@@ -20,250 +20,230 @@ model-index:
|
|
| 20 |
results: []
|
| 21 |
---
|
| 22 |
|
| 23 |
-
# Laravel 13.x Qwen2.5-Coder-7B-Instruct LoRA (
|
| 24 |
|
| 25 |
-
A 7B code model fine-tuned on **
|
| 26 |
|
| 27 |
-
## What Changed in
|
| 28 |
|
| 29 |
-
| |
|
| 30 |
-
|--|----|----|
|
| 31 |
-
| Training examples |
|
| 32 |
-
| Val loss | 0.
|
| 33 |
-
|
|
| 34 |
-
|
|
| 35 |
-
|
|
| 36 |
-
|
|
| 37 |
-
| Eval check added | β | **Check 11: `$this->count` detection** |
|
| 38 |
|
| 39 |
-
###
|
| 40 |
|
| 41 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 42 |
|
| 43 |
-
|
| 44 |
-
|-----|-----------|-----|
|
| 45 |
-
| `$this->count` instead of `count($rows)` | Pretraining PHP/Python analogy misfired (`self.count` is valid in Python) | 5 examples with `count($array)` in `$this->info()` output strings |
|
| 46 |
-
| `?? 0` missing on second accumulator key | Model's attention "used up" after initializing first key | 5 examples with multiple accumulator keys ALL using `?? 0` |
|
| 47 |
-
| Uninitialized closure vars | Autoregressive generation β model wrote closure body before the init line | 5 examples with explicit init-before-closure pattern + comments |
|
| 48 |
|
| 49 |
-
|
| 50 |
|
| 51 |
-
|
| 52 |
|
| 53 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 54 |
|
| 55 |
-
|
| 56 |
-
|-------|--------|
|
| 57 |
-
| PHP syntax valid | β
5/5 |
|
| 58 |
-
| extends Command | β
5/5 |
|
| 59 |
-
| protected $signature | β
5/5 |
|
| 60 |
-
| protected $description | β
5/5 |
|
| 61 |
-
| handle() method | β
5/5 |
|
| 62 |
-
| Command::SUCCESS/FAILURE | β
5/5 |
|
| 63 |
-
| No fake Facades\Progress | β
5/5 |
|
| 64 |
-
| No duplicate use statements | β
5/5 |
|
| 65 |
-
| Correct namespace | β
5/5 |
|
| 66 |
-
| No controller-only patterns | β
5/5 |
|
| 67 |
-
| No `$this->count` hallucination | β
5/5 |
|
| 68 |
|
| 69 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 70 |
|
| 71 |
-
##
|
| 72 |
|
| 73 |
-
**Prompt:** *"
|
| 74 |
|
| 75 |
```php
|
| 76 |
<?php
|
| 77 |
|
| 78 |
-
namespace App\
|
| 79 |
|
| 80 |
-
use Illuminate\
|
|
|
|
| 81 |
|
| 82 |
-
class
|
| 83 |
{
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
protected $description = 'Generate a summary of a CSV file';
|
| 87 |
-
|
| 88 |
-
public function handle(): int
|
| 89 |
{
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
$this->
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
|
| 98 |
-
|
| 99 |
-
$columnCounts = [];
|
| 100 |
-
|
| 101 |
-
if (($handle = fopen($path, 'r')) !== false) {
|
| 102 |
-
while (($row = fgetcsv($handle)) !== false) {
|
| 103 |
-
if ($headers === null) {
|
| 104 |
-
$headers = $row;
|
| 105 |
-
continue;
|
| 106 |
-
}
|
| 107 |
-
|
| 108 |
-
foreach ($headers as $index => $header) {
|
| 109 |
-
$columnCounts[$header]['total'] = ($columnCounts[$header]['total'] ?? 0) + 1;
|
| 110 |
-
$columnCounts[$header]['non_empty'] = ($columnCounts[$header]['non_empty'] ?? 0) + (! empty($row[$index]) ? 1 : 0);
|
| 111 |
-
}
|
| 112 |
-
}
|
| 113 |
-
fclose($handle);
|
| 114 |
-
}
|
| 115 |
-
|
| 116 |
-
$tableRows = [];
|
| 117 |
-
foreach ($columnCounts as $header => $counts) {
|
| 118 |
-
$total = $counts['total'] ?? 0;
|
| 119 |
-
$nonEmpty = $counts['non_empty'] ?? 0;
|
| 120 |
-
$tableRows[] = [$header, $total, $nonEmpty, $total - $nonEmpty];
|
| 121 |
-
}
|
| 122 |
-
|
| 123 |
-
$this->table(['Column', 'Total Rows', 'Non-Empty', 'Empty'], $tableRows);
|
| 124 |
-
|
| 125 |
-
return Command::SUCCESS;
|
| 126 |
}
|
| 127 |
}
|
| 128 |
```
|
| 129 |
|
| 130 |
-
|
| 131 |
|
| 132 |
-
|
|
|
|
|
|
|
| 133 |
|
| 134 |
```php
|
| 135 |
<?php
|
| 136 |
|
| 137 |
-
namespace App\
|
| 138 |
|
| 139 |
-
use
|
| 140 |
-
use Illuminate\Console\Command;
|
| 141 |
-
use Illuminate\Support\Facades\Hash;
|
| 142 |
|
| 143 |
-
class
|
| 144 |
{
|
| 145 |
-
|
|
|
|
|
|
|
|
|
|
| 146 |
|
| 147 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 148 |
|
| 149 |
-
public function
|
| 150 |
{
|
| 151 |
-
$
|
| 152 |
-
|
| 153 |
-
|
| 154 |
-
$this->error("File not found: {$path}");
|
| 155 |
-
|
| 156 |
-
return Command::FAILURE;
|
| 157 |
-
}
|
| 158 |
-
|
| 159 |
-
$rows = [];
|
| 160 |
-
$headers = null;
|
| 161 |
-
|
| 162 |
-
if (($handle = fopen($path, 'r')) !== false) {
|
| 163 |
-
while (($row = fgetcsv($handle)) !== false) {
|
| 164 |
-
if ($headers === null) {
|
| 165 |
-
$headers = $row;
|
| 166 |
-
continue;
|
| 167 |
-
}
|
| 168 |
-
$rows[] = array_combine($headers, $row);
|
| 169 |
-
}
|
| 170 |
-
fclose($handle);
|
| 171 |
-
}
|
| 172 |
-
|
| 173 |
-
// Initialize before closure capture
|
| 174 |
-
$count = 0;
|
| 175 |
-
$dryRun = $this->option('dry-run');
|
| 176 |
-
|
| 177 |
-
$this->withProgressBar($rows, function (array $row) use (&$count, $dryRun) {
|
| 178 |
-
if ($dryRun) {
|
| 179 |
-
$count++;
|
| 180 |
-
return;
|
| 181 |
-
}
|
| 182 |
-
|
| 183 |
-
User::updateOrCreate(
|
| 184 |
-
['email' => $row['email']],
|
| 185 |
-
['name' => $row['name'], 'password' => Hash::make('password')]
|
| 186 |
-
);
|
| 187 |
-
$count++;
|
| 188 |
-
});
|
| 189 |
-
|
| 190 |
-
$this->newLine();
|
| 191 |
-
|
| 192 |
-
if ($dryRun) {
|
| 193 |
-
$this->info("Dry run: {$count} users would be imported.");
|
| 194 |
-
return Command::SUCCESS;
|
| 195 |
-
}
|
| 196 |
-
|
| 197 |
-
$this->info("Imported {$count} users.");
|
| 198 |
-
|
| 199 |
-
return Command::SUCCESS;
|
| 200 |
}
|
| 201 |
}
|
| 202 |
```
|
| 203 |
|
| 204 |
-
|
| 205 |
-
|
| 206 |
-
##
|
| 207 |
-
|
| 208 |
-
|
| 209 |
-
|
| 210 |
-
|
| 211 |
-
|
| 212 |
-
|
| 213 |
-
|
| 214 |
-
|
| 215 |
-
|
| 216 |
-
|
| 217 |
-
|
| 218 |
-
|
| 219 |
-
|
| 220 |
-
|
| 221 |
-
|
| 222 |
-
|
| 223 |
-
|
| 224 |
-
-
|
| 225 |
-
|
| 226 |
-
-
|
| 227 |
-
|
| 228 |
-
|
| 229 |
-
|
| 230 |
-
-
|
| 231 |
-
-
|
| 232 |
-
|
| 233 |
-
|
| 234 |
-
|
| 235 |
-
|
| 236 |
-
|
| 237 |
-
-
|
| 238 |
-
-
|
| 239 |
-
-
|
| 240 |
-
|
| 241 |
-
|
| 242 |
-
|
| 243 |
-
|
| 244 |
-
|
| 245 |
-
- `Http::timeout(30)->get()->body()` for large downloads
|
| 246 |
-
- `tempnam()` + `unlink()` for temp file handling
|
| 247 |
|
| 248 |
## Usage
|
| 249 |
|
| 250 |
-
### With adapter (recommended
|
| 251 |
|
| 252 |
```python
|
| 253 |
from mlx_lm import load, generate
|
| 254 |
|
| 255 |
-
SYSTEM = """You are a senior Laravel developer. Write clean, production-ready Laravel code.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 256 |
|
| 257 |
### Artisan Commands
|
| 258 |
-
|
| 259 |
-
|
| 260 |
-
|
| 261 |
-
|
| 262 |
-
|
| 263 |
-
|
| 264 |
-
- Accumulators: ALL keys in same array use (?? 0) + $value pattern
|
| 265 |
-
- Initialize ALL closure variables BEFORE the closure definition
|
| 266 |
-
- Facades\\Progress does NOT exist"""
|
| 267 |
|
| 268 |
model, tok = load(
|
| 269 |
"mlx-community/Qwen2.5-Coder-7B-Instruct-4bit",
|
|
@@ -272,15 +252,14 @@ model, tok = load(
|
|
| 272 |
|
| 273 |
messages = [
|
| 274 |
{"role": "system", "content": SYSTEM},
|
| 275 |
-
{"role": "user", "content": "
|
| 276 |
]
|
| 277 |
text = tok.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
|
| 278 |
-
output = generate(model, tok, prompt=text, max_tokens=
|
| 279 |
|
| 280 |
-
# Strip end token
|
| 281 |
if '<|im_end|>' in output:
|
| 282 |
output = output[:output.index('<|im_end|>')]
|
| 283 |
-
print(output)
|
| 284 |
```
|
| 285 |
|
| 286 |
### CLI pipeline
|
|
@@ -289,7 +268,7 @@ print(output)
|
|
| 289 |
pip install mlx-lm
|
| 290 |
git clone https://github.com/florinel-chis/laravel-ai-gen
|
| 291 |
cd your-laravel-project
|
| 292 |
-
python3 laravel-gen.py --model 7b "
|
| 293 |
```
|
| 294 |
|
| 295 |
## Model Details
|
|
@@ -298,13 +277,13 @@ python3 laravel-gen.py --model 7b "import users from CSV with progress bar and d
|
|
| 298 |
|--------|-------|
|
| 299 |
| **Base model** | Qwen2.5-Coder-7B-Instruct (4-bit via MLX) |
|
| 300 |
| **Fine-tuning** | LoRA, 8 layers, rank=8, scale=20, lr=1e-5 |
|
| 301 |
-
| **Training data** |
|
| 302 |
-
| **Resumed from** |
|
| 303 |
-
| **Best checkpoint** | Iter
|
| 304 |
| **Training hardware** | Apple M2 Pro, 16GB unified memory |
|
| 305 |
-
| **Training time** | ~
|
| 306 |
-
| **Peak memory** | 10.
|
| 307 |
-
| **Val loss** | 0.
|
| 308 |
|
| 309 |
## Training History
|
| 310 |
|
|
@@ -313,29 +292,32 @@ python3 laravel-gen.py --model 7b "import users from CSV with progress bar and d
|
|
| 313 |
| v1 (1B) | 90 | β | Proof of concept |
|
| 314 |
| v2 (7B) | 162 | 0.178 | First 7B, repetition loops |
|
| 315 |
| v3 (7B) | 187 | 0.076 | +25 artisan examples, fixed repetition |
|
| 316 |
-
|
|
|
|
|
| 317 |
|
| 318 |
-
## Patterns Covered (
|
| 319 |
|
| 320 |
| Category | Examples | Patterns |
|
| 321 |
|----------|----------|----------|
|
| 322 |
| **Eloquent** | Models, relationships, scopes, casts, soft deletes | 20+ |
|
| 323 |
| **Database** | Migrations (create, alter, pivot), factories, seeders | 15+ |
|
| 324 |
| **HTTP** | Controllers (API resource, short), Form Requests | 25+ |
|
|
|
|
|
|
|
|
|
|
| 325 |
| **Queue** | Jobs (ShouldQueue, constructor injection) | 3 |
|
| 326 |
| **Events** | Events (Dispatchable), Listeners (queued), Observers | 5 |
|
| 327 |
| **Notifications** | Mail + database channels, Queueable | 3 |
|
| 328 |
-
| **Console** | Artisan commands β data I/O, CSV/JSON/HTTP, interactive, precision patterns |
|
| 329 |
| **Views** | Blade templates, View Composers, Blade components | 8 |
|
| 330 |
| **Mail** | Mailable (envelope, content, markdown) | 2 |
|
| 331 |
-
| **Other** | API Resources, routes, policies, service providers, cache | 10+ |
|
| 332 |
|
| 333 |
-
##
|
| 334 |
|
| 335 |
-
-
|
| 336 |
-
- Unseen patterns (Livewire, Inertia.js, complex
|
| 337 |
-
-
|
| 338 |
-
-
|
| 339 |
|
| 340 |
## Companion Models
|
| 341 |
|
|
@@ -345,7 +327,7 @@ python3 laravel-gen.py --model 7b "import users from CSV with progress bar and d
|
|
| 345 |
| Coder 3B | Generate code (faster) | [fchis/Laravel-13x-Qwen2.5-Coder-3B-Instruct-LoRA](https://huggingface.co/fchis/Laravel-13x-Qwen2.5-Coder-3B-Instruct-LoRA) |
|
| 346 |
| **Coder 7B** | **Generate code (best quality)** | **You're here** |
|
| 347 |
| CLI tool | End-to-end pipeline | [github.com/florinel-chis/laravel-ai-gen](https://github.com/florinel-chis/laravel-ai-gen) |
|
| 348 |
-
| Training data |
|
| 349 |
|
| 350 |
## License
|
| 351 |
|
|
|
|
| 20 |
results: []
|
| 21 |
---
|
| 22 |
|
| 23 |
+
# Laravel 13.x Qwen2.5-Coder-7B-Instruct LoRA (v5)
|
| 24 |
|
| 25 |
+
A 7B code model fine-tuned on **235 Laravel 13.x** instruction-to-code pairs with **Laravel Boost guidelines** baked into training. Covers 18+ Laravel class types including **API Resources with relationship loading, Form Request precision hooks, and Pest feature tests**. Trained on Apple M2 Pro 16GB β no cloud GPU needed.
|
| 26 |
|
| 27 |
+
## What Changed in v5 (Sprint 3: Coverage Expansion)
|
| 28 |
|
| 29 |
+
| | v4 | **v5** |
|
| 30 |
+
|--|----|-----|
|
| 31 |
+
| Training examples | 202 | **235** (+33 new examples) |
|
| 32 |
+
| Val loss | 0.055 | **0.032** (best: iter 150) |
|
| 33 |
+
| API Resources | 2 examples | **14 examples** β `whenLoaded`, `$this->when`, `mergeWhen`, `whenNotNull`, `ResourceCollection` |
|
| 34 |
+
| Form Requests | 3 examples | **13 examples** β `messages()`, `attributes()`, `prepareForValidation()`, `after()` |
|
| 35 |
+
| Pest Tests | 0 examples | **15 examples** β CRUD, auth, pagination, fakes |
|
| 36 |
+
| Artisan regression | 5/5 Γ 11/11 | **5/5 Γ 11/11** (no regression) |
|
|
|
|
| 37 |
|
| 38 |
+
### Sprint 3 eval results (E1βE8)
|
| 39 |
|
| 40 |
+
| Eval | Category | Result | Notes |
|
| 41 |
+
|------|----------|--------|-------|
|
| 42 |
+
| E1 | API Resource | β
8/8 | `whenLoaded` (posts + subscription), `whenNotNull` (email_verified_at) |
|
| 43 |
+
| E2 | API Resource | β
8/8 | `whenLoaded` (category), `$this->when` (is_featured, cost_price) |
|
| 44 |
+
| E3 | API Resource | β
6/8 | `ResourceCollection` with `with()` for aggregated meta |
|
| 45 |
+
| E4 | Form Request | β
pass | `messages()`, `attributes()`, `prepareForValidation()` + `$this->merge()` |
|
| 46 |
+
| E5 | Form Request | β
pass | `after()` with `Hash::check()`, Validator closure |
|
| 47 |
+
| E6 | Pest Test | β οΈ pass* | Correct test logic; style defaults to PHPUnit class unless explicitly prompted |
|
| 48 |
+
| E7 | Pest Test | β οΈ pass* | Pagination test passes in real Laravel |
|
| 49 |
+
| E8 | Pest Test | β οΈ pass* | Auth/ownership tests pass in real Laravel |
|
| 50 |
|
| 51 |
+
*Pest tests pass functionally in a real Laravel 13.2.0 + SQLite project (14/14 assertions pass), but the model defaults to PHPUnit class-based syntax unless the prompt explicitly requests Pest function syntax.
|
|
|
|
|
|
|
|
|
|
|
|
|
| 52 |
|
| 53 |
+
### Real-world validation
|
| 54 |
|
| 55 |
+
All eval prompts were tested in a real **Laravel 13.2.0 + SQLite** project:
|
| 56 |
|
| 57 |
+
```
|
| 58 |
+
php artisan test β 14/14 passed (28 assertions)
|
| 59 |
+
```
|
| 60 |
+
|
| 61 |
+
Tests cover: POST create (auth, 401, 422 validation), GET paginated (meta.total), DELETE own/other (204, 403).
|
| 62 |
|
| 63 |
+
### Root causes found in Sprint 3
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 64 |
|
| 65 |
+
| Gap | Root cause | v5 fix |
|
| 66 |
+
|-----|-----------|--------|
|
| 67 |
+
| API Resources: bare `$this->posts` | Model saw shell from pretraining but no `whenLoaded` examples | 12 examples with `whenLoaded`, `$this->when`, `mergeWhen`, `whenNotNull`, `whenCounted` |
|
| 68 |
+
| Form Requests: no `messages()` | Model generated `rules()` only β basic shell from pretraining | 10 examples with `messages()`, `attributes()`, `prepareForValidation()`, `after()` |
|
| 69 |
+
| Pest tests: PHPUnit class style | Strong pretraining prior to class-based tests; `uses()` not anchored | 15 Pest examples + explicit BOOST_CONTEXT block; prompt must specify "Pest function syntax" |
|
| 70 |
|
| 71 |
+
## Evaluation: API Resource (E1)
|
| 72 |
|
| 73 |
+
**Prompt:** *"Create UserResource with optional posts collection (whenLoaded) and subscription (whenLoaded). Use whenNotNull for email_verified_at."*
|
| 74 |
|
| 75 |
```php
|
| 76 |
<?php
|
| 77 |
|
| 78 |
+
namespace App\Http\Resources;
|
| 79 |
|
| 80 |
+
use Illuminate\Http\Request;
|
| 81 |
+
use Illuminate\Http\Resources\Json\JsonResource;
|
| 82 |
|
| 83 |
+
class UserResource extends JsonResource
|
| 84 |
{
|
| 85 |
+
public function toArray(Request $request): array
|
|
|
|
|
|
|
|
|
|
|
|
|
| 86 |
{
|
| 87 |
+
return [
|
| 88 |
+
'id' => $this->id,
|
| 89 |
+
'name' => $this->name,
|
| 90 |
+
'email' => $this->email,
|
| 91 |
+
'email_verified_at' => $this->whenNotNull($this->email_verified_at),
|
| 92 |
+
'posts' => PostResource::collection($this->whenLoaded('posts')),
|
| 93 |
+
'subscription' => SubscriptionResource::make($this->whenLoaded('subscription')),
|
| 94 |
+
'created_at' => $this->created_at,
|
| 95 |
+
];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 96 |
}
|
| 97 |
}
|
| 98 |
```
|
| 99 |
|
| 100 |
+
Correct: `whenLoaded` used (not `$this->posts`), `whenNotNull` used (not ternary), returns `array`.
|
| 101 |
|
| 102 |
+
## Evaluation: Form Request (E4)
|
| 103 |
+
|
| 104 |
+
**Prompt:** *"StoreInvoiceRequest with messages(), attributes(), prepareForValidation() that rounds amount."*
|
| 105 |
|
| 106 |
```php
|
| 107 |
<?php
|
| 108 |
|
| 109 |
+
namespace App\Http\Requests;
|
| 110 |
|
| 111 |
+
use Illuminate\Foundation\Http\FormRequest;
|
|
|
|
|
|
|
| 112 |
|
| 113 |
+
class StoreInvoiceRequest extends FormRequest
|
| 114 |
{
|
| 115 |
+
public function authorize(): bool
|
| 116 |
+
{
|
| 117 |
+
return true;
|
| 118 |
+
}
|
| 119 |
|
| 120 |
+
public function rules(): array
|
| 121 |
+
{
|
| 122 |
+
return [
|
| 123 |
+
'client_id' => ['required', 'exists:clients,id'],
|
| 124 |
+
'amount' => ['required', 'numeric', 'min:0.01'],
|
| 125 |
+
'due_date' => ['required', 'date', 'after:today'],
|
| 126 |
+
'notes' => ['nullable', 'string', 'max:1000'],
|
| 127 |
+
];
|
| 128 |
+
}
|
| 129 |
+
|
| 130 |
+
public function messages(): array
|
| 131 |
+
{
|
| 132 |
+
return [
|
| 133 |
+
'client_id.required' => 'The client is required.',
|
| 134 |
+
'amount.required' => 'The amount is required.',
|
| 135 |
+
'amount.numeric' => 'The amount must be a number.',
|
| 136 |
+
'due_date.required' => 'The due date is required.',
|
| 137 |
+
];
|
| 138 |
+
}
|
| 139 |
+
|
| 140 |
+
public function attributes(): array
|
| 141 |
+
{
|
| 142 |
+
return [
|
| 143 |
+
'client_id' => 'Client',
|
| 144 |
+
'due_date' => 'Due Date',
|
| 145 |
+
];
|
| 146 |
+
}
|
| 147 |
|
| 148 |
+
public function prepareForValidation(): void
|
| 149 |
{
|
| 150 |
+
$this->merge([
|
| 151 |
+
'amount' => round($this->amount, 2),
|
| 152 |
+
]);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 153 |
}
|
| 154 |
}
|
| 155 |
```
|
| 156 |
|
| 157 |
+
Correct: `messages()` uses `field.rule` keys, `prepareForValidation()` uses `$this->merge()` (not direct assignment).
|
| 158 |
+
|
| 159 |
+
## Evaluation: Pest Test (E6)
|
| 160 |
+
|
| 161 |
+
**Prompt:** *"Pest test for POST /api/posts: auth creates (201 + assertDatabaseHas), unauth returns 401, missing title returns 422 with validation errors."*
|
| 162 |
+
|
| 163 |
+
```php
|
| 164 |
+
<?php
|
| 165 |
+
|
| 166 |
+
use App\Models\Post;
|
| 167 |
+
use App\Models\User;
|
| 168 |
+
use Illuminate\Foundation\Testing\RefreshDatabase;
|
| 169 |
+
|
| 170 |
+
uses(RefreshDatabase::class);
|
| 171 |
+
|
| 172 |
+
test('authenticated user creates a post with title and body', function () {
|
| 173 |
+
$user = User::factory()->create();
|
| 174 |
+
|
| 175 |
+
$this->actingAs($user)
|
| 176 |
+
->postJson('/api/posts', ['title' => 'Test Post', 'body' => 'Body text.'])
|
| 177 |
+
->assertCreated();
|
| 178 |
+
|
| 179 |
+
$this->assertDatabaseHas('posts', ['title' => 'Test Post', 'user_id' => $user->id]);
|
| 180 |
+
});
|
| 181 |
+
|
| 182 |
+
test('unauthenticated user gets 401', function () {
|
| 183 |
+
$this->postJson('/api/posts', ['title' => 'Test Post', 'body' => 'Body text.'])
|
| 184 |
+
->assertUnauthorized();
|
| 185 |
+
});
|
| 186 |
+
|
| 187 |
+
test('authenticated user sends missing title gets 422', function () {
|
| 188 |
+
$user = User::factory()->create();
|
| 189 |
+
|
| 190 |
+
$this->actingAs($user)
|
| 191 |
+
->postJson('/api/posts', ['body' => 'Body text.'])
|
| 192 |
+
->assertUnprocessable()
|
| 193 |
+
->assertJsonValidationErrors(['title']);
|
| 194 |
+
});
|
| 195 |
+
```
|
| 196 |
+
|
| 197 |
+
Tested in real Laravel 13.2.0 β **3/3 assertions pass**.
|
|
|
|
|
|
|
| 198 |
|
| 199 |
## Usage
|
| 200 |
|
| 201 |
+
### With adapter (recommended)
|
| 202 |
|
| 203 |
```python
|
| 204 |
from mlx_lm import load, generate
|
| 205 |
|
| 206 |
+
SYSTEM = """You are a senior Laravel developer. Write clean, production-ready Laravel 13.x code.
|
| 207 |
+
Output only the PHP file contents β no markdown, no explanation, no ```php fences.
|
| 208 |
+
|
| 209 |
+
### API Resources
|
| 210 |
+
// Always extend JsonResource. toArray() returns array, not JsonResponse.
|
| 211 |
+
// Relationships: $this->whenLoaded('relation', fn() => RelatedResource::make($this->relation))
|
| 212 |
+
// Collections: $this->whenLoaded('items', fn() => ItemResource::collection($this->items))
|
| 213 |
+
// Conditional field: $this->when($condition, $value)
|
| 214 |
+
// Conditional block: $this->mergeWhen($condition, ['field' => $value])
|
| 215 |
+
// Nullable field: $this->whenNotNull($this->deleted_at)
|
| 216 |
+
// ResourceCollection: override with() to add pagination metadata
|
| 217 |
+
// Never call toArray() manually β Laravel calls it automatically
|
| 218 |
+
|
| 219 |
+
### Form Request precision
|
| 220 |
+
// messages(): return ['field.rule' => 'Custom message'] β overrides default error text
|
| 221 |
+
// attributes(): return ['field' => 'Human Name'] β used in :attribute placeholder
|
| 222 |
+
// prepareForValidation(): call $this->merge([...]) to normalize input before rules run
|
| 223 |
+
// after(): return [fn(Validator $v) => $v->errors()->addIf($condition, 'field', 'msg')]
|
| 224 |
+
// passedValidation(): runs after all rules pass β use for side effects, not validation
|
| 225 |
+
// NEVER use $this->validate() β that is a Controller method
|
| 226 |
+
|
| 227 |
+
### Pest Feature Tests
|
| 228 |
+
// File: tests/Feature/SomeTest.php
|
| 229 |
+
// uses(RefreshDatabase::class); at top of file
|
| 230 |
+
// HTTP: $this->getJson('/api/route'), postJson(), putJson(), patchJson(), deleteJson()
|
| 231 |
+
// Status: ->assertOk() (200), ->assertCreated() (201), ->assertNoContent() (204)
|
| 232 |
+
// ->assertUnprocessable() (422), ->assertUnauthorized() (401), ->assertForbidden() (403)
|
| 233 |
+
// JSON: ->assertJson(['key' => 'value']), ->assertJsonStructure(['data' => ['id', 'name']])
|
| 234 |
+
// ->assertJsonCount(3, 'data'), ->assertJsonPath('data.0.name', 'Alice')
|
| 235 |
+
// DB: assertDatabaseHas('table', ['col' => 'val']), assertDatabaseMissing(), assertDatabaseCount()
|
| 236 |
+
// Auth: $this->actingAs(User::factory()->create())
|
| 237 |
+
// Fakes: Queue::fake(), Event::fake(), Mail::fake() then ::assertPushed/Dispatched/Sent
|
| 238 |
+
// IMPORTANT: Pest uses function syntax (uses/test/it) NOT class-based syntax
|
| 239 |
|
| 240 |
### Artisan Commands
|
| 241 |
+
// Always extend Illuminate\\Console\\Command
|
| 242 |
+
// handle() returns Command::SUCCESS or Command::FAILURE (never raw int)
|
| 243 |
+
// count(): use count($array) β Command has NO $this->count property
|
| 244 |
+
// Accumulators: ALL keys in same array use (?? 0) + $value pattern
|
| 245 |
+
// Initialize ALL closure variables BEFORE the closure definition
|
| 246 |
+
// Facades\\Progress does NOT exist"""
|
|
|
|
|
|
|
|
|
|
| 247 |
|
| 248 |
model, tok = load(
|
| 249 |
"mlx-community/Qwen2.5-Coder-7B-Instruct-4bit",
|
|
|
|
| 252 |
|
| 253 |
messages = [
|
| 254 |
{"role": "system", "content": SYSTEM},
|
| 255 |
+
{"role": "user", "content": "Create a UserResource with whenLoaded for posts (PostResource collection) and subscription (SubscriptionResource). Use whenNotNull for email_verified_at."}
|
| 256 |
]
|
| 257 |
text = tok.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
|
| 258 |
+
output = generate(model, tok, prompt=text, max_tokens=1200)
|
| 259 |
|
|
|
|
| 260 |
if '<|im_end|>' in output:
|
| 261 |
output = output[:output.index('<|im_end|>')]
|
| 262 |
+
print(output.strip())
|
| 263 |
```
|
| 264 |
|
| 265 |
### CLI pipeline
|
|
|
|
| 268 |
pip install mlx-lm
|
| 269 |
git clone https://github.com/florinel-chis/laravel-ai-gen
|
| 270 |
cd your-laravel-project
|
| 271 |
+
python3 laravel-gen.py --model 7b "UserResource with posts collection and subscription via whenLoaded"
|
| 272 |
```
|
| 273 |
|
| 274 |
## Model Details
|
|
|
|
| 277 |
|--------|-------|
|
| 278 |
| **Base model** | Qwen2.5-Coder-7B-Instruct (4-bit via MLX) |
|
| 279 |
| **Fine-tuning** | LoRA, 8 layers, rank=8, scale=20, lr=1e-5 |
|
| 280 |
+
| **Training data** | 235 instruction-to-code pairs with Boost guidelines |
|
| 281 |
+
| **Resumed from** | v4 iter 150 (val_loss 0.055) |
|
| 282 |
+
| **Best checkpoint** | Iter 150 (val_loss 0.032) |
|
| 283 |
| **Training hardware** | Apple M2 Pro, 16GB unified memory |
|
| 284 |
+
| **Training time** | ~19 minutes (150 iters, max_seq_length 1500) |
|
| 285 |
+
| **Peak memory** | 10.820 GB |
|
| 286 |
+
| **Val loss** | 0.032 (iter 150) |
|
| 287 |
|
| 288 |
## Training History
|
| 289 |
|
|
|
|
| 292 |
| v1 (1B) | 90 | β | Proof of concept |
|
| 293 |
| v2 (7B) | 162 | 0.178 | First 7B, repetition loops |
|
| 294 |
| v3 (7B) | 187 | 0.076 | +25 artisan examples, fixed repetition |
|
| 295 |
+
| v4 (7B) | 202 | 0.055 | +15 precision examples, 3 bug patterns eliminated |
|
| 296 |
+
| **v5 (7B)** | **235** | **0.032** | **+33 examples: API Resources, Form Requests, Pest Tests** |
|
| 297 |
|
| 298 |
+
## Patterns Covered (18+ class types)
|
| 299 |
|
| 300 |
| Category | Examples | Patterns |
|
| 301 |
|----------|----------|----------|
|
| 302 |
| **Eloquent** | Models, relationships, scopes, casts, soft deletes | 20+ |
|
| 303 |
| **Database** | Migrations (create, alter, pivot), factories, seeders | 15+ |
|
| 304 |
| **HTTP** | Controllers (API resource, short), Form Requests | 25+ |
|
| 305 |
+
| **API Resources** | JsonResource, ResourceCollection, whenLoaded, $this->when, mergeWhen | **14** |
|
| 306 |
+
| **Form Requests** | rules, messages, attributes, prepareForValidation, after, passedValidation | **13** |
|
| 307 |
+
| **Tests** | Pest feature tests β CRUD, auth, pagination, validation, queue/mail fakes | **15** |
|
| 308 |
| **Queue** | Jobs (ShouldQueue, constructor injection) | 3 |
|
| 309 |
| **Events** | Events (Dispatchable), Listeners (queued), Observers | 5 |
|
| 310 |
| **Notifications** | Mail + database channels, Queueable | 3 |
|
| 311 |
+
| **Console** | Artisan commands β data I/O, CSV/JSON/HTTP, interactive, precision patterns | 40+ |
|
| 312 |
| **Views** | Blade templates, View Composers, Blade components | 8 |
|
| 313 |
| **Mail** | Mailable (envelope, content, markdown) | 2 |
|
|
|
|
| 314 |
|
| 315 |
+
## Known Limitations
|
| 316 |
|
| 317 |
+
- **Pest syntax**: model defaults to PHPUnit class-based tests unless prompt explicitly says "Pest function syntax (uses/test/it)"
|
| 318 |
+
- Unseen patterns (Livewire, Inertia.js, complex Eloquent scopes) still limited
|
| 319 |
+
- End-token stripping required in post-processing: `output[:output.index('<|im_end|>')]`
|
| 320 |
+
- Laravel official 17-task benchmark coverage: ~5/17 tasks (added Resources, Form precision, Tests to the previous 3)
|
| 321 |
|
| 322 |
## Companion Models
|
| 323 |
|
|
|
|
| 327 |
| Coder 3B | Generate code (faster) | [fchis/Laravel-13x-Qwen2.5-Coder-3B-Instruct-LoRA](https://huggingface.co/fchis/Laravel-13x-Qwen2.5-Coder-3B-Instruct-LoRA) |
|
| 328 |
| **Coder 7B** | **Generate code (best quality)** | **You're here** |
|
| 329 |
| CLI tool | End-to-end pipeline | [github.com/florinel-chis/laravel-ai-gen](https://github.com/florinel-chis/laravel-ai-gen) |
|
| 330 |
+
| Training data | 235 examples | [fchis/Laravel-13x-Code-Instructions](https://huggingface.co/datasets/fchis/Laravel-13x-Code-Instructions) |
|
| 331 |
|
| 332 |
## License
|
| 333 |
|