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:
wtrinkl
2025-11-18 22:18:36 +01:00
parent bef36c7ca2
commit 6573e15ba4
60 changed files with 5991 additions and 0 deletions

View File

@@ -0,0 +1,103 @@
<?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;
}
}
}