Skip to content

Commit 4ef6c4a

Browse files
committed
feature #117 [Agent] Add basic setup for memory injections to system prompt (DZunke)
This PR was merged into the main branch. Discussion ---------- [Agent] Add basic setup for memory injections to system prompt | Q | A | ------------- | --- | Bug fix? | no | New feature? | yes | Docs? | yes | Issues | | License | MIT See original contribution at php-llm/llm-chain#387 > This PR introduces a flexible memory system that allows the LLM to recall contextual information that are permantent for the conversation. In difference to tools it can be always utilized, when use_memory option is not disabled. It would be possible to fetch memory by a tool with a system instruction like always call the tool foo_bar but, for me, this feels like a bad design to always force the model to do a tool call without further need. > > Currently i have added just two memory providers to show what my idea is. I could also think about a write layer to fill a memory, together with a read layer, this could, for example, be working good with a graph database and tools for memory handling. But these are ideas for the future. > > So for this first throw i hope you get what i was thinking about to reach. I decided to inject the memory to the system prompt, when it is available, instead of adding a second system prompt to the message bag. But this was just a 50/50 thinking. I tried both seem to be working equally, at least for open ai. I am open to change it back again. > > What do you think? Commits ------- d9b13f9 Add basic setup for memory injections to system prompt
2 parents d4a9c5a + b307c5d commit 4ef6c4a

File tree

9 files changed

+770
-0
lines changed

9 files changed

+770
-0
lines changed

doc/index.rst

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -465,6 +465,84 @@ AgentAwareTrait::
465465
}
466466
}
467467

468+
Agent Memory Management
469+
-----------------------
470+
471+
Symfony AI supports adding contextual memory to agent conversations, allowing the model to recall past interactions or
472+
relevant information from different sources. Memory providers inject information into the system prompt, providing the
473+
model with context without changing your application logic.
474+
475+
Using Memory
476+
~~~~~~~~~~~~
477+
478+
Memory integration is handled through the ``MemoryInputProcessor`` and one or more ``MemoryProviderInterface`` implementations::
479+
480+
use Symfony\AI\Agent\Agent;
481+
use Symfony\AI\Agent\Memory\MemoryInputProcessor;
482+
use Symfony\AI\Agent\Memory\StaticMemoryProvider;
483+
use Symfony\AI\Platform\Message\Message;
484+
use Symfony\AI\Platform\Message\MessageBag;
485+
486+
// Platform & LLM instantiation
487+
488+
$personalFacts = new StaticMemoryProvider(
489+
'My name is Wilhelm Tell',
490+
'I wish to be a swiss national hero',
491+
'I am struggling with hitting apples but want to be professional with the bow and arrow',
492+
);
493+
$memoryProcessor = new MemoryInputProcessor($personalFacts);
494+
495+
$agent = new Agent($platform, $model, [$memoryProcessor]);
496+
$messages = new MessageBag(Message::ofUser('What do we do today?'));
497+
$response = $agent->call($messages);
498+
499+
Memory Providers
500+
~~~~~~~~~~~~~~~~
501+
502+
The library includes several memory provider implementations that are ready to use out of the box.
503+
504+
**Static Memory**
505+
506+
Static memory provides fixed information to the agent, such as user preferences, application context, or any other
507+
information that should be consistently available without being directly added to the system prompt::
508+
509+
use Symfony\AI\Agent\Memory\StaticMemoryProvider;
510+
511+
$staticMemory = new StaticMemoryProvider(
512+
'The user is allergic to nuts',
513+
'The user prefers brief explanations',
514+
);
515+
516+
**Embedding Provider**
517+
518+
This provider leverages vector storage to inject relevant knowledge based on the user's current message. It can be used
519+
for retrieving general knowledge from a store or recalling past conversation pieces that might be relevant::
520+
521+
use Symfony\AI\Agent\Memory\EmbeddingProvider;
522+
523+
$embeddingsMemory = new EmbeddingProvider(
524+
$platform,
525+
$embeddings, // Your embeddings model for vectorizing user messages
526+
$store // Your vector store to query for relevant context
527+
);
528+
529+
Dynamic Memory Control
530+
~~~~~~~~~~~~~~~~~~~~~~
531+
532+
Memory is globally configured for the agent, but you can selectively disable it for specific calls when needed. This is
533+
useful when certain interactions shouldn't be influenced by the memory context::
534+
535+
$response = $agent->call($messages, [
536+
'use_memory' => false, // Disable memory for this specific call
537+
]);
538+
539+
540+
**Code Examples**
541+
542+
* `Chat with static memory`_
543+
* `Chat with embedding search memory`_
544+
545+
468546
.. _`Platform Component`: https://github.com/symfony/ai-platform
469547
.. _`Brave Tool`: https://github.com/symfony/ai/blob/main/examples/toolbox/brave.php
470548
.. _`Clock Tool`: https://github.com/symfony/ai/blob/main/examples/toolbox/clock.php
@@ -479,3 +557,5 @@ AgentAwareTrait::
479557
.. _`RAG with Pinecone`: https://github.com/symfony/ai/blob/main/examples/store/pinecone-similarity-search.php
480558
.. _`Structured Output with PHP class`: https://github.com/symfony/ai/blob/main/examples/openai/structured-output-math.php
481559
.. _`Structured Output with array`: https://github.com/symfony/ai/blob/main/examples/openai/structured-output-clock.php
560+
.. _`Chat with static memory`: https://github.com/symfony/ai/blob/main/examples/misc/chat-with-memory.php
561+
.. _`Chat with embedding search memory`: https://github.com/symfony/ai/blob/main/examples/store/mariadb-chat-memory.php

