Zum Hauptinhalt springen

AI Enhancement Commands

Commands für AI-gesteuerte Profil-Anreicherung: Spracherkennung, Kategorisierung und Tag-Konsolidierung. Alle AI-Commands nutzen die Gemini API und teilen sich die ai-detection Queue mit Rate-Limiting.


social:detect-languages

Erkennt Sprache, Land und Kategorie von Social Profiles via Gemini AI. Verarbeitet Profile entweder synchron oder dispatcht Jobs asynchron in die ai-detection Queue.

social:detect-languages
{--limit=100 : Maximale Anzahl Profile (synchron)}
{--force : Auch bereits erkannte Profile neu erkennen}
{--profile= : Bestimmte Profile-ID}
{--queue : Jobs in Queue dispatchen statt synchron}
{--queue-limit=500 : Maximale Profile pro Queue-Run}
{--flush : Alle pending ai-detection Jobs entfernen}

Beispiele

# Synchrone Verarbeitung (Development/Debugging)
php artisan social:detect-languages --limit=50

# Asynchron in Queue dispatchen (Production)
php artisan social:detect-languages --queue --queue-limit=100

# Bestimmtes Profil force-redetecten
php artisan social:detect-languages --profile=12345 --force

# Queue komplett leeren
php artisan social:detect-languages --flush

Optionen

OptionBeschreibung
--limit=100Max Profile (synchroner Modus)
--forceRe-Detection trotz vorhandener Daten
--profile=Einzelnes Profil via ID
--queueQueue-Modus (async via DetectProfileLanguage Jobs)
--queue-limit=500Max Profile pro Queue-Dispatch
--flushAlle pending Jobs aus ai-detection Queue löschen

Interne Schritte (Queue-Modus)

  1. Hard-Cap prüfen: Queue-Tiefe gegen Limit prüfen. Überspringen wenn voll
  2. Profile laden: Sortiert nach Follower-Anzahl (populäre Profile zuerst)
  3. Filter: Nur Profile mit Description, ohne Sprach-/Land-Daten, außerhalb Cooldown-Periode. Geblockte, sanitized und archivierte Profile werden ausgeschlossen (notExcluded() Scope)
  4. Dispatch: DetectProfileLanguage Jobs in ai-detection Queue
  5. Rate-Limiting: Job-Middleware RateLimited('gemini-api') begrenzt auf 15 Requests/Minute. Rate-limited Jobs werden mit Delay zurueck in die Queue gelegt (nicht geloescht). ShouldBeUnique (30-Min Lock via uniqueFor()) verhindert Duplikate

Interne Schritte (Synchron-Modus)

  1. Profile laden (nach denselben Kriterien wie Queue-Modus)
  2. ChannelLanguageDetector::detect() pro Profil aufrufen
  3. Ergebnisse (detected_country, detected_language, detected_at) in DB speichern
  4. 250ms Pause zwischen Requests (4 RPS max)

Cooldown-System

Profile die kürzlich erkannt wurden, werden übersprungen:

  • Default Cooldown: 365 Tage (services.gemini.cooldown_days)
  • Mit --force wird der Cooldown ignoriert

Schedule

Alle 15 Minuten mit --queue und dynamischem --queue-limit (= hourly_limit / 4). Das Hourly-Limit kommt aus AI_ENHANCER_HOURLY_LIMIT (Default: 100).

Location: app/Console/Commands/DetectChannelLanguages.php


ai:prune-logs

Löscht fehlgeschlagene AI Detection Logs nach dem Retention-Fenster. Erfolgreiche Detections bleiben permanent erhalten, da sie wertvolle Sprach-/Land-Daten enthalten.

ai:prune-logs
{--days=7 : Aufbewahrungsdauer für fehlgeschlagene Logs}

Beispiele

# Standard: Failed Logs älter als 7 Tage löschen
php artisan ai:prune-logs

# Kürzere Retention
php artisan ai:prune-logs --days=3

# Längere Retention
php artisan ai:prune-logs --days=14

Optionen

OptionBeschreibung
--days=7Anzahl Tage die Failed Logs behalten werden

