Skip to content

Commit e9bfa96

Browse files
committed
feat(chat): redis added
1 parent 5db564b commit e9bfa96

File tree

15 files changed

+523
-1
lines changed

15 files changed

+523
-1
lines changed

docs/components/chat.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ You can find more advanced usage in combination with an Agent using the store fo
3838
* `Current process context storage with InMemory`_
3939
* `Long-term context with Meilisearch`_
4040
* `Long-term context with Pogocache`_
41+
* `Long-term context with Redis`_
4142

4243
Supported Message stores
4344
------------------------
@@ -47,6 +48,7 @@ Supported Message stores
4748
* `InMemory`_
4849
* `Meilisearch`_
4950
* `Pogocache`_
51+
* `Redis`_
5052

5153
Implementing a Bridge
5254
---------------------
@@ -127,8 +129,10 @@ store and ``bin/console ai:message-store:drop`` to clean up the message store:
127129
.. _`Current process context storage with InMemory`: https://github.com/symfony/ai/blob/main/examples/chat/persistent-chat.php
128130
.. _`Long-term context with Meilisearch`: https://github.com/symfony/ai/blob/main/examples/chat/persistent-chat-meilisearch.php
129131
.. _`Long-term context with Pogocache`: https://github.com/symfony/ai/blob/main/examples/chat/persistent-chat-pogocache.php
132+
.. _`Long-term context with Redis`: https://github.com/symfony/ai/blob/main/examples/chat/persistent-chat-redis.php
130133
.. _`Cache`: https://symfony.com/doc/current/components/cache.html
131134
.. _`InMemory`: https://www.php.net/manual/en/language.types.array.php
132135
.. _`HttpFoundation session`: https://developers.cloudflare.com/vectorize/
133136
.. _`Meilisearch`: https://www.meilisearch.com/
134137
.. _`Pogocache`: https://pogocache.com/
138+
.. _`Redis`: https://redis.io/

examples/.env

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,3 +168,7 @@ SUPABASE_MATCH_FUNCTION=match_documents
168168
# Pogocache (message store)
169169
POGOCACHE_HOST=http://127.0.0.1:9401
170170
POGOCACHE_PASSWORD=symfony
171+
172+
# Redis (both store and message store)
173+
REDIS_HOST=http://127.0.0.1
174+
REDIS_PORT=6379
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
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\Redis\MessageStore;
14+
use Symfony\AI\Chat\Chat;
15+
use Symfony\AI\Chat\MessageNormalizer;
16+
use Symfony\AI\Platform\Bridge\OpenAi\PlatformFactory;
17+
use Symfony\AI\Platform\Message\Message;
18+
use Symfony\AI\Platform\Message\MessageBag;
19+
use Symfony\Component\Serializer\Encoder\JsonEncoder;
20+
use Symfony\Component\Serializer\Normalizer\ArrayDenormalizer;
21+
use Symfony\Component\Serializer\Serializer;
22+
23+
require_once dirname(__DIR__).'/bootstrap.php';
24+
25+
$platform = PlatformFactory::create(env('OPENAI_API_KEY'), http_client());
26+
27+
$redis = new Redis([
28+
'host' => env('REDIS_HOST'),
29+
'port' => 6379,
30+
]);
31+
32+
$store = new MessageStore($redis, 'symfony', new Serializer([
33+
new ArrayDenormalizer(),
34+
new MessageNormalizer(),
35+
], [
36+
new JsonEncoder(),
37+
]));
38+
$store->setup();
39+
40+
$agent = new Agent($platform, 'gpt-4o-mini');
41+
$chat = new Chat($agent, $store);
42+
43+
$messages = new MessageBag(
44+
Message::forSystem('You are a helpful assistant. You only answer with short sentences.'),
45+
);
46+
47+
$chat->initiate($messages);
48+
$chat->submit(Message::ofUser('My name is Christopher.'));
49+
$message = $chat->submit(Message::ofUser('What is my name?'));
50+
51+
echo $message->getContent().\PHP_EOL;

