symfony/ai-meilisearch-message-store
Meilisearch Message Store integrates Meilisearch as a persistent message store for Symfony AI Chat. Index and query chat messages using Meilisearch APIs, with support for async task handling and configurable index settings via Meilisearch.
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?