Skip to content

Commit da4a972

Browse files
committed
feat: chat + message store (#208)
1 parent cdec121 commit da4a972

File tree

8 files changed

+277
-2
lines changed

8 files changed

+277
-2
lines changed

examples/misc/persistent-chat.php

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
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\Agent\Chat;
14+
use Symfony\AI\Agent\Chat\MessageStore\InMemoryStore;
15+
use Symfony\AI\Platform\Bridge\OpenAI\GPT;
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\Dotenv\Dotenv;
20+
21+
require_once dirname(__DIR__).'/vendor/autoload.php';
22+
(new Dotenv())->loadEnv(dirname(__DIR__).'/.env');
23+
24+
if (empty($_ENV['OPENAI_API_KEY'])) {
25+
echo 'Please set the OPENAI_API_KEY environment variable.'.\PHP_EOL;
26+
exit(1);
27+
}
28+
29+
$platform = PlatformFactory::create($_ENV['OPENAI_API_KEY']);
30+
$llm = new GPT(GPT::GPT_4O_MINI);
31+
32+
$agent = new Agent($platform, $llm);
33+
$chat = new Chat($agent, new InMemoryStore());
34+
35+
$messages = new MessageBag(
36+
Message::forSystem('You are a helpful assistant. You only answer with short sentences.'),
37+
);
38+
39+
$chat->initiate($messages);
40+
$chat->submit(Message::ofUser('My name is Christopher.'));
41+
$message = $chat->submit(Message::ofUser('What is my name?'));
42+
43+
echo $message->content.\PHP_EOL;

src/agent/composer.json

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,14 +37,19 @@
3737
"phpstan/phpstan": "^2.0",
3838
"phpunit/phpunit": "^11.5.13",
3939
"symfony/ai-store": "@dev",
40+
"symfony/cache": "^6.4 || ^7.1",
4041
"symfony/css-selector": "^6.4 || ^7.1",
4142
"symfony/dom-crawler": "^6.4 || ^7.1",
42-
"symfony/event-dispatcher": "^6.4 || ^7.1"
43+
"symfony/event-dispatcher": "^6.4 || ^7.1",
44+
"symfony/http-foundation": "^6.4 || ^7.1"
4345
},
4446
"suggest": {
4547
"mrmysql/youtube-transcript": "For using the YouTube transcription tool.",
4648
"symfony/ai-store": "For using Similarity Search with a vector store.",
47-
"symfony/dom-crawler": "For using the Crawler tool."
49+
"symfony/css-selector": "For using the YouTube transcription tool.",
50+
"symfony/dom-crawler": "For using the Crawler tool.",
51+
"symfony/http-foundation": "For using the SessionStore as message store.",
52+
"psr/cache": "For using the CacheStore as message store."
4853
},
4954
"config": {
5055
"sort-packages": true

src/agent/src/Chat.php

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 <[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+
namespace Symfony\AI\Agent;
13+
14+
use Symfony\AI\Agent\Chat\MessageStoreInterface;
15+
use Symfony\AI\Platform\Message\AssistantMessage;
16+
use Symfony\AI\Platform\Message\Message;
17+
use Symfony\AI\Platform\Message\MessageBagInterface;
18+
use Symfony\AI\Platform\Message\UserMessage;
19+
use Symfony\AI\Platform\Response\TextResponse;
20+
21+
final readonly class Chat implements ChatInterface
22+
{
23+
public function __construct(
24+
private AgentInterface $agent,
25+
private MessageStoreInterface $store,
26+
) {
27+
}
28+
29+
public function initiate(MessageBagInterface $messages): void
30+
{
31+
$this->store->clear();
32+
$this->store->save($messages);
33+
}
34+
35+
public function submit(UserMessage $message): AssistantMessage
36+
{
37+
$messages = $this->store->load();
38+
39+
$messages->add($message);
40+
$response = $this->agent->call($messages);
41+
42+
\assert($response instanceof TextResponse);
43+
44+
$assistantMessage = Message::ofAssistant($response->getContent());
45+
$messages->add($assistantMessage);
46+
47+
$this->store->save($messages);
48+
49+
return $assistantMessage;
50+
}
51+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
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+
namespace Symfony\AI\Agent\Chat\MessageStore;
13+
14+
use Psr\Cache\CacheItemPoolInterface;
15+
use Symfony\AI\Agent\Chat\MessageStoreInterface;
16+
use Symfony\AI\Platform\Message\MessageBag;
17+
use Symfony\AI\Platform\Message\MessageBagInterface;
18+
19+
final readonly class CacheStore implements MessageStoreInterface
20+
{
21+
public function __construct(
22+
private CacheItemPoolInterface $cache,
23+
private string $cacheKey,
24+
private int $ttl = 86400,
25+
) {
26+
}
27+
28+
public function save(MessageBagInterface $messages): void
29+
{
30+
$item = $this->cache->getItem($this->cacheKey);
31+
32+
$item->set($messages);
33+
$item->expiresAfter($this->ttl);
34+
35+
$this->cache->save($item);
36+
}
37+
38+
public function load(): MessageBag
39+
{
40+
$item = $this->cache->getItem($this->cacheKey);
41+
42+
return $item->isHit() ? $item->get() : new MessageBag();
43+
}
44+
45+
public function clear(): void
46+
{
47+
$this->cache->deleteItem($this->cacheKey);
48+
}
49+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
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+
namespace Symfony\AI\Agent\Chat\MessageStore;
13+
14+
use Symfony\AI\Agent\Chat\MessageStoreInterface;
15+
use Symfony\AI\Platform\Message\MessageBag;
16+
use Symfony\AI\Platform\Message\MessageBagInterface;
17+
18+
final class InMemoryStore implements MessageStoreInterface
19+
{
20+
private MessageBagInterface $messages;
21+
22+
public function save(MessageBagInterface $messages): void
23+
{
24+
$this->messages = $messages;
25+
}
26+
27+
public function load(): MessageBagInterface
28+
{
29+
return $this->messages ?? new MessageBag();
30+
}
31+
32+
public function clear(): void
33+
{
34+
$this->messages = new MessageBag();
35+
}
36+
}
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+
namespace Symfony\AI\Agent\Chat\MessageStore;
13+
14+
use Symfony\AI\Agent\Chat\MessageStoreInterface;
15+
use Symfony\AI\Platform\Message\MessageBag;
16+
use Symfony\AI\Platform\Message\MessageBagInterface;
17+
use Symfony\Component\HttpFoundation\RequestStack;
18+
use Symfony\Component\HttpFoundation\Session\SessionInterface;
19+
20+
final readonly class SessionStore implements MessageStoreInterface
21+
{
22+
private SessionInterface $session;
23+
24+
public function __construct(
25+
RequestStack $requestStack,
26+
private string $sessionKey = 'messages',
27+
) {
28+
$this->session = $requestStack->getSession();
29+
}
30+
31+
public function save(MessageBagInterface $messages): void
32+
{
33+
$this->session->set($this->sessionKey, $messages);
34+
}
35+
36+
public function load(): MessageBagInterface
37+
{
38+
return $this->session->get($this->sessionKey, new MessageBag());
39+
}
40+
41+
public function clear(): void
42+
{
43+
$this->session->remove($this->sessionKey);
44+
}
45+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
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+
namespace Symfony\AI\Agent\Chat;
13+
14+
use Symfony\AI\Platform\Message\MessageBagInterface;
15+
16+
interface MessageStoreInterface
17+
{
18+
public function save(MessageBagInterface $messages): void;
19+
20+
public function load(): MessageBagInterface;
21+
22+
public function clear(): void;
23+
}

src/agent/src/ChatInterface.php

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
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+
namespace Symfony\AI\Agent;
13+
14+
use Symfony\AI\Platform\Message\AssistantMessage;
15+
use Symfony\AI\Platform\Message\MessageBagInterface;
16+
use Symfony\AI\Platform\Message\UserMessage;
17+
18+
interface ChatInterface
19+
{
20+
public function initiate(MessageBagInterface $messages): void;
21+
22+
public function submit(UserMessage $message): AssistantMessage;
23+
}

0 commit comments

Comments
 (0)