2. Systemarchitektur
Die Systemarchitektur muss zwei Anforderungen erfüllen: Sie soll einfach genug sein, um schnell produktiv zu werden, aber sauber genug, um später mehrere Plattformen, unterschiedliche Content-Formate und kontrollierte Interaktionen zu unterstützen.
Der wichtigste Grundsatz lautet:
Erzeugen, Prüfen, Freigeben und Veröffentlichen sind getrennte Schritte.
Das verhindert, dass ein fehlerhafter KI-Text direkt veröffentlicht wird. Außerdem bleibt jeder Beitrag nachvollziehbar: Woher kam er, wann wurde er erzeugt, welche Prüfung hat er bestanden, wer oder was hat ihn freigegeben, wann wurde er veröffentlicht?
2.1 Gesamtübersicht
Das System besteht aus einer Pipeline.
Content-Quelle
↓
Content-Generator
↓
Quality-Gate
↓
Review-Queue
↓
Publisher
↓
Plattform
↓
Logging / Screenshot / Statusupdate
Für Interaktionen gilt eine ähnliche Pipeline:
Plattform-Beitrag oder Kommentar
↓
Relevanzprüfung
↓
Kommentarvorschlag
↓
Quality-Gate
↓
Freigabe oder Ablehnung
↓
Veröffentlichung
↓
Logging / Screenshot / Statusupdate
Die Architektur trennt also zwischen zwei Hauptbereichen:
Eigene Inhalte veröffentlichen
Auf fremde oder eigene Interaktionen reagieren
Eigene Inhalte können stärker automatisiert werden. Interaktionen bleiben vorsichtiger, weil sie kontextabhängiger sind.
2.2 Komponenten
Die Anwendung besteht aus mehreren klar getrennten Komponenten.
Config
OpenAIClient
ContentGenerator
QualityGate
ReviewQueue
Publisher
PlatformAdapter
InteractionWorker
Scheduler
Logger
Storage
SafetyGuard
Config
Die Konfiguration enthält Plattformen, Limits, Dateipfade, Zeitfenster und Betriebsmodus.
Beispiel:
mode: "dry_run"
limits:
max_posts_per_day: 1
max_comments_per_day: 3
max_likes_per_day: 10
platforms:
linkedin:
enabled: true
session_path: "storage/sessions/linkedin.json"
screenshots_path: "storage/screenshots/linkedin"
instagram:
enabled: false
session_path: "storage/sessions/instagram.json"
screenshots_path: "storage/screenshots/instagram"
OpenAIClient
Der OpenAI-Client kapselt alle API-Aufrufe.
Er ist zuständig für:
Texterzeugung
Textprüfung
Kürzung
Zusammenfassung
Kommentarvorschläge
Dublettenprüfung
strukturierte JSON-Ausgabe
Fehlerbehandlung
Der Rest der Anwendung sollte nicht direkt mit der OpenAI-API sprechen.
ContentGenerator
Der ContentGenerator erzeugt Entwürfe aus Eingangsdaten.
Eingangsdaten können sein:
Buchdaten
Autoreninformationen
Blogartikel
Zitate
Themenlisten
manuelle Notizen
Produktdaten
Aus diesen Daten entstehen PostDraft-Objekte.
QualityGate
Das QualityGate prüft, ob ein Beitrag veröffentlicht werden darf.
Prüfungen:
Zeichenlimit
Dubletten
verbotene Begriffe
zu werblicher Ton
fehlender Themenbezug
leerer Text
fehlender Bildpfad bei Instagram
fehlender Link bei Linkpost
generische KI-Formulierungen
Das QualityGate entscheidet nicht über Geschmack. Es verhindert technische und fachliche Mindestfehler.
ReviewQueue
Die ReviewQueue enthält Beiträge, die noch nicht veröffentlicht wurden.
Typische Status:
draft
approved
rejected
scheduled
publishing
published
failed
Die Queue ist die zentrale Kontrollstelle zwischen Texterzeugung und Veröffentlichung.
Publisher
Der Publisher verarbeitet freigegebene Beiträge.
Er entscheidet nicht selbst, wie LinkedIn, Instagram oder Facebook bedient werden. Dafür nutzt er Plattformadapter.
PlatformAdapter
Jede Plattform erhält einen eigenen Adapter.
LinkedInAdapter
InstagramAdapter
FacebookAdapter
Ein Adapter kann:
Session laden
Plattform öffnen
Postformular öffnen
Text einfügen
Bild hochladen
Beitrag veröffentlichen
Screenshot speichern
Kommentare lesen
Die gemeinsame Schnittstelle verhindert, dass Plattformlogik überall im Code verteilt wird.
InteractionWorker
Der InteractionWorker ist für Reaktionen zuständig.
Er kann:
Kommentare unter eigenen Beiträgen erfassen
Antwortvorschläge erzeugen
fremde Beiträge als Kandidaten bewerten
Interaktionen in eine Queue schreiben
Tageslimits prüfen
Interaktionen sollten nie blind veröffentlicht werden.
Scheduler
Der Scheduler startet wiederkehrende Aufgaben.
Beispiele:
morgens Beiträge erzeugen
mittags freigegebene Beiträge veröffentlichen
abends Kommentare unter eigenen Beiträgen prüfen
nachts Reports erzeugen
Für den Anfang reicht Cron. Später kann ein Python-Scheduler ergänzt werden.
Logger
Der Logger schreibt strukturierte Logs.
Nicht ausreichend:
print("Post published")
Besser:
{
"timestamp": "2026-05-11T09:30:00+02:00",
"event": "post_published",
"platform": "linkedin",
"post_id": "post_20260511_001",
"status": "success",
"screenshot": "storage/screenshots/linkedin/post_20260511_001.png"
}
Storage
Für das MVP reicht JSON.
Später ist SQLite sinnvoller.
Mögliche Speicherstruktur:
data/books.json
data/topics.json
data/drafts.json
data/approved_posts.json
data/published_posts.json
data/interactions.json
data/interaction_log.json
SafetyGuard
Der SafetyGuard stoppt das System bei kritischen Zuständen.
Beispiele:
CAPTCHA erkannt
Login abgelaufen
Plattformwarnung sichtbar
Tageslimit erreicht
zu viele Fehler hintereinander
fehlender Dry-Run bei neuem Adapter
Diese Komponente ist wichtig, weil Playwright sonst stumpf weiterarbeiten würde.
2.3 Datenfluss
Der Datenfluss für eigene Beiträge beginnt mit einer Content-Quelle.
Beispiel: books.json
[
{
"id": "book_001",
"title": "Stolz und Vorurteil",
"author": "Jane Austen",
"category": "Klassiker",
"shop_url": "https://example.com/stolz-und-vorurteil",
"description": "Ein Gesellschaftsroman über Stolz, Vorurteil, Herkunft und Heirat."
}
]
Daraus erzeugt der Generator einen Entwurf.
{
"id": "post_20260511_001",
"source_id": "book_001",
"platform": "linkedin",
"type": "book_intro",
"status": "draft",
"text": "Jane Austens Stolz und Vorurteil ist weniger eine romantische Komödie als eine präzise Gesellschaftsbeobachtung...",
"image_path": null,
"url": "https://example.com/stolz-und-vorurteil",
"created_at": "2026-05-11T09:00:00+02:00"
}
Dann prüft das QualityGate den Entwurf.
{
"post_id": "post_20260511_001",
"passed": true,
"checks": {
"length": true,
"duplicate": true,
"tone": true,
"url": true
}
}
Nach Freigabe wird daraus ein veröffentlichbarer Beitrag.
{
"id": "post_20260511_001",
"status": "approved",
"approved_at": "2026-05-11T09:10:00+02:00"
}
Der Publisher verarbeitet nur Beiträge mit passendem Status.
approved → publishing → published
Bei Fehler:
approved → publishing → failed
Nach Veröffentlichung werden Status, Screenshot und Log gespeichert.
{
"id": "post_20260511_001",
"status": "published",
"published_at": "2026-05-11T09:30:00+02:00",
"platform_url": null,
"screenshot_path": "storage/screenshots/linkedin/post_20260511_001.png"
}
2.4 Statusmodell für Beiträge
Beiträge brauchen ein klares Statusmodell.
Minimal:
draft
approved
scheduled
publishing
published
rejected
failed
Besser mit zusätzlichem Status für technische Sicherheit:
draft
quality_failed
approved
scheduled
publishing
published
rejected
failed
blocked
Status: draft
Der Beitrag wurde erzeugt, aber noch nicht freigegeben.
Erzeugt durch ContentGenerator.
Noch nicht veröffentlichbar.
Kann bearbeitet oder verworfen werden.
Status: quality_failed
Das QualityGate hat den Beitrag abgelehnt.
Gründe:
zu lang
zu kurz
generisch
Dublettenverdacht
fehlende Pflichtdaten
unpassender Ton
Status: approved
Der Beitrag darf veröffentlicht werden.
Freigabe kann erfolgen durch:
manuelle CLI-Freigabe
automatische Regel
vordefiniertes Template
Status: scheduled
Der Beitrag ist freigegeben, soll aber erst später veröffentlicht werden.
Zusätzliche Felder:
{
"scheduled_at": "2026-05-12T09:30:00+02:00"
}
Status: publishing
Der Publisher arbeitet gerade an diesem Beitrag.
Dieser Status verhindert Doppelveröffentlichungen.
approved → publishing → published
approved → publishing → failed
Status: published
Der Beitrag wurde veröffentlicht.
Pflichtdaten:
published_at
platform
screenshot_path
log_id
Falls die Plattform eine Beitrags-URL zuverlässig liefert, kann auch diese gespeichert werden.
Status: rejected
Der Beitrag wurde verworfen.
Mögliche Gründe:
inhaltlich schwach
nicht passend
doppelt
zu werblich
manuell abgelehnt
Status: failed
Die Veröffentlichung ist fehlgeschlagen.
Beispiele:
Upload fehlgeschlagen
Post-Button nicht gefunden
Session abgelaufen
Timeout
Netzwerkfehler
Status: blocked
Der Beitrag wird nicht erneut versucht, bis ein Mensch ihn prüft.
Das ist sinnvoll nach:
CAPTCHA
Plattformwarnung
Login-Problem
mehrfach fehlgeschlagenem Publishing
Beispiel als Python Enum
from enum import Enum
class PostStatus(str, Enum):
DRAFT = "draft"
QUALITY_FAILED = "quality_failed"
APPROVED = "approved"
SCHEDULED = "scheduled"
PUBLISHING = "publishing"
PUBLISHED = "published"
REJECTED = "rejected"
FAILED = "failed"
BLOCKED = "blocked"
2.5 Statusmodell für Interaktionen
Interaktionen sind riskanter als eigene Beiträge. Deshalb brauchen sie ein eigenes Statusmodell.
Eine Interaktion kann sein:
Kommentar unter eigenem Beitrag
Antwort auf Nutzerkommentar
Kommentar unter fremdem Beitrag
Like
Speichern eines Kandidaten
Minimalstatus:
candidate
analyzed
suggested
approved
executed
rejected
failed
blocked
Status: candidate
Das System hat einen möglichen Interaktionsfall gefunden.
Beispiel:
{
"id": "interaction_20260511_001",
"platform": "linkedin",
"type": "comment_reply",
"source_url": "https://linkedin.com/...",
"status": "candidate"
}
Status: analyzed
Der Kandidat wurde geprüft.
Prüfkriterien:
thematische Relevanz
geschäftliche Unbedenklichkeit
Ton
Risiko
Kontextbezug
Status: suggested
OpenAI hat eine Antwort oder einen Kommentar vorgeschlagen.
{
"status": "suggested",
"suggested_text": "Der Punkt zur Sichtbarkeit kleiner Anbieter ist im Buchmarkt ähnlich..."
}
Status: approved
Die Interaktion wurde freigegeben.
Bei fremden Beiträgen sollte das normalerweise manuell geschehen.
Status: executed
Die Interaktion wurde ausgeführt.
Gespeichert werden:
Zeitpunkt
Plattform
Art der Interaktion
Text
Screenshot
Quelle
Status: rejected
Die Interaktion wurde abgelehnt.
Gründe:
zu privat
zu politisch
zu werblich
zu generisch
kein echter Mehrwert
nicht relevant
Status: failed
Die Ausführung ist fehlgeschlagen.
Status: blocked
Die Interaktion darf nicht erneut versucht werden.
Beispiele:
Plattformwarnung
Kommentarbereich nicht verfügbar
Beitrag gelöscht
Account-Limit erreicht
Beispiel als Python Enum
from enum import Enum
class InteractionStatus(str, Enum):
CANDIDATE = "candidate"
ANALYZED = "analyzed"
SUGGESTED = "suggested"
APPROVED = "approved"
EXECUTED = "executed"
REJECTED = "rejected"
FAILED = "failed"
BLOCKED = "blocked"
2.6 Fehler- und Retry-Konzept
Automatisierung darf bei Fehlern nicht aggressiv weiterprobieren.
Ein gutes Retry-Konzept unterscheidet zwischen temporären und kritischen Fehlern.
Temporäre Fehler
Temporäre Fehler dürfen begrenzt erneut versucht werden.
Beispiele:
Timeout
kurzer Netzwerkfehler
Element noch nicht geladen
Upload dauert länger
OpenAI-Rate-Limit
Mögliche Behandlung:
kurz warten
einmal wiederholen
bei zweitem Fehler abbrechen
Status auf failed setzen
Log schreiben
Kritische Fehler
Kritische Fehler dürfen nicht automatisch wiederholt werden.
Beispiele:
Login-Seite erscheint
CAPTCHA erscheint
Plattformwarnung erscheint
Account eingeschränkt
Postformular nicht mehr auffindbar
ungewöhnliche Weiterleitung
Behandlung:
sofort stoppen
Status auf blocked setzen
Screenshot speichern
Plattform-Kill-Switch aktivieren
manuelle Prüfung erforderlich
Retry-Regeln
Sinnvolle Grundregeln:
maximal 2 Versuche pro Beitrag
keine Endlosschleifen
kein sofortiger Retry bei kritischen Fehlern
kein Retry bei CAPTCHA
kein Retry bei Login-Problemen
kein Retry bei Plattformwarnung
nach 3 Plattformfehlern Plattform deaktivieren
Beispiel Retry-Konfiguration
retry:
max_attempts_per_post: 2
max_platform_failures_per_day: 3
retry_delay_seconds: 30
critical_errors:
- "captcha_detected"
- "login_required"
- "platform_warning"
- "account_restricted"
Beispiel Fehlerklassen
class PublisherError(Exception):
pass
class TemporaryPublisherError(PublisherError):
pass
class CriticalPublisherError(PublisherError):
pass
class LoginRequiredError(CriticalPublisherError):
pass
class CaptchaDetectedError(CriticalPublisherError):
pass
class PlatformWarningError(CriticalPublisherError):
pass
Beispiel Retry-Logik
from collections.abc import Callable
def run_with_retry(action: Callable[[], None], max_attempts: int) -> None:
attempt = 1
while attempt <= max_attempts:
try:
action()
return
except CriticalPublisherError:
raise
except TemporaryPublisherError:
if attempt === max_attempts:
raise
attempt += 1
Hinweis: In Python muss der Vergleich == verwendet werden. Die obige Logik entspricht konzeptionell dem gewünschten Ablauf. Korrekt lautet die betreffende Zeile:
if attempt == max_attempts:
raise
Praktische Retry-Strategie
Für dieses Projekt reicht:
OpenAI-Aufruf: 2 Versuche
Browser-Navigation: 2 Versuche
Upload: 2 Versuche
Veröffentlichung: kein blinder zweiter Klick
Kommentarveröffentlichung: kein automatischer Retry bei unklarem Status
Besonders wichtig: Nach dem Klick auf „Veröffentlichen“ darf das System nicht einfach erneut klicken, wenn unklar ist, ob der Beitrag bereits veröffentlicht wurde. Sonst entstehen Doppelposts.
Besser:
Nach Veröffentlichungsversuch prüfen.
Screenshot speichern.
Status nur bei eindeutiger Bestätigung auf published setzen.
Bei Unsicherheit Status auf failed_manual_check setzen.
2.7 Logging und Nachvollziehbarkeit
Logging ist kein Zusatz, sondern Kernbestandteil des Systems.
Ohne Logging ist später nicht mehr nachvollziehbar:
welcher Text veröffentlicht wurde
welcher Prompt verwendet wurde
welches Modell verwendet wurde
ob ein Quality-Gate bestanden wurde
wann ein Beitrag gepostet wurde
warum ein Beitrag fehlgeschlagen ist
ob ein Retry stattgefunden hat
welcher Screenshot zum Vorgang gehört
Log-Arten
Sinnvoll sind mehrere Logtypen.
application.log
openai.log
publisher.log
interaction.log
error.log
audit.log
Für den Anfang reicht auch eine JSONL-Datei.
storage/logs/events.jsonl
Jede Zeile enthält ein vollständiges JSON-Objekt.
Beispiel:
{
"timestamp": "2026-05-11T09:30:00+02:00",
"level": "info",
"event": "post_published",
"platform": "linkedin",
"post_id": "post_20260511_001",
"status_before": "publishing",
"status_after": "published",
"screenshot_path": "storage/screenshots/linkedin/post_20260511_001.png"
}
Audit-Log
Das Audit-Log ist besonders wichtig.
Es beantwortet:
Was hat das System getan?
Wann?
Warum?
Mit welchen Eingangsdaten?
Mit welchem Ergebnis?
Für jeden Beitrag sollte gespeichert werden:
Post-ID
Quelle
Prompt-Version
OpenAI-Modell
generierter Text
Quality-Gate-Ergebnis
Freigabestatus
Veröffentlichungszeitpunkt
Plattform
Screenshot
Fehlerstatus
Prompt-Versionierung
Prompts sollten nicht nur überschrieben werden.
Besser:
prompts/linkedin_post_v1.txt
prompts/linkedin_post_v2.txt
prompts/quality_check_v1.txt
Oder mit Metadaten:
{
"prompt_name": "linkedin_post",
"prompt_version": "1.0.0",
"model": "gpt-4.1-mini"
}
So lässt sich später nachvollziehen, warum Beiträge zu einem bestimmten Zeitpunkt anders klangen.
Screenshot-Pflicht
Nach jeder wichtigen Browser-Aktion sollte ein Screenshot gespeichert werden.
Mindestens bei:
erfolgreicher Veröffentlichung
Fehler
Login-Problem
CAPTCHA
Plattformwarnung
unklarem Status
Dateinamen:
storage/screenshots/linkedin/post_20260511_001_success.png
storage/screenshots/linkedin/post_20260511_001_error.png
storage/screenshots/linkedin/post_20260511_001_blocked.png
Beispiel Logger
import json
from datetime import datetime
from pathlib import Path
from zoneinfo import ZoneInfo
from typing import Any
class JsonLogger:
def __init__(self, log_file: Path) -> None:
self.log_file = log_file
def write(self, event: str, data: dict[str, Any]) -> None:
payload: dict[str, Any] = {
"timestamp": datetime.now(ZoneInfo("Europe/Berlin")).isoformat(),
"event": event,
}
for key, value in data.items():
payload[key] = value
with self.log_file.open("a", encoding="utf-8") as file:
file.write(json.dumps(payload, ensure_ascii=False))
file.write("\n")
Mindeststandard für Nachvollziehbarkeit
Für ein produktives System sollte jeder veröffentlichte Beitrag diese Daten haben:
{
"id": "post_20260511_001",
"platform": "linkedin",
"type": "book_intro",
"source_id": "book_001",
"status": "published",
"text": "Jane Austens Stolz und Vorurteil ist weniger eine romantische Komödie...",
"created_at": "2026-05-11T09:00:00+02:00",
"approved_at": "2026-05-11T09:10:00+02:00",
"published_at": "2026-05-11T09:30:00+02:00",
"prompt_name": "linkedin_post",
"prompt_version": "1.0.0",
"model": "gpt-4.1-mini",
"quality_gate_passed": true,
"screenshot_path": "storage/screenshots/linkedin/post_20260511_001_success.png"
}
Ergebnis dieses Kapitels
Die Architektur ist damit definiert:
klare Pipeline
getrennte Komponenten
eigene Statusmodelle für Beiträge und Interaktionen
begrenzte Retry-Logik
harte Stop-Regeln
strukturierte Logs
Screenshots als Belege
nachvollziehbare Prompt- und Modellnutzung
Damit kann im nächsten Schritt das Projekt technisch aufgesetzt werden.
0 Kommentare