Skip to content

Commit bf12d14

Browse files
committed
feature #239 [Chat] Meilisearch message store (Guikingone)
This PR was merged into the main branch. Discussion ---------- [Chat] Meilisearch message store | Q | A | ------------- | --- | Bug fix? | no | New feature? | yes | Docs? | no | Issues | None | License | MIT Hi 👋🏻 Here's a small POC to store message bags outside of memory (for long term agents), the implementation is built around MS but the main interfaces are defined (Redis, Doctrine, etc, any bridge can use it) for any bridge (and heavily inspired by the `Store` package). Let me know if the idea is interesting, if not, feel free to close the PR 🙂 Commits ------- bbf483c feat(platform): meilisearch message bag
2 parents e1b2459 + bbf483c commit bf12d14

File tree

14 files changed

+682
-0
lines changed

14 files changed

+682
-0
lines changed

docs/components/chat.rst

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,20 @@ with a ``Symfony\AI\Agent\AgentInterface`` and a ``Symfony\AI\Chat\MessageStoreI
3131

3232
$chat->submit(Message::ofUser('Hello'));
3333

34+
You can find more advanced usage in combination with an Agent using the store for long-term context:
35+
36+
* `External services storage with Cache`_
37+
* `Current session context storage with HttpFoundation session`_
38+
* `Current process context storage with InMemory`_
39+
* `Long-term context with Meilisearch`_
40+
41+
Supported Message stores
42+
------------------------
43+
44+
* `Cache`_
45+
* `HttpFoundation session`_
46+
* `InMemory`_
47+
* `Meilisearch`_
3448

3549
Implementing a Bridge
3650
---------------------
@@ -105,3 +119,12 @@ store and ``bin/console ai:message-store:drop`` to clean up the message store:
105119
106120
$ php bin/console ai:message-store:setup symfonycon
107121
$ php bin/console ai:message-store:drop symfonycon
122+
123+
.. _`External services storage with Cache`: https://github.com/symfony/ai/blob/main/examples/chat/persistent-chat-cache.php
124+
.. _`Current session context storage with HttpFoundation session`: https://github.com/symfony/ai/blob/main/examples/chat/persistent-chat-session.php
125+
.. _`Current process context storage with InMemory`: https://github.com/symfony/ai/blob/main/examples/chat/persistent-chat.php
126+
.. _`Long-term context with Meilisearch`: https://github.com/symfony/ai/blob/main/examples/chat/persistent-chat-meilisearch.php
127+
.. _`Cache`: https://symfony.com/doc/current/components/cache.html
128+
.. _`InMemory`: https://www.php.net/manual/en/language.types.array.php
129+
.. _`HttpFoundation session`: https://developers.cloudflare.com/vectorize/
130+
.. _`Meilisearch`: https://www.meilisearch.com/
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
use Symfony\AI\Agent\Agent;
13+
use Symfony\AI\Chat\Bridge\Meilisearch\MessageStore;
14+
use Symfony\AI\Chat\Chat;
15+
use Symfony\AI\Platform\Bridge\OpenAi\PlatformFactory;
16+
use Symfony\AI\Platform\Message\Message;
17+
use Symfony\AI\Platform\Message\MessageBag;
18+
use Symfony\Component\Clock\MonotonicClock;
19+
20+
require_once dirname(__DIR__).'/bootstrap.php';
21+
22+
$platform = PlatformFactory::create(env('OPENAI_API_KEY'), http_client());
23+
24+
$store = new MessageStore(http_client(), env('MEILISEARCH_HOST'), env('MEILISEARCH_API_KEY'), new MonotonicClock());
25+
$store->setup();
26+
27+
$agent = new Agent($platform, 'gpt-4o-mini');
28+
$chat = new Chat($agent, $store);
29+
30+
$messages = new MessageBag(
31+
Message::forSystem('You are a helpful assistant. You only answer with short sentences.'),
32+
);
33+
34+
$chat->initiate($messages);
35+
$chat->submit(Message::ofUser('My name is Christopher.'));
36+
$message = $chat->submit(Message::ofUser('What is my name?'));
37+
38+
echo $message->getContent().\PHP_EOL;

examples/commands/message-stores.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,11 @@
1414
use Symfony\AI\Chat\Bridge\HttpFoundation\SessionStore;
1515
use Symfony\AI\Chat\Bridge\Local\CacheStore;
1616
use Symfony\AI\Chat\Bridge\Local\InMemoryStore;
17+
use Symfony\AI\Chat\Bridge\Meilisearch\MessageStore as MeilisearchMessageStore;
1718
use Symfony\AI\Chat\Command\DropStoreCommand;
1819
use Symfony\AI\Chat\Command\SetupStoreCommand;
1920
use Symfony\Component\Cache\Adapter\ArrayAdapter;
21+
use Symfony\Component\Clock\MonotonicClock;
2022
use Symfony\Component\Console\Application;
2123
use Symfony\Component\Console\Input\ArrayInput;
2224
use Symfony\Component\Console\Output\ConsoleOutput;
@@ -28,6 +30,13 @@
2830

