This repository was archived by the owner on Jul 16, 2025. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 23
Add basic setup for memory injections to system prompt #387
Merged
Merged
Changes from 1 commit
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| <?php | ||
|
|
||
| use PhpLlm\LlmChain\Chain\Chain; | ||
| use PhpLlm\LlmChain\Chain\InputProcessor\SystemPromptInputProcessor; | ||
| use PhpLlm\LlmChain\Chain\Memory\MemoryInputProcessor; | ||
| use PhpLlm\LlmChain\Chain\Memory\StaticMemoryProvider; | ||
| use PhpLlm\LlmChain\Platform\Bridge\OpenAI\GPT; | ||
| use PhpLlm\LlmChain\Platform\Bridge\OpenAI\PlatformFactory; | ||
| use PhpLlm\LlmChain\Platform\Message\Message; | ||
| use PhpLlm\LlmChain\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 Chain($platform, $model, [$systemPromptProcessor, $memoryProcessor]); | ||
| $messages = new MessageBag(Message::ofUser('What do we do today?')); | ||
| $response = $chain->call($messages); | ||
|
|
||
| echo $response->getContent().\PHP_EOL; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,71 @@ | ||
| <?php | ||
|
|
||
| use Doctrine\DBAL\DriverManager; | ||
| use Doctrine\DBAL\Tools\DsnParser; | ||
| use PhpLlm\LlmChain\Chain\Chain; | ||
| use PhpLlm\LlmChain\Chain\Memory\EmbeddingProvider; | ||
| use PhpLlm\LlmChain\Chain\Memory\MemoryInputProcessor; | ||
| use PhpLlm\LlmChain\Platform\Bridge\OpenAI\Embeddings; | ||
| use PhpLlm\LlmChain\Platform\Bridge\OpenAI\GPT; | ||
| use PhpLlm\LlmChain\Platform\Bridge\OpenAI\PlatformFactory; | ||
| use PhpLlm\LlmChain\Platform\Message\Message; | ||
| use PhpLlm\LlmChain\Platform\Message\MessageBag; | ||
| use PhpLlm\LlmChain\Store\Bridge\MariaDB\Store; | ||
| use PhpLlm\LlmChain\Store\Document\Metadata; | ||
| use PhpLlm\LlmChain\Store\Document\TextDocument; | ||
| use PhpLlm\LlmChain\Store\Document\Vectorizer; | ||
| use PhpLlm\LlmChain\Store\Indexer; | ||
| use Symfony\Component\Dotenv\Dotenv; | ||
| use Symfony\Component\Uid\Uuid; | ||
|
|
||
| require_once dirname(__DIR__, 2).'/vendor/autoload.php'; | ||
| (new Dotenv())->loadEnv(dirname(__DIR__, 2).'/.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 Chain($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; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,63 @@ | ||
| <?php | ||
|
|
||
| declare(strict_types=1); | ||
|
|
||
| namespace PhpLlm\LlmChain\Chain\Memory; | ||
|
|
||
| use PhpLlm\LlmChain\Chain\Input; | ||
| use PhpLlm\LlmChain\Platform\Message\Content\ContentInterface; | ||
| use PhpLlm\LlmChain\Platform\Message\Content\Text; | ||
| use PhpLlm\LlmChain\Platform\Message\MessageInterface; | ||
| use PhpLlm\LlmChain\Platform\Message\UserMessage; | ||
| use PhpLlm\LlmChain\Platform\Model; | ||
| use PhpLlm\LlmChain\Platform\PlatformInterface; | ||
| use PhpLlm\LlmChain\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): ?Memory | ||
| { | ||
| $messages = $input->messages->getMessages(); | ||
| /** @var MessageInterface|null $userMessage */ | ||
| $userMessage = $messages[array_key_last($messages)] ?? null; | ||
|
|
||
| if (!$userMessage instanceof UserMessage) { | ||
| return null; | ||
| } | ||
|
|
||
| $userMessageTextContent = array_filter( | ||
| $userMessage->content, | ||
| static fn (ContentInterface $content): bool => $content instanceof Text, | ||
| ); | ||
|
|
||
| if (0 === \count($userMessageTextContent)) { | ||
| return null; | ||
| } | ||
|
|
||
| $userMessageTextContent = array_shift($userMessageTextContent); | ||
| \assert($userMessageTextContent instanceof Text); | ||
|
|
||
| $vectors = $this->platform->request($this->model, $userMessageTextContent->text)->asVectors(); | ||
| $foundEmbeddingContent = $this->vectorStore->query($vectors[0]); | ||
| if (0 === \count($foundEmbeddingContent)) { | ||
| return null; | ||
| } | ||
|
|
||
| $content = '## Dynamic memories fitting user message'.\PHP_EOL.\PHP_EOL; | ||
| foreach ($foundEmbeddingContent as $document) { | ||
| $content .= json_encode($document->metadata); | ||
| } | ||
|
|
||
| return new Memory($content); | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| <?php | ||
|
|
||
| declare(strict_types=1); | ||
|
|
||
| namespace PhpLlm\LlmChain\Chain\Memory; | ||
|
|
||
| /** | ||
| * @author Denis Zunke <denis.zunke@gmail.com> | ||
| */ | ||
| final readonly class Memory | ||
| { | ||
| public function __construct(public string $content) | ||
| { | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,72 @@ | ||
| <?php | ||
|
|
||
| declare(strict_types=1); | ||
|
|
||
| namespace PhpLlm\LlmChain\Chain\Memory; | ||
|
|
||
| use PhpLlm\LlmChain\Chain\Input; | ||
| use PhpLlm\LlmChain\Chain\InputProcessorInterface; | ||
| use PhpLlm\LlmChain\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) { | ||
| $memoryMessage = $provider->loadMemory($input); | ||
|
|
||
| if (null === $memoryMessage) { | ||
| continue; | ||
| } | ||
|
|
||
| $memory .= \PHP_EOL.\PHP_EOL.$memoryMessage->content; | ||
| } | ||
|
|
||
| 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; | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| <?php | ||
|
|
||
| declare(strict_types=1); | ||
|
|
||
| namespace PhpLlm\LlmChain\Chain\Memory; | ||
|
|
||
| use PhpLlm\LlmChain\Chain\Input; | ||
|
|
||
| /** | ||
| * @author Denis Zunke <denis.zunke@gmail.com> | ||
| */ | ||
| interface MemoryProviderInterface | ||
| { | ||
| public function loadMemory(Input $input): ?Memory; | ||
OskarStark marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.