Skip to content

Commit 323f147

Browse files
committed
minor #810 [Doc][Cookbook] add dynamic toolbox guide for Symfony AI (DZunke)
This PR was merged into the main branch. Discussion ---------- [Doc][Cookbook] add dynamic toolbox guide for Symfony AI | Q | A | ------------- | --- | Bug fix? | no | New feature? | no | Docs? | yes | License | MIT Maybe it is interetsing to have a cookbook around the idea of having a more dynamic toolbox. I lead the cookbook around the demo application, utilizing the blog agent. Thought this might be a bit easier to follow as a cookbook. Commits ------- f2a1554 [Doc][Cookbook] add dynamic toolbox guide for Symfony AI
2 parents beadd5c + f2a1554 commit 323f147

File tree

1 file changed

+199
-0
lines changed

1 file changed

+199
-0
lines changed

docs/cookbook/dynamic-tools.rst

Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
Dynamic Toolbox for Flexible Tools
2+
==================================
3+
4+
This guide will lead you through the creation of a dynamic Toolbox for Symfony AI.
5+
A dynamic Toolbox allows you not only to add or remove tools at runtime, but also to
6+
customize tool names and descriptions.
7+
8+
Prerequisites
9+
10+
* Symfony AI Platform component
11+
* Symfony AI Agent component
12+
* A language model supporting tools (e.g., gpt-5-mini)
13+
14+
Example Use Cases
15+
-----------------
16+
17+
The example use-cases assume that you are working with the Symfony AI demo application, where an agent named
18+
`blog` is already defined with a set of tools.
19+
20+
Requirement: Set Up Dynamic Toolbox Class
21+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
22+
23+
First, create a class that implements the `ToolboxInterface` and, in its constructor, accepts
24+
another `ToolboxInterface` instance to delegate calls to the original toolbox. This implements the decorator
25+
pattern.
26+
27+
28+
::
29+
30+
namespace App;
31+
32+
use Symfony\AI\Agent\Toolbox\ToolboxInterface;
33+
use Symfony\AI\Agent\Toolbox\ToolResult;
34+
use Symfony\AI\Platform\Result\ToolCall;
35+
use Symfony\Component\DependencyInjection\Attribute\AsDecorator;
36+
use Symfony\Component\DependencyInjection\Attribute\AutowireDecorated;
37+
38+
#[AsDecorator('ai.toolbox.blog')]
39+
class DynamicToolbox implements ToolboxInterface
40+
{
41+
private ToolboxInterface $innerToolbox;
42+
43+
public function __construct(#[AutowireDecorated] ToolboxInterface $innerToolbox)
44+
{
45+
$this->innerToolbox = $innerToolbox;
46+
}
47+
48+
public function getTools(): array
49+
{
50+
return $this->innerToolbox->getTools();
51+
}
52+
53+
public function execute(ToolCall $toolCall): ToolResult
54+
{
55+
return $this->innerToolbox->execute($toolCall);
56+
}
57+
}
58+
59+
By utilizing the `AsDecorator` attribute, this class will automatically decorate the existing toolbox
60+
for the `blog` agent, and the `AutowireDecorated` attribute will inject the original toolbox instance to
61+
ensure that existing functionality is preserved and does not need to be reimplemented.
62+
63+
Case 1: Customizing Tools at Runtime
64+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
65+
66+
To change a tool description dynamically, override the `getTools` method in the
67+
`DynamicToolbox` class. Here is an example of how to modify the description of a specific tool.
68+
69+
70+
Let's assume that the existing tool `similarity_search` should not have a general-purpose description,
71+
but instead a description that blocks general questions unless someone asks very politely, in which case it
72+
allows use of the tool.
73+
74+
75+
::
76+
77+
use Symfony\AI\Platform\Tool\Tool;
78+
79+
// ...existing code...
80+
public function getTools(): array
81+
{
82+
$tools = $this->innerToolbox->getTools();
83+
foreach ($tools as $index => $tool) {
84+
if ($tool->getName() !== 'similarity_search') {
85+
continue;
86+
}
87+
88+
$tools[$index] = new Tool(
89+
$tool->getReference(),
90+
$tool->getName(),
91+
'Similarity search, but always add the word "please" to the searchTerm.',
92+
$tool->getParameters()
93+
);
94+
}
95+
96+
return $tools;
97+
}
98+
99+
100+
With this implementation, whenever the `similarity_search` tool is requested, it will have the new
101+
description that enforces the search term argument to include the word "please". For example,
102+
a question like "Find articles about Symfony" would become "articles symfony please".
103+
104+
Generally, this approach to customizing tools can be utilized to let users experiment with descriptions
105+
to optimize for their use case or minimize the tokens used for complex tools.
106+
107+
Case 2: Removing a Tool
108+
~~~~~~~~~~~~~~~~~~~~~~~
109+
110+
111+
To remove a tool dynamically, for example due to missing feature toggles, you can filter out the tool
112+
in the `getTools` method. In the following example, we simulate a feature toggle that is disabled, so
113+
the clock tool must not be available to the agent registered for the blog toolbox by default.
114+
115+
116+
::
117+
118+
public function getTools(): array
119+
{
120+
$tools = $this->innerToolbox->getTools();
121+
122+
$toggleClockFeature = false; // Simulate real feature toggle check
123+
if ($toggleClockFeature === false) {
124+
$tools = array_filter(
125+
$tools,
126+
static fn (Tool $tool) => $tool->getName() !== 'clock'
127+
);
128+
}
129+
130+
return $tools;
131+
}
132+
133+
134+
With this, and utilizing the blog example in the Symfony AI demo application, the agent will not be able
135+
to tell the date or time. Only if the `toggleClockFeature` is set to `true` will the agent answer with the
136+
current date and time again.
137+
138+
Case 3: Adding a Tool
139+
~~~~~~~~~~~~~~~~~~~~~
140+
141+
142+
To add a new tool dynamically, instantiate a new `Tool` object and append it to the list of tools
143+
returned by the `getTools` method. In the following example, we add a simple echo tool that returns whatever
144+
input it receives. Notably, this example will also intercept the requested tool execution and respond directly
145+
with an uppercased version of the input.
146+
147+
148+
::
149+
150+
use Symfony\AI\Platform\Tool\ExecutionReference;
151+
use Symfony\AI\Platform\Tool\Tool;
152+
153+
// ...existing code...
154+
public function getTools(): array
155+
{
156+
$tools = $this->innerToolbox->getTools();
157+
158+
$tools[] = new Tool(
159+
new ExecutionReference(self::class), // Required, not used
160+
'echo',
161+
'Echoes the input provided to it.',
162+
[
163+
'type' => 'object',
164+
'properties' => [
165+
'input' => [
166+
'type' => 'string',
167+
'description' => 'string used for similarity search',
168+
],
169+
],
170+
'required' => ['input'],
171+
'additionalProperties' => false,
172+
],
173+
);
174+
175+
return $tools;
176+
}
177+
178+
public function execute(ToolCall $toolCall): ToolResult
179+
{
180+
if ($toolCall->getName() === 'echo') {
181+
$args = $toolCall->getArguments();
182+
return new ToolResult($toolCall, \strtoupper($args['input']));
183+
}
184+
185+
return $this->innerToolbox->execute($toolCall);
186+
}
187+
188+
189+
With this implementation, the `echo` tool will be available to the agent alongside the existing tools.
190+
You can test this by using the blog example again and explicitly asking the agent to utilize the `echo` tool.
191+
192+
193+
Example:
194+
195+
196+
User: "What does the echo say?"
197+
198+
Blog Agent: "The echo says: 'WHAT DOES THE ECHO SAY?' If you have any other questions
199+
or need further assistance, feel free to ask!"

0 commit comments

Comments
 (0)