x-laravel / embedding
Automatic vector embedding generation for Laravel Eloquent models using laravel/ai.
Requires
- php: ^8.3
- illuminate/database: ^12.0|^13.0
- illuminate/queue: ^12.0|^13.0
- illuminate/support: ^12.0|^13.0
- laravel/ai: ^0.6
Requires (Dev)
- orchestra/testbench: ^10.0|^11.0
- phpunit/phpunit: ^11.0|^12.0
This package is auto-updated.
Last update: 2026-05-06 08:35:08 UTC
README
A Laravel package that automatically generates and stores vector embeddings for Eloquent models using laravel/ai.
How It Works
- Add the
Embeddabletrait to any model — embeddings are generated automatically on save - Only the fields you specify trigger embedding generation
- Embedding generation is handled by a queued job — no blocking
- Similarity search is driver-based: PHP (default), PostgreSQL (pgvector), or MySQL 9 native VECTOR
Requirements
- PHP ^8.3
- Laravel ^12.0 | ^13.0
laravel/ai ^0.6
Installation
composer require x-laravel/embedding
Run the migration:
php artisan migrate
Setup
1. Model
Add the Embeddable trait and implement HasEmbeddings:
use Illuminate\Database\Eloquent\Model; use XLaravel\Embedding\Concerns\Embeddable; use XLaravel\Embedding\Contracts\HasEmbeddings; class Post extends Model implements HasEmbeddings { use Embeddable; protected array $embeddable = ['title', 'body']; public function toEmbeddingText(): string { return $this->title . ' ' . $this->body; } }
toEmbeddingText() defines what text is sent to the embedding model. $embeddable defines which fields, when changed, trigger a new embedding.
2. Defining Which Fields Trigger Embedding
Option 1 — $embeddable property:
protected array $embeddable = ['title', 'body']; // [] = never, ['*'] = always
Option 2 — #[EmbedOn] PHP attribute:
use XLaravel\Embedding\Attributes\EmbedOn; #[EmbedOn('title', 'body')] class Post extends Model implements HasEmbeddings { ... }
Both approaches merge — you can use either or both.
Usage
Generating embeddings
$post->embed(); // dispatch async job $post->embedSync(); // generate synchronously $post->hasEmbedding(): bool $post->embedding // MorphOne → Embedding model
Suppressing embedding generation
Post::withoutEmbedding(fn() => Post::create([...])); // suppress for closure Post::disableEmbedding(); // suppress globally Post::enableEmbedding();
Similarity search
// Find models most similar to a query vector Post::similarTo($vector, limit: 10); // Filter by minimum similarity score and Eloquent constraints Post::similarTo($vector, limit: 10, threshold: 0.8, where: fn($q) => $q->where('status', 'published')); // Auto-embed a text query, then search Post::similarToText('web framework', limit: 10); // Rank an existing collection by similarity to a text or vector Post::rankByRelevance($posts, 'web framework'); Post::rankByRelevance($posts, $vector); // Compare two models or a model with a vector $post->similarityTo($otherPost): float $post->similarityTo($vector): float // Find the most similar records to this model, excluding itself $post->mostSimilar(limit: 5);
All similarity methods set a similarity_score attribute (float) on each returned model.
threshold defaults to 0.0 — pass a value between 0.0 and 1.0 to filter low-scoring results.
Similarity Drivers
The driver is selected automatically based on the database connection:
| Driver | Condition | Computation |
|---|---|---|
php |
Default (SQLite, MySQL 8, etc.) | PHP-side cosine similarity |
pgsql |
PostgreSQL with pgvector | vector <=> operator |
mysql |
MySQL 9+ | VEC_DISTANCE_COSINE |
Override via config or register a custom driver:
// config/embedding.php 'similarity' => ['driver' => 'pgsql'], // Custom driver app(SimilarityManager::class)->extend('custom', fn() => new MyDriver());
Model Events
// Static listeners Post::onEmbedding(fn($post) => ...); // before generation Post::onEmbedded(fn($post) => ...); // after record saved // Observer class class PostObserver { public function embedding(Post $post): void { ... } public function embedded(Post $post): void { ... } }
Laravel events ModelEmbedding and ModelEmbedded are also fired at the same points.
Soft Delete
By default, deleting a model also deletes its embedding. Set embedding.soft_delete to true to preserve embeddings on soft delete.
Per-model override:
class Post extends Model implements HasEmbeddings { use Embeddable, SoftDeletes; protected bool $keepEmbeddingOnSoftDelete = true; }
| Event | false (default) |
true |
|---|---|---|
| soft delete | embedding deleted | embedding kept |
| restore | embedding regenerated | unchanged |
| force delete | embedding deleted | embedding deleted |
Artisan Command
php artisan embedding:generate "App\Models\Post" # only missing embeddings php artisan embedding:generate "App\Models\Post" --all # regenerate all records php artisan embedding:generate "App\Models\Post" --chunk=500
Database
embeddings
├── id
├── embeddable_type (polymorphic — Post, Article, etc.)
├── embeddable_id
├── vector (json / pgvector / MySQL 9 VECTOR)
├── created_at
└── updated_at
The vector column type is selected automatically based on the database driver at migration time.
Testing
# Build first (once per PHP version) DOCKER_BUILDKIT=0 docker compose --profile php83 build # Run tests docker compose --profile php83 up docker compose --profile php84 up docker compose --profile php85 up
License
This package is open-sourced software licensed under the MIT license.