src/Memory/EmbeddingProvider.php

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
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\Memory;
13+
14+
use Symfony\AI\Agent\Input;
15+
use Symfony\AI\Platform\Message\Content\ContentInterface;
16+
use Symfony\AI\Platform\Message\Content\Text;
17+
use Symfony\AI\Platform\Message\MessageInterface;
18+
use Symfony\AI\Platform\Message\UserMessage;
19+
use Symfony\AI\Platform\Model;
20+
use Symfony\AI\Platform\PlatformInterface;
21+
use Symfony\AI\Store\VectorStoreInterface;
22+
23+
/**
24+
* @author Denis Zunke <[email protected]>
25+
*/
26+
final readonly class EmbeddingProvider implements MemoryProviderInterface
27+
{
28+
public function __construct(
29+
private PlatformInterface $platform,
30+
private Model $model,
31+
private VectorStoreInterface $vectorStore,
32+
) {
33+
}
34+
35+
public function loadMemory(Input $input): array
36+
{
37+
$messages = $input->messages->getMessages();
38+
/** @var MessageInterface|null $userMessage */
39+
$userMessage = $messages[array_key_last($messages)] ?? null;
40+
41+
if (!$userMessage instanceof UserMessage) {
42+
return [];
43+
}
44+
45+
$userMessageTextContent = array_filter(
46+
$userMessage->content,
47+
static fn (ContentInterface $content): bool => $content instanceof Text,
48+
);
49+
50+
if (0 === \count($userMessageTextContent)) {
51+
return [];
52+
}
53+
54+
$userMessageTextContent = array_shift($userMessageTextContent);
55+
56+
$vectors = $this->platform->request($this->model, $userMessageTextContent->text)->asVectors();
57+
$foundEmbeddingContent = $this->vectorStore->query($vectors[0]);
58+
if (0 === \count($foundEmbeddingContent)) {
59+
return [];
60+
}
61+
62+
$content = '## Dynamic memories fitting user message'.\PHP_EOL.\PHP_EOL;
63+
foreach ($foundEmbeddingContent as $document) {
64+
$content .= json_encode($document->metadata);
65+
}
66+
67+
return [new Memory($content)];
68+
}
69+
}