examples/commands/message-stores.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,10 @@
1616
use Symfony\AI\Chat\Bridge\Local\InMemoryStore;
1717
use Symfony\AI\Chat\Bridge\Meilisearch\MessageStore as MeilisearchMessageStore;
1818
use Symfony\AI\Chat\Bridge\Pogocache\MessageStore as PogocacheMessageStore;
19+
use Symfony\AI\Chat\Bridge\Redis\MessageStore as RedisMessageStore;
1920
use Symfony\AI\Chat\Command\DropStoreCommand;
2021
use Symfony\AI\Chat\Command\SetupStoreCommand;
22+
use Symfony\AI\Chat\MessageNormalizer;
2123
use Symfony\Component\Cache\Adapter\ArrayAdapter;
2224
use Symfony\Component\Clock\MonotonicClock;
2325
use Symfony\Component\Console\Application;
@@ -28,6 +30,9 @@
2830
use Symfony\Component\HttpFoundation\RequestStack;
2931
use Symfony\Component\HttpFoundation\Session\Session;
3032
use Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage;
33+
use Symfony\Component\Serializer\Encoder\JsonEncoder;
34+
use Symfony\Component\Serializer\Normalizer\ArrayDenormalizer;
35+
use Symfony\Component\Serializer\Serializer;
3136

