From d816e9d9b86ac30defab95c32faf7584779582c7 Mon Sep 17 00:00:00 2001 From: Christopher Hertel Date: Sat, 15 Mar 2025 00:36:04 +0100 Subject: [PATCH] =?UTF-8?q?feat:=20add=20inline=20tool=20definition=20and?= =?UTF-8?q?=20chain=20in=20chain=20=F0=9F=A4=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 15 ++++++++++- src/DependencyInjection/Configuration.php | 16 ++++++++++- src/DependencyInjection/LlmChainExtension.php | 27 ++++++++++++++++++- src/Resources/config/services.php | 1 + 4 files changed, 56 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 01aa345..73d79d5 100644 --- a/README.md +++ b/README.md @@ -50,7 +50,20 @@ llm_chain: system_prompt: 'You are a helpful assistant that can answer questions.' # The default system prompt of the chain include_tools: true # Include tool definitions at the end of the system prompt tools: - - 'PhpLlm\LlmChain\Chain\ToolBox\Tool\SimilaritySearch' + # Referencing a service with #[AsTool] attribute + - 'PhpLlm\LlmChain\Chain\Toolbox\Tool\SimilaritySearch' + + # Referencing a service without #[AsTool] attribute + - service: 'App\Chain\Tool\CompanyName' + name: 'company_name' + description: 'Provides the name of your company' + method: 'foo' # Optional with default value '__invoke' + + # Referencing a chain => chain in chain 🤯 + - service: 'llm_chain.chain.research' + name: 'wikipedia_research' + description: 'Can research on Wikipedia' + is_chain: true research: platform: 'llm_chain.platform.anthropic' model: diff --git a/src/DependencyInjection/Configuration.php b/src/DependencyInjection/Configuration.php index 5e0feed..890b0c6 100644 --- a/src/DependencyInjection/Configuration.php +++ b/src/DependencyInjection/Configuration.php @@ -98,7 +98,21 @@ public function getConfigTreeBuilder(): TreeBuilder ->children() ->booleanNode('enabled')->defaultTrue()->end() ->arrayNode('services') - ->scalarPrototype()->end() + ->arrayPrototype() + ->children() + ->scalarNode('service')->isRequired()->end() + ->scalarNode('name')->end() + ->scalarNode('description')->end() + ->scalarNode('method')->end() + ->booleanNode('is_chain')->defaultFalse()->end() + ->end() + ->beforeNormalization() + ->ifString() + ->then(function (string $v) { + return ['service' => $v]; + }) + ->end() + ->end() ->end() ->end() ->end() diff --git a/src/DependencyInjection/LlmChainExtension.php b/src/DependencyInjection/LlmChainExtension.php index eea492e..8a2e025 100644 --- a/src/DependencyInjection/LlmChainExtension.php +++ b/src/DependencyInjection/LlmChainExtension.php @@ -26,6 +26,10 @@ use PhpLlm\LlmChain\Chain\Toolbox\Attribute\AsTool; use PhpLlm\LlmChain\Chain\Toolbox\ChainProcessor as ToolProcessor; use PhpLlm\LlmChain\Chain\Toolbox\FaultTolerantToolbox; +use PhpLlm\LlmChain\Chain\Toolbox\MetadataFactory\ChainFactory; +use PhpLlm\LlmChain\Chain\Toolbox\MetadataFactory\MemoryFactory; +use PhpLlm\LlmChain\Chain\Toolbox\MetadataFactory\ReflectionFactory; +use PhpLlm\LlmChain\Chain\Toolbox\Tool\Chain as ChainTool; use PhpLlm\LlmChain\ChainInterface; use PhpLlm\LlmChain\Embedder; use PhpLlm\LlmChain\Model\EmbeddingsModel; @@ -249,9 +253,30 @@ private function processChainConfig(string $name, array $config, ContainerBuilde if ($config['tools']['enabled']) { // Create specific toolbox and process if tools are explicitly defined if (0 !== count($config['tools']['services'])) { - $tools = array_map(static fn (string $tool) => new Reference($tool), $config['tools']['services']); + $memoryFactoryDefinition = new Definition(MemoryFactory::class); + $container->setDefinition('llm_chain.toolbox.'.$name.'.memory_factory', $memoryFactoryDefinition); + $chainFactoryDefinition = new Definition(ChainFactory::class, [ + '$factories' => [new Reference('llm_chain.toolbox.'.$name.'.memory_factory'), new Reference(ReflectionFactory::class)], + ]); + $container->setDefinition('llm_chain.toolbox.'.$name.'.chain_factory', $chainFactoryDefinition); + + $tools = []; + foreach ($config['tools']['services'] as $tool) { + $reference = new Reference($tool['service']); + // We use the memory factory in case method, description and name are set + if (isset($tool['name'], $tool['description'])) { + if ($tool['is_chain']) { + $chainWrapperDefinition = new Definition(ChainTool::class, ['$chain' => $reference]); + $container->setDefinition('llm_chain.toolbox.'.$name.'.chain_wrapper.'.$tool['name'], $chainWrapperDefinition); + $reference = new Reference('llm_chain.toolbox.'.$name.'.chain_wrapper.'.$tool['name']); + } + $memoryFactoryDefinition->addMethodCall('addTool', [$reference, $tool['name'], $tool['description'], $tool['method'] ?? '__invoke']); + } + $tools[] = $reference; + } $toolboxDefinition = (new ChildDefinition('llm_chain.toolbox.abstract')) + ->replaceArgument('$metadataFactory', new Reference('llm_chain.toolbox.'.$name.'.chain_factory')) ->replaceArgument('$tools', $tools); $container->setDefinition('llm_chain.toolbox.'.$name, $toolboxDefinition); diff --git a/src/Resources/config/services.php b/src/Resources/config/services.php index a7bfba1..3092fcb 100644 --- a/src/Resources/config/services.php +++ b/src/Resources/config/services.php @@ -33,6 +33,7 @@ ->autowire() ->abstract() ->args([ + '$metadataFactory' => service(MetadataFactory::class), '$tools' => abstract_arg('Collection of tools'), ]) ->set(Toolbox::class)