Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions docs/components/chat.rst
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ You can find more advanced usage in combination with an Agent using the store fo
* `Long-term context with Meilisearch`_
* `Long-term context with Pogocache`_
* `Long-term context with Redis`_
* `Long-term context with SurrealDb`_

Supported Message stores
------------------------
Expand All @@ -49,6 +50,7 @@ Supported Message stores
* `Meilisearch`_
* `Pogocache`_
* `Redis`_
* `SurrealDb`_

Implementing a Bridge
---------------------
Expand Down Expand Up @@ -130,9 +132,11 @@ store and ``bin/console ai:message-store:drop`` to clean up the message store:
.. _`Long-term context with Meilisearch`: https://github.com/symfony/ai/blob/main/examples/chat/persistent-chat-meilisearch.php
.. _`Long-term context with Pogocache`: https://github.com/symfony/ai/blob/main/examples/chat/persistent-chat-pogocache.php
.. _`Long-term context with Redis`: https://github.com/symfony/ai/blob/main/examples/chat/persistent-chat-redis.php
.. _`Long-term context with SurrealDb`: https://github.com/symfony/ai/blob/main/examples/chat/persistent-chat-surrealdb.php
.. _`Cache`: https://symfony.com/doc/current/components/cache.html
.. _`InMemory`: https://www.php.net/manual/en/language.types.array.php
.. _`HttpFoundation session`: https://developers.cloudflare.com/vectorize/
.. _`Meilisearch`: https://www.meilisearch.com/
.. _`Pogocache`: https://pogocache.com/
.. _`Redis`: https://redis.io/
.. _`SurrealDb`: https://surrealdb.com/
45 changes: 45 additions & 0 deletions examples/chat/persistent-chat-surrealdb.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

use Symfony\AI\Agent\Agent;
use Symfony\AI\Chat\Bridge\SurrealDb\MessageStore;
use Symfony\AI\Chat\Chat;
use Symfony\AI\Platform\Bridge\OpenAi\PlatformFactory;
use Symfony\AI\Platform\Message\Message;
use Symfony\AI\Platform\Message\MessageBag;

require_once dirname(__DIR__).'/bootstrap.php';

$platform = PlatformFactory::create(env('OPENAI_API_KEY'), http_client());

// SurrealDb does not require to call the `setup()` method as the table is created during insertion
$store = new MessageStore(
httpClient: http_client(),
endpointUrl: 'http://127.0.0.1:8000',
user: env('SURREALDB_USER'),
password: env('SURREALDB_PASS'),
namespace: 'default',
database: 'chat',
table: 'chat',
);

$agent = new Agent($platform, 'gpt-4o-mini');
$chat = new Chat($agent, $store);

$messages = new MessageBag(
Message::forSystem('You are a helpful assistant. You only answer with short sentences.'),
);

$chat->initiate($messages);
$chat->submit(Message::ofUser('My name is Christopher.'));
$message = $chat->submit(Message::ofUser('What is my name?'));

echo $message->getContent().\PHP_EOL;
10 changes: 10 additions & 0 deletions examples/commands/message-stores.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
use Symfony\AI\Chat\Bridge\Meilisearch\MessageStore as MeilisearchMessageStore;
use Symfony\AI\Chat\Bridge\Pogocache\MessageStore as PogocacheMessageStore;
use Symfony\AI\Chat\Bridge\Redis\MessageStore as RedisMessageStore;
use Symfony\AI\Chat\Bridge\SurrealDb\MessageStore as SurrealDbMessageStore;
use Symfony\AI\Chat\Command\DropStoreCommand;
use Symfony\AI\Chat\Command\SetupStoreCommand;
use Symfony\AI\Chat\MessageNormalizer;
Expand Down Expand Up @@ -68,6 +69,15 @@

return new SessionStore($requestStack, 'symfony');
},
'surrealdb' => static fn (): SurrealDbMessageStore => new SurrealDbMessageStore(
httpClient: http_client(),
endpointUrl: env('SURREALDB_HOST'),
user: env('SURREALDB_USER'),
password: env('SURREALDB_PASS'),
namespace: 'default',
database: 'chat',
table: 'chat',
),
];

