symfony/ai-meilisearch-message-store
Meilisearch message store integration for Symfony AI Chat, using Meilisearch indexes and documents to persist and search conversation messages with async task support.
Install the Package
composer require symfony/ai-meilisearch-message-store meilisearch/meilisearch-php
For Laravel compatibility, ensure symfony/http-client is replaced with Laravel’s Http facade or Guzzle via a custom adapter (see Implementation Patterns).
Configure Meilisearch
Add to .env:
MEILISEARCH_HOST=http://localhost:7700
MEILISEARCH_API_KEY=your-api-key
MEILISEARCH_INDEX=chat_messages # Default index name
Register the Message Store
In AppServiceProvider or a dedicated service provider:
use Symfony\Component\Ai\Chat\MessageStore\MeilisearchMessageStore;
use Meilisearch\Client;
public function register()
{
$this->app->singleton(\Symfony\Component\Ai\Chat\MessageStore\MessageStoreInterface::class, function ($app) {
$client = new Client(
env('MEILISEARCH_HOST'),
env('MEILISEARCH_API_KEY')
);
return new MeilisearchMessageStore($client, env('MEILISEARCH_INDEX'));
});
}
First Use Case: Store and Retrieve Messages
use Symfony\Component\Ai\Chat\Message;
use Symfony\Component\Ai\Chat\MessageStore\MessageStoreInterface;
// Store a message
$message = new Message(
'conv-123',
'user-456',
'What is Laravel?',
'user',
'Laravel is a PHP framework...',
['timestamp' => now()->toDateTimeString()]
);
$store = app(MessageStoreInterface::class);
$store->save($message);
// Retrieve messages
$messages = $store->findByConversationId('conv-123');
Verify with Tinker
php artisan tinker
>>> $store = app(\Symfony\Component\Ai\Chat\MessageStore\MessageStoreInterface::class);
>>> $messages = $store->findByConversationId('conv-123');
>>> dd($messages);
Since the package is Symfony-focused, create a Laravel-compatible adapter:
// app/Providers/MeilisearchServiceProvider.php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use Meilisearch\Client;
use Symfony\Component\Ai\Chat\MessageStore\MeilisearchMessageStore;
class MeilisearchServiceProvider extends ServiceProvider
{
public function register()
{
$this->app->singleton(\Symfony\Component\Ai\Chat\MessageStore\MessageStoreInterface::class, function ($app) {
$client = new Client(
config('meilisearch.host'),
config('meilisearch.api_key')
);
return new MeilisearchMessageStore($client, config('meilisearch.index'));
});
}
}
Configure in config/meilisearch.php:
return [
'host' => env('MEILISEARCH_HOST', 'http://localhost:7700'),
'api_key' => env('MEILISEARCH_API_KEY'),
'index' => env('MEILISEARCH_INDEX', 'chat_messages'),
];
Customize how messages are serialized/deserialized for Meilisearch:
// app/Services/MeilisearchMessageMapper.php
namespace App\Services;
use Symfony\Component\Ai\Chat\Message;
use Symfony\Component\Ai\Chat\MessageStore\MessageStoreInterface;
class MeilisearchMessageMapper
{
public function toArray(Message $message): array
{
return [
'id' => $message->getId(),
'conversationId' => $message->getConversationId(),
'role' => $message->getRole(),
'content' => $message->getContent(),
'createdAt' => $message->getCreatedAt()?->format('Y-m-d\TH:i:sP'),
'metadata' => $message->getMetadata() ?? [],
];
}
public function toMessage(array $data): Message
{
return new Message(
$data['conversationId'],
$data['role'] === 'user' ? 'user' : 'assistant',
$data['content'],
$data['role'],
null,
$data['metadata'] ?? []
);
}
}
Inject the mapper into MeilisearchMessageStore via constructor or setter.
Handle Meilisearch’s async tasks (e.g., index updates) with Laravel queues:
// app/Services/MeilisearchAsyncHandler.php
namespace App\Services;
use Illuminate\Support\Facades\Queue;
use Meilisearch\Client;
use Meilisearch\Tasks\Task;
class MeilisearchAsyncHandler
{
public function handleTask(Client $client, string $taskUid): void
{
Queue::dispatch(function () use ($client, $taskUid) {
$task = $client->waitForTask($taskUid);
if (!$task->getStatusData()['completed_at']) {
$this->handleTask($client, $taskUid); // Retry
}
});
}
}
Use in MeilisearchMessageStore:
public function save(Message $message)
{
$data = $this->mapper->toArray($message);
$task = $this->client->index($this->index)->addDocuments([$data]);
$this->asyncHandler->handleTask($this->client, $task->getTaskUid());
}
Customize search queries for Laravel use cases:
// app/Services/MeilisearchSearcher.php
namespace App\Services;
use Symfony\Component\Ai\Chat\MessageStore\MeilisearchMessageStore;
class MeilisearchSearcher
{
public function searchByQuery(MeilisearchMessageStore $store, string $query, int $limit = 10)
{
return $store->client->index($store->index)->search($query, [
'limit' => $limit,
'attributesToRetrieve' => ['content', 'role', 'createdAt'],
'filter' => ['role = user'], // Example: Only search user messages
]);
}
}
Isolate message stores per tenant:
// app/Services/TenantAwareMeilisearchStore.php
namespace App\Services;
use Illuminate\Support\Facades\Auth;
use Symfony\Component\Ai\Chat\MessageStore\MeilisearchMessageStore;
class TenantAwareMeilisearchStore extends MeilisearchMessageStore
{
public function __construct(MeilisearchMessageStore $store)
{
$tenantId = Auth::guard('tenant')->id();
parent::__construct($store->client, "tenant_{$tenantId}_{$store->index}");
}
}
Register in AppServiceProvider:
$this->app->singleton(MessageStoreInterface::class, function ($app) {
return new TenantAwareMeilisearchStore(
$app->make(MeilisearchMessageStore::class)
);
});
Combine Meilisearch’s keyword search with vector similarity:
// app/Services/HybridSearcher.php
namespace App\Services;
use Symfony\Component\Ai\Chat\MessageStore\MeilisearchMessageStore;
class HybridSearcher
{
public function search(MeilisearchMessageStore $store, string $query, array $vector)
{
// Keyword search
$keywordResults = $store->client->index($store->index)->search($query, [
'limit' => 5,
]);
// Vector search (requires Meilisearch with vector module)
$vectorResults = $store->client->index($store->index)->search($vector, [
'limit' => 5,
'using' => 'vector', // Requires Meilisearch 1.0+
]);
// Merge results (custom logic)
return array_merge($keywordResults['hits'], $vectorResults['hits']);
}
}
Async Task Timeouts
laravel-queue-retries package).Index Schema Mismatches
chat_messages_v1, `chat_messages_v2How can I help you explore Laravel packages today?