From 934aae50cf259185314c7849e12c9872ddf40156 Mon Sep 17 00:00:00 2001 From: glady Date: Fri, 12 Sep 2025 13:06:38 +0200 Subject: [PATCH 01/11] add deptrac for checking dependencies, e.g. from platform to agent --- composer.json | 5 +++-- deptrac.yaml | 26 ++++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 2 deletions(-) create mode 100644 deptrac.yaml diff --git a/composer.json b/composer.json index d94d57cec..cd3d97e5a 100644 --- a/composer.json +++ b/composer.json @@ -7,9 +7,10 @@ ], "require-dev": { "php": ">=8.2", + "deptrac/deptrac": "^4.2", "php-cs-fixer/shim": "^3.75", - "symfony/finder": "^6.4 || ^7.0", - "symfony/filesystem": "^6.4 || ^7.0" + "symfony/filesystem": "^6.4 || ^7.0", + "symfony/finder": "^6.4 || ^7.0" }, "config": { "sort-packages": true diff --git a/deptrac.yaml b/deptrac.yaml new file mode 100644 index 000000000..8820ae52d --- /dev/null +++ b/deptrac.yaml @@ -0,0 +1,26 @@ +deptrac: + paths: + - ./src + exclude_files: + - '#.*test.*#' + layers: + - name: Agent + collectors: + - type: classLike + value: Symfony\\AI\\Agent.* + - name: Platform + collectors: + - type: classLike + value: Symfony\\AI\\Platform.* + - name: Store + collectors: + - type: classLike + value: Symfony\\AI\\Store.* + ruleset: + Platform: ~ + Agent: + - Platform + - orm + - Store + Store: + - Platform From 627352fd1bbe5a4eb1baeee2c874224f3305b05d Mon Sep 17 00:00:00 2001 From: glady Date: Fri, 12 Sep 2025 13:09:44 +0200 Subject: [PATCH 02/11] enable github action --- .github/workflows/deptrac.yaml | 67 ++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 .github/workflows/deptrac.yaml diff --git a/.github/workflows/deptrac.yaml b/.github/workflows/deptrac.yaml new file mode 100644 index 000000000..0cd3fed46 --- /dev/null +++ b/.github/workflows/deptrac.yaml @@ -0,0 +1,67 @@ +name: deptrac + +on: + push: + paths-ignore: + - 'src/*/doc/**' + - 'src/**/*.md' + pull_request: + paths-ignore: + - 'src/*/doc/**' + - 'src/**/*.md' + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +jobs: + deptrac: + name: deptrac + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + php-version: [ '8.4' ] + steps: + - name: Checkout + uses: actions/checkout@v5 + + - name: Configure environment + run: | + echo COLUMNS=120 >> $GITHUB_ENV + echo COMPOSER_UP='composer update --no-progress --no-interaction --ansi --ignore-platform-req=ext-mongodb' >> $GITHUB_ENV + echo DEPTRAC='vendor/bin/deptrac' >> $GITHUB_ENV + + PACKAGES=$(find src/ -mindepth 2 -type f -name composer.json -not -path "*/vendor/*" -printf '%h\n' | sed 's/^src\///' | grep -Ev "examples" | sort | tr '\n' ' ') + echo "Packages: $PACKAGES" + echo "PACKAGES=$PACKAGES" >> $GITHUB_ENV + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php-version }} + + - name: Get composer cache directory + id: composer-cache + run: | + echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT + + - name: Cache packages dependencies + uses: actions/cache@v4 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: ${{ runner.os }}-composer-packages-${{ matrix.php-version }}-${{ hashFiles('src/**/composer.json') }} + restore-keys: | + ${{ runner.os }}-composer-packages-${{ matrix.php-version }} + + - name: Install root dependencies + uses: ramsey/composer-install@v3 + + - name: Build root packages + run: php .github/build-packages.php + + - name: Run DEPTRAC on packages + run: | + source .github/workflows/.utils.sh + + echo "$PACKAGES" | xargs -n1 | parallel -j +3 "_run_task {} '(cd src/{} && $COMPOSER_UP && $DEPTRAC)'" From 37e02d671a32ada5bd0c9694eb349d6a9b468f07 Mon Sep 17 00:00:00 2001 From: glady Date: Fri, 12 Sep 2025 13:26:09 +0200 Subject: [PATCH 03/11] fix github action --- .github/workflows/deptrac.yaml | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/.github/workflows/deptrac.yaml b/.github/workflows/deptrac.yaml index 0cd3fed46..6edc45787 100644 --- a/.github/workflows/deptrac.yaml +++ b/.github/workflows/deptrac.yaml @@ -30,11 +30,6 @@ jobs: run: | echo COLUMNS=120 >> $GITHUB_ENV echo COMPOSER_UP='composer update --no-progress --no-interaction --ansi --ignore-platform-req=ext-mongodb' >> $GITHUB_ENV - echo DEPTRAC='vendor/bin/deptrac' >> $GITHUB_ENV - - PACKAGES=$(find src/ -mindepth 2 -type f -name composer.json -not -path "*/vendor/*" -printf '%h\n' | sed 's/^src\///' | grep -Ev "examples" | sort | tr '\n' ' ') - echo "Packages: $PACKAGES" - echo "PACKAGES=$PACKAGES" >> $GITHUB_ENV - name: Setup PHP uses: shivammathur/setup-php@v2 @@ -60,8 +55,5 @@ jobs: - name: Build root packages run: php .github/build-packages.php - - name: Run DEPTRAC on packages - run: | - source .github/workflows/.utils.sh - - echo "$PACKAGES" | xargs -n1 | parallel -j +3 "_run_task {} '(cd src/{} && $COMPOSER_UP && $DEPTRAC)'" + - name: Run DEPTRAC + run: $COMPOSER_UP && vendor/bin/deptrac From d9eaebb8d308aa9d05901596192c8601859d2ffd Mon Sep 17 00:00:00 2001 From: glady Date: Fri, 12 Sep 2025 14:13:14 +0200 Subject: [PATCH 04/11] desired change in composer.json --- src/platform/composer.json | 1 - 1 file changed, 1 deletion(-) diff --git a/src/platform/composer.json b/src/platform/composer.json index fc7da2e3e..d4a6489fb 100644 --- a/src/platform/composer.json +++ b/src/platform/composer.json @@ -58,7 +58,6 @@ "phpstan/phpstan": "^2.1.17", "phpstan/phpstan-symfony": "^2.0.6", "phpunit/phpunit": "^11.5", - "symfony/ai-agent": "@dev", "symfony/console": "^6.4 || ^7.1", "symfony/dotenv": "^6.4 || ^7.1", "symfony/event-dispatcher": "^6.4 || ^7.1", From 5abcc00d573dd2f8953dab52a9d99ff1662c6d11 Mon Sep 17 00:00:00 2001 From: glady Date: Fri, 12 Sep 2025 17:30:14 +0200 Subject: [PATCH 05/11] added TokenOutputProcessor on Agent, consuming a TokenUsageDeterminer from Platform. --- examples/gemini/token-metadata.php | 6 +- examples/mistral/token-metadata.php | 6 +- examples/openai/token-metadata.php | 6 +- examples/perplexity/token-metadata.php | 7 ++- examples/vertexai/token-metadata.php | 5 +- .../OutputProcessor/TokenOutputProcessor.php | 29 +++++++++ ...Processor.php => TokenUsageDeterminer.php} | 14 ++--- ...Processor.php => TokenUsageDeterminer.php} | 14 ++--- ...Processor.php => TokenUsageDeterminer.php} | 14 ++--- ...Processor.php => TokenUsageDeterminer.php} | 14 ++--- ...Processor.php => TokenUsageDeterminer.php} | 16 ++--- .../Result/TokenUsageDeterminerInterface.php | 17 ++++++ ...rTest.php => TokenUsageDeterminerTest.php} | 60 ++++++------------- ...rTest.php => TokenUsageDeterminerTest.php} | 59 ++++++------------ ...rTest.php => TokenUsageDeterminerTest.php} | 59 ++++++------------ ...rTest.php => TokenUsageDeterminerTest.php} | 51 +++++----------- ...rTest.php => TokenUsageDeterminerTest.php} | 57 ++++++------------ 17 files changed, 190 insertions(+), 244 deletions(-) create mode 100644 src/agent/src/OutputProcessor/TokenOutputProcessor.php rename src/platform/src/Bridge/Gemini/{TokenOutputProcessor.php => TokenUsageDeterminer.php} (78%) rename src/platform/src/Bridge/Mistral/{TokenOutputProcessor.php => TokenUsageDeterminer.php} (80%) rename src/platform/src/Bridge/OpenAi/{TokenOutputProcessor.php => TokenUsageDeterminer.php} (80%) rename src/platform/src/Bridge/Perplexity/{TokenOutputProcessor.php => TokenUsageDeterminer.php} (75%) rename src/platform/src/Bridge/VertexAi/{TokenOutputProcessor.php => TokenUsageDeterminer.php} (85%) create mode 100644 src/platform/src/Result/TokenUsageDeterminerInterface.php rename src/platform/tests/Bridge/Gemini/{TokenOutputProcessorTest.php => TokenUsageDeterminerTest.php} (69%) rename src/platform/tests/Bridge/Mistral/{TokenOutputProcessorTest.php => TokenUsageDeterminerTest.php} (70%) rename src/platform/tests/Bridge/OpenAi/{TokenOutputProcessorTest.php => TokenUsageDeterminerTest.php} (70%) rename src/platform/tests/Bridge/Perplexity/{TokenOutputProcessorTest.php => TokenUsageDeterminerTest.php} (68%) rename src/platform/tests/Bridge/VertexAi/{TokenOutputProcessorTest.php => TokenUsageDeterminerTest.php} (73%) diff --git a/examples/gemini/token-metadata.php b/examples/gemini/token-metadata.php index 9bddd429c..e2a05f3d5 100644 --- a/examples/gemini/token-metadata.php +++ b/examples/gemini/token-metadata.php @@ -10,9 +10,10 @@ */ use Symfony\AI\Agent\Agent; +use Symfony\AI\Agent\OutputProcessor\TokenOutputProcessor; use Symfony\AI\Platform\Bridge\Gemini\Gemini; use Symfony\AI\Platform\Bridge\Gemini\PlatformFactory; -use Symfony\AI\Platform\Bridge\Gemini\TokenOutputProcessor; +use Symfony\AI\Platform\Bridge\Gemini\TokenUsageDeterminer; use Symfony\AI\Platform\Message\Message; use Symfony\AI\Platform\Message\MessageBag; @@ -20,8 +21,9 @@ $platform = PlatformFactory::create(env('GEMINI_API_KEY'), http_client()); $model = new Gemini(Gemini::GEMINI_2_FLASH); +$tokenUsageDeterminer = new TokenUsageDeterminer(); -$agent = new Agent($platform, $model, outputProcessors: [new TokenOutputProcessor()], logger: logger()); +$agent = new Agent($platform, $model, outputProcessors: [new TokenOutputProcessor($tokenUsageDeterminer)], logger: logger()); $messages = new MessageBag( Message::forSystem('You are a pirate and you write funny.'), Message::ofUser('What is the Symfony framework?'), diff --git a/examples/mistral/token-metadata.php b/examples/mistral/token-metadata.php index 76bd84af3..036f9fb81 100644 --- a/examples/mistral/token-metadata.php +++ b/examples/mistral/token-metadata.php @@ -10,9 +10,10 @@ */ use Symfony\AI\Agent\Agent; +use Symfony\AI\Agent\OutputProcessor\TokenOutputProcessor; use Symfony\AI\Platform\Bridge\Mistral\Mistral; use Symfony\AI\Platform\Bridge\Mistral\PlatformFactory; -use Symfony\AI\Platform\Bridge\Mistral\TokenOutputProcessor; +use Symfony\AI\Platform\Bridge\Mistral\TokenUsageDeterminer; use Symfony\AI\Platform\Message\Message; use Symfony\AI\Platform\Message\MessageBag; @@ -20,8 +21,9 @@ $platform = PlatformFactory::create(env('MISTRAL_API_KEY'), http_client()); $model = new Mistral(); +$tokenUsageDeterminer = new TokenUsageDeterminer(); -$agent = new Agent($platform, $model, outputProcessors: [new TokenOutputProcessor()], logger: logger()); +$agent = new Agent($platform, $model, outputProcessors: [new TokenOutputProcessor($tokenUsageDeterminer)], logger: logger()); $messages = new MessageBag( Message::forSystem('You are a pirate and you write funny.'), diff --git a/examples/openai/token-metadata.php b/examples/openai/token-metadata.php index 7f115bcfe..dcb94f39c 100644 --- a/examples/openai/token-metadata.php +++ b/examples/openai/token-metadata.php @@ -10,9 +10,10 @@ */ use Symfony\AI\Agent\Agent; +use Symfony\AI\Agent\OutputProcessor\TokenOutputProcessor; use Symfony\AI\Platform\Bridge\OpenAi\Gpt; use Symfony\AI\Platform\Bridge\OpenAi\PlatformFactory; -use Symfony\AI\Platform\Bridge\OpenAi\TokenOutputProcessor; +use Symfony\AI\Platform\Bridge\OpenAi\TokenUsageDeterminer; use Symfony\AI\Platform\Message\Message; use Symfony\AI\Platform\Message\MessageBag; @@ -22,8 +23,9 @@ $model = new Gpt(Gpt::GPT_4O_MINI, [ 'temperature' => 0.5, // default options for the model ]); +$tokenUsageDeterminer = new TokenUsageDeterminer(); -$agent = new Agent($platform, $model, outputProcessors: [new TokenOutputProcessor()], logger: logger()); +$agent = new Agent($platform, $model, outputProcessors: [new TokenOutputProcessor($tokenUsageDeterminer)], logger: logger()); $messages = new MessageBag( Message::forSystem('You are a pirate and you write funny.'), Message::ofUser('What is the Symfony framework?'), diff --git a/examples/perplexity/token-metadata.php b/examples/perplexity/token-metadata.php index 3f32e2597..033f0918c 100644 --- a/examples/perplexity/token-metadata.php +++ b/examples/perplexity/token-metadata.php @@ -10,9 +10,10 @@ */ use Symfony\AI\Agent\Agent; +use Symfony\AI\Agent\OutputProcessor\TokenOutputProcessor; use Symfony\AI\Platform\Bridge\Perplexity\Perplexity; use Symfony\AI\Platform\Bridge\Perplexity\PlatformFactory; -use Symfony\AI\Platform\Bridge\Perplexity\TokenOutputProcessor; +use Symfony\AI\Platform\Bridge\Perplexity\TokenUsageDeterminer; use Symfony\AI\Platform\Message\Message; use Symfony\AI\Platform\Message\MessageBag; @@ -20,7 +21,9 @@ $platform = PlatformFactory::create(env('PERPLEXITY_API_KEY'), http_client()); $model = new Perplexity(); -$agent = new Agent($platform, $model, outputProcessors: [new TokenOutputProcessor()], logger: logger()); +$tokenUsageDeterminer = new TokenUsageDeterminer(); + +$agent = new Agent($platform, $model, outputProcessors: [new TokenOutputProcessor($tokenUsageDeterminer)], logger: logger()); $messages = new MessageBag( Message::forSystem('You are a pirate and you write funny.'), diff --git a/examples/vertexai/token-metadata.php b/examples/vertexai/token-metadata.php index 56ed15575..4d8ecf643 100644 --- a/examples/vertexai/token-metadata.php +++ b/examples/vertexai/token-metadata.php @@ -10,8 +10,10 @@ */ use Symfony\AI\Agent\Agent; +use Symfony\AI\Agent\OutputProcessor\TokenOutputProcessor; use Symfony\AI\Platform\Bridge\VertexAi\Gemini\Model; use Symfony\AI\Platform\Bridge\VertexAi\PlatformFactory; +use Symfony\AI\Platform\Bridge\VertexAi\TokenUsageDeterminer; use Symfony\AI\Platform\Message\Message; use Symfony\AI\Platform\Message\MessageBag; @@ -19,8 +21,9 @@ $platform = PlatformFactory::create(env('GOOGLE_CLOUD_LOCATION'), env('GOOGLE_CLOUD_PROJECT'), adc_aware_http_client()); $model = new Model(Model::GEMINI_2_0_FLASH_LITE); +$tokenUsageDeterminer = new TokenUsageDeterminer(); -$agent = new Agent($platform, $model, outputProcessors: [new Symfony\AI\Platform\Bridge\VertexAi\TokenOutputProcessor()], logger: logger()); +$agent = new Agent($platform, $model, outputProcessors: [new TokenOutputProcessor($tokenUsageDeterminer)], logger: logger()); $messages = new MessageBag( Message::forSystem('You are an expert assistant in animal study.'), Message::ofUser('What does a cat usually eat?'), diff --git a/src/agent/src/OutputProcessor/TokenOutputProcessor.php b/src/agent/src/OutputProcessor/TokenOutputProcessor.php new file mode 100644 index 000000000..4a46df156 --- /dev/null +++ b/src/agent/src/OutputProcessor/TokenOutputProcessor.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\AI\Agent\OutputProcessor; + +use Symfony\AI\Agent\Output; +use Symfony\AI\Agent\OutputProcessorInterface; +use Symfony\AI\Platform\Result\TokenUsageDeterminerInterface; + +class TokenOutputProcessor implements OutputProcessorInterface +{ + public function __construct( + private readonly TokenUsageDeterminerInterface $tokenUsageDeterminer, + ) { + } + + public function processOutput(Output $output): void + { + $this->tokenUsageDeterminer->determineTokenUsage($output->result); + } +} diff --git a/src/platform/src/Bridge/Gemini/TokenOutputProcessor.php b/src/platform/src/Bridge/Gemini/TokenUsageDeterminer.php similarity index 78% rename from src/platform/src/Bridge/Gemini/TokenOutputProcessor.php rename to src/platform/src/Bridge/Gemini/TokenUsageDeterminer.php index f55a41172..277fc1525 100644 --- a/src/platform/src/Bridge/Gemini/TokenOutputProcessor.php +++ b/src/platform/src/Bridge/Gemini/TokenUsageDeterminer.php @@ -11,27 +11,27 @@ namespace Symfony\AI\Platform\Bridge\Gemini; -use Symfony\AI\Agent\Output; -use Symfony\AI\Agent\OutputProcessorInterface; use Symfony\AI\Platform\Metadata\TokenUsage; +use Symfony\AI\Platform\Result\ResultInterface; use Symfony\AI\Platform\Result\StreamResult; +use Symfony\AI\Platform\Result\TokenUsageDeterminerInterface; use Symfony\Contracts\HttpClient\ResponseInterface; -final class TokenOutputProcessor implements OutputProcessorInterface +final class TokenUsageDeterminer implements TokenUsageDeterminerInterface { - public function processOutput(Output $output): void + public function determineTokenUsage(ResultInterface $result): void { - if ($output->result instanceof StreamResult) { + if ($result instanceof StreamResult) { // Streams have to be handled manually as the tokens are part of the streamed chunks return; } - $rawResponse = $output->result->getRawResult()?->getObject(); + $rawResponse = $result->getRawResult()?->getObject(); if (!$rawResponse instanceof ResponseInterface) { return; } - $metadata = $output->result->getMetadata(); + $metadata = $result->getMetadata(); $tokenUsage = new TokenUsage(); diff --git a/src/platform/src/Bridge/Mistral/TokenOutputProcessor.php b/src/platform/src/Bridge/Mistral/TokenUsageDeterminer.php similarity index 80% rename from src/platform/src/Bridge/Mistral/TokenOutputProcessor.php rename to src/platform/src/Bridge/Mistral/TokenUsageDeterminer.php index 44d5ad8b6..97dff8810 100644 --- a/src/platform/src/Bridge/Mistral/TokenOutputProcessor.php +++ b/src/platform/src/Bridge/Mistral/TokenUsageDeterminer.php @@ -11,30 +11,30 @@ namespace Symfony\AI\Platform\Bridge\Mistral; -use Symfony\AI\Agent\Output; -use Symfony\AI\Agent\OutputProcessorInterface; use Symfony\AI\Platform\Metadata\TokenUsage; +use Symfony\AI\Platform\Result\ResultInterface; use Symfony\AI\Platform\Result\StreamResult; +use Symfony\AI\Platform\Result\TokenUsageDeterminerInterface; use Symfony\Contracts\HttpClient\ResponseInterface; /** * @author Quentin Fahrner */ -final class TokenOutputProcessor implements OutputProcessorInterface +final class TokenUsageDeterminer implements TokenUsageDeterminerInterface { - public function processOutput(Output $output): void + public function determineTokenUsage(ResultInterface $result): void { - if ($output->result instanceof StreamResult) { + if ($result instanceof StreamResult) { // Streams have to be handled manually as the tokens are part of the streamed chunks return; } - $rawResponse = $output->result->getRawResult()?->getObject(); + $rawResponse = $result->getRawResult()?->getObject(); if (!$rawResponse instanceof ResponseInterface) { return; } - $metadata = $output->result->getMetadata(); + $metadata = $result->getMetadata(); $headers = $rawResponse->getHeaders(false); $remainingTokensMinute = $headers['x-ratelimit-limit-tokens-minute'][0] ?? null; diff --git a/src/platform/src/Bridge/OpenAi/TokenOutputProcessor.php b/src/platform/src/Bridge/OpenAi/TokenUsageDeterminer.php similarity index 80% rename from src/platform/src/Bridge/OpenAi/TokenOutputProcessor.php rename to src/platform/src/Bridge/OpenAi/TokenUsageDeterminer.php index a313efd4a..d76c9e77a 100644 --- a/src/platform/src/Bridge/OpenAi/TokenOutputProcessor.php +++ b/src/platform/src/Bridge/OpenAi/TokenUsageDeterminer.php @@ -11,30 +11,30 @@ namespace Symfony\AI\Platform\Bridge\OpenAi; -use Symfony\AI\Agent\Output; -use Symfony\AI\Agent\OutputProcessorInterface; use Symfony\AI\Platform\Metadata\TokenUsage; +use Symfony\AI\Platform\Result\ResultInterface; use Symfony\AI\Platform\Result\StreamResult; +use Symfony\AI\Platform\Result\TokenUsageDeterminerInterface; use Symfony\Contracts\HttpClient\ResponseInterface; /** * @author Denis Zunke */ -final class TokenOutputProcessor implements OutputProcessorInterface +final class TokenUsageDeterminer implements TokenUsageDeterminerInterface { - public function processOutput(Output $output): void + public function determineTokenUsage(ResultInterface $result): void { - if ($output->result instanceof StreamResult) { + if ($result instanceof StreamResult) { // Streams have to be handled manually as the tokens are part of the streamed chunks return; } - $rawResponse = $output->result->getRawResult()?->getObject(); + $rawResponse = $result->getRawResult()?->getObject(); if (!$rawResponse instanceof ResponseInterface) { return; } - $metadata = $output->result->getMetadata(); + $metadata = $result->getMetadata(); $remainingTokens = $rawResponse->getHeaders(false)['x-ratelimit-remaining-tokens'][0] ?? null; $tokenUsage = new TokenUsage( diff --git a/src/platform/src/Bridge/Perplexity/TokenOutputProcessor.php b/src/platform/src/Bridge/Perplexity/TokenUsageDeterminer.php similarity index 75% rename from src/platform/src/Bridge/Perplexity/TokenOutputProcessor.php rename to src/platform/src/Bridge/Perplexity/TokenUsageDeterminer.php index 3a8d624fc..4594e1d99 100644 --- a/src/platform/src/Bridge/Perplexity/TokenOutputProcessor.php +++ b/src/platform/src/Bridge/Perplexity/TokenUsageDeterminer.php @@ -11,25 +11,25 @@ namespace Symfony\AI\Platform\Bridge\Perplexity; -use Symfony\AI\Agent\Output; -use Symfony\AI\Agent\OutputProcessorInterface; use Symfony\AI\Platform\Metadata\TokenUsage; +use Symfony\AI\Platform\Result\ResultInterface; use Symfony\AI\Platform\Result\StreamResult; +use Symfony\AI\Platform\Result\TokenUsageDeterminerInterface; use Symfony\Contracts\HttpClient\ResponseInterface; /** * @author Mathieu Santostefano */ -final class TokenOutputProcessor implements OutputProcessorInterface +final class TokenUsageDeterminer implements TokenUsageDeterminerInterface { - public function processOutput(Output $output): void + public function determineTokenUsage(ResultInterface $result): void { - if ($output->result instanceof StreamResult) { + if ($result instanceof StreamResult) { // Streams have to be handled manually as the tokens are part of the streamed chunks return; } - $rawResponse = $output->result->getRawResult()?->getObject(); + $rawResponse = $result->getRawResult()?->getObject(); if (!$rawResponse instanceof ResponseInterface) { return; } @@ -40,7 +40,7 @@ public function processOutput(Output $output): void return; } - $metadata = $output->result->getMetadata(); + $metadata = $result->getMetadata(); $tokenUsage = new TokenUsage(); $usage = $content['usage']; diff --git a/src/platform/src/Bridge/VertexAi/TokenOutputProcessor.php b/src/platform/src/Bridge/VertexAi/TokenUsageDeterminer.php similarity index 85% rename from src/platform/src/Bridge/VertexAi/TokenOutputProcessor.php rename to src/platform/src/Bridge/VertexAi/TokenUsageDeterminer.php index 4d8841bfc..b1befb32e 100644 --- a/src/platform/src/Bridge/VertexAi/TokenOutputProcessor.php +++ b/src/platform/src/Bridge/VertexAi/TokenUsageDeterminer.php @@ -11,11 +11,11 @@ namespace Symfony\AI\Platform\Bridge\VertexAi; -use Symfony\AI\Agent\Output; -use Symfony\AI\Agent\OutputProcessorInterface; use Symfony\AI\Platform\Metadata\Metadata; use Symfony\AI\Platform\Metadata\TokenUsage; +use Symfony\AI\Platform\Result\ResultInterface; use Symfony\AI\Platform\Result\StreamResult; +use Symfony\AI\Platform\Result\TokenUsageDeterminerInterface; use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface; use Symfony\Contracts\HttpClient\Exception\DecodingExceptionInterface; use Symfony\Contracts\HttpClient\Exception\RedirectionExceptionInterface; @@ -26,7 +26,7 @@ /** * @author Junaid Farooq */ -final class TokenOutputProcessor implements OutputProcessorInterface +final class TokenUsageDeterminer implements TokenUsageDeterminerInterface { /** * @throws TransportExceptionInterface @@ -35,15 +35,15 @@ final class TokenOutputProcessor implements OutputProcessorInterface * @throws DecodingExceptionInterface * @throws ClientExceptionInterface */ - public function processOutput(Output $output): void + public function determineTokenUsage(ResultInterface $result): void { $tokenUsage = new TokenUsage(); - $metadata = $output->result->getMetadata(); + $metadata = $result->getMetadata(); - if ($output->result instanceof StreamResult) { + if ($result instanceof StreamResult) { $lastChunk = null; - foreach ($output->result->getContent() as $chunk) { + foreach ($result->getContent() as $chunk) { // Store last event that contains usage metadata if (isset($chunk['usageMetadata'])) { $lastChunk = $chunk; @@ -57,7 +57,7 @@ public function processOutput(Output $output): void return; } - $rawResponse = $output->result->getRawResult()?->getObject(); + $rawResponse = $result->getRawResult()?->getObject(); if (!$rawResponse instanceof ResponseInterface) { return; } diff --git a/src/platform/src/Result/TokenUsageDeterminerInterface.php b/src/platform/src/Result/TokenUsageDeterminerInterface.php new file mode 100644 index 000000000..d5bf4af6c --- /dev/null +++ b/src/platform/src/Result/TokenUsageDeterminerInterface.php @@ -0,0 +1,17 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\AI\Platform\Result; + +interface TokenUsageDeterminerInterface +{ + public function determineTokenUsage(ResultInterface $result): void; +} diff --git a/src/platform/tests/Bridge/Gemini/TokenOutputProcessorTest.php b/src/platform/tests/Bridge/Gemini/TokenUsageDeterminerTest.php similarity index 69% rename from src/platform/tests/Bridge/Gemini/TokenOutputProcessorTest.php rename to src/platform/tests/Bridge/Gemini/TokenUsageDeterminerTest.php index e25994282..83ace31fe 100644 --- a/src/platform/tests/Bridge/Gemini/TokenOutputProcessorTest.php +++ b/src/platform/tests/Bridge/Gemini/TokenUsageDeterminerTest.php @@ -15,63 +15,54 @@ use PHPUnit\Framework\Attributes\Small; use PHPUnit\Framework\Attributes\UsesClass; use PHPUnit\Framework\TestCase; -use Symfony\AI\Agent\Output; -use Symfony\AI\Platform\Bridge\Gemini\TokenOutputProcessor; -use Symfony\AI\Platform\Message\MessageBag; +use Symfony\AI\Platform\Bridge\Gemini\TokenUsageDeterminer; use Symfony\AI\Platform\Metadata\Metadata; use Symfony\AI\Platform\Metadata\TokenUsage; -use Symfony\AI\Platform\Model; use Symfony\AI\Platform\Result\RawHttpResult; -use Symfony\AI\Platform\Result\ResultInterface; use Symfony\AI\Platform\Result\StreamResult; use Symfony\AI\Platform\Result\TextResult; use Symfony\Contracts\HttpClient\ResponseInterface; -#[CoversClass(TokenOutputProcessor::class)] -#[UsesClass(Output::class)] +#[CoversClass(TokenUsageDeterminer::class)] #[UsesClass(TextResult::class)] #[UsesClass(StreamResult::class)] #[UsesClass(Metadata::class)] #[UsesClass(TokenUsage::class)] #[Small] -final class TokenOutputProcessorTest extends TestCase +final class TokenUsageDeterminerTest extends TestCase { public function testItHandlesStreamResponsesWithoutProcessing() { - $processor = new TokenOutputProcessor(); + $determiner = new TokenUsageDeterminer(); $streamResult = new StreamResult((static function () { yield 'test'; })()); - $output = $this->createOutput($streamResult); - $processor->processOutput($output); + $determiner->determineTokenUsage($streamResult); - $metadata = $output->result->getMetadata(); + $metadata = $streamResult->getMetadata(); $this->assertCount(0, $metadata); } public function testItDoesNothingWithoutRawResponse() { - $processor = new TokenOutputProcessor(); + $determiner = new TokenUsageDeterminer(); $textResult = new TextResult('test'); - $output = $this->createOutput($textResult); - $processor->processOutput($output); + $determiner->determineTokenUsage($textResult); - $metadata = $output->result->getMetadata(); + $metadata = $textResult->getMetadata(); $this->assertCount(0, $metadata); } public function testItAddsRemainingTokensToMetadata() { - $processor = new TokenOutputProcessor(); + $determiner = new TokenUsageDeterminer(); $textResult = new TextResult('test'); $textResult->setRawResult($this->createRawResult()); - $output = $this->createOutput($textResult); + $determiner->determineTokenUsage($textResult); - $processor->processOutput($output); - - $metadata = $output->result->getMetadata(); + $metadata = $textResult->getMetadata(); $tokenUsage = $metadata->get('token_usage'); $this->assertCount(1, $metadata); @@ -81,7 +72,7 @@ public function testItAddsRemainingTokensToMetadata() public function testItAddsUsageTokensToMetadata() { - $processor = new TokenOutputProcessor(); + $determiner = new TokenUsageDeterminer(); $textResult = new TextResult('test'); $rawResult = $this->createRawResult([ @@ -97,11 +88,9 @@ public function testItAddsUsageTokensToMetadata() $textResult->setRawResult($rawResult); - $output = $this->createOutput($textResult); - - $processor->processOutput($output); + $determiner->determineTokenUsage($textResult); - $metadata = $output->result->getMetadata(); + $metadata = $textResult->getMetadata(); $tokenUsage = $metadata->get('token_usage'); $this->assertInstanceOf(TokenUsage::class, $tokenUsage); @@ -116,7 +105,7 @@ public function testItAddsUsageTokensToMetadata() public function testItHandlesMissingUsageFields() { - $processor = new TokenOutputProcessor(); + $determiner = new TokenUsageDeterminer(); $textResult = new TextResult('test'); $rawResult = $this->createRawResult([ @@ -125,14 +114,11 @@ public function testItHandlesMissingUsageFields() 'promptTokenCount' => 10, ], ]); - $textResult->setRawResult($rawResult); - $output = $this->createOutput($textResult); - - $processor->processOutput($output); + $determiner->determineTokenUsage($textResult); - $metadata = $output->result->getMetadata(); + $metadata = $textResult->getMetadata(); $tokenUsage = $metadata->get('token_usage'); $this->assertInstanceOf(TokenUsage::class, $tokenUsage); @@ -149,14 +135,4 @@ private function createRawResult(array $data = []): RawHttpResult return new RawHttpResult($rawResponse); } - - private function createOutput(ResultInterface $result): Output - { - return new Output( - $this->createStub(Model::class), - $result, - $this->createStub(MessageBag::class), - [], - ); - } } diff --git a/src/platform/tests/Bridge/Mistral/TokenOutputProcessorTest.php b/src/platform/tests/Bridge/Mistral/TokenUsageDeterminerTest.php similarity index 70% rename from src/platform/tests/Bridge/Mistral/TokenOutputProcessorTest.php rename to src/platform/tests/Bridge/Mistral/TokenUsageDeterminerTest.php index c43d4ceef..e469178da 100644 --- a/src/platform/tests/Bridge/Mistral/TokenOutputProcessorTest.php +++ b/src/platform/tests/Bridge/Mistral/TokenUsageDeterminerTest.php @@ -15,62 +15,53 @@ use PHPUnit\Framework\Attributes\Small; use PHPUnit\Framework\Attributes\UsesClass; use PHPUnit\Framework\TestCase; -use Symfony\AI\Agent\Output; -use Symfony\AI\Platform\Bridge\Mistral\TokenOutputProcessor; -use Symfony\AI\Platform\Message\MessageBag; +use Symfony\AI\Platform\Bridge\Mistral\TokenUsageDeterminer; use Symfony\AI\Platform\Metadata\Metadata; use Symfony\AI\Platform\Metadata\TokenUsage; -use Symfony\AI\Platform\Model; use Symfony\AI\Platform\Result\RawHttpResult; -use Symfony\AI\Platform\Result\ResultInterface; use Symfony\AI\Platform\Result\StreamResult; use Symfony\AI\Platform\Result\TextResult; use Symfony\Contracts\HttpClient\ResponseInterface; -#[CoversClass(TokenOutputProcessor::class)] -#[UsesClass(Output::class)] +#[CoversClass(TokenUsageDeterminer::class)] #[UsesClass(TextResult::class)] #[UsesClass(StreamResult::class)] #[UsesClass(Metadata::class)] #[Small] -final class TokenOutputProcessorTest extends TestCase +final class TokenUsageDeterminerTest extends TestCase { public function testItHandlesStreamResponsesWithoutProcessing() { - $processor = new TokenOutputProcessor(); + $determiner = new TokenUsageDeterminer(); $streamResult = new StreamResult((static function () { yield 'test'; })()); - $output = $this->createOutput($streamResult); - $processor->processOutput($output); + $determiner->determineTokenUsage($streamResult); - $metadata = $output->result->getMetadata(); + $metadata = $streamResult->getMetadata(); $this->assertCount(0, $metadata); } public function testItDoesNothingWithoutRawResponse() { - $processor = new TokenOutputProcessor(); + $determiner = new TokenUsageDeterminer(); $textResult = new TextResult('test'); - $output = $this->createOutput($textResult); - $processor->processOutput($output); + $determiner->determineTokenUsage($textResult); - $metadata = $output->result->getMetadata(); + $metadata = $textResult->getMetadata(); $this->assertCount(0, $metadata); } public function testItAddsRemainingTokensToMetadata() { - $processor = new TokenOutputProcessor(); + $determiner = new TokenUsageDeterminer(); $textResult = new TextResult('test'); $textResult->setRawResult($this->createRawResponse()); - $output = $this->createOutput($textResult); + $determiner->determineTokenUsage($textResult); - $processor->processOutput($output); - - $metadata = $output->result->getMetadata(); + $metadata = $textResult->getMetadata(); $tokenUsage = $metadata->get('token_usage'); $this->assertCount(1, $metadata); @@ -81,7 +72,7 @@ public function testItAddsRemainingTokensToMetadata() public function testItAddsUsageTokensToMetadata() { - $processor = new TokenOutputProcessor(); + $determiner = new TokenUsageDeterminer(); $textResult = new TextResult('test'); $rawResponse = $this->createRawResponse([ @@ -94,11 +85,9 @@ public function testItAddsUsageTokensToMetadata() $textResult->setRawResult($rawResponse); - $output = $this->createOutput($textResult); - - $processor->processOutput($output); + $determiner->determineTokenUsage($textResult); - $metadata = $output->result->getMetadata(); + $metadata = $textResult->getMetadata(); $tokenUsage = $metadata->get('token_usage'); $this->assertInstanceOf(TokenUsage::class, $tokenUsage); @@ -111,7 +100,7 @@ public function testItAddsUsageTokensToMetadata() public function testItHandlesMissingUsageFields() { - $processor = new TokenOutputProcessor(); + $determiner = new TokenUsageDeterminer(); $textResult = new TextResult('test'); $rawResponse = $this->createRawResponse([ @@ -123,11 +112,9 @@ public function testItHandlesMissingUsageFields() $textResult->setRawResult($rawResponse); - $output = $this->createOutput($textResult); - - $processor->processOutput($output); + $determiner->determineTokenUsage($textResult); - $metadata = $output->result->getMetadata(); + $metadata = $textResult->getMetadata(); $tokenUsage = $metadata->get('token_usage'); $this->assertInstanceOf(TokenUsage::class, $tokenUsage); @@ -150,14 +137,4 @@ private function createRawResponse(array $data = []): RawHttpResult return new RawHttpResult($rawResponse); } - - private function createOutput(ResultInterface $result): Output - { - return new Output( - $this->createStub(Model::class), - $result, - $this->createStub(MessageBag::class), - [], - ); - } } diff --git a/src/platform/tests/Bridge/OpenAi/TokenOutputProcessorTest.php b/src/platform/tests/Bridge/OpenAi/TokenUsageDeterminerTest.php similarity index 70% rename from src/platform/tests/Bridge/OpenAi/TokenOutputProcessorTest.php rename to src/platform/tests/Bridge/OpenAi/TokenUsageDeterminerTest.php index cd2f10482..3926d5552 100644 --- a/src/platform/tests/Bridge/OpenAi/TokenOutputProcessorTest.php +++ b/src/platform/tests/Bridge/OpenAi/TokenUsageDeterminerTest.php @@ -15,63 +15,54 @@ use PHPUnit\Framework\Attributes\Small; use PHPUnit\Framework\Attributes\UsesClass; use PHPUnit\Framework\TestCase; -use Symfony\AI\Agent\Output; -use Symfony\AI\Platform\Bridge\OpenAi\TokenOutputProcessor; -use Symfony\AI\Platform\Message\MessageBag; +use Symfony\AI\Platform\Bridge\OpenAi\TokenUsageDeterminer; use Symfony\AI\Platform\Metadata\Metadata; use Symfony\AI\Platform\Metadata\TokenUsage; -use Symfony\AI\Platform\Model; use Symfony\AI\Platform\Result\RawHttpResult; -use Symfony\AI\Platform\Result\ResultInterface; use Symfony\AI\Platform\Result\StreamResult; use Symfony\AI\Platform\Result\TextResult; use Symfony\Contracts\HttpClient\ResponseInterface; -#[CoversClass(TokenOutputProcessor::class)] -#[UsesClass(Output::class)] +#[CoversClass(TokenUsageDeterminer::class)] #[UsesClass(TextResult::class)] #[UsesClass(StreamResult::class)] #[UsesClass(Metadata::class)] #[UsesClass(TokenUsage::class)] #[Small] -final class TokenOutputProcessorTest extends TestCase +final class TokenUsageDeterminerTest extends TestCase { public function testItHandlesStreamResponsesWithoutProcessing() { - $processor = new TokenOutputProcessor(); + $determiner = new TokenUsageDeterminer(); $streamResult = new StreamResult((static function () { yield 'test'; })()); - $output = $this->createOutput($streamResult); - $processor->processOutput($output); + $determiner->determineTokenUsage($streamResult); - $metadata = $output->result->getMetadata(); + $metadata = $streamResult->getMetadata(); $this->assertCount(0, $metadata); } public function testItDoesNothingWithoutRawResponse() { - $processor = new TokenOutputProcessor(); + $determiner = new TokenUsageDeterminer(); $textResult = new TextResult('test'); - $output = $this->createOutput($textResult); - $processor->processOutput($output); + $determiner->determineTokenUsage($textResult); - $metadata = $output->result->getMetadata(); + $metadata = $textResult->getMetadata(); $this->assertCount(0, $metadata); } public function testItAddsRemainingTokensToMetadata() { - $processor = new TokenOutputProcessor(); + $determiner = new TokenUsageDeterminer(); $textResult = new TextResult('test'); $textResult->setRawResult($this->createRawResult()); - $output = $this->createOutput($textResult); + $determiner->determineTokenUsage($textResult); - $processor->processOutput($output); - - $metadata = $output->result->getMetadata(); + $metadata = $textResult->getMetadata(); $tokenUsage = $metadata->get('token_usage'); $this->assertCount(1, $metadata); @@ -81,7 +72,7 @@ public function testItAddsRemainingTokensToMetadata() public function testItAddsUsageTokensToMetadata() { - $processor = new TokenOutputProcessor(); + $determiner = new TokenUsageDeterminer(); $textResult = new TextResult('test'); $rawResult = $this->createRawResult([ @@ -100,11 +91,9 @@ public function testItAddsUsageTokensToMetadata() $textResult->setRawResult($rawResult); - $output = $this->createOutput($textResult); - - $processor->processOutput($output); + $determiner->determineTokenUsage($textResult); - $metadata = $output->result->getMetadata(); + $metadata = $textResult->getMetadata(); $tokenUsage = $metadata->get('token_usage'); $this->assertInstanceOf(TokenUsage::class, $tokenUsage); @@ -118,7 +107,7 @@ public function testItAddsUsageTokensToMetadata() public function testItHandlesMissingUsageFields() { - $processor = new TokenOutputProcessor(); + $determiner = new TokenUsageDeterminer(); $textResult = new TextResult('test'); $rawResult = $this->createRawResult([ @@ -130,11 +119,9 @@ public function testItHandlesMissingUsageFields() $textResult->setRawResult($rawResult); - $output = $this->createOutput($textResult); - - $processor->processOutput($output); + $determiner->determineTokenUsage($textResult); - $metadata = $output->result->getMetadata(); + $metadata = $textResult->getMetadata(); $tokenUsage = $metadata->get('token_usage'); $this->assertInstanceOf(TokenUsage::class, $tokenUsage); @@ -154,14 +141,4 @@ private function createRawResult(array $data = []): RawHttpResult return new RawHttpResult($rawResponse); } - - private function createOutput(ResultInterface $result): Output - { - return new Output( - $this->createStub(Model::class), - $result, - $this->createStub(MessageBag::class), - [], - ); - } } diff --git a/src/platform/tests/Bridge/Perplexity/TokenOutputProcessorTest.php b/src/platform/tests/Bridge/Perplexity/TokenUsageDeterminerTest.php similarity index 68% rename from src/platform/tests/Bridge/Perplexity/TokenOutputProcessorTest.php rename to src/platform/tests/Bridge/Perplexity/TokenUsageDeterminerTest.php index c0a284bab..844704903 100644 --- a/src/platform/tests/Bridge/Perplexity/TokenOutputProcessorTest.php +++ b/src/platform/tests/Bridge/Perplexity/TokenUsageDeterminerTest.php @@ -15,14 +15,10 @@ use PHPUnit\Framework\Attributes\Small; use PHPUnit\Framework\Attributes\UsesClass; use PHPUnit\Framework\TestCase; -use Symfony\AI\Agent\Output; -use Symfony\AI\Platform\Bridge\Perplexity\TokenOutputProcessor; -use Symfony\AI\Platform\Message\MessageBag; +use Symfony\AI\Platform\Bridge\Perplexity\TokenUsageDeterminer; use Symfony\AI\Platform\Metadata\Metadata; use Symfony\AI\Platform\Metadata\TokenUsage; -use Symfony\AI\Platform\Model; use Symfony\AI\Platform\Result\RawHttpResult; -use Symfony\AI\Platform\Result\ResultInterface; use Symfony\AI\Platform\Result\StreamResult; use Symfony\AI\Platform\Result\TextResult; use Symfony\Contracts\HttpClient\ResponseInterface; @@ -30,42 +26,39 @@ /** * @author Mathieu Santostefano */ -#[CoversClass(TokenOutputProcessor::class)] -#[UsesClass(Output::class)] +#[CoversClass(TokenUsageDeterminer::class)] #[UsesClass(TextResult::class)] #[UsesClass(StreamResult::class)] #[UsesClass(Metadata::class)] #[UsesClass(TokenUsage::class)] #[Small] -final class TokenOutputProcessorTest extends TestCase +final class TokenUsageDeterminerTest extends TestCase { public function testItHandlesStreamResponsesWithoutProcessing() { - $processor = new TokenOutputProcessor(); + $determiner = new TokenUsageDeterminer(); $streamResult = new StreamResult((static function () { yield 'test'; })()); - $output = $this->createOutput($streamResult); - $processor->processOutput($output); + $determiner->determineTokenUsage($streamResult); - $metadata = $output->result->getMetadata(); + $metadata = $streamResult->getMetadata(); $this->assertCount(0, $metadata); } public function testItDoesNothingWithoutRawResponse() { - $processor = new TokenOutputProcessor(); + $determiner = new TokenUsageDeterminer(); $textResult = new TextResult('test'); - $output = $this->createOutput($textResult); - $processor->processOutput($output); + $determiner->determineTokenUsage($textResult); - $metadata = $output->result->getMetadata(); + $metadata = $textResult->getMetadata(); $this->assertCount(0, $metadata); } public function testItAddsUsageTokensToMetadata() { - $processor = new TokenOutputProcessor(); + $determiner = new TokenUsageDeterminer(); $textResult = new TextResult('test'); $rawResult = $this->createRawResult([ @@ -79,11 +72,9 @@ public function testItAddsUsageTokensToMetadata() $textResult->setRawResult($rawResult); - $output = $this->createOutput($textResult); + $determiner->determineTokenUsage($textResult); - $processor->processOutput($output); - - $metadata = $output->result->getMetadata(); + $metadata = $textResult->getMetadata(); $tokenUsage = $metadata->get('token_usage'); $this->assertInstanceOf(TokenUsage::class, $tokenUsage); @@ -95,7 +86,7 @@ public function testItAddsUsageTokensToMetadata() public function testItHandlesMissingUsageFields() { - $processor = new TokenOutputProcessor(); + $determiner = new TokenUsageDeterminer(); $textResult = new TextResult('test'); $rawResult = $this->createRawResult([ @@ -107,11 +98,9 @@ public function testItHandlesMissingUsageFields() $textResult->setRawResult($rawResult); - $output = $this->createOutput($textResult); - - $processor->processOutput($output); + $determiner->determineTokenUsage($textResult); - $metadata = $output->result->getMetadata(); + $metadata = $textResult->getMetadata(); $tokenUsage = $metadata->get('token_usage'); $this->assertInstanceOf(TokenUsage::class, $tokenUsage); @@ -128,14 +117,4 @@ private function createRawResult(array $data = []): RawHttpResult return new RawHttpResult($rawResponse); } - - private function createOutput(ResultInterface $result): Output - { - return new Output( - $this->createStub(Model::class), - $result, - $this->createStub(MessageBag::class), - [], - ); - } } diff --git a/src/platform/tests/Bridge/VertexAi/TokenOutputProcessorTest.php b/src/platform/tests/Bridge/VertexAi/TokenUsageDeterminerTest.php similarity index 73% rename from src/platform/tests/Bridge/VertexAi/TokenOutputProcessorTest.php rename to src/platform/tests/Bridge/VertexAi/TokenUsageDeterminerTest.php index d6de42fd5..0ecf65f6d 100644 --- a/src/platform/tests/Bridge/VertexAi/TokenOutputProcessorTest.php +++ b/src/platform/tests/Bridge/VertexAi/TokenUsageDeterminerTest.php @@ -15,35 +15,29 @@ use PHPUnit\Framework\Attributes\Small; use PHPUnit\Framework\Attributes\UsesClass; use PHPUnit\Framework\TestCase; -use Symfony\AI\Agent\Output; -use Symfony\AI\Platform\Bridge\VertexAi\TokenOutputProcessor; -use Symfony\AI\Platform\Message\MessageBag; +use Symfony\AI\Platform\Bridge\VertexAi\TokenUsageDeterminer; use Symfony\AI\Platform\Metadata\Metadata; use Symfony\AI\Platform\Metadata\TokenUsage; -use Symfony\AI\Platform\Model; use Symfony\AI\Platform\Result\RawHttpResult; -use Symfony\AI\Platform\Result\ResultInterface; use Symfony\AI\Platform\Result\StreamResult; use Symfony\AI\Platform\Result\TextResult; use Symfony\Contracts\HttpClient\ResponseInterface; -#[CoversClass(TokenOutputProcessor::class)] -#[UsesClass(Output::class)] +#[CoversClass(TokenUsageDeterminer::class)] #[UsesClass(TextResult::class)] #[UsesClass(StreamResult::class)] #[UsesClass(Metadata::class)] #[Small] -final class TokenOutputProcessorTest extends TestCase +final class TokenUsageDeterminerTest extends TestCase { public function testItDoesNothingWithoutRawResponse() { - $processor = new TokenOutputProcessor(); + $determiner = new TokenUsageDeterminer(); $textResult = new TextResult('test'); - $output = $this->createOutput($textResult); - $processor->processOutput($output); + $determiner->determineTokenUsage($textResult); - $this->assertCount(0, $output->result->getMetadata()); + $this->assertCount(0, $textResult->getMetadata()); } public function testItAddsUsageTokensToMetadata() @@ -61,14 +55,13 @@ public function testItAddsUsageTokensToMetadata() ]); $textResult->setRawResult($rawResponse); - $processor = new TokenOutputProcessor(); - $output = $this->createOutput($textResult); + $determiner = new TokenUsageDeterminer(); // Act - $processor->processOutput($output); + $determiner->determineTokenUsage($textResult); // Assert - $metadata = $output->result->getMetadata(); + $metadata = $textResult->getMetadata(); $tokenUsage = $metadata->get('token_usage'); $this->assertCount(1, $metadata); @@ -91,14 +84,13 @@ public function testItHandlesMissingUsageFields() ]); $textResult->setRawResult($rawResponse); - $processor = new TokenOutputProcessor(); - $output = $this->createOutput($textResult); + $determiner = new TokenUsageDeterminer(); // Act - $processor->processOutput($output); + $determiner->determineTokenUsage($textResult); // Assert - $metadata = $output->result->getMetadata(); + $metadata = $textResult->getMetadata(); $tokenUsage = $metadata->get('token_usage'); $this->assertCount(1, $metadata); @@ -115,14 +107,13 @@ public function testItAddsEmptyTokenUsageWhenUsageMetadataNotPresent() $textResult = new TextResult('test'); $rawResponse = $this->createRawResponse(['other' => 'data']); $textResult->setRawResult($rawResponse); - $processor = new TokenOutputProcessor(); - $output = $this->createOutput($textResult); + $determiner = new TokenUsageDeterminer(); // Act - $processor->processOutput($output); + $determiner->determineTokenUsage($textResult); // Assert - $metadata = $output->result->getMetadata(); + $metadata = $textResult->getMetadata(); $tokenUsage = $metadata->get('token_usage'); $this->assertCount(1, $metadata); @@ -135,7 +126,7 @@ public function testItAddsEmptyTokenUsageWhenUsageMetadataNotPresent() public function testItHandlesStreamResults() { - $processor = new TokenOutputProcessor(); + $determiner = new TokenUsageDeterminer(); $chunks = [ ['content' => 'chunk1'], ['content' => 'chunk2', 'usageMetadata' => [ @@ -151,11 +142,9 @@ public function testItHandlesStreamResults() } })()); - $output = $this->createOutput($streamResult); + $determiner->determineTokenUsage($streamResult); - $processor->processOutput($output); - - $metadata = $output->result->getMetadata(); + $metadata = $streamResult->getMetadata(); $tokenUsage = $metadata->get('token_usage'); $this->assertCount(1, $metadata); @@ -174,14 +163,4 @@ private function createRawResponse(array $data = []): RawHttpResult return new RawHttpResult($rawResponse); } - - private function createOutput(ResultInterface $result): Output - { - return new Output( - $this->createStub(Model::class), - $result, - new MessageBag(), - [], - ); - } } From 3473dc0bf946c21e9924f38b487fdd2be4f63888 Mon Sep 17 00:00:00 2001 From: glady Date: Fri, 12 Sep 2025 17:52:44 +0200 Subject: [PATCH 06/11] adapt ai-bundle for new TokenOutputProcessor and platform bridge dependent TokenUsageDeterminer constellation --- src/ai-bundle/config/services.php | 16 ++++++++-------- src/ai-bundle/src/AiBundle.php | 11 ++++++++--- .../tests/DependencyInjection/AiBundleTest.php | 18 ++++++++++++++++-- 3 files changed, 32 insertions(+), 13 deletions(-) diff --git a/src/ai-bundle/config/services.php b/src/ai-bundle/config/services.php index 88be4784f..e77db79e8 100644 --- a/src/ai-bundle/config/services.php +++ b/src/ai-bundle/config/services.php @@ -27,13 +27,13 @@ use Symfony\AI\AiBundle\Security\EventListener\IsGrantedToolAttributeListener; use Symfony\AI\Platform\Bridge\Anthropic\Contract\AnthropicContract; use Symfony\AI\Platform\Bridge\Gemini\Contract\GeminiContract; -use Symfony\AI\Platform\Bridge\Gemini\TokenOutputProcessor as GeminiTokenOutputProcessor; -use Symfony\AI\Platform\Bridge\Mistral\TokenOutputProcessor as MistralTokenOutputProcessor; +use Symfony\AI\Platform\Bridge\Gemini\TokenUsageDeterminer as GeminiTokenUsageDeterminer; +use Symfony\AI\Platform\Bridge\Mistral\TokenUsageDeterminer as MistralTokenUsageDeterminer; use Symfony\AI\Platform\Bridge\Ollama\Contract\OllamaContract; use Symfony\AI\Platform\Bridge\OpenAi\Contract\OpenAiContract; -use Symfony\AI\Platform\Bridge\OpenAi\TokenOutputProcessor as OpenAiTokenOutputProcessor; +use Symfony\AI\Platform\Bridge\OpenAi\TokenUsageDeterminer as OpenAiTokenUsageDeterminer; use Symfony\AI\Platform\Bridge\VertexAi\Contract\GeminiContract as VertexAiGeminiContract; -use Symfony\AI\Platform\Bridge\VertexAi\TokenOutputProcessor as VertexAiTokenOutputProcessor; +use Symfony\AI\Platform\Bridge\VertexAi\TokenUsageDeterminer as VertexAiTokenUsageDeterminer; use Symfony\AI\Platform\Contract; use Symfony\AI\Platform\Contract\JsonSchema\DescriptionParser; use Symfony\AI\Platform\Contract\JsonSchema\Factory as SchemaFactory; @@ -137,10 +137,10 @@ ->tag('ai.traceable_toolbox') // token usage processors - ->set('ai.platform.token_usage_processor.mistral', MistralTokenOutputProcessor::class) - ->set('ai.platform.token_usage_processor.gemini', GeminiTokenOutputProcessor::class) - ->set('ai.platform.token_usage_processor.openai', OpenAiTokenOutputProcessor::class) - ->set('ai.platform.token_usage_processor.vertexai', VertexAiTokenOutputProcessor::class) + ->set('ai.platform.token_usage_determiner.mistral', MistralTokenUsageDeterminer::class) + ->set('ai.platform.token_usage_determiner.gemini', GeminiTokenUsageDeterminer::class) + ->set('ai.platform.token_usage_determiner.openai', OpenAiTokenUsageDeterminer::class) + ->set('ai.platform.token_usage_determiner.vertexai', VertexAiTokenUsageDeterminer::class) // commands ->set('ai.command.chat', ChatCommand::class) diff --git a/src/ai-bundle/src/AiBundle.php b/src/ai-bundle/src/AiBundle.php index 21ff23804..a26b962dc 100644 --- a/src/ai-bundle/src/AiBundle.php +++ b/src/ai-bundle/src/AiBundle.php @@ -19,6 +19,7 @@ use Symfony\AI\Agent\Attribute\AsOutputProcessor; use Symfony\AI\Agent\InputProcessor\SystemPromptInputProcessor; use Symfony\AI\Agent\InputProcessorInterface; +use Symfony\AI\Agent\OutputProcessor\TokenOutputProcessor; use Symfony\AI\Agent\OutputProcessorInterface; use Symfony\AI\Agent\Toolbox\Attribute\AsTool; use Symfony\AI\Agent\Toolbox\FaultTolerantToolbox; @@ -577,9 +578,13 @@ private function processAgentConfig(string $name, array $config, ContainerBuilde $platform = 'azure'; } - if ($container->hasDefinition('ai.platform.token_usage_processor.'.$platform)) { - $container->getDefinition('ai.platform.token_usage_processor.'.$platform) - ->addTag('ai.agent.output_processor', ['agent' => $agentId, 'priority' => -30]); + if ($container->hasDefinition('ai.platform.token_usage_determiner.'.$platform)) { + $container->getDefinition('ai.platform.token_usage_determiner.'.$platform) + ->addTag('ai.agent.token_usage_determiner', ['agent' => $agentId]); + + $tokenOutputProcessorDefinition = new Definition(TokenOutputProcessor::class); + $container->setDefinition('ai.platform.token_usage_processor.'.$platform, $tokenOutputProcessorDefinition) + ->addTag('ai.platform.token_usage_processor', ['agent' => $agentId, 'priority' => -30]); } } } diff --git a/src/ai-bundle/tests/DependencyInjection/AiBundleTest.php b/src/ai-bundle/tests/DependencyInjection/AiBundleTest.php index ae2142724..5d6745daf 100644 --- a/src/ai-bundle/tests/DependencyInjection/AiBundleTest.php +++ b/src/ai-bundle/tests/DependencyInjection/AiBundleTest.php @@ -543,9 +543,23 @@ public function testTokenUsageProcessorTags() $agentId = 'ai.agent.tracked_agent'; + // Token usage determiner must exist for OpenAI platform + $tokenUsageDeterminer = $container->getDefinition('ai.platform.token_usage_determiner.openai'); + $outputTags = $tokenUsageDeterminer->getTag('ai.agent.token_usage_determiner'); + + $foundTag = false; + foreach ($outputTags as $tag) { + if (($tag['agent'] ?? '') === $agentId) { + $foundTag = true; + break; + } + } + + $this->assertTrue($foundTag, 'Token usage determiner should have output tag with full agent ID'); + // Token usage processor must exist for OpenAI platform - $tokenUsageProcessor = $container->getDefinition('ai.platform.token_usage_processor.openai'); - $outputTags = $tokenUsageProcessor->getTag('ai.agent.output_processor'); + $tokenOutputProcessor = $container->getDefinition('ai.platform.token_usage_processor.openai'); + $outputTags = $tokenOutputProcessor->getTag('ai.agent.token_usage_processor'); $foundTag = false; foreach ($outputTags as $tag) { From a77230eb46a955632b1a60ae3ba106b7e41f5c64 Mon Sep 17 00:00:00 2001 From: glady Date: Fri, 12 Sep 2025 18:28:52 +0200 Subject: [PATCH 07/11] added a more abstract output processor that only works with the result and accepts a rDResultHandlerInterface from platform component --- examples/gemini/token-metadata.php | 7 +++--- examples/mistral/token-metadata.php | 7 +++--- examples/openai/token-metadata.php | 7 +++--- examples/perplexity/academic-search.php | 6 +++-- examples/perplexity/image-input-url.php | 6 +++-- examples/perplexity/pdf-input-url.php | 6 +++-- examples/perplexity/stream.php | 6 +++-- examples/perplexity/token-metadata.php | 7 +++--- examples/vertexai/token-metadata.php | 7 +++--- ...rocessor.php => ResultOutputProcessor.php} | 8 +++---- src/ai-bundle/config/services.php | 16 ++++++------- src/ai-bundle/src/AiBundle.php | 10 ++++---- ...rminer.php => TokenUsageResultHandler.php} | 6 ++--- ...rminer.php => TokenUsageResultHandler.php} | 6 ++--- ...rminer.php => TokenUsageResultHandler.php} | 6 ++--- ...tProcessor.php => SearchResultHandler.php} | 16 ++++++------- ...rminer.php => TokenUsageResultHandler.php} | 6 ++--- ...rminer.php => TokenUsageResultHandler.php} | 6 ++--- ...terface.php => ResultHandlerInterface.php} | 4 ++-- .../Gemini/TokenUsageDeterminerTest.php | 24 +++++++++---------- .../Mistral/TokenUsageDeterminerTest.php | 24 +++++++++---------- .../OpenAi/TokenUsageDeterminerTest.php | 24 +++++++++---------- .../Perplexity/TokenUsageDeterminerTest.php | 20 ++++++++-------- .../VertexAi/TokenUsageDeterminerTest.php | 24 +++++++++---------- 24 files changed, 131 insertions(+), 128 deletions(-) rename src/agent/src/OutputProcessor/{TokenOutputProcessor.php => ResultOutputProcessor.php} (63%) rename src/platform/src/Bridge/Gemini/{TokenUsageDeterminer.php => TokenUsageResultHandler.php} (88%) rename src/platform/src/Bridge/Mistral/{TokenUsageDeterminer.php => TokenUsageResultHandler.php} (90%) rename src/platform/src/Bridge/OpenAi/{TokenUsageDeterminer.php => TokenUsageResultHandler.php} (89%) rename src/platform/src/Bridge/Perplexity/{SearchResultProcessor.php => SearchResultHandler.php} (70%) rename src/platform/src/Bridge/Perplexity/{TokenUsageDeterminer.php => TokenUsageResultHandler.php} (87%) rename src/platform/src/Bridge/VertexAi/{TokenUsageDeterminer.php => TokenUsageResultHandler.php} (93%) rename src/platform/src/Result/{TokenUsageDeterminerInterface.php => ResultHandlerInterface.php} (71%) diff --git a/examples/gemini/token-metadata.php b/examples/gemini/token-metadata.php index e2a05f3d5..bc0958357 100644 --- a/examples/gemini/token-metadata.php +++ b/examples/gemini/token-metadata.php @@ -10,10 +10,10 @@ */ use Symfony\AI\Agent\Agent; -use Symfony\AI\Agent\OutputProcessor\TokenOutputProcessor; +use Symfony\AI\Agent\OutputProcessor\ResultOutputProcessor; use Symfony\AI\Platform\Bridge\Gemini\Gemini; use Symfony\AI\Platform\Bridge\Gemini\PlatformFactory; -use Symfony\AI\Platform\Bridge\Gemini\TokenUsageDeterminer; +use Symfony\AI\Platform\Bridge\Gemini\TokenUsageResultHandler; use Symfony\AI\Platform\Message\Message; use Symfony\AI\Platform\Message\MessageBag; @@ -21,9 +21,8 @@ $platform = PlatformFactory::create(env('GEMINI_API_KEY'), http_client()); $model = new Gemini(Gemini::GEMINI_2_FLASH); -$tokenUsageDeterminer = new TokenUsageDeterminer(); -$agent = new Agent($platform, $model, outputProcessors: [new TokenOutputProcessor($tokenUsageDeterminer)], logger: logger()); +$agent = new Agent($platform, $model, outputProcessors: [new ResultOutputProcessor(new TokenUsageResultHandler())], logger: logger()); $messages = new MessageBag( Message::forSystem('You are a pirate and you write funny.'), Message::ofUser('What is the Symfony framework?'), diff --git a/examples/mistral/token-metadata.php b/examples/mistral/token-metadata.php index 036f9fb81..77ac9b000 100644 --- a/examples/mistral/token-metadata.php +++ b/examples/mistral/token-metadata.php @@ -10,10 +10,10 @@ */ use Symfony\AI\Agent\Agent; -use Symfony\AI\Agent\OutputProcessor\TokenOutputProcessor; +use Symfony\AI\Agent\OutputProcessor\ResultOutputProcessor; use Symfony\AI\Platform\Bridge\Mistral\Mistral; use Symfony\AI\Platform\Bridge\Mistral\PlatformFactory; -use Symfony\AI\Platform\Bridge\Mistral\TokenUsageDeterminer; +use Symfony\AI\Platform\Bridge\Mistral\TokenUsageResultHandler; use Symfony\AI\Platform\Message\Message; use Symfony\AI\Platform\Message\MessageBag; @@ -21,9 +21,8 @@ $platform = PlatformFactory::create(env('MISTRAL_API_KEY'), http_client()); $model = new Mistral(); -$tokenUsageDeterminer = new TokenUsageDeterminer(); -$agent = new Agent($platform, $model, outputProcessors: [new TokenOutputProcessor($tokenUsageDeterminer)], logger: logger()); +$agent = new Agent($platform, $model, outputProcessors: [new ResultOutputProcessor(new TokenUsageResultHandler())], logger: logger()); $messages = new MessageBag( Message::forSystem('You are a pirate and you write funny.'), diff --git a/examples/openai/token-metadata.php b/examples/openai/token-metadata.php index dcb94f39c..9ce3e39b7 100644 --- a/examples/openai/token-metadata.php +++ b/examples/openai/token-metadata.php @@ -10,10 +10,10 @@ */ use Symfony\AI\Agent\Agent; -use Symfony\AI\Agent\OutputProcessor\TokenOutputProcessor; +use Symfony\AI\Agent\OutputProcessor\ResultOutputProcessor; use Symfony\AI\Platform\Bridge\OpenAi\Gpt; use Symfony\AI\Platform\Bridge\OpenAi\PlatformFactory; -use Symfony\AI\Platform\Bridge\OpenAi\TokenUsageDeterminer; +use Symfony\AI\Platform\Bridge\OpenAi\TokenUsageResultHandler; use Symfony\AI\Platform\Message\Message; use Symfony\AI\Platform\Message\MessageBag; @@ -23,9 +23,8 @@ $model = new Gpt(Gpt::GPT_4O_MINI, [ 'temperature' => 0.5, // default options for the model ]); -$tokenUsageDeterminer = new TokenUsageDeterminer(); -$agent = new Agent($platform, $model, outputProcessors: [new TokenOutputProcessor($tokenUsageDeterminer)], logger: logger()); +$agent = new Agent($platform, $model, outputProcessors: [new ResultOutputProcessor(new TokenUsageResultHandler())], logger: logger()); $messages = new MessageBag( Message::forSystem('You are a pirate and you write funny.'), Message::ofUser('What is the Symfony framework?'), diff --git a/examples/perplexity/academic-search.php b/examples/perplexity/academic-search.php index 8e7149c26..d3e0a0d90 100644 --- a/examples/perplexity/academic-search.php +++ b/examples/perplexity/academic-search.php @@ -10,9 +10,10 @@ */ use Symfony\AI\Agent\Agent; +use Symfony\AI\Agent\OutputProcessor\ResultOutputProcessor; use Symfony\AI\Platform\Bridge\Perplexity\Perplexity; use Symfony\AI\Platform\Bridge\Perplexity\PlatformFactory; -use Symfony\AI\Platform\Bridge\Perplexity\SearchResultProcessor; +use Symfony\AI\Platform\Bridge\Perplexity\SearchResultHandler; use Symfony\AI\Platform\Message\Message; use Symfony\AI\Platform\Message\MessageBag; @@ -20,7 +21,8 @@ $platform = PlatformFactory::create(env('PERPLEXITY_API_KEY'), http_client()); $model = new Perplexity(); -$agent = new Agent($platform, $model, outputProcessors: [new SearchResultProcessor()], logger: logger()); + +$agent = new Agent($platform, $model, outputProcessors: [new ResultOutputProcessor(new SearchResultHandler())], logger: logger()); $messages = new MessageBag(Message::ofUser('What is the best French cheese of the first quarter-century of 21st century?')); $response = $agent->call($messages, [ diff --git a/examples/perplexity/image-input-url.php b/examples/perplexity/image-input-url.php index ea8c08432..0088fa4ff 100644 --- a/examples/perplexity/image-input-url.php +++ b/examples/perplexity/image-input-url.php @@ -10,9 +10,10 @@ */ use Symfony\AI\Agent\Agent; +use Symfony\AI\Agent\OutputProcessor\ResultOutputProcessor; use Symfony\AI\Platform\Bridge\Perplexity\Perplexity; use Symfony\AI\Platform\Bridge\Perplexity\PlatformFactory; -use Symfony\AI\Platform\Bridge\Perplexity\SearchResultProcessor; +use Symfony\AI\Platform\Bridge\Perplexity\SearchResultHandler; use Symfony\AI\Platform\Message\Content\ImageUrl; use Symfony\AI\Platform\Message\Message; use Symfony\AI\Platform\Message\MessageBag; @@ -21,7 +22,8 @@ $platform = PlatformFactory::create(env('PERPLEXITY_API_KEY'), http_client()); $model = new Perplexity(); -$agent = new Agent($platform, $model, outputProcessors: [new SearchResultProcessor()], logger: logger()); + +$agent = new Agent($platform, $model, outputProcessors: [new ResultOutputProcessor(new SearchResultHandler())], logger: logger()); $messages = new MessageBag( Message::forSystem('You are an image analyzer bot that helps identify the content of images.'), diff --git a/examples/perplexity/pdf-input-url.php b/examples/perplexity/pdf-input-url.php index 6454498c0..fed8af510 100644 --- a/examples/perplexity/pdf-input-url.php +++ b/examples/perplexity/pdf-input-url.php @@ -10,9 +10,10 @@ */ use Symfony\AI\Agent\Agent; +use Symfony\AI\Agent\OutputProcessor\ResultOutputProcessor; use Symfony\AI\Platform\Bridge\Perplexity\Perplexity; use Symfony\AI\Platform\Bridge\Perplexity\PlatformFactory; -use Symfony\AI\Platform\Bridge\Perplexity\SearchResultProcessor; +use Symfony\AI\Platform\Bridge\Perplexity\SearchResultHandler; use Symfony\AI\Platform\Message\Content\DocumentUrl; use Symfony\AI\Platform\Message\Message; use Symfony\AI\Platform\Message\MessageBag; @@ -21,7 +22,8 @@ $platform = PlatformFactory::create(env('PERPLEXITY_API_KEY'), http_client()); $model = new Perplexity(); -$agent = new Agent($platform, $model, outputProcessors: [new SearchResultProcessor()], logger: logger()); + +$agent = new Agent($platform, $model, outputProcessors: [new ResultOutputProcessor(new SearchResultHandler())], logger: logger()); $messages = new MessageBag( Message::ofUser( diff --git a/examples/perplexity/stream.php b/examples/perplexity/stream.php index 5f2bb526a..4026293ff 100644 --- a/examples/perplexity/stream.php +++ b/examples/perplexity/stream.php @@ -10,9 +10,10 @@ */ use Symfony\AI\Agent\Agent; +use Symfony\AI\Agent\OutputProcessor\ResultOutputProcessor; use Symfony\AI\Platform\Bridge\Perplexity\Perplexity; use Symfony\AI\Platform\Bridge\Perplexity\PlatformFactory; -use Symfony\AI\Platform\Bridge\Perplexity\SearchResultProcessor; +use Symfony\AI\Platform\Bridge\Perplexity\SearchResultHandler; use Symfony\AI\Platform\Message\Message; use Symfony\AI\Platform\Message\MessageBag; @@ -20,7 +21,8 @@ $platform = PlatformFactory::create(env('PERPLEXITY_API_KEY'), http_client()); $model = new Perplexity(); -$agent = new Agent($platform, $model, outputProcessors: [new SearchResultProcessor()], logger: logger()); + +$agent = new Agent($platform, $model, outputProcessors: [new ResultOutputProcessor(new SearchResultHandler())], logger: logger()); $messages = new MessageBag( Message::forSystem('You are a thoughtful philosopher.'), diff --git a/examples/perplexity/token-metadata.php b/examples/perplexity/token-metadata.php index 033f0918c..bfbd6cfd0 100644 --- a/examples/perplexity/token-metadata.php +++ b/examples/perplexity/token-metadata.php @@ -10,10 +10,10 @@ */ use Symfony\AI\Agent\Agent; -use Symfony\AI\Agent\OutputProcessor\TokenOutputProcessor; +use Symfony\AI\Agent\OutputProcessor\ResultOutputProcessor; use Symfony\AI\Platform\Bridge\Perplexity\Perplexity; use Symfony\AI\Platform\Bridge\Perplexity\PlatformFactory; -use Symfony\AI\Platform\Bridge\Perplexity\TokenUsageDeterminer; +use Symfony\AI\Platform\Bridge\Perplexity\TokenUsageResultHandler; use Symfony\AI\Platform\Message\Message; use Symfony\AI\Platform\Message\MessageBag; @@ -21,9 +21,8 @@ $platform = PlatformFactory::create(env('PERPLEXITY_API_KEY'), http_client()); $model = new Perplexity(); -$tokenUsageDeterminer = new TokenUsageDeterminer(); -$agent = new Agent($platform, $model, outputProcessors: [new TokenOutputProcessor($tokenUsageDeterminer)], logger: logger()); +$agent = new Agent($platform, $model, outputProcessors: [new ResultOutputProcessor(new TokenUsageResultHandler())], logger: logger()); $messages = new MessageBag( Message::forSystem('You are a pirate and you write funny.'), diff --git a/examples/vertexai/token-metadata.php b/examples/vertexai/token-metadata.php index 4d8ecf643..7a6abf34d 100644 --- a/examples/vertexai/token-metadata.php +++ b/examples/vertexai/token-metadata.php @@ -10,10 +10,10 @@ */ use Symfony\AI\Agent\Agent; -use Symfony\AI\Agent\OutputProcessor\TokenOutputProcessor; +use Symfony\AI\Agent\OutputProcessor\ResultOutputProcessor; use Symfony\AI\Platform\Bridge\VertexAi\Gemini\Model; use Symfony\AI\Platform\Bridge\VertexAi\PlatformFactory; -use Symfony\AI\Platform\Bridge\VertexAi\TokenUsageDeterminer; +use Symfony\AI\Platform\Bridge\VertexAi\TokenUsageResultHandler; use Symfony\AI\Platform\Message\Message; use Symfony\AI\Platform\Message\MessageBag; @@ -21,9 +21,8 @@ $platform = PlatformFactory::create(env('GOOGLE_CLOUD_LOCATION'), env('GOOGLE_CLOUD_PROJECT'), adc_aware_http_client()); $model = new Model(Model::GEMINI_2_0_FLASH_LITE); -$tokenUsageDeterminer = new TokenUsageDeterminer(); -$agent = new Agent($platform, $model, outputProcessors: [new TokenOutputProcessor($tokenUsageDeterminer)], logger: logger()); +$agent = new Agent($platform, $model, outputProcessors: [new ResultOutputProcessor(new TokenUsageResultHandler())], logger: logger()); $messages = new MessageBag( Message::forSystem('You are an expert assistant in animal study.'), Message::ofUser('What does a cat usually eat?'), diff --git a/src/agent/src/OutputProcessor/TokenOutputProcessor.php b/src/agent/src/OutputProcessor/ResultOutputProcessor.php similarity index 63% rename from src/agent/src/OutputProcessor/TokenOutputProcessor.php rename to src/agent/src/OutputProcessor/ResultOutputProcessor.php index 4a46df156..c8e09cd54 100644 --- a/src/agent/src/OutputProcessor/TokenOutputProcessor.php +++ b/src/agent/src/OutputProcessor/ResultOutputProcessor.php @@ -13,17 +13,17 @@ use Symfony\AI\Agent\Output; use Symfony\AI\Agent\OutputProcessorInterface; -use Symfony\AI\Platform\Result\TokenUsageDeterminerInterface; +use Symfony\AI\Platform\Result\ResultHandlerInterface; -class TokenOutputProcessor implements OutputProcessorInterface +readonly class ResultOutputProcessor implements OutputProcessorInterface { public function __construct( - private readonly TokenUsageDeterminerInterface $tokenUsageDeterminer, + private ResultHandlerInterface $resultProcessor, ) { } public function processOutput(Output $output): void { - $this->tokenUsageDeterminer->determineTokenUsage($output->result); + $this->resultProcessor->handleResult($output->result); } } diff --git a/src/ai-bundle/config/services.php b/src/ai-bundle/config/services.php index e77db79e8..64bbb0d41 100644 --- a/src/ai-bundle/config/services.php +++ b/src/ai-bundle/config/services.php @@ -27,13 +27,13 @@ use Symfony\AI\AiBundle\Security\EventListener\IsGrantedToolAttributeListener; use Symfony\AI\Platform\Bridge\Anthropic\Contract\AnthropicContract; use Symfony\AI\Platform\Bridge\Gemini\Contract\GeminiContract; -use Symfony\AI\Platform\Bridge\Gemini\TokenUsageDeterminer as GeminiTokenUsageDeterminer; -use Symfony\AI\Platform\Bridge\Mistral\TokenUsageDeterminer as MistralTokenUsageDeterminer; +use Symfony\AI\Platform\Bridge\Gemini\TokenUsageResultHandler as GeminiTokenUsageResultHandler; +use Symfony\AI\Platform\Bridge\Mistral\TokenUsageResultHandler as MistralTokenUsageResultHandler; use Symfony\AI\Platform\Bridge\Ollama\Contract\OllamaContract; use Symfony\AI\Platform\Bridge\OpenAi\Contract\OpenAiContract; -use Symfony\AI\Platform\Bridge\OpenAi\TokenUsageDeterminer as OpenAiTokenUsageDeterminer; +use Symfony\AI\Platform\Bridge\OpenAi\TokenUsageResultHandler as OpenAiTokenUsageResultHandler; use Symfony\AI\Platform\Bridge\VertexAi\Contract\GeminiContract as VertexAiGeminiContract; -use Symfony\AI\Platform\Bridge\VertexAi\TokenUsageDeterminer as VertexAiTokenUsageDeterminer; +use Symfony\AI\Platform\Bridge\VertexAi\TokenUsageResultHandler as VertexAiTokenUsageResultHandler; use Symfony\AI\Platform\Contract; use Symfony\AI\Platform\Contract\JsonSchema\DescriptionParser; use Symfony\AI\Platform\Contract\JsonSchema\Factory as SchemaFactory; @@ -137,10 +137,10 @@ ->tag('ai.traceable_toolbox') // token usage processors - ->set('ai.platform.token_usage_determiner.mistral', MistralTokenUsageDeterminer::class) - ->set('ai.platform.token_usage_determiner.gemini', GeminiTokenUsageDeterminer::class) - ->set('ai.platform.token_usage_determiner.openai', OpenAiTokenUsageDeterminer::class) - ->set('ai.platform.token_usage_determiner.vertexai', VertexAiTokenUsageDeterminer::class) + ->set('ai.platform.token_usage_result_handler.mistral', MistralTokenUsageResultHandler::class) + ->set('ai.platform.token_usage_result_handler.gemini', GeminiTokenUsageResultHandler::class) + ->set('ai.platform.token_usage_result_handler.openai', OpenAiTokenUsageResultHandler::class) + ->set('ai.platform.token_usage_result_handler.vertexai', VertexAiTokenUsageResultHandler::class) // commands ->set('ai.command.chat', ChatCommand::class) diff --git a/src/ai-bundle/src/AiBundle.php b/src/ai-bundle/src/AiBundle.php index a26b962dc..fac4d66e6 100644 --- a/src/ai-bundle/src/AiBundle.php +++ b/src/ai-bundle/src/AiBundle.php @@ -19,7 +19,7 @@ use Symfony\AI\Agent\Attribute\AsOutputProcessor; use Symfony\AI\Agent\InputProcessor\SystemPromptInputProcessor; use Symfony\AI\Agent\InputProcessorInterface; -use Symfony\AI\Agent\OutputProcessor\TokenOutputProcessor; +use Symfony\AI\Agent\OutputProcessor\ResultOutputProcessor; use Symfony\AI\Agent\OutputProcessorInterface; use Symfony\AI\Agent\Toolbox\Attribute\AsTool; use Symfony\AI\Agent\Toolbox\FaultTolerantToolbox; @@ -578,11 +578,11 @@ private function processAgentConfig(string $name, array $config, ContainerBuilde $platform = 'azure'; } - if ($container->hasDefinition('ai.platform.token_usage_determiner.'.$platform)) { - $container->getDefinition('ai.platform.token_usage_determiner.'.$platform) - ->addTag('ai.agent.token_usage_determiner', ['agent' => $agentId]); + if ($container->hasDefinition('ai.platform.token_usage_result_handler.'.$platform)) { + $container->getDefinition('ai.platform.token_usage_result_handler.'.$platform) + ->addTag('ai.agent.token_usage_result_handler', ['agent' => $agentId]); - $tokenOutputProcessorDefinition = new Definition(TokenOutputProcessor::class); + $tokenOutputProcessorDefinition = new Definition(ResultOutputProcessor::class); $container->setDefinition('ai.platform.token_usage_processor.'.$platform, $tokenOutputProcessorDefinition) ->addTag('ai.platform.token_usage_processor', ['agent' => $agentId, 'priority' => -30]); } diff --git a/src/platform/src/Bridge/Gemini/TokenUsageDeterminer.php b/src/platform/src/Bridge/Gemini/TokenUsageResultHandler.php similarity index 88% rename from src/platform/src/Bridge/Gemini/TokenUsageDeterminer.php rename to src/platform/src/Bridge/Gemini/TokenUsageResultHandler.php index 277fc1525..9641ee8b4 100644 --- a/src/platform/src/Bridge/Gemini/TokenUsageDeterminer.php +++ b/src/platform/src/Bridge/Gemini/TokenUsageResultHandler.php @@ -12,14 +12,14 @@ namespace Symfony\AI\Platform\Bridge\Gemini; use Symfony\AI\Platform\Metadata\TokenUsage; +use Symfony\AI\Platform\Result\ResultHandlerInterface; use Symfony\AI\Platform\Result\ResultInterface; use Symfony\AI\Platform\Result\StreamResult; -use Symfony\AI\Platform\Result\TokenUsageDeterminerInterface; use Symfony\Contracts\HttpClient\ResponseInterface; -final class TokenUsageDeterminer implements TokenUsageDeterminerInterface +final class TokenUsageResultHandler implements ResultHandlerInterface { - public function determineTokenUsage(ResultInterface $result): void + public function handleResult(ResultInterface $result): void { if ($result instanceof StreamResult) { // Streams have to be handled manually as the tokens are part of the streamed chunks diff --git a/src/platform/src/Bridge/Mistral/TokenUsageDeterminer.php b/src/platform/src/Bridge/Mistral/TokenUsageResultHandler.php similarity index 90% rename from src/platform/src/Bridge/Mistral/TokenUsageDeterminer.php rename to src/platform/src/Bridge/Mistral/TokenUsageResultHandler.php index 97dff8810..61fadb6d6 100644 --- a/src/platform/src/Bridge/Mistral/TokenUsageDeterminer.php +++ b/src/platform/src/Bridge/Mistral/TokenUsageResultHandler.php @@ -12,17 +12,17 @@ namespace Symfony\AI\Platform\Bridge\Mistral; use Symfony\AI\Platform\Metadata\TokenUsage; +use Symfony\AI\Platform\Result\ResultHandlerInterface; use Symfony\AI\Platform\Result\ResultInterface; use Symfony\AI\Platform\Result\StreamResult; -use Symfony\AI\Platform\Result\TokenUsageDeterminerInterface; use Symfony\Contracts\HttpClient\ResponseInterface; /** * @author Quentin Fahrner */ -final class TokenUsageDeterminer implements TokenUsageDeterminerInterface +final class TokenUsageResultHandler implements ResultHandlerInterface { - public function determineTokenUsage(ResultInterface $result): void + public function handleResult(ResultInterface $result): void { if ($result instanceof StreamResult) { // Streams have to be handled manually as the tokens are part of the streamed chunks diff --git a/src/platform/src/Bridge/OpenAi/TokenUsageDeterminer.php b/src/platform/src/Bridge/OpenAi/TokenUsageResultHandler.php similarity index 89% rename from src/platform/src/Bridge/OpenAi/TokenUsageDeterminer.php rename to src/platform/src/Bridge/OpenAi/TokenUsageResultHandler.php index d76c9e77a..8214d5d5d 100644 --- a/src/platform/src/Bridge/OpenAi/TokenUsageDeterminer.php +++ b/src/platform/src/Bridge/OpenAi/TokenUsageResultHandler.php @@ -12,17 +12,17 @@ namespace Symfony\AI\Platform\Bridge\OpenAi; use Symfony\AI\Platform\Metadata\TokenUsage; +use Symfony\AI\Platform\Result\ResultHandlerInterface; use Symfony\AI\Platform\Result\ResultInterface; use Symfony\AI\Platform\Result\StreamResult; -use Symfony\AI\Platform\Result\TokenUsageDeterminerInterface; use Symfony\Contracts\HttpClient\ResponseInterface; /** * @author Denis Zunke */ -final class TokenUsageDeterminer implements TokenUsageDeterminerInterface +final class TokenUsageResultHandler implements ResultHandlerInterface { - public function determineTokenUsage(ResultInterface $result): void + public function handleResult(ResultInterface $result): void { if ($result instanceof StreamResult) { // Streams have to be handled manually as the tokens are part of the streamed chunks diff --git a/src/platform/src/Bridge/Perplexity/SearchResultProcessor.php b/src/platform/src/Bridge/Perplexity/SearchResultHandler.php similarity index 70% rename from src/platform/src/Bridge/Perplexity/SearchResultProcessor.php rename to src/platform/src/Bridge/Perplexity/SearchResultHandler.php index 217be96c1..1bc704d44 100644 --- a/src/platform/src/Bridge/Perplexity/SearchResultProcessor.php +++ b/src/platform/src/Bridge/Perplexity/SearchResultHandler.php @@ -11,29 +11,29 @@ namespace Symfony\AI\Platform\Bridge\Perplexity; -use Symfony\AI\Agent\Output; -use Symfony\AI\Agent\OutputProcessorInterface; +use Symfony\AI\Platform\Result\ResultHandlerInterface; +use Symfony\AI\Platform\Result\ResultInterface; use Symfony\AI\Platform\Result\StreamResult; use Symfony\Contracts\HttpClient\ResponseInterface; /** * @author Mathieu Santostefano */ -final class SearchResultProcessor implements OutputProcessorInterface +final class SearchResultHandler implements ResultHandlerInterface { - public function processOutput(Output $output): void + public function handleResult(ResultInterface $result): void { - $metadata = $output->result->getMetadata(); + $metadata = $result->getMetadata(); - if ($output->result instanceof StreamResult) { - $generator = $output->result->getContent(); + if ($result instanceof StreamResult) { + $generator = $result->getContent(); // Makes $metadata accessible in the stream loop. $generator->send($metadata); return; } - $rawResponse = $output->result->getRawResult()?->getObject(); + $rawResponse = $result->getRawResult()?->getObject(); if (!$rawResponse instanceof ResponseInterface) { return; } diff --git a/src/platform/src/Bridge/Perplexity/TokenUsageDeterminer.php b/src/platform/src/Bridge/Perplexity/TokenUsageResultHandler.php similarity index 87% rename from src/platform/src/Bridge/Perplexity/TokenUsageDeterminer.php rename to src/platform/src/Bridge/Perplexity/TokenUsageResultHandler.php index 4594e1d99..e5ed59e76 100644 --- a/src/platform/src/Bridge/Perplexity/TokenUsageDeterminer.php +++ b/src/platform/src/Bridge/Perplexity/TokenUsageResultHandler.php @@ -12,17 +12,17 @@ namespace Symfony\AI\Platform\Bridge\Perplexity; use Symfony\AI\Platform\Metadata\TokenUsage; +use Symfony\AI\Platform\Result\ResultHandlerInterface; use Symfony\AI\Platform\Result\ResultInterface; use Symfony\AI\Platform\Result\StreamResult; -use Symfony\AI\Platform\Result\TokenUsageDeterminerInterface; use Symfony\Contracts\HttpClient\ResponseInterface; /** * @author Mathieu Santostefano */ -final class TokenUsageDeterminer implements TokenUsageDeterminerInterface +final class TokenUsageResultHandler implements ResultHandlerInterface { - public function determineTokenUsage(ResultInterface $result): void + public function handleResult(ResultInterface $result): void { if ($result instanceof StreamResult) { // Streams have to be handled manually as the tokens are part of the streamed chunks diff --git a/src/platform/src/Bridge/VertexAi/TokenUsageDeterminer.php b/src/platform/src/Bridge/VertexAi/TokenUsageResultHandler.php similarity index 93% rename from src/platform/src/Bridge/VertexAi/TokenUsageDeterminer.php rename to src/platform/src/Bridge/VertexAi/TokenUsageResultHandler.php index b1befb32e..fb8fc4c2b 100644 --- a/src/platform/src/Bridge/VertexAi/TokenUsageDeterminer.php +++ b/src/platform/src/Bridge/VertexAi/TokenUsageResultHandler.php @@ -13,9 +13,9 @@ use Symfony\AI\Platform\Metadata\Metadata; use Symfony\AI\Platform\Metadata\TokenUsage; +use Symfony\AI\Platform\Result\ResultHandlerInterface; use Symfony\AI\Platform\Result\ResultInterface; use Symfony\AI\Platform\Result\StreamResult; -use Symfony\AI\Platform\Result\TokenUsageDeterminerInterface; use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface; use Symfony\Contracts\HttpClient\Exception\DecodingExceptionInterface; use Symfony\Contracts\HttpClient\Exception\RedirectionExceptionInterface; @@ -26,7 +26,7 @@ /** * @author Junaid Farooq */ -final class TokenUsageDeterminer implements TokenUsageDeterminerInterface +final class TokenUsageResultHandler implements ResultHandlerInterface { /** * @throws TransportExceptionInterface @@ -35,7 +35,7 @@ final class TokenUsageDeterminer implements TokenUsageDeterminerInterface * @throws DecodingExceptionInterface * @throws ClientExceptionInterface */ - public function determineTokenUsage(ResultInterface $result): void + public function handleResult(ResultInterface $result): void { $tokenUsage = new TokenUsage(); $metadata = $result->getMetadata(); diff --git a/src/platform/src/Result/TokenUsageDeterminerInterface.php b/src/platform/src/Result/ResultHandlerInterface.php similarity index 71% rename from src/platform/src/Result/TokenUsageDeterminerInterface.php rename to src/platform/src/Result/ResultHandlerInterface.php index d5bf4af6c..df561e248 100644 --- a/src/platform/src/Result/TokenUsageDeterminerInterface.php +++ b/src/platform/src/Result/ResultHandlerInterface.php @@ -11,7 +11,7 @@ namespace Symfony\AI\Platform\Result; -interface TokenUsageDeterminerInterface +interface ResultHandlerInterface { - public function determineTokenUsage(ResultInterface $result): void; + public function handleResult(ResultInterface $result): void; } diff --git a/src/platform/tests/Bridge/Gemini/TokenUsageDeterminerTest.php b/src/platform/tests/Bridge/Gemini/TokenUsageDeterminerTest.php index 83ace31fe..29c62d53f 100644 --- a/src/platform/tests/Bridge/Gemini/TokenUsageDeterminerTest.php +++ b/src/platform/tests/Bridge/Gemini/TokenUsageDeterminerTest.php @@ -15,7 +15,7 @@ use PHPUnit\Framework\Attributes\Small; use PHPUnit\Framework\Attributes\UsesClass; use PHPUnit\Framework\TestCase; -use Symfony\AI\Platform\Bridge\Gemini\TokenUsageDeterminer; +use Symfony\AI\Platform\Bridge\Gemini\TokenUsageResultHandler; use Symfony\AI\Platform\Metadata\Metadata; use Symfony\AI\Platform\Metadata\TokenUsage; use Symfony\AI\Platform\Result\RawHttpResult; @@ -23,7 +23,7 @@ use Symfony\AI\Platform\Result\TextResult; use Symfony\Contracts\HttpClient\ResponseInterface; -#[CoversClass(TokenUsageDeterminer::class)] +#[CoversClass(TokenUsageResultHandler::class)] #[UsesClass(TextResult::class)] #[UsesClass(StreamResult::class)] #[UsesClass(Metadata::class)] @@ -33,10 +33,10 @@ final class TokenUsageDeterminerTest extends TestCase { public function testItHandlesStreamResponsesWithoutProcessing() { - $determiner = new TokenUsageDeterminer(); + $tokenUsageResultHandler = new TokenUsageResultHandler(); $streamResult = new StreamResult((static function () { yield 'test'; })()); - $determiner->determineTokenUsage($streamResult); + $tokenUsageResultHandler->handleResult($streamResult); $metadata = $streamResult->getMetadata(); $this->assertCount(0, $metadata); @@ -44,10 +44,10 @@ public function testItHandlesStreamResponsesWithoutProcessing() public function testItDoesNothingWithoutRawResponse() { - $determiner = new TokenUsageDeterminer(); + $tokenUsageResultHandler = new TokenUsageResultHandler(); $textResult = new TextResult('test'); - $determiner->determineTokenUsage($textResult); + $tokenUsageResultHandler->handleResult($textResult); $metadata = $textResult->getMetadata(); $this->assertCount(0, $metadata); @@ -55,12 +55,12 @@ public function testItDoesNothingWithoutRawResponse() public function testItAddsRemainingTokensToMetadata() { - $determiner = new TokenUsageDeterminer(); + $tokenUsageResultHandler = new TokenUsageResultHandler(); $textResult = new TextResult('test'); $textResult->setRawResult($this->createRawResult()); - $determiner->determineTokenUsage($textResult); + $tokenUsageResultHandler->handleResult($textResult); $metadata = $textResult->getMetadata(); $tokenUsage = $metadata->get('token_usage'); @@ -72,7 +72,7 @@ public function testItAddsRemainingTokensToMetadata() public function testItAddsUsageTokensToMetadata() { - $determiner = new TokenUsageDeterminer(); + $tokenUsageResultHandler = new TokenUsageResultHandler(); $textResult = new TextResult('test'); $rawResult = $this->createRawResult([ @@ -88,7 +88,7 @@ public function testItAddsUsageTokensToMetadata() $textResult->setRawResult($rawResult); - $determiner->determineTokenUsage($textResult); + $tokenUsageResultHandler->handleResult($textResult); $metadata = $textResult->getMetadata(); $tokenUsage = $metadata->get('token_usage'); @@ -105,7 +105,7 @@ public function testItAddsUsageTokensToMetadata() public function testItHandlesMissingUsageFields() { - $determiner = new TokenUsageDeterminer(); + $tokenUsageResultHandler = new TokenUsageResultHandler(); $textResult = new TextResult('test'); $rawResult = $this->createRawResult([ @@ -116,7 +116,7 @@ public function testItHandlesMissingUsageFields() ]); $textResult->setRawResult($rawResult); - $determiner->determineTokenUsage($textResult); + $tokenUsageResultHandler->handleResult($textResult); $metadata = $textResult->getMetadata(); $tokenUsage = $metadata->get('token_usage'); diff --git a/src/platform/tests/Bridge/Mistral/TokenUsageDeterminerTest.php b/src/platform/tests/Bridge/Mistral/TokenUsageDeterminerTest.php index e469178da..882db5437 100644 --- a/src/platform/tests/Bridge/Mistral/TokenUsageDeterminerTest.php +++ b/src/platform/tests/Bridge/Mistral/TokenUsageDeterminerTest.php @@ -15,7 +15,7 @@ use PHPUnit\Framework\Attributes\Small; use PHPUnit\Framework\Attributes\UsesClass; use PHPUnit\Framework\TestCase; -use Symfony\AI\Platform\Bridge\Mistral\TokenUsageDeterminer; +use Symfony\AI\Platform\Bridge\Mistral\TokenUsageResultHandler; use Symfony\AI\Platform\Metadata\Metadata; use Symfony\AI\Platform\Metadata\TokenUsage; use Symfony\AI\Platform\Result\RawHttpResult; @@ -23,7 +23,7 @@ use Symfony\AI\Platform\Result\TextResult; use Symfony\Contracts\HttpClient\ResponseInterface; -#[CoversClass(TokenUsageDeterminer::class)] +#[CoversClass(TokenUsageResultHandler::class)] #[UsesClass(TextResult::class)] #[UsesClass(StreamResult::class)] #[UsesClass(Metadata::class)] @@ -32,10 +32,10 @@ final class TokenUsageDeterminerTest extends TestCase { public function testItHandlesStreamResponsesWithoutProcessing() { - $determiner = new TokenUsageDeterminer(); + $tokenUsageResultHandler = new TokenUsageResultHandler(); $streamResult = new StreamResult((static function () { yield 'test'; })()); - $determiner->determineTokenUsage($streamResult); + $tokenUsageResultHandler->handleResult($streamResult); $metadata = $streamResult->getMetadata(); $this->assertCount(0, $metadata); @@ -43,10 +43,10 @@ public function testItHandlesStreamResponsesWithoutProcessing() public function testItDoesNothingWithoutRawResponse() { - $determiner = new TokenUsageDeterminer(); + $tokenUsageResultHandler = new TokenUsageResultHandler(); $textResult = new TextResult('test'); - $determiner->determineTokenUsage($textResult); + $tokenUsageResultHandler->handleResult($textResult); $metadata = $textResult->getMetadata(); $this->assertCount(0, $metadata); @@ -54,12 +54,12 @@ public function testItDoesNothingWithoutRawResponse() public function testItAddsRemainingTokensToMetadata() { - $determiner = new TokenUsageDeterminer(); + $tokenUsageResultHandler = new TokenUsageResultHandler(); $textResult = new TextResult('test'); $textResult->setRawResult($this->createRawResponse()); - $determiner->determineTokenUsage($textResult); + $tokenUsageResultHandler->handleResult($textResult); $metadata = $textResult->getMetadata(); $tokenUsage = $metadata->get('token_usage'); @@ -72,7 +72,7 @@ public function testItAddsRemainingTokensToMetadata() public function testItAddsUsageTokensToMetadata() { - $determiner = new TokenUsageDeterminer(); + $tokenUsageResultHandler = new TokenUsageResultHandler(); $textResult = new TextResult('test'); $rawResponse = $this->createRawResponse([ @@ -85,7 +85,7 @@ public function testItAddsUsageTokensToMetadata() $textResult->setRawResult($rawResponse); - $determiner->determineTokenUsage($textResult); + $tokenUsageResultHandler->handleResult($textResult); $metadata = $textResult->getMetadata(); $tokenUsage = $metadata->get('token_usage'); @@ -100,7 +100,7 @@ public function testItAddsUsageTokensToMetadata() public function testItHandlesMissingUsageFields() { - $determiner = new TokenUsageDeterminer(); + $tokenUsageResultHandler = new TokenUsageResultHandler(); $textResult = new TextResult('test'); $rawResponse = $this->createRawResponse([ @@ -112,7 +112,7 @@ public function testItHandlesMissingUsageFields() $textResult->setRawResult($rawResponse); - $determiner->determineTokenUsage($textResult); + $tokenUsageResultHandler->handleResult($textResult); $metadata = $textResult->getMetadata(); $tokenUsage = $metadata->get('token_usage'); diff --git a/src/platform/tests/Bridge/OpenAi/TokenUsageDeterminerTest.php b/src/platform/tests/Bridge/OpenAi/TokenUsageDeterminerTest.php index 3926d5552..4959dd1ba 100644 --- a/src/platform/tests/Bridge/OpenAi/TokenUsageDeterminerTest.php +++ b/src/platform/tests/Bridge/OpenAi/TokenUsageDeterminerTest.php @@ -15,7 +15,7 @@ use PHPUnit\Framework\Attributes\Small; use PHPUnit\Framework\Attributes\UsesClass; use PHPUnit\Framework\TestCase; -use Symfony\AI\Platform\Bridge\OpenAi\TokenUsageDeterminer; +use Symfony\AI\Platform\Bridge\OpenAi\TokenUsageResultHandler; use Symfony\AI\Platform\Metadata\Metadata; use Symfony\AI\Platform\Metadata\TokenUsage; use Symfony\AI\Platform\Result\RawHttpResult; @@ -23,7 +23,7 @@ use Symfony\AI\Platform\Result\TextResult; use Symfony\Contracts\HttpClient\ResponseInterface; -#[CoversClass(TokenUsageDeterminer::class)] +#[CoversClass(TokenUsageResultHandler::class)] #[UsesClass(TextResult::class)] #[UsesClass(StreamResult::class)] #[UsesClass(Metadata::class)] @@ -33,10 +33,10 @@ final class TokenUsageDeterminerTest extends TestCase { public function testItHandlesStreamResponsesWithoutProcessing() { - $determiner = new TokenUsageDeterminer(); + $tokenUsageResultHandler = new TokenUsageResultHandler(); $streamResult = new StreamResult((static function () { yield 'test'; })()); - $determiner->determineTokenUsage($streamResult); + $tokenUsageResultHandler->handleResult($streamResult); $metadata = $streamResult->getMetadata(); $this->assertCount(0, $metadata); @@ -44,10 +44,10 @@ public function testItHandlesStreamResponsesWithoutProcessing() public function testItDoesNothingWithoutRawResponse() { - $determiner = new TokenUsageDeterminer(); + $tokenUsageResultHandler = new TokenUsageResultHandler(); $textResult = new TextResult('test'); - $determiner->determineTokenUsage($textResult); + $tokenUsageResultHandler->handleResult($textResult); $metadata = $textResult->getMetadata(); $this->assertCount(0, $metadata); @@ -55,12 +55,12 @@ public function testItDoesNothingWithoutRawResponse() public function testItAddsRemainingTokensToMetadata() { - $determiner = new TokenUsageDeterminer(); + $tokenUsageResultHandler = new TokenUsageResultHandler(); $textResult = new TextResult('test'); $textResult->setRawResult($this->createRawResult()); - $determiner->determineTokenUsage($textResult); + $tokenUsageResultHandler->handleResult($textResult); $metadata = $textResult->getMetadata(); $tokenUsage = $metadata->get('token_usage'); @@ -72,7 +72,7 @@ public function testItAddsRemainingTokensToMetadata() public function testItAddsUsageTokensToMetadata() { - $determiner = new TokenUsageDeterminer(); + $tokenUsageResultHandler = new TokenUsageResultHandler(); $textResult = new TextResult('test'); $rawResult = $this->createRawResult([ @@ -91,7 +91,7 @@ public function testItAddsUsageTokensToMetadata() $textResult->setRawResult($rawResult); - $determiner->determineTokenUsage($textResult); + $tokenUsageResultHandler->handleResult($textResult); $metadata = $textResult->getMetadata(); $tokenUsage = $metadata->get('token_usage'); @@ -107,7 +107,7 @@ public function testItAddsUsageTokensToMetadata() public function testItHandlesMissingUsageFields() { - $determiner = new TokenUsageDeterminer(); + $tokenUsageResultHandler = new TokenUsageResultHandler(); $textResult = new TextResult('test'); $rawResult = $this->createRawResult([ @@ -119,7 +119,7 @@ public function testItHandlesMissingUsageFields() $textResult->setRawResult($rawResult); - $determiner->determineTokenUsage($textResult); + $tokenUsageResultHandler->handleResult($textResult); $metadata = $textResult->getMetadata(); $tokenUsage = $metadata->get('token_usage'); diff --git a/src/platform/tests/Bridge/Perplexity/TokenUsageDeterminerTest.php b/src/platform/tests/Bridge/Perplexity/TokenUsageDeterminerTest.php index 844704903..615d5dd42 100644 --- a/src/platform/tests/Bridge/Perplexity/TokenUsageDeterminerTest.php +++ b/src/platform/tests/Bridge/Perplexity/TokenUsageDeterminerTest.php @@ -15,7 +15,7 @@ use PHPUnit\Framework\Attributes\Small; use PHPUnit\Framework\Attributes\UsesClass; use PHPUnit\Framework\TestCase; -use Symfony\AI\Platform\Bridge\Perplexity\TokenUsageDeterminer; +use Symfony\AI\Platform\Bridge\Perplexity\TokenUsageResultHandler; use Symfony\AI\Platform\Metadata\Metadata; use Symfony\AI\Platform\Metadata\TokenUsage; use Symfony\AI\Platform\Result\RawHttpResult; @@ -26,7 +26,7 @@ /** * @author Mathieu Santostefano */ -#[CoversClass(TokenUsageDeterminer::class)] +#[CoversClass(TokenUsageResultHandler::class)] #[UsesClass(TextResult::class)] #[UsesClass(StreamResult::class)] #[UsesClass(Metadata::class)] @@ -36,10 +36,10 @@ final class TokenUsageDeterminerTest extends TestCase { public function testItHandlesStreamResponsesWithoutProcessing() { - $determiner = new TokenUsageDeterminer(); + $tokenUsageResultHandler = new TokenUsageResultHandler(); $streamResult = new StreamResult((static function () { yield 'test'; })()); - $determiner->determineTokenUsage($streamResult); + $tokenUsageResultHandler->handleResult($streamResult); $metadata = $streamResult->getMetadata(); $this->assertCount(0, $metadata); @@ -47,10 +47,10 @@ public function testItHandlesStreamResponsesWithoutProcessing() public function testItDoesNothingWithoutRawResponse() { - $determiner = new TokenUsageDeterminer(); + $tokenUsageResultHandler = new TokenUsageResultHandler(); $textResult = new TextResult('test'); - $determiner->determineTokenUsage($textResult); + $tokenUsageResultHandler->handleResult($textResult); $metadata = $textResult->getMetadata(); $this->assertCount(0, $metadata); @@ -58,7 +58,7 @@ public function testItDoesNothingWithoutRawResponse() public function testItAddsUsageTokensToMetadata() { - $determiner = new TokenUsageDeterminer(); + $tokenUsageResultHandler = new TokenUsageResultHandler(); $textResult = new TextResult('test'); $rawResult = $this->createRawResult([ @@ -72,7 +72,7 @@ public function testItAddsUsageTokensToMetadata() $textResult->setRawResult($rawResult); - $determiner->determineTokenUsage($textResult); + $tokenUsageResultHandler->handleResult($textResult); $metadata = $textResult->getMetadata(); $tokenUsage = $metadata->get('token_usage'); @@ -86,7 +86,7 @@ public function testItAddsUsageTokensToMetadata() public function testItHandlesMissingUsageFields() { - $determiner = new TokenUsageDeterminer(); + $tokenUsageResultHandler = new TokenUsageResultHandler(); $textResult = new TextResult('test'); $rawResult = $this->createRawResult([ @@ -98,7 +98,7 @@ public function testItHandlesMissingUsageFields() $textResult->setRawResult($rawResult); - $determiner->determineTokenUsage($textResult); + $tokenUsageResultHandler->handleResult($textResult); $metadata = $textResult->getMetadata(); $tokenUsage = $metadata->get('token_usage'); diff --git a/src/platform/tests/Bridge/VertexAi/TokenUsageDeterminerTest.php b/src/platform/tests/Bridge/VertexAi/TokenUsageDeterminerTest.php index 0ecf65f6d..d726f67fe 100644 --- a/src/platform/tests/Bridge/VertexAi/TokenUsageDeterminerTest.php +++ b/src/platform/tests/Bridge/VertexAi/TokenUsageDeterminerTest.php @@ -15,7 +15,7 @@ use PHPUnit\Framework\Attributes\Small; use PHPUnit\Framework\Attributes\UsesClass; use PHPUnit\Framework\TestCase; -use Symfony\AI\Platform\Bridge\VertexAi\TokenUsageDeterminer; +use Symfony\AI\Platform\Bridge\VertexAi\TokenUsageResultHandler; use Symfony\AI\Platform\Metadata\Metadata; use Symfony\AI\Platform\Metadata\TokenUsage; use Symfony\AI\Platform\Result\RawHttpResult; @@ -23,7 +23,7 @@ use Symfony\AI\Platform\Result\TextResult; use Symfony\Contracts\HttpClient\ResponseInterface; -#[CoversClass(TokenUsageDeterminer::class)] +#[CoversClass(TokenUsageResultHandler::class)] #[UsesClass(TextResult::class)] #[UsesClass(StreamResult::class)] #[UsesClass(Metadata::class)] @@ -32,10 +32,10 @@ final class TokenUsageDeterminerTest extends TestCase { public function testItDoesNothingWithoutRawResponse() { - $determiner = new TokenUsageDeterminer(); + $tokenUsageResultHandler = new TokenUsageResultHandler(); $textResult = new TextResult('test'); - $determiner->determineTokenUsage($textResult); + $tokenUsageResultHandler->handleResult($textResult); $this->assertCount(0, $textResult->getMetadata()); } @@ -55,10 +55,10 @@ public function testItAddsUsageTokensToMetadata() ]); $textResult->setRawResult($rawResponse); - $determiner = new TokenUsageDeterminer(); + $tokenUsageResultHandler = new TokenUsageResultHandler(); // Act - $determiner->determineTokenUsage($textResult); + $tokenUsageResultHandler->handleResult($textResult); // Assert $metadata = $textResult->getMetadata(); @@ -84,10 +84,10 @@ public function testItHandlesMissingUsageFields() ]); $textResult->setRawResult($rawResponse); - $determiner = new TokenUsageDeterminer(); + $tokenUsageResultHandler = new TokenUsageResultHandler(); // Act - $determiner->determineTokenUsage($textResult); + $tokenUsageResultHandler->handleResult($textResult); // Assert $metadata = $textResult->getMetadata(); @@ -107,10 +107,10 @@ public function testItAddsEmptyTokenUsageWhenUsageMetadataNotPresent() $textResult = new TextResult('test'); $rawResponse = $this->createRawResponse(['other' => 'data']); $textResult->setRawResult($rawResponse); - $determiner = new TokenUsageDeterminer(); + $tokenUsageResultHandler = new TokenUsageResultHandler(); // Act - $determiner->determineTokenUsage($textResult); + $tokenUsageResultHandler->handleResult($textResult); // Assert $metadata = $textResult->getMetadata(); @@ -126,7 +126,7 @@ public function testItAddsEmptyTokenUsageWhenUsageMetadataNotPresent() public function testItHandlesStreamResults() { - $determiner = new TokenUsageDeterminer(); + $tokenUsageResultHandler = new TokenUsageResultHandler(); $chunks = [ ['content' => 'chunk1'], ['content' => 'chunk2', 'usageMetadata' => [ @@ -142,7 +142,7 @@ public function testItHandlesStreamResults() } })()); - $determiner->determineTokenUsage($streamResult); + $tokenUsageResultHandler->handleResult($streamResult); $metadata = $streamResult->getMetadata(); $tokenUsage = $metadata->get('token_usage'); From 53bf2356db223a7e39ffa89fef3d7cd6aa591cb5 Mon Sep 17 00:00:00 2001 From: glady Date: Fri, 12 Sep 2025 18:32:46 +0200 Subject: [PATCH 08/11] fixed renamed service in ai-bundle --- src/ai-bundle/tests/DependencyInjection/AiBundleTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ai-bundle/tests/DependencyInjection/AiBundleTest.php b/src/ai-bundle/tests/DependencyInjection/AiBundleTest.php index 5d6745daf..ecd46e65b 100644 --- a/src/ai-bundle/tests/DependencyInjection/AiBundleTest.php +++ b/src/ai-bundle/tests/DependencyInjection/AiBundleTest.php @@ -544,8 +544,8 @@ public function testTokenUsageProcessorTags() $agentId = 'ai.agent.tracked_agent'; // Token usage determiner must exist for OpenAI platform - $tokenUsageDeterminer = $container->getDefinition('ai.platform.token_usage_determiner.openai'); - $outputTags = $tokenUsageDeterminer->getTag('ai.agent.token_usage_determiner'); + $tokenUsageDeterminer = $container->getDefinition('ai.platform.token_usage_result_handler.openai'); + $outputTags = $tokenUsageDeterminer->getTag('ai.agent.token_usage_result_handler'); $foundTag = false; foreach ($outputTags as $tag) { From dd79232a942511b02b5d5fa05bebb4ab3f3aa455 Mon Sep 17 00:00:00 2001 From: glady Date: Fri, 12 Sep 2025 18:45:43 +0200 Subject: [PATCH 09/11] added argument to tokenOutputProcessor --- src/ai-bundle/src/AiBundle.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/ai-bundle/src/AiBundle.php b/src/ai-bundle/src/AiBundle.php index fac4d66e6..b5c300bcd 100644 --- a/src/ai-bundle/src/AiBundle.php +++ b/src/ai-bundle/src/AiBundle.php @@ -583,6 +583,8 @@ private function processAgentConfig(string $name, array $config, ContainerBuilde ->addTag('ai.agent.token_usage_result_handler', ['agent' => $agentId]); $tokenOutputProcessorDefinition = new Definition(ResultOutputProcessor::class); + $tokenOutputProcessorDefinition->setArgument(0, new Reference('ai.agent.token_usage_result_handler')); + $container->setDefinition('ai.platform.token_usage_processor.'.$platform, $tokenOutputProcessorDefinition) ->addTag('ai.platform.token_usage_processor', ['agent' => $agentId, 'priority' => -30]); } From 5ee882c0d4872cfe880a21d42b70b37bdfb16db5 Mon Sep 17 00:00:00 2001 From: glady Date: Fri, 12 Sep 2025 18:48:28 +0200 Subject: [PATCH 10/11] fixed name of tag in test --- src/ai-bundle/tests/DependencyInjection/AiBundleTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ai-bundle/tests/DependencyInjection/AiBundleTest.php b/src/ai-bundle/tests/DependencyInjection/AiBundleTest.php index ecd46e65b..76d30bb84 100644 --- a/src/ai-bundle/tests/DependencyInjection/AiBundleTest.php +++ b/src/ai-bundle/tests/DependencyInjection/AiBundleTest.php @@ -559,7 +559,7 @@ public function testTokenUsageProcessorTags() // Token usage processor must exist for OpenAI platform $tokenOutputProcessor = $container->getDefinition('ai.platform.token_usage_processor.openai'); - $outputTags = $tokenOutputProcessor->getTag('ai.agent.token_usage_processor'); + $outputTags = $tokenOutputProcessor->getTag('ai.platform.token_usage_processor'); $foundTag = false; foreach ($outputTags as $tag) { From 4ceff9d500d061c959f5026d61541ffde5d66e60 Mon Sep 17 00:00:00 2001 From: glady Date: Fri, 12 Sep 2025 19:09:58 +0200 Subject: [PATCH 11/11] fixed merge conflicts from main because of added token output processor for anthropic --- examples/anthropic/token-metadata.php | 5 +- src/ai-bundle/config/services.php | 12 ++-- ...cessor.php => TokenUsageResultHandler.php} | 14 ++--- ...st.php => TokenUsageResultHandlerTest.php} | 59 ++++++------------- 4 files changed, 34 insertions(+), 56 deletions(-) rename src/platform/src/Bridge/Anthropic/{TokenOutputProcessor.php => TokenUsageResultHandler.php} (79%) rename src/platform/tests/Bridge/Anthropic/{TokenOutputProcessorTest.php => TokenUsageResultHandlerTest.php} (69%) diff --git a/examples/anthropic/token-metadata.php b/examples/anthropic/token-metadata.php index cf1c37c43..18f27752c 100644 --- a/examples/anthropic/token-metadata.php +++ b/examples/anthropic/token-metadata.php @@ -10,9 +10,10 @@ */ use Symfony\AI\Agent\Agent; +use Symfony\AI\Agent\OutputProcessor\ResultOutputProcessor; use Symfony\AI\Platform\Bridge\Anthropic\Claude; use Symfony\AI\Platform\Bridge\Anthropic\PlatformFactory; -use Symfony\AI\Platform\Bridge\Anthropic\TokenOutputProcessor; +use Symfony\AI\Platform\Bridge\Anthropic\TokenUsageResultHandler; use Symfony\AI\Platform\Message\Message; use Symfony\AI\Platform\Message\MessageBag; @@ -21,7 +22,7 @@ $platform = PlatformFactory::create(env('ANTHROPIC_API_KEY'), http_client()); $model = new Claude(Claude::SONNET_37); -$agent = new Agent($platform, $model, outputProcessors: [new TokenOutputProcessor()], logger: logger()); +$agent = new Agent($platform, $model, outputProcessors: [new ResultOutputProcessor(new TokenUsageResultHandler())], logger: logger()); $messages = new MessageBag( Message::forSystem('You are a pirate and you write funny.'), Message::ofUser('What is the Symfony framework?'), diff --git a/src/ai-bundle/config/services.php b/src/ai-bundle/config/services.php index ba513c2a3..ebc10199f 100644 --- a/src/ai-bundle/config/services.php +++ b/src/ai-bundle/config/services.php @@ -26,7 +26,7 @@ use Symfony\AI\AiBundle\Profiler\TraceableToolbox; use Symfony\AI\AiBundle\Security\EventListener\IsGrantedToolAttributeListener; use Symfony\AI\Platform\Bridge\Anthropic\Contract\AnthropicContract; -use Symfony\AI\Platform\Bridge\Anthropic\TokenOutputProcessor as AnthropicTokenOutputProcessor; +use Symfony\AI\Platform\Bridge\Anthropic\TokenUsageResultHandler as AnthropicTokenUsageResultHandler; use Symfony\AI\Platform\Bridge\Gemini\Contract\GeminiContract; use Symfony\AI\Platform\Bridge\Gemini\TokenUsageResultHandler as GeminiTokenUsageResultHandler; use Symfony\AI\Platform\Bridge\Mistral\TokenUsageResultHandler as MistralTokenUsageResultHandler; @@ -34,8 +34,8 @@ use Symfony\AI\Platform\Bridge\OpenAi\Contract\OpenAiContract; use Symfony\AI\Platform\Bridge\OpenAi\TokenUsageResultHandler as OpenAiTokenUsageResultHandler; use Symfony\AI\Platform\Bridge\Perplexity\Contract\PerplexityContract; -use Symfony\AI\Platform\Bridge\Perplexity\SearchResultProcessor as PerplexitySearchResultProcessor; -use Symfony\AI\Platform\Bridge\Perplexity\TokenOutputProcessor as PerplexityTokenOutputProcessor; +use Symfony\AI\Platform\Bridge\Perplexity\SearchResultHandler as PerplexitySearchResultHandler; +use Symfony\AI\Platform\Bridge\Perplexity\TokenUsageResultHandler as PerplexityTokenUsageResultHandler; use Symfony\AI\Platform\Bridge\VertexAi\Contract\GeminiContract as VertexAiGeminiContract; use Symfony\AI\Platform\Bridge\VertexAi\TokenUsageResultHandler as VertexAiTokenUsageResultHandler; use Symfony\AI\Platform\Contract; @@ -146,12 +146,12 @@ ->set('ai.platform.token_usage_result_handler.mistral', MistralTokenUsageResultHandler::class) ->set('ai.platform.token_usage_result_handler.gemini', GeminiTokenUsageResultHandler::class) ->set('ai.platform.token_usage_result_handler.openai', OpenAiTokenUsageResultHandler::class) - ->set('ai.platform.token_usage_result_handler.perplexity', PerplexityTokenOutputProcessor::class) + ->set('ai.platform.token_usage_result_handler.perplexity', PerplexityTokenUsageResultHandler::class) ->set('ai.platform.token_usage_result_handler.vertexai', VertexAiTokenUsageResultHandler::class) - ->set('ai.platform.token_usage_result_handler.anthropic', AnthropicTokenOutputProcessor::class) + ->set('ai.platform.token_usage_result_handler.anthropic', AnthropicTokenUsageResultHandler::class) // search result processors - ->set('ai.platform.search_result_processor.perplexity', PerplexitySearchResultProcessor::class) + ->set('ai.platform.search_result_processor.perplexity', PerplexitySearchResultHandler::class) // commands ->set('ai.command.chat', ChatCommand::class) diff --git a/src/platform/src/Bridge/Anthropic/TokenOutputProcessor.php b/src/platform/src/Bridge/Anthropic/TokenUsageResultHandler.php similarity index 79% rename from src/platform/src/Bridge/Anthropic/TokenOutputProcessor.php rename to src/platform/src/Bridge/Anthropic/TokenUsageResultHandler.php index b9c27922a..6c318a748 100644 --- a/src/platform/src/Bridge/Anthropic/TokenOutputProcessor.php +++ b/src/platform/src/Bridge/Anthropic/TokenUsageResultHandler.php @@ -11,27 +11,27 @@ namespace Symfony\AI\Platform\Bridge\Anthropic; -use Symfony\AI\Agent\Output; -use Symfony\AI\Agent\OutputProcessorInterface; use Symfony\AI\Platform\Metadata\TokenUsage; +use Symfony\AI\Platform\Result\ResultHandlerInterface; +use Symfony\AI\Platform\Result\ResultInterface; use Symfony\AI\Platform\Result\StreamResult; use Symfony\Contracts\HttpClient\ResponseInterface; -final class TokenOutputProcessor implements OutputProcessorInterface +final class TokenUsageResultHandler implements ResultHandlerInterface { - public function processOutput(Output $output): void + public function handleResult(ResultInterface $result): void { - if ($output->result instanceof StreamResult) { + if ($result instanceof StreamResult) { // Streams have to be handled manually as the tokens are part of the streamed chunks return; } - $rawResponse = $output->result->getRawResult()?->getObject(); + $rawResponse = $result->getRawResult()?->getObject(); if (!$rawResponse instanceof ResponseInterface) { return; } - $metadata = $output->result->getMetadata(); + $metadata = $result->getMetadata(); $tokenUsage = new TokenUsage(); diff --git a/src/platform/tests/Bridge/Anthropic/TokenOutputProcessorTest.php b/src/platform/tests/Bridge/Anthropic/TokenUsageResultHandlerTest.php similarity index 69% rename from src/platform/tests/Bridge/Anthropic/TokenOutputProcessorTest.php rename to src/platform/tests/Bridge/Anthropic/TokenUsageResultHandlerTest.php index 6310704a1..67c068986 100644 --- a/src/platform/tests/Bridge/Anthropic/TokenOutputProcessorTest.php +++ b/src/platform/tests/Bridge/Anthropic/TokenUsageResultHandlerTest.php @@ -15,63 +15,54 @@ use PHPUnit\Framework\Attributes\Small; use PHPUnit\Framework\Attributes\UsesClass; use PHPUnit\Framework\TestCase; -use Symfony\AI\Agent\Output; -use Symfony\AI\Platform\Bridge\Anthropic\TokenOutputProcessor; -use Symfony\AI\Platform\Message\MessageBag; +use Symfony\AI\Platform\Bridge\Anthropic\TokenUsageResultHandler; use Symfony\AI\Platform\Metadata\Metadata; use Symfony\AI\Platform\Metadata\TokenUsage; -use Symfony\AI\Platform\Model; use Symfony\AI\Platform\Result\RawHttpResult; -use Symfony\AI\Platform\Result\ResultInterface; use Symfony\AI\Platform\Result\StreamResult; use Symfony\AI\Platform\Result\TextResult; use Symfony\Contracts\HttpClient\ResponseInterface; -#[CoversClass(TokenOutputProcessor::class)] -#[UsesClass(Output::class)] +#[CoversClass(TokenUsageResultHandler::class)] #[UsesClass(TextResult::class)] #[UsesClass(StreamResult::class)] #[UsesClass(Metadata::class)] #[UsesClass(TokenUsage::class)] #[Small] -final class TokenOutputProcessorTest extends TestCase +final class TokenUsageResultHandlerTest extends TestCase { public function testItHandlesStreamResponsesWithoutProcessing() { - $processor = new TokenOutputProcessor(); + $tokenUsageResultHandler = new TokenUsageResultHandler(); $streamResult = new StreamResult((static function () { yield 'test'; })()); - $output = $this->createOutput($streamResult); - $processor->processOutput($output); + $tokenUsageResultHandler->handleResult($streamResult); - $metadata = $output->result->getMetadata(); + $metadata = $streamResult->getMetadata(); $this->assertCount(0, $metadata); } public function testItDoesNothingWithoutRawResponse() { - $processor = new TokenOutputProcessor(); + $tokenUsageResultHandler = new TokenUsageResultHandler(); $textResult = new TextResult('test'); - $output = $this->createOutput($textResult); - $processor->processOutput($output); + $tokenUsageResultHandler->handleResult($textResult); - $metadata = $output->result->getMetadata(); + $metadata = $textResult->getMetadata(); $this->assertCount(0, $metadata); } public function testItAddsRemainingTokensToMetadata() { - $processor = new TokenOutputProcessor(); + $tokenUsageResultHandler = new TokenUsageResultHandler(); $textResult = new TextResult('test'); $textResult->setRawResult($this->createRawResult()); - $output = $this->createOutput($textResult); + $tokenUsageResultHandler->handleResult($textResult); - $processor->processOutput($output); - - $metadata = $output->result->getMetadata(); + $metadata = $textResult->getMetadata(); $tokenUsage = $metadata->get('token_usage'); $this->assertCount(1, $metadata); @@ -81,7 +72,7 @@ public function testItAddsRemainingTokensToMetadata() public function testItAddsUsageTokensToMetadata() { - $processor = new TokenOutputProcessor(); + $tokenUsageResultHandler = new TokenUsageResultHandler(); $textResult = new TextResult('test'); $rawResult = $this->createRawResult([ @@ -98,11 +89,9 @@ public function testItAddsUsageTokensToMetadata() $textResult->setRawResult($rawResult); - $output = $this->createOutput($textResult); - - $processor->processOutput($output); + $tokenUsageResultHandler->handleResult($textResult); - $metadata = $output->result->getMetadata(); + $metadata = $textResult->getMetadata(); $tokenUsage = $metadata->get('token_usage'); $this->assertInstanceOf(TokenUsage::class, $tokenUsage); @@ -117,7 +106,7 @@ public function testItAddsUsageTokensToMetadata() public function testItHandlesMissingUsageFields() { - $processor = new TokenOutputProcessor(); + $tokenUsageResultHandler = new TokenUsageResultHandler(); $textResult = new TextResult('test'); $rawResult = $this->createRawResult([ @@ -129,11 +118,9 @@ public function testItHandlesMissingUsageFields() $textResult->setRawResult($rawResult); - $output = $this->createOutput($textResult); - - $processor->processOutput($output); + $tokenUsageResultHandler->handleResult($textResult); - $metadata = $output->result->getMetadata(); + $metadata = $textResult->getMetadata(); $tokenUsage = $metadata->get('token_usage'); $this->assertInstanceOf(TokenUsage::class, $tokenUsage); @@ -150,14 +137,4 @@ private function createRawResult(array $data = []): RawHttpResult return new RawHttpResult($rawResponse); } - - private function createOutput(ResultInterface $result): Output - { - return new Output( - $this->createStub(Model::class), - $result, - new MessageBag(), - [], - ); - } }