mirror of
https://github.com/symfony/ai.git
synced 2026-03-23 23:42:18 +01:00
bug #1794 [Agent] Fix dispatch of multiple tool instances of the same class (chr-hertel)
This PR was squashed before being merged into the main branch.
Discussion
----------
[Agent] Fix dispatch of multiple tool instances of the same class
| Q | A
| ------------- | ---
| Bug fix? | yes
| New feature? | no
| Docs? | no
| Issues | Fix #1657
| License | MIT
When multiple instances of the same class are registered as separate tools (e.g. two `Subagent` instances wrapping different agents), `Toolbox` would always dispatch to the first matching instance since it resolved executables by class name.
This PR fixes the issue by:
- **`MemoryToolFactory`**: Keys tool registrations by `spl_object_id` when objects are passed, allowing per-instance lookups. Builds `Tool` objects directly with `ExecutionReference` instead of going through `AsTool`/`convertAttribute`.
- **`Toolbox`**: Tries instance-specific lookup via `spl_object_id` first, falls back to class-based lookup. Maintains an `$instanceMap` to dispatch tool calls to the correct instance.
- **Removes `AbstractToolFactory`**: Inlines its logic into `ReflectionToolFactory`, since `MemoryToolFactory` no longer needs it.
- **Updates AI Bundle DI config** to reflect the removal of the abstract factory service.
Replaces #1658 1658
Commits
-------
fe195ea1 [Agent] Fix dispatch of multiple tool instances of the same class
This commit is contained in:
13
UPGRADE.md
13
UPGRADE.md
@@ -1,6 +1,19 @@
|
||||
UPGRADE FROM 0.6 to 0.7
|
||||
=======================
|
||||
|
||||
Agent
|
||||
-----
|
||||
|
||||
* The `Symfony\AI\Agent\Toolbox\ToolFactory\AbstractToolFactory` class has been removed. If you extended it to create
|
||||
a custom tool factory, implement `ToolFactoryInterface` yourself and use `Symfony\AI\Platform\Contract\JsonSchema\Factory`
|
||||
directly if needed.
|
||||
* The `ToolFactoryInterface::getTool()` method signature has changed to accept `object|string` instead of `string`:
|
||||
|
||||
```diff
|
||||
-public function getTool(string $reference): iterable;
|
||||
+public function getTool(object|string $reference): iterable;
|
||||
```
|
||||
|
||||
AI Bundle
|
||||
---------
|
||||
|
||||
|
||||
64
examples/gemini/subagents.php
Normal file
64
examples/gemini/subagents.php
Normal file
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
use Symfony\AI\Agent\Agent;
|
||||
use Symfony\AI\Agent\InputProcessor\SystemPromptInputProcessor;
|
||||
use Symfony\AI\Agent\Toolbox\AgentProcessor;
|
||||
use Symfony\AI\Agent\Toolbox\Tool\Subagent;
|
||||
use Symfony\AI\Agent\Toolbox\Toolbox;
|
||||
use Symfony\AI\Agent\Toolbox\ToolFactory\MemoryToolFactory;
|
||||
use Symfony\AI\Platform\Bridge\Gemini\PlatformFactory;
|
||||
use Symfony\AI\Platform\Message\Message;
|
||||
use Symfony\AI\Platform\Message\MessageBag;
|
||||
|
||||
require_once dirname(__DIR__).'/bootstrap.php';
|
||||
|
||||
$platform = PlatformFactory::create(env('GEMINI_API_KEY'), http_client());
|
||||
|
||||
// Create a specialized agent for mathematical calculations
|
||||
$mathAgent = new Agent(
|
||||
$platform,
|
||||
'gemini-2.5-flash',
|
||||
[new SystemPromptInputProcessor('You are a mathematical calculator. When given a math problem, solve it and return only the numerical result with a brief explanation.')],
|
||||
);
|
||||
|
||||
// Create a specialized agent for unit conversions
|
||||
$conversionAgent = new Agent(
|
||||
$platform,
|
||||
'gemini-2.5-flash',
|
||||
[new SystemPromptInputProcessor('You are a unit conversion specialist. Convert values between different units of measurement and return the result with a brief explanation.')],
|
||||
);
|
||||
|
||||
$mathTool = new Subagent($mathAgent);
|
||||
$conversionTool = new Subagent($conversionAgent);
|
||||
|
||||
$memoryFactory = new MemoryToolFactory();
|
||||
$memoryFactory->addTool(
|
||||
$mathTool,
|
||||
'calculate',
|
||||
'Performs mathematical calculations. Use this when you need to solve math problems or do arithmetic.',
|
||||
);
|
||||
$memoryFactory->addTool(
|
||||
$conversionTool,
|
||||
'convert_units',
|
||||
'Converts values between units of measurement (e.g. km to miles, kg to pounds, Celsius to Fahrenheit).',
|
||||
);
|
||||
|
||||
// Create the main orchestrating agent with both subagents as tools
|
||||
$toolbox = new Toolbox([$mathTool, $conversionTool], toolFactory: $memoryFactory, logger: logger());
|
||||
$processor = new AgentProcessor($toolbox);
|
||||
$agent = new Agent($platform, 'gemini-2.5-flash', [$processor], [$processor]);
|
||||
|
||||
// Ask a question that requires both calculation and conversion
|
||||
$messages = new MessageBag(Message::ofUser('I drove 150 kilometers. How many miles is that? Also, what is 150 divided by 8?'));
|
||||
$result = $agent->call($messages);
|
||||
|
||||
echo $result->getContent().\PHP_EOL;
|
||||
@@ -14,9 +14,7 @@ use Symfony\AI\Agent\InputProcessor\SystemPromptInputProcessor;
|
||||
use Symfony\AI\Agent\Toolbox\AgentProcessor;
|
||||
use Symfony\AI\Agent\Toolbox\Tool\Subagent;
|
||||
use Symfony\AI\Agent\Toolbox\Toolbox;
|
||||
use Symfony\AI\Agent\Toolbox\ToolFactory\ChainFactory;
|
||||
use Symfony\AI\Agent\Toolbox\ToolFactory\MemoryToolFactory;
|
||||
use Symfony\AI\Agent\Toolbox\ToolFactory\ReflectionToolFactory;
|
||||
use Symfony\AI\Platform\Bridge\OpenAi\PlatformFactory;
|
||||
use Symfony\AI\Platform\Message\Message;
|
||||
use Symfony\AI\Platform\Message\MessageBag;
|
||||
@@ -26,33 +24,41 @@ require_once dirname(__DIR__).'/bootstrap.php';
|
||||
$platform = PlatformFactory::create(env('OPENAI_API_KEY'), http_client());
|
||||
|
||||
// Create a specialized agent for mathematical calculations
|
||||
$mathSystemPrompt = new SystemPromptInputProcessor('You are a mathematical calculator. When given a math problem, solve it and return only the numerical result with a brief explanation.');
|
||||
$mathAgent = new Agent($platform, 'gpt-5.2', [$mathSystemPrompt]);
|
||||
$mathAgent = new Agent(
|
||||
$platform,
|
||||
'gpt-4o-mini',
|
||||
[new SystemPromptInputProcessor('You are a mathematical calculator. When given a math problem, solve it and return only the numerical result with a brief explanation.')],
|
||||
);
|
||||
|
||||
// Create a specialized agent for unit conversions
|
||||
$conversionAgent = new Agent(
|
||||
$platform,
|
||||
'gpt-4o-mini',
|
||||
[new SystemPromptInputProcessor('You are a unit conversion specialist. Convert values between different units of measurement and return the result with a brief explanation.')],
|
||||
);
|
||||
|
||||
// Wrap the math agent as a tool
|
||||
$mathTool = new Subagent($mathAgent);
|
||||
$conversionTool = new Subagent($conversionAgent);
|
||||
|
||||
// Use MemoryToolFactory to register the tool with metadata
|
||||
$memoryFactory = new MemoryToolFactory();
|
||||
$memoryFactory->addTool(
|
||||
$mathTool,
|
||||
'calculate',
|
||||
'Performs mathematical calculations. Use this when you need to solve math problems or do arithmetic.',
|
||||
);
|
||||
$memoryFactory->addTool(
|
||||
$conversionTool,
|
||||
'convert_units',
|
||||
'Converts values between units of measurement (e.g. km to miles, kg to pounds, Celsius to Fahrenheit).',
|
||||
);
|
||||
|
||||
// Combine with ReflectionToolFactory using ChainFactory
|
||||
$chainFactory = new ChainFactory([
|
||||
$memoryFactory,
|
||||
new ReflectionToolFactory(),
|
||||
]);
|
||||
|
||||
// Create the main agent with the math agent as a tool
|
||||
$toolbox = new Toolbox([$mathTool], toolFactory: $chainFactory, logger: logger());
|
||||
// Create the main orchestrating agent with both subagents as tools
|
||||
$toolbox = new Toolbox([$mathTool, $conversionTool], toolFactory: $memoryFactory, logger: logger());
|
||||
$processor = new AgentProcessor($toolbox);
|
||||
$agent = new Agent($platform, 'gpt-5-mini', [$processor], [$processor]);
|
||||
$agent = new Agent($platform, 'gpt-4o-mini', [$processor], [$processor]);
|
||||
|
||||
// Ask a question that requires mathematical calculation
|
||||
$messages = new MessageBag(Message::ofUser('I have 15 apples and I want to share them equally among 4 friends. How many apples does each friend get and how many are left over?'));
|
||||
// Ask a question that requires both calculation and conversion
|
||||
$messages = new MessageBag(Message::ofUser('I drove 150 kilometers. How many miles is that? Also, what is 150 divided by 8?'));
|
||||
$result = $agent->call($messages);
|
||||
|
||||
echo $result->getContent().\PHP_EOL;
|
||||
@@ -4,6 +4,8 @@ CHANGELOG
|
||||
0.7
|
||||
---
|
||||
|
||||
* [BC BREAK] Remove `AbstractToolFactory` in favor of standalone `ReflectionToolFactory` and `MemoryToolFactory`
|
||||
* [BC BREAK] Change `ToolFactoryInterface::getTool()` signature from `string $reference` to `object|string $reference`
|
||||
* Add `ToolCallRequested` event dispatched before tool execution
|
||||
|
||||
0.4
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\AI\Agent\Toolbox\ToolFactory;
|
||||
|
||||
use Symfony\AI\Agent\Toolbox\Attribute\AsTool;
|
||||
use Symfony\AI\Agent\Toolbox\Exception\ToolConfigurationException;
|
||||
use Symfony\AI\Agent\Toolbox\ToolFactoryInterface;
|
||||
use Symfony\AI\Platform\Contract\JsonSchema\Factory;
|
||||
use Symfony\AI\Platform\Tool\ExecutionReference;
|
||||
use Symfony\AI\Platform\Tool\Tool;
|
||||
|
||||
/**
|
||||
* @author Christopher Hertel <mail@christopher-hertel.de>
|
||||
*/
|
||||
abstract class AbstractToolFactory implements ToolFactoryInterface
|
||||
{
|
||||
public function __construct(
|
||||
private readonly Factory $factory = new Factory(),
|
||||
) {
|
||||
}
|
||||
|
||||
protected function convertAttribute(string $className, AsTool $attribute): Tool
|
||||
{
|
||||
try {
|
||||
return new Tool(
|
||||
new ExecutionReference($className, $attribute->method),
|
||||
$attribute->name,
|
||||
$attribute->description,
|
||||
$this->factory->buildParameters($className, $attribute->method)
|
||||
);
|
||||
} catch (\ReflectionException $e) {
|
||||
throw ToolConfigurationException::invalidMethod($className, $attribute->method, $e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -27,7 +27,7 @@ final class ChainFactory implements ToolFactoryInterface
|
||||
) {
|
||||
}
|
||||
|
||||
public function getTool(string $reference): iterable
|
||||
public function getTool(object|string $reference): iterable
|
||||
{
|
||||
$invalid = 0;
|
||||
foreach ($this->factories as $factory) {
|
||||
|
||||
@@ -11,38 +11,68 @@
|
||||
|
||||
namespace Symfony\AI\Agent\Toolbox\ToolFactory;
|
||||
|
||||
use Symfony\AI\Agent\Toolbox\Attribute\AsTool;
|
||||
use Symfony\AI\Agent\Toolbox\Exception\ToolConfigurationException;
|
||||
use Symfony\AI\Agent\Toolbox\Exception\ToolException;
|
||||
use Symfony\AI\Agent\Toolbox\ToolFactoryInterface;
|
||||
use Symfony\AI\Platform\Contract\JsonSchema\Factory;
|
||||
use Symfony\AI\Platform\Tool\ExecutionReference;
|
||||
use Symfony\AI\Platform\Tool\Tool;
|
||||
|
||||
/**
|
||||
* @author Christopher Hertel <mail@christopher-hertel.de>
|
||||
*/
|
||||
final class MemoryToolFactory extends AbstractToolFactory
|
||||
final class MemoryToolFactory implements ToolFactoryInterface
|
||||
{
|
||||
/**
|
||||
* @var array<string, AsTool[]>
|
||||
* @var array<string, Tool[]>
|
||||
*/
|
||||
private array $tools = [];
|
||||
|
||||
public function __construct(
|
||||
private readonly Factory $factory = new Factory(),
|
||||
) {
|
||||
}
|
||||
|
||||
public function addTool(string|object $class, string $name, string $description, string $method = '__invoke'): self
|
||||
{
|
||||
$className = \is_object($class) ? $class::class : $class;
|
||||
$this->tools[$className][] = new AsTool($name, $description, $method);
|
||||
$key = \is_object($class) ? (string) spl_object_id($class) : $className;
|
||||
|
||||
try {
|
||||
$this->tools[$key][] = new Tool(
|
||||
new ExecutionReference($className, $method),
|
||||
$name,
|
||||
$description,
|
||||
$this->factory->buildParameters($className, $method),
|
||||
);
|
||||
} catch (\ReflectionException $e) {
|
||||
throw ToolConfigurationException::invalidMethod($className, $method, $e);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param class-string $className
|
||||
*/
|
||||
public function getTool(string $className): iterable
|
||||
public function getTool(object|string $reference): iterable
|
||||
{
|
||||
if (!isset($this->tools[$className])) {
|
||||
throw ToolException::invalidReference($className);
|
||||
if (\is_object($reference)) {
|
||||
$key = (string) spl_object_id($reference);
|
||||
|
||||
if (isset($this->tools[$key])) {
|
||||
yield from $this->tools[$key];
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Fall back to class name for tools registered by class string
|
||||
$key = $reference::class;
|
||||
} else {
|
||||
$key = $reference;
|
||||
}
|
||||
|
||||
foreach ($this->tools[$className] as $tool) {
|
||||
yield $this->convertAttribute($className, $tool);
|
||||
if (!isset($this->tools[$key])) {
|
||||
throw ToolException::invalidReference($key);
|
||||
}
|
||||
|
||||
yield from $this->tools[$key];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,33 +12,53 @@
|
||||
namespace Symfony\AI\Agent\Toolbox\ToolFactory;
|
||||
|
||||
use Symfony\AI\Agent\Toolbox\Attribute\AsTool;
|
||||
use Symfony\AI\Agent\Toolbox\Exception\ToolConfigurationException;
|
||||
use Symfony\AI\Agent\Toolbox\Exception\ToolException;
|
||||
use Symfony\AI\Agent\Toolbox\ToolFactoryInterface;
|
||||
use Symfony\AI\Platform\Contract\JsonSchema\Factory;
|
||||
use Symfony\AI\Platform\Tool\ExecutionReference;
|
||||
use Symfony\AI\Platform\Tool\Tool;
|
||||
|
||||
/**
|
||||
* Metadata factory that uses reflection in combination with `#[AsTool]` attribute to extract metadata from tools.
|
||||
*
|
||||
* @author Christopher Hertel <mail@christopher-hertel.de>
|
||||
*/
|
||||
final class ReflectionToolFactory extends AbstractToolFactory
|
||||
final class ReflectionToolFactory implements ToolFactoryInterface
|
||||
{
|
||||
/**
|
||||
* @param class-string $reference
|
||||
*/
|
||||
public function getTool(string $reference): iterable
|
||||
public function __construct(
|
||||
private readonly Factory $factory = new Factory(),
|
||||
) {
|
||||
}
|
||||
|
||||
public function getTool(object|string $reference): iterable
|
||||
{
|
||||
if (!class_exists($reference)) {
|
||||
throw ToolException::invalidReference($reference);
|
||||
$className = \is_object($reference) ? $reference::class : $reference;
|
||||
|
||||
if (!class_exists($className)) {
|
||||
throw ToolException::invalidReference($className);
|
||||
}
|
||||
|
||||
$reflectionClass = new \ReflectionClass($reference);
|
||||
$reflectionClass = new \ReflectionClass($className);
|
||||
$attributes = $reflectionClass->getAttributes(AsTool::class);
|
||||
|
||||
if ([] === $attributes) {
|
||||
throw ToolException::missingAttribute($reference);
|
||||
throw ToolException::missingAttribute($className);
|
||||
}
|
||||
|
||||
foreach ($attributes as $attribute) {
|
||||
yield $this->convertAttribute($reference, $attribute->newInstance());
|
||||
$asTool = $attribute->newInstance();
|
||||
|
||||
try {
|
||||
yield new Tool(
|
||||
new ExecutionReference($className, $asTool->method),
|
||||
$asTool->name,
|
||||
$asTool->description,
|
||||
$this->factory->buildParameters($className, $asTool->method),
|
||||
);
|
||||
} catch (\ReflectionException $e) {
|
||||
throw ToolConfigurationException::invalidMethod($className, $asTool->method, $e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,5 +24,5 @@ interface ToolFactoryInterface
|
||||
*
|
||||
* @throws ToolException if the metadata for the given reference is not found
|
||||
*/
|
||||
public function getTool(string $reference): iterable;
|
||||
public function getTool(object|string $reference): iterable;
|
||||
}
|
||||
|
||||
@@ -39,6 +39,13 @@ final class Toolbox implements ToolboxInterface
|
||||
*/
|
||||
private array $toolsMetadata;
|
||||
|
||||
/**
|
||||
* Maps tool name to the specific object instance that was registered for it.
|
||||
*
|
||||
* @var array<string, object>
|
||||
*/
|
||||
private array $instanceMap = [];
|
||||
|
||||
/**
|
||||
* @param iterable<object> $tools
|
||||
*/
|
||||
@@ -59,7 +66,8 @@ final class Toolbox implements ToolboxInterface
|
||||
|
||||
$toolsMetadata = [];
|
||||
foreach ($this->tools as $tool) {
|
||||
foreach ($this->toolFactory->getTool($tool::class) as $metadata) {
|
||||
foreach ($this->toolFactory->getTool($tool) as $metadata) {
|
||||
$this->instanceMap[$metadata->getName()] = $tool;
|
||||
$toolsMetadata[] = $metadata;
|
||||
}
|
||||
}
|
||||
@@ -128,6 +136,10 @@ final class Toolbox implements ToolboxInterface
|
||||
|
||||
private function getExecutable(Tool $metadata): object
|
||||
{
|
||||
if (isset($this->instanceMap[$metadata->getName()])) {
|
||||
return $this->instanceMap[$metadata->getName()];
|
||||
}
|
||||
|
||||
$className = $metadata->getReference()->getClass();
|
||||
foreach ($this->tools as $tool) {
|
||||
if ($tool instanceof $className) {
|
||||
|
||||
@@ -26,7 +26,7 @@ final class MemoryFactoryTest extends TestCase
|
||||
$this->expectExceptionMessage('The reference "SomeClass" is not a valid tool.');
|
||||
|
||||
$factory = new MemoryToolFactory();
|
||||
iterator_to_array($factory->getTool('SomeClass')); // @phpstan-ignore-line Yes, this class does not exist
|
||||
iterator_to_array($factory->getTool('SomeClass'));
|
||||
}
|
||||
|
||||
public function testGetMetadataWithDistinctToolPerClass()
|
||||
|
||||
@@ -38,7 +38,7 @@ final class ReflectionFactoryTest extends TestCase
|
||||
$this->expectException(ToolException::class);
|
||||
$this->expectExceptionMessage('The reference "invalid" is not a valid tool.');
|
||||
|
||||
iterator_to_array($this->factory->getTool('invalid')); // @phpstan-ignore-line Yes, this class does not exist
|
||||
iterator_to_array($this->factory->getTool('invalid'));
|
||||
}
|
||||
|
||||
public function testWithoutAttribute()
|
||||
|
||||
@@ -13,6 +13,7 @@ namespace Symfony\AI\Agent\Tests\Toolbox;
|
||||
|
||||
use PHPUnit\Framework\Attributes\DataProvider;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\AI\Agent\MockAgent;
|
||||
use Symfony\AI\Agent\Tests\Fixtures\Tool\ToolCustomException;
|
||||
use Symfony\AI\Agent\Tests\Fixtures\Tool\ToolDate;
|
||||
use Symfony\AI\Agent\Tests\Fixtures\Tool\ToolException;
|
||||
@@ -27,6 +28,7 @@ use Symfony\AI\Agent\Toolbox\Exception\ToolExecutionException;
|
||||
use Symfony\AI\Agent\Toolbox\Exception\ToolExecutionExceptionInterface;
|
||||
use Symfony\AI\Agent\Toolbox\Exception\ToolNotFoundException;
|
||||
use Symfony\AI\Agent\Toolbox\Source\Source;
|
||||
use Symfony\AI\Agent\Toolbox\Tool\Subagent;
|
||||
use Symfony\AI\Agent\Toolbox\Toolbox;
|
||||
use Symfony\AI\Agent\Toolbox\ToolFactory\ChainFactory;
|
||||
use Symfony\AI\Agent\Toolbox\ToolFactory\MemoryToolFactory;
|
||||
@@ -255,6 +257,51 @@ final class ToolboxTest extends TestCase
|
||||
$this->assertSame('Happy Birthday, John! You are 30 years old.', $result->getResult());
|
||||
}
|
||||
|
||||
public function testToolboxMapWithMultipleSubagents()
|
||||
{
|
||||
$mathAgent = new MockAgent(['2+2' => '4']);
|
||||
$conversionAgent = new MockAgent(['100km' => '62 miles']);
|
||||
|
||||
$mathTool = new Subagent($mathAgent);
|
||||
$conversionTool = new Subagent($conversionAgent);
|
||||
|
||||
$memoryFactory = (new MemoryToolFactory())
|
||||
->addTool($mathTool, 'calculate', 'Performs calculations')
|
||||
->addTool($conversionTool, 'convert', 'Converts units');
|
||||
|
||||
$toolbox = new Toolbox([$mathTool, $conversionTool], $memoryFactory);
|
||||
|
||||
$tools = $toolbox->getTools();
|
||||
|
||||
$this->assertCount(2, $tools);
|
||||
$this->assertSame('calculate', $tools[0]->getName());
|
||||
$this->assertSame('convert', $tools[1]->getName());
|
||||
}
|
||||
|
||||
public function testToolboxExecutionWithMultipleSubagentsDispatchesToCorrectOne()
|
||||
{
|
||||
$mathAgent = new MockAgent(['2+2' => '4']);
|
||||
$conversionAgent = new MockAgent(['100km' => '62 miles']);
|
||||
|
||||
$mathTool = new Subagent($mathAgent);
|
||||
$conversionTool = new Subagent($conversionAgent);
|
||||
|
||||
$memoryFactory = (new MemoryToolFactory())
|
||||
->addTool($mathTool, 'calculate', 'Performs calculations')
|
||||
->addTool($conversionTool, 'convert', 'Converts units');
|
||||
|
||||
$toolbox = new Toolbox([$mathTool, $conversionTool], $memoryFactory);
|
||||
|
||||
$mathResult = $toolbox->execute(new ToolCall('call_math', 'calculate', ['message' => '2+2']));
|
||||
$this->assertSame('4', $mathResult->getResult());
|
||||
|
||||
$conversionResult = $toolbox->execute(new ToolCall('call_convert', 'convert', ['message' => '100km']));
|
||||
$this->assertSame('62 miles', $conversionResult->getResult());
|
||||
|
||||
$mathAgent->assertCallCount(1);
|
||||
$conversionAgent->assertCallCount(1);
|
||||
}
|
||||
|
||||
public function testToolboxMapWithOverrideViaChain()
|
||||
{
|
||||
$factory1 = (new MemoryToolFactory())
|
||||
|
||||
@@ -14,7 +14,6 @@ namespace Symfony\Component\DependencyInjection\Loader\Configurator;
|
||||
use Symfony\AI\Agent\Toolbox\AgentProcessor as ToolProcessor;
|
||||
use Symfony\AI\Agent\Toolbox\Toolbox;
|
||||
use Symfony\AI\Agent\Toolbox\ToolCallArgumentResolver;
|
||||
use Symfony\AI\Agent\Toolbox\ToolFactory\AbstractToolFactory;
|
||||
use Symfony\AI\Agent\Toolbox\ToolFactory\ReflectionToolFactory;
|
||||
use Symfony\AI\Agent\Toolbox\ToolResultConverter;
|
||||
use Symfony\AI\AiBundle\Command\AgentCallCommand;
|
||||
@@ -215,13 +214,10 @@ return static function (ContainerConfigurator $container): void {
|
||||
service('logger')->ignoreOnInvalid(),
|
||||
service('event_dispatcher')->nullOnInvalid(),
|
||||
])
|
||||
->set('ai.tool_factory.abstract', AbstractToolFactory::class)
|
||||
->abstract()
|
||||
->set('ai.tool_factory', ReflectionToolFactory::class)
|
||||
->args([
|
||||
service('ai.platform.json_schema_factory'),
|
||||
])
|
||||
->set('ai.tool_factory', ReflectionToolFactory::class)
|
||||
->parent('ai.tool_factory.abstract')
|
||||
->set('ai.tool_result_converter', ToolResultConverter::class)
|
||||
->args([
|
||||
service('serializer'),
|
||||
|
||||
@@ -352,7 +352,6 @@ final class AiBundle extends AbstractBundle
|
||||
if (!ContainerBuilder::willBeAvailable('symfony/ai-agent', Agent::class, ['symfony/ai-bundle'])) {
|
||||
$builder->removeDefinition('ai.command.chat');
|
||||
$builder->removeDefinition('ai.toolbox.abstract');
|
||||
$builder->removeDefinition('ai.tool_factory.abstract');
|
||||
$builder->removeDefinition('ai.tool_factory');
|
||||
$builder->removeDefinition('ai.tool_result_converter');
|
||||
$builder->removeDefinition('ai.tool_call_argument_resolver');
|
||||
@@ -1077,8 +1076,9 @@ final class AiBundle extends AbstractBundle
|
||||
// TOOLBOX
|
||||
if ($config['tools']['enabled']) {
|
||||
// Setup toolbox for agent
|
||||
$memoryFactoryDefinition = new ChildDefinition('ai.tool_factory.abstract');
|
||||
$memoryFactoryDefinition->setClass(MemoryToolFactory::class);
|
||||
$memoryFactoryDefinition = new Definition(MemoryToolFactory::class, [
|
||||
new Reference('ai.platform.json_schema_factory'),
|
||||
]);
|
||||
$container->setDefinition('ai.toolbox.'.$name.'.memory_factory', $memoryFactoryDefinition);
|
||||
$chainFactoryDefinition = new Definition(ChainFactory::class, [
|
||||
[new Reference('ai.toolbox.'.$name.'.memory_factory'), new Reference('ai.tool_factory')],
|
||||
|
||||
Reference in New Issue
Block a user