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-meilisearch-store meilisearch/meilisearch-php
// config/meilisearch.php
return [
'host' => env('MEILISEARCH_HOST', 'http://localhost:7700'),
'api_key' => env('MEILISEARCH_API_KEY', null),
'index_name' => 'your_vector_index',
];
// app/Providers/MeilisearchServiceProvider.php
use Symfony\Component\Ai\Store\MeilisearchStore;
use Meilisearch\Client;
public function register()
{
$this->app->singleton(MeilisearchStore::class, function ($app) {
$client = new Client(config('meilisearch.host'), config('meilisearch.api_key'));
return new MeilisearchStore($client, config('meilisearch.index_name'));
});
}
// Add embeddings
$store = app(MeilisearchStore::class);
$store->add([0.1, 0.2, ...], ['id' => 1, 'content' => 'Sample text']);
// Hybrid search
$results = $store->search('query', [0.1, 0.2, ...], ['semanticRatio' => 0.5]);
src/MeilisearchStore.php: Core implementation.tests/: Usage examples and edge cases.// Batch add embeddings (e.g., from a model)
$embeddings = Model::chunk(100, function ($chunk) use ($store) {
$documents = $chunk->map(fn ($model) => [
'embedding' => $model->embedding->toArray(),
'metadata' => $model->toArray(),
]);
$store->add($documents);
});
// Combine keyword and vector search
$results = $store->search(
query: 'user manual',
vector: $queryEmbedding,
options: [
'semanticRatio' => 0.7, // 70% vector, 30% keyword
'filter' => ['category' => 'docs'],
]
);
// Filter by metadata (e.g., user ID)
$results = $store->search(
query: null, // Pure vector search
vector: $embedding,
options: ['filter' => ['user_id' => auth()->id()]]
);
// Remove by ID or vector
$store->remove(['id' => 123]); // By metadata
// OR
$store->remove([], ['embedding' => $vector]); // By vector (advanced)
// app/Services/AIService.php
class AIService {
public function __construct(
private MeilisearchStore $store,
private EmbeddingService $embeddingService
) {}
public function semanticSearch(string $query, int $userId): array
{
$embedding = $this->embeddingService->create($query);
return $this->store->search(
query: $query,
vector: $embedding,
options: ['filter' => ['user_id' => $userId]]
);
}
}
// app/Jobs/IndexEmbeddings.php
class IndexEmbeddings implements ShouldQueue {
public function handle() {
$store = app(MeilisearchStore::class);
$store->add($this->embeddings, $this->metadata);
}
}
// Configure index via Meilisearch PHP SDK
$client->index('your_index')->updateSettings([
'filterableAttributes' => ['user_id', 'category'],
'rankingRules' => ['semanticScore', 'typo', 'words'],
]);
// Cache frequent queries (e.g., Redis)
$cacheKey = "meilisearch:{$query}:".implode(',', $vector);
return Cache::remember($cacheKey, now()->addMinutes(5), function () use ($store, $query, $vector) {
return $store->search($query, $vector);
});
// PHPUnit test example
public function testHybridSearch()
{
$store = new MeilisearchStore($this->client, 'test_index');
$store->add([0.1, 0.2], ['id' => 1]);
$results = $store->search('test', [0.1, 0.2], ['semanticRatio' => 1]);
$this->assertCount(1, $results);
}
Vector Dimension Mismatch:
Dimension mismatch when adding vectors.text-embedding-ada-002). Validate during insertion:
$store->add($embedding, $metadata, ['validate' => true]);
Meilisearch API Rate Limits:
429 Too Many Requests.use Symfony\Component\Ai\Store\MeilisearchStore;
use GuzzleHttp\Exception\RequestException;
try {
$results = $store->search(...);
} catch (RequestException $e) {
if ($e->getCode() === 429) {
sleep(2); // Retry after delay
return $store->search(...);
}
throw $e;
}
Hybrid Search SemanticRatio:
semanticRatio > 0.5 may ignore keyword relevance.0.3 (30% vector, 70% keyword) and tune based on A/B tests.Filter Syntax:
Invalid filter (e.g., user_id:1 vs. user_id=1).$options = ['filter' => ['user_id = 1', 'category IN (docs, guides)']];
Laravel Container Conflicts:
Class not found if Symfony’s StoreInterface isn’t bound.$this->app->bind(\Symfony\Component\Ai\Store\StoreInterface::class, MeilisearchStore::class);
$client = new Client(config('meilisearch.host'), config('meilisearch.api_key'), [
'timeout' => 10,
'debug' => true, // Logs raw API requests
]);
curl -X GET http://localhost:7700/indexes/your_index/docs | jq
\Log::debug('Meilisearch query', [
'query' => $query,
'vector' => $vector,
'options' => $options,
'results' => $results,
]);
Custom Query Builder:
// Extend MeilisearchStore to add Laravel-specific methods
class LaravelMeilisearchStore extends MeilisearchStore {
public function searchByUserId(string $query, array $vector, int $userId): array
{
return $this->search($query, $vector, [
'filter' => ['user_id = ' . $userId],
]);
}
}
Event Dispatching:
// Dispatch events for vector operations
event(new VectorAdded($embedding, $metadata));
Fallback to Keyword Search:
public function fallbackSearch(string $query): array
{
return $this->search($query
How can I help you explore Laravel packages today?