From 05c6c6de043dc3d051b9aedbf9da5fcab183b23d Mon Sep 17 00:00:00 2001 From: Christian Dangl Date: Fri, 27 Dec 2024 12:30:13 +0100 Subject: [PATCH] add output of estimated price of data --- CHANGELOG.md | 1 + src/Command/MediaGenerateCommand.php | 5 ++ src/Command/ProductGenerateCommand.php | 7 +- src/Service/Generator/ProductGenerator.php | 2 +- src/Service/OpenAI/Client.php | 79 +++++++++------------- src/Service/OpenAI/OpenAIUsageTracker.php | 73 ++++++++++++++++++++ src/Service/OpenAI/pricing.json | 18 +++++ src/Traits/CommandOutputTrait.php | 22 ++++++ 8 files changed, 157 insertions(+), 50 deletions(-) create mode 100644 src/Service/OpenAI/OpenAIUsageTracker.php create mode 100644 src/Service/OpenAI/pricing.json create mode 100644 src/Traits/CommandOutputTrait.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 996447e..4da8b88 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ using the [Keep a CHANGELOG](https://keepachangelog.com/) principles. - Add brand new option to generate **product variants**. Configure what property group to use, and AI will automatically generate all variants, if appropriate for the product. - Add 2 files in the cache directory for the generated prompt and response of product generation requests. - Add new product image styles. Open the plugin configuration an select what styles to use for the product images. +- Add new outout of **estimated price** for the generated data. ### Changed diff --git a/src/Command/MediaGenerateCommand.php b/src/Command/MediaGenerateCommand.php index 87327d5..9b81f46 100644 --- a/src/Command/MediaGenerateCommand.php +++ b/src/Command/MediaGenerateCommand.php @@ -5,6 +5,7 @@ use AIDemoData\Service\Config\ConfigService; use AIDemoData\Service\Generator\MediaGenerator; use AIDemoData\Service\Generator\MediaGeneratorInterface; +use AIDemoData\Traits\CommandOutputTrait; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; @@ -13,6 +14,8 @@ class MediaGenerateCommand extends Command implements MediaGeneratorInterface { + use CommandOutputTrait; + public static $defaultName = 'ai-demodata:generate:media'; /** @@ -105,6 +108,8 @@ protected function execute(InputInterface $input, OutputInterface $output): int $this->mediaGenerator->generate($keyWords, $size, $count); + $this->showOpenAIUsageData($output); + if ($this->errorCount <= 0) { $this->io->success('Generated ' . $this->generatedCount . ' images for keywords in CMS Media folder'); } else { diff --git a/src/Command/ProductGenerateCommand.php b/src/Command/ProductGenerateCommand.php index 3909c1f..950abe9 100644 --- a/src/Command/ProductGenerateCommand.php +++ b/src/Command/ProductGenerateCommand.php @@ -6,6 +6,7 @@ use AIDemoData\Service\Config\ConfigService; use AIDemoData\Service\Generator\ProductGenerator; use AIDemoData\Service\Generator\ProductGeneratorInterface; +use AIDemoData\Traits\CommandOutputTrait; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Helper\Table; use Symfony\Component\Console\Input\InputInterface; @@ -15,6 +16,8 @@ class ProductGenerateCommand extends Command implements ProductGeneratorInterface { + use CommandOutputTrait; + public static $defaultName = 'ai-demodata:generate:products'; /** @@ -150,7 +153,6 @@ protected function execute(InputInterface $input, OutputInterface $output): int $rows[] = ['Image Size', $imgSize]; - $table = new Table($output); $table->setStyle('default'); $table->setHeaders(['Configuration', 'Value']); @@ -178,6 +180,9 @@ protected function execute(InputInterface $input, OutputInterface $output): int $imageStyles ); + + $this->showOpenAIUsageData($output); + if ($this->errorCount <= 0) { $this->io->success('Generated ' . $this->generatedCount . ' products for keywords'); } else { diff --git a/src/Service/Generator/ProductGenerator.php b/src/Service/Generator/ProductGenerator.php index 1d52aff..2ed17bd 100644 --- a/src/Service/Generator/ProductGenerator.php +++ b/src/Service/Generator/ProductGenerator.php @@ -148,7 +148,7 @@ public function generate(string $keywords, int $maxCount, string $category, stri $prompt .= 'Every resulting line should be in the order and sort provided below:' . PHP_EOL; $prompt .= PHP_EOL; $prompt .= '- product count.' . PHP_EOL; - $prompt .= '- product number code. should be 24 unique random alphanumeric (always unique, maybe consider datetime now).' . PHP_EOL; + $prompt .= '- product number code. should be 24 unique random alphanumeric' . PHP_EOL; $prompt .= '- name of the product.' . PHP_EOL; $prompt .= '- description (about ' . $descriptionLength . ' characters).' . PHP_EOL; $prompt .= '- price value (no currency just number).' . PHP_EOL; diff --git a/src/Service/OpenAI/Client.php b/src/Service/OpenAI/Client.php index 5abdbcf..981f4fd 100644 --- a/src/Service/OpenAI/Client.php +++ b/src/Service/OpenAI/Client.php @@ -40,9 +40,10 @@ public function generateText(string $prompt): Choice $this->openAi = new OpenAi($this->apiKey); + $model = 'gpt-3.5-turbo-instruct'; $params = [ - 'model' => "gpt-3.5-turbo-instruct", + 'model' => $model, 'prompt' => $prompt, 'temperature' => 0.3, 'max_tokens' => 1000, @@ -65,6 +66,8 @@ public function generateText(string $prompt): Choice throw new \Exception($msg); } + $this->trackTextCosts($prompt, $json, $model); + if (!isset($json['choices'])) { throw new \Exception('No choices found in OpenAI response.'); } @@ -100,12 +103,15 @@ public function generateImage(string $prompt, string $size): string $this->openAi = new OpenAi($this->apiKey); + $model = "dall-e-3"; + $complete = $this->openAi->image([ - "model" => "dall-e-3", + "model" => $model, "prompt" => $prompt, "n" => 1, "size" => $size, "style" => "natural", + "quality" => "standard", "response_format" => "url", ]); @@ -120,66 +126,43 @@ public function generateImage(string $prompt, string $size): string throw new \Exception($msg); } + if ($size === "1024x1024") { + OpenAIUsageTracker::getInstance()->addRequest($prompt, 0.040); + } else { + OpenAIUsageTracker::getInstance()->addRequest($prompt, 0.080); + } + return (string)$json['data'][0]['url']; } + /** * @param string $prompt - * @throws \JsonException - * @return Choice + * @param array $json + * @param string $model + * @return void */ - public function askChatGPT(string $prompt): Choice + private function trackTextCosts(string $prompt, array $json, string $model): void { - if (empty($this->apiKey)) { - throw new \Exception('No API Key found in plugin configuration. Please provide your key'); + if (!isset($json['usage'])) { + OpenAIUsageTracker::getInstance()->addMissingPrices($prompt, $model); + return; } - $this->openAi = new OpenAi($this->apiKey); + $pricingData = json_decode((string)file_get_contents(__DIR__ . '/pricing.json'), true); - $params = [ - 'model' => "gpt-3.5-turbo-instruct", - 'messages' => [ - ['role' => 'user', 'content' => $prompt], - ], - 'temperature' => 0.8, - 'max_tokens' => 400, - 'top_p' => 1.0, - 'frequency_penalty' => 0.0, - 'presence_penalty' => 0.0, - ]; - - - $complete = (string)$this->openAi->chat($params); - - $json = json_decode($complete, true, 512, JSON_THROW_ON_ERROR); - - if (!is_array($json)) { - return new Choice(''); - } - - if (isset($json['error'])) { - $msg = 'OpenAI Error: ' . $json['error']['message'] . '[' . $json['error']['code'] . ']'; - throw new \Exception($msg); - } - - if (!isset($json['choices'])) { - throw new \Exception('No choices found in OpenAI response.'); + if (!isset($pricingData[$model])) { + OpenAIUsageTracker::getInstance()->addMissingPrices($prompt, $model); } - $choices = $json['choices']; - - if (!is_array($choices) || count($choices) <= 0) { - return new Choice(''); - } + $costsInputToken = $pricingData[$model]['input'] ?? 0; + $costsOutputToken = $pricingData[$model]['output'] ?? 0; - if (!isset($choices[0]['message']['content'])) { - return new Choice(''); - } + $totalInputTokens = $json['usage']['prompt_tokens']; + $totalOutputTokens = $json['usage']['completion_tokens']; - $choiceData = $choices[0]; + $costUSD = ($totalInputTokens * $costsInputToken) + ($totalOutputTokens * $costsOutputToken); - $text = trim($choiceData['message']['content']); - - return new Choice($text); + OpenAIUsageTracker::getInstance()->addRequest($prompt, $costUSD); } } diff --git a/src/Service/OpenAI/OpenAIUsageTracker.php b/src/Service/OpenAI/OpenAIUsageTracker.php new file mode 100644 index 0000000..597c490 --- /dev/null +++ b/src/Service/OpenAI/OpenAIUsageTracker.php @@ -0,0 +1,73 @@ + + */ + private array $requests = []; + + /** + * @var array + */ + private array $missingModelPrices = []; + + + public static function getInstance(): OpenAIUsageTracker + { + if (!self::$instance instanceof OpenAIUsageTracker) { + self::$instance = new self(); + } + + return self::$instance; + } + + + public function addRequest(string $prompt, float $costUSD): void + { + $this->requests[] = [ + 'prompt' => $prompt, + 'costUSD' => $costUSD, + ]; + } + + public function addMissingPrices(string $prompt, string $model): void + { + $this->requests[] = [ + 'prompt' => $prompt, + 'costUSD' => 0, + ]; + + $this->missingModelPrices[] = $model; + } + + public function getRequestCount(): int + { + return count($this->requests); + } + + /** + * Gets the total costs rounded to 2 decimal places + */ + public function getTotalCostsUSD(): float + { + $totalCostsUSD = 0; + foreach ($this->requests as $cost) { + $totalCostsUSD += $cost['costUSD']; + } + + return $totalCostsUSD; + } + + /** + * @return mixed[] + */ + public function getMissingModelPrices(): array + { + return $this->missingModelPrices; + } +} diff --git a/src/Service/OpenAI/pricing.json b/src/Service/OpenAI/pricing.json new file mode 100644 index 0000000..3fa52f3 --- /dev/null +++ b/src/Service/OpenAI/pricing.json @@ -0,0 +1,18 @@ +{ + "gpt-4": { + "input": 0.00003, + "output": 0.00006 + }, + "gpt-4-turbo": { + "input": 0.00001, + "output": 0.00003 + }, + "gpt-3.5-turbo": { + "input": 0.0000015, + "output": 0.000002 + }, + "gpt-3.5-turbo-instruct": { + "input": 0.0000015, + "output": 0.000002 + } +} \ No newline at end of file diff --git a/src/Traits/CommandOutputTrait.php b/src/Traits/CommandOutputTrait.php new file mode 100644 index 0000000..67ccb8d --- /dev/null +++ b/src/Traits/CommandOutputTrait.php @@ -0,0 +1,22 @@ +writeln("\n=== OpenAI Usage Summary ====================="); + $output->writeln("Total Requests: " . $tracker->getRequestCount()); + $output->writeln("Estimated Costs: " . $tracker->getTotalCostsUSD() . " USD (approx.)"); + if (count($tracker->getMissingModelPrices()) > 0) { + $output->writeln("Missing Model Prices: " . implode(', ', $tracker->getMissingModelPrices())); + } + $output->writeln("==============================================\n"); + } +}