Skip to content

[Agent] Add basic setup for memory injections to system prompt #117

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jul 16, 2025
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
46 changes: 46 additions & 0 deletions examples/misc/chat-with-memory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<?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\Agent\InputProcessor\SystemPromptInputProcessor;
use Symfony\AI\Agent\Memory\MemoryInputProcessor;
use Symfony\AI\Agent\Memory\StaticMemoryProvider;
use Symfony\AI\Platform\Bridge\OpenAI\GPT;
use Symfony\AI\Platform\Bridge\OpenAI\PlatformFactory;
use Symfony\AI\Platform\Message\Message;
use Symfony\AI\Platform\Message\MessageBag;
use Symfony\Component\Dotenv\Dotenv;

require_once dirname(__DIR__).'/vendor/autoload.php';
(new Dotenv())->loadEnv(dirname(__DIR__).'/.env');

if (!$_ENV['OPENAI_API_KEY']) {
echo 'Please set the OPENAI_API_KEY environment variable.'.\PHP_EOL;
exit(1);
}

$platform = PlatformFactory::create($_ENV['OPENAI_API_KEY']);
$model = new GPT(GPT::GPT_4O_MINI);

$systemPromptProcessor = new SystemPromptInputProcessor('You are a professional trainer with short, personalized advices and a motivating claim.');

$personalFacts = new StaticMemoryProvider(
'My name is Wilhelm Tell',
'I wish to be a swiss national hero',
'I am struggling with hitting apples but want to be professional with the bow and arrow',
);
$memoryProcessor = new MemoryInputProcessor($personalFacts);

$chain = new Agent($platform, $model, [$systemPromptProcessor, $memoryProcessor]);
$messages = new MessageBag(Message::ofUser('What do we do today?'));
$response = $chain->call($messages);

echo $response->getContent().\PHP_EOL;
80 changes: 80 additions & 0 deletions examples/store/mariadb-chat-memory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
<?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 Doctrine\DBAL\DriverManager;
use Doctrine\DBAL\Tools\DsnParser;
use Symfony\AI\Agent\Agent;
use Symfony\AI\Agent\Memory\EmbeddingProvider;
use Symfony\AI\Agent\Memory\MemoryInputProcessor;
use Symfony\AI\Platform\Bridge\OpenAI\Embeddings;
use Symfony\AI\Platform\Bridge\OpenAI\GPT;
use Symfony\AI\Platform\Bridge\OpenAI\PlatformFactory;
use Symfony\AI\Platform\Message\Message;
use Symfony\AI\Platform\Message\MessageBag;
use Symfony\AI\Store\Bridge\MariaDB\Store;
use Symfony\AI\Store\Document\Metadata;
use Symfony\AI\Store\Document\TextDocument;
use Symfony\AI\Store\Document\Vectorizer;
use Symfony\AI\Store\Indexer;
use Symfony\Component\Dotenv\Dotenv;
use Symfony\Component\Uid\Uuid;

require_once dirname(__DIR__).'/vendor/autoload.php';
(new Dotenv())->loadEnv(dirname(__DIR__).'/.env');

if (!$_ENV['OPENAI_API_KEY'] || !$_ENV['MARIADB_URI']) {
echo 'Please set OPENAI_API_KEY and MARIADB_URI environment variables.'.\PHP_EOL;
exit(1);
}

// initialize the store
$store = Store::fromDbal(
connection: DriverManager::getConnection((new DsnParser())->parse($_ENV['MARIADB_URI'])),
tableName: 'my_table',
indexName: 'my_index',
vectorFieldName: 'embedding',
);

// our data
$pastConversationPieces = [
['role' => 'user', 'timestamp' => '2024-12-14 12:00:00', 'content' => 'My friends John and Emma are friends, too, are there hints why?'],
['role' => 'assistant', 'timestamp' => '2024-12-14 12:00:01', 'content' => 'Based on the found documents i would expect they are friends since childhood, this can give a deep bound!'],
['role' => 'user', 'timestamp' => '2024-12-14 12:02:02', 'content' => 'Yeah but how does this bound? I know John was once there with a wound dressing as Emma fell, could this be a hint?'],
['role' => 'assistant', 'timestamp' => '2024-12-14 12:02:03', 'content' => 'Yes, this could be a hint that they have been through difficult times together, which can strengthen their bond.'],
];

