Skip to content

Commit e8b7b22

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

File tree

5 files changed

+62
-10
lines changed

5 files changed

+62
-10
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: 49 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,26 +13,72 @@
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($this->getFullConfig());
32+
}
33+
34+
#[Test]
35+
public function agentsCanBeRegisteredAsTools(): void
36+
{
37+
$container = $this->buildContainer([
38+
'ai' => [
39+
'agent' => [
40+
'main_agent' => [
41+
'model' => ['class' => 'Symfony\AI\Platform\Bridge\OpenAI\GPT'],
42+
'tools' => [
43+
['agent' => 'another_agent', 'description' => 'Agent tool with implicit name'],
44+
['agent' => 'another_agent', 'name' => 'another_agent_instance', 'description' => 'Agent tool with explicit name'],
45+
],
46+
],
47+
],
48+
],
49+
]);
50+
51+
$this->assertTrue($container->hasDefinition('ai.toolbox.main_agent.agent_wrapper.another_agent'));
52+
$this->assertTrue($container->hasDefinition('ai.toolbox.main_agent.agent_wrapper.another_agent_instance'));
53+
}
54+
55+
#[Test]
56+
public function agentsAsToolsCannotDefineService(): void
57+
{
58+
$this->expectException(InvalidConfigurationException::class);
59+
$this->buildContainer([
60+
'ai' => [
61+
'agent' => [
62+
'main_agent' => [
63+
'model' => ['class' => 'Symfony\AI\Platform\Bridge\OpenAI\GPT'],
64+
'tools' => [['agent' => 'another_agent', 'service' => 'foo_bar', 'description' => 'Agent with service']],
65+
],
66+
],
67+
],
68+
]);
69+
}
70+
71+
private function buildContainer(array $configuration): ContainerBuilder
2772
{
2873
$container = new ContainerBuilder();
2974
$container->setParameter('kernel.debug', true);
3075
$container->setParameter('kernel.environment', 'dev');
3176
$container->setParameter('kernel.build_dir', 'public');
77+
3278
$extension = (new AIBundle())->getContainerExtension();
79+
$extension->load($configuration, $container);
3380

34-
$configs = $this->getFullConfig();
35-
$extension->load($configs, $container);
81+
return $container;
3682
}
3783

3884
/**

0 commit comments

Comments
 (0)