Add complete Laravel LLM Gateway implementation
Core Features: - Multi-provider support (OpenAI, Anthropic, DeepSeek, Gemini, Mistral) - Provider service architecture with abstract base class - Dynamic model discovery from provider APIs - Encrypted per-user provider credentials storage Admin Interface: - Complete admin panel with Livewire components - User management with CRUD operations - API key management with testing capabilities - Budget system with limits and reset schedules - Usage logs with filtering and CSV export - Model pricing management with cost calculator - Dashboard with Chart.js visualizations Database Schema: - MariaDB migrations for all tables - User provider credentials (encrypted) - LLM request logging - Budget tracking and rate limiting - Model pricing configuration API Implementation: - OpenAI-compatible endpoints - Budget checking middleware - Rate limit enforcement - Request logging jobs - Cost calculation service Testing: - Unit tests for all provider services - Provider factory tests - Cost calculator tests Documentation: - Admin user seeder - Model pricing seeder - Configuration files
This commit is contained in:
128
laravel-app/app/Services/LLM/CostCalculator.php
Normal file
128
laravel-app/app/Services/LLM/CostCalculator.php
Normal file
@@ -0,0 +1,128 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\LLM;
|
||||
|
||||
use App\Models\ModelPricing;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class CostCalculator
|
||||
{
|
||||
/**
|
||||
* Calculate cost for a specific provider and model
|
||||
*
|
||||
* @param string $provider Provider name (openai, anthropic, etc.)
|
||||
* @param string $model Model name
|
||||
* @param int $promptTokens Number of prompt tokens
|
||||
* @param int $completionTokens Number of completion tokens
|
||||
* @return array ['prompt_cost', 'completion_cost', 'total_cost']
|
||||
*/
|
||||
public function calculate(
|
||||
string $provider,
|
||||
string $model,
|
||||
int $promptTokens,
|
||||
int $completionTokens
|
||||
): array {
|
||||
$pricing = $this->getPricing($provider, $model);
|
||||
|
||||
if (!$pricing) {
|
||||
Log::warning("No pricing found for {$provider}/{$model}, returning zero cost");
|
||||
return [
|
||||
'prompt_cost' => 0.0,
|
||||
'completion_cost' => 0.0,
|
||||
'total_cost' => 0.0,
|
||||
];
|
||||
}
|
||||
|
||||
$promptCost = ($promptTokens / 1_000_000) * $pricing->input_price_per_million;
|
||||
$completionCost = ($completionTokens / 1_000_000) * $pricing->output_price_per_million;
|
||||
$totalCost = $promptCost + $completionCost;
|
||||
|
||||
return [
|
||||
'prompt_cost' => round($promptCost, 6),
|
||||
'completion_cost' => round($completionCost, 6),
|
||||
'total_cost' => round($totalCost, 6),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Estimate cost before making the request
|
||||
* Uses average token estimation
|
||||
*
|
||||
* @param string $provider
|
||||
* @param string $model
|
||||
* @param int $estimatedPromptTokens
|
||||
* @param int $estimatedCompletionTokens
|
||||
* @return float Estimated total cost
|
||||
*/
|
||||
public function estimateCost(
|
||||
string $provider,
|
||||
string $model,
|
||||
int $estimatedPromptTokens,
|
||||
int $estimatedCompletionTokens
|
||||
): float {
|
||||
$costs = $this->calculate($provider, $model, $estimatedPromptTokens, $estimatedCompletionTokens);
|
||||
return $costs['total_cost'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get pricing from cache or database
|
||||
*
|
||||
* @param string $provider
|
||||
* @param string $model
|
||||
* @return ModelPricing|null
|
||||
*/
|
||||
private function getPricing(string $provider, string $model): ?ModelPricing
|
||||
{
|
||||
$cacheKey = "pricing:{$provider}:{$model}";
|
||||
$cacheTTL = 3600; // 1 hour
|
||||
|
||||
return Cache::remember($cacheKey, $cacheTTL, function () use ($provider, $model) {
|
||||
return ModelPricing::where('provider', $provider)
|
||||
->where('model', $model)
|
||||
->where('is_active', true)
|
||||
->where('effective_from', '<=', now())
|
||||
->where(function ($query) {
|
||||
$query->whereNull('effective_until')
|
||||
->orWhere('effective_until', '>=', now());
|
||||
})
|
||||
->orderBy('effective_from', 'desc')
|
||||
->first();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear pricing cache for a specific provider/model
|
||||
*
|
||||
* @param string|null $provider
|
||||
* @param string|null $model
|
||||
* @return void
|
||||
*/
|
||||
public function clearCache(?string $provider = null, ?string $model = null): void
|
||||
{
|
||||
if ($provider && $model) {
|
||||
Cache::forget("pricing:{$provider}:{$model}");
|
||||
} else {
|
||||
// Clear all pricing cache
|
||||
Cache::flush();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all active pricing entries
|
||||
*
|
||||
* @return \Illuminate\Support\Collection
|
||||
*/
|
||||
public function getAllActivePricing(): \Illuminate\Support\Collection
|
||||
{
|
||||
return ModelPricing::where('is_active', true)
|
||||
->where('effective_from', '<=', now())
|
||||
->where(function ($query) {
|
||||
$query->whereNull('effective_until')
|
||||
->orWhere('effective_until', '>=', now());
|
||||
})
|
||||
->orderBy('provider')
|
||||
->orderBy('model')
|
||||
->get();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user