Skip to content

Commit 266afd6

Browse files
committed
[AI Bundle] clean tracers memory after each request
1 parent 417e6a1 commit 266afd6

File tree

11 files changed

+125
-8
lines changed

11 files changed

+125
-8
lines changed

src/ai-bundle/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
CHANGELOG
22
=========
33

4+
0.3
5+
---
6+
7+
* Add `kernel.reset` support to tracers to clear call logs between requests
8+
49
0.2
510
---
611

src/ai-bundle/composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
"symfony/console": "^7.3|^8.0",
2626
"symfony/dependency-injection": "^7.3|^8.0",
2727
"symfony/framework-bundle": "^7.3|^8.0",
28+
"symfony/service-contracts": "^2.5|^3",
2829
"symfony/string": "^7.3|^8.0"
2930
},
3031
"require-dev": {

src/ai-bundle/src/AiBundle.php

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,8 @@ public function loadExtension(array $config, ContainerConfigurator $container, C
182182
$traceablePlatformDefinition = (new Definition(TraceablePlatform::class))
183183
->setDecoratedService($platform, priority: -1024)
184184
->setArguments([new Reference('.inner')])
185-
->addTag('ai.traceable_platform');
185+
->addTag('ai.traceable_platform')
186+
->addTag('kernel.reset', ['method' => 'reset']);
186187
$suffix = u($platform)->after('ai.platform.')->toString();
187188
$builder->setDefinition('ai.traceable_platform.'.$suffix, $traceablePlatformDefinition);
188189
}
@@ -259,7 +260,8 @@ public function loadExtension(array $config, ContainerConfigurator $container, C
259260
new Reference('.inner'),
260261
new Reference(ClockInterface::class),
261262
])
262-
->addTag('ai.traceable_message_store');
263+
->addTag('ai.traceable_message_store')
264+
->addTag('kernel.reset', ['method' => 'reset']);
263265
$suffix = u($messageStore)->afterLast('.')->toString();
264266
$builder->setDefinition('ai.traceable_message_store.'.$suffix, $traceableMessageStoreDefinition);
265267
}
@@ -294,7 +296,8 @@ public function loadExtension(array $config, ContainerConfigurator $container, C
294296
new Reference('.inner'),
295297
new Reference(ClockInterface::class),
296298
])
297-
->addTag('ai.traceable_chat');
299+
->addTag('ai.traceable_chat')
300+
->addTag('kernel.reset', ['method' => 'reset']);
298301
$suffix = u($chat)->afterLast('.')->toString();
299302
$builder->setDefinition('ai.traceable_chat.'.$suffix, $traceableChatDefinition);
300303
}
@@ -1121,7 +1124,8 @@ private function processAgentConfig(string $name, array $config, ContainerBuilde
11211124
->setClass(TraceableToolbox::class)
11221125
->setArguments([new Reference('.inner')])
11231126
->setDecoratedService('ai.toolbox.'.$name, priority: -1024)
1124-
->addTag('ai.traceable_toolbox');
1127+
->addTag('ai.traceable_toolbox')
1128+
->addTag('kernel.reset', ['method' => 'reset']);
11251129
$container->setDefinition('ai.traceable_toolbox.'.$name, $traceableToolboxDefinition);
11261130
}
11271131

src/ai-bundle/src/Profiler/TraceableChat.php

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
use Symfony\AI\Platform\Message\MessageBag;
1717
use Symfony\AI\Platform\Message\UserMessage;
1818
use Symfony\Component\Clock\ClockInterface;
19+
use Symfony\Contracts\Service\ResetInterface;
1920

