Files
archived-ai/UPGRADE.md
Christopher Hertel 4d9639c760 bug #1794 [Agent] Fix dispatch of multiple tool instances of the same class (chr-hertel)
This PR was squashed before being merged into the main branch.

Discussion
----------

[Agent] Fix dispatch of multiple tool instances of the same class

| Q             | A
| ------------- | ---
| Bug fix?      | yes
| New feature?  | no
| Docs?         | no
| Issues        | Fix #1657
| License       | MIT

When multiple instances of the same class are registered as separate tools (e.g. two `Subagent` instances wrapping different agents), `Toolbox` would always dispatch to the first matching instance since it resolved executables by class name.

This PR fixes the issue by:

- **`MemoryToolFactory`**: Keys tool registrations by `spl_object_id` when objects are passed, allowing per-instance lookups. Builds `Tool` objects directly with `ExecutionReference` instead of going through `AsTool`/`convertAttribute`.
- **`Toolbox`**: Tries instance-specific lookup via `spl_object_id` first, falls back to class-based lookup. Maintains an `$instanceMap` to dispatch tool calls to the correct instance.
- **Removes `AbstractToolFactory`**: Inlines its logic into `ReflectionToolFactory`, since `MemoryToolFactory` no longer needs it.
- **Updates AI Bundle DI config** to reflect the removal of the abstract factory service.

Replaces #1658 1658

Commits
-------

fe195ea1 [Agent] Fix dispatch of multiple tool instances of the same class
2026-03-22 23:44:17 +01:00

358 lines
12 KiB
Markdown