3237
$factories = [
3338
'cache' => static fn (): CacheStore => new CacheStore(new ArrayAdapter(), cacheKey: 'symfony'),
@@ -45,6 +50,15 @@
4550
env('POGOCACHE_PASSWORD'),
4651
'symfony',
4752
),
53+
'redis' => static fn (): RedisMessageStore => new RedisMessageStore(new Redis([
54+
'host' => env('REDIS_HOST'),
55+
'port' => 6379,
56+
]), 'symfony', new Serializer([
57+
new ArrayDenormalizer(),
58+
new MessageNormalizer(),
59+
], [
60+
new JsonEncoder(),
61+
])),
4862
'session' => static function (): SessionStore {
4963
$request = Request::create('/');
5064
$request->setSession(new Session(new MockArraySessionStorage()));

examples/composer.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
"require": {
77
"php": ">=8.2",
88
"ext-pdo": "*",
9+
"ext-redis": "*",
910
"async-aws/bedrock-runtime": "^1.1",
1011
"codewithkyrian/chromadb-php": "^0.4.0",
1112
"codewithkyrian/transformers": "^0.6.2",
@@ -32,6 +33,7 @@
3233
"symfony/finder": "^7.3|^8.0",
3334
"symfony/http-foundation": "^7.3|^8.0",
3435
"symfony/process": "^7.3|^8.0",
36+
"symfony/serializer": "^7.3|^8.0",
3537
"symfony/var-dumper": "^7.3|^8.0"
3638
},
3739
"require-dev": {

src/ai-bundle/config/options.php

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -764,6 +764,31 @@
764764
->end()
765765
->end()
766766
->end()
767+
->arrayNode('redis')
768+
->useAttributeAsKey('name')
769+
->arrayPrototype()
770+
->children()
771+
->variableNode('connection_parameters')
772+
->info('see https://github.com/phpredis/phpredis?tab=readme-ov-file#example-1')
773+
->cannotBeEmpty()
774+
->end()
775+
->stringNode('client')
776+
->info('a service id of a Redis client')
777+
->cannotBeEmpty()
778+
->end()
779+
->stringNode('endpoint')->cannotBeEmpty()->end()
780+
->stringNode('index_name')->cannotBeEmpty()->end()
781+
->end()
782+
->validate()
783+
->ifTrue(static fn (array $v): bool => !isset($v['connection_parameters']) && !isset($v['client']))
784+
->thenInvalid('Either "connection_parameters" or "client" must be configured.')
785+
->end()
786+
->validate()
787+
->ifTrue(static fn (array $v): bool => isset($v['connection_parameters']) && isset($v['client']))
788+
->thenInvalid('Either "connection_parameters" or "client" can be configured, but not both.')
789+
->end()
790+
->end()
791+
->end()
767792
->arrayNode('session')
768793
->useAttributeAsKey('name')
769794
->arrayPrototype()

src/ai-bundle/config/services.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
use Symfony\AI\AiBundle\Security\EventListener\IsGrantedToolAttributeListener;
2424
use Symfony\AI\Chat\Command\DropStoreCommand as DropMessageStoreCommand;
2525
use Symfony\AI\Chat\Command\SetupStoreCommand as SetupMessageStoreCommand;
26+
use Symfony\AI\Chat\MessageNormalizer;
2627
use Symfony\AI\Platform\Bridge\AiMlApi\ModelCatalog as AiMlApiModelCatalog;
2728
use Symfony\AI\Platform\Bridge\Anthropic\Contract\AnthropicContract;
2829
use Symfony\AI\Platform\Bridge\Anthropic\ModelCatalog as AnthropicModelCatalog;
@@ -182,6 +183,10 @@
182183
// search result processors
183184
->set('ai.platform.search_result_processor.perplexity', PerplexitySearchResultProcessor::class)
184185

186+
// serializer
187+
->set('ai.chat.message_bag.normalizer', MessageNormalizer::class)
188+
->tag('serializer.normalizer')
189+
185190
// commands
186191
->set('ai.command.chat', AgentCallCommand::class)
187192
->args([

src/ai-bundle/src/AiBundle.php

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
use Symfony\AI\Chat\Bridge\HttpFoundation\SessionStore;
3838
use Symfony\AI\Chat\Bridge\Meilisearch\MessageStore as MeilisearchMessageStore;
3939
use Symfony\AI\Chat\Bridge\Pogocache\MessageStore as PogocacheMessageStore;
40+
use Symfony\AI\Chat\Bridge\Redis\MessageStore as RedisMessageStore;
4041
use Symfony\AI\Chat\MessageStoreInterface;
4142
use Symfony\AI\Platform\Bridge\Anthropic\PlatformFactory as AnthropicPlatformFactory;
4243
use Symfony\AI\Platform\Bridge\Azure\OpenAi\PlatformFactory as AzureOpenAiPlatformFactory;
@@ -1379,6 +1380,30 @@ private function processMessageStoreConfig(string $type, array $messageStores, C
13791380
}
13801381
}
13811382

1383+
if ('redis' === $type) {
1384+
foreach ($messageStores as $name => $messageStore) {
1385+
if (isset($messageStore['client'])) {
1386+
$redisClient = new Reference($messageStore['client']);
1387+
} else {
1388+
$redisClient = new Definition(\Redis::class);
1389+
$redisClient->setArguments([$messageStore['connection_parameters']]);
1390+
}
1391+
1392+
$definition = new Definition(RedisMessageStore::class);
1393+
$definition
1394+
->setArguments([
1395+
$redisClient,
1396+
$messageStore['index_name'],
1397+
new Reference('serializer'),
1398+
])
1399+
->addTag('ai.message_store');
1400+
1401+
$container->setDefinition('ai.message_store.'.$type.'.'.$name, $definition);
1402+
$container->registerAliasForArgument('ai.message_store.'.$type.'.'.$name, MessageStoreInterface::class, $name);
1403+
$container->registerAliasForArgument('ai.message_store.'.$type.'.'.$name, MessageStoreInterface::class, $type.'_'.$name);
1404+
}
1405+
}
1406+
13821407
if ('session' === $type) {
13831408
foreach ($messageStores as $name => $messageStore) {
13841409
$definition = new Definition(SessionStore::class);

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

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,13 @@ public function testMessageStoreCommandsAreDefined()
120120
$this->assertArrayHasKey('console.command', $dropStoreCommandDefinition->getTags());
121121
}
122122

123+
public function testMessageBagNormalizerIsRegistered()
124+
{
125+
$container = $this->buildContainer($this->getFullConfig());
126+
127+
$this->assertTrue($container->hasDefinition('ai.chat.message_bag.normalizer'));
128+
}
129+
123130
public function testInjectionAgentAliasIsRegistered()
124131
{
125132
$container = $this->buildContainer([
@@ -3032,6 +3039,15 @@ private function getFullConfig(): array
30323039
'key' => 'bar',
30333040
],
30343041
],
3042+
'redis' => [
3043+
'my_redis_store' => [
3044+
'connection_parameters' => [
3045+
'host' => '1.2.3.4',
3046+
'port' => 6379,
3047+
],
3048+
'index_name' => 'my_message_store',
3049+
],
3050+
],
30353051
'session' => [
30363052
'my_session_message_store' => [
30373053
'identifier' => 'session',

src/chat/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,7 @@ CHANGELOG
55
---
66

77
* Introduce the component
8+
* Add support for external message stores:
9+
- Meilisearch
10+
- Pogocache
11+
- Redis

0 commit comments

Comments
 (0)