Looking to hire Laravel developers? Try LaraJobs

laravel-relevance maintained by cyrus-48

Description
A Multipurpose Search & Recommendation Toolkit for Laravel
Author
Last update
2026/10/16 19:17 (dev-main)
License
Links
Downloads
1

Comments
comments powered by Disqus

Laravel Relevance

Latest Version PHP Version Laravel Version PHPStan Level 8 Tests Passing License: MIT

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

  • 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/:

  1. Installation & Configuration
  2. Indexing, Schema & Transactional Outbox
  3. Search, Hybrid Retrieval & Vectors
  4. Relevance Controls: Profiles, Boosts, Rules & Synonyms
  5. Recommendations Engine & Collaborative Filtering
  6. A/B Experiments & Offline Evaluation (NDCG CI Gate)
  7. Scale-Out: Reranking, Diversity, Federated & RAG
  8. Security, Multi-Tenancy & GDPR Privacy
  9. 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.