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
38 changes: 38 additions & 0 deletions examples/chat/persistent-chat-cache.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* 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\Local\CacheStore;
use Symfony\AI\Chat\Chat;
use Symfony\AI\Platform\Bridge\OpenAi\PlatformFactory;
use Symfony\AI\Platform\Message\Message;
use Symfony\AI\Platform\Message\MessageBag;
use Symfony\Component\Cache\Adapter\ArrayAdapter;

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

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

$store = new CacheStore(new ArrayAdapter(), 'chat');
$store->setup();

$agent = new Agent($platform, 'gpt-4o-mini', logger: logger());
$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->content.\PHP_EOL;
47 changes: 47 additions & 0 deletions examples/chat/persistent-chat-session.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* 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\HttpFoundation\SessionStore;
use Symfony\AI\Chat\Chat;
use Symfony\AI\Platform\Bridge\OpenAi\PlatformFactory;
use Symfony\AI\Platform\Message\Message;
use Symfony\AI\Platform\Message\MessageBag;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage;

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

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

$request = Request::create('/');
$request->setSession(new Session(new MockArraySessionStorage()));

$requestStack = new RequestStack();
$requestStack->push($request);

$store = new SessionStore($requestStack, 'chat');
$store->setup();

$agent = new Agent($platform, 'gpt-4o-mini', logger: logger());
$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->content.\PHP_EOL;
1 change: 1 addition & 0 deletions examples/composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
"symfony/event-dispatcher": "^7.3|^8.0",
"symfony/filesystem": "^7.3|^8.0",
"symfony/finder": "^7.3|^8.0",
"symfony/http-foundation": "^7.3|^8.0",
"symfony/process": "^7.3|^8.0",
"symfony/var-dumper": "^7.3|^8.0"
},
Expand Down
2 changes: 1 addition & 1 deletion examples/openai/agent-as-tool.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@
use Symfony\AI\Agent\InputProcessor\SystemPromptInputProcessor;
use Symfony\AI\Agent\Toolbox\AgentProcessor;
use Symfony\AI\Agent\Toolbox\Tool\Agent as AgentTool;
use Symfony\AI\Agent\Toolbox\Toolbox;
use Symfony\AI\Agent\Toolbox\ToolFactory\ChainFactory;
use Symfony\AI\Agent\Toolbox\ToolFactory\MemoryToolFactory;
use Symfony\AI\Agent\Toolbox\ToolFactory\ReflectionToolFactory;
use Symfony\AI\Agent\Toolbox\Toolbox;
use Symfony\AI\Platform\Bridge\OpenAi\PlatformFactory;
use Symfony\AI\Platform\Message\Message;
use Symfony\AI\Platform\Message\MessageBag;
Expand Down
1 change: 1 addition & 0 deletions src/chat/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ Library for building chats with agents using messages. Built on Platform and Age
- **Chat** (`src/Chat.php`): Main orchestration class
- **ChatInterface**: Contract for implementations
- **MessageStoreInterface** High-level conversation storage interface
- **ManagedStoreInterface** High-level store management interface

### Key Features
- **Bridge** (`src/Bridge/`): Storage capacity for messages and conversations
Expand Down
1 change: 1 addition & 0 deletions src/chat/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ Library for building chats with agents using messages. Built on Platform and Age
- **Chat** (`src/Chat.php`): Main orchestration class
- **ChatInterface**: Contract for implementations
- **MessageStoreInterface** High-level conversation storage interface
- **ManagedStoreInterface** High-level store management interface

### Key Features
- **Bridge** (`src/Bridge/`): Storage capacity for messages and conversations
Expand Down
50 changes: 50 additions & 0 deletions src/chat/doc/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,53 @@ with a ``Symfony\AI\Agent\AgentInterface`` and a ``Symfony\AI\Chat\MessageStoreI

$chat->submit(Message::ofUser('Hello'));


Implementing a Bridge
---------------------

The main extension points of the Chat component is the ``Symfony\AI\Chat\MessageStoreInterface``, that defines the methods
for adding messages to the message store, and returning the messages from a store.

This leads to a store implementing two methods::

use Symfony\AI\Store\MessageStoreInterface;

class MyCustomStore implements MessageStoreInterface
{
public function save(MessageBag $messages): void
{
// Implementation to add a message bag to the store
}

public function load(): MessageBag
{
// Implementation to return a message bag from the store
}
}

Managing a store
----------------

Some store might requires to create table, indexes and so on before storing messages,
the ``Symfony\AI\Chat\ManagedStoreInterface`` defines the methods
to setup and drop the store.

This leads to a store implementing two methods::

use Symfony\AI\Store\ManagedStoreInterface;
use Symfony\AI\Store\MessageStoreInterface;

