- Umfassende Analyse der aktuellen Implementierung durchgeführt - Identifiziert: Zwei getrennte User-Systeme (users vs gateway_users) - Problem: API verwendet falsche Tabelle (users statt gateway_users) - Lösung: Kompletter Implementierungsplan für korrekte Architektur - Dokument: ARCHITEKTUR.md mit 6-Tage-Umsetzungsplan erstellt - Enthält: Custom API-Key Guard, Gateway-User-Credentials, Budget-System - Swagger/Scramble Paket hinzugefügt (für spätere API-Dokumentation) Status: Bereit für Implementierung (Start: Tag 1 - Datenbank & Models)
1168 lines
32 KiB
Markdown
1168 lines
32 KiB
Markdown
# Laravel LLM Gateway - Korrekte Architektur-Umsetzung
|
|
|
|
**Datum:** 2025-11-18
|
|
**Status:** 🎯 Klarstellung der Architektur-Vision
|
|
|
|
---
|
|
|
|
## 🏗️ Architektur-Konzept (KORREKT)
|
|
|
|
### Zwei getrennte User-Typen:
|
|
|
|
#### 1. **Admins** (`users` Tabelle)
|
|
- **Zweck:** Verwaltung des Gateway-Systems
|
|
- **Zugriff:** Laravel Web-Interface (Session-basiert)
|
|
- **Funktionen:**
|
|
- Gateway-Users verwalten
|
|
- Budgets konfigurieren
|
|
- Usage-Logs einsehen
|
|
- Model-Pricing pflegen
|
|
- Provider-Credentials für Gateway-Users konfigurieren
|
|
|
|
#### 2. **Gateway-Users** (`gateway_users` Tabelle)
|
|
- **Zweck:** API-Clients (externe Anwendungen)
|
|
- **Zugriff:** REST API via API-Keys
|
|
- **Funktionen:**
|
|
- Chat-Completions anfragen
|
|
- Antworten von LLM-Providern erhalten
|
|
- Innerhalb ihres Budgets arbeiten
|
|
|
|
**Das ist eine saubere Trennung - so sollte es sein!** ✅
|
|
|
|
---
|
|
|
|
## 🔴 Aktuelles Problem
|
|
|
|
Die API-Implementation verwendet die **falschen Tabellen**:
|
|
|
|
### Was der Code aktuell macht:
|
|
```php
|
|
// ChatCompletionController.php
|
|
Route::middleware('auth:sanctum')->group(function () {
|
|
Route::post('/chat/completions', ...);
|
|
});
|
|
|
|
// Verwendet:
|
|
$user = $request->user(); // ← Aus 'users' Tabelle (Admins!)
|
|
$credential = UserProviderCredential::where('user_id', $user->id)->first();
|
|
```
|
|
|
|
### Was der Code machen sollte:
|
|
```php
|
|
// API-Key Authentication für gateway_users
|
|
Route::middleware('auth:api')->group(function () {
|
|
Route::post('/chat/completions', ...);
|
|
});
|
|
|
|
// Sollte verwenden:
|
|
$gatewayUser = $request->user(); // ← Aus 'gateway_users' Tabelle
|
|
$credential = GatewayUserCredential::where('gateway_user_id', $gatewayUser->id)->first();
|
|
```
|
|
|
|
---
|
|
|
|
|
|
## 📋 Umsetzungsplan
|
|
|
|
### Phase 1: Datenbank-Struktur anpassen
|
|
|
|
#### 1.1 Neue Migration: Provider-Credentials für Gateway-Users
|
|
|
|
**Aktuell:** `user_provider_credentials` hat Foreign Key zu `users`
|
|
|
|
**Neu:** Credential-Tabelle für `gateway_users` erstellen
|
|
|
|
```php
|
|
// Migration: create_gateway_user_credentials_table.php
|
|
Schema::create('gateway_user_credentials', function (Blueprint $table) {
|
|
$table->id();
|
|
$table->string('gateway_user_id'); // ← Foreign Key zu gateway_users
|
|
$table->string('provider'); // openai, anthropic, google, deepseek, mistral
|
|
$table->text('api_key'); // Verschlüsselt
|
|
$table->string('organization_id')->nullable();
|
|
$table->boolean('is_active')->default(true);
|
|
$table->timestamp('last_used_at')->nullable();
|
|
$table->timestamp('last_tested_at')->nullable();
|
|
$table->string('test_status')->nullable(); // success, failed
|
|
$table->text('test_error')->nullable();
|
|
$table->timestamps();
|
|
|
|
$table->foreign('gateway_user_id')
|
|
->references('user_id')
|
|
->on('gateway_users')
|
|
->onDelete('cascade');
|
|
|
|
$table->unique(['gateway_user_id', 'provider']);
|
|
$table->index('is_active');
|
|
});
|
|
```
|
|
|
|
#### 1.2 Budget-Tabelle vereinheitlichen
|
|
|
|
**Entscheidung:** Welche Tabelle behalten?
|
|
|
|
**Option A:** `budgets` Tabelle behalten (aktuell für gateway_users)
|
|
```sql
|
|
-- budgets Tabelle hat bereits:
|
|
- id, name, monthly_limit, current_spending, alert_threshold
|
|
- Keine direkte User-Zuordnung (wird über gateway_users.budget_id verknüpft)
|
|
```
|
|
|
|
**Option B:** Direkte Budget-Felder in `gateway_users`
|
|
```sql
|
|
ALTER TABLE gateway_users ADD COLUMN monthly_budget_limit DECIMAL(10,2);
|
|
ALTER TABLE gateway_users ADD COLUMN current_month_spending DECIMAL(10,2);
|
|
ALTER TABLE gateway_users ADD COLUMN budget_alert_threshold INT;
|
|
```
|
|
|
|
**Empfehlung:** Option B - Vereinfachung
|
|
- Direkter Zugriff auf Budget pro User
|
|
- Weniger Joins
|
|
- Einfacheres Datenmodell
|
|
|
|
#### 1.3 Usage-Logging konsolidieren
|
|
|
|
**Aktuell:** Zwei Tabellen:
|
|
- `llm_requests` (von API-Code genutzt)
|
|
- `usage_logs` (im Admin-Interface angezeigt)
|
|
|
|
**Neu:** Nur `usage_logs` verwenden, aber anpassen:
|
|
```php
|
|
Schema::table('usage_logs', function (Blueprint $table) {
|
|
// Stelle sicher dass alle Felder vorhanden sind:
|
|
// user_id → gateway_user_id umbenennen
|
|
$table->renameColumn('user_id', 'gateway_user_id');
|
|
|
|
// Falls fehlt:
|
|
if (!Schema::hasColumn('usage_logs', 'request_payload')) {
|
|
$table->json('request_payload')->nullable();
|
|
}
|
|
if (!Schema::hasColumn('usage_logs', 'response_payload')) {
|
|
$table->json('response_payload')->nullable();
|
|
}
|
|
if (!Schema::hasColumn('usage_logs', 'ip_address')) {
|
|
$table->string('ip_address')->nullable();
|
|
}
|
|
if (!Schema::hasColumn('usage_logs', 'user_agent')) {
|
|
$table->string('user_agent')->nullable();
|
|
}
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
### Phase 2: API-Key Authentication System
|
|
|
|
#### 2.1 Custom Guard für API-Keys
|
|
|
|
Laravel kann mit Custom Guards arbeiten. Wir erstellen einen `api-key` Guard:
|
|
|
|
**config/auth.php:**
|
|
```php
|
|
'guards' => [
|
|
'web' => [
|
|
'driver' => 'session',
|
|
'provider' => 'users', // ← Für Admins
|
|
],
|
|
|
|
'api' => [
|
|
'driver' => 'api-key', // ← Custom Guard
|
|
'provider' => 'gateway_users',
|
|
],
|
|
],
|
|
|
|
'providers' => [
|
|
'users' => [
|
|
'driver' => 'eloquent',
|
|
'model' => App\Models\User::class,
|
|
],
|
|
|
|
'gateway_users' => [
|
|
'driver' => 'eloquent',
|
|
'model' => App\Models\GatewayUser::class,
|
|
],
|
|
],
|
|
```
|
|
|
|
|
|
#### 2.2 API-Key Guard Implementation
|
|
|
|
**Neue Datei: `app/Auth/ApiKeyGuard.php`**
|
|
```php
|
|
<?php
|
|
|
|
namespace App\Auth;
|
|
|
|
use App\Models\ApiKey;
|
|
use App\Models\GatewayUser;
|
|
use Illuminate\Auth\GuardHelpers;
|
|
use Illuminate\Contracts\Auth\Guard;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Support\Facades\Hash;
|
|
|
|
class ApiKeyGuard implements Guard
|
|
{
|
|
use GuardHelpers;
|
|
|
|
protected $request;
|
|
protected $provider;
|
|
|
|
public function __construct($provider, Request $request)
|
|
{
|
|
$this->provider = $provider;
|
|
$this->request = $request;
|
|
}
|
|
|
|
public function user()
|
|
{
|
|
if ($this->user !== null) {
|
|
return $this->user;
|
|
}
|
|
|
|
// Get API key from header: Authorization: Bearer sk-xxx
|
|
$apiKey = $this->request->bearerToken();
|
|
|
|
if (!$apiKey) {
|
|
return null;
|
|
}
|
|
|
|
// Find API key in database
|
|
$keyRecord = ApiKey::where('key_prefix', substr($apiKey, 0, 10))
|
|
->where('is_active', true)
|
|
->first();
|
|
|
|
if (!$keyRecord) {
|
|
return null;
|
|
}
|
|
|
|
// Verify full key hash
|
|
if (!Hash::check($apiKey, $keyRecord->key_hash)) {
|
|
return null;
|
|
}
|
|
|
|
// Update last used timestamp
|
|
$keyRecord->update(['last_used_at' => now()]);
|
|
|
|
// Return the gateway user
|
|
$this->user = GatewayUser::find($keyRecord->gateway_user_id);
|
|
|
|
return $this->user;
|
|
}
|
|
|
|
public function validate(array $credentials = [])
|
|
{
|
|
return $this->user() !== null;
|
|
}
|
|
}
|
|
```
|
|
|
|
#### 2.3 Service Provider registrieren
|
|
|
|
**app/Providers/AuthServiceProvider.php:**
|
|
```php
|
|
use App\Auth\ApiKeyGuard;
|
|
use Illuminate\Support\Facades\Auth;
|
|
|
|
public function boot(): void
|
|
{
|
|
Auth::extend('api-key', function ($app, $name, array $config) {
|
|
return new ApiKeyGuard(
|
|
Auth::createUserProvider($config['provider']),
|
|
$app['request']
|
|
);
|
|
});
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### Phase 3: API-Code anpassen
|
|
|
|
#### 3.1 Routes aktualisieren
|
|
|
|
**routes/api.php:**
|
|
```php
|
|
use App\Http\Controllers\Api\ChatCompletionController;
|
|
|
|
// Gateway API - verwendet 'api' guard (API-Keys für gateway_users)
|
|
Route::middleware('auth:api')->group(function () {
|
|
Route::post('/chat/completions', [ChatCompletionController::class, 'create'])
|
|
->middleware(['checkbudget', 'checkratelimit']);
|
|
|
|
Route::get('/user', function (Request $request) {
|
|
return $request->user(); // Gibt GatewayUser zurück
|
|
});
|
|
});
|
|
```
|
|
|
|
|
|
#### 3.2 GatewayUser Model erweitern
|
|
|
|
**app/Models/GatewayUser.php:**
|
|
```php
|
|
<?php
|
|
|
|
namespace App\Models;
|
|
|
|
use Illuminate\Database\Eloquent\Model;
|
|
use Illuminate\Contracts\Auth\Authenticatable;
|
|
use Illuminate\Auth\Authenticatable as AuthenticatableTrait;
|
|
|
|
class GatewayUser extends Model implements Authenticatable
|
|
{
|
|
use AuthenticatableTrait;
|
|
|
|
protected $primaryKey = 'user_id';
|
|
public $incrementing = false;
|
|
protected $keyType = 'string';
|
|
|
|
protected $fillable = [
|
|
'user_id',
|
|
'alias',
|
|
'monthly_budget_limit',
|
|
'current_month_spending',
|
|
'budget_alert_threshold',
|
|
'rate_limit_per_hour',
|
|
'blocked',
|
|
'metadata',
|
|
];
|
|
|
|
protected $casts = [
|
|
'blocked' => 'boolean',
|
|
'metadata' => 'json',
|
|
'monthly_budget_limit' => 'decimal:2',
|
|
'current_month_spending' => 'decimal:2',
|
|
];
|
|
|
|
// Relations
|
|
public function apiKeys()
|
|
{
|
|
return $this->hasMany(ApiKey::class, 'gateway_user_id', 'user_id');
|
|
}
|
|
|
|
public function credentials()
|
|
{
|
|
return $this->hasMany(GatewayUserCredential::class, 'gateway_user_id', 'user_id');
|
|
}
|
|
|
|
public function usageLogs()
|
|
{
|
|
return $this->hasMany(UsageLog::class, 'gateway_user_id', 'user_id');
|
|
}
|
|
|
|
// Helper methods
|
|
public function isBlocked(): bool
|
|
{
|
|
return $this->blocked;
|
|
}
|
|
|
|
public function hasExceededBudget(): bool
|
|
{
|
|
if (!$this->monthly_budget_limit) {
|
|
return false;
|
|
}
|
|
return $this->current_month_spending >= $this->monthly_budget_limit;
|
|
}
|
|
|
|
public function incrementSpending(float $amount): void
|
|
{
|
|
$this->increment('current_month_spending', $amount);
|
|
}
|
|
|
|
public function resetMonthlySpending(): void
|
|
{
|
|
$this->update(['current_month_spending' => 0]);
|
|
}
|
|
}
|
|
```
|
|
|
|
#### 3.3 Neues Model: GatewayUserCredential
|
|
|
|
**app/Models/GatewayUserCredential.php:**
|
|
```php
|
|
<?php
|
|
|
|
namespace App\Models;
|
|
|
|
use Illuminate\Database\Eloquent\Model;
|
|
use Illuminate\Support\Facades\Crypt;
|
|
|
|
class GatewayUserCredential extends Model
|
|
{
|
|
protected $fillable = [
|
|
'gateway_user_id',
|
|
'provider',
|
|
'api_key',
|
|
'organization_id',
|
|
'is_active',
|
|
'last_used_at',
|
|
'last_tested_at',
|
|
'test_status',
|
|
'test_error',
|
|
];
|
|
|
|
protected $hidden = ['api_key'];
|
|
|
|
protected $casts = [
|
|
'is_active' => 'boolean',
|
|
'last_used_at' => 'datetime',
|
|
'last_tested_at' => 'datetime',
|
|
];
|
|
|
|
// Automatic encryption
|
|
public function setApiKeyAttribute($value): void
|
|
{
|
|
$this->attributes['api_key'] = Crypt::encryptString($value);
|
|
}
|
|
|
|
public function getApiKeyAttribute($value): string
|
|
{
|
|
return Crypt::decryptString($value);
|
|
}
|
|
|
|
public function gatewayUser()
|
|
{
|
|
return $this->belongsTo(GatewayUser::class, 'gateway_user_id', 'user_id');
|
|
}
|
|
}
|
|
```
|
|
|
|
|
|
#### 3.4 GatewayService anpassen
|
|
|
|
**app/Services/LLM/GatewayService.php - Änderungen:**
|
|
```php
|
|
<?php
|
|
|
|
namespace App\Services\LLM;
|
|
|
|
use App\Models\GatewayUser; // ← Statt User
|
|
use App\Models\GatewayUserCredential; // ← Neu
|
|
use App\Exceptions\{ProviderException, InsufficientBudgetException};
|
|
|
|
class GatewayService
|
|
{
|
|
public function chatCompletion(
|
|
GatewayUser $user, // ← Typ geändert
|
|
string $provider,
|
|
string $model,
|
|
array $messages,
|
|
array $options = [],
|
|
?string $ipAddress = null,
|
|
?string $userAgent = null
|
|
): array {
|
|
// ... (Rest bleibt gleich, nur getUserCredential anpassen)
|
|
}
|
|
|
|
private function getUserCredential(GatewayUser $user, string $provider): GatewayUserCredential
|
|
{
|
|
$credential = GatewayUserCredential::where('gateway_user_id', $user->user_id)
|
|
->where('provider', $provider)
|
|
->where('is_active', true)
|
|
->first();
|
|
|
|
if (!$credential) {
|
|
throw new ProviderException(
|
|
"No active API credentials found for provider: {$provider}",
|
|
400
|
|
);
|
|
}
|
|
|
|
$credential->update(['last_used_at' => now()]);
|
|
|
|
return $credential;
|
|
}
|
|
|
|
private function updateUserBudget(GatewayUser $user, float $cost): void
|
|
{
|
|
// Budget direkt in gateway_users Tabelle
|
|
$user->incrementSpending($cost);
|
|
|
|
// Check if budget exceeded
|
|
if ($user->hasExceededBudget()) {
|
|
// TODO: Dispatch alert notification
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
#### 3.5 RequestLogger anpassen
|
|
|
|
**app/Services/LLM/RequestLogger.php:**
|
|
```php
|
|
use App\Models\UsageLog; // ← Statt LlmRequest
|
|
|
|
public function logSuccess(
|
|
string $gatewayUserId, // ← Statt int $userId
|
|
string $provider,
|
|
string $model,
|
|
array $requestPayload,
|
|
array $response,
|
|
array $costs,
|
|
int $responseTimeMs,
|
|
?string $ipAddress = null,
|
|
?string $userAgent = null
|
|
): int {
|
|
$log = UsageLog::create([
|
|
'gateway_user_id' => $gatewayUserId, // ← Geändert
|
|
'provider' => $provider,
|
|
'model' => $model,
|
|
'prompt_tokens' => $response['usage']['prompt_tokens'],
|
|
'completion_tokens' => $response['usage']['completion_tokens'],
|
|
'total_tokens' => $response['usage']['total_tokens'],
|
|
'cost' => $costs['total_cost'],
|
|
'request_payload' => $requestPayload,
|
|
'response_payload' => $response,
|
|
'response_time_ms' => $responseTimeMs,
|
|
'ip_address' => $ipAddress,
|
|
'user_agent' => $userAgent,
|
|
'status' => 'success',
|
|
]);
|
|
|
|
return $log->id;
|
|
}
|
|
```
|
|
|
|
|
|
#### 3.6 Middleware anpassen
|
|
|
|
**app/Http/Middleware/CheckBudget.php:**
|
|
```php
|
|
<?php
|
|
|
|
namespace App\Http\Middleware;
|
|
|
|
use Closure;
|
|
use Illuminate\Http\Request;
|
|
use Symfony\Component\HttpFoundation\Response;
|
|
|
|
class CheckBudget
|
|
{
|
|
public function handle(Request $request, Closure $next): Response
|
|
{
|
|
$user = $request->user(); // GatewayUser
|
|
|
|
if ($user && $user->hasExceededBudget()) {
|
|
return response()->json([
|
|
'error' => [
|
|
'message' => 'Budget exceeded. Please contact your administrator.',
|
|
'type' => 'budget_exceeded',
|
|
'code' => 429,
|
|
'budget_limit' => $user->monthly_budget_limit,
|
|
'current_spending' => $user->current_month_spending,
|
|
]
|
|
], 429);
|
|
}
|
|
|
|
if ($user && $user->isBlocked()) {
|
|
return response()->json([
|
|
'error' => [
|
|
'message' => 'User is blocked. Please contact your administrator.',
|
|
'type' => 'user_blocked',
|
|
'code' => 403,
|
|
]
|
|
], 403);
|
|
}
|
|
|
|
return $next($request);
|
|
}
|
|
}
|
|
```
|
|
|
|
**app/Http/Middleware/CheckRateLimit.php:**
|
|
```php
|
|
<?php
|
|
|
|
namespace App\Http\Middleware;
|
|
|
|
use Closure;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Support\Facades\Cache;
|
|
use Symfony\Component\HttpFoundation\Response;
|
|
|
|
class CheckRateLimit
|
|
{
|
|
public function handle(Request $request, Closure $next): Response
|
|
{
|
|
$user = $request->user(); // GatewayUser
|
|
|
|
if (!$user || !$user->rate_limit_per_hour) {
|
|
return $next($request);
|
|
}
|
|
|
|
$key = 'rate_limit:' . $user->user_id;
|
|
$requests = Cache::get($key, 0);
|
|
|
|
if ($requests >= $user->rate_limit_per_hour) {
|
|
$ttl = Cache::get($key . ':ttl', 3600);
|
|
|
|
return response()->json([
|
|
'error' => [
|
|
'message' => 'Rate limit exceeded. Please try again later.',
|
|
'type' => 'rate_limit_exceeded',
|
|
'code' => 429,
|
|
'limit' => $user->rate_limit_per_hour,
|
|
'current' => $requests,
|
|
'retry_after' => $ttl,
|
|
]
|
|
], 429);
|
|
}
|
|
|
|
// Increment counter
|
|
Cache::put($key, $requests + 1, 3600);
|
|
if ($requests == 0) {
|
|
Cache::put($key . ':ttl', 3600, 3600);
|
|
}
|
|
|
|
return $next($request);
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### Phase 4: Admin-Interface für Gateway-User Credentials
|
|
|
|
#### 4.1 Neuer Controller: GatewayUserCredentialController
|
|
|
|
**app/Http/Controllers/Admin/GatewayUserCredentialController.php:**
|
|
```php
|
|
<?php
|
|
|
|
namespace App\Http\Controllers\Admin;
|
|
|
|
use App\Http\Controllers\Controller;
|
|
use App\Models\GatewayUser;
|
|
use App\Models\GatewayUserCredential;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Support\Facades\Log;
|
|
|
|
class GatewayUserCredentialController extends Controller
|
|
{
|
|
public function index(Request $request, $gatewayUserId)
|
|
{
|
|
$user = GatewayUser::findOrFail($gatewayUserId);
|
|
$credentials = $user->credentials()->get();
|
|
|
|
return view('admin.gateway-user-credentials.index', compact('user', 'credentials'));
|
|
}
|
|
|
|
public function create($gatewayUserId)
|
|
{
|
|
$user = GatewayUser::findOrFail($gatewayUserId);
|
|
$providers = ['openai', 'anthropic', 'google', 'deepseek', 'mistral'];
|
|
|
|
return view('admin.gateway-user-credentials.create', compact('user', 'providers'));
|
|
}
|
|
|
|
public function store(Request $request, $gatewayUserId)
|
|
{
|
|
$validated = $request->validate([
|
|
'provider' => 'required|in:openai,anthropic,google,deepseek,mistral',
|
|
'api_key' => 'required|string',
|
|
'organization_id' => 'nullable|string',
|
|
]);
|
|
|
|
GatewayUserCredential::create([
|
|
'gateway_user_id' => $gatewayUserId,
|
|
'provider' => $validated['provider'],
|
|
'api_key' => $validated['api_key'],
|
|
'organization_id' => $validated['organization_id'] ?? null,
|
|
'is_active' => true,
|
|
]);
|
|
|
|
return redirect()
|
|
->route('admin.gateway-users.credentials.index', $gatewayUserId)
|
|
->with('success', 'Credential added successfully');
|
|
}
|
|
|
|
// Test, Toggle, Delete methods...
|
|
}
|
|
```
|
|
|
|
|
|
#### 4.2 Routes erweitern
|
|
|
|
**routes/web.php - Hinzufügen:**
|
|
```php
|
|
Route::middleware(['auth', 'verified'])->group(function () {
|
|
// ... existierende Routes ...
|
|
|
|
// Gateway User Credentials Management
|
|
Route::prefix('admin/gateway-users/{gatewayUser}')->name('admin.gateway-users.')->group(function () {
|
|
Route::get('credentials', [GatewayUserCredentialController::class, 'index'])
|
|
->name('credentials.index');
|
|
Route::get('credentials/create', [GatewayUserCredentialController::class, 'create'])
|
|
->name('credentials.create');
|
|
Route::post('credentials', [GatewayUserCredentialController::class, 'store'])
|
|
->name('credentials.store');
|
|
Route::post('credentials/{credential}/test', [GatewayUserCredentialController::class, 'test'])
|
|
->name('credentials.test');
|
|
Route::post('credentials/{credential}/toggle', [GatewayUserCredentialController::class, 'toggle'])
|
|
->name('credentials.toggle');
|
|
Route::delete('credentials/{credential}', [GatewayUserCredentialController::class, 'destroy'])
|
|
->name('credentials.destroy');
|
|
});
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
## 🎯 Migrations-Reihenfolge
|
|
|
|
### Migration 1: Budget-Felder zu gateway_users hinzufügen
|
|
```bash
|
|
php artisan make:migration add_budget_fields_to_gateway_users
|
|
```
|
|
|
|
```php
|
|
Schema::table('gateway_users', function (Blueprint $table) {
|
|
$table->decimal('monthly_budget_limit', 10, 2)->nullable()->after('alias');
|
|
$table->decimal('current_month_spending', 10, 2)->default(0)->after('monthly_budget_limit');
|
|
$table->integer('budget_alert_threshold')->nullable()->after('current_month_spending');
|
|
$table->integer('rate_limit_per_hour')->default(60)->after('budget_alert_threshold');
|
|
$table->dropColumn('spend'); // Alte Spalte entfernen
|
|
$table->dropColumn('budget_id'); // Nicht mehr gebraucht
|
|
});
|
|
```
|
|
|
|
### Migration 2: Gateway User Credentials erstellen
|
|
```bash
|
|
php artisan make:migration create_gateway_user_credentials_table
|
|
```
|
|
|
|
(Wie oben bereits dokumentiert)
|
|
|
|
### Migration 3: Usage Logs anpassen
|
|
```bash
|
|
php artisan make:migration update_usage_logs_for_gateway_users
|
|
```
|
|
|
|
```php
|
|
Schema::table('usage_logs', function (Blueprint $table) {
|
|
// Spalte umbenennen
|
|
$table->renameColumn('user_id', 'gateway_user_id');
|
|
|
|
// Neue Spalten hinzufügen falls nicht vorhanden
|
|
if (!Schema::hasColumn('usage_logs', 'request_payload')) {
|
|
$table->json('request_payload')->nullable();
|
|
}
|
|
if (!Schema::hasColumn('usage_logs', 'response_payload')) {
|
|
$table->json('response_payload')->nullable();
|
|
}
|
|
if (!Schema::hasColumn('usage_logs', 'response_time_ms')) {
|
|
$table->integer('response_time_ms')->nullable();
|
|
}
|
|
if (!Schema::hasColumn('usage_logs', 'ip_address')) {
|
|
$table->string('ip_address', 45)->nullable();
|
|
}
|
|
if (!Schema::hasColumn('usage_logs', 'user_agent')) {
|
|
$table->string('user_agent')->nullable();
|
|
}
|
|
});
|
|
```
|
|
|
|
### Migration 4: Alte Tabellen optional behalten/entfernen
|
|
```bash
|
|
php artisan make:migration cleanup_old_tables
|
|
```
|
|
|
|
```php
|
|
// Optional: Diese Tabellen könnten gelöscht werden wenn nicht mehr gebraucht:
|
|
// - user_provider_credentials (alte Struktur)
|
|
// - user_budgets (für users, nicht gateway_users)
|
|
// - llm_requests (wenn usage_logs konsolidiert ist)
|
|
// - budgets (wenn Budget jetzt in gateway_users ist)
|
|
|
|
// Oder umbenennen für Archiv:
|
|
Schema::rename('user_provider_credentials', 'old_user_provider_credentials');
|
|
Schema::rename('user_budgets', 'old_user_budgets');
|
|
Schema::rename('llm_requests', 'old_llm_requests');
|
|
Schema::rename('budgets', 'old_budgets');
|
|
```
|
|
|
|
---
|
|
|
|
|
|
## ⏱️ Implementierungs-Zeitplan
|
|
|
|
### Tag 1-2: Datenbank & Models
|
|
- ✅ Migration 1: Budget-Felder zu gateway_users
|
|
- ✅ Migration 2: Gateway User Credentials Tabelle
|
|
- ✅ Migration 3: Usage Logs anpassen
|
|
- ✅ GatewayUser Model erweitern
|
|
- ✅ GatewayUserCredential Model erstellen
|
|
- ✅ ApiKey Model anpassen (falls nötig)
|
|
|
|
### Tag 3: Authentication System
|
|
- ✅ ApiKeyGuard implementieren
|
|
- ✅ AuthServiceProvider erweitern
|
|
- ✅ config/auth.php anpassen
|
|
- ✅ Middleware anpassen (CheckBudget, CheckRateLimit)
|
|
- ✅ Testing: API-Key Authentication
|
|
|
|
### Tag 4: API Services anpassen
|
|
- ✅ GatewayService umbauen
|
|
- ✅ RequestLogger anpassen
|
|
- ✅ routes/api.php anpassen
|
|
- ✅ ChatCompletionController anpassen
|
|
- ✅ Testing: Chat Completions Endpoint
|
|
|
|
### Tag 5: Admin-Interface
|
|
- ✅ GatewayUserCredentialController erstellen
|
|
- ✅ Views für Credential-Management
|
|
- ✅ Routes hinzufügen
|
|
- ✅ GatewayUserController anpassen (Budget-Felder)
|
|
- ✅ Testing: Admin-Interface
|
|
|
|
### Tag 6: Testing & Cleanup
|
|
- ✅ Integration Tests schreiben
|
|
- ✅ Ende-zu-Ende Testing
|
|
- ✅ Alte Tabellen archivieren/entfernen
|
|
- ✅ Dokumentation finalisieren
|
|
|
|
**Geschätzter Aufwand:** 6 Arbeitstage
|
|
|
|
---
|
|
|
|
## 📝 Checkliste
|
|
|
|
### Datenbank
|
|
- [ ] Migration: Budget-Felder zu gateway_users
|
|
- [ ] Migration: Gateway User Credentials Tabelle erstellen
|
|
- [ ] Migration: Usage Logs anpassen
|
|
- [ ] Migration: Alte Tabellen archivieren
|
|
- [ ] php artisan migrate
|
|
|
|
### Models
|
|
- [ ] GatewayUser Model erweitern (Authenticatable)
|
|
- [ ] GatewayUserCredential Model erstellen
|
|
- [ ] UsageLog Model anpassen (gateway_user_id)
|
|
|
|
### Authentication
|
|
- [ ] ApiKeyGuard implementieren
|
|
- [ ] AuthServiceProvider registrieren
|
|
- [ ] config/auth.php anpassen
|
|
|
|
### API
|
|
- [ ] routes/api.php: auth:api statt auth:sanctum
|
|
- [ ] GatewayService: GatewayUser statt User
|
|
- [ ] GatewayService: GatewayUserCredential verwenden
|
|
- [ ] RequestLogger: gateway_user_id verwenden
|
|
- [ ] Middleware: CheckBudget anpassen
|
|
- [ ] Middleware: CheckRateLimit anpassen
|
|
|
|
### Admin-Interface
|
|
- [ ] GatewayUserCredentialController erstellen
|
|
- [ ] Routes für Credential-Management hinzufügen
|
|
- [ ] Views erstellen für Credential-Management
|
|
- [ ] GatewayUserController: Budget-Felder anpassen
|
|
|
|
### Testing
|
|
- [ ] API-Key Authentication testen
|
|
- [ ] Chat Completions Endpoint testen
|
|
- [ ] Budget-System testen
|
|
- [ ] Rate-Limiting testen
|
|
- [ ] Admin-Interface testen
|
|
|
|
---
|
|
|
|
|
|
## 📊 Finale Architektur (Übersicht)
|
|
|
|
### User-Typen & Zugriff
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────────┐
|
|
│ Laravel LLM Gateway │
|
|
├─────────────────────────────────────────────────────────────┤
|
|
│ │
|
|
│ ┌──────────────────┐ ┌──────────────────┐ │
|
|
│ │ Admin Users │ │ Gateway Users │ │
|
|
│ │ (users table) │ │ (gateway_users) │ │
|
|
│ ├──────────────────┤ ├──────────────────┤ │
|
|
│ │ • Web-Interface │ │ • API-Zugriff │ │
|
|
│ │ • Session Auth │ │ • API-Key Auth │ │
|
|
│ │ • Management │ │ • Chat Requests │ │
|
|
│ └──────────────────┘ └──────────────────┘ │
|
|
│ │ │ │
|
|
│ │ │ │
|
|
│ ▼ ▼ │
|
|
│ ┌──────────────────┐ ┌──────────────────┐ │
|
|
│ │ Web-Interface │ │ REST API │ │
|
|
│ │ /dashboard │ │ /api/chat/... │ │
|
|
│ │ /gateway-users │ │ │ │
|
|
│ │ /budgets │ │ Auth: API-Key │ │
|
|
│ │ /usage-logs │ │ Guard: 'api' │ │
|
|
│ └──────────────────┘ └──────────────────┘ │
|
|
│ │
|
|
└─────────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
### Datenfluss: API-Request
|
|
|
|
```
|
|
1. Externe App sendet Request:
|
|
POST /api/chat/completions
|
|
Authorization: Bearer sk-gw-abc123xyz...
|
|
|
|
2. ApiKeyGuard validiert API-Key
|
|
→ Findet GatewayUser in gateway_users
|
|
|
|
3. Middleware: CheckBudget
|
|
→ Prüft gateway_user.monthly_budget_limit
|
|
→ Prüft gateway_user.current_month_spending
|
|
→ Prüft gateway_user.blocked
|
|
|
|
4. Middleware: CheckRateLimit
|
|
→ Prüft gateway_user.rate_limit_per_hour
|
|
|
|
5. ChatCompletionController
|
|
→ Empfängt GatewayUser
|
|
|
|
6. GatewayService
|
|
→ Holt GatewayUserCredential (Provider API-Key)
|
|
→ Ruft LLM-Provider auf
|
|
→ Berechnet Kosten
|
|
|
|
7. RequestLogger
|
|
→ Speichert in usage_logs (gateway_user_id)
|
|
|
|
8. Budget Update
|
|
→ Erhöht gateway_user.current_month_spending
|
|
|
|
9. Response zurück an externe App
|
|
```
|
|
|
|
---
|
|
|
|
## 🔧 Verwendungs-Beispiele
|
|
|
|
### Admin erstellt Gateway-User
|
|
|
|
**Im Admin-Interface:**
|
|
```
|
|
1. Login als Admin (users Tabelle)
|
|
2. Navigate zu: /gateway-users/create
|
|
3. Erstelle Gateway-User:
|
|
- User ID: "app-customer-123"
|
|
- Alias: "Customer App"
|
|
- Budget: 100.00 EUR
|
|
- Rate Limit: 60 requests/hour
|
|
4. Gateway-User wird in gateway_users gespeichert
|
|
```
|
|
|
|
### Admin konfiguriert Provider-Credentials für Gateway-User
|
|
|
|
**Im Admin-Interface:**
|
|
```
|
|
1. Navigate zu: /admin/gateway-users/app-customer-123/credentials
|
|
2. Klick auf "Add Credential"
|
|
3. Wähle Provider: "OpenAI"
|
|
4. API-Key: "sk-proj-..."
|
|
5. Speichern
|
|
6. GatewayUserCredential wird erstellt (verschlüsselt)
|
|
```
|
|
|
|
### Admin generiert API-Key für Gateway-User
|
|
|
|
**Im Admin-Interface:**
|
|
```
|
|
1. Navigate zu: /api-keys
|
|
2. Klick auf "Create API Key"
|
|
3. Wähle Gateway-User: "app-customer-123"
|
|
4. Name: "Production Key"
|
|
5. System generiert:
|
|
- Full Key: "sk-gw-1234567890abcdef..."
|
|
- Prefix: "sk-gw-1234"
|
|
- Hash wird gespeichert
|
|
6. WICHTIG: Zeige Full Key nur EINMAL an!
|
|
```
|
|
|
|
### Externe App verwendet die API
|
|
|
|
**Request:**
|
|
```bash
|
|
curl -X POST http://localhost/api/chat/completions \
|
|
-H "Authorization: Bearer sk-gw-1234567890abcdef..." \
|
|
-H "Content-Type: application/json" \
|
|
-d '{
|
|
"provider": "openai",
|
|
"model": "gpt-4",
|
|
"messages": [
|
|
{"role": "user", "content": "Hello, how are you?"}
|
|
]
|
|
}'
|
|
```
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"success": true,
|
|
"request_id": 12345,
|
|
"provider": "openai",
|
|
"model": "gpt-4",
|
|
"content": "I'm doing well, thank you for asking! ...",
|
|
"role": "assistant",
|
|
"finish_reason": "stop",
|
|
"usage": {
|
|
"prompt_tokens": 15,
|
|
"completion_tokens": 20,
|
|
"total_tokens": 35
|
|
},
|
|
"cost": {
|
|
"prompt_cost": 0.00045,
|
|
"completion_cost": 0.0012,
|
|
"total_cost": 0.00165
|
|
},
|
|
"response_time_ms": 1234
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
|
|
## ✅ Vorteile dieser Architektur
|
|
|
|
1. **Klare Trennung**
|
|
- Admins (users) vs. API-Clients (gateway_users)
|
|
- Verschiedene Auth-Mechanismen (Session vs. API-Key)
|
|
- Verschiedene Use-Cases
|
|
|
|
2. **Sicherheit**
|
|
- API-Keys sind verschlüsselt und gehashed
|
|
- Provider-Credentials sind verschlüsselt
|
|
- Budget-Limits pro Gateway-User
|
|
- Rate-Limiting pro Gateway-User
|
|
|
|
3. **Skalierbarkeit**
|
|
- Jeder Gateway-User kann eigene Provider-Credentials haben
|
|
- Oder zentrale Admin-Credentials nutzen
|
|
- Flexible Budget-Verwaltung
|
|
|
|
4. **Management**
|
|
- Admins haben volle Kontrolle
|
|
- Können Gateway-Users blockieren
|
|
- Können Budgets anpassen
|
|
- Sehen alle Usage-Logs
|
|
|
|
5. **Standard-Laravel**
|
|
- Nutzt Laravel Guards (Standard-Mechanismus)
|
|
- Nutzt Eloquent Models
|
|
- Nutzt Laravel Migrations
|
|
- Gut dokumentiert und wartbar
|
|
|
|
---
|
|
|
|
## ⚠️ Potenzielle Herausforderungen
|
|
|
|
1. **Custom Guard**
|
|
- Etwas mehr Code als Sanctum
|
|
- Braucht sorgfältiges Testing
|
|
|
|
**Mitigation:** Guard ist einfach und klar strukturiert
|
|
|
|
2. **API-Key Management**
|
|
- API-Keys müssen sicher gespeichert werden
|
|
- Nur einmal anzeigbar
|
|
|
|
**Mitigation:** Standard-Pattern (wie GitHub, AWS, etc.)
|
|
|
|
3. **Migration von existierendem Code**
|
|
- Mehrere Dateien müssen angepasst werden
|
|
- Testing ist wichtig
|
|
|
|
**Mitigation:** Systematischer Plan (6 Tage)
|
|
|
|
---
|
|
|
|
## 🎯 Zusammenfassung
|
|
|
|
### Problem erkannt:
|
|
- ❌ API-Code verwendet `users` (für Admins gedacht)
|
|
- ❌ Admin-Interface verwaltet `gateway_users` (für API-Clients)
|
|
- ❌ Keine Integration zwischen beiden
|
|
|
|
### Lösung:
|
|
- ✅ API-Code auf `gateway_users` umstellen
|
|
- ✅ Custom Guard für API-Key Authentication
|
|
- ✅ Admin-Interface für Gateway-User Credentials
|
|
- ✅ Klare Architektur: Admins vs. API-Clients
|
|
|
|
### Architektur:
|
|
```
|
|
users (Admins)
|
|
└── Web-Interface (Session Auth)
|
|
└── Verwalten gateway_users
|
|
|
|
gateway_users (API-Clients)
|
|
├── API-Key Authentication
|
|
├── gateway_user_credentials (Provider API-Keys)
|
|
├── Budget & Rate Limits
|
|
└── usage_logs
|
|
```
|
|
|
|
### Aufwand:
|
|
- **6 Arbeitstage** für vollständige Umsetzung
|
|
- Systematischer Plan vorhanden
|
|
- Klare Checkliste
|
|
|
|
### Ergebnis:
|
|
- Funktionierende API
|
|
- Saubere Architektur
|
|
- Standard Laravel-Patterns
|
|
- Wartbar und erweiterbar
|
|
|
|
---
|
|
|
|
## 🚀 Nächste Schritte
|
|
|
|
**Option A: Sofort starten (Empfohlen)**
|
|
1. Migrations erstellen und ausführen
|
|
2. Models anpassen
|
|
3. ApiKeyGuard implementieren
|
|
4. API-Code anpassen
|
|
5. Admin-Interface erweitern
|
|
6. Testing
|
|
|
|
**Option B: Erst Prototyp**
|
|
1. Nur Migrations und Models
|
|
2. Minimal-Implementierung für Testing
|
|
3. Dann entscheiden ob weitermachen
|
|
|
|
**Option C: Review und Anpassungen**
|
|
1. Plan reviewen
|
|
2. Anpassungen vornehmen
|
|
3. Dann starten
|
|
|
|
---
|
|
|
|
## 📎 Wichtige Dateien (Neu/Geändert)
|
|
|
|
### Neu erstellen:
|
|
- `app/Auth/ApiKeyGuard.php`
|
|
- `app/Models/GatewayUserCredential.php`
|
|
- `app/Http/Controllers/Admin/GatewayUserCredentialController.php`
|
|
- `database/migrations/*_add_budget_fields_to_gateway_users.php`
|
|
- `database/migrations/*_create_gateway_user_credentials_table.php`
|
|
- `database/migrations/*_update_usage_logs_for_gateway_users.php`
|
|
|
|
### Anpassen:
|
|
- `app/Models/GatewayUser.php` (Authenticatable implementieren)
|
|
- `app/Services/LLM/GatewayService.php` (GatewayUser statt User)
|
|
- `app/Services/LLM/RequestLogger.php` (gateway_user_id)
|
|
- `app/Http/Middleware/CheckBudget.php`
|
|
- `app/Http/Middleware/CheckRateLimit.php`
|
|
- `app/Providers/AuthServiceProvider.php`
|
|
- `config/auth.php`
|
|
- `routes/api.php`
|
|
- `routes/web.php`
|
|
|
|
---
|
|
|
|
**Dokument-Ende**
|
|
|
|
*Erstellt am: 2025-11-18*
|
|
*Status: BEREIT FÜR IMPLEMENTIERUNG*
|
|
*Geschätzter Aufwand: 6 Arbeitstage*
|