fchis/Laravel-13x-Qwen2.5-Coder-7B-Instruct-LoRA
Laravel 13.x Qwen2.5-Coder-7B-Instruct LoRA (v5)
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.
What Changed in v5 (Sprint 3: Coverage Expansion)
Sprint 3 eval results (E1–E8)
*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.
Real-world validation
All eval prompts were tested in a real Laravel 13.2.0 + SQLite project:
php artisan test → 14/14 passed (28 assertions)Tests cover: POST create (auth, 401, 422 validation), GET paginated (meta.total), DELETE own/other (204, 403).
Root causes found in Sprint 3
Evaluation: API Resource (E1)
Prompt: "Create UserResource with optional posts collection (whenLoaded) and subscription (whenLoaded). Use whenNotNull for email_verified_at."
<?php
namespace App\Http\Resources;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
class UserResource extends JsonResource
{
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'name' => $this->name,
'email' => $this->email,
'email_verified_at' => $this->whenNotNull($this->email_verified_at),
'posts' => PostResource::collection($this->whenLoaded('posts')),
'subscription' => SubscriptionResource::make($this->whenLoaded('subscription')),
'created_at' => $this->created_at,
];
}
}Correct: whenLoaded used (not $this->posts), whenNotNull used (not ternary), returns array.
Evaluation: Form Request (E4)
Prompt: "StoreInvoiceRequest with messages(), attributes(), prepareForValidation() that rounds amount."
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class StoreInvoiceRequest extends FormRequest
{
public function authorize(): bool
{
return true;
}
public function rules(): array
{
return [
'client_id' => ['required', 'exists:clients,id'],
'amount' => ['required', 'numeric', 'min:0.01'],
'due_date' => ['required', 'date', 'after:today'],
'notes' => ['nullable', 'string', 'max:1000'],
];
}
public function messages(): array
{
return [
'client_id.required' => 'The client is required.',
'amount.required' => 'The amount is required.',
'amount.numeric' => 'The amount must be a number.',
'due_date.required' => 'The due date is required.',
];
}
public function attributes(): array
{
return [
'client_id' => 'Client',
'due_date' => 'Due Date',
];
}
public function prepareForValidation(): void
{
$this->merge([
'amount' => round($this->amount, 2),
]);
}
}Correct: messages() uses field.rule keys, prepareForValidation() uses $this->merge() (not direct assignment).
Evaluation: Pest Test (E6)
Prompt: "Pest test for POST /api/posts: auth creates (201 + assertDatabaseHas), unauth returns 401, missing title returns 422 with validation errors."
<?php
use App\Models\Post;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
uses(RefreshDatabase::class);
test('authenticated user creates a post with title and body', function () {
$user = User::factory()->create();
$this->actingAs($user)
->postJson('/api/posts', ['title' => 'Test Post', 'body' => 'Body text.'])
->assertCreated();
$this->assertDatabaseHas('posts', ['title' => 'Test Post', 'user_id' => $user->id]);
});
test('unauthenticated user gets 401', function () {
$this->postJson('/api/posts', ['title' => 'Test Post', 'body' => 'Body text.'])
->assertUnauthorized();
});
test('authenticated user sends missing title gets 422', function () {
$user = User::factory()->create();
$this->actingAs($user)
->postJson('/api/posts', ['body' => 'Body text.'])
->assertUnprocessable()
->assertJsonValidationErrors(['title']);
});Tested in real Laravel 13.2.0 — 3/3 assertions pass.
Usage
With adapter (recommended)
from mlx_lm import load, generate
SYSTEM = """You are a senior Laravel developer. Write clean, production-ready Laravel 13.x code.
Output only the PHP file contents — no markdown, no explanation, no ```php fences.
### API Resources
// Always extend JsonResource. toArray() returns array, not JsonResponse.
// Relationships: $this->whenLoaded('relation', fn() => RelatedResource::make($this->relation))
// Collections: $this->whenLoaded('items', fn() => ItemResource::collection($this->items))
// Conditional field: $this->when($condition, $value)
// Conditional block: $this->mergeWhen($condition, ['field' => $value])
// Nullable field: $this->whenNotNull($this->deleted_at)
// ResourceCollection: override with() to add pagination metadata
// Never call toArray() manually — Laravel calls it automatically
### Form Request precision
// messages(): return ['field.rule' => 'Custom message'] — overrides default error text
// attributes(): return ['field' => 'Human Name'] — used in :attribute placeholder
// prepareForValidation(): call $this->merge([...]) to normalize input before rules run
// after(): return [fn(Validator $v) => $v->errors()->addIf($condition, 'field', 'msg')]
// passedValidation(): runs after all rules pass — use for side effects, not validation
// NEVER use $this->validate() — that is a Controller method
### Pest Feature Tests
// File: tests/Feature/SomeTest.php
// uses(RefreshDatabase::class); at top of file
// HTTP: $this->getJson('/api/route'), postJson(), putJson(), patchJson(), deleteJson()
// Status: ->assertOk() (200), ->assertCreated() (201), ->assertNoContent() (204)
// ->assertUnprocessable() (422), ->assertUnauthorized() (401), ->assertForbidden() (403)
// JSON: ->assertJson(['key' => 'value']), ->assertJsonStructure(['data' => ['id', 'name']])
// ->assertJsonCount(3, 'data'), ->assertJsonPath('data.0.name', 'Alice')
// DB: assertDatabaseHas('table', ['col' => 'val']), assertDatabaseMissing(), assertDatabaseCount()
// Auth: $this->actingAs(User::factory()->create())
// Fakes: Queue::fake(), Event::fake(), Mail::fake() then ::assertPushed/Dispatched/Sent
// IMPORTANT: Pest uses function syntax (uses/test/it) NOT class-based syntax
### Artisan Commands
// Always extend Illuminate\\Console\\Command
// handle() returns Command::SUCCESS or Command::FAILURE (never raw int)
// count(): use count($array) — Command has NO $this->count property
// Accumulators: ALL keys in same array use (?? 0) + $value pattern
// Initialize ALL closure variables BEFORE the closure definition
// Facades\\Progress does NOT exist"""
model, tok = load(
"mlx-community/Qwen2.5-Coder-7B-Instruct-4bit",
adapter_path="fchis/Laravel-13x-Qwen2.5-Coder-7B-Instruct-LoRA"
)
messages = [
{"role": "system", "content": SYSTEM},
{"role": "user", "content": "Create a UserResource with whenLoaded for posts (PostResource collection) and subscription (SubscriptionResource). Use whenNotNull for email_verified_at."}
]
text = tok.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
output = generate(model, tok, prompt=text, max_tokens=1200)
if '<|im_end|>' in output:
output = output[:output.index('<|im_end|>')]
print(output.strip())CLI pipeline
pip install mlx-lm
git clone https://github.com/florinel-chis/laravel-ai-gen
cd your-laravel-project
python3 laravel-gen.py --model 7b "UserResource with posts collection and subscription via whenLoaded"Model Details
Training History
Patterns Covered (18+ class types)
Known Limitations
- Pest syntax: model defaults to PHPUnit class-based tests unless prompt explicitly says "Pest function syntax (uses/test/it)"
- Unseen patterns (Livewire, Inertia.js, complex Eloquent scopes) still limited
- End-token stripping required in post-processing:
output[:output.index('<|im_end|>')] - Laravel official 17-task benchmark coverage: ~5/17 tasks (added Resources, Form precision, Tests to the previous 3)
Companion Models
License
Apache 2.0
