Skip to content

Commit e96dc8e

Browse files
committed
feat(chat): SurrealDb message store
1 parent f9da9c0 commit e96dc8e

File tree

8 files changed

+596
-0
lines changed

8 files changed

+596
-0
lines changed
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
@@ -847,6 +847,22 @@
847847
->end()
848848
->end()
849849
->end()
850+
->arrayNode('surreal_db')
851+
->useAttributeAsKey('name')
852+
->arrayPrototype()
853+
->children()
854+
->stringNode('endpoint')->cannotBeEmpty()->end()
855+
->stringNode('username')->cannotBeEmpty()->end()
856+
->stringNode('password')->cannotBeEmpty()->end()
857+
->stringNode('namespace')->cannotBeEmpty()->end()
858+
->stringNode('database')->cannotBeEmpty()->end()
859+
->stringNode('table')->end()
860+
->booleanNode('namespaced_user')
861+
->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')
862+
->end()
863+
->end()
864+
->end()
865+
->end()
850866
->end()
851867
->end()
852868
->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;
@@ -1596,6 +1597,36 @@ private function processMessageStoreConfig(string $type, array $messageStores, C
15961597
$container->registerAliasForArgument('ai.message_store.'.$type.'.'.$name, MessageStoreInterface::class, $type.'_'.$name);
15971598
}
15981599
}
1600+
1601+
if ('surreal_db' === $type) {
1602+
foreach ($messageStores as $name => $messageStore) {
1603+
$arguments = [
1604+
new Reference('http_client'),
1605+
$messageStore['endpoint'],
1606+
$messageStore['username'],
1607+
$messageStore['password'],
1608+
$messageStore['namespace'],
1609+
$messageStore['database'],
1610+
new Reference('serializer'),
1611+
$messageStore['table'] ?? $name,
1612+
];
1613+
1614+
if (\array_key_exists('namespaced_user', $messageStore)) {
1615+
$arguments[8] = $messageStore['namespaced_user'];
1616+
}
1617+
1618+
$definition = new Definition(SurrealDbMessageStore::class);
1619+
$definition
1620+
->setLazy(true)
1621+
->addTag('proxy', ['interface' => MessageStoreInterface::class])
1622+
->addTag('ai.message_store')
1623+
->setArguments($arguments);
1624+
1625+
$container->setDefinition('ai.message_store.'.$type.'.'.$name, $definition);
1626+
$container->registerAliasForArgument('ai.message_store.'.$type.'.'.$name, StoreInterface::class, $name);
1627+
$container->registerAliasForArgument('ai.message_store.'.$type.'.'.$name, StoreInterface::class, $type.'_'.$name);
1628+
}
1629+
}
15991630
}
16001631

