AIREITER

22 Plattformen, 241 Commands: Warum ich kein einheitliches Response-Modell gebaut habe

Zuletzt aktualisiert: 2026-07-31 06:22:07

Wer öffentliche Daten von mehr als zwanzig Plattformen einsammelt, greift fast automatisch zur ersten großen Abstraktion: ein gemeinsames Post-Modell, ein gemeinsames User-Modell, in das jede API-Antwort überführt wird. Ein bilibili-Video, ein tiktok-Video, eine zhihu-Antwort und ein linkedin-Beitrag wirken schließlich alle wie „Inhalt plus Autor“. Für die ersten drei Plattformen ist das angenehm. Bei der zwanzigsten wird es zur Belastung.

Deshalb habe ich am Ende kein einheitliches Modell gebaut. Bei mehr als zwanzig Plattformen und mehr als zweihundert Commands hat sich die vermeintlich simplere Aufteilung bewährt: Jede Plattform kümmert sich um sich selbst.

Warum ein gemeinsames Modell irgendwann nicht mehr hilft

Der Zusammenbruch kommt schleichend. Spätestens bei der achten Plattform hängen an Post schon ein Dutzend optionaler Felder: Manche Plattformen liefern eine Danmaku-Anzahl, andere nicht. Die „Veröffentlichungszeit“ ist hier ein Zeitstempel auf Sekundenebene, dort ein String wie „vor 3 Tagen“. Bei mehr als zwanzig Plattformen ist das Modell endgültig gekippt. Nicht mit einem Compile-Fehler, sondern weil es keinerlei Arbeit mehr spart. Jeder nachgelagerte Code muss zunächst klären, ob die jeweilige Plattform ein Feld überhaupt befüllt hat. Diese Entscheidungslogik wird länger, als die Rohantwort direkt zu lesen. Die gemeinsame Schicht wird zum Hindernis, das man umgehen muss, um weiterzuarbeiten. Auch beim Schreiben zahlt man dafür: Jede neu integrierte Plattform zwingt dazu, Felder in Strukturen zu pressen, die für frühere Plattformen zugeschnitten wurden.

Eine Plattform, ein eigener Bounded Context

Die tragfähige Aufteilung ist das Gegenteil: kein globales Modell erzwingen, sondern jede Plattform ihren eigenen Bereich verwalten lassen. Im Katalog ist jede Plattform ein <platform>_reverse/-Kontext. Er besitzt vier Dinge exklusiv:

  • Eingabevalidierung. Wie IDs auf dieser Plattform aussehen und welche Parameterkombinationen zulässig sind, weiß nur sie selbst.

  • Protokoll. Direkte HTTP-Requests oder ein lokaler JS-Signing-Ausschnitt, Domains und Header: all das bleibt plattformintern.

  • Signing. Die Signing-Mechanismen unterscheiden sich zwischen Plattformen massiv. Ein gemeinsamer Signer würde nur ein Monster aus if-else-Verzweigungen erzeugen.

  • Response-Normalisierung. Die Rohantwort wird in eine Struktur überführt, die der jeweiligen Plattform gehört – nicht in ein globales Einheitsschema.

Gerade der letzte Punkt wird leicht missverstanden. „Kein gemeinsames Modell“ heißt nicht „keine Normalisierung“. Natürlich normalisiert jede Plattform ihre Daten. Nur definiert sie ihr Zielmodell selbst, statt sich einem Shared Model unterzuordnen. Vereinheitlichung ist dort sinnvoll, wo wirklich dasselbe Objekt vorliegt: Wenn zwei Endpoints innerhalb einer Plattform dieselbe Post-Struktur teilen, ist das legitim. Der Fehler beginnt, wenn diese interne Vereinheitlichung über Plattformgrenzen hinweg gezogen wird.

In die Shared Layer gehört nur echte Gemeinsamkeit

Was darf also in die gemeinsame Schicht? Nur Fähigkeiten, die auf jeder Plattform tatsächlich gleich funktionieren – nicht Dinge, die sich oberflächlich ähneln. Meine Shared Layer enthält genau drei Bausteine:

  1. Das Read-Model der Schnittstelle. Aus den argparse-Deklarationen jeder Plattform entsteht ein gemeinsamer Capability-Katalog. Vereinheitlicht wird damit, wie Commands gefunden und beschrieben werden, nicht was sie zurückgeben. Ersteres ist plattformübergreifend, Letzteres bleibt privat. Die Idee „Deklaration ist die Schnittstelle“ erläutert der Beitrag zu Interface-as-Code.

  2. Lokaler Loopback-Transport. Authentifizierte Requests laufen über einen lokalen WebSocket-Session-Service. Er behandelt alle Plattformen gleich, ohne eines ihrer Business-Felder anzufassen.

  3. Dispatch-Einstieg. Plattform erkennen, Command an ihren Kontext übergeben – und nicht mehr.

