Skip to content

Commit 543488a

Browse files
committed
feat(aibundle): improve support for traceable*
1 parent ff0bc3e commit 543488a

File tree

15 files changed

+504
-8
lines changed

15 files changed

+504
-8
lines changed

docs/bundles/ai-bundle.rst

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -956,6 +956,48 @@ The profiler panel provides insights into the agent's execution:
956956
.. image:: profiler.png
957957
:alt: Profiler Panel
958958

959+
Message stores
960+
--------------
961+
962+
Message stores are critical to store messages sent to agents in the short / long term, they can be configured
963+
and reused in multiple chats, providing the capacity to agents to keep previous interactions.
964+
965+
Configuring message stores
966+
~~~~~~~~~~~~~~~~~~~~~~~~~~
967+
968+
Message stores are defined in the ``message_store`` section of your configuration:
969+
970+
.. code-block:: yaml
971+
972+
ai:
973+
# ...
974+
message_store:
975+
youtube:
976+
cache:
977+
service: 'cache.app'
978+
key: 'youtube'
979+
980+
Chats
981+
-----
982+
983+
Chats are the entrypoint when it comes to sending messages to agents and retrieving content (mostly text)
984+
that contains the response from the agent.
985+
986+
Each chat requires to define an agent and a message store.
987+
988+
Configuring Chats
989+
~~~~~~~~~~~~~~~~~
990+
991+
Chats are defined in the ``chat`` section of your configuration:
992+
993+
.. code-block:: yaml
994+
995+
ai:
996+
# ...
997+
chat:
998+
youtube:
999+
agent: 'ai.agent.youtube'
1000+
message_store: 'ai.message_store.cache.youtube'
9591001
9601002
.. _`Symfony AI Agent`: https://github.com/symfony/ai-agent
9611003
.. _`Symfony AI Chat`: https://github.com/symfony/ai-chat

src/ai-bundle/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,3 +33,4 @@ CHANGELOG
3333
- Automatic registration of token output processors for Mistral, OpenAI and Vertex AI
3434
- Token usage metadata in agent results including prompt, completion, total, cached, and thinking tokens
3535
- Rate limit information tracking for supported platforms
36+
* Add support for configuring chats and message stores

src/ai-bundle/composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
"symfony/ai-chat": "@dev",
2020
"symfony/ai-platform": "@dev",
2121
"symfony/ai-store": "@dev",
22+
"symfony/clock": "^7.3|^8.0",
2223
"symfony/config": "^7.3|^8.0",
2324
"symfony/console": "^7.3|^8.0",
2425
"symfony/dependency-injection": "^7.3|^8.0",
@@ -30,7 +31,6 @@
3031
"phpstan/phpstan": "^2.1",
3132
"phpstan/phpstan-strict-rules": "^2.0",
3233
"phpunit/phpunit": "^11.5",
33-
"symfony/clock": "^7.3|^8.0",
3434
"symfony/expression-language": "^7.3|^8.0",
3535
"symfony/security-core": "^7.3|^8.0",
3636
"symfony/translation": "^7.3|^8.0"

src/ai-bundle/config/options.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -775,6 +775,15 @@
775775
->end()
776776
->end()
777777
->end()
778+
->arrayNode('chat')
779+
->useAttributeAsKey('name')
780+
->arrayPrototype()
781+
->children()
782+
->stringNode('agent')->cannotBeEmpty()->end()
783+
->stringNode('message_store')->cannotBeEmpty()->end()
784+
->end()
785+
->end()
786+
->end()
778787
->arrayNode('vectorizer')
779788
->info('Vectorizers for converting strings to Vector objects and transforming TextDocument arrays to VectorDocument arrays')
780789
->useAttributeAsKey('name')

src/ai-bundle/config/services.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,8 @@
167167
->args([
168168
tagged_iterator('ai.traceable_platform'),
169169
tagged_iterator('ai.traceable_toolbox'),
170+
tagged_iterator('ai.traceable_message_store'),
171+
tagged_iterator('ai.traceable_chat'),
170172
])
171173
->tag('data_collector')
172174