16011632
/**

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

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

3083+
public function testSurrealDbMessageStoreIsConfiguredWithoutCustomTable()
3084+
{
3085+
$container = $this->buildContainer([
3086+
'ai' => [
3087+
'message_store' => [
3088+
'surreal_db' => [
3089+
'custom' => [
3090+
'endpoint' => 'http://127.0.0.1:8000',
3091+
'username' => 'test',
3092+
'password' => 'test',
3093+
'namespace' => 'foo',
3094+
'database' => 'bar',
3095+
],
3096+
],
3097+
],
3098+
],
3099+
]);
3100+
3101+
$surrealDbMessageStoreDefinition = $container->getDefinition('ai.message_store.surreal_db.custom');
3102+
3103+
$this->assertTrue($surrealDbMessageStoreDefinition->isLazy());
3104+
$this->assertCount(8, $surrealDbMessageStoreDefinition->getArguments());
3105+
$this->assertInstanceOf(Reference::class, $surrealDbMessageStoreDefinition->getArgument(0));
3106+
$this->assertSame('http_client', (string) $surrealDbMessageStoreDefinition->getArgument(0));
3107+
$this->assertSame('http://127.0.0.1:8000', (string) $surrealDbMessageStoreDefinition->getArgument(1));
3108+
$this->assertSame('test', (string) $surrealDbMessageStoreDefinition->getArgument(2));
3109+
$this->assertSame('test', (string) $surrealDbMessageStoreDefinition->getArgument(3));
3110+
$this->assertSame('foo', (string) $surrealDbMessageStoreDefinition->getArgument(4));
3111+
$this->assertSame('bar', (string) $surrealDbMessageStoreDefinition->getArgument(5));
3112+
$this->assertInstanceOf(Reference::class, $surrealDbMessageStoreDefinition->getArgument(6));
3113+
$this->assertSame('serializer', (string) $surrealDbMessageStoreDefinition->getArgument(6));
3114+
$this->assertSame('custom', (string) $surrealDbMessageStoreDefinition->getArgument(7));
3115+
3116+
$this->assertTrue($surrealDbMessageStoreDefinition->hasTag('proxy'));
3117+
$this->assertSame([['interface' => MessageStoreInterface::class]], $surrealDbMessageStoreDefinition->getTag('proxy'));
3118+
$this->assertTrue($surrealDbMessageStoreDefinition->hasTag('ai.message_store'));
3119+
}
3120+
3121+
public function testSurrealDbMessageStoreIsConfiguredWithCustomTable()
3122+
{
3123+
$container = $this->buildContainer([
3124+
'ai' => [
3125+
'message_store' => [
3126+
'surreal_db' => [
3127+
'custom' => [
3128+
'endpoint' => 'http://127.0.0.1:8000',
3129+
'username' => 'test',
3130+
'password' => 'test',
3131+
'namespace' => 'foo',
3132+
'database' => 'bar',
3133+
'table' => 'random',
3134+
],
3135+
],
3136+
],
3137+
],
3138+
]);
3139+
3140+
$surrealDbMessageStoreDefinition = $container->getDefinition('ai.message_store.surreal_db.custom');
3141+
3142+
$this->assertTrue($surrealDbMessageStoreDefinition->isLazy());
3143+
$this->assertCount(8, $surrealDbMessageStoreDefinition->getArguments());
3144+
$this->assertInstanceOf(Reference::class, $surrealDbMessageStoreDefinition->getArgument(0));
3145+
$this->assertSame('http_client', (string) $surrealDbMessageStoreDefinition->getArgument(0));
3146+
$this->assertSame('http://127.0.0.1:8000', (string) $surrealDbMessageStoreDefinition->getArgument(1));
3147+
$this->assertSame('test', (string) $surrealDbMessageStoreDefinition->getArgument(2));
3148+
$this->assertSame('test', (string) $surrealDbMessageStoreDefinition->getArgument(3));
3149+
$this->assertSame('foo', (string) $surrealDbMessageStoreDefinition->getArgument(4));
3150+
$this->assertSame('bar', (string) $surrealDbMessageStoreDefinition->getArgument(5));
3151+
$this->assertInstanceOf(Reference::class, $surrealDbMessageStoreDefinition->getArgument(6));
3152+
$this->assertSame('serializer', (string) $surrealDbMessageStoreDefinition->getArgument(6));
3153+
$this->assertSame('random', (string) $surrealDbMessageStoreDefinition->getArgument(7));
3154+
3155+
$this->assertTrue($surrealDbMessageStoreDefinition->hasTag('proxy'));
3156+
$this->assertSame([['interface' => MessageStoreInterface::class]], $surrealDbMessageStoreDefinition->getTag('proxy'));
3157+
$this->assertTrue($surrealDbMessageStoreDefinition->hasTag('ai.message_store'));
3158+
}
3159+
3160+
public function testSurrealDbMessageStoreIsConfiguredWithNamespacedUser()
3161+
{
3162+
$container = $this->buildContainer([
3163+
'ai' => [
3164+
'message_store' => [
3165+
'surreal_db' => [
3166+
'custom' => [
3167+
'endpoint' => 'http://127.0.0.1:8000',
3168+
'username' => 'test',
3169+
'password' => 'test',
3170+
'namespace' => 'foo',
3171+
'database' => 'bar',
3172+
'namespaced_user' => true,
3173+
],
3174+
],
3175+
],
3176+
],
3177+
]);
3178+
3179+
$surrealDbMessageStoreDefinition = $container->getDefinition('ai.message_store.surreal_db.custom');
3180+
3181+
$this->assertTrue($surrealDbMessageStoreDefinition->isLazy());
3182+
$this->assertCount(9, $surrealDbMessageStoreDefinition->getArguments());
3183+
$this->assertInstanceOf(Reference::class, $surrealDbMessageStoreDefinition->getArgument(0));
3184+
$this->assertSame('http_client', (string) $surrealDbMessageStoreDefinition->getArgument(0));
3185+
$this->assertSame('http://127.0.0.1:8000', (string) $surrealDbMessageStoreDefinition->getArgument(1));
3186+
$this->assertSame('test', (string) $surrealDbMessageStoreDefinition->getArgument(2));
3187+
$this->assertSame('test', (string) $surrealDbMessageStoreDefinition->getArgument(3));
3188+
$this->assertSame('foo', (string) $surrealDbMessageStoreDefinition->getArgument(4));
3189+
$this->assertSame('bar', (string) $surrealDbMessageStoreDefinition->getArgument(5));
3190+
$this->assertInstanceOf(Reference::class, $surrealDbMessageStoreDefinition->getArgument(6));
3191+
$this->assertSame('serializer', (string) $surrealDbMessageStoreDefinition->getArgument(6));
3192+
$this->assertSame('custom', (string) $surrealDbMessageStoreDefinition->getArgument(7));
3193+
$this->assertTrue($surrealDbMessageStoreDefinition->getArgument(8));
3194+
3195+
$this->assertTrue($surrealDbMessageStoreDefinition->hasTag('proxy'));
3196+
$this->assertSame([['interface' => MessageStoreInterface::class]], $surrealDbMessageStoreDefinition->getTag('proxy'));
3197+
$this->assertTrue($surrealDbMessageStoreDefinition->hasTag('ai.message_store'));
3198+
}
3199+
30833200
private function buildContainer(array $configuration): ContainerBuilder
30843201
{
30853202
$container = new ContainerBuilder();
@@ -3440,6 +3557,25 @@ private function getFullConfig(): array
34403557
'identifier' => 'session',
34413558
],
34423559
],
3560+
'surreal_db' => [
3561+
'my_surreal_db_message_store' => [
3562+
'endpoint' => 'http://127.0.0.1:8000',
3563+
'username' => 'test',
3564+
'password' => 'test',
3565+
'namespace' => 'foo',
3566+
'database' => 'bar',
3567+
'namespaced_user' => true,
3568+
],
3569+
'my_surreal_db_message_store_with_custom_table' => [
3570+
'endpoint' => 'http://127.0.0.1:8000',
3571+
'username' => 'test',
3572+
'password' => 'test',
3573+
'namespace' => 'foo',
3574+
'database' => 'bar',
3575+
'table' => 'bar',
3576+
'namespaced_user' => true,
3577+
],
3578+
],
34433579
],
34443580
'chat' => [
34453581
'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)