$storesIds = array_keys($factories);
Expand Down
16 changes: 16 additions & 0 deletions src/ai-bundle/config/options.php
Original file line number Diff line number Diff line change
Expand Up @@ -847,6 +847,22 @@
->end()
->end()
->end()
->arrayNode('surreal_db')
->useAttributeAsKey('name')
->arrayPrototype()
->children()
->stringNode('endpoint')->cannotBeEmpty()->end()
->stringNode('username')->cannotBeEmpty()->end()
->stringNode('password')->cannotBeEmpty()->end()
->stringNode('namespace')->cannotBeEmpty()->end()
->stringNode('database')->cannotBeEmpty()->end()
->stringNode('table')->end()
->booleanNode('namespaced_user')
->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')
->end()
->end()
->end()
->end()
->end()
->end()
->arrayNode('chat')
Expand Down
31 changes: 31 additions & 0 deletions src/ai-bundle/src/AiBundle.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
use Symfony\AI\Chat\Bridge\Meilisearch\MessageStore as MeilisearchMessageStore;
use Symfony\AI\Chat\Bridge\Pogocache\MessageStore as PogocacheMessageStore;
use Symfony\AI\Chat\Bridge\Redis\MessageStore as RedisMessageStore;
use Symfony\AI\Chat\Bridge\SurrealDb\MessageStore as SurrealDbMessageStore;
use Symfony\AI\Chat\Chat;
use Symfony\AI\Chat\ChatInterface;
use Symfony\AI\Chat\MessageStoreInterface;
Expand Down Expand Up @@ -1596,6 +1597,36 @@ private function processMessageStoreConfig(string $type, array $messageStores, C
$container->registerAliasForArgument('ai.message_store.'.$type.'.'.$name, MessageStoreInterface::class, $type.'_'.$name);
}
}

if ('surreal_db' === $type) {
foreach ($messageStores as $name => $messageStore) {
$arguments = [
new Reference('http_client'),
$messageStore['endpoint'],
$messageStore['username'],
$messageStore['password'],
$messageStore['namespace'],
$messageStore['database'],
new Reference('serializer'),
$messageStore['table'] ?? $name,
];

if (\array_key_exists('namespaced_user', $messageStore)) {
$arguments[8] = $messageStore['namespaced_user'];
}

$definition = new Definition(SurrealDbMessageStore::class);
$definition
->setLazy(true)
->addTag('proxy', ['interface' => MessageStoreInterface::class])
->addTag('ai.message_store')
->setArguments($arguments);

$container->setDefinition('ai.message_store.'.$type.'.'.$name, $definition);
$container->registerAliasForArgument('ai.message_store.'.$type.'.'.$name, StoreInterface::class, $name);
$container->registerAliasForArgument('ai.message_store.'.$type.'.'.$name, StoreInterface::class, $type.'_'.$name);
}
}
}