src/ai-bundle/src/AiBundle.php

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,16 @@
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\TraceableChat;
35+
use Symfony\AI\AiBundle\Profiler\TraceableMessageStore;
3436
use Symfony\AI\AiBundle\Profiler\TraceablePlatform;
3537
use Symfony\AI\AiBundle\Profiler\TraceableToolbox;
3638
use Symfony\AI\AiBundle\Security\Attribute\IsGrantedTool;
3739
use Symfony\AI\Chat\Bridge\HttpFoundation\SessionStore;
3840
use Symfony\AI\Chat\Bridge\Meilisearch\MessageStore as MeilisearchMessageStore;
3941
use Symfony\AI\Chat\Bridge\Pogocache\MessageStore as PogocacheMessageStore;
42+
use Symfony\AI\Chat\Chat;
43+
use Symfony\AI\Chat\ChatInterface;
4044
use Symfony\AI\Chat\MessageStoreInterface;
4145
use Symfony\AI\Platform\Bridge\Anthropic\PlatformFactory as AnthropicPlatformFactory;
4246
use Symfony\AI\Platform\Bridge\Azure\OpenAi\PlatformFactory as AzureOpenAiPlatformFactory;
@@ -179,11 +183,49 @@ public function loadExtension(array $config, ContainerConfigurator $container, C
179183
$builder->setAlias(MessageStoreInterface::class, reset($messageStores));
180184
}
181185

186+
if ($builder->getParameter('kernel.debug')) {
187+
foreach ($messageStores as $messageStore) {
188+
$traceableMessageStoreDefinition = (new Definition(TraceableMessageStore::class))
189+
->setDecoratedService($messageStore)
190+
->setArguments([
191+
new Reference('.inner'),
192+
new Reference(ClockInterface::class),
193+
])
194+
->addTag('ai.traceable_message_store');
195+
$suffix = u($messageStore)->afterLast('.')->toString();
196+
$builder->setDefinition('ai.traceable_message_store.'.$suffix, $traceableMessageStoreDefinition);
197+
}
198+
}
199+
182200
if ([] === $messageStores) {
183201
$builder->removeDefinition('ai.command.setup_message_store');
184202
$builder->removeDefinition('ai.command.drop_message_store');
185203
}
186204

205+
foreach ($config['chat'] ?? [] as $name => $chat) {
206+
$this->processChatConfig($name, $chat, $builder);
207+
}
208+
209+
$chats = array_keys($builder->findTaggedServiceIds('ai.chat'));
210+
211+
if (1 === \count($chats)) {
212+
$builder->setAlias(ChatInterface::class, reset($chats));
213+
}
214+
215+
if ($builder->getParameter('kernel.debug')) {
216+
foreach ($chats as $chat) {
217+
$traceableChatDefinition = (new Definition(TraceableChat::class))
218+
->setDecoratedService($chat)
219+
->setArguments([
220+
new Reference('.inner'),
221+
new Reference(ClockInterface::class),
222+
])
223+
->addTag('ai.traceable_chat');
224+
$suffix = u($chat)->afterLast('.')->toString();
225+
$builder->setDefinition('ai.traceable_chat.'.$suffix, $traceableChatDefinition);
226+
}
227+
}
228+
187229
foreach ($config['vectorizer'] ?? [] as $vectorizerName => $vectorizer) {
188230
$this->processVectorizerConfig($vectorizerName, $vectorizer, $builder);
189231
}
@@ -1387,6 +1429,26 @@ private function processMessageStoreConfig(string $type, array $messageStores, C
13871429
}
13881430
}
13891431

