fchis commited on
Commit
33567b5
Β·
verified Β·
1 Parent(s): a4ee8a4

Upload README.md with huggingface_hub

Browse files
Files changed (1) hide show
  1. README.md +198 -216
README.md CHANGED
@@ -20,250 +20,230 @@ model-index:
20
  results: []
21
  ---
22
 
23
- # Laravel 13.x Qwen2.5-Coder-7B-Instruct LoRA (v4)
24
 
25
- A 7B code model fine-tuned on **202 Laravel 13.x** instruction-to-code pairs with **Laravel Boost guidelines** baked into training. Covers 15+ Laravel class types including **Artisan console commands with CSV/JSON/HTTP data operations**. Trained on Apple M2 Pro 16GB β€” no cloud GPU needed.
26
 
27
- ## What Changed in v4 (Sprint 2: Precision Fixes)
28
 
29
- | | v3 | **v4** |
30
- |--|----|----|
31
- | Training examples | 187 | **202** (+15 precision-fix examples) |
32
- | Val loss | 0.076 | **0.055** (best checkpoint: iter 100) |
33
- | Eval score | 8/8 Γ— 10/10 | **5/5 Γ— 11/11** |
34
- | `$this->count` hallucination | Present | **Eliminated** |
35
- | Missing `?? 0` on accumulator keys | Present | **Eliminated** |
36
- | Uninitialized closure variables | Present | **Eliminated** |
37
- | Eval check added | β€” | **Check 11: `$this->count` detection** |
38
 
39
- ### Root causes fixed
40
 
41
- All 3 bugs in v3 were **precision gaps**, not structural misunderstandings:
 
 
 
 
 
 
 
 
 
42
 
43
- | Bug | Root cause | Fix |
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
- Anti-patterns also added to BOOST_CONTEXT system prompt, making the corrections zero-cost at inference.
50
 
51
- ## Evaluation Results (v4): 5/5 Γ— 11/11
52
 
53
- All 5 real-Laravel test commands (separate from training data) score **11/11** on automated checks:
 
 
 
 
54
 
55
- | Check | Result |
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
- All 5 commands were deployed and run in a real **Laravel 13.2.0 + SQLite** project with zero manual fixes. Full end-to-end pipeline: generate test data β†’ summarize columns β†’ convert CSV↔JSON β†’ import with progress bar + dry-run β†’ export.
 
 
 
 
70
 
71
- ## Sample Output (v4)
72
 
73
- **Prompt:** *"Write a Laravel Artisan command CsvSummary with signature 'app:csv-summary {file}' that reads a CSV and outputs a table showing column name, total rows, and non-empty count for each column. Both 'total' and 'non_empty' keys must use ?? 0 initialization."*
74
 
75
  ```php
76
  <?php
77
 
78
- namespace App\Console\Commands;
79
 
80
- use Illuminate\Console\Command;
 
81
 
82
- class CsvSummary extends Command
83
  {
84
- protected $signature = 'app:csv-summary {file}';
85
-
86
- protected $description = 'Generate a summary of a CSV file';
87
-
88
- public function handle(): int
89
  {
90
- $path = $this->argument('file');
91
-
92
- if (! file_exists($path)) {
93
- $this->error("File not found: {$path}");
94
-
95
- return Command::FAILURE;
96
- }
97
-
98
- $headers = null;
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
- Note: both `total` and `non_empty` accumulator keys correctly use `?? 0` β€” this was a precision bug in v3 that is fixed in v4.
131
 
132
- **Prompt:** *"Write a Laravel Artisan command ImportUsersFromCsv with signature 'app:import-users {file} {--dry-run}' that reads a CSV and imports users with a progress bar and dry-run mode."*
 
 
133
 
134
  ```php
135
  <?php
136
 
137
- namespace App\Console\Commands;
138
 
139
- use App\Models\User;
140
- use Illuminate\Console\Command;
141
- use Illuminate\Support\Facades\Hash;
142
 