Der Test ist einfach: Etwas gehört nur dann in die Shared Layer, wenn es sich auf jeder Plattform tatsächlich gleich verhält. Transport, Dispatch und die Erzeugung von Schnittstellenbeschreibungen erfüllen das. „Ein Inhalt“ dagegen verhält sich auf bilibili völlig anders als auf linkedin und gehört deshalb nicht hinein. Ähnlichkeit ist die größte Abstraktionsfalle: Zwei Videos sehen ähnlich aus, also möchte man sie vereinheitlichen. Aber visuelle Ähnlichkeit ist keine Verhaltensgleichheit. Sie trotzdem als gemeinsames Domain-Modell zu behandeln, ist die Ursache für den Kollaps des Einheitsschemas.

Die Command-Verteilung zeigt, wo sich Abstraktion lohnt

Wer beim gemeinsamen Modell noch unsicher ist, sollte auf die tatsächliche Verteilung der Commands schauen. 22 Plattformen, 241 Commands – extrem ungleich verteilt:

Plattform

Commands

tiktok

34

bilibili

26

linkedin

18

zhihu

18

douyin

17

xiaohongshu

16

Die übrigen 16 Plattformen

jeweils 1 bis 13

Die sechs größten Plattformen kommen zusammen auf 129 Commands, also mehr als die Hälfte aller Commands. Die andere Hälfte verteilt sich auf 16 Long-Tail-Plattformen – viele mit nur zwei oder drei Commands, manche sogar mit genau einem.

Diese Verteilung bestimmt die Ökonomie der Abstraktion: Die Kosten eines gemeinsamen Modells sind fix. Jede Integration muss Felder befüllen, auf null prüfen und um Einschränkungen herumarbeiten. Der Nutzen fällt dagegen pro Plattform an. Bei einer Long-Tail-Plattform mit zwei oder drei Commands wird er negativ: Der Adaptercode, der sie in das Einheitsmodell zwängt, ist umfangreicher als ihre gesamte Fachlogik.

Keine Abstraktion für eine einzige Implementierung vormerken

Aus dieser Verteilung folgt eine weitere Regel: Für eine einzige Implementierung wird keine Abstraktion reserviert. Hat eine Plattform nur eine Implementierung, braucht sie kein Repository, keine Factory und keine Interface-Schicht für den Fall, dass später vielleicht noch eine zweite kommt. Eine neue Plattform bedeutet einfach: einen <platform>_reverse/-Kontext hinzufügen. Eine gemeinsame Basisklasse muss dafür nicht erst angepasst werden.

Eine Interface-Schicht schafft Wert, wenn sie mehrere Implementierungen austauschbar macht. Bei einer einzigen Implementierung ist ihr Nutzen null, ihre Wartungskosten aber positiv. Eine Abstraktion für eine nicht existierende zweite Implementierung vorzuhalten, ist derselbe Fehler wie eine plattformübergreifende Gemeinsamkeit anzunehmen, die es nicht gibt. Die sprachübergreifende Migration hat das erneut gezeigt: Im alten Registry blieben einige hundert Commands bewusst als Batch unmigriert – ohne Stubs oder Kompatibilitäts-Proxys. Denn eine leere Hülle kostet mehr als eine Lücke: Sie lässt die nächste Person glauben, dort sei bereits etwas implementiert. Mit reservierten Abstraktionen verhält es sich genauso.

Modelle ebenfalls mit Plattformkontext normalisieren lassen

Dasselbe Prinzip gilt, wenn ein Modell die Normalisierung übernimmt. Rohantworten von mehr als zwanzig Plattformen in eine analysierbare Struktur zu bringen, ist ein naheliegender LLM-Einsatz. Der einfachste Fehler gleicht jedoch exakt dem auf Code-Ebene: Ein einheitliches Schema definieren, jeder Plattform ihr Raw JSON geben und dazuschreiben: „Auf dieses Schema abbilden.“ Das funktioniert nicht. Das Modell weiß nicht, ob das Play-Count-Feld von bilibili und das von tiktok tatsächlich dasselbe bedeuten. Ein Lowest-Common-Denominator-Schema führt dann entweder dazu, dass plattformrelevante Felder verloren gehen oder nur halb korrekt ausgefüllt werden.

Der richtige Weg ist Kontext pro Plattform: Dem Modell sagen: „Das ist bilibili, so sind diese Felder zu verstehen, und das ist die gewünschte Struktur für diese Plattform.“ Jede Plattform wird für sich normalisiert; das Zusammenführen über Plattformen hinweg bleibt Aufgabe der Analyseschicht. Der Ablauf besteht aus mehreren Schritten, die jeweils etwas anderes vom Modell verlangen:

Schritt

Benötigte Fähigkeit

Auswahl

model id

Die vollständige Rohantwort einer Plattform in ihrer Struktur erfassen

Langer Kontext, verarbeitet die komplette Antwort samt Feldnotizen auf einmal

Kimi K3

kimi-k3

Die Normalisierungsgrenze festlegen: Welche Felder sind wirklich plattformübergreifend, welche plattformspezifisch?

Starkes Reasoning, widersteht übermäßiger Vereinheitlichung