1432+
/**
1433+
* @param array{
1434+
* agent: string,
1435+
* message_store: string,
1436+
* } $configuration
1437+
*/
1438+
private function processChatConfig(string $name, array $configuration, ContainerBuilder $container): void
1439+
{
1440+
$definition = new Definition(Chat::class);
1441+
$definition
1442+
->setArguments([
1443+
new Reference($configuration['agent']),
1444+
new Reference($configuration['message_store']),
1445+
])
1446+
->addTag('ai.chat');
1447+
1448+
$container->setDefinition('ai.chat.'.$name, $definition);
1449+
$container->registerAliasForArgument('ai.chat.'.$name, ChatInterface::class, $name);
1450+
}
1451+
13901452
/**
13911453
* @param array<string, mixed> $config
13921454
*/

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

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
* @author Christopher Hertel <[email protected]>
2424
*
2525
* @phpstan-import-type PlatformCallData from TraceablePlatform
26+
* @phpstan-import-type MessageStoreData from TraceableMessageStore
27+
* @phpstan-import-type ChatData from TraceableChat
2628
*/
2729
final class DataCollector extends AbstractDataCollector implements LateDataCollectorInterface
2830
{
@@ -37,15 +39,31 @@ final class DataCollector extends AbstractDataCollector implements LateDataColle
3739
private readonly array $toolboxes;
3840

3941
/**
40-
* @param TraceablePlatform[] $platforms
41-
* @param TraceableToolbox[] $toolboxes
42+
* @var TraceableMessageStore[]
43+
*/
44+
private readonly array $messageStores;
45+
46+
/**
47+
* @var TraceableChat[]
48+
*/
49+
private readonly array $chats;
50+
51+
/**
52+
* @param TraceablePlatform[] $platforms
53+
* @param TraceableToolbox[] $toolboxes
54+
* @param TraceableMessageStore[] $messageStores
55+
* @param TraceableChat[] $chats
4256
*/
4357
public function __construct(
4458
iterable $platforms,
4559
iterable $toolboxes,
60+
iterable $messageStores,
61+
iterable $chats,
4662
) {
4763
$this->platforms = $platforms instanceof \Traversable ? iterator_to_array($platforms) : $platforms;
4864
$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;
4967
}
5068

5169
public function collect(Request $request, Response $response, ?\Throwable $exception = null): void
@@ -59,6 +77,8 @@ public function lateCollect(): void
5977
'tools' => $this->getAllTools(),
6078
'platform_calls' => array_merge(...array_map($this->awaitCallResults(...), $this->platforms)),
6179
'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)),
81+
'chats' => array_merge(...array_map(static fn (TraceableChat $chat): array => $chat->calls, $this->chats)),
6282
];
6383
}
6484

@@ -91,6 +111,22 @@ public function getToolCalls(): array
91111
return $this->data['tool_calls'] ?? [];
92112
}
93113

114+
/**
115+
* @return MessageStoreData[]
116+
*/
117+
public function getMessages(): array
118+
{
119+
return $this->data['messages'] ?? [];
120+
}
121+
122+
/**
123+
* @return ChatData[]
124+
*/
125+
public function getChats(): array
126+
{
127+
return $this->data['chats'] ?? [];
128+
}
129+
94130
/**
95131
* @return Tool[]
96132
*/
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\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+
use Symfony\Component\Clock\ClockInterface;
19+
20+
/**
21+
* @author Guillaume Loulier <[email protected]>
22+
*
23+
* @phpstan-type ChatData array{
24+
* action: string,
25+
* bag?: MessageBag,
26+
* message?: UserMessage,
27+
* saved_at: \DateTimeImmutable,
28+
* }
29+
*/
30+
final class TraceableChat implements ChatInterface
31+
{
32+
/**
33+
* @var array<int, array{
34+
* action: string,
35+
* bag?: MessageBag,
36+
* message?: UserMessage,
37+
* saved_at: \DateTimeImmutable,
38+
* }>
39+
*/
40+
public array $calls = [];
41+
42+
public function __construct(
43+
private readonly ChatInterface $chat,
44+
private readonly ClockInterface $clock,
45+
) {
46+
}
47+
48+
public function initiate(MessageBag $messages): void
49+
{
50+
$this->calls[] = [
51+
'action' => __FUNCTION__,
52+
'bag' => $messages,
53+
'saved_at' => $this->clock->now(),
54+
];
55+
56+
$this->chat->initiate($messages);
57+
}
58+
59+
public function submit(UserMessage $message): AssistantMessage
60+
{
61+
$this->calls[] = [
62+
'action' => __FUNCTION__,
63+
'message' => $message,
64+
'saved_at' => $this->clock->now(),
65+
];
66+
67+
return $this->chat->submit($message);
68+
}
69+
}

0 commit comments

Comments
 (0)