2021
/**
2122
* @author Guillaume Loulier <personal@guillaumeloulier.fr>
@@ -27,7 +28,7 @@
2728
* saved_at: \DateTimeImmutable,
2829
* }
2930
*/
30-
final class TraceableChat implements ChatInterface
31+
final class TraceableChat implements ChatInterface, ResetInterface
3132
{
3233
/**
3334
* @var array<int, array{
@@ -66,4 +67,9 @@ public function submit(UserMessage $message): AssistantMessage
6667

6768
return $this->chat->submit($message);
6869
}
70+
71+
public function reset(): void
72+
{
73+
$this->calls = [];
74+
}
6975
}

src/ai-bundle/src/Profiler/TraceableMessageStore.php

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
use Symfony\AI\Chat\MessageStoreInterface;
1616
use Symfony\AI\Platform\Message\MessageBag;
1717
use Symfony\Component\Clock\ClockInterface;
18+
use Symfony\Contracts\Service\ResetInterface;
1819

1920
/**
2021
* @author Guillaume Loulier <personal@guillaumeloulier.fr>
@@ -24,7 +25,7 @@
2425
* saved_at: \DateTimeImmutable,
2526
* }
2627
*/
27-
final class TraceableMessageStore implements ManagedStoreInterface, MessageStoreInterface
28+
final class TraceableMessageStore implements ManagedStoreInterface, MessageStoreInterface, ResetInterface
2829
{
2930
/**
3031
* @var MessageStoreData[]
@@ -69,4 +70,9 @@ public function drop(): void
6970

7071
$this->messageStore->drop();
7172
}
73+
74+
public function reset(): void
75+
{
76+
$this->calls = [];
77+
}
7278
}

src/ai-bundle/src/Profiler/TraceablePlatform.php

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
use Symfony\AI\Platform\Result\DeferredResult;
2020
use Symfony\AI\Platform\Result\ResultInterface;
2121
use Symfony\AI\Platform\Result\StreamResult;
22+
use Symfony\Contracts\Service\ResetInterface;
2223

2324
/**
2425
* @author Christopher Hertel <mail@christopher-hertel.de>
@@ -30,7 +31,7 @@
3031
* result: DeferredResult,
3132
* }
3233
*/
33-
final class TraceablePlatform implements PlatformInterface
34+
final class TraceablePlatform implements PlatformInterface, ResetInterface
3435
{
3536
/**
3637
* @var PlatformCallData[]
@@ -74,6 +75,12 @@ public function getModelCatalog(): ModelCatalogInterface
7475
return $this->platform->getModelCatalog();
7576
}
7677

78+
public function reset(): void
79+
{
80+
$this->calls = [];
81+
$this->resultCache = new \WeakMap();
82+
}
83+
7784
private function createTraceableStreamResult(DeferredResult $originalStream): StreamResult
7885
{
7986
return $result = new StreamResult((function () use (&$result, $originalStream) {

src/ai-bundle/src/Profiler/TraceableToolbox.php

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,12 @@
1414
use Symfony\AI\Agent\Toolbox\ToolboxInterface;
1515
use Symfony\AI\Agent\Toolbox\ToolResult;
1616
use Symfony\AI\Platform\Result\ToolCall;
17+
use Symfony\Contracts\Service\ResetInterface;
1718

1819
/**
1920
* @author Christopher Hertel <mail@christopher-hertel.de>
2021
*/
21-
final class TraceableToolbox implements ToolboxInterface
22+
final class TraceableToolbox implements ToolboxInterface, ResetInterface
2223
{
2324
/**
2425
* @var ToolResult[]
@@ -39,4 +40,9 @@ public function execute(ToolCall $toolCall): ToolResult
3940
{
4041
return $this->calls[] = $this->toolbox->execute($toolCall);
4142
}
43+
44+
public function reset(): void
45+
{
46+
$this->calls = [];
47+
}
4248
}

src/ai-bundle/tests/Profiler/TraceableChatTest.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,4 +60,17 @@ public function testInitializationMessageBagCanBeRetrieved()
6060
$this->assertInstanceOf(UserMessage::class, $traceableChat->calls[1]['message']);
6161
$this->assertInstanceOf(\DateTimeImmutable::class, $traceableChat->calls[1]['saved_at']);
6262
}
63+
64+
public function testResetClearsCalls()
65+
{
66+
$agent = $this->createStub(AgentInterface::class);
67+
$chat = new Chat($agent, new InMemoryStore());
68+
$traceableChat = new TraceableChat($chat, new MonotonicClock());
69+
70+
$traceableChat->initiate(new MessageBag());
71+
$this->assertCount(1, $traceableChat->calls);
72+
73+
$traceableChat->reset();
74+
$this->assertCount(0, $traceableChat->calls);
75+
}
6376
}

src/ai-bundle/tests/Profiler/TraceableMessageStoreTest.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,4 +42,16 @@ public function testSubmittedMessageBagCanBeRetrieved()
4242
$this->assertCount(1, $calls[0]['bag']);
4343
$this->assertInstanceOf(\DateTimeImmutable::class, $calls[0]['saved_at']);
4444
}
45+
46+
public function testResetClearsCalls()
47+
{
48+
$messageStore = new InMemoryStore();
49+
$traceableMessageStore = new TraceableMessageStore($messageStore, new MonotonicClock());
50+
51+
$traceableMessageStore->save(new MessageBag());
52+
$this->assertCount(1, $traceableMessageStore->calls);
53+
54+
$traceableMessageStore->reset();
55+
$this->assertCount(0, $traceableMessageStore->calls);
56+
}
4557
}
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 <fabien@symfony.com>
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\AiBundle\Tests\Profiler;
13+
14+
use PHPUnit\Framework\TestCase;
15+
use Symfony\AI\AiBundle\Profiler\TraceablePlatform;
16+
use Symfony\AI\Platform\PlainConverter;
17+
use Symfony\AI\Platform\PlatformInterface;
18+
use Symfony\AI\Platform\Result\DeferredResult;
19+
use Symfony\AI\Platform\Result\RawResultInterface;
20+
use Symfony\AI\Platform\Result\TextResult;
21+
22+
final class TraceablePlatformTest extends TestCase
23+
{
24+
public function testResetClearsCallsAndResultCache()
25+
{
26+
$platform = $this->createStub(PlatformInterface::class);
27+
$traceablePlatform = new TraceablePlatform($platform);
28+
$result = new TextResult('Assistant response');
29+
30+
$platform->method('invoke')->willReturn(new DeferredResult(new PlainConverter($result), $this->createStub(RawResultInterface::class)));
31+
32+
$traceablePlatform->invoke('gpt-4o', 'Hello');
33+
$this->assertCount(1, $traceablePlatform->calls);
34+
$this->assertSame('gpt-4o', $traceablePlatform->calls[0]['model']);
35+
$this->assertSame('Hello', $traceablePlatform->calls[0]['input']);
36+
37+
$oldCache = $traceablePlatform->resultCache;
38+
39+
$traceablePlatform->reset();
40+
41+
$this->assertCount(0, $traceablePlatform->calls);
42+
$this->assertNotSame($oldCache, $traceablePlatform->resultCache);
43+
$this->assertInstanceOf(\WeakMap::class, $traceablePlatform->resultCache);
44+
}
45+
}

0 commit comments

Comments
 (0)