Issues (34)

src/Services/CostCalculator.php (1 issue)

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Squareetlabs\LaravelToon\Services;
6
7
class CostCalculator
8
{
9
    public function __construct(
10
        private readonly array $models = [],
11
        private readonly ToonService $toon = new ToonService(),
12
        private readonly TokenAnalyzer $tokenAnalyzer = new TokenAnalyzer(),
13
    ) {}
14
15
    public function estimateCost(string $model, mixed $data, string $role = 'input'): array
16
    {
17
        $tokens = $this->tokenAnalyzer->estimateToon($data);
18
        $costPerMillionTokens = $this->getPricePerMillionTokens($model, $role);
19
20
        if (null === $costPerMillionTokens) {
21
            return [
22
                'success' => false,
23
                'error' => "Modelo '{$model}' no configurado o rol '{$role}' no válido",
24
            ];
25
        }
26
27
        $cost = ($tokens / 1_000_000) * $costPerMillionTokens;
0 ignored issues
show
A parse error occurred: Syntax error, unexpected T_STRING on line 27 at column 28
Loading history...
28
29
        return [
30
            'success' => true,
31
            'model' => $model,
32
            'role' => $role,
33
            'tokens' => $tokens,
34
            'cost_per_million' => $costPerMillionTokens,
35
            'cost' => round($cost, 6),
36
            'cost_formatted' => '$'.number_format($cost, 4),
37
        ];
38
    }
39
40
    public function compareModels(mixed $data, string $role = 'input'): array
41
    {
42
        $tokens = $this->tokenAnalyzer->estimateToon($data);
43
        $results = [];
44
45
        foreach ($this->models as $model => $prices) {
46
            if (isset($prices[$role])) {
47
                $costPerMillionTokens = $prices[$role];
48
                $cost = ($tokens / 1_000_000) * $costPerMillionTokens;
49
50
                $results[$model] = [
51
                    'tokens' => $tokens,
52
                    'cost_per_million' => $costPerMillionTokens,
53
                    'cost' => round($cost, 6),
54
                    'cost_formatted' => '$'.number_format($cost, 4),
55
                ];
56
            }
57
        }
58
59
        // Sort by cost (lowest first)
60
        uasort($results, fn ($a, $b) => $a['cost'] <=> $b['cost']);
61
62
        return [
63
            'tokens' => $tokens,
64
            'role' => $role,
65
            'models' => $results,
66
            'cheapest' => array_key_first($results) ?? null,
67
            'most_expensive' => array_key_last($results) ?? null,
68
        ];
69
    }
70
71
    public function estimateWithJsonComparison(string $model, mixed $data, string $role = 'input'): array
72
    {
73
        $jsonTokens = $this->tokenAnalyzer->estimateJson($data);
74
        $toonTokens = $this->tokenAnalyzer->estimateToon($data);
75
76
        $costPerMillionTokens = $this->getPricePerMillionTokens($model, $role);
77
78
        if (null === $costPerMillionTokens) {
79
            return [
80
                'success' => false,
81
                'error' => "Modelo '{$model}' no configurado o rol '{$role}' no válido",
82
            ];
83
        }
84
85
        $jsonCost = ($jsonTokens / 1_000_000) * $costPerMillionTokens;
86
        $toonCost = ($toonTokens / 1_000_000) * $costPerMillionTokens;
87
        $savings = $jsonCost - $toonCost;
88
        $savingsPercent = $jsonCost > 0 ? (($savings / $jsonCost) * 100) : 0;
89
90
        return [
91
            'success' => true,
92
            'model' => $model,
93
            'role' => $role,
94
            'json' => [
95
                'tokens' => $jsonTokens,
96
                'cost' => round($jsonCost, 6),
97
                'cost_formatted' => '$'.number_format($jsonCost, 4),
98
            ],
99
            'toon' => [
100
                'tokens' => $toonTokens,
101
                'cost' => round($toonCost, 6),
102
                'cost_formatted' => '$'.number_format($toonCost, 4),
103
            ],
104
            'savings' => [
105
                'tokens' => $jsonTokens - $toonTokens,
106
                'cost' => round($savings, 6),
107
                'cost_formatted' => '$'.number_format($savings, 4),
108
                'percent' => round($savingsPercent, 2),
109
            ],
110
        ];
111
    }
112
113
    public function estimateBatchCost(string $model, array $dataItems, string $role = 'input'): array
114
    {
115
        $totalTokens = 0;
116
        $costs = [];
117
118
        foreach ($dataItems as $index => $item) {
119
            $tokens = $this->tokenAnalyzer->estimateToon($item);
120
            $totalTokens += $tokens;
121
            $costPerMillionTokens = $this->getPricePerMillionTokens($model, $role);
122
123
            if (null === $costPerMillionTokens) {
124
                continue;
125
            }
126
127
            $cost = ($tokens / 1_000_000) * $costPerMillionTokens;
128
            $costs[$index] = [
129
                'tokens' => $tokens,
130
                'cost' => round($cost, 6),
131
            ];
132
        }
133
134
        $costPerMillionTokens = $this->getPricePerMillionTokens($model, $role);
135
        $totalCost = null === $costPerMillionTokens ? 0 : ($totalTokens / 1_000_000) * $costPerMillionTokens;
136
137
        return [
138
            'success' => null !== $costPerMillionTokens,
139
            'model' => $model,
140
            'role' => $role,
141
            'items_count' => count($dataItems),
142
            'items' => $costs,
143
            'total_tokens' => $totalTokens,
144
            'total_cost' => round($totalCost, 6),
145
            'total_cost_formatted' => '$'.number_format($totalCost, 4),
146
            'average_cost_per_item' => count($costs) > 0 ? round($totalCost / count($costs), 6) : 0,
147
        ];
148
    }
149
150
    public function budgetAnalysis(string $model, float $budget, mixed $data, string $role = 'input'): array
151
    {
152
        $costResult = $this->estimateCost($model, $data, $role);
153
154
        if (!$costResult['success']) {
155
            return [
156
                'success' => false,
157
                'error' => $costResult['error'],
158
            ];
159
        }
160
161
        $costPerRequest = $costResult['cost'];
162
        $requestsAffordable = (int)floor($budget / $costPerRequest);
163
        $percentBudgetUsed = $budget > 0 ? (($costPerRequest / $budget) * 100) : 0;
164
165
        return [
166
            'success' => true,
167
            'model' => $model,
168
            'budget' => round($budget, 2),
169
            'cost_per_request' => round($costPerRequest, 6),
170
            'cost_per_request_formatted' => '$'.number_format($costPerRequest, 4),
171
            'requests_affordable' => $requestsAffordable,
172
            'percent_budget_per_request' => round($percentBudgetUsed, 2),
173
            'remaining_budget_after_one_request' => round($budget - $costPerRequest, 6),
174
        ];
175
    }
176
177
    public function priceComparison(mixed $data, string $role = 'input'): array
178
    {
179
        $tokens = $this->tokenAnalyzer->estimateToon($data);
180
        $comparison = [];
181
182
        foreach ($this->models as $model => $prices) {
183
            if (isset($prices[$role])) {
184
                $costPerMillionTokens = $prices[$role];
185
                $cost = ($tokens / 1_000_000) * $costPerMillionTokens;
186
187
                $comparison[$model] = [
188
                    'cost_per_million' => $costPerMillionTokens,
189
                    'cost_for_this_request' => round($cost, 6),
190
                    'cost_for_1m_tokens' => '$'.number_format($costPerMillionTokens, 4),
191
                    'cost_for_this_request_formatted' => '$'.number_format($cost, 4),
192
                ];
193
            }
194
        }
195
196
        // Sort by cost
197
        uasort($comparison, fn ($a, $b) => $a['cost_for_this_request'] <=> $b['cost_for_this_request']);
198
199
        return [
200
            'tokens' => $tokens,
201
            'role' => $role,
202
            'models' => $comparison,
203
        ];
204
    }
205
206
    private function getPricePerMillionTokens(string $model, string $role): ?float
207
    {
208
        // First check in instance models
209
        if (isset($this->models[$model][$role])) {
210
            return $this->models[$model][$role];
211
        }
212
213
        // Then check in config
214
        $configModels = config('laravel-toon.cost_calculation.models', []);
215
        if (isset($configModels[$model][$role])) {
216
            return $configModels[$model][$role];
217
        }
218
219
        return null;
220
    }
221
222
    public function getAvailableModels(): array
223
    {
224
        $configModels = config('laravel-toon.cost_calculation.models', []);
225
        $all = array_merge($this->models, $configModels);
226
227
        $result = [];
228
        foreach ($all as $model => $prices) {
229
            $result[$model] = array_keys($prices);
230
        }
231
232
        return $result;
233
    }
234
}
235
236