Prism-Shadow/model-message-stream-protocol

Fester Workflow zur Entwicklung des MMSP selbst – Hinzufügen oder Aktualisieren von Modellunterstützung und Änderungen an den Seiten. Verwenden, wenn aufgefordert, ein neues Modell oder eine neue Protokollversion in diesem Repository zu unterstützen, llmsdk_docs zu synchronisieren, einen Provider-Client zu implementieren oder das Playground, den Tracer oder die Website zu ändern. Deckt Docs-Synchronisation, Live-API-Erfassung, gepaarte Python/TypeScript-Implementierung, modellbegrenzte E2E-Tests und die UI-Regeln für die drei Seiten ab.

Was ist model-message-stream-protocol?

Dieser Skill definiert einen festen, schrittweisen Workflow zur Entwicklung des `model-message-stream-protocol` (MMSP) selbst. Er wird verwendet, wenn Sie aufgefordert werden, ein neues Modell oder eine neue Protokollversion zu unterstützen, offizielle Dokumentationen zu synchronisieren, einen Provider-Client (Python und TypeScript) zu implementieren oder die Begleitkomponenten wie das Playground, den Tracer oder die Astro-Website zu ändern. Der Workflow beginnt mit der Synchronisierung offizieller Modell-Dokumentationen in `llmsdk_docs/`, erfasst dann einen Live-API-Austausch zur Referenz, implementiert anschließend die gepaarten Python- und TypeScript-Clients und endet mit dem Hinzufügen von E2E-Tests im Umfang des neuen Modells. Jede Phase enthält explizite Anweisungen, wann anzuhalten und beim Benutzer nachzufragen – zum Beispiel, wenn ein API-Schlüssel fehlt oder eine offizielle URL unklar ist. Der Skill legt großen Wert auf minimale, aber präzise Wiedergabetreue: Er implementiert nur das, was der API-Austausch erfordert, und befolgt eine Sicherheitsregel, die sicherstellt, dass Anmeldedaten nur an den vorgesehenen Endpunkt gesendet werden. Der Skill unterstützt die vollständige Entwicklungsumgebung, die das Repository nutzt, einschließlich clientseitiger Implementierungen in Python und TypeScript, und gewährleistet die Einhaltung der Projektregeln wie das Vermeiden von KI-generiertem Code-Slop und die Beibehaltung einer zweisprachigen Dokumentation.

Funktioniert mit✓Claude Code✓Codex CLI~Cursor✓Gemini CLI
npx skills add https://github.com/Prism-Shadow/model-message-stream-protocol/tree/HEAD/.agents/skills/mmsp-dev

Installed? Explore more Produktivität & Zusammenarbeit skills: steipete/gemini, steipete/gh-issues, steipete/skill-creator · View all 6 →

In Ihrer bevorzugten KI fragen

Öffnet einen neuen Chat, in dem dieser Agent-Skill bereits geladen ist.

Dokumentation

MMSP-Entwicklungsworkflow

Das Hinzufügen oder Aktualisieren von Modellunterstützung erfolgt in den unten aufgeführten Phasen in der angegebenen Reihenfolge. Wenn in einer Phase stop and ask (anhalten und fragen) steht, pausieren Sie und fragen Sie den Benutzer; fahren Sie nicht fort, bis das Problem gelöst ist, und füllen Sie die Lücke niemals selbst.

Verzeichnisstruktur

llmsdk_docs/<model_version>/      Offizieller Docs-Snapshot, ein Ordner pro Modellgeneration (README.md + docs/)
api_captures/<protocol>/          Von Git ignorierte, rohe API-Erfassungen: Anfrage-Payloads + Stream-Events
src_py/mmsp/<client_type>/        Python-Client, ein Ordner pro Client-Typ: <vendor>_official/ oder <protocol>/
src_py/mmsp/auto_client.py        Erstellt den Client, den ein client_type benennt; die Familie einer Modell-ID benennt ihren offiziellen Client
src_ts/src/<client_type>/         TypeScript-Client, spiegelt den Python-Ordner wider
src_ts/src/autoClient.ts          TypeScript-Routing, spiegelt auto_client.py wider
src_py/tests/test_client.py       Parametrisierte E2E-Tests (umgebungsgesteuert durch AVAILABLE_MODELS)
src_ts/tests/client.test.ts       Gleiches für TypeScript
changelog/unreleased/             Wo noch nicht veröffentlichte Änderungen abgelegt werden; bei Veröffentlichung in die Version umbenannt
changelog/<version>/              Release-Zusammenfassung (README.md) plus eine Detaildatei pro Eintrag
changelog/README.md               Das Format der Einträge: Metadatenblock, Textregeln, zweisprachige Paarung
CHANGELOG.md                      Eine kurze Zeile pro Release mit Link zu changelog/
src_py/mmsp/integration/          Das Playground, die Tracer-Seiten und der MMSP-Server, gespiegelt in src_ts/src/integration/
site/                             Die Astro-Website unter mmsp.penguin.ooo
.agents/skills/mmsp-dev/kill-ai-slop/   Eingekaufter AI-Slop-Leitfaden und Scanner für die UI-Arbeit unten
<name>.zh.md                      Chinesisches Gegenstück, erforderlich für jede Datei in changelog/

