symfony/ai-meilisearch-store
Meilisearch Store integrates Meilisearch as a vector store for Symfony AI Store, enabling hybrid and vector/semantic search with semanticRatio support. Includes links to Meilisearch docs and points to the main Symfony AI repo for issues and PRs.
composer require symfony/ai symfony/ai-meilisearch-store meilisearch/meilisearch-php
MEILISEARCH_HOST=http://localhost:7700
MEILISEARCH_API_KEY=your_master_key
MEILISEARCH_INDEX=your_vector_index
use Symfony\Component\AI\Store\MeilisearchStore;
use Meilisearch\Client;
$client = new Client(env('MEILISEARCH_HOST'), env('MEILISEARCH_API_KEY'));
$store = new MeilisearchStore($client, env('MEILISEARCH_INDEX'));
// Add a vector embedding
$store->add([0.1, 0.2, 0.3], ['id' => 1, 'title' => 'Example Document']);
// Hybrid search (keyword + vector)
$results = $store->search(
'laravel',
[0.1, 0.2, 0.3],
semanticRatio: 0.5 // 50% keyword, 50% vector
);
symfony/ai to generate embeddings for FAQ documents (e.g., with a model like sentence-transformers/all-mpnet-base-v2).$faqEmbeddings = [
['embedding' => $embedding1, 'metadata' => ['question' => 'How to install Laravel?']],
['embedding' => $embedding2, 'metadata' => ['question' => 'What is Eloquent?']],
];
foreach ($faqEmbeddings as $item) {
$store->add($item['embedding'], $item['metadata']);
}
$userQuery = "Set up Laravel project";
$userEmbedding = generateEmbedding($userQuery); // Using symfony/ai
$results = $store->search($userQuery, $userEmbedding, semanticRatio: 0.7);
$store->addMany([
['embedding' => $vec1, 'metadata' => ['id' => 1]],
['embedding' => $vec2, 'metadata' => ['id' => 2]],
]);
id) to update existing vectors:
$store->add($newEmbedding, ['id' => 1, 'title' => 'Updated Title']);
$results = $store->search(
'laravel tutorial',
$userEmbedding,
semanticRatio: 0.6, // 60% vector, 40% keyword
limit: 5
);
$results = $store->search(
'laravel',
$userEmbedding,
filters: ['category' => 'tutorial', 'published' => true]
);
$relevantDocs = $store->search(
'Explain Laravel middleware',
$queryEmbedding,
semanticRatio: 0.8,
limit: 3
);
$prompt = "Answer the question based on these documents: " . json_encode($relevantDocs);
use Illuminate\Support\Facades\Cache;
$cacheKey = "hybrid_search:{$userQuery}:".implode(',', $userEmbedding);
$results = Cache::remember($cacheKey, now()->addHours(1), function () use ($store, $userQuery, $userEmbedding) {
return $store->search($userQuery, $userEmbedding, semanticRatio: 0.5);
});
// app/Providers/AppServiceProvider.php
public function register()
{
$this->app->singleton(MeilisearchStore::class, function ($app) {
$client = new Client(
env('MEILISEARCH_HOST'),
env('MEILISEARCH_API_KEY')
);
return new MeilisearchStore($client, env('MEILISEARCH_INDEX'));
});
}
// app/Repositories/VectorSearchRepository.php
class VectorSearchRepository
{
public function __construct(private MeilisearchStore $store) {}
public function findSimilarDocuments(array $embedding, int $limit = 5): array
{
return $this->store->search('', $embedding, limit: $limit);
}
public function findHybridResults(string $query, array $embedding, float $semanticRatio = 0.5): array
{
return $this->store->search($query, $embedding, semanticRatio: $semanticRatio);
}
}
// app/Console/Commands/ReindexVectors.php
class ReindexVectors extends Command
{
public function handle()
{
$store = app(MeilisearchStore::class);
$store->clear(); // Remove all vectors
$newVectors = $this->fetchVectorsFromDatabase();
$store->addMany($newVectors);
}
}
Meilisearch Index Mismatch:
MeilisearchStore uses the same index name for add() and search().http://localhost:7700) for active indices.Embedding Dimension Mismatch:
$expectedDim = 384;
if (count($embedding) !== $expectedDim) {
throw new \InvalidArgumentException("Embedding must have {$expectedDim} dimensions.");
}
Hybrid Search Semantic Ratio:
semanticRatio of 0 or 1 disables hybrid search (pure keyword or vector).0.5 and adjust based on relevance feedback.Rate Limiting:
$batchSize = 100;
foreach (array_chunk($vectors, $batchSize) as $batch) {
$store->addMany($batch);
sleep(1); // Throttle
}
Metadata Filtering:
filterableAttributes to be set in Meilisearch.$client->index('your_index')->updateSchema([
'filterableAttributes' => ['category', 'published'],
]);
Meilisearch Logs: Enable debug logs for Meilisearch:
MEILI_MASTER_KEY=masterKey
MEILI_LOG_LEVEL=debug
View logs at http://localhost:7700/logs.
Query Validation: Use Meilisearch’s API explorer to test queries:
curl -X GET "http://localhost:7700/indexes/your_index/search?q=laravel&semanticRatio=0.5"
Laravel Logging: Log raw queries for debugging:
$query = $store->search($queryText, $embedding, semanticRatio: 0.5);
\Log::debug('Meilisearch query', ['query' => $queryText, 'embedding' => $embedding]);
Custom Scoring:
Extend the search method to include Meilisearch’s rankingRules:
$results = $store->search(
$query,
$embedding,
rankingRules: [
'typoTolerance' => 'lenient',
'words' => ['title' => 5], // Boost title matches
]
);
Laravel Events: Trigger events for vector
How can I help you explore Laravel packages today?