diff --git a/examples/openai/platform-as-tool.php b/examples/openai/platform-as-tool.php new file mode 100644 index 000000000..501536f91 --- /dev/null +++ b/examples/openai/platform-as-tool.php @@ -0,0 +1,54 @@ + + * + * 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\Toolbox\AgentProcessor; +use Symfony\AI\Agent\Toolbox\Tool\Platform as PlatformTool; +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; + +require_once dirname(__DIR__).'/bootstrap.php'; + +$platform = PlatformFactory::create(env('OPENAI_API_KEY'), http_client()); + +// Create a specialized OpenAI platform tool using gpt-4o for mathematical calculations +$mathTool = new PlatformTool($platform, 'gpt-4o'); + +// Use MemoryToolFactory to register the tool with metadata +$memoryFactory = new MemoryToolFactory(); +$memoryFactory->addTool( + $mathTool, + 'calculate', + 'Performs mathematical calculations using GPT-4o. Use this when you need to solve math problems or do arithmetic.', +); + +// Combine with ReflectionToolFactory using ChainFactory +$chainFactory = new ChainFactory([ + $memoryFactory, + new ReflectionToolFactory(), +]); + +// Create the main agent with gpt-4o-mini but with gpt-4o available as a tool +$toolbox = new Toolbox([$mathTool], toolFactory: $chainFactory, logger: logger()); +$processor = new AgentProcessor($toolbox); +$agent = new Agent($platform, 'gpt-4o-mini', [$processor], [$processor], logger: logger()); + +// Ask a question that requires mathematical calculation +$result = $agent->call(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?' +))); + +echo $result->getContent().\PHP_EOL; diff --git a/src/agent/src/Toolbox/Tool/Platform.php b/src/agent/src/Toolbox/Tool/Platform.php new file mode 100644 index 000000000..0affe2feb --- /dev/null +++ b/src/agent/src/Toolbox/Tool/Platform.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\AI\Agent\Toolbox\Tool; + +use Symfony\AI\Platform\PlatformInterface; + +/** + * Wraps a Platform instance as a tool, allowing agents to use specialized platforms for specific tasks. + * + * This enables scenarios where an agent can leverage different models or platforms + * as tools (e.g., using gpt-4o for complex calculations while using gpt-4o-mini for the main agent). + * + * @author Oskar Stark + */ +final readonly class Platform +{ + /** + * @param array $options + */ + public function __construct( + private PlatformInterface $platform, + private string $model, + private array $options = [], + ) { + } + + /** + * @param string $message the message to pass to the chain + */ + public function __invoke(string $message): string + { + return $this->platform->invoke( + $this->model, + $message, + $this->options, + )->asText(); + } +} diff --git a/src/ai-bundle/config/options.php b/src/ai-bundle/config/options.php index 730ee6f87..6bef4fabd 100644 --- a/src/ai-bundle/config/options.php +++ b/src/ai-bundle/config/options.php @@ -390,6 +390,12 @@ ->children() ->stringNode('service')->cannotBeEmpty()->end() ->stringNode('agent')->cannotBeEmpty()->end() + ->stringNode('platform')->cannotBeEmpty()->end() + ->stringNode('model')->cannotBeEmpty()->end() + ->arrayNode('options') + ->info('Options to pass to the platform') + ->scalarPrototype()->end() + ->end() ->stringNode('name')->end() ->stringNode('description')->end() ->stringNode('method')->end() @@ -401,8 +407,27 @@ }) ->end() ->validate() - ->ifTrue(static fn ($v) => !(empty($v['agent']) xor empty($v['service']))) - ->thenInvalid('Either "agent" or "service" must be configured, and never both.') + ->ifTrue(static function ($v) { + $count = 0; + if (!empty($v['agent'])) { + ++$count; + } + if (!empty($v['service'])) { + ++$count; + } + if (!empty($v['platform'])) { + ++$count; + } + + return 1 !== $count; + }) + ->thenInvalid('Exactly one of "agent", "service", or "platform" must be configured.') + ->end() + ->validate() + ->ifTrue(static function ($v) { + return !empty($v['platform']) && empty($v['model']); + }) + ->thenInvalid('When "platform" is configured, "model" must also be provided.') ->end() ->end() ->end() diff --git a/src/ai-bundle/src/AiBundle.php b/src/ai-bundle/src/AiBundle.php index 82fe6f448..5da22446d 100644 --- a/src/ai-bundle/src/AiBundle.php +++ b/src/ai-bundle/src/AiBundle.php @@ -27,6 +27,7 @@ use Symfony\AI\Agent\Toolbox\Attribute\AsTool; use Symfony\AI\Agent\Toolbox\FaultTolerantToolbox; use Symfony\AI\Agent\Toolbox\Tool\Agent as AgentTool; +use Symfony\AI\Agent\Toolbox\Tool\Platform as PlatformTool; use Symfony\AI\Agent\Toolbox\ToolFactory\ChainFactory; use Symfony\AI\Agent\Toolbox\ToolFactory\MemoryToolFactory; use Symfony\AI\AiBundle\DependencyInjection\ProcessorCompilerPass; @@ -589,6 +590,17 @@ private function processAgentConfig(string $name, array $config, ContainerBuilde if (isset($tool['agent'])) { $tool['name'] ??= $tool['agent']; $tool['service'] = \sprintf('ai.agent.%s', $tool['agent']); + } elseif (isset($tool['platform'])) { + $tool['name'] ??= $tool['platform'].'_'.$tool['model']; + $platformReference = new Reference(\sprintf('ai.platform.%s', $tool['platform'])); + $platformWrapperDefinition = new Definition(PlatformTool::class, [ + $platformReference, + $tool['model'], + $tool['options'] ?? [], + ]); + $wrapperServiceId = 'ai.toolbox.'.$name.'.platform_wrapper.'.$tool['name']; + $container->setDefinition($wrapperServiceId, $platformWrapperDefinition); + $tool['service'] = $wrapperServiceId; } $reference = new Reference($tool['service']); // We use the memory factory in case method, description and name are set