Skip to content

Commit 2f25a00

Browse files
authored
feat: chat + message store (#208)
1 parent 4d4a7fa commit 2f25a00

File tree

8 files changed

+224
-2
lines changed

8 files changed

+224
-2
lines changed

composer.json

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@
2525
"oskarstark/enum-helper": "^1.5",
2626
"phpdocumentor/reflection-docblock": "^5.4",
2727
"phpstan/phpdoc-parser": "^2.1",
28-
"psr/cache": "^3.0",
2928
"psr/log": "^3.0",
3029
"symfony/clock": "^6.4 || ^7.1",
3130
"symfony/http-client": "^6.4 || ^7.1",
@@ -53,12 +52,14 @@
5352
"probots-io/pinecone-php": "^1.0",
5453
"psr/http-factory-implementation": "*",
5554
"rector/rector": "^2.0",
55+
"symfony/cache": "^6.4 || ^7.1",
5656
"symfony/console": "^6.4 || ^7.1",
5757
"symfony/css-selector": "^6.4 || ^7.1",
5858
"symfony/dom-crawler": "^6.4 || ^7.1",
5959
"symfony/dotenv": "^6.4 || ^7.1",
6060
"symfony/event-dispatcher": "^6.4 || ^7.1",
6161
"symfony/finder": "^6.4 || ^7.1",
62+
"symfony/http-foundation": "^6.4 || ^7.1",
6263
"symfony/process": "^6.4 || ^7.1",
6364
"symfony/var-dumper": "^6.4 || ^7.1"
6465
},
@@ -71,7 +72,9 @@
7172
"mongodb/mongodb": "For using MongoDB Atlas as retrieval vector store.",
7273
"mrmysql/youtube-transcript": "For using the YouTube transcription tool.",
7374
"probots-io/pinecone-php": "For using the Pinecone as retrieval vector store.",
74-
"symfony/dom-crawler": "For using the Crawler tool."
75+
"symfony/dom-crawler": "For using the Crawler tool.",
76+
"symfony/http-foundation": "For using the SessionStore as message store.",
77+
"psr/cache": "For using the CacheStore as message store."
7578
},
7679
"config": {
7780
"allow-plugins": {

examples/persistent-chat.php

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<?php
2+
3+
use PhpLlm\LlmChain\Chain\Chain;
4+
use PhpLlm\LlmChain\Chain\Chat;
5+
use PhpLlm\LlmChain\Chain\Chat\MessageStore\InMemoryStore;
6+
use PhpLlm\LlmChain\Platform\Bridge\OpenAI\GPT;
7+
use PhpLlm\LlmChain\Platform\Bridge\OpenAI\PlatformFactory;
8+
use PhpLlm\LlmChain\Platform\Message\Message;
9+
use PhpLlm\LlmChain\Platform\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->initiate($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/Chain/Chat.php

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

src/Chain/ChatInterface.php

Lines changed: 16 additions & 0 deletions
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\Chain;
6+
7+
use PhpLlm\LlmChain\Platform\Message\AssistantMessage;
8+
use PhpLlm\LlmChain\Platform\Message\MessageBagInterface;
9+
use PhpLlm\LlmChain\Platform\Message\UserMessage;
10+
11+
interface ChatInterface
12+
{
13+
public function initiate(MessageBagInterface $messages): void;
14+
15+
public function submit(UserMessage $message): AssistantMessage;
16+
}

0 commit comments

Comments
 (0)