Phase 1 – Offizielle Docs in llmsdk_docs/ synchronisieren

  • Quellen müssen die offizielle Dokumentationsseite des Modellanbieters sein (z. B. docs.anthropic.com, platform.openai.com, ai.google.dev). Verwenden Sie niemals Drittanbieter-Spiegel, Blogbeiträge oder Modellgedächtnis.
  • Speichern Sie den Snapshot unter llmsdk_docs/<model_version>/ gemäß den bestehenden Ordnerkonventionen und listen Sie den Ordner in llmsdk_docs/README.md auf. Das Ausführen dieses Workflows ist die explizite Anfrage, die die Repository-Regel gegen das Bearbeiten von llmsdk_docs/ verlangt.
  • Wenn sich die abgerufenen Docs von einem vorhandenen Snapshot unterscheiden, gewinnen die neuen offiziellen Docs: Aktualisieren Sie die alten Dateien direkt.
  • Der Snapshot muss vollständig genug für die Implementierung sein: Request/Response-Schemas, Streaming-Event-Sequenz, Thinking-Ausgabe, Tool-Aufrufe, Usage-Felder und Fehlerantworten.
  • Stop and ask (Anhalten und fragen), wenn die offizielle URL unsicher ist oder eine Seite nicht abgerufen werden kann. Der Benutzer kann den Inhalt manuell einfügen.

Phase 2 – Live-API-Austausch in api_captures/ erfassen

  • Voraussetzung: Die API-Key-Umgebungsvariable des Anbieters muss gesetzt und verwendbar sein. Verwenden Sie dieselben Umgebungsvariablen und Basis-URLs wie src_py/tests/test_client.py (AVAILABLE_MODELS-Steuerung und _create_client). Stop and ask (Anhalten und fragen) Sie den Benutzer, den Schlüssel bereitzustellen, wenn er fehlt; der Workflow darf ohne ihn nicht fortgesetzt werden.
  • Führen Sie unter Verwendung des offiziellen SDKs des Anbieters oder von rohem HTTP genau wie dokumentiert eine Streaming-Tool-Call-Anfrage mit aktiviertem Thinking aus, senden Sie dann das Tool-Ergebnis zurück, sodass die Erfassung auch zeigt, wie Assistant-Turns erneut gesendet werden.
  • Speichern Sie den vollständigen Austausch unverändert unter api_captures/<protocol>/ (von Git ignoriert), z. B. round1.request.json plus round1.stream.jsonl mit jedem rohen Stream-Event in der richtigen Reihenfolge. Speichern Sie niemals Anmeldedaten.
  • Wenn etwas im Austausch nicht in JSON serialisiert werden kann (binäre Payloads, SDK-Objekte mit Nicht-JSON-Werten), speichern Sie seine str()-Form und analysieren Sie daraus. Halten Sie den Container analysierbar: Wickeln Sie den Text in ein JSON-Objekt, sodass die .jsonl einen JSON-Wert pro Zeile enthält ({"unserializable_str": "<str(event)>"}, wobei das Event in der Stream-Reihenfolge bleibt), oder legen Sie den gesamten Austausch in eine nebengeordnete .txt-Datei, wenn nichts daran serialisierbar ist. Eine str()-Erfassung ist immer noch die maßgebliche Aufzeichnung dessen, was die API zurückgegeben hat. Lassen Sie das Event niemals weg, bearbeiten Sie es nicht zu gültigem JSON oder greifen Sie nicht auf die Docs zurück, weil die Serialisierung fehlgeschlagen ist.
  • Stop and ask (Anhalten und fragen) bei jedem API-Fehler (ungültiger Schlüssel, unzureichendes Kontingent, Ratenbegrenzung). Simulieren Sie die Antwort nicht und fahren Sie nicht allein mit den Docs fort.
  • Die Erfassung ist die primäre Implementierungsreferenz und hat Vorrang vor den Docs: Wenn sie sich unterscheiden, implementieren Sie, was die API tatsächlich zurückgegeben hat.
  • Testen Sie dann das Replay, da die Erfassung nur zeigt, was die API sendet, nicht was sie zurück erwartet. Senden Sie den Assistant-Turn wiederholt, jedes Mal mit einem entfernten Kandidatenfeld – der ID des Reasoning-Items, seinem Inhalt, dem gesamten Reasoning-Item, jedem Feld, das Sie behalten wollten – und notieren Sie, welche Entfernungen die API noch mit intaktem Modellverhalten akzeptiert. Dieses Ergebnis, nicht die Form der Antwort, entscheidet, was Phase 3 speichert. Speichern Sie die Testergebnisse neben der Erfassung.

