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
104 lines
3.0 KiB
PHP
104 lines
3.0 KiB
PHP
<?php
|
|
|
|
namespace App\Services\LLM\Providers;
|
|
|
|
use App\Services\LLM\Contracts\ProviderInterface;
|
|
use App\Exceptions\ProviderException;
|
|
use Illuminate\Support\Facades\Http;
|
|
use Illuminate\Support\Facades\Log;
|
|
|
|
abstract class AbstractProvider implements ProviderInterface
|
|
{
|
|
protected string $apiKey;
|
|
protected string $baseUrl;
|
|
protected int $timeout = 60;
|
|
protected int $retryAttempts = 3;
|
|
protected int $retryDelay = 1000; // milliseconds
|
|
|
|
public function __construct(string $apiKey)
|
|
{
|
|
$this->apiKey = $apiKey;
|
|
}
|
|
|
|
/**
|
|
* Build request payload for provider
|
|
*/
|
|
abstract protected function buildRequest(array $messages, array $options): array;
|
|
|
|
/**
|
|
* Get authorization headers for provider
|
|
*/
|
|
abstract protected function getAuthHeaders(): array;
|
|
|
|
/**
|
|
* Make HTTP request with retry logic
|
|
*/
|
|
protected function makeRequest(string $endpoint, array $data): array
|
|
{
|
|
$attempt = 0;
|
|
$lastException = null;
|
|
|
|
while ($attempt < $this->retryAttempts) {
|
|
try {
|
|
$response = Http::withHeaders($this->getAuthHeaders())
|
|
->timeout($this->timeout)
|
|
->post($this->baseUrl . $endpoint, $data);
|
|
|
|
if ($response->successful()) {
|
|
return $response->json();
|
|
}
|
|
|
|
// Handle specific HTTP errors
|
|
if ($response->status() === 401) {
|
|
throw new ProviderException('Invalid API key', 401);
|
|
}
|
|
|
|
if ($response->status() === 429) {
|
|
throw new ProviderException('Rate limit exceeded', 429);
|
|
}
|
|
|
|
if ($response->status() >= 500) {
|
|
throw new ProviderException('Provider server error', $response->status());
|
|
}
|
|
|
|
throw new ProviderException(
|
|
'Request failed: ' . $response->body(),
|
|
$response->status()
|
|
);
|
|
|
|
} catch (\Exception $e) {
|
|
$lastException = $e;
|
|
$attempt++;
|
|
|
|
if ($attempt < $this->retryAttempts) {
|
|
Log::warning("Provider request failed, retrying ({$attempt}/{$this->retryAttempts})", [
|
|
'provider' => static::class,
|
|
'error' => $e->getMessage()
|
|
]);
|
|
usleep($this->retryDelay * 1000);
|
|
}
|
|
}
|
|
}
|
|
|
|
throw new ProviderException(
|
|
'All retry attempts failed: ' . ($lastException ? $lastException->getMessage() : 'Unknown error'),
|
|
$lastException ? $lastException->getCode() : 500
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Validate API key by making a test request
|
|
*/
|
|
public function validateApiKey(): bool
|
|
{
|
|
try {
|
|
$this->chatCompletion([
|
|
['role' => 'user', 'content' => 'test']
|
|
], ['max_tokens' => 5]);
|
|
return true;
|
|
} catch (\Exception $e) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|