vlados/laravel-related-content
Composer 安装命令:
composer require vlados/laravel-related-content
包简介
Build related content links using vector embeddings and pgvector for Laravel
README 文档
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, 12, or 13
- PostgreSQL with pgvector extension
Installation
1. Install pgvector extension in PostgreSQL
CREATE EXTENSION IF NOT EXISTS vector;
The migration runs this automatically, but CREATE EXTENSION requires a
privileged database user. On managed Postgres (RDS, Cloud SQL, Supabase, etc.)
enable the extension up front — as shown above or via the provider's dashboard —
so the migration only needs to create the tables.
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 (this model as source only) $post->relatedContent()->with('related')->get();
getRelatedModels()andgetRelatedOfType()are the complete views — they merge both directions (where this model is the source or the related target). The rawrelatedContent()relation returns only the rows where this model is the source; because each pair is stored once (and refreshed on re-sync), prefer the helper methods when you want the full set.
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, ]); // By default search returns the closest N matches regardless of distance. // Pass a minimum similarity (0-1) to filter out weak matches: $results = $service->search('charging stations', [], limit: 10, threshold: 0.5);
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', ], ];
Embedding dimensions
The active provider's dimensions value is the single source of truth. It
sizes the vector column when the migration runs and determines the length of
every stored vector, so the two can never drift. The top-level dimensions
key is only a fallback used when the active provider does not define its own.
Because the column width is fixed at migration time, changing the effective
dimension count (or switching to a provider with a different one) requires a
fresh migration of the embeddings table — re-run related-content:rebuild
afterwards to regenerate the vectors.
Disabling
Set RELATED_CONTENT_ENABLED=false (or leave the provider's credentials empty)
and the package degrades gracefully: no jobs are dispatched and no embeddings
are written, so existing rows are left untouched.
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.
When a model is re-synced (its embeddable content changed), every link incident
to it is rebuilt in both directions, so similarity scores never go stale. Links
to models that are no longer mutually similar are rebuilt on their own next sync,
or run related-content:rebuild --force to refresh the whole graph at once.
Search accuracy at scale
The embeddings table uses an HNSW index, which performs approximate nearest
-neighbour search. When you mix several model types and filter by type (or by the
similarity threshold), pgvector may occasionally return fewer than
max_related_items candidates because the type filter is applied after the
index narrows the search. If you rely on cross-model results over a large
dataset, raise hnsw.ef_search for the session or consider pgvector 0.8+
iterative index scans.
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.
Credits
vlados/laravel-related-content 适用场景与选型建议
vlados/laravel-related-content 是一款 基于 PHP 开发的 Composer 扩展包,目前已累计 628 次下载、GitHub Stars 达 13, 最近一次更新时间为 2025 年 12 月 06 日, 在 PHP 生态内属于活跃度较高的组件。
它主要适用于以下技术方向: 「laravel」 「vectors」 「similarity」 「recommendations」 「vlados」 「embeddings」 等业务场景。在实际项目中,围绕这些方向常见需要落地的问题包括:接口对接、性能调优、并发安全、与既有框架(Laravel / ThinkPHP / Yii / Webman 等)的兼容适配,以及生产环境的日志埋点与稳定性保障。
我们在过去多个企业项目中使用过 vlados/laravel-related-content 或与其功能相近的方案,如果你在选型或落地过程中遇到问题,例如 版本兼容、二次改造、私有化封装、与内部系统对接、生产 BUG 排查,欢迎联系我们协助评估。
基于 vlados/laravel-related-content 在你已有业务上做功能扩展、字段裁剪、UI 适配、与内部账号 / 权限 / 日志系统的深度对接。
线上偶发问题、内存泄漏、慢查询、并发异常等排查修复;针对高流量场景做缓存、队列、索引层面的调优。
承接完整的项目从需求 → 设计 → 开发 → 上线 → 长期运维;也可按月提供技术保姆服务。
与 vlados/laravel-related-content 相关的其它包
同方向 / 同关键字的高下载量 PHP Composer 包推荐,方便对比选型:
A PHP client for the Chroma Open Source Embedding Database
SimHash similarities algorithm implementation for PHP 5.3
PHP Interface for Babel Street Text Analytics
A fulltext search module for SilverStripe that filters any data object and their relations, with filtering, fuzzy and boosting options.
Raphael.js Bundle for Symfony2
ChromaDB Laravel is a Laravel client for the Chroma Open Source Embedding Database
统计信息
- 总下载量: 628
- 月度下载量: 0
- 日度下载量: 0
- 收藏数: 13
- 点击次数: 35
- 依赖项目数: 0
- 推荐数: 0
其他信息
- 授权协议: MIT
- 更新时间: 2025-12-06