// create embeddings and documents
foreach ($pastConversationPieces as $i => $message) {
$documents[] = new TextDocument(
id: Uuid::v4(),
content: 'Role: '.$message['role'].\PHP_EOL.'Timestamp: '.$message['timestamp'].\PHP_EOL.'Message: '.$message['content'],
metadata: new Metadata($message),
);
}

// initialize the table
$store->initialize();

// create embeddings for documents as preparation of the chain memory
$platform = PlatformFactory::create($_ENV['OPENAI_API_KEY']);
$vectorizer = new Vectorizer($platform, $embeddings = new Embeddings());
$indexer = new Indexer($vectorizer, $store);
$indexer->index($documents);

// Execute a chat call that is utilizing the memory
$embeddingsMemory = new EmbeddingProvider($platform, $embeddings, $store);
$memoryProcessor = new MemoryInputProcessor($embeddingsMemory);

$chain = new Agent($platform, new GPT(GPT::GPT_4O_MINI), [$memoryProcessor]);
$messages = new MessageBag(Message::ofUser('Have we discussed about my friend John in the past? If yes, what did we talk about?'));
$response = $chain->call($messages);

echo $response->getContent().\PHP_EOL;
80 changes: 80 additions & 0 deletions src/agent/doc/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -465,6 +465,84 @@ AgentAwareTrait::
}
}

Agent Memory Management
-----------------------

Symfony AI supports adding contextual memory to agent conversations, allowing the model to recall past interactions or
relevant information from different sources. Memory providers inject information into the system prompt, providing the
model with context without changing your application logic.

Using Memory
~~~~~~~~~~~~

Memory integration is handled through the ``MemoryInputProcessor`` and one or more ``MemoryProviderInterface`` implementations::

use Symfony\AI\Agent\Agent;
use Symfony\AI\Agent\Memory\MemoryInputProcessor;
use Symfony\AI\Agent\Memory\StaticMemoryProvider;
use Symfony\AI\Platform\Message\Message;
use Symfony\AI\Platform\Message\MessageBag;

// Platform & LLM instantiation

$personalFacts = new StaticMemoryProvider(
'My name is Wilhelm Tell',
'I wish to be a swiss national hero',
'I am struggling with hitting apples but want to be professional with the bow and arrow',
);
$memoryProcessor = new MemoryInputProcessor($personalFacts);

$agent = new Agent($platform, $model, [$memoryProcessor]);
$messages = new MessageBag(Message::ofUser('What do we do today?'));
$response = $agent->call($messages);

Memory Providers
~~~~~~~~~~~~~~~~

The library includes several memory provider implementations that are ready to use out of the box.

**Static Memory**

Static memory provides fixed information to the agent, such as user preferences, application context, or any other
information that should be consistently available without being directly added to the system prompt::

use Symfony\AI\Agent\Memory\StaticMemoryProvider;

$staticMemory = new StaticMemoryProvider(
'The user is allergic to nuts',
'The user prefers brief explanations',
);

**Embedding Provider**

This provider leverages vector storage to inject relevant knowledge based on the user's current message. It can be used
for retrieving general knowledge from a store or recalling past conversation pieces that might be relevant::

use Symfony\AI\Agent\Memory\EmbeddingProvider;

$embeddingsMemory = new EmbeddingProvider(
$platform,
$embeddings, // Your embeddings model for vectorizing user messages
$store // Your vector store to query for relevant context
);

Dynamic Memory Control
~~~~~~~~~~~~~~~~~~~~~~

Memory is globally configured for the agent, but you can selectively disable it for specific calls when needed. This is
useful when certain interactions shouldn't be influenced by the memory context::

$response = $agent->call($messages, [
'use_memory' => false, // Disable memory for this specific call
]);


**Code Examples**

* `Chat with static memory`_
* `Chat with embedding search memory`_


.. _`Platform Component`: https://github.com/symfony/ai-platform
.. _`Brave Tool`: https://github.com/symfony/ai/blob/main/examples/toolbox/brave.php
.. _`Clock Tool`: https://github.com/symfony/ai/blob/main/examples/toolbox/clock.php
Expand All @@ -479,3 +557,5 @@ AgentAwareTrait::
.. _`RAG with Pinecone`: https://github.com/symfony/ai/blob/main/examples/store/pinecone-similarity-search.php
.. _`Structured Output with PHP class`: https://github.com/symfony/ai/blob/main/examples/openai/structured-output-math.php
.. _`Structured Output with array`: https://github.com/symfony/ai/blob/main/examples/openai/structured-output-clock.php
.. _`Chat with static memory`: https://github.com/symfony/ai/blob/main/examples/misc/chat-with-memory.php
.. _`Chat with embedding search memory`: https://github.com/symfony/ai/blob/main/examples/store/mariadb-chat-memory.php
69 changes: 69 additions & 0 deletions src/agent/src/Memory/EmbeddingProvider.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<?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\Agent\Memory;

