Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 0 additions & 7 deletions deptrac.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -306,11 +306,4 @@ deptrac:
- PlatformComponent
# Baseline of known violations to be skipped for now
skip_violations:
Symfony\AI\Platform\Bridge\Anthropic\TokenOutputProcessor: [Symfony\AI\Agent\OutputProcessorInterface, Symfony\AI\Agent\Output]
Symfony\AI\Platform\Bridge\DeepSeek\TokenOutputProcessor: [Symfony\AI\Agent\OutputProcessorInterface, Symfony\AI\Agent\Output]
Symfony\AI\Platform\Bridge\Gemini\TokenOutputProcessor: [Symfony\AI\Agent\OutputProcessorInterface, Symfony\AI\Agent\Output]
Symfony\AI\Platform\Bridge\Mistral\TokenOutputProcessor: [Symfony\AI\Agent\OutputProcessorInterface, Symfony\AI\Agent\Output]
Symfony\AI\Platform\Bridge\OpenAi\TokenOutputProcessor: [Symfony\AI\Agent\OutputProcessorInterface, Symfony\AI\Agent\Output]
Symfony\AI\Platform\Bridge\Perplexity\SearchResultProcessor: [Symfony\AI\Agent\OutputProcessorInterface, Symfony\AI\Agent\Output]
Symfony\AI\Platform\Bridge\Perplexity\TokenOutputProcessor: [Symfony\AI\Agent\OutputProcessorInterface, Symfony\AI\Agent\Output]
Symfony\AI\Platform\Bridge\VertexAi\TokenOutputProcessor: [Symfony\AI\Agent\OutputProcessorInterface, Symfony\AI\Agent\Output]
18 changes: 2 additions & 16 deletions docs/bundles/ai-bundle.rst
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,6 @@ Advanced Example with Multiple Agents
agent:
rag:
platform: 'ai.platform.azure.gpt_deployment'
track_token_usage: true # Enable tracking of token usage for the agent, default is true
model: 'gpt-4o-mini'
memory: 'You have access to conversation history and user preferences' # Optional: static memory content
prompt: # The system prompt configuration
Expand Down Expand Up @@ -899,8 +898,8 @@ Token Usage Tracking
Token usage tracking is a feature provided by some of the Platform's bridges, for monitoring and analyzing the
consumption of tokens by your agents. This feature is particularly useful for understanding costs and performance.

When enabled, the agent will automatically track token usage information and add it
to the result metadata. The tracked information includes:
In case a Platform bridge supports token usage tracking, the Platform will automatically track token usage information
and add it to the result metadata. The tracked information includes:

* **Prompt tokens**: Number of tokens used in the input/prompt
* **Completion tokens**: Number of tokens generated in the response
Expand Down Expand Up @@ -932,19 +931,6 @@ The token usage information can be accessed from the result metadata::
}
}

Disable Tracking
~~~~~~~~~~~~~~~~

To disable token usage tracking for an agent, set the ``track_token_usage`` option to ``false``:

.. code-block:: yaml

ai:
agent:
my_agent:
model: 'gpt-4o-mini'
track_token_usage: false

Vectorizers
-----------

Expand Down
3 changes: 1 addition & 2 deletions examples/anthropic/token-metadata.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,14 @@

use Symfony\AI\Agent\Agent;
use Symfony\AI\Platform\Bridge\Anthropic\PlatformFactory;
use Symfony\AI\Platform\Bridge\Anthropic\TokenOutputProcessor;
use Symfony\AI\Platform\Message\Message;
use Symfony\AI\Platform\Message\MessageBag;

require_once dirname(__DIR__).'/bootstrap.php';

$platform = PlatformFactory::create(env('ANTHROPIC_API_KEY'), http_client());