/**
Expand Down
136 changes: 136 additions & 0 deletions src/ai-bundle/tests/DependencyInjection/AiBundleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -3080,6 +3080,123 @@ public function testSessionMessageStoreIsConfigured()
$this->assertTrue($sessionMessageStoreDefinition->hasTag('ai.message_store'));
}

public function testSurrealDbMessageStoreIsConfiguredWithoutCustomTable()
{
$container = $this->buildContainer([
'ai' => [
'message_store' => [
'surreal_db' => [
'custom' => [
'endpoint' => 'http://127.0.0.1:8000',
'username' => 'test',
'password' => 'test',
'namespace' => 'foo',
'database' => 'bar',
],
],
],
],
]);

$surrealDbMessageStoreDefinition = $container->getDefinition('ai.message_store.surreal_db.custom');

$this->assertTrue($surrealDbMessageStoreDefinition->isLazy());
$this->assertCount(8, $surrealDbMessageStoreDefinition->getArguments());
$this->assertInstanceOf(Reference::class, $surrealDbMessageStoreDefinition->getArgument(0));
$this->assertSame('http_client', (string) $surrealDbMessageStoreDefinition->getArgument(0));
$this->assertSame('http://127.0.0.1:8000', (string) $surrealDbMessageStoreDefinition->getArgument(1));
$this->assertSame('test', (string) $surrealDbMessageStoreDefinition->getArgument(2));
$this->assertSame('test', (string) $surrealDbMessageStoreDefinition->getArgument(3));
$this->assertSame('foo', (string) $surrealDbMessageStoreDefinition->getArgument(4));
$this->assertSame('bar', (string) $surrealDbMessageStoreDefinition->getArgument(5));
$this->assertInstanceOf(Reference::class, $surrealDbMessageStoreDefinition->getArgument(6));
$this->assertSame('serializer', (string) $surrealDbMessageStoreDefinition->getArgument(6));
$this->assertSame('custom', (string) $surrealDbMessageStoreDefinition->getArgument(7));

$this->assertTrue($surrealDbMessageStoreDefinition->hasTag('proxy'));
$this->assertSame([['interface' => MessageStoreInterface::class]], $surrealDbMessageStoreDefinition->getTag('proxy'));
$this->assertTrue($surrealDbMessageStoreDefinition->hasTag('ai.message_store'));
}

public function testSurrealDbMessageStoreIsConfiguredWithCustomTable()
{
$container = $this->buildContainer([
'ai' => [
'message_store' => [
'surreal_db' => [
'custom' => [
'endpoint' => 'http://127.0.0.1:8000',
'username' => 'test',
'password' => 'test',
'namespace' => 'foo',
'database' => 'bar',
'table' => 'random',
],
],
],
],
]);

$surrealDbMessageStoreDefinition = $container->getDefinition('ai.message_store.surreal_db.custom');

$this->assertTrue($surrealDbMessageStoreDefinition->isLazy());
$this->assertCount(8, $surrealDbMessageStoreDefinition->getArguments());
$this->assertInstanceOf(Reference::class, $surrealDbMessageStoreDefinition->getArgument(0));
$this->assertSame('http_client', (string) $surrealDbMessageStoreDefinition->getArgument(0));
$this->assertSame('http://127.0.0.1:8000', (string) $surrealDbMessageStoreDefinition->getArgument(1));
$this->assertSame('test', (string) $surrealDbMessageStoreDefinition->getArgument(2));
$this->assertSame('test', (string) $surrealDbMessageStoreDefinition->getArgument(3));
$this->assertSame('foo', (string) $surrealDbMessageStoreDefinition->getArgument(4));
$this->assertSame('bar', (string) $surrealDbMessageStoreDefinition->getArgument(5));
$this->assertInstanceOf(Reference::class, $surrealDbMessageStoreDefinition->getArgument(6));
$this->assertSame('serializer', (string) $surrealDbMessageStoreDefinition->getArgument(6));
$this->assertSame('random', (string) $surrealDbMessageStoreDefinition->getArgument(7));

$this->assertTrue($surrealDbMessageStoreDefinition->hasTag('proxy'));
$this->assertSame([['interface' => MessageStoreInterface::class]], $surrealDbMessageStoreDefinition->getTag('proxy'));
$this->assertTrue($surrealDbMessageStoreDefinition->hasTag('ai.message_store'));
}

public function testSurrealDbMessageStoreIsConfiguredWithNamespacedUser()
{
$container = $this->buildContainer([
'ai' => [
'message_store' => [
'surreal_db' => [
'custom' => [
'endpoint' => 'http://127.0.0.1:8000',
'username' => 'test',
'password' => 'test',
'namespace' => 'foo',
'database' => 'bar',
'namespaced_user' => true,
],
],
],
],
]);

$surrealDbMessageStoreDefinition = $container->getDefinition('ai.message_store.surreal_db.custom');

$this->assertTrue($surrealDbMessageStoreDefinition->isLazy());
$this->assertCount(9, $surrealDbMessageStoreDefinition->getArguments());
$this->assertInstanceOf(Reference::class, $surrealDbMessageStoreDefinition->getArgument(0));
$this->assertSame('http_client', (string) $surrealDbMessageStoreDefinition->getArgument(0));
$this->assertSame('http://127.0.0.1:8000', (string) $surrealDbMessageStoreDefinition->getArgument(1));
$this->assertSame('test', (string) $surrealDbMessageStoreDefinition->getArgument(2));
$this->assertSame('test', (string) $surrealDbMessageStoreDefinition->getArgument(3));
$this->assertSame('foo', (string) $surrealDbMessageStoreDefinition->getArgument(4));
$this->assertSame('bar', (string) $surrealDbMessageStoreDefinition->getArgument(5));
$this->assertInstanceOf(Reference::class, $surrealDbMessageStoreDefinition->getArgument(6));
$this->assertSame('serializer', (string) $surrealDbMessageStoreDefinition->getArgument(6));
$this->assertSame('custom', (string) $surrealDbMessageStoreDefinition->getArgument(7));
$this->assertTrue($surrealDbMessageStoreDefinition->getArgument(8));

$this->assertTrue($surrealDbMessageStoreDefinition->hasTag('proxy'));
$this->assertSame([['interface' => MessageStoreInterface::class]], $surrealDbMessageStoreDefinition->getTag('proxy'));
$this->assertTrue($surrealDbMessageStoreDefinition->hasTag('ai.message_store'));
}

private function buildContainer(array $configuration): ContainerBuilder
{
$container = new ContainerBuilder();
Expand Down Expand Up @@ -3440,6 +3557,25 @@ private function getFullConfig(): array
'identifier' => 'session',
],
],
'surreal_db' => [
'my_surreal_db_message_store' => [
'endpoint' => 'http://127.0.0.1:8000',
'username' => 'test',
'password' => 'test',
'namespace' => 'foo',
'database' => 'bar',
'namespaced_user' => true,
],
'my_surreal_db_message_store_with_custom_table' => [
'endpoint' => 'http://127.0.0.1:8000',
'username' => 'test',
'password' => 'test',
'namespace' => 'foo',
'database' => 'bar',
'table' => 'bar',
'namespaced_user' => true,
],
],
],
'chat' => [
'main' => [
Expand Down
1 change: 1 addition & 0 deletions src/chat/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ CHANGELOG
- Meilisearch
- Pogocache
- Redis
- SurrealDb
Loading