Skip to content

Commit 19b05e3

Browse files
committed
feature #881 [Chat] Introduce SurrealDb as message store (Guikingone)
This PR was merged into the main branch. Discussion ---------- [Chat] Introduce `SurrealDb` as message store | Q | A | ------------- | --- | Bug fix? | no | New feature? | yes | Docs? | yes | Issues | #16 | License | MIT Commits ------- 21f40f3 feat(chat): SurrealDb message store
2 parents 4d3e544 + 21f40f3 commit 19b05e3

File tree

9 files changed

+600
-0
lines changed

9 files changed

+600
-0
lines changed

docs/components/chat.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ You can find more advanced usage in combination with an Agent using the store fo
3939
* `Long-term context with Meilisearch`_
4040
* `Long-term context with Pogocache`_
4141
* `Long-term context with Redis`_
42+
* `Long-term context with SurrealDb`_
4243

4344
Supported Message stores
4445
------------------------
@@ -49,6 +50,7 @@ Supported Message stores
4950
* `Meilisearch`_
5051
* `Pogocache`_
5152
* `Redis`_
53+
* `SurrealDb`_
5254

5355
Implementing a Bridge
5456
---------------------
@@ -130,9 +132,11 @@ store and ``bin/console ai:message-store:drop`` to clean up the message store:
130132
.. _`Long-term context with Meilisearch`: https://github.com/symfony/ai/blob/main/examples/chat/persistent-chat-meilisearch.php
131133
.. _`Long-term context with Pogocache`: https://github.com/symfony/ai/blob/main/examples/chat/persistent-chat-pogocache.php
132134
.. _`Long-term context with Redis`: https://github.com/symfony/ai/blob/main/examples/chat/persistent-chat-redis.php
135+
.. _`Long-term context with SurrealDb`: https://github.com/symfony/ai/blob/main/examples/chat/persistent-chat-surrealdb.php
133136
.. _`Cache`: https://symfony.com/doc/current/components/cache.html
134137
.. _`InMemory`: https://www.php.net/manual/en/language.types.array.php
135138
.. _`HttpFoundation session`: https://developers.cloudflare.com/vectorize/
136139
.. _`Meilisearch`: https://www.meilisearch.com/
137140
.. _`Pogocache`: https://pogocache.com/
138141
.. _`Redis`: https://redis.io/
142+
.. _`SurrealDb`: https://surrealdb.com/
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
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\SurrealDb\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+
19+
require_once dirname(__DIR__).'/bootstrap.php';
20+
21+
$platform = PlatformFactory::create(env('OPENAI_API_KEY'), http_client());
22+
23+
// SurrealDb does not require to call the `setup()` method as the table is created during insertion
24+
$store = new MessageStore(
25+
httpClient: http_client(),
26+
endpointUrl: 'http://127.0.0.1:8000',
27+
user: env('SURREALDB_USER'),
28+
password: env('SURREALDB_PASS'),
29+
namespace: 'default',
30+
database: 'chat',
31+
table: 'chat',
32+
);
33+
34+
$agent = new Agent($platform, 'gpt-4o-mini');
35+
$chat = new Chat($agent, $store);
36+
37+
$messages = new MessageBag(
38+
Message::forSystem('You are a helpful assistant. You only answer with short sentences.'),
39+
);
40+
41+
$chat->initiate($messages);
42+
$chat->submit(Message::ofUser('My name is Christopher.'));
43+
$message = $chat->submit(Message::ofUser('What is my name?'));
44+
45+
echo $message->getContent().\PHP_EOL;

examples/commands/message-stores.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
use Symfony\AI\Chat\Bridge\Meilisearch\MessageStore as MeilisearchMessageStore;
1818
use Symfony\AI\Chat\Bridge\Pogocache\MessageStore as PogocacheMessageStore;
1919
use Symfony\AI\Chat\Bridge\Redis\MessageStore as RedisMessageStore;
20+
use Symfony\AI\Chat\Bridge\SurrealDb\MessageStore as SurrealDbMessageStore;
2021
use Symfony\AI\Chat\Command\DropStoreCommand;
2122
use Symfony\AI\Chat\Command\SetupStoreCommand;
2223
use Symfony\AI\Chat\MessageNormalizer;
@@ -68,6 +69,15 @@
6869

