Skip to content

Commit aa8064c

Browse files
authored
Merge pull request #3 from llm-agents-php/feature/dynamic-memory-tool
Adds dynamic memory tool
2 parents 2159489 + ee65f23 commit aa8064c

File tree

7 files changed

+183
-29
lines changed

7 files changed

+183
-29
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace App\Agents\DynamicMemoryTool;
6+
7+
use Spiral\JsonSchemaGenerator\Attribute\Field;
8+
9+
final class DynamicMemoryInput
10+
{
11+
public function __construct(
12+
#[Field(title: 'Session ID', description: 'The unique identifier for the current session')]
13+
public string $sessionId,
14+
#[Field(title: 'User preference', description: 'The user preference to add or update')]
15+
public string $preference,
16+
) {}
17+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace App\Agents\DynamicMemoryTool;
6+
7+
use App\Application\Entity\Uuid;
8+
use LLM\Agents\Solution\SolutionMetadata;
9+
use Psr\SimpleCache\CacheInterface;
10+
use Spiral\Core\Attribute\Singleton;
11+
12+
#[Singleton]
13+
final readonly class DynamicMemoryService
14+
{
15+
public function __construct(
16+
private CacheInterface $cache,
17+
) {}
18+
19+
public function addMemory(Uuid $sessionUuid, SolutionMetadata $metadata): void
20+
{
21+
$memories = $this->getCurrentMemory($sessionUuid);
22+
23+
$memories->addMemory($metadata);
24+
25+
$this->cache->set($this->getKey($sessionUuid), $memories);
26+
}
27+
28+
public function updateMemory(Uuid $sessionUuid, SolutionMetadata $metadata): void
29+
{
30+
$memories = $this->getCurrentMemory($sessionUuid);
31+
$memories->updateMemory($metadata);
32+
33+
$this->cache->set($this->getKey($sessionUuid), $memories);
34+
}
35+
36+
public function getCurrentMemory(Uuid $sessionUuid): Memories
37+
{
38+
return $this->cache->get($this->getKey($sessionUuid)) ?? new Memories(
39+
\Ramsey\Uuid\Uuid::uuid4(),
40+
);
41+
}
42+
43+
private function getKey(Uuid $sessionUuid): string
44+
{
45+
return 'user_memory';
46+
}
47+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace App\Agents\DynamicMemoryTool;
6+
7+
use App\Application\Entity\Uuid;
8+
use App\Domain\Tool\PhpTool;
9+
use LLM\Agents\Solution\MetadataType;
10+
use LLM\Agents\Solution\SolutionMetadata;
11+
12+
final class DynamicMemoryTool extends PhpTool
13+
{
14+
public const NAME = 'dynamic_memory';
15+
16+
public function __construct(
17+
private readonly DynamicMemoryService $memoryService,
18+
) {
19+
parent::__construct(
20+
name: self::NAME,
21+
inputSchema: DynamicMemoryInput::class,
22+
description: 'Use this tool to add or update a important memory about the user. This memory will be used in the future to provide a better experience.',
23+
);
24+
}
25+
26+
public function execute(object $input): string
27+
{
28+
$metadata = new SolutionMetadata(
29+
type: MetadataType::Memory,
30+
key: 'user_memory',
31+
content: $input->preference,
32+
);
33+
34+
$sessionUuid = Uuid::fromString($input->sessionId);
35+
$this->memoryService->addMemory($sessionUuid, $metadata);
36+
37+
return 'Memory updated';
38+
}
39+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace App\Agents\DynamicMemoryTool;
6+
7+
use LLM\Agents\Solution\SolutionMetadata;
8+
use Ramsey\Uuid\UuidInterface;
9+
use Traversable;
10+
11+
final class Memories implements \IteratorAggregate
12+
{
13+
public function __construct(
14+
public readonly UuidInterface $uuid,
15+
/** @var array<SolutionMetadata> */
16+
public array $memories = [],
17+
) {}
18+
19+
public function addMemory(SolutionMetadata $metadata): void
20+
{
21+
$this->memories[] = $metadata;
22+
}
23+
24+
public function getIterator(): Traversable
25+
{
26+
yield from $this->memories;
27+
}
28+
}

app/src/Agents/SmartHomeControl/SmartHomeControlAgent.php

+12-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
namespace App\Agents\SmartHomeControl;
66

7+
use App\Agents\DynamicMemoryTool\DynamicMemoryTool;
78
use LLM\Agents\Agent\Agent;
89
use LLM\Agents\Agent\AgentAggregate;
910
use LLM\Agents\OpenAI\Client\OpenAIModel;
@@ -23,7 +24,11 @@ public static function create(): self
2324
key: self::NAME,
2425
name: 'Smart Home Control Assistant',
2526
description: 'This agent manages and controls various smart home devices across multiple rooms, including lights, fireplaces, and TVs.',
26-
instruction: 'You are a Smart Home Control Assistant. Your primary goal is to help users manage their smart home devices efficiently.',
27+
instruction: <<<'INSTRUCTION'
28+
You are a Smart Home Control Assistant.
29+
Your primary goal is to help users manage their smart home devices efficiently.
30+
INSTRUCTION
31+
,
2732
);
2833

2934
$aggregate = new self($agent);
@@ -49,6 +54,11 @@ public static function create(): self
4954
key: 'home_name',
5055
content: 'We are currently in the "Home" home.',
5156
),
57+
new SolutionMetadata(
58+
type: MetadataType::Memory,
59+
key: 'store_important_memory',
60+
content: 'Store important information in memory for future reference. For example if user tells that he likes some specific setting, store it in memory.',
61+
),
5262

5363
new SolutionMetadata(
5464
type: MetadataType::Configuration,
@@ -95,6 +105,7 @@ public static function create(): self
95105
$aggregate->addAssociation(new ToolLink(name: GetDeviceDetailsTool::NAME));
96106
$aggregate->addAssociation(new ToolLink(name: ControlDeviceTool::NAME));
97107
$aggregate->addAssociation(new ToolLink(name: GetRoomListTool::NAME));
108+
$aggregate->addAssociation(new ToolLink(name: DynamicMemoryTool::NAME));
98109

99110
return $aggregate;
100111
}

app/src/Domain/Chat/SimpleChatService.php

+17-3
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
namespace App\Domain\Chat;
66

7+
use App\Agents\DynamicMemoryTool\DynamicMemoryService;
78
use App\Application\Entity\Uuid;
89
use App\Domain\Agent\AgentExecutorBuilder;
910
use App\Domain\Chat\Exception\ChatNotFoundException;
@@ -15,6 +16,7 @@
1516
use LLM\Agents\LLM\Response\ChatResponse;
1617
use LLM\Agents\LLM\Response\ToolCall;
1718
use LLM\Agents\LLM\Response\ToolCalledResponse;
19+
use LLM\Agents\Solution\SolutionMetadata;
1820
use LLM\Agents\Tool\ToolExecutor;
1921
use Psr\EventDispatcher\EventDispatcherInterface;
2022

@@ -26,6 +28,7 @@ public function __construct(
2628
private EntityManagerInterface $em,
2729
private AgentRepositoryInterface $agents,
2830
private ToolExecutor $toolExecutor,
31+
private DynamicMemoryService $memoryService,
2932
private ?EventDispatcherInterface $eventDispatcher = null,
3033
) {}
3134

@@ -164,11 +167,22 @@ private function buildAgent(Session $session, ?Prompt $prompt): AgentExecutorBui
164167
'session_uuid' => (string) $session->uuid,
165168
]);
166169

