qdrant-labs / qdrant-laravel-scout
A Qdrant driver for Laravel Scout with full-text, semantic, and hybrid search.
Package info
github.com/qdrant-labs/qdrant-laravel-scout
pkg:composer/qdrant-labs/qdrant-laravel-scout
Requires
- php: ^8.2
- illuminate/database: ^12.0|^13.0
- illuminate/http: ^12.0|^13.0
- illuminate/support: ^12.0|^13.0
- laravel/scout: ^11.6
Requires (Dev)
- orchestra/testbench: ^10.0|^11.0
- phpunit/phpunit: ^11.5|^12.0
Suggests
- laravel/ai: Required to generate embeddings with the default laravel-ai driver (^1.0, needs PHP 8.3).
Provides
None
Conflicts
- laravel/ai: <1.0
Replaces
None
README
A Qdrant driver for Laravel Scout with full-text, semantic, and hybrid search.
Requirements
- PHP 8.2 or later
- Laravel 12 or 13
- Laravel Scout 11.6 or a later 11.x release
- Qdrant 1.17 or later
- For the default embedding driver: the Laravel AI SDK 1.0 or later, which requires PHP 8.3
Installation
Install the package with Composer:
composer require qdrant-labs/qdrant-laravel-scout
Set the Scout driver and the Qdrant connection in your .env file:
SCOUT_DRIVER=qdrant QDRANT_HOST=http://localhost:6333 QDRANT_API_KEY=
Publish the configuration file to config/scout-qdrant.php:
php artisan vendor:publish --tag=scout-qdrant-config
Configuration
Add each searchable model to model-settings in config/scout-qdrant.php:
'model-settings' => [ App\Models\Post::class => [ 'searchable-attributes' => ['title', 'body', 'tags'], 'embedding' => [ 'attribute' => 'embedding', 'dimensions' => 1536, ], 'payload-indexes' => [ 'status' => 'keyword', 'published_at' => 'integer', ], ], ],
Scout creates the collection, its vectors, and its payload indexes the first time it indexes a model. You don't need to run scout:index.
| Setting | Use |
|---|---|
searchable-attributes |
The attributes for full-text search. Qdrant generates one BM25 sparse vector from their values. Array attributes are included. |
full-text |
Optional. The sparse vector name (vector, default bm25), the model (model, default qdrant/bm25), and model options. |
embedding |
The dense vector for semantic and hybrid search. Set the vector name (attribute) and the dimensions. Optional keys are distance (default Cosine), driver, provider, model, and options. The options are provider options for the Laravel AI SDK or Qdrant inference. |
payload-indexes |
The payload indexes to create, as field name and schema type. Use them for the fields that you filter or order on. |
collection |
Optional. Extra collection parameters, such as HNSW, quantization, or strict mode settings. |
Scout reads the vector settings only when it creates the collection. If you change searchable-attributes or embedding later, run scout:flush and then scout:import. Scout creates payload indexes that you add later on the next import.
The engine also indexes __scout_key and, when you use soft deletes, __soft_deleted.
Embeddings
By default, Scout generates embeddings with the Laravel AI SDK. Install it with composer require laravel/ai. The SDK requires PHP 8.3 or later. On PHP 8.2, use the qdrant driver below.
Add a toSearchableEmbedding method to the model. It returns the text to embed, or an embedding array that you have computed already:
public function toSearchableEmbedding(): string|array { return $this->title."\n\n".$this->body; }
To let Qdrant generate the embeddings with inference, set the qdrant driver and a model:
'embedding' => [ 'attribute' => 'embedding', 'dimensions' => 384, 'driver' => 'qdrant', 'model' => 'sentence-transformers/all-minilm-l6-v2', ],
Dense model inference requires Qdrant Cloud Inference.
Usage
Full-text search
$posts = Post::search('deploy laravel') ->where('status', 'published') ->paginate(15);
Semantic search
$posts = Post::search('how do I ship my app to production') ->semantic(minSimilarity: 0.7) ->get();
minSimilarity requires the Cosine or Dot distance.
Hybrid search
Hybrid search combines the full-text and semantic results with weighted Reciprocal Rank Fusion:
$posts = Post::search('deploy laravel') ->hybrid(textWeight: 1, semanticWeight: 2) ->get();
Filters and ordering
The engine supports where with the =, !=, <, <=, >, and >= operators, whereIn, and whereNotIn.
Filter values use the type of the field's payload index. On a field without an index, a number also matches its string form, and the reverse.
orderBy works on searches without a query string, such as Post::search(''). The field needs an integer, float, or datetime payload index.
Qdrant options
Pass Query API parameters with options:
$posts = Post::search('') ->options([ 'filter' => ['must' => [['key' => 'tags', 'match' => ['value' => 'php']]]], 'params' => ['hnsw_ef' => 128], ]) ->get();
Each model in the results has its Qdrant score in $post->scoutMetadata()['_qdrant_score'].
Pagination
Full-text, semantic, and hybrid searches return at most max_results results across all pages (default 1,000). Call take to change the maximum for one search. Searches without a query string have no maximum.
Limitations
searchable-attributestakes a list of attribute names without weights.- A search can use only one
orderByclause. Ordering by an array field repeats points across pages, because Qdrant returns a point once for each value. scout:indexdoes nothing, because Scout creates collections on the first import.