6970
return new SessionStore($requestStack, 'symfony');
7071
},
72+
'surrealdb' => static fn (): SurrealDbMessageStore => new SurrealDbMessageStore(
73+
httpClient: http_client(),
74+
endpointUrl: env('SURREALDB_HOST'),
75+
user: env('SURREALDB_USER'),
76+
password: env('SURREALDB_PASS'),
77+
namespace: 'default',
78+
database: 'chat',
79+
table: 'chat',
80+
),
7181
];
7282

7383
$storesIds = array_keys($factories);

src/ai-bundle/config/options.php

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -864,6 +864,22 @@
864864
->end()
865865
->end()
866866
->end()
867+
->arrayNode('surreal_db')
868+
->useAttributeAsKey('name')
869+
->arrayPrototype()
870+
->children()
871+
->stringNode('endpoint')->cannotBeEmpty()->end()
872+
->stringNode('username')->cannotBeEmpty()->end()
873+
->stringNode('password')->cannotBeEmpty()->end()
874+
->stringNode('namespace')->cannotBeEmpty()->end()
875+
->stringNode('database')->cannotBeEmpty()->end()
876+
->stringNode('table')->end()
877+
->booleanNode('namespaced_user')
878+
->info('Using a namespaced user is a good practice to prevent any undesired access to a specific table, see https://surrealdb.com/docs/surrealdb/reference-guide/security-best-practices')
879+
->end()
880+
->end()
881+
->end()
882+
->end()
867883
->end()
868884
->end()
869885
->arrayNode('chat')

src/ai-bundle/src/AiBundle.php

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
use Symfony\AI\Chat\Bridge\Meilisearch\MessageStore as MeilisearchMessageStore;
4242
use Symfony\AI\Chat\Bridge\Pogocache\MessageStore as PogocacheMessageStore;
4343
use Symfony\AI\Chat\Bridge\Redis\MessageStore as RedisMessageStore;
44+
use Symfony\AI\Chat\Bridge\SurrealDb\MessageStore as SurrealDbMessageStore;
4445
use Symfony\AI\Chat\Chat;
4546
use Symfony\AI\Chat\ChatInterface;
4647
use Symfony\AI\Chat\MessageStoreInterface;
@@ -1623,6 +1624,36 @@ private function processMessageStoreConfig(string $type, array $messageStores, C
16231624
$container->registerAliasForArgument('ai.message_store.'.$type.'.'.$name, MessageStoreInterface::class, $type.'_'.$name);
16241625
}
16251626
}
1627+
1628+
if ('surreal_db' === $type) {
1629+
foreach ($messageStores as $name => $messageStore) {
1630+
$arguments = [
1631+
new Reference('http_client'),
1632+
$messageStore['endpoint'],
1633+
$messageStore['username'],
1634+
$messageStore['password'],
1635+
$messageStore['namespace'],
1636+
$messageStore['database'],
1637+
new Reference('serializer'),
1638+
$messageStore['table'] ?? $name,
1639+
];
1640+
1641+
if (\array_key_exists('namespaced_user', $messageStore)) {
1642+
$arguments[8] = $messageStore['namespaced_user'];
1643+
}
1644+
1645+
$definition = new Definition(SurrealDbMessageStore::class);
1646+
$definition
1647+
->setLazy(true)
1648+
->addTag('proxy', ['interface' => MessageStoreInterface::class])
1649+
->addTag('ai.message_store')
1650+
->setArguments($arguments);
1651+
1652+
$container->setDefinition('ai.message_store.'.$type.'.'.$name, $definition);
1653+
$container->registerAliasForArgument('ai.message_store.'.$type.'.'.$name, StoreInterface::class, $name);
1654+
$container->registerAliasForArgument('ai.message_store.'.$type.'.'.$name, StoreInterface::class, $type.'_'.$name);
1655+
}
1656+
}
16261657
}
16271658