src/Memory/Memory.php

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
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\Memory;
13+
14+
/**
15+
* @author Denis Zunke <[email protected]>
16+
*/
17+
final readonly class Memory
18+
{
19+
public function __construct(public string $content)
20+
{
21+
}
22+
}
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
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\Memory;
13+
14+
use Symfony\AI\Agent\Input;
15+
use Symfony\AI\Agent\InputProcessorInterface;
16+
use Symfony\AI\Platform\Message\Message;
17+
18+
/**
19+
* @author Denis Zunke <[email protected]>
20+
*/
21+
final readonly class MemoryInputProcessor implements InputProcessorInterface
22+
{
23+
private const MEMORY_PROMPT_MESSAGE = <<<MARKDOWN
24+
# Conversation Memory
25+
This is the memory I have found for this conversation. The memory has more weight to answer user input,
26+
so try to answer utilizing the memory as much as possible. Your answer must be changed to fit the given
27+
memory. If the memory is irrelevant, ignore it. Do not reply to the this section of the prompt and do not
28+
reference it as this is just for your reference.
29+
MARKDOWN;
30+
31+
/**
32+
* @var MemoryProviderInterface[]
33+
*/
34+
private array $memoryProviders;
35+
36+
public function __construct(
37+
MemoryProviderInterface ...$memoryProviders,
38+
) {
39+
$this->memoryProviders = $memoryProviders;
40+
}
41+
42+
public function processInput(Input $input): void
43+
{
44+
$options = $input->getOptions();
45+
$useMemory = $options['use_memory'] ?? true;
46+
unset($options['use_memory']);
47+
$input->setOptions($options);
48+
49+
if (false === $useMemory || 0 === \count($this->memoryProviders)) {
50+
return;
51+
}
52+
53+
$memory = '';
54+
foreach ($this->memoryProviders as $provider) {
55+
$memoryMessages = $provider->loadMemory($input);
56+
57+
if (0 === \count($memoryMessages)) {
58+
continue;
59+
}
60+
61+
$memory .= \PHP_EOL.\PHP_EOL;
62+
$memory .= implode(
63+
\PHP_EOL,
64+
array_map(static fn (Memory $memory): string => $memory->content, $memoryMessages),
65+
);
66+
}
67+
68+
if ('' === $memory) {
69+
return;
70+
}
71+
72+
$systemMessage = $input->messages->getSystemMessage()->content ?? '';
73+
if ('' !== $systemMessage) {
74+
$systemMessage .= \PHP_EOL.\PHP_EOL;
75+
}
76+
77+
$messages = $input->messages
78+
->withoutSystemMessage()
79+
->prepend(Message::forSystem($systemMessage.self::MEMORY_PROMPT_MESSAGE.$memory));
80+
81+
$input->messages = $messages;
82+
}
83+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
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\Memory;
13+
14+
use Symfony\AI\Agent\Input;
15+
16+
/**
17+
* @author Denis Zunke <[email protected]>
18+
*/
19+
interface MemoryProviderInterface
20+
{
21+
/**
22+
* @return list<Memory>
23+
*/
24+
public function loadMemory(Input $input): array;
25+
}
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\Memory;
13+
14+
use Symfony\AI\Agent\Input;
15+
16+
/**
17+
* @author Denis Zunke <[email protected]>
18+
*/
19+
final readonly class StaticMemoryProvider implements MemoryProviderInterface
20+
{
21+
/**
22+
* @var array<string>
23+
*/
24+
private array $memory;
25+
26+
public function __construct(string ...$memory)
27+
{
28+
$this->memory = $memory;
29+
}
30+
31+
public function loadMemory(Input $input): array
32+
{
33+
if (0 === \count($this->memory)) {
34+
return [];
35+
}
36+
37+
$content = '## Static Memory'.\PHP_EOL;
38+
39+
foreach ($this->memory as $memory) {
40+
$content .= \PHP_EOL.'- '.$memory;
41+
}
42+
43+
return [new Memory($content)];
44+
}
45+
}

0 commit comments

Comments
 (0)