Skip to content

Commit 72886d2

Browse files
committed
[AIBundle] Simplify agent-as-tool configuration
1 parent a010cfd commit 72886d2

File tree

5 files changed

+70
-13
lines changed

5 files changed

+70
-13
lines changed

demo/config/packages/ai.yaml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,10 +46,9 @@ ai:
4646
system_prompt: 'You are a friendly chatbot that likes to have a conversation with users and asks them some questions.'
4747
tools:
4848
# Agent in agent 🤯
49-
- service: 'ai.agent.blog'
49+
- agent: 'blog'
5050
name: 'symfony_blog'
5151
description: 'Can answer questions based on the Symfony blog.'
52-
is_agent: true
5352
store:
5453
chroma_db:
5554
symfonycon:

src/ai-bundle/config/options.php

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -117,18 +117,22 @@
117117
->arrayNode('services')
118118
->arrayPrototype()
119119
->children()
120-
->scalarNode('service')->isRequired()->end()
120+
->scalarNode('service')->cannotBeEmpty()->end()
121+
->scalarNode('agent')->cannotBeEmpty()->end()
121122
->scalarNode('name')->end()
122123
->scalarNode('description')->end()
123124
->scalarNode('method')->end()
124-
->booleanNode('is_agent')->defaultFalse()->end()
125125
->end()
126126
->beforeNormalization()
127127
->ifString()
128128
->then(function (string $v) {
129129
return ['service' => $v];
130130
})
131131
->end()
132+
->validate()
133+
->ifTrue(static fn ($v) => !(empty($v['agent']) xor empty($v['service'])))
134+
->thenInvalid('Either "agent" or "service" must be configured, and never both.')
135+
->end()
132136
->end()
133137
->end()
134138
->end()

src/ai-bundle/doc/index.rst

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,10 +72,9 @@ Configuration
7272
method: 'foo' # Optional with default value '__invoke'
7373
7474
# Referencing a agent => agent in agent 🤯
75-
- service: 'ai.agent.research'
75+
- agent: 'research'
7676
name: 'wikipedia_research'
7777
description: 'Can research on Wikipedia'
78-
is_agent: true
7978
research:
8079
platform: 'ai.platform.anthropic'
8180
model:

src/ai-bundle/src/AIBundle.php

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -333,10 +333,14 @@ private function processAgentConfig(string $name, array $config, ContainerBuilde
333333

334334
$tools = [];
335335
foreach ($config['tools']['services'] as $tool) {
336+
if (isset($tool['agent'])) {
337+
$tool['name'] ??= $tool['agent'];
338+
$tool['service'] = \sprintf('ai.agent.%s', $tool['agent']);
339+
}
336340
$reference = new Reference($tool['service']);
337341
// We use the memory factory in case method, description and name are set
338342
if (isset($tool['name'], $tool['description'])) {
339-
if ($tool['is_agent']) {
343+
if (isset($tool['agent'])) {
340344
$agentWrapperDefinition = new Definition(AgentTool::class, [$reference]);
341345
$container->setDefinition('ai.toolbox.'.$name.'.agent_wrapper.'.$tool['name'], $agentWrapperDefinition);
342346
$reference = new Reference('ai.toolbox.'.$name.'.agent_wrapper.'.$tool['name']);

src/ai-bundle/tests/DependencyInjection/AIBundleTest.php

Lines changed: 57 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,34 +13,85 @@
1313

1414
use PHPUnit\Framework\Attributes\CoversClass;
1515
use PHPUnit\Framework\Attributes\DoesNotPerformAssertions;
16+
use PHPUnit\Framework\Attributes\Test;
1617
use PHPUnit\Framework\Attributes\UsesClass;
1718
use PHPUnit\Framework\TestCase;
1819
use Symfony\AI\AIBundle\AIBundle;
20+
use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
1921
use Symfony\Component\DependencyInjection\ContainerBuilder;
2022

2123
#[CoversClass(AIBundle::class)]
2224
#[UsesClass(ContainerBuilder::class)]
2325
class AIBundleTest extends TestCase
2426
{
2527
#[DoesNotPerformAssertions]
26-
public function testExtensionLoad(): void
28+
#[Test]
29+
public function extensionLoadDoesNotThrow(): void
30+
{
31+
$this->buildContainer();
32+
}
33+
34+
#[Test]
35+
public function agentsCanBeRegisteredAsTools(): void
36+
{
37+
$container = $this->buildContainer([
38+
'ai' => [
39+
'agent' => [
40+
'main_agent' => [
41+
'model' => [
42+
'class' => 'Symfony\AI\Platform\Bridge\OpenAI\GPT',
43+
],
44+
'tools' => [
45+
'services' => [
46+
['agent' => 'another_agent', 'description' => 'Agent tool with implicit name'],
47+
['agent' => 'another_agent', 'name' => 'another_agent_instance', 'description' => 'Agent tool with explicit name'],
48+
],
49+
],
50+
],
51+
],
52+
],
53+
]);
54+
55+
$this->assertTrue($container->hasDefinition('ai.toolbox.main_agent.agent_wrapper.another_agent'));
56+
$this->assertTrue($container->hasDefinition('ai.toolbox.main_agent.agent_wrapper.another_agent_instance'));
57+
}
58+
59+
#[Test]
60+
public function agentsAsToolsCannotDefineService(): void
61+
{
62+
$this->expectException(InvalidConfigurationException::class);
63+
$this->buildContainer([
64+
'ai' => [
65+
'agent' => [
66+
'main_agent' => [
67+
'model' => ['class' => 'Symfony\AI\Platform\Bridge\OpenAI\GPT'],
68+
'tools' => [['agent' => 'another_agent', 'service' => 'foo_bar', 'description' => 'Agent with service']],
69+
],
70+
],
71+
],
72+
]);
73+
}
74+
75+
private function buildContainer(?array $configuration = null): ContainerBuilder
2776
{
2877
$container = new ContainerBuilder();
2978
$container->setParameter('kernel.debug', true);
3079
$container->setParameter('kernel.environment', 'dev');
3180
$container->setParameter('kernel.build_dir', 'public');
3281
$extension = (new AIBundle())->getContainerExtension();
3382

34-
$configs = $this->getFullConfig();
35-
$extension->load($configs, $container);
83+
$configuration ??= $this->getFullConfig();
84+
$extension->load($configuration, $container);
85+
86+
return $container;
3687
}
3788

3889
/**
3990
* @return array<string, mixed>
4091
*/
41-
private function getFullConfig(): array
92+
private function getFullConfig(array $extraConfig = []): array
4293
{
43-
return [
94+
return array_replace_recursive([
4495
'ai' => [
4596
'platform' => [
4697
'anthropic' => [
@@ -149,6 +200,6 @@ private function getFullConfig(): array
149200
],
150201
],
151202
],
152-
];
203+
], $extraConfig);
153204
}
154205
}

0 commit comments

Comments
 (0)