2931
$factories = [
3032
'cache' => static fn (): CacheStore => new CacheStore(new ArrayAdapter(), cacheKey: 'symfony'),
33+
'meilisearch' => static fn (): MeilisearchMessageStore => new MeilisearchMessageStore(
34+
http_client(),
35+
env('MEILISEARCH_HOST'),
36+
env('MEILISEARCH_API_KEY'),
37+
new MonotonicClock(),
38+
'symfony',
39+
),
3140
'memory' => static fn (): InMemoryStore => new InMemoryStore('symfony'),
3241
'session' => static function (): SessionStore {
3342
$request = Request::create('/');

examples/composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
"symfony/ai-platform": "@dev",
2222
"symfony/ai-store": "@dev",
2323
"symfony/cache": "^7.3|^8.0",
24+
"symfony/clock": "^7.3|^8.0",
2425
"symfony/console": "^7.3|^8.0",
2526
"symfony/css-selector": "^7.3|^8.0",
2627
"symfony/dependency-injection": "^7.3|^8.0",

src/ai-bundle/composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
"phpstan/phpstan": "^2.1",
3131
"phpstan/phpstan-strict-rules": "^2.0",
3232
"phpunit/phpunit": "^11.5",
33+
"symfony/clock": "^7.3|^8.0",
3334
"symfony/expression-language": "^7.3|^8.0",
3435
"symfony/security-core": "^7.3|^8.0",
3536
"symfony/translation": "^7.3|^8.0"

src/ai-bundle/config/options.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -745,6 +745,16 @@
745745
->end()
746746
->end()
747747
->end()
748+
->arrayNode('meilisearch')
749+
->useAttributeAsKey('name')
750+
->arrayPrototype()
751+
->children()
752+
->stringNode('endpoint')->cannotBeEmpty()->end()
753+
->stringNode('api_key')->cannotBeEmpty()->end()
754+
->stringNode('index_name')->cannotBeEmpty()->end()
755+
->end()
756+
->end()
757+
->end()
748758
->arrayNode('session')
749759
->useAttributeAsKey('name')
750760
->arrayPrototype()

src/ai-bundle/src/AiBundle.php

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
use Symfony\AI\AiBundle\Profiler\TraceableToolbox;
3636
use Symfony\AI\AiBundle\Security\Attribute\IsGrantedTool;
3737
use Symfony\AI\Chat\Bridge\HttpFoundation\SessionStore;
38+
use Symfony\AI\Chat\Bridge\Meilisearch\MessageStore as MeilisearchMessageStore;
3839
use Symfony\AI\Chat\MessageStoreInterface;
3940
use Symfony\AI\Platform\Bridge\Anthropic\PlatformFactory as AnthropicPlatformFactory;
4041
use Symfony\AI\Platform\Bridge\Azure\OpenAi\PlatformFactory as AzureOpenAiPlatformFactory;
@@ -82,6 +83,7 @@
8283
use Symfony\AI\Store\Indexer;
8384
use Symfony\AI\Store\IndexerInterface;
8485
use Symfony\AI\Store\StoreInterface;
86+
use Symfony\Component\Clock\ClockInterface;
8587
use Symfony\Component\Config\Definition\Configurator\DefinitionConfigurator;
8688
use Symfony\Component\DependencyInjection\Attribute\Target;
8789
use Symfony\Component\DependencyInjection\ChildDefinition;
@@ -1331,6 +1333,24 @@ private function processMessageStoreConfig(string $type, array $messageStores, C
13311333
}
13321334
}
13331335

1336+
if ('meilisearch' === $type) {
1337+
foreach ($messageStores as $name => $messageStore) {
1338+
$definition = new Definition(MeilisearchMessageStore::class);
1339+
$definition
1340+
->setArguments([
1341+
$messageStore['endpoint'],
1342+
$messageStore['api_key'],
1343+
new Reference(ClockInterface::class),
1344+
$messageStore['index_name'],
1345+
])
1346+
->addTag('ai.message_store');
1347+
1348+
$container->setDefinition('ai.message_store.'.$type.'.'.$name, $definition);
1349+
$container->registerAliasForArgument('ai.message_store.'.$type.'.'.$name, MessageStoreInterface::class, $name);
1350+
$container->registerAliasForArgument('ai.message_store.'.$type.'.'.$name, MessageStoreInterface::class, $type.'_'.$name);
1351+
}
1352+
}
1353+
13341354
if ('session' === $type) {
13351355
foreach ($messageStores as $name => $messageStore) {
13361356
$definition = new Definition(SessionStore::class);

src/ai-bundle/tests/DependencyInjection/AiBundleTest.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3035,6 +3035,13 @@ private function getFullConfig(): array
30353035
'identifier' => '_memory',
30363036
],
30373037
],
3038+
'meilisearch' => [
3039+
'my_meilisearch_store' => [
3040+
'endpoint' => 'http://127.0.0.1:7700',
3041+
'api_key' => 'foo',
3042+
'index_name' => 'test',
3043+
],
3044+
],
30383045
'session' => [
30393046
'my_session_message_store' => [
30403047
'identifier' => 'session',

src/chat/composer.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,14 @@
2929
"phpstan/phpstan-strict-rules": "^2.0",
3030
"phpunit/phpunit": "^11.5.13",
3131
"symfony/dependency-injection": "^7.3|^8.0",
32+
"symfony/clock": "^7.3|^8.0",
3233
"symfony/console": "^7.3|^8.0",
3334
"symfony/http-foundation": "^7.3|^8.0",
3435
"psr/cache": "^3.0"
3536
},
37+
"suggest": {
38+
"symfony/clock": "To use stores that requires waiting for actions like Meilisearch"
39+
},
3640
"autoload": {
3741
"psr-4": {
3842
"Symfony\\AI\\Chat\\": "src/"

0 commit comments

Comments
 (0)