16281659
/**

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

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3104,6 +3104,123 @@ public function testSessionMessageStoreIsConfigured()
31043104
$this->assertTrue($sessionMessageStoreDefinition->hasTag('ai.message_store'));
31053105
}
31063106

3107+
public function testSurrealDbMessageStoreIsConfiguredWithoutCustomTable()
3108+
{
3109+
$container = $this->buildContainer([
3110+
'ai' => [
3111+
'message_store' => [
3112+
'surreal_db' => [
3113+
'custom' => [
3114+
'endpoint' => 'http://127.0.0.1:8000',
3115+
'username' => 'test',
3116+
'password' => 'test',
3117+
'namespace' => 'foo',
3118+
'database' => 'bar',
3119+
],
3120+
],
3121+
],
3122+
],
3123+
]);
3124+
3125+
$surrealDbMessageStoreDefinition = $container->getDefinition('ai.message_store.surreal_db.custom');
3126+
3127+
$this->assertTrue($surrealDbMessageStoreDefinition->isLazy());
3128+
$this->assertCount(8, $surrealDbMessageStoreDefinition->getArguments());
3129+
$this->assertInstanceOf(Reference::class, $surrealDbMessageStoreDefinition->getArgument(0));
3130+
$this->assertSame('http_client', (string) $surrealDbMessageStoreDefinition->getArgument(0));
3131+
$this->assertSame('http://127.0.0.1:8000', (string) $surrealDbMessageStoreDefinition->getArgument(1));
3132+
$this->assertSame('test', (string) $surrealDbMessageStoreDefinition->getArgument(2));
3133+
$this->assertSame('test', (string) $surrealDbMessageStoreDefinition->getArgument(3));
3134+
$this->assertSame('foo', (string) $surrealDbMessageStoreDefinition->getArgument(4));
3135+
$this->assertSame('bar', (string) $surrealDbMessageStoreDefinition->getArgument(5));
3136+
$this->assertInstanceOf(Reference::class, $surrealDbMessageStoreDefinition->getArgument(6));
3137+
$this->assertSame('serializer', (string) $surrealDbMessageStoreDefinition->getArgument(6));
3138+
$this->assertSame('custom', (string) $surrealDbMessageStoreDefinition->getArgument(7));
3139+
3140+
$this->assertTrue($surrealDbMessageStoreDefinition->hasTag('proxy'));
3141+
$this->assertSame([['interface' => MessageStoreInterface::class]], $surrealDbMessageStoreDefinition->getTag('proxy'));
3142+
$this->assertTrue($surrealDbMessageStoreDefinition->hasTag('ai.message_store'));
3143+
}
3144+
3145+
public function testSurrealDbMessageStoreIsConfiguredWithCustomTable()
3146+
{
3147+
$container = $this->buildContainer([
3148+
'ai' => [
3149+
'message_store' => [
3150+
'surreal_db' => [
3151+
'custom' => [
3152+
'endpoint' => 'http://127.0.0.1:8000',
3153+
'username' => 'test',
3154+
'password' => 'test',
3155+
'namespace' => 'foo',
3156+
'database' => 'bar',
3157+
'table' => 'random',
3158+
],
3159+
],
3160+
],
3161+
],
3162+
]);
3163+
3164+
$surrealDbMessageStoreDefinition = $container->getDefinition('ai.message_store.surreal_db.custom');
3165+
3166+
$this->assertTrue($surrealDbMessageStoreDefinition->isLazy());
3167+
$this->assertCount(8, $surrealDbMessageStoreDefinition->getArguments());
3168+
$this->assertInstanceOf(Reference::class, $surrealDbMessageStoreDefinition->getArgument(0));
3169+
$this->assertSame('http_client', (string) $surrealDbMessageStoreDefinition->getArgument(0));
3170+
$this->assertSame('http://127.0.0.1:8000', (string) $surrealDbMessageStoreDefinition->getArgument(1));
3171+
$this->assertSame('test', (string) $surrealDbMessageStoreDefinition->getArgument(2));
3172+
$this->assertSame('test', (string) $surrealDbMessageStoreDefinition->getArgument(3));
3173+
$this->assertSame('foo', (string) $surrealDbMessageStoreDefinition->getArgument(4));
3174+
$this->assertSame('bar', (string) $surrealDbMessageStoreDefinition->getArgument(5));
3175+
$this->assertInstanceOf(Reference::class, $surrealDbMessageStoreDefinition->getArgument(6));
3176+
$this->assertSame('serializer', (string) $surrealDbMessageStoreDefinition->getArgument(6));
3177+
$this->assertSame('random', (string) $surrealDbMessageStoreDefinition->getArgument(7));
3178+
3179+
$this->assertTrue($surrealDbMessageStoreDefinition->hasTag('proxy'));
3180+
$this->assertSame([['interface' => MessageStoreInterface::class]], $surrealDbMessageStoreDefinition->getTag('proxy'));
3181+
$this->assertTrue($surrealDbMessageStoreDefinition->hasTag('ai.message_store'));
3182+
}
3183+
3184+
public function testSurrealDbMessageStoreIsConfiguredWithNamespacedUser()
3185+
{
3186+
$container = $this->buildContainer([
3187+
'ai' => [
3188+
'message_store' => [
3189+
'surreal_db' => [
3190+
'custom' => [
3191+
'endpoint' => 'http://127.0.0.1:8000',
3192+
'username' => 'test',
3193+
'password' => 'test',
3194+
'namespace' => 'foo',
3195+
'database' => 'bar',
3196+
'namespaced_user' => true,
3197+
],
3198+
],
3199+
],
3200+
],
3201+
]);
3202+
3203+
$surrealDbMessageStoreDefinition = $container->getDefinition('ai.message_store.surreal_db.custom');
3204+
3205+
$this->assertTrue($surrealDbMessageStoreDefinition->isLazy());
3206+
$this->assertCount(9, $surrealDbMessageStoreDefinition->getArguments());
3207+
$this->assertInstanceOf(Reference::class, $surrealDbMessageStoreDefinition->getArgument(0));
3208+
$this->assertSame('http_client', (string) $surrealDbMessageStoreDefinition->getArgument(0));
3209+
$this->assertSame('http://127.0.0.1:8000', (string) $surrealDbMessageStoreDefinition->getArgument(1));
3210+
$this->assertSame('test', (string) $surrealDbMessageStoreDefinition->getArgument(2));
3211+
$this->assertSame('test', (string) $surrealDbMessageStoreDefinition->getArgument(3));
3212+
$this->assertSame('foo', (string) $surrealDbMessageStoreDefinition->getArgument(4));
3213+
$this->assertSame('bar', (string) $surrealDbMessageStoreDefinition->getArgument(5));
3214+
$this->assertInstanceOf(Reference::class, $surrealDbMessageStoreDefinition->getArgument(6));
3215+
$this->assertSame('serializer', (string) $surrealDbMessageStoreDefinition->getArgument(6));
3216+
$this->assertSame('custom', (string) $surrealDbMessageStoreDefinition->getArgument(7));
3217+
$this->assertTrue($surrealDbMessageStoreDefinition->getArgument(8));
3218+
3219+
$this->assertTrue($surrealDbMessageStoreDefinition->hasTag('proxy'));
3220+
$this->assertSame([['interface' => MessageStoreInterface::class]], $surrealDbMessageStoreDefinition->getTag('proxy'));
3221+
$this->assertTrue($surrealDbMessageStoreDefinition->hasTag('ai.message_store'));
3222+
}
3223+
31073224
private function buildContainer(array $configuration): ContainerBuilder
31083225
{
31093226
$container = new ContainerBuilder();
@@ -3473,6 +3590,25 @@ private function getFullConfig(): array
34733590
'identifier' => 'session',
34743591
],
34753592
],
3593+
'surreal_db' => [
3594+
'my_surreal_db_message_store' => [
3595+
'endpoint' => 'http://127.0.0.1:8000',
3596+
'username' => 'test',
3597+
'password' => 'test',
3598+
'namespace' => 'foo',
3599+
'database' => 'bar',
3600+
'namespaced_user' => true,
3601+
],
3602+
'my_surreal_db_message_store_with_custom_table' => [
3603+
'endpoint' => 'http://127.0.0.1:8000',
3604+
'username' => 'test',
3605+
'password' => 'test',
3606+
'namespace' => 'foo',
3607+
'database' => 'bar',
3608+
'table' => 'bar',
3609+
'namespaced_user' => true,
3610+
],
3611+
],
34763612
],
34773613
'chat' => [
34783614
'main' => [

src/chat/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,4 @@ CHANGELOG
99
- Meilisearch
1010
- Pogocache
1111
- Redis
12+
- SurrealDb

0 commit comments

Comments
 (0)