Skip to content

Commit e823c87

Browse files
committed
feat(aibundle): add support for traceable*
1 parent a23b6aa commit e823c87

File tree

7 files changed

+207
-1
lines changed

7 files changed

+207
-1
lines changed

src/ai-bundle/config/services.php

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424
use Symfony\AI\AiBundle\Command\AgentCallCommand;
2525
use Symfony\AI\AiBundle\Command\PlatformInvokeCommand;
2626
use Symfony\AI\AiBundle\Profiler\DataCollector;
27+
use Symfony\AI\AiBundle\Profiler\TraceableChat;
28+
use Symfony\AI\AiBundle\Profiler\TraceableMessageStore;
2729
use Symfony\AI\AiBundle\Profiler\TraceableToolbox;
2830
use Symfony\AI\AiBundle\Security\EventListener\IsGrantedToolAttributeListener;
2931
use Symfony\AI\Chat\Command\DropStoreCommand as DropMessageStoreCommand;
@@ -180,6 +182,8 @@
180182
tagged_iterator('ai.traceable_platform'),
181183
service('ai.toolbox'),
182184
tagged_iterator('ai.traceable_toolbox'),
185+
tagged_iterator('ai.traceable_message_store'),
186+
tagged_iterator('ai.traceable_chat'),
183187
])
184188
->tag('data_collector')
185189
->set('ai.traceable_toolbox', TraceableToolbox::class)
@@ -188,6 +192,18 @@
188192
service('.inner'),
189193
])
190194
->tag('ai.traceable_toolbox')
195+
->set('ai.traceable_message_store', TraceableMessageStore::class)
196+
->decorate('ai.message_store')
197+
->args([
198+
service('.inner'),
199+
])
200+
->tag('ai.traceable_message_store')
201+
->set('ai.traceable_chat', TraceableChat::class)
202+
->decorate('ai.chat')
203+
->args([
204+
service('.inner'),
205+
])
206+
->tag('ai.traceable_chat')
191207

192208
// token usage processors
193209
->set('ai.platform.token_usage_processor.anthropic', AnthropicTokenOutputProcessor::class)

src/ai-bundle/src/AiBundle.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
use Symfony\AI\Agent\Toolbox\ToolFactory\MemoryToolFactory;
3232
use Symfony\AI\AiBundle\DependencyInjection\ProcessorCompilerPass;
3333
use Symfony\AI\AiBundle\Exception\InvalidArgumentException;
34+
use Symfony\AI\AiBundle\Profiler\TraceableMessageStore;
3435
use Symfony\AI\AiBundle\Profiler\TraceablePlatform;
3536
use Symfony\AI\AiBundle\Profiler\TraceableToolbox;
3637
use Symfony\AI\AiBundle\Security\Attribute\IsGrantedTool;
@@ -175,6 +176,17 @@ public function loadExtension(array $config, ContainerConfigurator $container, C
175176
$builder->setAlias(MessageStoreInterface::class, reset($messageStores));
176177
}
177178

179+
if ($builder->getParameter('kernel.debug')) {
180+
foreach ($messageStores as $messageStore) {
181+
$traceablePlatformDefinition = (new Definition(TraceableMessageStore::class))
182+
->setDecoratedService($messageStore)
183+
->setArguments([new Reference('.inner')])
184+
->addTag('ai.traceable_message_store');
185+
$suffix = u($messageStore)->afterLast('.')->toString();
186+
$builder->setDefinition('ai.traceable_message_store.'.$suffix, $traceablePlatformDefinition);
187+
}
188+
}
189+
178190
if ([] === $messageStores) {
179191
$builder->removeDefinition('ai.command.setup_message_store');
180192
$builder->removeDefinition('ai.command.drop_message_store');

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

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
namespace Symfony\AI\AiBundle\Profiler;
1313

1414
use Symfony\AI\Agent\Toolbox\ToolboxInterface;
15-
use Symfony\AI\Platform\Model;
1615
use Symfony\AI\Platform\Tool\Tool;
1716
use Symfony\Bundle\FrameworkBundle\DataCollector\AbstractDataCollector;
1817
use Symfony\Component\HttpFoundation\Request;
@@ -24,6 +23,7 @@
2423
*
2524
* @phpstan-import-type PlatformCallData from TraceablePlatform
2625
* @phpstan-import-type ToolCallData from TraceableToolbox
26+
* @phpstan-import-type MessageStoreData from TraceableMessageStore
2727
*/
2828
final class DataCollector extends AbstractDataCollector implements LateDataCollectorInterface
2929
{
@@ -37,17 +37,33 @@ final class DataCollector extends AbstractDataCollector implements LateDataColle
3737
*/
3838
private readonly array $toolboxes;
3939

40+
/**
41+
* @var TraceableMessageStore[]
42+
*/
43+
private readonly array $messageStores;
44+
45+
/**
46+
* @var TraceableChat[]
47+
*/
48+
private readonly array $chats;
49+
4050
/**
4151
* @param TraceablePlatform[] $platforms
4252
* @param TraceableToolbox[] $toolboxes
53+
* @param TraceableMessageStore[] $messageStores
54+
* @param TraceableChat[] $chats
4355
*/
4456
public function __construct(
4557
iterable $platforms,
4658
private readonly ToolboxInterface $defaultToolBox,
4759
iterable $toolboxes,
60+
iterable $messageStores,
61+
iterable $chats,
4862
) {
4963
$this->platforms = $platforms instanceof \Traversable ? iterator_to_array($platforms) : $platforms;
5064
$this->toolboxes = $toolboxes instanceof \Traversable ? iterator_to_array($toolboxes) : $toolboxes;
65+
$this->messageStores = $messageStores instanceof \Traversable ? iterator_to_array($messageStores) : $messageStores;
66+
$this->chats = $chats instanceof \Traversable ? iterator_to_array($chats) : $chats;
5167
}
5268

5369
public function collect(Request $request, Response $response, ?\Throwable $exception = null): void
@@ -61,6 +77,7 @@ public function lateCollect(): void
6177
'tools' => $this->defaultToolBox->getTools(),
6278
'platform_calls' => array_merge(...array_map($this->awaitCallResults(...), $this->platforms)),
6379
'tool_calls' => array_merge(...array_map(fn (TraceableToolbox $toolbox) => $toolbox->calls, $this->toolboxes)),
80+
'messages' => array_merge(...array_map(static fn (TraceableMessageStore $messageStore): array => $messageStore->calls, $this->messageStores)),
6481
];
6582
}
6683