use Symfony\AI\Agent\Input;
use Symfony\AI\Platform\Message\Content\ContentInterface;
use Symfony\AI\Platform\Message\Content\Text;
use Symfony\AI\Platform\Message\MessageInterface;
use Symfony\AI\Platform\Message\UserMessage;
use Symfony\AI\Platform\Model;
use Symfony\AI\Platform\PlatformInterface;
use Symfony\AI\Store\VectorStoreInterface;

/**
* @author Denis Zunke <denis.zunke@gmail.com>
*/
final readonly class EmbeddingProvider implements MemoryProviderInterface
{
public function __construct(
private PlatformInterface $platform,
private Model $model,
private VectorStoreInterface $vectorStore,
) {
}

public function loadMemory(Input $input): array
{
$messages = $input->messages->getMessages();
/** @var MessageInterface|null $userMessage */
$userMessage = $messages[array_key_last($messages)] ?? null;

if (!$userMessage instanceof UserMessage) {
return [];
}

$userMessageTextContent = array_filter(
$userMessage->content,
static fn (ContentInterface $content): bool => $content instanceof Text,
);

if (0 === \count($userMessageTextContent)) {
return [];
}

$userMessageTextContent = array_shift($userMessageTextContent);

$vectors = $this->platform->request($this->model, $userMessageTextContent->text)->asVectors();
$foundEmbeddingContent = $this->vectorStore->query($vectors[0]);
if (0 === \count($foundEmbeddingContent)) {
return [];
}

$content = '## Dynamic memories fitting user message'.\PHP_EOL.\PHP_EOL;
foreach ($foundEmbeddingContent as $document) {
$content .= json_encode($document->metadata);
}

return [new Memory($content)];
}
}
22 changes: 22 additions & 0 deletions src/agent/src/Memory/Memory.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\Agent\Memory;

/**
* @author Denis Zunke <denis.zunke@gmail.com>
*/
final readonly class Memory
{
public function __construct(public string $content)
{
}
}
83 changes: 83 additions & 0 deletions src/agent/src/Memory/MemoryInputProcessor.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
<?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\Agent\Memory;

use Symfony\AI\Agent\Input;
use Symfony\AI\Agent\InputProcessorInterface;
use Symfony\AI\Platform\Message\Message;

/**
* @author Denis Zunke <denis.zunke@gmail.com>
*/
final readonly class MemoryInputProcessor implements InputProcessorInterface
{
private const MEMORY_PROMPT_MESSAGE = <<<MARKDOWN
# Conversation Memory
This is the memory I have found for this conversation. The memory has more weight to answer user input,
so try to answer utilizing the memory as much as possible. Your answer must be changed to fit the given
memory. If the memory is irrelevant, ignore it. Do not reply to the this section of the prompt and do not
reference it as this is just for your reference.
MARKDOWN;

/**
* @var MemoryProviderInterface[]
*/
private array $memoryProviders;

public function __construct(
MemoryProviderInterface ...$memoryProviders,
) {
$this->memoryProviders = $memoryProviders;
}

public function processInput(Input $input): void
{
$options = $input->getOptions();
$useMemory = $options['use_memory'] ?? true;
unset($options['use_memory']);
$input->setOptions($options);

if (false === $useMemory || 0 === \count($this->memoryProviders)) {
return;
}

$memory = '';
foreach ($this->memoryProviders as $provider) {
$memoryMessages = $provider->loadMemory($input);

if (0 === \count($memoryMessages)) {
continue;
}

$memory .= \PHP_EOL.\PHP_EOL;
$memory .= implode(
\PHP_EOL,
array_map(static fn (Memory $memory): string => $memory->content, $memoryMessages),
);
}

if ('' === $memory) {
return;
}

$systemMessage = $input->messages->getSystemMessage()->content ?? '';
if ('' !== $systemMessage) {
$systemMessage .= \PHP_EOL.\PHP_EOL;
}

$messages = $input->messages
->withoutSystemMessage()
->prepend(Message::forSystem($systemMessage.self::MEMORY_PROMPT_MESSAGE.$memory));

$input->messages = $messages;
}
}
Loading