Skip to content

Commit 6f0b303

Browse files
authored
refactor: introduce execution reference in tool metadata (#260)
replaces #255 if successful motivation: the execution reference is a pointer what the toolbox needs to execute. classname + method is only one type - with MCP it could also be a server + toolname.
1 parent af174b9 commit 6f0b303

File tree

9 files changed

+66
-30
lines changed

9 files changed

+66
-30
lines changed

src/Chain/ToolBox/Exception/ToolNotFoundException.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
namespace PhpLlm\LlmChain\Chain\ToolBox\Exception;
66

7+
use PhpLlm\LlmChain\Chain\ToolBox\ExecutionReference;
78
use PhpLlm\LlmChain\Model\Response\ToolCall;
89

910
final class ToolNotFoundException extends \RuntimeException implements ExceptionInterface
@@ -17,4 +18,9 @@ public static function notFoundForToolCall(ToolCall $toolCall): self
1718

1819
return $exception;
1920
}
21+
22+
public static function notFoundForReference(ExecutionReference $reference): self
23+
{
24+
return new self(sprintf('Tool not found for reference: %s::%s.', $reference->class, $reference->method));
25+
}
2026
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PhpLlm\LlmChain\Chain\ToolBox;
6+
7+
final class ExecutionReference
8+
{
9+
public function __construct(
10+
public string $class,
11+
public string $method = '__invoke',
12+
) {
13+
}
14+
}

src/Chain/ToolBox/Metadata.php

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,9 @@
1515
* @param JsonSchema|null $parameters
1616
*/
1717
public function __construct(
18-
public string $className,
18+
public ExecutionReference $reference,
1919
public string $name,
2020
public string $description,
21-
public string $method,
2221
public ?array $parameters,
2322
) {
2423
}

src/Chain/ToolBox/MetadataFactory/ReflectionFactory.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use PhpLlm\LlmChain\Chain\JsonSchema\Factory;
88
use PhpLlm\LlmChain\Chain\ToolBox\Attribute\AsTool;
99
use PhpLlm\LlmChain\Chain\ToolBox\Exception\ToolConfigurationException;
10+
use PhpLlm\LlmChain\Chain\ToolBox\ExecutionReference;
1011
use PhpLlm\LlmChain\Chain\ToolBox\Metadata;
1112
use PhpLlm\LlmChain\Chain\ToolBox\MetadataFactory;
1213

