Looking to hire Laravel developers? Try LaraJobs

laravel-hybrid-search maintained by illuma-law

Description
Portable Full-Text Search (FTS) schema macros and the Reciprocal Rank Fusion (RRF) algorithm.
Author
Last update
2026/04/27 03:01 (v1.0.7)
License
Downloads
24

Comments
comments powered by Disqus

Laravel Hybrid Search

Tests Packagist License Latest Stable Version

Portable Full-Text Search macros and Reciprocal Rank Fusion

This package provides portable Full-Text Search (FTS) schema macros and the Reciprocal Rank Fusion (RRF) algorithm for Laravel. It enables seamless text searching across PostgreSQL, MySQL, SQL Server, and SQLite (via FTS5 virtual tables), and provides an elegant way to combine keyword search results with vector search results.

Database Support Matrix

Database Full-Text Macro Query Macro Syntax
PostgreSQL Native Native whereFullText
MySQL Native Native whereFullText
SQL Server Manual Instructions Native (via CONTAINS) CONTAINS
SQLite Virtual Table + Triggers Native (via MATCH) MATCH

Installation

Require this package with composer using the following command:

composer require illuma-law/laravel-hybrid-search

Usage

TL;DR

Create a full-text index in your migration:

$table->hybridFullText(['title', 'body'], 'articles_search_index');

Perform a keyword search:

$results = Article::query()->whereHybridFullText(['title', 'body'], 'laravel macros')->get();

Combine keyword and vector search results:

$combined = ReciprocalRankFusion::combine([$keywordResults, $vectorResults]);

Schema Migrations

Use the hybridFullText macro to define portable full-text indexes.

On PostgreSQL and MySQL, this delegates to Laravel's native full-text index generation. On SQLite, it intelligently creates an FTS5 virtual table (e.g., articles_fts) and sets up INSERT, UPDATE, and DELETE database triggers to ensure the virtual table automatically stays synchronized with your main table.

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration {
    public function up(): void
    {
        Schema::create('articles', function (Blueprint $table) {
            $table->id();
            $table->string('title');
            $table->text('body');
            $table->timestamps();
        });

        // Creates native FTS index on pgsql/mysql, 
        // or FTS5 virtual table + triggers on sqlite.
        Schema::table('articles', function (Blueprint $table) {
            $table->hybridFullText(['title', 'body'], 'articles_search_index');
        });
    }

    public function down(): void
    {
        Schema::table('articles', function (Blueprint $table) {
            // Safely drops native indexes or SQLite virtual tables/triggers
            $table->dropHybridFullText('articles_search_index');
        });
        Schema::dropIfExists('articles');
    }
};

Note for SQL Server: The macro will throw an exception with instructions to manually create the Full-Text Catalog and Index, as SQL Server requires specific configuration for FTS.

Full-Text Searching

Use the whereHybridFullText macro on any Query Builder or Eloquent Builder instance to perform text searches. It safely abstracts the complex MATCH syntax required by SQLite's FTS5, and the CONTAINS syntax for SQL Server, while using native whereFullText capabilities on PostgreSQL and MySQL.

$results = Article::query()
    ->whereHybridFullText(['title', 'body'], 'laravel hybrid search')
    ->get();

You can also invert the search to exclude matches:

$results = Article::query()
    ->whereHybridFullText(['title', 'body'], 'outdated', not: true)
    ->get();

Reciprocal Rank Fusion (RRF)

When building advanced search systems, you often want to combine results from multiple retrieval strategies. For example, you might retrieve the top 50 results using traditional keyword search (BM25), and another top 50 using semantic vector search (Cosine Similarity).

The ReciprocalRankFusion class provides a mathematically sound way to merge these disparate result sets and rank them based on their relative positions in the original lists.

use IllumaLaw\HybridSearch\ReciprocalRankFusion;

// 1. Get results from Keyword Search
$keywordResults = Article::query()
    ->whereHybridFullText(['title', 'body'], 'authentication')
    ->take(50)
    ->get();

// 2. Get results from Vector Search (using laravel-vector-schema)
$vectorResults = Article::query()
    ->whereHybridVectorSimilarTo('embedding', $queryVector)
    ->take(50)
    ->get();

// 3. Combine and re-rank the results
$combined = ReciprocalRankFusion::combine(
    [$keywordResults, $vectorResults],
    k: 60 // Optional: RRF constant (default is 60)
);

// $combined is a unique Collection of Article models, 
// ordered by their computed RRF score.

Testing

composer test

Credits

License

The MIT License (MIT). Please see License File for more information.