@@ -93,6 +110,14 @@ public function getToolCalls(): array
93110
return $this->data['tool_calls'] ?? [];
94111
}
95112

113+
/**
114+
* @return MessageStoreData[]
115+
*/
116+
public function getMessages(): array
117+
{
118+
return $this->data['messages'] ?? [];
119+
}
120+
96121
/**
97122
* @return array{
98123
* model: string,
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
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\AiBundle\Profiler;
13+
14+
use Symfony\AI\Chat\ChatInterface;
15+
use Symfony\AI\Platform\Message\AssistantMessage;
16+
use Symfony\AI\Platform\Message\MessageBag;
17+
use Symfony\AI\Platform\Message\UserMessage;
18+
19+
/**
20+
* @author Guillaume Loulier <[email protected]>
21+
*
22+
* @phpstan-type MessageStoreData array{
23+
* bag: MessageBag,
24+
* }
25+
*/
26+
final class TraceableChat implements ChatInterface
27+
{
28+
/**
29+
* @var array
30+
*/
31+
public array $calls = [];
32+
33+
public function __construct(
34+
private readonly ChatInterface $chat,
35+
) {
36+
}
37+
38+
public function initiate(MessageBag $messages): void
39+
{
40+
// TODO: Implement initiate() method.
41+
}
42+
43+
public function submit(UserMessage $message): AssistantMessage
44+
{
45+
// TODO: Implement submit() method.
46+
}
47+
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
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\AiBundle\Profiler;
13+
14+
use Symfony\AI\Chat\ManagedStoreInterface;
15+
use Symfony\AI\Chat\MessageStoreInterface;
16+
use Symfony\AI\Platform\Message\MessageBag;
17+
18+
/**
19+
* @author Guillaume Loulier <[email protected]>
20+
*
21+
* @phpstan-type MessageStoreData array{
22+
* bag: MessageBag,
23+
* }
24+
*/
25+
final class TraceableMessageStore implements ManagedStoreInterface, MessageStoreInterface
26+
{
27+
/**
28+
* @var MessageStoreData[]
29+
*/
30+
public array $calls = [];
31+
32+
public function __construct(
33+
private readonly MessageStoreInterface|ManagedStoreInterface $messageStore,
34+
) {
35+
}
36+
37+
public function save(MessageBag $messages): void
38+
{
39+
$this->calls[] = [
40+
'bag' => $messages,
41+
];
42+
43+
$this->messageStore->save($messages);
44+
}
45+
46+
public function load(): MessageBag
47+
{
48+
return $this->messageStore->load();
49+
}
50+
51+
public function setup(array $options = []): void
52+
{
53+
if (!$this->messageStore instanceof ManagedStoreInterface) {
54+
return;
55+
}
56+
57+
$this->messageStore->setup($options);
58+
}
59+
60+
public function drop(): void
61+
{
62+
if (!$this->messageStore instanceof ManagedStoreInterface) {
63+
return;
64+
}
65+
66+
$this->messageStore->drop();
67+
}
68+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
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\AiBundle\Tests\Profiler;
13+
14+
use PHPUnit\Framework\TestCase;
15+
16+
final class TraceableChatTest extends TestCase
17+
{
18+
19+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
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\AiBundle\Tests\Profiler;
13+
14+
use PHPUnit\Framework\TestCase;
15+
16+
final class TraceableMessageStoreTest extends TestCase
17+
{
18+
19+
}

0 commit comments

Comments
 (0)