Claude Opus 5

claude-opus-5

Felder plattformweise in großer Menge extrahieren und Eintrag für Eintrag abbilden

Günstig, Hunderte bis Tausende hochparallele Aufrufe

Claude Sonnet 5

claude-sonnet-5

Erklären, warum gleich benannte Felder zweier Plattformen nicht übereinstimmen

Mittleres Reasoning, erklärt Unterschiede anhand der Felder

GPT-5.6 Sol

gpt-5.6-sol

Nur im zweiten Schritt verändert ein Modellwechsel das Ergebnis sichtbar. Hier wird geprüft, ob das Modell anerkennt, dass zwei Felder nicht wirklich dasselbe meinen – ähnlich wie im Abschnitt zu Gegenbelegen bei der Identifikation von Algorithmusfamilien. Ein schwaches Modell folgt der Vorgabe „vereinheitlichen“. Ein starkes benennt die Grenze.

Der eigentliche Bremsklotz sind Wechselkosten

Die vier Stufen stammen von drei Anbietern, drei SDKs, drei Authentifizierungsschemata und drei Fehlerformaten. Den Client für den Modellwechsel zwischen den Schritten dreimal umzuschreiben, lohnt sich nicht. Deshalb nutzen die meisten durchgehend nur eine Stufe – oft sogar eine, die ausgerechnet beim Festlegen der Grenze nur alles glattbügelt. So entsteht erneut ein Schema, das bei mehr als zwanzig Plattformen zusammenbricht.

AIReiter vereinheitlicht diese Ebene: ein Key, eine OpenAI-kompatible Schnittstelle, alle vier Stufen dahinter. Der Wechsel erfolgt allein über das Feld model im Request-Body.

# Set the normalization boundary: the reasoning tier
curl https://aireiter.com/api/v1/chat/completions \
  -H "Authorization: Bearer $AIREITER_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "claude-opus-5",
    "messages": [{"role": "user", "content": "<one platform response sample + have it mark which fields are platform-specific>"}]
  }'

# Extract fields per platform in bulk: change the model field, leave the rest
#   "model": "claude-sonnet-5"
# Field-difference attribution:
#   "model": "gpt-5.6-sol"

Wer bereits das OpenAI SDK verwendet, setzt base_url auf https://aireiter.com/api/v1 und ändert sonst nichts. Mit dem Anthropic SDK geht derselbe Key an POST /api/v1/messages.

Preislich liegen Claude-Modelle 30 % unter Listenpreis, GPT-Modelle bei der Hälfte, und Kimi K3 ist mit demselben Key aufrufbar. Der Rabatt trifft den größten Kostenblock direkt: Die massenhafte feldweise Extraktion pro Plattform erzeugt die meisten Aufrufe – mehr als zwanzig Plattformen mit jeweils Hunderten bis Tausenden Datensätzen, ein Aufruf pro Datensatz. Dafür läuft das günstigste Sonnet mit zusätzlichen 30 % Rabatt. Das Lesen einer langen vollständigen Antwort mit Kimi K3 kostet pro Eingabe einige hunderttausend Tokens und damit ebenfalls einen relevanten Betrag. Die Reasoning-Stufe zum Festlegen der Grenzen braucht dagegen nur wenige Aufrufe und fällt kaum ins Gewicht.

  • API-Key holen

  • Ohne Anmeldung ausprobieren: Zuerst eine Plattformantwort manuell durchlaufen lassen. Prüfen, ob das Modell die Unterschiede ehrlich markiert oder sie vorschnell vereinheitlicht – erst dann entscheiden, ob es integriert wird.

Fazit

Das einheitliche Post-/User-Modell ist bei plattformübergreifender Datenerfassung die naheliegende erste Reaktion. Im kleinen Maßstab fühlt es sich gut an, bei mehr als zwanzig Plattformen bricht es jedoch zwangsläufig zusammen: fixe Kosten, Nutzen pro Plattform und eine extrem ausgeprägte Long-Tail-Verteilung der Commands. Tragfähig ist ein Bounded Context pro Plattform, der Eingabevalidierung, Protokoll, Signing und Response-Normalisierung selbst besitzt. In die Shared Layer gehören nur Dinge, die sich wirklich auf jeder Plattform gleich verhalten – Transport, Dispatch und die Erzeugung von Schnittstellenbeschreibungen –, nicht ein Domain-Modell, dessen Elemente sich nur ähnlich sehen. Ebenso wenig sollte eine Abstraktion für eine einzelne Implementierung oder eine nicht existierende Gemeinsamkeit vorgehalten werden. Für Modelle gilt derselbe Satz: mit Kontext pro Plattform normalisieren, kein Einheitsschema vorgeben und plattformübergreifendes Zusammenführen erst in der Analyseschicht erledigen. Der vollständige Workflow mit vier Stufen beschreibt die Aufteilung der vier Tiers ausführlicher. Über eine gemeinsame Schnittstelle angebunden, sind die Wechselkosten jedenfalls kein Grund mehr, sie nicht zu nutzen.