167-
if ($prompt !== null) {
168-
$agent = $agent->withPrompt($prompt);
170+
if ($prompt === null) {
171+
return $agent;
169172
}
170173

171-
return $agent;
174+
$memories = $this->memoryService->getCurrentMemory($session->uuid);
175+
176+
177+
return $agent->withPrompt($prompt->withValues([
178+
'dynamic_memory' => \implode(
179+
"\n",
180+
\array_map(
181+
fn(SolutionMetadata $memory) => $memory->content,
182+
$memories->memories,
183+
),
184+
),
185+
]));
172186
}
173187

174188
private function callTool(Session $session, ToolCall $tool): ToolCallResultMessage

app/src/Domain/MLQ/AgentPromptGenerator.php

+23-25
Original file line numberDiff line numberDiff line change
@@ -39,21 +39,11 @@ public function generate(
3939
- think before responding to user
4040
PROMPT,
4141
),
42-
values: ['prompt' => $agent->getInstruction()],
4342
),
4443

4544
// Agent memory
4645
MessagePrompt::system(
47-
prompt: 'Instructions about your experiences, follow them: {memory}',
48-
values: [
49-
'memory' => \implode(
50-
"\n",
51-
\array_map(
52-
static fn(SolutionMetadata $metadata) => $metadata->content,
53-
$agent->getMemory(),
54-
),
55-
),
56-
],
46+
prompt: 'Instructions about your experiences, follow them: {memory}. And also {dynamic_memory}',
5747
),
5848
];
5949

@@ -74,26 +64,12 @@ public function generate(
7464
Always follow rules:
7565
- Don't make up the agent key. Use only the ones from the provided list.
7666
PROMPT,
77-
values: [
78-
'associated_agents' => \implode(
79-
PHP_EOL,
80-
\array_map(
81-
static fn(array $agent): string => \json_encode([
82-
'key' => $agent['agent']->getKey(),
83-
'description' => $agent['agent']->getDescription(),
84-
'output_schema' => $agent['output_schema'],
85-
]),
86-
$associatedAgents,
87-
),
88-
),
89-
],
9067
);
9168
}
9269

9370
if ($sessionContext !== null) {
9471
$messages[] = MessagePrompt::system(
9572
prompt: 'Session context: {active_context}',
96-
values: ['active_context' => \json_encode($sessionContext)],
9773
);
9874
}
9975

@@ -105,6 +81,28 @@ public function generate(
10581

10682
return new Prompt(
10783
messages: $messages,
84+
variables: [
85+
'prompt' => $agent->getInstruction(),
86+
'active_context' => \json_encode($sessionContext),
87+
'associated_agents' => \implode(
88+
PHP_EOL,
89+
\array_map(
90+
static fn(array $agent): string => \json_encode([
91+
'key' => $agent['agent']->getKey(),
92+
'description' => $agent['agent']->getDescription(),
93+
'output_schema' => $agent['output_schema'],
94+
]),
95+
$associatedAgents,
96+
),
97+
),
98+
'memory' => \implode(
99+
"\n",
100+
\array_map(
101+
static fn(SolutionMetadata $metadata) => $metadata->content,
102+
$agent->getMemory(),
103+
),
104+
),
105+
],
108106
);
109107
}
110108
}

0 commit comments

Comments
 (0)