class MyCustomStore implements ManagedStoreInterface, MessageStoreInterface
{
# ...

public function setup(array $options = []): void
{
// Implementation to create the store
}

public function drop(): void
{
// Implementation to drop the store (and related messages)
}
}
11 changes: 9 additions & 2 deletions src/chat/src/Bridge/HttpFoundation/SessionStore.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
namespace Symfony\AI\Chat\Bridge\HttpFoundation;

use Symfony\AI\Agent\Exception\RuntimeException;
use Symfony\AI\Chat\ManagedStoreInterface;
use Symfony\AI\Chat\MessageStoreInterface;
use Symfony\AI\Platform\Message\MessageBag;
use Symfony\Component\HttpFoundation\RequestStack;
Expand All @@ -20,7 +21,7 @@
/**
* @author Christopher Hertel <mail@christopher-hertel.de>
*/
final readonly class SessionStore implements MessageStoreInterface
final readonly class SessionStore implements ManagedStoreInterface, MessageStoreInterface
{
private SessionInterface $session;

Expand All @@ -31,9 +32,15 @@ public function __construct(
if (!class_exists(RequestStack::class)) {
throw new RuntimeException('For using the SessionStore as message store, the symfony/http-foundation package is required. Try running "composer require symfony/http-foundation".');
}

$this->session = $requestStack->getSession();
}

public function setup(array $options = []): void
{
$this->session->set($this->sessionKey, new MessageBag());
}

public function save(MessageBag $messages): void
{
$this->session->set($this->sessionKey, $messages);
Expand All @@ -44,7 +51,7 @@ public function load(): MessageBag
return $this->session->get($this->sessionKey, new MessageBag());
}

public function clear(): void
public function drop(): void
{
$this->session->remove($this->sessionKey);
}
Expand Down
15 changes: 13 additions & 2 deletions src/chat/src/Bridge/Local/CacheStore.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,14 @@

use Psr\Cache\CacheItemPoolInterface;
use Symfony\AI\Agent\Exception\RuntimeException;
use Symfony\AI\Chat\ManagedStoreInterface;
use Symfony\AI\Chat\MessageStoreInterface;
use Symfony\AI\Platform\Message\MessageBag;

/**
* @author Christopher Hertel <mail@christopher-hertel.de>
*/
final readonly class CacheStore implements MessageStoreInterface
final readonly class CacheStore implements ManagedStoreInterface, MessageStoreInterface
{
public function __construct(
private CacheItemPoolInterface $cache,
Expand All @@ -31,6 +32,16 @@ public function __construct(
}
}

public function setup(array $options = []): void
{
$item = $this->cache->getItem($this->cacheKey);

$item->set(new MessageBag());
$item->expiresAfter($this->ttl);

$this->cache->save($item);
}

public function save(MessageBag $messages): void
{
$item = $this->cache->getItem($this->cacheKey);
Expand All @@ -48,7 +59,7 @@ public function load(): MessageBag
return $item->isHit() ? $item->get() : new MessageBag();
}

public function clear(): void
public function drop(): void
{
$this->cache->deleteItem($this->cacheKey);
}
Expand Down
10 changes: 8 additions & 2 deletions src/chat/src/Bridge/Local/InMemoryStore.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,22 @@

namespace Symfony\AI\Chat\Bridge\Local;

use Symfony\AI\Chat\ManagedStoreInterface;
use Symfony\AI\Chat\MessageStoreInterface;
use Symfony\AI\Platform\Message\MessageBag;

/**
* @author Christopher Hertel <mail@christopher-hertel.de>
*/
final class InMemoryStore implements MessageStoreInterface
final class InMemoryStore implements ManagedStoreInterface, MessageStoreInterface
{
private MessageBag $messages;

public function setup(array $options = []): void
{
$this->messages = new MessageBag();
}

public function save(MessageBag $messages): void
{
$this->messages = $messages;
Expand All @@ -31,7 +37,7 @@ public function load(): MessageBag
return $this->messages ?? new MessageBag();
}

public function clear(): void
public function drop(): void
{
$this->messages = new MessageBag();
}
Expand Down
4 changes: 2 additions & 2 deletions src/chat/src/Chat.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,13 @@
{
public function __construct(
private AgentInterface $agent,
private MessageStoreInterface $store,
private MessageStoreInterface&ManagedStoreInterface $store,
) {
}

public function initiate(MessageBag $messages): void
{
$this->store->clear();
$this->store->drop();
$this->store->save($messages);
}

Expand Down
22 changes: 22 additions & 0 deletions src/chat/src/ManagedStoreInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

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

namespace Symfony\AI\Chat;

interface ManagedStoreInterface
{
/**
* @param array<mixed> $options
*/
public function setup(array $options = []): void;

public function drop(): void;
}
2 changes: 0 additions & 2 deletions src/chat/src/MessageStoreInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,4 @@ interface MessageStoreInterface
public function save(MessageBag $messages): void;

public function load(): MessageBag;

public function clear(): void;
}
Loading