Phase 3 – Python- und TypeScript-Clients implementieren

  • Ein Ordner pro Client-Typ, benannt nach ihm: Ein offizieller Client pro Anbieter (openai_official/, anthropic_official/, google_official/, zai_official/, moonshot_official/, deepseek_official/, minimax_official/) spricht die eigene API des Anbieters, kennt jede Generation der Modelle des Anbieters und liest den Schlüssel des Anbieters aus der Umgebung; ein kompatibler Client pro Drahtprotokoll (openai_responses/, openai_chat/, openai_chat_vllm_adapter/, openai_embedding/, ant_messages/, google_genai/, mmsp/) spricht dieses Protokoll für jeden Endpunkt. Die Klasse wird nach dem Ordner benannt (OpenAIOfficialClient, OpenaiChatClient), und der Client-Typ ist der Ordnername mit Bindestrichen (openai-official, openai-chat).
    • Eine neue Generation der Modelle eines Anbieters kommt in den offiziellen Client des Anbieters. Vergleichen Sie das neue Protokoll (Erfassung + Docs) mit dem, was der Client heute sendet; wenn sich eine Generation unterscheidet, selbst um einen einzelnen Schlüsselnamen, unterscheiden Sie die Generationen innerhalb des Clients durch explizite Version ("4-6" in self._model, "gpt-6" in self._model), niemals durch einen bloßen Teilstring wie "claude" in model, und lassen Sie die älteren Generationen funktionsfähig. Entfernen Sie niemals eine Generation, es sei denn, der Benutzer weist explizit dazu an.
    • Ein neuer Anbieter erhält einen neuen offiziellen Client-Ordner, einen neuen <vendor>-official Client-Typ in auto_client.py / autoClient.ts und eine neue Zeile in der Modellfamilientabelle dort: Die Familie ist das Präfix, mit dem jede ID der Modelle des Anbieters beginnt (glm-, kimi-), groß-/kleinschreibungsunabhängig abgeglichen.
    • Ein neues Drahtprotokoll, das von mehreren Anbietern bedient wird, erhält einen kompatiblen Client-Ordner und Client-Typ; ein kompatibler Client trägt keine Besonderheiten des Anbieters.
  • auto_client.py / autoClient.ts erstellen den Client, den ein client_type benennt, aus der einen Tabelle der Client-Typen. Ohne client_type benennt die Familie, mit der eine Modell-ID beginnt, ihren offiziellen Client; eine ID ohne bekannte Familie löst einen Fehler aus und fragt nach einer. Das Routing hat keine andere Regel: keine Teilstring-Übereinstimmung bei Modell-IDs, keine Versionsübereinstimmung außerhalb der offiziellen Clients.
  • Eine Anmeldeinformation wird nur dorthin gegeben, wofür sie gegeben wurde. Dies ist das Sicherheitsmodell, und jeder Client-Konstruktor befolgt es. Ein als api_key übergebener Schlüssel wird so verwendet, wie er ist. Ein Client, der den generischen Schlüssel der Umgebung liest (OPENAI_API_KEY für openai_official und die OpenAI-Protokoll-kompatiblen Clients, ANTHROPIC_API_KEY für anthropic_official und ant_messages, GEMINI_API_KEY für google_genai), liest ihn nur, wenn der Endpunkt ebenfalls aus der Umgebung kommt (OPENAI_BASE_URL / ANTHROPIC_BASE_URL / GEMINI_BASE_URL, oder die eigene Voreinstellung des Anbieters): Bei einer base_url und ohne api_key löst er beim Bauen einen Fehler aus, anstatt diesen Schlüssel an einen anderen Endpunkt zu senden. Das ist resolve_credentials / resolveCredentials in den Hilfsprogrammen; rufen Sie es auf, implementieren Sie es nicht neu. Jeder andere offizielle Client (deepseek_official, zai_official, moonshot_official, minimax_official, google_official) liest seine eigene Variable (DEEPSEEK_API_KEY, ZAI_API_KEY, MOONSHOT_API_KEY, MINIMAX_API_KEY, GEMINI_API_KEY) unabhängig von der übergebenen Basis-URL und niemals die generische; ohne eine von beiden löst er <VAR> is required for <Client>. aus. Übergeben Sie jede Anmeldeinformation explizit an das Anbieter-SDK, einschließlich "none" (authToken: null in TypeScript, ein gelöschtes auth_token in Python), da die SDKs alles ausfüllen, was sie nicht erhalten, aus ihren eigenen Umgebungsvariablen. Die Offline-Tests in tests/env-credentials.test.ts / tests/test_env_credentials.py bauen jeden Client unter einer kontrollierten Umgebung auf und lesen die Anmeldeinformation, die die SDK-Instanz hält; fügen Sie einen neuen Client zu ihnen hinzu.
  • Das Replay muss minimal, nicht erschöpfend sein. Ein wiederholter Assistant-Turn muss von der API mit intaktem Modellverhalten akzeptiert werden; er muss das Wire-Item nicht Feld für Feld reproduzieren. fidelity ist ein Ausnahmekanal, kein Spiegel der Payload: Ein Feld verdient dort nur einen Platz, wenn die Phase-2-Untersuchung zeigt, dass die Anfrage fehlschlägt oder das Modell ohne es degradiert. Von Anbietern generierte IDs, die die API neu generiert oder ignoriert – eine Reasoning-Item-ID, eine Output-Item-ID – bleiben außen vor. Felder, die die API verlangt, kommen hinein: GPT-5.5's phase, Claudes Thinking-signature, der genaue Reasoning-Feldname, den eine strenge vorgeschaltete Stelle erfordert.
  • Kopieren Sie niemals in fidelity, was ein universelles Feld bereits trägt – den Thinking-Text, den Namen eines Tool-Aufrufs oder seine analysierten Argumente. Bauen Sie das Wire-Item stattdessen aus den universellen Feldern neu auf.
  • tool_call_id ist nicht optional: Erfassen Sie immer die Aufruf-ID des Anbieters und spielen Sie sie erneut ab. Wenn das Wire-Format sowohl eine Item-ID als auch eine Aufruf-ID trägt, ist die Aufruf-ID diejenige, die ein Ergebnis mit seinem Aufruf korreliert.
  • Folgen Sie dem Referenzclient; entwerfen Sie nicht neu. openai_official/ ist die Form für Responses-ähnliche Protokolle, openai_chat/ für Chat Completions: gleiche Methodenreihenfolge, gleicher Steuerfluss, gleiche Namen. Ein neuer Client sollte wie ein Diff gegen seinen Referenzclient lesbar sein, denn so wird er überprüft.
  • Halten Sie es von oben nach unten lesbar. Ein Leser sollte eine Anfrage oder Antwort direkt durch die Datei verfolgen können, ohne zwischen Definitionen springen zu müssen. Daher ist das Inlining die Voreinstellung: Lesen Sie die Attribute des SDKs direkt (model_output.delta, model_output.item.call_id) und halten Sie Feldzugriff, Usage-Arithmetik und Fehlertexte dort, wo sie verwendet werden. Extrahieren Sie einen Helfer nur für einen wirklich großen, in sich geschlossenen Block – die Art, die den Hauptfluss begraben würde, wenn sie inline wäre, wie das Abrufen und Dekodieren eines Bildes – niemals für ein paar Zeilen. Spiegeln Sie die eigenen privaten Methoden des Referenzclients wider (_convert_thinking_level_*, _convert_tool_choice), anstatt eine Ebene daneben zu erfinden; ein Shim, der sowohl Diktate als auch SDK-Objekte akzeptiert, ist niemals einer von ihnen, da die Events mit den eigenen Typen des SDKs typisiert sind.
  • Clients geben nur Deltas ab; die Basisklasse schließt die Items. transform_model_output_to_uni_event / transformModelOutputToUniEvent wandelt ein Wire-Event in ein UniEvent um, dessen content_items die .delta-Items sind, die dieses Event trägt, in Wire-Reihenfolge, und niemals ein .done-Item. _streaming_response_internal / _streamingResponseInternal baut die Anfrage, öffnet den Stream und gibt ein Event pro Wire-Event aus: eine einfache Schleife, ohne Item-Buchhaltung, ohne Akkumulator, ohne Argument-Parsing, ohne Usage-Zusammenführung und ohne synthetisiertem Stopp. usage_metadata / finish_reason reiten auf stop-Events, wo das Wire sie meldet – Anthropics message_start und message_delta, die Chat-Completions-Finish- und Usage-Chunks, response.completed – Feld für Feld von der Basis zusammengeführt; ein stop-Event eines Clients beendet niemals den öffentlichen Stream. Ein Wire-Event ohne etwas Universelles ist das leere Delta-Event: keine Items, null Usage und Finish Reason. Die Basisklasse übergibt jedes Delta an StreamItems (stream_items.py / streamItems.ts), das es sofort ausgibt und jedes Item mit seinem .done-Item schließt, wenn das nächste Item beginnt oder der Client-Stream endet; die Basis gibt dann das eine Stop-Event aus oder löst EmptyResponseError aus. Events, die das Protokoll verletzen, lösen in jedem Modus StreamProtocolError aus, und die gemeinsamen E2E-Tests überprüfen jeden Client-Stream mit assert_stream_grammar / assertStreamGrammar.
  • Die Deltas eines Items sind zusammenhängend und teilen sich eine fidelity.item_id. Die Modellausgabe ist seriell, sodass jeweils ein Item streamt und die End-of-Item-Events des Anbieters (content_block_stop, response.output_item.done, step.stop) als No-Ops aufgeführt werden: Ein Gateway, das seine Items spät oder in falscher Reihenfolge schließt, ändert nichts. Übergeben Sie die eigene ID des Anbieters, als Zeichenkette, als fidelity.item_id: den Anthropic-Block-index, das Responses-Event item_id oder item.id or item.call_id (None / undefined, wo ein Gateway keine sendet), den Interactions-Schritt-index, das Chat-Completions-Wire-Feld (reasoning_content, reasoning, content, tool_calls), die generateContent-Teil-Art (function_call, thought, inline_thinking, inline_data, text); Einbettungsvektoren tragen keine. StreamItems entfernt sie, sodass sie niemals Verbraucher, Ablaufverfolgungen oder Verläufe erreicht.
  • StreamItems entscheidet, wo ein Item endet; Clients tun das nie. Ein Delta gehört zum nächsten Item, wenn es eine andere item_id trägt, von einer anderen Art ist (ein Interactions-Thought-Schritt, der Text, Bild, Text wird, sind drei Items unter einem Index), oder ein Item für sich beginnt: ein tool_call.delta, das einen Namen trägt (jeder Anbieter sendet ihn einmal, beim ersten Delta des Aufrufs, daher benötigen Chat-Completions-Aufrufe keine eigenen IDs), ein Bild (inline_thinking, oder inline_data mit einem image/*-MIME-Typ, während Audio in Chunks eines Items streamt), ein Einbettungsvektor. Andernfalls setzt es das jetzt streamende Item fort: Ein Delta ohne ID tut das, ebenso wie die Argumente eines Aufrufs, unabhängig von der ID, die ein Gateway darauf setzt. Fidelity allein unter der ID des Items gesendet, gehört zu diesem Item, unabhängig von der Art, die es trägt (ein Interactions thought_signature nach einem Bild-thought). Die tabelle pro Art in dieser Datei – das wachsende Feld, die Kopffelder des ersten Deltas, wie die Chunks zusammengefügt werden – ist der einzige Pro-Art-Code im Stream: Das Done-Item ist das erste Delta des Items, wobei das wachsende Feld durch die Verbindung der

…

[gekürzt für die Übersetzung]

Verwandte Skills