Interne Schritte

  1. AiDetectionLog Records mit status = 'error' selektieren
  2. Filter: created_at älter als angegebene Tage
  3. Records löschen
  4. Gelöschte Anzahl ausgeben

Hinweise

  • Nur error-Logs werden gelöscht -- erfolgreiche Detections bleiben permanent
  • Typische Fehler: API-Timeouts, Rate-Limiting, ungültige Responses
  • Die AiDetectionLog Tabelle speichert Request/Response für Debugging

Schedule

2x täglich: 02:30 und 14:30 UTC mit .withoutOverlapping().

Location: app/Console/Commands/PruneAiDetectionLogs.php


tags:consolidate

Wöchentliche AI-gesteuerte Tag-Konsolidierung via Gemini. Aggregiert alle AI-generierten Tags, identifiziert Low-Use-Tags und dispatcht Chunks zur Gemini-Analyse.

tags:consolidate
{--dry-run : Vorschau ohne Dispatch}
{--force : Cooldown ignorieren, alle Tags verarbeiten}
{--chunk-size= : Chunk-Größe überschreiben}
{--max-chunks= : Maximale Chunk-Anzahl überschreiben}

Beispiele

# Standard-Lauf (nur Low-Use Tags, Cooldown beachten)
php artisan tags:consolidate

# Vorschau: welche Chunks würden dispatched
php artisan tags:consolidate --dry-run

# Alle Tags verarbeiten, Cooldown ignorieren
php artisan tags:consolidate --force

# Angepasste Chunk-Größe
php artisan tags:consolidate --chunk-size=30 --max-chunks=50

Optionen

OptionBeschreibung
--dry-runZeigt Chunks ohne zu dispatchen
--forceCooldown ignorieren, alle Kandidaten verarbeiten
--chunk-size=Override für postbox.tag_consolidation.chunk_size (Default: 50)
--max-chunks=Override für postbox.tag_consolidation.max_chunks (Default: 100)

Interne Schritte

  1. Guard: Queue-Tiefe prüfen (ai-detection Queue, max = max_chunks * 3)
  2. Tags aggregieren: Alle ai_keywords aus social_profiles (JSONB) zählen
  3. Filtern: Blocked Tags, Stop Words, zu kurze Tags (< 3 Zeichen)
  4. Split: Tags in "established" (>= 5 Profile) und "candidates" (< 5 Profile) aufteilen
  5. Cooldown: Kürzlich konsolidierte Tags überspringen (90 Tage Default)
  6. Chunking: Candidates in Chunks aufteilen (max 50 Tags/Chunk, max 100 Chunks)
  7. Dispatch: ConsolidateTagChunk Jobs auf ai-detection Queue mit Batch-ID

Konsolidierungs-Aktionen

Gemini analysiert jeden Chunk und schlägt Aktionen vor:

AktionBeschreibung
mergeTag in einen bestehenden "established" Tag mergen
suppressTag auf Blocklist setzen (irrelevant)
keepTag behalten (keine Aktion)

High-Confidence Merges werden automatisch ausgeführt; Low-Confidence gehen an Admin-Review.

Konfiguration

// config/postbox.php → tag_consolidation
'chunk_size' => env('TAG_CONSOLIDATION_CHUNK_SIZE', 50),
'max_chunks' => env('TAG_CONSOLIDATION_MAX_CHUNKS', 100),
'established_threshold' => 5,
'min_tag_length' => 3,
'consolidation_cooldown_days' => 90,
'rate_per_minute' => 10,

Schedule

Sonntags 18:00 UTC (Low-Traffic Window) mit .withoutOverlapping().

Location: app/Console/Commands/ConsolidateTags.php


ai:retry-failed

Setzt permanent fehlgeschlagene AI-Detections zurück und dispatched sie erneut. Profile mit ai_detection_failed_at Marker werden für einen neuen Versuch freigegeben.

ai:retry-failed
{--limit=50 : Maximale Anzahl Profile pro Retry}
{--dry-run : Vorschau ohne Dispatch}

Optionen

