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:
213
laravel-app/app/Services/Budget/BudgetChecker.php
Normal file
213
laravel-app/app/Services/Budget/BudgetChecker.php
Normal file
@@ -0,0 +1,213 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Budget;
|
||||
|
||||
use App\Models\User;
|
||||
use App\Models\UserBudget;
|
||||
use App\Exceptions\InsufficientBudgetException;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class BudgetChecker
|
||||
{
|
||||
/**
|
||||
* Check if user has sufficient budget for a request
|
||||
*
|
||||
* @param User $user
|
||||
* @param float $estimatedCost
|
||||
* @return bool
|
||||
* @throws InsufficientBudgetException
|
||||
*/
|
||||
public function checkBudget(User $user, float $estimatedCost = 0.0): bool
|
||||
{
|
||||
$budget = $this->getOrCreateBudget($user);
|
||||
|
||||
// If budget is already exceeded, deny immediately
|
||||
if ($budget->is_budget_exceeded) {
|
||||
throw new InsufficientBudgetException(
|
||||
"Budget limit exceeded. Current spending: $" . number_format($budget->current_month_spending, 2) .
|
||||
" / Monthly limit: $" . number_format($budget->monthly_limit, 2)
|
||||
);
|
||||
}
|
||||
|
||||
// Check daily limit if set
|
||||
if ($budget->daily_limit > 0) {
|
||||
$projectedDailySpending = $budget->current_day_spending + $estimatedCost;
|
||||
|
||||
if ($projectedDailySpending > $budget->daily_limit) {
|
||||
throw new InsufficientBudgetException(
|
||||
"Daily budget limit would be exceeded. Current: $" . number_format($budget->current_day_spending, 2) .
|
||||
" / Daily limit: $" . number_format($budget->daily_limit, 2)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Check monthly limit
|
||||
if ($budget->monthly_limit > 0) {
|
||||
$projectedMonthlySpending = $budget->current_month_spending + $estimatedCost;
|
||||
|
||||
if ($projectedMonthlySpending > $budget->monthly_limit) {
|
||||
throw new InsufficientBudgetException(
|
||||
"Monthly budget limit would be exceeded. Current: $" . number_format($budget->current_month_spending, 2) .
|
||||
" / Monthly limit: $" . number_format($budget->monthly_limit, 2)
|
||||
);
|
||||
}
|
||||
|
||||
// Check alert threshold
|
||||
$usagePercentage = ($projectedMonthlySpending / $budget->monthly_limit) * 100;
|
||||
|
||||
if ($usagePercentage >= $budget->alert_threshold_percentage) {
|
||||
$this->sendBudgetAlert($user, $budget, $usagePercentage);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update user budget after a request
|
||||
*
|
||||
* @param User $user
|
||||
* @param float $actualCost
|
||||
* @return void
|
||||
*/
|
||||
public function updateBudget(User $user, float $actualCost): void
|
||||
{
|
||||
$budget = $this->getOrCreateBudget($user);
|
||||
|
||||
// Reset periods if needed
|
||||
$this->checkAndResetPeriods($budget);
|
||||
|
||||
// Update spending
|
||||
$budget->current_month_spending += $actualCost;
|
||||
$budget->current_day_spending += $actualCost;
|
||||
|
||||
// Check if budget is now exceeded
|
||||
if ($budget->monthly_limit > 0 && $budget->current_month_spending >= $budget->monthly_limit) {
|
||||
$budget->is_budget_exceeded = true;
|
||||
}
|
||||
|
||||
$budget->save();
|
||||
|
||||
// Invalidate cache
|
||||
Cache::forget("user_budget:{$user->id}");
|
||||
|
||||
Log::info('Budget updated', [
|
||||
'user_id' => $user->id,
|
||||
'cost' => $actualCost,
|
||||
'monthly_spending' => $budget->current_month_spending,
|
||||
'daily_spending' => $budget->current_day_spending
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get or create user budget
|
||||
*
|
||||
* @param User $user
|
||||
* @return UserBudget
|
||||
*/
|
||||
private function getOrCreateBudget(User $user): UserBudget
|
||||
{
|
||||
$budget = $user->budget;
|
||||
|
||||
if (!$budget) {
|
||||
$budget = UserBudget::create([
|
||||
'user_id' => $user->id,
|
||||
'monthly_limit' => config('llm.default_monthly_budget', 100.00),
|
||||
'daily_limit' => config('llm.default_daily_budget', 10.00),
|
||||
'month_started_at' => now()->startOfMonth(),
|
||||
'day_started_at' => now()->startOfDay(),
|
||||
'alert_threshold_percentage' => 80,
|
||||
]);
|
||||
|
||||
Log::info('Budget created for user', ['user_id' => $user->id]);
|
||||
}
|
||||
|
||||
return $budget;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check and reset budget periods if needed
|
||||
*
|
||||
* @param UserBudget $budget
|
||||
* @return void
|
||||
*/
|
||||
private function checkAndResetPeriods(UserBudget $budget): void
|
||||
{
|
||||
$now = now();
|
||||
|
||||
// Reset monthly budget if new month
|
||||
if ($now->startOfMonth()->greaterThan($budget->month_started_at)) {
|
||||
$budget->current_month_spending = 0.0;
|
||||
$budget->month_started_at = $now->startOfMonth();
|
||||
$budget->is_budget_exceeded = false;
|
||||
$budget->last_alert_sent_at = null;
|
||||
|
||||
Log::info('Monthly budget reset', ['user_id' => $budget->user_id]);
|
||||
}
|
||||
|
||||
// Reset daily budget if new day
|
||||
if ($now->startOfDay()->greaterThan($budget->day_started_at)) {
|
||||
$budget->current_day_spending = 0.0;
|
||||
$budget->day_started_at = $now->startOfDay();
|
||||
|
||||
Log::info('Daily budget reset', ['user_id' => $budget->user_id]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send budget alert to user
|
||||
*
|
||||
* @param User $user
|
||||
* @param UserBudget $budget
|
||||
* @param float $usagePercentage
|
||||
* @return void
|
||||
*/
|
||||
private function sendBudgetAlert(User $user, UserBudget $budget, float $usagePercentage): void
|
||||
{
|
||||
// Only send alert once per day
|
||||
if ($budget->last_alert_sent_at && $budget->last_alert_sent_at->isToday()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Log::warning('Budget threshold reached', [
|
||||
'user_id' => $user->id,
|
||||
'user_email' => $user->email,
|
||||
'usage_percentage' => round($usagePercentage, 2),
|
||||
'current_spending' => $budget->current_month_spending,
|
||||
'monthly_limit' => $budget->monthly_limit
|
||||
]);
|
||||
|
||||
// TODO: Send email notification
|
||||
// Mail::to($user->email)->send(new BudgetAlertMail($budget, $usagePercentage));
|
||||
|
||||
$budget->last_alert_sent_at = now();
|
||||
$budget->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get budget status for user
|
||||
*
|
||||
* @param User $user
|
||||
* @return array
|
||||
*/
|
||||
public function getBudgetStatus(User $user): array
|
||||
{
|
||||
$budget = $this->getOrCreateBudget($user);
|
||||
|
||||
return [
|
||||
'monthly_limit' => $budget->monthly_limit,
|
||||
'daily_limit' => $budget->daily_limit,
|
||||
'current_month_spending' => $budget->current_month_spending,
|
||||
'current_day_spending' => $budget->current_day_spending,
|
||||
'monthly_remaining' => max(0, $budget->monthly_limit - $budget->current_month_spending),
|
||||
'daily_remaining' => max(0, $budget->daily_limit - $budget->current_day_spending),
|
||||
'monthly_usage_percentage' => $budget->monthly_limit > 0
|
||||
? ($budget->current_month_spending / $budget->monthly_limit) * 100
|
||||
: 0,
|
||||
'is_exceeded' => $budget->is_budget_exceeded,
|
||||
'month_started_at' => $budget->month_started_at,
|
||||
'day_started_at' => $budget->day_started_at,
|
||||
];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user