Files
laravel-llm-gateway/ARCHITEKTUR.md
wtrinkl c149bdbdde Architektur-Analyse und Korrektur-Konzept
- 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)
2025-11-18 23:42:29 +01:00

32 KiB

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:

// 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:

// 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

// 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)

-- 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

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:

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:

'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

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:

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:

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

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

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

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:

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

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

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

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:

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

php artisan make:migration add_budget_fields_to_gateway_users
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

php artisan make:migration create_gateway_user_credentials_table

(Wie oben bereits dokumentiert)

Migration 3: Usage Logs anpassen

php artisan make:migration update_usage_logs_for_gateway_users
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

php artisan make:migration cleanup_old_tables
// 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:

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:

{
  "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