@@ -45,10 +46,9 @@ private function convertAttribute(string $className, AsTool $attribute): Metadat
4546
{
4647
try {
4748
return new Metadata(
48-
$className,
49+
new ExecutionReference($className, $attribute->method),
4950
$attribute->name,
5051
$attribute->description,
51-
$attribute->method,
5252
$this->factory->buildParameters($className, $attribute->method)
5353
);
5454
} catch (\ReflectionException) {

src/Chain/ToolBox/ToolBox.php

Lines changed: 30 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -57,24 +57,39 @@ public function getMap(): array
5757

5858
public function execute(ToolCall $toolCall): mixed
5959
{
60-
foreach ($this->tools as $tool) {
61-
foreach ($this->metadataFactory->getMetadata($tool) as $metadata) {
62-
if ($metadata->name !== $toolCall->name) {
63-
continue;
64-
}
65-
66-
try {
67-
$this->logger->debug(sprintf('Executing tool "%s".', $metadata->name), $toolCall->arguments);
68-
$result = $tool->{$metadata->method}(...$toolCall->arguments);
69-
} catch (\Throwable $e) {
70-
$this->logger->warning(sprintf('Failed to execute tool "%s".', $metadata->name), ['exception' => $e]);
71-
throw ToolExecutionException::executionFailed($toolCall, $e);
72-
}
73-
74-
return $result;
60+
$metadata = $this->getMetadata($toolCall);
61+
$tool = $this->getTool($metadata);
62+
63+
try {
64+
$this->logger->debug(sprintf('Executing tool "%s".', $toolCall->name), $toolCall->arguments);
65+
$result = $tool->{$metadata->reference->method}(...$toolCall->arguments);
66+
} catch (\Throwable $e) {
67+
$this->logger->warning(sprintf('Failed to execute tool "%s".', $toolCall->name), ['exception' => $e]);
68+
throw ToolExecutionException::executionFailed($toolCall, $e);
69+
}
70+
71+
return $result;
72+
}
73+
74+
private function getMetadata(ToolCall $toolCall): Metadata
75+
{
76+
foreach ($this->getMap() as $metadata) {
77+
if ($metadata->name === $toolCall->name) {
78+
return $metadata;
7579
}
7680
}
7781

7882
throw ToolNotFoundException::notFoundForToolCall($toolCall);
7983
}
84+
85+
private function getTool(Metadata $metadata): object
86+
{
87+
foreach ($this->tools as $tool) {
88+
if ($tool instanceof $metadata->reference->class) {
89+
return $tool;
90+
}
91+
}
92+
93+
throw ToolNotFoundException::notFoundForReference($metadata->reference);
94+
}
8095
}

tests/Chain/InputProcessor/SystemPromptInputProcessorTest.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use PhpLlm\LlmChain\Bridge\OpenAI\GPT;
88
use PhpLlm\LlmChain\Chain\Input;
99
use PhpLlm\LlmChain\Chain\InputProcessor\SystemPromptInputProcessor;
10+
use PhpLlm\LlmChain\Chain\ToolBox\ExecutionReference;
1011
use PhpLlm\LlmChain\Chain\ToolBox\Metadata;
1112
use PhpLlm\LlmChain\Chain\ToolBox\ToolBoxInterface;
1213
use PhpLlm\LlmChain\Model\Message\Content\Text;
@@ -106,15 +107,14 @@ public function includeToolDefinitions(): void
106107
public function getMap(): array
107108
{
108109
return [
109-
new Metadata(ToolNoParams::class, 'tool_no_params', 'A tool without parameters', '__invoke', null),
110+
new Metadata(new ExecutionReference(ToolNoParams::class), 'tool_no_params', 'A tool without parameters', null),
110111
new Metadata(
111-
ToolRequiredParams::class,
112+
new ExecutionReference(ToolRequiredParams::class, 'bar'),
112113
'tool_required_params',
113114
<<<DESCRIPTION
114115
A tool with required parameters
115116
or not
116117
DESCRIPTION,
117-
'bar',
118118
null
119119
),
120120
];

tests/Chain/ToolBox/ChainProcessorTest.php

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
use PhpLlm\LlmChain\Chain\Input;
88
use PhpLlm\LlmChain\Chain\ToolBox\ChainProcessor;
9+
use PhpLlm\LlmChain\Chain\ToolBox\ExecutionReference;
910
use PhpLlm\LlmChain\Chain\ToolBox\Metadata;
1011
use PhpLlm\LlmChain\Chain\ToolBox\ToolBoxInterface;
1112
use PhpLlm\LlmChain\Exception\MissingModelSupport;
@@ -44,8 +45,8 @@ public function processInputWithoutRegisteredToolsWillResultInNoOptionChange():
4445
public function processInputWithRegisteredToolsWillResultInOptionChange(): void
4546
{
4647
$toolBox = $this->createStub(ToolBoxInterface::class);
47-
$tool1 = new Metadata('ClassTool1', 'tool1', 'description1', 'method1', null);
48-
$tool2 = new Metadata('ClassTool2', 'tool2', 'description2', 'method2', null);
48+
$tool1 = new Metadata(new ExecutionReference('ClassTool1', 'method1'), 'tool1', 'description1', null);
49+
$tool2 = new Metadata(new ExecutionReference('ClassTool2', 'method1'), 'tool2', 'description2', null);
4950
$toolBox->method('getMap')->willReturn([$tool1, $tool2]);
5051

5152
$llm = $this->createMock(LanguageModel::class);
@@ -63,8 +64,8 @@ public function processInputWithRegisteredToolsWillResultInOptionChange(): void
6364
public function processInputWithRegisteredToolsButToolOverride(): void
6465
{
6566
$toolBox = $this->createStub(ToolBoxInterface::class);
66-
$tool1 = new Metadata('ClassTool1', 'tool1', 'description1', 'method1', null);
67-
$tool2 = new Metadata('ClassTool2', 'tool2', 'description2', 'method2', null);
67+
$tool1 = new Metadata(new ExecutionReference('ClassTool1', 'method1'), 'tool1', 'description1', null);
68+
$tool2 = new Metadata(new ExecutionReference('ClassTool2', 'method1'), 'tool2', 'description2', null);
6869
$toolBox->method('getMap')->willReturn([$tool1, $tool2]);
6970

7071
$llm = $this->createMock(LanguageModel::class);

tests/Chain/ToolBox/FaultTolerantToolBoxTest.php

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
use PhpLlm\LlmChain\Chain\ToolBox\Exception\ToolExecutionException;
88
use PhpLlm\LlmChain\Chain\ToolBox\Exception\ToolNotFoundException;
9+
use PhpLlm\LlmChain\Chain\ToolBox\ExecutionReference;
910
use PhpLlm\LlmChain\Chain\ToolBox\FaultTolerantToolBox;
1011
use PhpLlm\LlmChain\Chain\ToolBox\Metadata;
1112
use PhpLlm\LlmChain\Chain\ToolBox\ToolBoxInterface;
@@ -69,8 +70,8 @@ public function __construct(private readonly \Closure $exceptionFactory)
6970
public function getMap(): array
7071
{
7172
return [
72-
new Metadata(ToolNoParams::class, 'tool_no_params', 'A tool without parameters', '__invoke', null),
73-
new Metadata(ToolRequiredParams::class, 'tool_required_params', 'A tool with required parameters', 'bar', null),
73+
new Metadata(new ExecutionReference(ToolNoParams::class), 'tool_no_params', 'A tool without parameters', null),
74+
new Metadata(new ExecutionReference(ToolRequiredParams::class, 'bar'), 'tool_required_params', 'A tool with required parameters', null),
7475
];
7576
}
7677

tests/Chain/ToolBox/MetadataFactory/ReflectionFactoryTest.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -145,10 +145,10 @@ className: ToolMultiple::class,
145145

146146
private function assertToolConfiguration(Metadata $metadata, string $className, string $name, string $description, string $method, array $parameters): void
147147
{
148-
self::assertSame($className, $metadata->className);
148+
self::assertSame($className, $metadata->reference->class);
149+
self::assertSame($method, $metadata->reference->method);
149150
self::assertSame($name, $metadata->name);
150151
self::assertSame($description, $metadata->description);
151-
self::assertSame($method, $metadata->method);
152152
self::assertSame($parameters, $metadata->parameters);
153153
}
154154
}

0 commit comments

Comments
 (0)