vlados / laravel-related-content
Build related content links using vector embeddings and pgvector for Laravel
Installs: 0
Dependents: 0
Suggesters: 0
Security: 0
Stars: 0
Watchers: 0
Forks: 0
Open Issues: 0
pkg:composer/vlados/laravel-related-content
Requires
- php: ^8.2
- illuminate/contracts: ^11.0||^12.0
- pgvector/pgvector: ^0.2
- spatie/laravel-package-tools: ^1.16
Requires (Dev)
- guzzlehttp/guzzle: ^7.8
- larastan/larastan: ^3.0
- laravel/pint: ^1.14
- mockery/mockery: ^1.6
- nunomaduro/collision: ^8.8
- orchestra/testbench: ^10.0.0||^9.0.0
- pestphp/pest: ^4.0
- pestphp/pest-plugin-arch: ^4.0
- pestphp/pest-plugin-laravel: ^4.0
This package is auto-updated.
Last update: 2025-12-06 21:21:09 UTC
README
Build related content links using vector embeddings and pgvector for Laravel.
Features
- 🔗 Pre-computed Related Links - Related content is calculated on save, not on every page load
- 🚀 Fast Lookups - O(1) relationship queries instead of real-time similarity search
- 🔄 Cross-Model Relationships - Find related content across different model types (Blog → Events → Questions)
- 🧠 Multiple Embedding Providers - Support for OpenAI and Ollama
- 📦 Queue Support - Process embeddings in the background
- 🔍 Semantic Search - Search content by meaning, not just keywords
Requirements
- PHP 8.3+
- Laravel 11 or 12
- PostgreSQL with pgvector extension
Installation
1. Install pgvector extension in PostgreSQL
CREATE EXTENSION IF NOT EXISTS vector;
2. Install the package via Composer
composer require vlados/laravel-related-content
3. Publish the config and migrations
php artisan vendor:publish --tag="related-content-config" php artisan vendor:publish --tag="related-content-migrations" php artisan migrate
4. Configure your environment
# Embedding provider (openai or ollama) RELATED_CONTENT_PROVIDER=openai # OpenAI settings OPENAI_API_KEY=your-api-key OPENAI_EMBEDDING_MODEL=text-embedding-3-small OPENAI_EMBEDDING_DIMENSIONS=1536 # Or Ollama settings OLLAMA_BASE_URL=http://localhost:11434 OLLAMA_EMBEDDING_MODEL=nomic-embed-text
Usage
1. Add the trait to your models
use Vlados\LaravelRelatedContent\Concerns\HasRelatedContent; class BlogPost extends Model { use HasRelatedContent; /** * Define which fields should be embedded. */ public function embeddableFields(): array { return ['title', 'excerpt', 'content']; } }
2. Configure models for cross-model relationships
In config/related-content.php:
'models' => [ \App\Models\BlogPost::class, \App\Models\Event::class, \App\Models\Question::class, ],
3. Related content is automatically synced on save
$post = BlogPost::create([ 'title' => 'Electric Vehicle Charging Guide', 'content' => '...', ]); // Embedding is generated and related content is found automatically
4. Retrieve related content
// Get all related content $related = $post->getRelatedModels(); // Get related content of a specific type $relatedEvents = $post->getRelatedOfType(Event::class); // Get the raw relationship with similarity scores $post->relatedContent()->with('related')->get();
5. Use in Blade templates
@if($post->relatedContent->isNotEmpty()) <div class="related-content"> <h3>Related Content</h3> @foreach($post->getRelatedModels(5) as $item) <a href="{{ $item->url }}">{{ $item->title }}</a> @endforeach </div> @endif
Artisan Commands
Rebuild embeddings and related content
# Process models missing embeddings (default behavior) php artisan related-content:rebuild # Process a specific model (missing only) php artisan related-content:rebuild "App\Models\BlogPost" # Force regenerate all embeddings php artisan related-content:rebuild --force # Process synchronously (instead of queuing) php artisan related-content:rebuild --sync # With custom chunk size php artisan related-content:rebuild --chunk=50
Semantic Search
You can also use the package for semantic search:
use Vlados\LaravelRelatedContent\Services\RelatedContentService; $service = app(RelatedContentService::class); // Search across all embeddable models $results = $service->search('electric vehicle charging'); // Search specific model types $results = $service->search('charging stations', [ \App\Models\Event::class, \App\Models\BlogPost::class, ]);
Configuration
return [ // Embedding provider: 'openai' or 'ollama' 'provider' => env('RELATED_CONTENT_PROVIDER', 'openai'), // Provider-specific settings 'providers' => [ 'openai' => [ 'api_key' => env('OPENAI_API_KEY'), 'base_url' => env('OPENAI_BASE_URL', 'https://api.openai.com/v1'), 'model' => env('OPENAI_EMBEDDING_MODEL', 'text-embedding-3-small'), 'dimensions' => env('OPENAI_EMBEDDING_DIMENSIONS', 1536), ], 'ollama' => [ 'base_url' => env('OLLAMA_BASE_URL', 'http://localhost:11434'), 'model' => env('OLLAMA_EMBEDDING_MODEL', 'nomic-embed-text'), 'dimensions' => env('OLLAMA_EMBEDDING_DIMENSIONS', 768), ], ], // Maximum related items per model 'max_related_items' => 10, // Minimum similarity threshold (0-1) 'similarity_threshold' => 0.5, // Queue settings 'queue' => [ 'connection' => 'default', 'name' => 'default', ], // Models to include in cross-model relationships 'models' => [], // Database table names 'tables' => [ 'embeddings' => 'embeddings', 'related_content' => 'related_content', ], ];
Events
The package dispatches events you can listen to:
use Vlados\LaravelRelatedContent\Events\RelatedContentSynced; class HandleRelatedContentSynced { public function handle(RelatedContentSynced $event): void { // $event->model - The model that was synced } }
How It Works
- On Model Save: When a model with
HasRelatedContentis saved, a job is dispatched - Generate Embedding: The job generates a vector embedding from the model's embeddable fields
- Find Similar: Uses pgvector to find similar content across all configured models
- Store Links: Stores the related content relationships in the
related_contenttable - Fast Retrieval: When displaying related content, it's a simple database lookup (no API calls)
Bidirectional Relationships
Related content works in both directions automatically. When a new BlogPost is saved and finds an Event as related, the Event will also show the BlogPost in its related content - without needing to re-sync the Event.
This is achieved by querying both directions:
- Forward: where this model is the source
- Reverse: where this model is the related target
Results are deduplicated and sorted by similarity score.
Performance
- Embedding Generation: ~200-500ms per model (depends on text length and provider)
- Related Content Lookup: ~5ms (simple database query)
- Storage: ~6KB per embedding (1536 dimensions x 4 bytes)
License
MIT License. See LICENSE for more information.