143
- class ImportUsersFromCsv extends Command
144
  {
145
- protected $signature = 'app:import-users {file} {--dry-run}';
 
 
 
146
 
147
- protected $description = 'Import users from a CSV file';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
148
 
149
- public function handle(): int
150
  {
151
- $path = $this->argument('file');
152
-
153
- if (! file_exists($path)) {
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
- Note: `$count` and `$dryRun` are initialized before the `withProgressBar()` closure β€” this was a bug in v3.
205
-
206
- ## Artisan Command Patterns Covered (v4)
207
-
208
- ### Level 1: Core I/O
209
- - CSV β†’ JSON conversion (`fgetcsv`, `array_combine`, `json_encode`)
210
- - JSON β†’ CSV conversion (`json_decode`, `fputcsv`)
211
- - CSV import with progress bar (`$this->withProgressBar()`)
212
- - JSON import with dry-run option
213
- - DB export to CSV with `fputcsv` + `chunk()`
214
- - Interactive commands (`confirm()`, `ask()`, `choice()`)
215
- - Table output (`$this->table()`)
216
-
217
- ### Level 2: External Data Sources
218
- - HTTP JSON fetch β†’ DB upsert (`Http::get($url)->throw()->json()`)
219
- - Download CSV from URL β†’ temp file β†’ parse β†’ insert
220
- - API sync with date filter (`{--since=}`)
221
- - Per-row validation with error table
222
-
223
- ### Level 3: Real-World Processing
224
- - Chunked large file import (`$this->output->createProgressBar()`, `Record::insert($batch)`)
225
- - Parent-child CSV import (two-pass, `firstOrCreate()`)
226
- - Dispatch a job per imported row
227
- - Export filtered report with `Order::query()->chunk()`
228
-
229
- ### Level 4: Utilities
230
- - JSON schema validation
231
- - Merge two CSVs with deduplication
232
- - Generate fake test CSV with `fake()`
233
- - CSV column statistics with `?? 0` accumulator pattern
234
-
235
- ### Precision Patterns (v4 additions)
236
- - `count($array)` in `$this->info()` β€” never `$this->count`
237
- - Multi-key accumulators with consistent `?? 0` on ALL keys
238
- - Variable initialization before `withProgressBar()` closure capture
239
- - Dry-run + counter pattern with closure variable capture
240
-
241
- ### Advanced Patterns
242
- - `implements Isolatable` (prevent concurrent runs)
243
- - `implements PromptsForMissingInput`
244
- - `Storage::disk()->allFiles()` + `lastModified()` for file cleanup
245
- - `Http::timeout(30)->get()->body()` for large downloads
246
- - `tempnam()` + `unlink()` for temp file handling
247
 
248
  ## Usage
249
 
250
- ### With adapter (recommended β€” smaller download)
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
- - Always extend Illuminate\\Console\\Command
259
- - handle() returns Command::SUCCESS or Command::FAILURE (never raw int)
260
- - Progress bar: $this->withProgressBar($items, fn($item) => ...)
261
- - CSV parsing: fgetcsv($handle), array_combine($headers, $row)
262
- - HTTP fetch: Http::get($url)->throw()->json()
263
- - count(): use count($array) β€” Command has NO $this->count property
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": "Write a Laravel Artisan command app:import-products {file} {--dry-run} that reads a CSV with columns name, price, sku and upserts Product records using updateOrCreate(['sku' => $row['sku']], [...]), shows a progress bar, returns Command::SUCCESS."}
276
  ]
277
  text = tok.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
278
- output = generate(model, tok, prompt=text, max_tokens=900)
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 "import users from CSV with progress bar and dry-run option"
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** | 202 instruction-to-code pairs with Boost guidelines |
302
- | **Resumed from** | v3 iter 150 (val_loss 0.076) |
303
- | **Best checkpoint** | Iter 100 (val_loss 0.055) |
304
  | **Training hardware** | Apple M2 Pro, 16GB unified memory |
305
- | **Training time** | ~9 minutes (100 iters) |
306
- | **Peak memory** | 10.576 GB |
307
- | **Val loss** | 0.055 (iter 100) |
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
- | **v4 (7B)** | **202** | **0.055** | **+15 precision examples, 3 bug patterns eliminated** |
 
317
 
318
- ## Patterns Covered (15+ class types)
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 | **40+** |
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
- ## Evaluation Caveats
334
 
335
- - A proper evaluation using [Laravel's official 17-task benchmark](https://laravel.com/blog/which-ai-model-is-best-for-laravel) has not been done (we cover ~3/17 tasks)
336
- - Unseen patterns (Livewire, Inertia.js, complex relationships) still fail
337
- - The model needs end-token stripping in post-processing (`output[:output.index('<|im_end|>')]`)
338
- - Best results with specific instructions (name exact methods/signatures) rather than vague descriptions
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 | 202 examples | [fchis/Laravel-13x-Code-Instructions](https://huggingface.co/datasets/fchis/Laravel-13x-Code-Instructions) |
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