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

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*