x-laravel/embedding

Automatic vector embedding generation for Laravel Eloquent models using laravel/ai.

Maintainers

Package info

github.com/x-laravel/embedding

pkg:composer/x-laravel/embedding

Statistics

Installs: 0

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v1.0.0 2026-05-06 08:32 UTC

This package is auto-updated.

Last update: 2026-05-06 08:35:08 UTC


README

Tests PHP Laravel License

A Laravel package that automatically generates and stores vector embeddings for Eloquent models using laravel/ai.

How It Works

  • Add the Embeddable trait 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.