diff --git a/src/agent/src/InputProcessor/SystemPromptInputProcessor.php b/src/agent/src/InputProcessor/SystemPromptInputProcessor.php index b8f33d0f0..0f022cc40 100644 --- a/src/agent/src/InputProcessor/SystemPromptInputProcessor.php +++ b/src/agent/src/InputProcessor/SystemPromptInputProcessor.php @@ -68,8 +68,8 @@ public function processInput(Input $input): void $tools = implode(\PHP_EOL.\PHP_EOL, array_map( fn (Tool $tool) => <<name} - {$tool->description} + ## {$tool->getName()} + {$tool->getDescription()} TOOL, $this->toolbox->getTools() )); diff --git a/src/agent/src/Toolbox/AgentProcessor.php b/src/agent/src/Toolbox/AgentProcessor.php index f454f0358..510f02e8d 100644 --- a/src/agent/src/Toolbox/AgentProcessor.php +++ b/src/agent/src/Toolbox/AgentProcessor.php @@ -52,7 +52,7 @@ public function processInput(Input $input): void $options = $input->getOptions(); // only filter tool map if list of strings is provided as option if (isset($options['tools']) && $this->isFlatStringArray($options['tools'])) { - $toolMap = array_values(array_filter($toolMap, fn (Tool $tool) => \in_array($tool->name, $options['tools'], true))); + $toolMap = array_values(array_filter($toolMap, fn (Tool $tool) => \in_array($tool->getName(), $options['tools'], true))); } $options['tools'] = $toolMap; diff --git a/src/agent/src/Toolbox/FaultTolerantToolbox.php b/src/agent/src/Toolbox/FaultTolerantToolbox.php index 236ccb8c4..ff555cb1a 100644 --- a/src/agent/src/Toolbox/FaultTolerantToolbox.php +++ b/src/agent/src/Toolbox/FaultTolerantToolbox.php @@ -40,7 +40,7 @@ public function execute(ToolCall $toolCall): mixed } catch (ToolExecutionExceptionInterface $e) { return $e->getToolCallResult(); } catch (ToolNotFoundException) { - $names = array_map(fn (Tool $metadata) => $metadata->name, $this->getTools()); + $names = array_map(fn (Tool $metadata) => $metadata->getName(), $this->getTools()); return \sprintf('Tool "%s" was not found, please use one of these: %s', $toolCall->getName(), implode(', ', $names)); } diff --git a/src/agent/src/Toolbox/ToolCallArgumentResolver.php b/src/agent/src/Toolbox/ToolCallArgumentResolver.php index d6232804d..39c59aff8 100644 --- a/src/agent/src/Toolbox/ToolCallArgumentResolver.php +++ b/src/agent/src/Toolbox/ToolCallArgumentResolver.php @@ -43,7 +43,7 @@ public function __construct( */ public function resolveArguments(Tool $metadata, ToolCall $toolCall): array { - $method = new \ReflectionMethod($metadata->reference->getClass(), $metadata->reference->getMethod()); + $method = new \ReflectionMethod($metadata->getReference()->getClass(), $metadata->getReference()->getMethod()); /** @var array $parameters */ $parameters = array_column($method->getParameters(), null, 'name'); diff --git a/src/agent/src/Toolbox/Toolbox.php b/src/agent/src/Toolbox/Toolbox.php index 16bf7e475..d44a84817 100644 --- a/src/agent/src/Toolbox/Toolbox.php +++ b/src/agent/src/Toolbox/Toolbox.php @@ -82,7 +82,7 @@ public function execute(ToolCall $toolCall): mixed $arguments = $this->argumentResolver->resolveArguments($metadata, $toolCall); $this->eventDispatcher?->dispatch(new ToolCallArgumentsResolved($tool, $metadata, $arguments)); - $result = $tool->{$metadata->reference->getMethod()}(...$arguments); + $result = $tool->{$metadata->getReference()->getMethod()}(...$arguments); $this->eventDispatcher?->dispatch(new ToolCallSucceeded($tool, $metadata, $arguments, $result)); } catch (ToolExecutionExceptionInterface $e) { $this->eventDispatcher?->dispatch(new ToolCallFailed($tool, $metadata, $arguments ?? [], $e)); @@ -99,7 +99,7 @@ public function execute(ToolCall $toolCall): mixed private function getMetadata(ToolCall $toolCall): Tool { foreach ($this->getTools() as $metadata) { - if ($metadata->name === $toolCall->getName()) { + if ($metadata->getName() === $toolCall->getName()) { return $metadata; } } @@ -109,13 +109,13 @@ private function getMetadata(ToolCall $toolCall): Tool private function getExecutable(Tool $metadata): object { - $className = $metadata->reference->getClass(); + $className = $metadata->getReference()->getClass(); foreach ($this->tools as $tool) { if ($tool instanceof $className) { return $tool; } } - throw ToolNotFoundException::notFoundForReference($metadata->reference); + throw ToolNotFoundException::notFoundForReference($metadata->getReference()); } } diff --git a/src/agent/tests/Toolbox/MetadataFactory/ChainFactoryTest.php b/src/agent/tests/Toolbox/MetadataFactory/ChainFactoryTest.php index bc9eb5db1..bcbde2fac 100644 --- a/src/agent/tests/Toolbox/MetadataFactory/ChainFactoryTest.php +++ b/src/agent/tests/Toolbox/MetadataFactory/ChainFactoryTest.php @@ -65,9 +65,9 @@ public function testTestGetMetadataOverwrite() $metadata = iterator_to_array($this->factory->getTool(ToolOptionalParam::class)); $this->assertCount(1, $metadata); - $this->assertSame('optional_param', $metadata[0]->name); - $this->assertSame('Tool with optional param', $metadata[0]->description); - $this->assertSame('bar', $metadata[0]->reference->getMethod()); + $this->assertSame('optional_param', $metadata[0]->getName()); + $this->assertSame('Tool with optional param', $metadata[0]->getDescription()); + $this->assertSame('bar', $metadata[0]->getReference()->getMethod()); } public function testTestGetMetadataWithAttributeDoubleHit() diff --git a/src/agent/tests/Toolbox/MetadataFactory/MemoryFactoryTest.php b/src/agent/tests/Toolbox/MetadataFactory/MemoryFactoryTest.php index e3e742f88..0a92f0182 100644 --- a/src/agent/tests/Toolbox/MetadataFactory/MemoryFactoryTest.php +++ b/src/agent/tests/Toolbox/MetadataFactory/MemoryFactoryTest.php @@ -39,9 +39,9 @@ public function testGetMetadataWithDistinctToolPerClass() $this->assertCount(1, $metadata); $this->assertInstanceOf(Tool::class, $metadata[0]); - $this->assertSame('happy_birthday', $metadata[0]->name); - $this->assertSame('Generates birthday message', $metadata[0]->description); - $this->assertSame('__invoke', $metadata[0]->reference->getMethod()); + $this->assertSame('happy_birthday', $metadata[0]->getName()); + $this->assertSame('Generates birthday message', $metadata[0]->getDescription()); + $this->assertSame('__invoke', $metadata[0]->getReference()->getMethod()); $expectedParams = [ 'type' => 'object', @@ -53,7 +53,7 @@ public function testGetMetadataWithDistinctToolPerClass() 'additionalProperties' => false, ]; - $this->assertSame($expectedParams, $metadata[0]->parameters); + $this->assertSame($expectedParams, $metadata[0]->getParameters()); } public function testGetMetadataWithMultipleToolsInClass() @@ -66,9 +66,9 @@ public function testGetMetadataWithMultipleToolsInClass() $this->assertCount(2, $metadata); $this->assertInstanceOf(Tool::class, $metadata[0]); - $this->assertSame('checkout', $metadata[0]->name); - $this->assertSame('Buys a number of items per product', $metadata[0]->description); - $this->assertSame('buy', $metadata[0]->reference->getMethod()); + $this->assertSame('checkout', $metadata[0]->getName()); + $this->assertSame('Buys a number of items per product', $metadata[0]->getDescription()); + $this->assertSame('buy', $metadata[0]->getReference()->getMethod()); $expectedParams = [ 'type' => 'object', @@ -79,12 +79,12 @@ public function testGetMetadataWithMultipleToolsInClass() 'required' => ['id', 'amount'], 'additionalProperties' => false, ]; - $this->assertSame($expectedParams, $metadata[0]->parameters); + $this->assertSame($expectedParams, $metadata[0]->getParameters()); $this->assertInstanceOf(Tool::class, $metadata[1]); - $this->assertSame('cancel', $metadata[1]->name); - $this->assertSame('Cancels an order', $metadata[1]->description); - $this->assertSame('cancel', $metadata[1]->reference->getMethod()); + $this->assertSame('cancel', $metadata[1]->getName()); + $this->assertSame('Cancels an order', $metadata[1]->getDescription()); + $this->assertSame('cancel', $metadata[1]->getReference()->getMethod()); $expectedParams = [ 'type' => 'object', @@ -94,6 +94,6 @@ public function testGetMetadataWithMultipleToolsInClass() 'required' => ['orderId'], 'additionalProperties' => false, ]; - $this->assertSame($expectedParams, $metadata[1]->parameters); + $this->assertSame($expectedParams, $metadata[1]->getParameters()); } } diff --git a/src/agent/tests/Toolbox/MetadataFactory/ReflectionFactoryTest.php b/src/agent/tests/Toolbox/MetadataFactory/ReflectionFactoryTest.php index a3c13b43c..589f06fca 100644 --- a/src/agent/tests/Toolbox/MetadataFactory/ReflectionFactoryTest.php +++ b/src/agent/tests/Toolbox/MetadataFactory/ReflectionFactoryTest.php @@ -127,10 +127,10 @@ className: ToolMultiple::class, private function assertToolConfiguration(Tool $metadata, string $className, string $name, string $description, string $method, array $parameters): void { - $this->assertSame($className, $metadata->reference->getClass()); - $this->assertSame($method, $metadata->reference->getMethod()); - $this->assertSame($name, $metadata->name); - $this->assertSame($description, $metadata->description); - $this->assertSame($parameters, $metadata->parameters); + $this->assertSame($className, $metadata->getReference()->getClass()); + $this->assertSame($method, $metadata->getReference()->getMethod()); + $this->assertSame($name, $metadata->getName()); + $this->assertSame($description, $metadata->getDescription()); + $this->assertSame($parameters, $metadata->getParameters()); } } diff --git a/src/ai-bundle/src/Security/EventListener/IsGrantedToolAttributeListener.php b/src/ai-bundle/src/Security/EventListener/IsGrantedToolAttributeListener.php index 0a16ae3ba..e0ce3185b 100644 --- a/src/ai-bundle/src/Security/EventListener/IsGrantedToolAttributeListener.php +++ b/src/ai-bundle/src/Security/EventListener/IsGrantedToolAttributeListener.php @@ -37,7 +37,7 @@ public function __invoke(ToolCallArgumentsResolved $event): void { $tool = $event->tool; $class = new \ReflectionClass($tool); - $method = $class->getMethod($event->metadata->reference->getMethod()); + $method = $class->getMethod($event->metadata->getReference()->getMethod()); $classAttributes = $class->getAttributes(IsGrantedTool::class); $methodAttributes = $method->getAttributes(IsGrantedTool::class); diff --git a/src/ai-bundle/tests/Security/IsGrantedToolAttributeListenerTest.php b/src/ai-bundle/tests/Security/IsGrantedToolAttributeListenerTest.php index 84ebfb139..0c9f2614f 100644 --- a/src/ai-bundle/tests/Security/IsGrantedToolAttributeListenerTest.php +++ b/src/ai-bundle/tests/Security/IsGrantedToolAttributeListenerTest.php @@ -39,7 +39,7 @@ public function testItWillThrowWhenNotGranted(object $tool, Tool $metadata) $this->authChecker->expects($this->once())->method('isGranted')->willReturn(false); $this->expectException(AccessDeniedException::class); - $this->expectExceptionMessage(\sprintf('No access to %s tool.', $metadata->name)); + $this->expectExceptionMessage(\sprintf('No access to %s tool.', $metadata->getName())); $this->dispatcher->dispatch(new ToolCallArgumentsResolved($tool, $metadata, [])); } diff --git a/src/platform/src/Bridge/Anthropic/Contract/ToolNormalizer.php b/src/platform/src/Bridge/Anthropic/Contract/ToolNormalizer.php index 817b34b1c..86c5effd7 100644 --- a/src/platform/src/Bridge/Anthropic/Contract/ToolNormalizer.php +++ b/src/platform/src/Bridge/Anthropic/Contract/ToolNormalizer.php @@ -36,9 +36,9 @@ class ToolNormalizer extends ModelContractNormalizer public function normalize(mixed $data, ?string $format = null, array $context = []): array { return [ - 'name' => $data->name, - 'description' => $data->description, - 'input_schema' => $data->parameters ?? ['type' => 'object'], + 'name' => $data->getName(), + 'description' => $data->getDescription(), + 'input_schema' => $data->getParameters() ?? ['type' => 'object'], ]; } diff --git a/src/platform/src/Bridge/Bedrock/Nova/Contract/ToolNormalizer.php b/src/platform/src/Bridge/Bedrock/Nova/Contract/ToolNormalizer.php index f9eca24d8..c7d70e2e1 100644 --- a/src/platform/src/Bridge/Bedrock/Nova/Contract/ToolNormalizer.php +++ b/src/platform/src/Bridge/Bedrock/Nova/Contract/ToolNormalizer.php @@ -41,10 +41,10 @@ public function normalize(mixed $data, ?string $format = null, array $context = { return [ 'toolSpec' => [ - 'name' => $data->name, - 'description' => $data->description, + 'name' => $data->getName(), + 'description' => $data->getDescription(), 'inputSchema' => [ - 'json' => $data->parameters ?? new \stdClass(), + 'json' => $data->getParameters() ?? new \stdClass(), ], ], ]; diff --git a/src/platform/src/Bridge/Gemini/Contract/ToolNormalizer.php b/src/platform/src/Bridge/Gemini/Contract/ToolNormalizer.php index f26b49737..c092fef57 100644 --- a/src/platform/src/Bridge/Gemini/Contract/ToolNormalizer.php +++ b/src/platform/src/Bridge/Gemini/Contract/ToolNormalizer.php @@ -36,9 +36,9 @@ final class ToolNormalizer extends ModelContractNormalizer public function normalize(mixed $data, ?string $format = null, array $context = []): array { return [ - 'description' => $data->description, - 'name' => $data->name, - 'parameters' => $data->parameters ? $this->removeAdditionalProperties($data->parameters) : null, + 'description' => $data->getDescription(), + 'name' => $data->getName(), + 'parameters' => $data->getParameters() ? $this->removeAdditionalProperties($data->getParameters()) : null, ]; } diff --git a/src/platform/src/Bridge/VertexAi/Contract/ToolNormalizer.php b/src/platform/src/Bridge/VertexAi/Contract/ToolNormalizer.php index 4d44781f3..544b6a7b7 100644 --- a/src/platform/src/Bridge/VertexAi/Contract/ToolNormalizer.php +++ b/src/platform/src/Bridge/VertexAi/Contract/ToolNormalizer.php @@ -35,11 +35,11 @@ final class ToolNormalizer extends ModelContractNormalizer */ public function normalize(mixed $data, ?string $format = null, array $context = []): array { - $parameters = $data->parameters ? $this->removeAdditionalProperties($data->parameters) : null; + $parameters = $data->getParameters() ? $this->removeAdditionalProperties($data->getParameters()) : null; return [ - 'name' => $data->name, - 'description' => $data->description, + 'name' => $data->getName(), + 'description' => $data->getDescription(), 'parameters' => $parameters, ]; } diff --git a/src/platform/src/Contract/Normalizer/ToolNormalizer.php b/src/platform/src/Contract/Normalizer/ToolNormalizer.php index 04d3e0624..0cf2e0a22 100644 --- a/src/platform/src/Contract/Normalizer/ToolNormalizer.php +++ b/src/platform/src/Contract/Normalizer/ToolNormalizer.php @@ -49,12 +49,12 @@ public function getSupportedTypes(?string $format): array public function normalize(mixed $data, ?string $format = null, array $context = []): array { $function = [ - 'name' => $data->name, - 'description' => $data->description, + 'name' => $data->getName(), + 'description' => $data->getDescription(), ]; - if (isset($data->parameters)) { - $function['parameters'] = $data->parameters; + if (null !== $data->getParameters()) { + $function['parameters'] = $data->getParameters(); } return [ diff --git a/src/platform/src/Tool/Tool.php b/src/platform/src/Tool/Tool.php index 826a04f4c..c670016e1 100644 --- a/src/platform/src/Tool/Tool.php +++ b/src/platform/src/Tool/Tool.php @@ -24,10 +24,33 @@ * @param JsonSchema|null $parameters */ public function __construct( - public ExecutionReference $reference, - public string $name, - public string $description, - public ?array $parameters = null, + private ExecutionReference $reference, + private string $name, + private string $description, + private ?array $parameters = null, ) { } + + public function getReference(): ExecutionReference + { + return $this->reference; + } + + public function getName(): string + { + return $this->name; + } + + public function getDescription(): string + { + return $this->description; + } + + /** + * @return JsonSchema|null + */ + public function getParameters(): ?array + { + return $this->parameters; + } } diff --git a/src/platform/tests/Tool/ToolTest.php b/src/platform/tests/Tool/ToolTest.php new file mode 100644 index 000000000..0a70f4bb7 --- /dev/null +++ b/src/platform/tests/Tool/ToolTest.php @@ -0,0 +1,70 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\AI\Platform\Tests\Tool; + +use PHPUnit\Framework\TestCase; +use Symfony\AI\Platform\Tool\ExecutionReference; +use Symfony\AI\Platform\Tool\Tool; + +final class ToolTest extends TestCase +{ + public function testReturnsReference() + { + $reference = new ExecutionReference('MyClass', 'myMethod'); + $tool = new Tool($reference, 'tool_name', 'tool description'); + + $this->assertSame($reference, $tool->getReference()); + } + + public function testReturnsName() + { + $reference = new ExecutionReference('MyClass'); + $tool = new Tool($reference, 'tool_name', 'tool description'); + + $this->assertSame('tool_name', $tool->getName()); + } + + public function testReturnsDescription() + { + $reference = new ExecutionReference('MyClass'); + $tool = new Tool($reference, 'tool_name', 'tool description'); + + $this->assertSame('tool description', $tool->getDescription()); + } + + public function testReturnsParametersWhenProvided() + { + $reference = new ExecutionReference('MyClass'); + $parameters = [ + 'type' => 'object', + 'properties' => [ + 'param1' => [ + 'type' => 'string', + 'description' => 'A parameter', + ], + ], + 'required' => [], + 'additionalProperties' => false, + ]; + $tool = new Tool($reference, 'tool_name', 'tool description', $parameters); + + $this->assertSame($parameters, $tool->getParameters()); + } + + public function testReturnsNullParametersByDefault() + { + $reference = new ExecutionReference('MyClass'); + $tool = new Tool($reference, 'tool_name', 'tool description'); + + $this->assertNull($tool->getParameters()); + } +}