OptionTypDefaultBeschreibung
--limit=int50Maximale Anzahl Profile pro Lauf
--dry-runflagNur Vorschau, keine Jobs dispatchen

Beispiele

# Standard: bis zu 50 fehlgeschlagene Profile retrien
php artisan ai:retry-failed

# Größerer Batch
php artisan ai:retry-failed --limit=200

# Vorschau: welche Profile wären betroffen?
php artisan ai:retry-failed --dry-run

Interne Logik

  1. Profile mit ai_detection_failed_at IS NOT NULL finden
  2. Filter: tracking_enabled = true, notExcluded() (nicht blocked/sanitized/archived)
  3. Queue-Tiefe prüfen: Überspringt wenn Queue ai-detection bereits > max($limit, 100) Jobs hat (verhindert Job-Snowballing)
  4. ai_detection_failed_at auf null zurücksetzen
  5. DetectProfileLanguage Job dispatchen

Schedule

Wöchentlich sonntags um 08:00 UTC.

Location: app/Console/Commands/RetryFailedAiDetection.php


keywords:update-stopwords

Analysiert Keyword-Verteilung und aktualisiert die dynamische Stopword-Liste. Keywords die in zu vielen Profilen vorkommen (z.B. "Official", "Music") werden als Stopwords markiert.

keywords:update-stopwords
{--threshold=30 : Prozent-Schwelle — Keywords in mehr als X% der Profile werden Stopwords}
{--min-profiles=100 : Mindestanzahl Profile mit Keywords bevor die Analyse startet}
{--dry-run : Vorschau ohne Speicherung}

Optionen

OptionTypDefaultBeschreibung
--threshold=int30Prozent-Schwelle für Stopword-Erkennung
--min-profiles=int100Mindestanzahl Profile bevor Analyse sinnvoll ist
--dry-runflagNur Vorschau, keine Cache-Aktualisierung

Beispiele

# Standard: Stopwords für Keywords in > 30% aller Profile
php artisan keywords:update-stopwords

# Strengerer Schwellwert (> 20%)
php artisan keywords:update-stopwords --threshold=20

# Vorschau der erkannten Stopwords
php artisan keywords:update-stopwords --dry-run

Interne Logik

  1. Anzahl Profile mit Keywords zählen — überspringt wenn < --min-profiles
  2. Keywords via LOWER(jsonb_array_elements_text(ai_keywords)) extrahieren (PostgreSQL-spezifisch)
  3. Keywords in > threshold% der Profile als Stopwords identifizieren
  4. Dynamische Stopword-Liste in Redis cachen (keyword_dynamic_stopwords, TTL: 8 Tage)
  5. Heartbeat in Cache für Health-Monitoring setzen

Hinweise

  • PostgreSQL-spezifisch: Verwendet jsonb_array_elements_text() — funktioniert nicht mit SQLite
  • Die dynamischen Stopwords ergänzen die statische Liste in der Keyword-Extraktion
  • 8-Tage TTL stellt sicher, dass die Liste auch bei verpasstem Wochenlauf noch gültig ist

Schedule

Wöchentlich sonntags um 02:00 UTC.

Location: app/Console/Commands/UpdateKeywordStopwords.php


AI Enhancer Re-Run für gezielte Profile

Anleitung zum gezielten Re-Run der AI Enhancement auf bestimmte Profilgruppen (z.B. PRO-Profile, Favoriten). Nützlich nach Feature-Updates (neue Felder, bessere Prompts), um bestehende Profile mit der aktuellen AI-Logik neu zu verarbeiten.

Konzept

Die AI Enhancement wird über das detected_at Feld gesteuert. Profile mit detected_at innerhalb des Cooldowns (default 365 Tage) werden übersprungen. Um ein Re-Run zu erzwingen:

  1. detected_at auf NULL setzen → Profile erscheinen als "noch nicht erkannt"
  2. social:detect-languages --queue dispatcht sie in die Queue

Re-Run auf PRO-Profile

PRO-Profile sind via social_profiles.pro_enabled = true markiert.

