laravel-relevance maintained by cyrus-48
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.