cyrus-48 / laravel-relevance
A Multipurpose Search & Recommendation Toolkit for Laravel
Requires
- php: ^8.2
- illuminate/console: ^11.0|^12.0|^13.0
- illuminate/contracts: ^11.0|^12.0|^13.0
- illuminate/database: ^11.0|^12.0|^13.0
- illuminate/events: ^11.0|^12.0|^13.0
- illuminate/pipeline: ^11.0|^12.0|^13.0
- illuminate/support: ^11.0|^12.0|^13.0
Requires (Dev)
- larastan/larastan: ^3.13
- orchestra/testbench: ^9.0|^10.0
- phpstan/phpstan: ^1.11|^2.0
- phpunit/phpunit: ^11.0|^12.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-10-16 17:17:14 UTC
README
Laravel Relevance
Laravel Relevance is an enterprise-grade search and recommendation orchestration toolkit for Laravel. Rather than being a simple engine wrapper, it provides the end-to-end relevance engineering pipeline: transactional outbox indexing, zero-downtime reindexing, vector embeddings, hybrid Reciprocal Rank Fusion (RRF), query rules, business signal boosts, recommendation placements with collaborative filtering, A/B experiments, offline relevance evaluation gates (NDCG@10), cross-encoder rerankers, diversity field collapse, federated multi-search, RAG chunk retrieval, and GDPR privacy tooling.
Warning
Active Early Development: Laravel Relevance is currently under active pre-v1.0 development. Public APIs and schema definitions may evolve before the initial stable release. Testing in non-production environments and contributing feedback, bug reports, and ideas via GitHub Issues is highly encouraged!
Table of Contents
- Architectural Highlights
- Quickstart (5 Minutes)
- Core Capabilities
- Hybrid Search (Text + Vectors + RRF)
- Relevance Profiles, Boosts & Query Rules
- Recommendations Engine & Placements
- A/B Experiments & Offline Evaluation CI Gate
- Scale-Out: Cross-Encoder Rerankers & Diversity
- Federated Multi-Index Search & RAG Retrieval
- Laravel Scout Compatibility
- Privacy (GDPR) & Fail-Closed Tenancy
- Artisan CLI Commands
- Detailed Documentation Guides
Architectural Highlights
- Fail-Soft Degradation: When an optional stage (query embedding, cross-encoder reranker, personalizer) times out or experiences an outage, search does not fail 500; it degrades gracefully back to candidate ranking, tagging
response->degraded = ['rerank']. - Transactional Outbox: Model mutations are committed in the same database transaction as the business write. A debounced queue worker drains, coalesces, and vectorizes updates with monotonic version guarantees.
- Zero-Downtime Generations: Rebuilding an index writes to a temporary physical generation (
products_g2) without affecting production reads on alias (products). Swapping aliases is atomic. - Strict Level 8 Typing: All internal DTOs are
final readonly, strictly typed, with 100% PHPStan Level 8 cleanliness.
Quickstart (5 Minutes)
1. Installation & Publishing
composer require cyrus-48/laravel-relevance php artisan relevance:install php artisan migrate
Verify your setup at any time with the built-in diagnostic doctor:
php artisan relevance:doctor
2. Define an Index
Generate a declarative index definition:
php artisan make:relevance-index ProductIndex
In app/Search/ProductIndex.php:
namespace App\Search; use App\Models\Product; use Illuminate\Database\Eloquent\Builder; use Relevance\Schema\IndexDefinition; use Relevance\Schema\Schema; class ProductIndex extends IndexDefinition { public function name(): string { return 'products'; } /** Source query used by `relevance:index:rebuild` (eager-load what toDocument() touches). */ public function source(): ?Builder { return Product::query(); } public function schema(Schema $s): void { $s->id('id'); $s->text('title')->weight(2.0); $s->text('description'); $s->keyword('brand')->filterable()->facet(); $s->keyword('category')->filterable()->facet(); $s->number('price')->filterable()->sortable(); $s->bool('in_stock')->filterable(); // Embedded from the listed fields via the configured embedding provider. $s->vector('embedding')->from(['title', 'description']); } public function toDocument(mixed $item): array { /** @var Product $item */ return [ 'id' => (string) $item->getKey(), 'title' => $item->title, 'description' => $item->description, 'brand' => $item->brand, 'category' => $item->category, 'price' => (float) $item->price, 'in_stock' => (bool) $item->in_stock, ]; } }
Register it in config/relevance.php:
'indexes' => [ \App\Search\ProductIndex::class, ],
3. Make Models Searchable
Add the Searchable trait to your Eloquent model:
namespace App\Models; use App\Search\ProductIndex; use Illuminate\Database\Eloquent\Model; use Relevance\Concerns\Searchable; class Product extends Model { use Searchable; public static function relevanceIndex(): ?string { return ProductIndex::class; } }
Every save(), update(), or delete() on Product now automatically writes an entry to relevance_outbox inside the model's database transaction.
4. Search with Eloquent Hydration
Execute queries with rich filters, facets, and rank-ordered Eloquent hydration:
use App\Models\Product; use Relevance\Facades\Relevance; // Fluent Builder on Model $products = Product::relevance('wireless noise cancelling headphones') ->where('in_stock', true) ->where('price', '<', 300) ->facets('brand', 'category') ->limit(20) ->models(); // Returns Collection<Product> in precise rank order // Or via the Relevance Facade $response = Relevance::index('products') ->search('mechanical keyboard') ->where('brand', '=', 'Keychron') ->limit(10) ->get(); foreach ($response->hits as $hit) { echo "ID: {$hit->id}, Score: {$hit->score}\n"; }
Core Capabilities
Hybrid Search (Text + Vectors + RRF)
Combine lexical BM25 matching and semantic dense vectors with reciprocal rank fusion:
$response = Relevance::index('products') ->search('running shoes for plantar fasciitis') ->mode('hybrid') ->semanticRatio(0.65) // 65% vector weight, 35% text weight ->get();
If the embedding provider is unreachable or times out, the engine automatically degrades to text retrieval without throwing exceptions:
if (in_array('embedding', $response->degraded, true)) { // Gracefully handled text fallback }
Relevance Profiles, Boosts & Query Rules
Configure ranking profiles with business signal boosts and rules:
use Relevance\Facades\Relevance; use Relevance\Search\Profiles\ProfileDefinition; Relevance::profiles()->register(new ProfileDefinition( name: 'ecommerce_default', index: 'products', semanticRatio: 0.5, boosts: [ ['type' => 'log', 'field' => 'sales_count', 'weight' => 0.15], ['type' => 'decay', 'field' => 'published_at', 'half_life_days' => 14, 'weight' => 0.10], ['type' => 'bool', 'field' => 'in_stock', 'weight' => 0.20], ], boostCap: 0.35, // Ensures business signals never exceed 35% of total score )); // Merchandising query rules Relevance::rules()->pin('iphone', ids: ['prod_15_pro_max']); Relevance::rules()->bury('cheap cables', ids: ['bad_cable_1']); Relevance::rules()->exclude('adult shoes', ids: ['kids_item_3']); // Synonyms Relevance::synonyms()->twoWay('tv', 'television', 'smart tv'); Relevance::synonyms()->oneWay('sneakers', 'kicks');
Explain ranking step-by-step:
$explain = Relevance::explain('laptop', index: 'products'); print_r($explain->trace);
Recommendations Engine & Placements
Register placements combining Item-kNN collaborative filtering, popularity, recently viewed, and exploration slots:
use Relevance\Facades\Relevance; use Relevance\Recommendations\Data\PlacementDefinition; Relevance::placements()->register(new PlacementDefinition( name: 'home.for_you', generators: [ 'item_knn' => 0.6, 'popularity' => 0.4, ], filters: ['already_purchased', 'out_of_stock', 'dismissed'], exploration: 0.10, // 10% bandit exploration slots limit: 12, )); // Serve recommendations for user $recs = Relevance::placement('home.for_you')->for($user)->get(); // Model Shortcuts $similar = $product->similar(limit: 6); $forUser = Product::recommendFor($user)->limit(10)->get();
A/B Experiments & Offline Evaluation CI Gate
Evaluate relevance offline using NDCG@10, MRR, Recall@k, and MAP:
# Run golden query judgments and enforce minimum NDCG@10 gate threshold
php artisan relevance:eval --suite=golden_ecommerce --gate-ndcg=0.65
If offline retrieval quality drops below threshold in CI, the command exits with code 1, preventing deployment of degraded ranking configurations.
Run online A/B experiments:
Relevance::experiments()->create( name: 'search_rrf_tuning_v2', variants: ['control' => 'profile_v1', 'treatment' => 'profile_v2'], weights: ['control' => 50, 'treatment' => 50], );
Scale-Out: Cross-Encoder Rerankers & Diversity
Cross-Encoder Rerankers with Graceful Timeout
Relevance::profiles()->register(new ProfileDefinition( name: 'reranked_search', index: 'products', rerank: [ 'provider' => 'cohere', 'top' => 50, 'timeout_ms' => 150, ], ));
Diversity & Field Collapse
Avoid brand monopolies in search results (e.g. limit to at most 2 per brand in top results):
Relevance::profiles()->register(new ProfileDefinition( name: 'diverse_search', index: 'products', diversify: [ 'max_per' => ['brand' => 2], ], ));
External Model Artifact Activation Gate
Activate offline-trained ML models (ALS, item2vec, EASE) with automatic validation threshold checking:
php artisan relevance:artifacts:activate storage/models/item2vec/manifest.json --index=products --min-metric=0.35
Federated Multi-Index Search & RAG Retrieval
Federated Search
Query across multiple indices with custom weightings:
$results = Relevance::multi([ 'products' => 1.0, 'articles' => 0.5, 'faqs' => 0.4, ])->query('wireless headphones return policy')->limit(10)->get(); $allHits = $results->all(); $sections = $results->sections(); // ['products' => SearchResponse, 'articles' => ...]
RAG Retrieval API
Chunk retrieval with parent document collapse and citation metadata for LLMs:
$rag = Relevance::retrieve('How do I reset my password?') ->index('knowledge_base') ->chunks() ->maxPerParent(2) ->limit(8) ->get(); foreach ($rag->chunks as $chunk) { echo "Content: {$chunk->content}\n"; echo "Parent ID: {$chunk->parentId}\n"; echo "Citations: " . json_encode($chunk->citations) . "\n"; }
Laravel Scout Compatibility
Seamlessly switch existing Laravel Scout projects to Relevance by updating .env:
SCOUT_DRIVER=relevance
RelevanceScoutEngine powers standard Scout methods (search(), paginate(), raw(), import(), flush()) through the underlying engine.
Privacy (GDPR) & Fail-Closed Tenancy
GDPR Right to Erasure & Portability
// Erase all analytics, logs, interactions, and vectors for a subject $report = Relevance::privacy()->erase($user); echo "Erased records: {$report->totalDeleted}\n"; // Export all user data for GDPR data access request $data = Relevance::privacy()->export($user);
Multi-Tenancy (Fail-Closed)
When config('relevance.tenancy.required') === true:
// Must supply tenant on search context, otherwise throws MissingTenantScopeException $res = Relevance::index('products') ->tenant('tenant_42') ->search('order #100') ->get(); // Admin audited escape hatch $adminRes = Relevance::index('products') ->withoutScopes('admin reconciliation') ->search('all data') ->get();
Artisan CLI Commands
| Command | Description |
|---|---|
php artisan relevance:install |
Publish configuration and migration files. |
php artisan relevance:doctor |
Run comprehensive health check and driver capability diagnostics. |
php artisan make:relevance-index {name} |
Scaffold a new declarative IndexDefinition class. |
php artisan relevance:sync |
Drain and vectorize pending records in relevance_outbox. |
php artisan relevance:index:rebuild {index} |
Zero-downtime rebuild into new physical generation. |
php artisan relevance:index:swap {index} {generation} |
Atomic alias swap or rollback. |
php artisan relevance:explain "{query}" |
Trace full query normalization, rules, and scoring stages. |
php artisan relevance:rec:train |
Train item-kNN shrunk cosine similarities. |
php artisan relevance:eval --suite={suite} |
Run offline relevance evaluation and enforce NDCG@10 CI gate. |
php artisan relevance:benchmark |
Benchmark latency across retrieval modes (text, vector, hybrid). |
php artisan relevance:artifacts:activate {manifest} |
Validate and activate external ML model artifacts. |
php artisan relevance:privacy:erase {user} |
Permanently delete all subject behavioral data under GDPR. |
php artisan relevance:prune |
Clean up interactions, search logs, and events past retention policies. |
Detailed Documentation Guides
For in-depth explanations, configuration options, and architectural blueprints, explore our documentation guides in docs/:
- Installation & Configuration
- Indexing, Schema & Transactional Outbox
- Search, Hybrid Retrieval & Vectors
- Relevance Controls: Profiles, Boosts, Rules & Synonyms
- Recommendations Engine & Collaborative Filtering
- A/B Experiments & Offline Evaluation (NDCG CI Gate)
- Scale-Out: Reranking, Diversity, Federated & RAG
- Security, Multi-Tenancy & GDPR Privacy
- Artisan CLI Command Reference
Testing & Quality Assurance
Run the test suite:
composer test # or vendor/bin/phpunit
Run static analysis:
composer analyse
# or
vendor/bin/phpstan analyse
Contributing
Contributions, bug reports, and pull requests are warmly welcomed! Please read our Contributing Guide for coding standards, strict PHPStan Level 8 guidelines, test execution, and the pull request checklist.
License
Laravel Relevance is open-sourced software licensed under the MIT license.