-- Vorschau: Wie viele PRO-Profile betroffen?
SELECT COUNT(*) FROM social_profiles
WHERE pro_enabled = true
AND tracking_enabled = true
AND blocked_at IS NULL
AND sanitized_at IS NULL
AND archived_at IS NULL
AND detected_at IS NOT NULL;

-- Reset: detected_at auf NULL setzen
UPDATE social_profiles
SET detected_at = NULL, updated_at = NOW()
WHERE pro_enabled = true
AND tracking_enabled = true
AND blocked_at IS NULL
AND sanitized_at IS NULL
AND archived_at IS NULL
AND detected_at IS NOT NULL;

Re-Run auf Favoriten-Profile

Favoriten-Profile liegen in Workspaces mit name = 'Favoriten' (dem automatisch erstellten Favorites-Workspace pro User).

-- Vorschau: Wie viele Favoriten-Profile betroffen?
SELECT COUNT(DISTINCT sp.id) FROM social_profiles sp
JOIN watcher_sources ws ON ws.social_profile_id = sp.id
JOIN watchers w ON w.id = ws.watcher_id
JOIN workspaces wk ON wk.id = w.workspace_id
WHERE wk.name = 'Favoriten'
AND sp.tracking_enabled = true
AND sp.blocked_at IS NULL
AND sp.sanitized_at IS NULL
AND sp.archived_at IS NULL
AND sp.detected_at IS NOT NULL;

-- Reset: detected_at auf NULL setzen
UPDATE social_profiles
SET detected_at = NULL, updated_at = NOW()
WHERE id IN (
SELECT DISTINCT sp.id FROM social_profiles sp
JOIN watcher_sources ws ON ws.social_profile_id = sp.id
JOIN watchers w ON w.id = ws.watcher_id
JOIN workspaces wk ON wk.id = w.workspace_id
WHERE wk.name = 'Favoriten'
AND sp.tracking_enabled = true
AND sp.blocked_at IS NULL
AND sp.sanitized_at IS NULL
AND sp.archived_at IS NULL
AND sp.detected_at IS NOT NULL
);

Re-Run auf PRO + Favoriten kombiniert

UPDATE social_profiles
SET detected_at = NULL, updated_at = NOW()
WHERE tracking_enabled = true
AND blocked_at IS NULL
AND sanitized_at IS NULL
AND archived_at IS NULL
AND detected_at IS NOT NULL
AND (
pro_enabled = true
OR id IN (
SELECT DISTINCT ws.social_profile_id
FROM watcher_sources ws
JOIN watchers w ON w.id = ws.watcher_id
JOIN workspaces wk ON wk.id = w.workspace_id
WHERE wk.name = 'Favoriten'
)
);

Nach dem SQL-Reset

Der reguläre 15-Minuten-Scheduler (social:detect-languages --queue) pickt die Profile automatisch auf. Für schnellere Verarbeitung:

# Sofort einen größeren Batch dispatchen
php artisan social:detect-languages --queue --queue-limit=500

# Oder mit --force für alle (auch nicht-resetted) Profile
php artisan social:detect-languages --queue --queue-limit=200 --force

Monitoring

# Queue-Tiefe prüfen
php artisan queue:monitor ai-detection

# AI Detection Stats im Admin Dashboard
# → /admin/ai-enhancements

Hinweis: Das Daily Limit (AI_ENHANCER_DAILY_LIMIT, default 1400) gilt weiterhin. Bei großen Re-Runs verteilt sich die Verarbeitung über mehrere Tage.


AI Queue Worker

Alle drei AI-Commands teilen sich den ai-detection Queue Worker:

php8.4 artisan queue:work database --sleep=5 --daemon --quiet --timeout=60 --tries=3 --queue=ai-detection

Rate-Limiting erfolgt ueber Job-Middleware (RateLimited('gemini-api') mit 15 RPM). Rate-limited Jobs werden automatisch mit Delay zurueck in die Queue gelegt und bei naechster Gelegenheit verarbeitet. ShouldBeUnique mit 30-Minuten-Lock verhindert, dass der Scheduler Duplikate fuer dasselbe Profil dispatcht, waehrend ein verzoegerter Job noch aussteht.