$agent = new Agent($platform, 'claude-sonnet-4-5-20250929', outputProcessors: [new TokenOutputProcessor()]);
$agent = new Agent($platform, 'claude-sonnet-4-5-20250929');
$messages = new MessageBag(
Message::forSystem('You are a pirate and you write funny.'),
Message::ofUser('What is the Symfony framework?'),
Expand Down
2 changes: 1 addition & 1 deletion examples/bootstrap.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@
use Symfony\AI\Agent\Exception\ExceptionInterface as AgentException;
use Symfony\AI\Platform\Exception\ExceptionInterface as PlatformException;
use Symfony\AI\Platform\Metadata\Metadata;
use Symfony\AI\Platform\Metadata\TokenUsage;
use Symfony\AI\Platform\Result\DeferredResult;
use Symfony\AI\Platform\TokenUsage\TokenUsage;
use Symfony\AI\Store\Exception\ExceptionInterface as StoreException;
use Symfony\Component\Console\Helper\Table;
use Symfony\Component\Console\Logger\ConsoleLogger;
Expand Down
3 changes: 1 addition & 2 deletions examples/deepseek/token-metadata.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,14 @@

use Symfony\AI\Agent\Agent;
use Symfony\AI\Platform\Bridge\DeepSeek\PlatformFactory;
use Symfony\AI\Platform\Bridge\DeepSeek\TokenOutputProcessor;
use Symfony\AI\Platform\Message\Message;
use Symfony\AI\Platform\Message\MessageBag;

require_once dirname(__DIR__).'/bootstrap.php';

$platform = PlatformFactory::create(env('DEEPSEEK_API_KEY'), http_client());

$agent = new Agent($platform, 'deepseek-chat', outputProcessors: [new TokenOutputProcessor()]);
$agent = new Agent($platform, 'deepseek-chat');
$messages = new MessageBag(
Message::forSystem('You are a pirate and you write funny.'),
Message::ofUser('What is the Symfony framework?'),
Expand Down
3 changes: 1 addition & 2 deletions examples/gemini/token-metadata.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,14 @@

use Symfony\AI\Agent\Agent;
use Symfony\AI\Platform\Bridge\Gemini\PlatformFactory;
use Symfony\AI\Platform\Bridge\Gemini\TokenOutputProcessor;
use Symfony\AI\Platform\Message\Message;
use Symfony\AI\Platform\Message\MessageBag;

require_once dirname(__DIR__).'/bootstrap.php';

$platform = PlatformFactory::create(env('GEMINI_API_KEY'), http_client());

$agent = new Agent($platform, 'gemini-2.0-flash', outputProcessors: [new TokenOutputProcessor()]);
$agent = new Agent($platform, 'gemini-2.0-flash');
$messages = new MessageBag(
Message::forSystem('You are a pirate and you write funny.'),
Message::ofUser('What is the Symfony framework?'),
Expand Down
3 changes: 1 addition & 2 deletions examples/mistral/token-metadata.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,14 @@

use Symfony\AI\Agent\Agent;
use Symfony\AI\Platform\Bridge\Mistral\PlatformFactory;
use Symfony\AI\Platform\Bridge\Mistral\TokenOutputProcessor;
use Symfony\AI\Platform\Message\Message;
use Symfony\AI\Platform\Message\MessageBag;

require_once dirname(__DIR__).'/bootstrap.php';

$platform = PlatformFactory::create(env('MISTRAL_API_KEY'), http_client());

$agent = new Agent($platform, 'mistral-large-latest', outputProcessors: [new TokenOutputProcessor()]);
$agent = new Agent($platform, 'mistral-large-latest');

$messages = new MessageBag(
Message::forSystem('You are a pirate and you write funny.'),
Expand Down
3 changes: 1 addition & 2 deletions examples/openai/token-metadata.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,14 @@

use Symfony\AI\Agent\Agent;
use Symfony\AI\Platform\Bridge\OpenAi\PlatformFactory;
use Symfony\AI\Platform\Bridge\OpenAi\TokenOutputProcessor;
use Symfony\AI\Platform\Message\Message;
use Symfony\AI\Platform\Message\MessageBag;

require_once dirname(__DIR__).'/bootstrap.php';

$platform = PlatformFactory::create(env('OPENAI_API_KEY'), http_client());

$agent = new Agent($platform, 'gpt-4o-mini', outputProcessors: [new TokenOutputProcessor()]);
$agent = new Agent($platform, 'gpt-4o-mini');
$messages = new MessageBag(
Message::forSystem('You are a pirate and you write funny.'),
Message::ofUser('What is the Symfony framework?'),
Expand Down
3 changes: 1 addition & 2 deletions examples/perplexity/token-metadata.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,13 @@

use Symfony\AI\Agent\Agent;
use Symfony\AI\Platform\Bridge\Perplexity\PlatformFactory;
use Symfony\AI\Platform\Bridge\Perplexity\TokenOutputProcessor;
use Symfony\AI\Platform\Message\Message;
use Symfony\AI\Platform\Message\MessageBag;

require_once dirname(__DIR__).'/bootstrap.php';

$platform = PlatformFactory::create(env('PERPLEXITY_API_KEY'), http_client());
$agent = new Agent($platform, 'sonar', outputProcessors: [new TokenOutputProcessor()]);
$agent = new Agent($platform, 'sonar');

$messages = new MessageBag(
Message::forSystem('You are a pirate and you write funny.'),
Expand Down
3 changes: 1 addition & 2 deletions examples/vertexai/token-metadata.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,14 @@

use Symfony\AI\Agent\Agent;
use Symfony\AI\Platform\Bridge\VertexAi\PlatformFactory;
use Symfony\AI\Platform\Bridge\VertexAi\TokenOutputProcessor;
use Symfony\AI\Platform\Message\Message;
use Symfony\AI\Platform\Message\MessageBag;

require_once __DIR__.'/bootstrap.php';

$platform = PlatformFactory::create(env('GOOGLE_CLOUD_LOCATION'), env('GOOGLE_CLOUD_PROJECT'), adc_aware_http_client());

$agent = new Agent($platform, 'gemini-2.0-flash-lite', outputProcessors: [new TokenOutputProcessor()]);
$agent = new Agent($platform, 'gemini-2.0-flash-lite');
$messages = new MessageBag(
Message::forSystem('You are an expert assistant in animal study.'),
Message::ofUser('What does a cat usually eat?'),
Expand Down
4 changes: 0 additions & 4 deletions src/ai-bundle/config/options.php
Original file line number Diff line number Diff line change
Expand Up @@ -314,10 +314,6 @@
->info('Service name of platform')
->defaultValue(PlatformInterface::class)
->end()
->booleanNode('track_token_usage')
->info('Enable tracking of token usage for the agent')
->defaultTrue()
->end()
->variableNode('model')
->validate()
->ifTrue(function ($v) {
Expand Down
14 changes: 0 additions & 14 deletions src/ai-bundle/config/services.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@
use Symfony\AI\Platform\Bridge\Albert\ModelCatalog as AlbertModelCatalog;
use Symfony\AI\Platform\Bridge\Anthropic\Contract\AnthropicContract;
use Symfony\AI\Platform\Bridge\Anthropic\ModelCatalog as AnthropicModelCatalog;
use Symfony\AI\Platform\Bridge\Anthropic\TokenOutputProcessor as AnthropicTokenOutputProcessor;
use Symfony\AI\Platform\Bridge\Azure\OpenAi\ModelCatalog as AzureOpenAiModelCatalog;
use Symfony\AI\Platform\Bridge\Cartesia\ModelCatalog as CartesiaModelCatalog;
use Symfony\AI\Platform\Bridge\Cerebras\ModelCatalog as CerebrasModelCatalog;
Expand All @@ -38,28 +37,23 @@
use Symfony\AI\Platform\Bridge\ElevenLabs\ModelCatalog as ElevenLabsModelCatalog;
use Symfony\AI\Platform\Bridge\Gemini\Contract\GeminiContract;
use Symfony\AI\Platform\Bridge\Gemini\ModelCatalog as GeminiModelCatalog;
use Symfony\AI\Platform\Bridge\Gemini\TokenOutputProcessor as GeminiTokenOutputProcessor;
use Symfony\AI\Platform\Bridge\HuggingFace\Contract\HuggingFaceContract;
use Symfony\AI\Platform\Bridge\HuggingFace\ModelCatalog as HuggingFaceModelCatalog;
use Symfony\AI\Platform\Bridge\LmStudio\ModelCatalog as LmStudioModelCatalog;
use Symfony\AI\Platform\Bridge\Meta\ModelCatalog as MetaModelCatalog;
use Symfony\AI\Platform\Bridge\Mistral\ModelCatalog as MistralModelCatalog;
use Symfony\AI\Platform\Bridge\Mistral\TokenOutputProcessor as MistralTokenOutputProcessor;
use Symfony\AI\Platform\Bridge\Ollama\Contract\OllamaContract;
use Symfony\AI\Platform\Bridge\Ollama\ModelCatalog as OllamaModelCatalog;
use Symfony\AI\Platform\Bridge\OpenAi\Contract\OpenAiContract;
use Symfony\AI\Platform\Bridge\OpenAi\ModelCatalog as OpenAiModelCatalog;
use Symfony\AI\Platform\Bridge\OpenAi\TokenOutputProcessor as OpenAiTokenOutputProcessor;
use Symfony\AI\Platform\Bridge\OpenRouter\ModelCatalog as OpenRouterModelCatalog;
use Symfony\AI\Platform\Bridge\Perplexity\Contract\PerplexityContract;
use Symfony\AI\Platform\Bridge\Perplexity\ModelCatalog as PerplexityModelCatalog;
use Symfony\AI\Platform\Bridge\Perplexity\SearchResultProcessor as PerplexitySearchResultProcessor;
use Symfony\AI\Platform\Bridge\Perplexity\TokenOutputProcessor as PerplexityTokenOutputProcessor;
use Symfony\AI\Platform\Bridge\Replicate\ModelCatalog as ReplicateModelCatalog;
use Symfony\AI\Platform\Bridge\Scaleway\ModelCatalog as ScalewayModelCatalog;
use Symfony\AI\Platform\Bridge\VertexAi\Contract\GeminiContract as VertexAiGeminiContract;
use Symfony\AI\Platform\Bridge\VertexAi\ModelCatalog as VertexAiModelCatalog;
use Symfony\AI\Platform\Bridge\VertexAi\TokenOutputProcessor as VertexAiTokenOutputProcessor;
use Symfony\AI\Platform\Bridge\Voyage\ModelCatalog as VoyageModelCatalog;
use Symfony\AI\Platform\Contract;
use Symfony\AI\Platform\Contract\JsonSchema\DescriptionParser;
Expand Down Expand Up @@ -191,14 +185,6 @@
])
->tag('data_collector')

// 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)

// search result processors
->set('ai.platform.search_result_processor.perplexity', PerplexitySearchResultProcessor::class)

Expand Down
22 changes: 0 additions & 22 deletions src/ai-bundle/src/AiBundle.php
Original file line number Diff line number Diff line change
Expand Up @@ -910,28 +910,6 @@ private function processAgentConfig(string $name, array $config, ContainerBuilde
}
}

// TOKEN USAGE TRACKING
if ($config['track_token_usage'] ?? true) {
$platformServiceId = $config['platform'];

if ($container->hasAlias($platformServiceId)) {
$platformServiceId = (string) $container->getAlias($platformServiceId);
}

if (str_starts_with($platformServiceId, 'ai.platform.')) {
$platform = u($platformServiceId)->after('ai.platform.')->toString();

if (str_contains($platform, 'azure')) {
$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]);
}
}
}

// SYSTEM PROMPT
if (isset($config['prompt'])) {
$includeTools = isset($config['prompt']['include_tools']) && $config['prompt']['include_tools'];
Expand Down
38 changes: 0 additions & 38 deletions src/ai-bundle/tests/DependencyInjection/AiBundleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4012,43 +4012,6 @@ public function testElevenLabsPlatformWithApiCatalogCanBeRegistered()
$this->assertSame([['interface' => ModelCatalogInterface::class]], $modelCatalogDefinition->getTag('proxy'));
}

#[TestDox('Token usage processor tags use the correct agent ID')]
public function testTokenUsageProcessorTags()
{
$container = $this->buildContainer([
'ai' => [
'platform' => [
'openai' => [
'api_key' => 'sk-test_key',
],
],
'agent' => [
'tracked_agent' => [
'platform' => 'ai.platform.openai',
'model' => 'gpt-4',
'track_token_usage' => true,
],
],
],
]);

$agentId = 'ai.agent.tracked_agent';

// Token usage processor must exist for OpenAI platform
$tokenUsageProcessor = $container->getDefinition('ai.platform.token_usage_processor.openai');
$outputTags = $tokenUsageProcessor->getTag('ai.agent.output_processor');

$foundTag = false;
foreach ($outputTags as $tag) {
if (($tag['agent'] ?? '') === $agentId) {
$foundTag = true;
break;
}
}

$this->assertTrue($foundTag, 'Token usage processor should have output tag with full agent ID');
}

public function testOpenAiPlatformWithDefaultRegion()
{
$container = $this->buildContainer([
Expand Down Expand Up @@ -7082,7 +7045,6 @@ private function getFullConfig(): array
'nested' => ['options' => ['work' => 'too']],
],
],
'track_token_usage' => true,
'prompt' => [
'text' => 'You are a helpful assistant.',
'include_tools' => true,
Expand Down
5 changes: 5 additions & 0 deletions src/platform/src/Bridge/Anthropic/ResultConverter.php
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,11 @@ public function convert(RawHttpResult|RawResultInterface $result, array $options
return new TextResult($data['content'][0]['text']);
}

public function getTokenUsageExtractor(): TokenUsageExtractor
{
return new TokenUsageExtractor();
}

private function convertStream(RawResultInterface $result): \Generator
{
foreach ($result->getDataStream() as $data) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,33 +11,23 @@

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\StreamResult;
use Symfony\Contracts\HttpClient\ResponseInterface;
use Symfony\AI\Platform\Result\RawResultInterface;
use Symfony\AI\Platform\TokenUsage\TokenUsage;
use Symfony\AI\Platform\TokenUsage\TokenUsageExtractorInterface;

final class TokenOutputProcessor implements OutputProcessorInterface
final class TokenUsageExtractor implements TokenUsageExtractorInterface
{
public function processOutput(Output $output): void
public function extract(RawResultInterface $rawResult, array $options = []): ?TokenUsage
{
if ($output->getResult() instanceof StreamResult) {
if ($options['stream'] ?? false) {
// Streams have to be handled manually as the tokens are part of the streamed chunks
return;
return null;
}

$rawResponse = $output->getResult()->getRawResult()?->getObject();
if (!$rawResponse instanceof ResponseInterface) {
return;
}

$metadata = $output->getResult()->getMetadata();
$content = $rawResponse->toArray(false);
$content = $rawResult->getData();

if (!\array_key_exists('usage', $content)) {
$metadata->add('token_usage', new TokenUsage());

return;
return null;
}

$usage = $content['usage'];
Expand All @@ -46,11 +36,11 @@ public function processOutput(Output $output): void
$cachedTokens = ($usage['cache_creation_input_tokens'] ?? 0) + ($usage['cache_read_input_tokens'] ?? 0);
}

$metadata->add('token_usage', new TokenUsage(
return new TokenUsage(
promptTokens: $usage['input_tokens'] ?? null,
completionTokens: $usage['output_tokens'] ?? null,
toolTokens: $usage['server_tool_use']['web_search_requests'] ?? null,
cachedTokens: $cachedTokens,
));
);
}
}
Loading