diff --git a/.github/workflows/deptrac.yaml b/.github/workflows/deptrac.yaml new file mode 100644 index 000000000..6edc45787 --- /dev/null +++ b/.github/workflows/deptrac.yaml @@ -0,0 +1,59 @@ +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 + + - 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 + run: $COMPOSER_UP && vendor/bin/deptrac 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 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/examples/gemini/token-metadata.php b/examples/gemini/token-metadata.php index 9bddd429c..bc0958357 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\ResultOutputProcessor; 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\TokenUsageResultHandler; use Symfony\AI\Platform\Message\Message; use Symfony\AI\Platform\Message\MessageBag; @@ -21,7 +22,7 @@ $platform = PlatformFactory::create(env('GEMINI_API_KEY'), http_client()); $model = new Gemini(Gemini::GEMINI_2_FLASH); -$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/examples/mistral/token-metadata.php b/examples/mistral/token-metadata.php index 76bd84af3..77ac9b000 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\ResultOutputProcessor; 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\TokenUsageResultHandler; use Symfony\AI\Platform\Message\Message; use Symfony\AI\Platform\Message\MessageBag; @@ -21,7 +22,7 @@ $platform = PlatformFactory::create(env('MISTRAL_API_KEY'), http_client()); $model = new Mistral(); -$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.'), diff --git a/examples/openai/token-metadata.php b/examples/openai/token-metadata.php index d0c22bd44..36d02394f 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\ResultOutputProcessor; 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\TokenUsageResultHandler; use Symfony\AI\Platform\Message\Message; use Symfony\AI\Platform\Message\MessageBag; @@ -21,7 +22,7 @@ $platform = PlatformFactory::create(env('OPENAI_API_KEY'), http_client()); $model = new Gpt(Gpt::GPT_4O_MINI); -$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/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 3f32e2597..bfbd6cfd0 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\ResultOutputProcessor; 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\TokenUsageResultHandler; 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 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.'), diff --git a/examples/vertexai/token-metadata.php b/examples/vertexai/token-metadata.php index 56ed15575..7a6abf34d 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\ResultOutputProcessor; use Symfony\AI\Platform\Bridge\VertexAi\Gemini\Model; use Symfony\AI\Platform\Bridge\VertexAi\PlatformFactory; +use Symfony\AI\Platform\Bridge\VertexAi\TokenUsageResultHandler; use Symfony\AI\Platform\Message\Message; use Symfony\AI\Platform\Message\MessageBag; @@ -20,7 +22,7 @@ $platform = PlatformFactory::create(env('GOOGLE_CLOUD_LOCATION'), env('GOOGLE_CLOUD_PROJECT'), adc_aware_http_client()); $model = new Model(Model::GEMINI_2_0_FLASH_LITE); -$agent = new Agent($platform, $model, outputProcessors: [new Symfony\AI\Platform\Bridge\VertexAi\TokenOutputProcessor()], 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/ResultOutputProcessor.php b/src/agent/src/OutputProcessor/ResultOutputProcessor.php new file mode 100644 index 000000000..c8e09cd54 --- /dev/null +++ b/src/agent/src/OutputProcessor/ResultOutputProcessor.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\ResultHandlerInterface; + +readonly class ResultOutputProcessor implements OutputProcessorInterface +{ + public function __construct( + private ResultHandlerInterface $resultProcessor, + ) { + } + + public function processOutput(Output $output): void + { + $this->resultProcessor->handleResult($output->result); + } +} diff --git a/src/ai-bundle/config/services.php b/src/ai-bundle/config/services.php index 2437ef8d4..ebc10199f 100644 --- a/src/ai-bundle/config/services.php +++ b/src/ai-bundle/config/services.php @@ -26,18 +26,18 @@ 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\TokenOutputProcessor as GeminiTokenOutputProcessor; -use Symfony\AI\Platform\Bridge\Mistral\TokenOutputProcessor as MistralTokenOutputProcessor; +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\TokenOutputProcessor as OpenAiTokenOutputProcessor; +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\TokenOutputProcessor as VertexAiTokenOutputProcessor; +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; @@ -143,15 +143,15 @@ ->tag('ai.traceable_toolbox') // token usage processors - ->set('ai.platform.token_usage_processor.anthropic', AnthropicTokenOutputProcessor::class) - ->set('ai.platform.token_usage_processor.gemini', GeminiTokenOutputProcessor::class) - ->set('ai.platform.token_usage_processor.mistral', MistralTokenOutputProcessor::class) - ->set('ai.platform.token_usage_processor.openai', OpenAiTokenOutputProcessor::class) - ->set('ai.platform.token_usage_processor.perplexity', PerplexityTokenOutputProcessor::class) - ->set('ai.platform.token_usage_processor.vertexai', VertexAiTokenOutputProcessor::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.perplexity', PerplexityTokenUsageResultHandler::class) + ->set('ai.platform.token_usage_result_handler.vertexai', VertexAiTokenUsageResultHandler::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/ai-bundle/src/AiBundle.php b/src/ai-bundle/src/AiBundle.php index c12033d6d..f424c9e76 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\ResultOutputProcessor; use Symfony\AI\Agent\OutputProcessorInterface; use Symfony\AI\Agent\Toolbox\Attribute\AsTool; use Symfony\AI\Agent\Toolbox\FaultTolerantToolbox; @@ -596,9 +597,15 @@ 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_result_handler.'.$platform)) { + $container->getDefinition('ai.platform.token_usage_result_handler.'.$platform) + ->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]); } } } diff --git a/src/ai-bundle/tests/DependencyInjection/AiBundleTest.php b/src/ai-bundle/tests/DependencyInjection/AiBundleTest.php index 9eea6717e..819e3e9a9 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_result_handler.openai'); + $outputTags = $tokenUsageDeterminer->getTag('ai.agent.token_usage_result_handler'); + + $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.platform.token_usage_processor'); $foundTag = false; foreach ($outputTags as $tag) { 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", 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/src/Bridge/Gemini/TokenOutputProcessor.php b/src/platform/src/Bridge/Gemini/TokenUsageResultHandler.php similarity index 78% rename from src/platform/src/Bridge/Gemini/TokenOutputProcessor.php rename to src/platform/src/Bridge/Gemini/TokenUsageResultHandler.php index f55a41172..9641ee8b4 100644 --- a/src/platform/src/Bridge/Gemini/TokenOutputProcessor.php +++ b/src/platform/src/Bridge/Gemini/TokenUsageResultHandler.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\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/src/Bridge/Mistral/TokenOutputProcessor.php b/src/platform/src/Bridge/Mistral/TokenUsageResultHandler.php similarity index 81% rename from src/platform/src/Bridge/Mistral/TokenOutputProcessor.php rename to src/platform/src/Bridge/Mistral/TokenUsageResultHandler.php index 44d5ad8b6..61fadb6d6 100644 --- a/src/platform/src/Bridge/Mistral/TokenOutputProcessor.php +++ b/src/platform/src/Bridge/Mistral/TokenUsageResultHandler.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\ResultHandlerInterface; +use Symfony\AI\Platform\Result\ResultInterface; use Symfony\AI\Platform\Result\StreamResult; use Symfony\Contracts\HttpClient\ResponseInterface; /** * @author Quentin Fahrner */ -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(); $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/TokenUsageResultHandler.php similarity index 80% rename from src/platform/src/Bridge/OpenAi/TokenOutputProcessor.php rename to src/platform/src/Bridge/OpenAi/TokenUsageResultHandler.php index a313efd4a..8214d5d5d 100644 --- a/src/platform/src/Bridge/OpenAi/TokenOutputProcessor.php +++ b/src/platform/src/Bridge/OpenAi/TokenUsageResultHandler.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\ResultHandlerInterface; +use Symfony\AI\Platform\Result\ResultInterface; use Symfony\AI\Platform\Result\StreamResult; use Symfony\Contracts\HttpClient\ResponseInterface; /** * @author Denis Zunke */ -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(); $remainingTokens = $rawResponse->getHeaders(false)['x-ratelimit-remaining-tokens'][0] ?? null; $tokenUsage = new TokenUsage( 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/TokenOutputProcessor.php b/src/platform/src/Bridge/Perplexity/TokenUsageResultHandler.php similarity index 76% rename from src/platform/src/Bridge/Perplexity/TokenOutputProcessor.php rename to src/platform/src/Bridge/Perplexity/TokenUsageResultHandler.php index 3a8d624fc..e5ed59e76 100644 --- a/src/platform/src/Bridge/Perplexity/TokenOutputProcessor.php +++ b/src/platform/src/Bridge/Perplexity/TokenUsageResultHandler.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\ResultHandlerInterface; +use Symfony\AI\Platform\Result\ResultInterface; use Symfony\AI\Platform\Result\StreamResult; use Symfony\Contracts\HttpClient\ResponseInterface; /** * @author Mathieu Santostefano */ -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; } @@ -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/TokenUsageResultHandler.php similarity index 85% rename from src/platform/src/Bridge/VertexAi/TokenOutputProcessor.php rename to src/platform/src/Bridge/VertexAi/TokenUsageResultHandler.php index 4d8841bfc..fb8fc4c2b 100644 --- a/src/platform/src/Bridge/VertexAi/TokenOutputProcessor.php +++ b/src/platform/src/Bridge/VertexAi/TokenUsageResultHandler.php @@ -11,10 +11,10 @@ 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\ResultHandlerInterface; +use Symfony\AI\Platform\Result\ResultInterface; use Symfony\AI\Platform\Result\StreamResult; use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface; use Symfony\Contracts\HttpClient\Exception\DecodingExceptionInterface; @@ -26,7 +26,7 @@ /** * @author Junaid Farooq */ -final class TokenOutputProcessor implements OutputProcessorInterface +final class TokenUsageResultHandler implements ResultHandlerInterface { /** * @throws TransportExceptionInterface @@ -35,15 +35,15 @@ final class TokenOutputProcessor implements OutputProcessorInterface * @throws DecodingExceptionInterface * @throws ClientExceptionInterface */ - public function processOutput(Output $output): void + public function handleResult(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/ResultHandlerInterface.php b/src/platform/src/Result/ResultHandlerInterface.php new file mode 100644 index 000000000..df561e248 --- /dev/null +++ b/src/platform/src/Result/ResultHandlerInterface.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 ResultHandlerInterface +{ + public function handleResult(ResultInterface $result): void; +} 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(), - [], - ); - } } 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..29c62d53f 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\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 TokenUsageDeterminerTest 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([ @@ -97,11 +88,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); @@ -116,7 +105,7 @@ public function testItAddsUsageTokensToMetadata() public function testItHandlesMissingUsageFields() { - $processor = new TokenOutputProcessor(); + $tokenUsageResultHandler = new TokenUsageResultHandler(); $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); + $tokenUsageResultHandler->handleResult($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..882db5437 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\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)] #[Small] -final class TokenOutputProcessorTest extends TestCase +final class TokenUsageDeterminerTest 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->createRawResponse()); - $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'); $rawResponse = $this->createRawResponse([ @@ -94,11 +85,9 @@ public function testItAddsUsageTokensToMetadata() $textResult->setRawResult($rawResponse); - $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); @@ -111,7 +100,7 @@ public function testItAddsUsageTokensToMetadata() public function testItHandlesMissingUsageFields() { - $processor = new TokenOutputProcessor(); + $tokenUsageResultHandler = new TokenUsageResultHandler(); $textResult = new TextResult('test'); $rawResponse = $this->createRawResponse([ @@ -123,11 +112,9 @@ public function testItHandlesMissingUsageFields() $textResult->setRawResult($rawResponse); - $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 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..4959dd1ba 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\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 TokenUsageDeterminerTest 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([ @@ -100,11 +91,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); @@ -118,7 +107,7 @@ public function testItAddsUsageTokensToMetadata() public function testItHandlesMissingUsageFields() { - $processor = new TokenOutputProcessor(); + $tokenUsageResultHandler = new TokenUsageResultHandler(); $textResult = new TextResult('test'); $rawResult = $this->createRawResult([ @@ -130,11 +119,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); @@ -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..615d5dd42 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\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; @@ -30,42 +26,39 @@ /** * @author Mathieu Santostefano */ -#[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 TokenUsageDeterminerTest 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 testItAddsUsageTokensToMetadata() { - $processor = new TokenOutputProcessor(); + $tokenUsageResultHandler = new TokenUsageResultHandler(); $textResult = new TextResult('test'); $rawResult = $this->createRawResult([ @@ -79,11 +72,9 @@ public function testItAddsUsageTokensToMetadata() $textResult->setRawResult($rawResult); - $output = $this->createOutput($textResult); + $tokenUsageResultHandler->handleResult($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(); + $tokenUsageResultHandler = new TokenUsageResultHandler(); $textResult = new TextResult('test'); $rawResult = $this->createRawResult([ @@ -107,11 +98,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); @@ -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 74% rename from src/platform/tests/Bridge/VertexAi/TokenOutputProcessorTest.php rename to src/platform/tests/Bridge/VertexAi/TokenUsageDeterminerTest.php index d6de42fd5..d726f67fe 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\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)] #[Small] -final class TokenOutputProcessorTest extends TestCase +final class TokenUsageDeterminerTest extends TestCase { public function testItDoesNothingWithoutRawResponse() { - $processor = new TokenOutputProcessor(); + $tokenUsageResultHandler = new TokenUsageResultHandler(); $textResult = new TextResult('test'); - $output = $this->createOutput($textResult); - $processor->processOutput($output); + $tokenUsageResultHandler->handleResult($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); + $tokenUsageResultHandler = new TokenUsageResultHandler(); // Act - $processor->processOutput($output); + $tokenUsageResultHandler->handleResult($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); + $tokenUsageResultHandler = new TokenUsageResultHandler(); // Act - $processor->processOutput($output); + $tokenUsageResultHandler->handleResult($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); + $tokenUsageResultHandler = new TokenUsageResultHandler(); // Act - $processor->processOutput($output); + $tokenUsageResultHandler->handleResult($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(); + $tokenUsageResultHandler = new TokenUsageResultHandler(); $chunks = [ ['content' => 'chunk1'], ['content' => 'chunk2', 'usageMetadata' => [ @@ -151,11 +142,9 @@ public function testItHandlesStreamResults() } })()); - $output = $this->createOutput($streamResult); + $tokenUsageResultHandler->handleResult($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(), - [], - ); - } }