Skip to content

Commit a133112

Browse files
committed
feat: chat + message store
1 parent 06e52f1 commit a133112

8 files changed

+221
-2
lines changed

composer.json

+5-2
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
"php": ">=8.2",
1414
"oskarstark/enum-helper": "^1.5",
1515
"phpdocumentor/reflection-docblock": "^5.4",
16-
"psr/cache": "^3.0",
1716
"psr/log": "^3.0",
1817
"symfony/clock": "^6.4 || ^7.1",
1918
"symfony/http-client": "^6.4 || ^7.1",
@@ -33,12 +32,14 @@
3332
"phpunit/phpunit": "^11.3",
3433
"probots-io/pinecone-php": "^1.0",
3534
"rector/rector": "^1.2",
35+
"symfony/cache": "^6.4 || ^7.1",
3636
"symfony/console": "^6.4 || ^7.1",
3737
"symfony/css-selector": "^6.4 || ^7.1",
3838
"symfony/dom-crawler": "^6.4 || ^7.1",
3939
"symfony/dotenv": "^6.4 || ^7.1",
4040
"symfony/event-dispatcher": "^6.4 || ^7.1",
4141
"symfony/finder": "^6.4 || ^7.1",
42+
"symfony/http-foundation": "^6.4 || ^7.1",
4243
"symfony/process": "^6.4 || ^7.1",
4344
"symfony/var-dumper": "^6.4 || ^7.1"
4445
},
@@ -50,7 +51,9 @@
5051
"mongodb/mongodb": "For using MongoDB Atlas as retrieval vector store.",
5152
"probots-io/pinecone-php": "For using the Pinecone as retrieval vector store.",
5253
"symfony/css-selector": "For using the YouTube transcription tool.",
53-
"symfony/dom-crawler": "For using the YouTube transcription tool."
54+
"symfony/dom-crawler": "For using the YouTube transcription tool.",
55+
"symfony/http-foundation": "For using the SessionStore as message store.",
56+
"psr/cache": "For using the CacheStore as message store."
5457
},
5558
"autoload": {
5659
"psr-4": {

examples/persistent-chat.php

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<?php
2+
3+
use PhpLlm\LlmChain\Bridge\OpenAI\GPT;
4+
use PhpLlm\LlmChain\Bridge\OpenAI\PlatformFactory;
5+
use PhpLlm\LlmChain\Chain;
6+
use PhpLlm\LlmChain\Chat;
7+
use PhpLlm\LlmChain\Chat\MessageStore\InMemoryStore;
8+
use PhpLlm\LlmChain\Model\Message\Message;
9+
use PhpLlm\LlmChain\Model\Message\MessageBag;
10+
use Symfony\Component\Dotenv\Dotenv;
11+
12+
require_once dirname(__DIR__).'/vendor/autoload.php';
13+
(new Dotenv())->loadEnv(dirname(__DIR__).'/.env');
14+
15+
if (empty($_ENV['OPENAI_API_KEY'])) {
16+
echo 'Please set the OPENAI_API_KEY environment variable.'.PHP_EOL;
17+
exit(1);
18+
}
19+
20+
$platform = PlatformFactory::create($_ENV['OPENAI_API_KEY']);
21+
$llm = new GPT(GPT::GPT_4O_MINI);
22+
23+
$chain = new Chain($platform, $llm);
24+
$chat = new Chat($chain, new InMemoryStore());
25+
26+
$messages = new MessageBag(
27+
Message::forSystem('You are a helpful assistant. You only answer with short sentences.'),
28+
);
29+
30+
$chat->start($messages);
31+
$chat->submit(Message::ofUser('My name is Christopher.'));
32+
$message = $chat->submit(Message::ofUser('What is my name?'));
33+
34+
echo $message->content.PHP_EOL;

src/Chat.php

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PhpLlm\LlmChain;
6+
7+
use PhpLlm\LlmChain\Chat\MessageStoreInterface;
8+
use PhpLlm\LlmChain\Model\Message\AssistantMessage;
9+
use PhpLlm\LlmChain\Model\Message\Message;
10+
use PhpLlm\LlmChain\Model\Message\MessageBag;
11+
use PhpLlm\LlmChain\Model\Message\UserMessage;
12+
use PhpLlm\LlmChain\Model\Response\TextResponse;
13+
14+
final readonly class Chat implements ChatInterface
15+
{
16+
public function __construct(
17+
private ChainInterface $chain,
18+
private MessageStoreInterface $store,
19+
) {
20+
}
21+
22+
public function start(MessageBag $messages): void
23+
{
24+
$this->store->clear();
25+
$this->store->save($messages);
26+
}
27+
28+
public function submit(UserMessage $message): AssistantMessage
29+
{
30+
$messages = $this->store->load();
31+
32+
$messages->add($message);
33+
$response = $this->chain->call($messages);
34+
35+
assert($response instanceof TextResponse);
36+
37+
$assistantMessage = Message::ofAssistant($response->getContent());
38+
$messages->add($assistantMessage);
39+
40+
$this->store->save($messages);
41+
42+
return $assistantMessage;
43+
}
44+
}

src/Chat/MessageStore/CacheStore.php

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PhpLlm\LlmChain\Chat\MessageStore;
6+
7+
use PhpLlm\LlmChain\Chat\MessageStoreInterface;
8+
use PhpLlm\LlmChain\Model\Message\MessageBag;
9+
use Psr\Cache\CacheItemPoolInterface;
10+
11+
final readonly class CacheStore implements MessageStoreInterface
12+
{
13+
public function __construct(
14+
private CacheItemPoolInterface $cache,
15+
private string $cacheKey = 'messages',
16+
private int $ttl = 86400,
17+
) {
18+
}
19+
20+
public function save(MessageBag $messages): void
21+
{
22+
$item = $this->cache->getItem($this->cacheKey);
23+
24+
$item->set($messages);
25+
$item->expiresAfter($this->ttl);
26+
27+
$this->cache->save($item);
28+
}
29+
30+
public function load(): MessageBag
31+
{
32+
$item = $this->cache->getItem($this->cacheKey);
33+
34+
return $item->isHit() ? $item->get() : new MessageBag();
35+
}
36+
37+
public function clear(): void
38+
{
39+
$this->cache->deleteItem($this->cacheKey);
40+
}
41+
}
+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PhpLlm\LlmChain\Chat\MessageStore;
6+
7+
use PhpLlm\LlmChain\Chat\MessageStoreInterface;
8+
use PhpLlm\LlmChain\Model\Message\MessageBag;
9+
10+
final class InMemoryStore implements MessageStoreInterface
11+
{
12+
private MessageBag $messages;
13+
14+
public function save(MessageBag $messages): void
15+
{
16+
$this->messages = $messages;
17+
}
18+
19+
public function load(): MessageBag
20+
{
21+
return $this->messages ?? new MessageBag();
22+
}
23+
24+
public function clear(): void
25+
{
26+
$this->messages = new MessageBag();
27+
}
28+
}
+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PhpLlm\LlmChain\Chat\MessageStore;
6+
7+
use PhpLlm\LlmChain\Chat\MessageStoreInterface;
8+
use PhpLlm\LlmChain\Model\Message\MessageBag;
9+
use Symfony\Component\HttpFoundation\RequestStack;
10+
use Symfony\Component\HttpFoundation\Session\SessionInterface;
11+
12+
final readonly class SessionStore implements MessageStoreInterface
13+
{
14+
private SessionInterface $session;
15+
16+
public function __construct(
17+
RequestStack $requestStack,
18+
private string $sessionKey = 'messages',
19+
) {
20+
$this->session = $requestStack->getSession();
21+
}
22+
23+
public function save(MessageBag $messages): void
24+
{
25+
$this->session->set($this->sessionKey, $messages);
26+
}
27+
28+
public function load(): MessageBag
29+
{
30+
return $this->session->get($this->sessionKey, new MessageBag());
31+
}
32+
33+
public function clear(): void
34+
{
35+
$this->session->remove($this->sessionKey);
36+
}
37+
}

src/Chat/MessageStoreInterface.php

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PhpLlm\LlmChain\Chat;
6+
7+
use PhpLlm\LlmChain\Model\Message\MessageBag;
8+
9+
interface MessageStoreInterface
10+
{
11+
public function save(MessageBag $messages): void;
12+
13+
public function load(): MessageBag;
14+
15+
public function clear(): void;
16+
}

src/ChatInterface.php

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PhpLlm\LlmChain;
6+
7+
use PhpLlm\LlmChain\Model\Message\AssistantMessage;
8+
use PhpLlm\LlmChain\Model\Message\MessageBag;
9+
use PhpLlm\LlmChain\Model\Message\UserMessage;
10+
11+
interface ChatInterface
12+
{
13+
public function start(MessageBag $messages): void;
14+
15+
public function submit(UserMessage $message): AssistantMessage;
16+
}

0 commit comments

Comments
 (0)