UPGRADE FROM 0.6 to 0.7
=======================
Agent
-----
* The `Symfony\AI\Agent\Toolbox\ToolFactory\AbstractToolFactory` class has been removed. If you extended it to create
a custom tool factory, implement `ToolFactoryInterface` yourself and use `Symfony\AI\Platform\Contract\JsonSchema\Factory`
directly if needed.
* The `ToolFactoryInterface::getTool()` method signature has changed to accept `object|string` instead of `string`:
```diff
-public function getTool(string $reference): iterable;
+public function getTool(object|string $reference): iterable;
```
AI Bundle
---------
* The `api_catalog` option for `Ollama` has been removed as the catalog is now automatically fetched from the Ollama server
* The `api_key` option for `Ollama` is now `null` by default to allow the usage of a `ScopingHttpClient`
* The `endpoint` option for `Ollama` is now `null` by default to allow the usage of a `ScopingHttpClient`
Platform
-----
* `ModelCatalog` in `Ollama` has been replaced by `OllamaApiCatalog`
* `OllamaApiCatalog` in `Ollama` has been replaced to `ModelCatalog`
* `Ollama` model is now `final`
Store
-----
* The `Retriever` constructor has a new `?EventDispatcherInterface $eventDispatcher` parameter
inserted as 3rd argument before `LoggerInterface $logger`. If you pass `$logger` positionally,
update to use a named argument:
```diff
-$retriever = new Retriever($store, $vectorizer, $logger);
+$retriever = new Retriever($store, $vectorizer, logger: $logger);
```
UPGRADE FROM 0.5 to 0.6
=======================
AI Bundle
---------
* The `api_key` option for `ElevenLabs` is not required anymore if a `ScopedHttpClient` is used in `http_client` option
Platform
--------
* The `PlatformFactory` is now in charge of creating `ElevenLabsApiCatalog` if `apiCatalog` is provided as `true`
* `Symfony\AI\Platform\TokenUsage\TokenUsageInterface` has two new methods:
`getCacheCreationTokens()` and `getCacheReadTokens()`
UPGRADE FROM 0.4 to 0.5
=======================
Platform
--------
* The `hostUrl` parameter for `ElevenLabsClient` has been removed
* The `host` parameter for `ElevenLabsApiCatalog` has been removed
* The `hostUrl` parameter for `PlatformFactory::create()` in `ElevenLabs` has been renamed to `endpoint`
UPGRADE FROM 0.3 to 0.4
=======================
Agent
-----
* The `keepToolMessages` parameter of `AgentProcessor` has been removed and replaced with `excludeToolMessages`.
Tool messages (`AssistantMessage` with tool call invocations and `ToolCallMessage` with tool call results) are now
**preserved** in the `MessageBag` by default.
If you were opting in to keep tool messages, just remove the parameter:
```diff
-$processor = new AgentProcessor($toolbox, keepToolMessages: true);
+$processor = new AgentProcessor($toolbox);
```
If you were explicitly discarding tool messages, use the new parameter:
```diff
-$processor = new AgentProcessor($toolbox, keepToolMessages: false);
+$processor = new AgentProcessor($toolbox, excludeToolMessages: true);
```
If you were relying on the previous default (tool messages discarded), opt in explicitly:
```diff
-$processor = new AgentProcessor($toolbox);
+$processor = new AgentProcessor($toolbox, excludeToolMessages: true);
```
* The `Symfony\AI\Agent\Toolbox\Tool\Agent` class has been renamed to `Symfony\AI\Agent\Toolbox\Tool\Subagent`:
```diff
-use Symfony\AI\Agent\Toolbox\Tool\Agent;
+use Symfony\AI\Agent\Toolbox\Tool\Subagent;
-$agentTool = new Agent($agent);
+$subagent = new Subagent($agent);
```
AI Bundle
---------
* The service ID prefix for agent tools wrapping sub-agents has changed from `ai.toolbox.{agent}.agent_wrapper.`
to `ai.toolbox.{agent}.subagent.`:
```diff
-$container->get('ai.toolbox.my_agent.agent_wrapper.research_agent');
+$container->get('ai.toolbox.my_agent.subagent.research_agent');
```
* An indexer configured with a `source`, now wraps the indexer with a `Symfony\AI\Store\Indexer\ConfiguredSourceIndexer` decorator. This is
transparent - the configured source is still used by default, but can be overridden by passing a source to `index()`.
* The `host_url` parameter for `Ollama` platform has been renamed `endpoint`.
Platform
-------
* The `hostUrl` parameter for `OllamaClient` has been removed
* The `host` parameter for `OllamaApiCatalog` has been removed
* The `hostUrl` parameter for `PlatformFactory::create()` in `Ollama` has been renamed to `endpoint`
Store
-----
* The `Symfony\AI\Store\Indexer` class has been replaced with two specialized implementations:
- `Symfony\AI\Store\Indexer\SourceIndexer`: For indexing from sources (file paths, URLs, etc.) using a `LoaderInterface`
- `Symfony\AI\Store\Indexer\DocumentIndexer`: For indexing documents directly without a loader
```diff
-use Symfony\AI\Store\Indexer;
+use Symfony\AI\Store\Indexer\SourceIndexer;
-$indexer = new Indexer($loader, $vectorizer, $store, '/path/to/source');
-$indexer->index();
+$indexer = new SourceIndexer($loader, $vectorizer, $store);
+$indexer->index('/path/to/file');
```
For indexing documents directly:
```php
use Symfony\AI\Store\Document\TextDocument;use Symfony\AI\Store\Indexer\DocumentIndexer;
$indexer = new DocumentIndexer($processor);
$indexer->index(new TextDocument($id, 'content'));
$indexer->index([$document1, $document2]);
```
* The `Symfony\AI\Store\ConfiguredIndexer` class has been renamed to `Symfony\AI\Store\Indexer\ConfiguredSourceIndexer`:
```diff
-use Symfony\AI\Store\ConfiguredIndexer;
+use Symfony\AI\Store\Indexer\ConfiguredSourceIndexer;
-$indexer = new ConfiguredIndexer($innerIndexer, 'default-source');
+$indexer = new ConfiguredSourceIndexer($sourceIndexer, 'default-source');
```
* The `Symfony\AI\Store\IndexerInterface::index()` method signature has changed - the input parameter is no longer nullable:
```diff
-public function index(string|iterable|null $source = null, array $options = []): void;
+public function index(string|iterable|object $input, array $options = []): void;
```
* The `Symfony\AI\Store\IndexerInterface::withSource()` method has been removed. Use the `$source` parameter of `index()` instead:
```diff
-$indexer->withSource('/new/source')->index();
+$indexer->index('/new/source');
```
* The `Symfony\AI\Store\Document\TextDocument` and `Symfony\AI\Store\Document\VectorDocument` classes now only accept
`string` or `int` types for the `$id` property. The `Uuid` type has been removed and auto-casting to `uuid` in store
bridges has been removed as well:
```diff
use Symfony\AI\Store\Document\TextDocument;
use Symfony\AI\Store\Document\VectorDocument;
use Symfony\Component\Uid\Uuid;
-$textDoc = new TextDocument(Uuid::v4(), 'content');
+$textDoc = new TextDocument(Uuid::v4()->toString(), 'content');
-$vectorDoc = new VectorDocument(Uuid::v4(), [0.1, ...]);
+$vectorDoc = new VectorDocument(Uuid::v4()->toString(), [0.1, ...]);
```
* The `Symfony\AI\Store\Document\EmbeddableDocumentInterface::getId()` can only return `string` or `int` types now.
* Properties of `Symfony\AI\Store\Document\VectorDocument` and `Symfony\AI\Store\Document\Loader\Rss\RssItem` have been changed to private, use getters instead of public properties:
```diff
-$document->id;
-$document->metadata;
-$document->score;
-$document->vector;
+$document->getId();
+$document->getMetadata();
+$document->getScore();
+$document->getVector();
```
* The `StoreInterface::query()` method signature has changed to accept a `QueryInterface` instead of a `Vector`:
```diff
-use Symfony\AI\Platform\Vector\Vector;
+use Symfony\AI\Store\Query\VectorQuery;
-$results = $store->query($vector, ['limit' => 10]);
+$results = $store->query(new VectorQuery($vector), ['limit' => 10]);
```
The Store component now supports multiple query types through a query abstraction system. Available query types:
- `VectorQuery`: For vector similarity search (replaces the old `Vector` parameter)
- `TextQuery`: For full-text search (when supported by the store)
- `HybridQuery`: For combined vector + text search (when supported by the store)
Check if a store supports a specific query type using the new `supports()` method:
```php
if ($store->supports(VectorQuery::class)) {
$results = $store->query(new VectorQuery($vector));
}
```
* The `Symfony\AI\Store\Retriever` constructor signature has changed - the first two arguments have been swapped:
```diff
-use Symfony\AI\Store\Retriever;
-
-$retriever = new Retriever($vectorizer, $store, $logger);
+$retriever = new Retriever($store, $vectorizer, $logger);
```
This change aligns the constructor with the primary dependency (the store) being first, followed by the optional vectorizer.
UPGRADE FROM 0.2 to 0.3
=======================
Agent
-----
* The `Symfony\AI\Agent\Toolbox\StreamResult` class has been removed in favor of a `StreamListener`. Checks should now target
`Symfony\AI\Platform\Result\StreamResult` instead.
* The `Symfony\AI\Agent\Toolbox\Source\SourceMap` class has been renamed to `SourceCollection`. Its methods have also been renamed:
* `getSources()` is now `all()`
* `addSource()` is now `add()`
* The third argument of the `Symfony\AI\Agent\Toolbox\ToolResult::__construct()` method now expects a `SourceCollection` instead of an `array<int, Source>`
Platform
--------
* The `TokenUsageAggregation::__construct()` method signature has changed from variadic to accept an array of `TokenUsageInterface`
```diff
-$aggregation = new TokenUsageAggregation($usage1, $usage2);
+$aggregation = new TokenUsageAggregation([$usage1, $usage2]);
```
* The `Symfony\AI\Platform\CachedPlatform` has been renamed `Symfony\AI\Platform\Bridge\Cache\CachePlatform`
* To use it, consider the following steps:
* Run `composer require symfony/ai-cache-platform`
* Change `Symfony\AI\Platform\CachedPlatform` namespace usages to `Symfony\AI\Platform\Bridge\Cache\CachePlatform`
* The `ttl` option can be used in the configuration
* Adopt usage of class `Symfony\AI\Platform\Serializer\StructuredOutputSerializer` to `Symfony\AI\Platform\StructuredOutput\Serializer`
UPGRADE FROM 0.1 to 0.2
=======================
AI Bundle
---------
* Agents are now injected using their configuration name directly, instead of appending Agent or MultiAgent
```diff
public function __construct(
- private AgentInterface $blogAgent,
+ private AgentInterface $blog,
- private AgentInterface $supportMultiAgent,
+ private AgentInterface $support,
) {}
```
Agent
-----
* Constructor of `MemoryInputProcessor` now accepts an iterable of inputs instead of variadic arguments.
```php
use Symfony\AI\Agent\Memory\MemoryInputProcessor;
// Before
$processor = new MemoryInputProcessor($input1, $input2);
// After
$processor = new MemoryInputProcessor([$input1, $input2]);
```
Platform
--------
* The `ChoiceResult::__construct()` method signature has changed from variadic to accept an array of `ResultInterface`
```php
use Symfony\AI\Platform\Result\ChoiceResult;
// Before
$choiceResult = new ChoiceResult($result1, $result2);
// After
$choiceResult = new ChoiceResult([$result1, $result2]);
```
Store
-----
* The `StoreInterface::remove()` method was added to the interface
```php
public function remove(string|array $ids, array $options = []): void;
// Usage
$store->remove('vector-id-1');
$store->remove(['vid-1', 'vid-2']);
```
* The `StoreInterface::add()` method signature has changed from variadic to accept a single document or an array
*Before:*
```php
public function add(VectorDocument ...$documents): void;
// Usage
$store->add($document1, $document2);
$store->add(...$documents);
```
*After:*
```php
public function add(VectorDocument|array $documents): void;
// Usage
$store->add($document);
$store->add([$document1, $document2]);
$store->add($documents);
```