2023-07-31

Wie bringt man einer KI das Jassen bei?

Was ist die Technik hinter der KI, die Trumpf ansagen kann?

Jassen mit Sprachmodellen, Algorithmen und künstlicher Intelligenz

Wir hatten den Ehrgeiz, ein KI-Sprachmodell allein durch Prompten dazu zu bringen, gute Entscheidungen für das Ansagen oder Schieben beim Jassen zu treffen. Das tönt nach einer einfachen Aufgabe. Bis wir Euch jedoch auf unserer Website eine fertige Demo zum Ausprobieren anbieten konnten, mussten wir einige technische Probleme lösen. Eine solche Applikation muss nämlich das Sprachmodell, die Prompt-Erzeugung, das Weiterverarbeiten und Darstellen der Ergebnisse zu einem Gesamtkunstwerk integrieren. In diesem Blogbeitrag wollen wir Euch zeigen, wie wir das gemacht haben.

Mit Schere und Klebstoff an die Prompts?

Als erstes mussten wir herausfinden, wie wir effizient komplexe Prompts generieren und diese Prompts während der Entwicklungszeit schnell und zuverlässig optimieren und abändern können. Wie in Nathalie's Blogpost beschrieben ist, waren die Ergebnisse des Sprachmodells mit unseren ersten Prompts noch nicht sehr gut - es hatte manchmal Halluzinationen und verstieg sich bei den Erklärungen. Wir mussten also die Prompts optimieren und lernen, wo im Prompt wir die nötigen Informationen zum aktuellen Problem (nämlich: welche Karten hast du auf der Hand?) einfügen müssen, und welche Instruktionen wir geben müssen, um Halluzinationen und Denkfehler zu minimieren. Mit Schere und Kleber kann man das aber nicht machen - und copy/paste ist ja fast dasselbe.

Es musste eine Möglichkeit her, um längere Prompt-Texte zu verwalten, die dann an verschiedenen Stellen Lücken enthalten, so dass man sie zur Laufzeit, passend für die Situation, befüllen kann.

Bei der Jass-Demo haben wir das pragmatisch und einfach mit Python Bordmitteln gelöst. Python kann mit der format Methode Variablen in einen String einfügen. Das geht so:

prompt = "Du hast folgende Karten in der Hand: {karten}. Sortiere sie nach Farbe und Wert."

karten = "Schilten Banner, Rosen Under, Schilten As, Rosen Ober"
print(prompt.format(karten=karten))

# Resultierender Prompt: Du hast folgende Karten in der Hand: Schilten Banner, Rosen Under, Schilten As, Rosen Ober. Sortiere sie nach Farbe und Wert.

karten = "Eichle Under, Rosen Banner"
print(prompt.format(karten=karten))

# Resultierender Prompt: Du hast folgende Karten in der Hand: Eichle Under, Rosen Banner. Sortiere sie nach Farbe und Wert.

Das funktioniert zwar gut, in neueren Projekten verwenden wir aber das LangChain Framework, denn es bietet noch ausgefeiltere Möglichkeiten, um komplexe Prompts zusammenzustellen.

Ein bisschen mehr Struktur, bitte!

Nach einigen Prompt-Optimierungsrunden waren wir so weit, dass das Sprachmodell gute Ergebnisse geliefert hat. Bei der Optimierung stellte sich sehr schnell heraus, dass das ChatCPT Standardmodell (GPT-3.5) mit der Aufgabe überfordert war. Das GPT4-Modell ist viel stärker im Bereich "Reasoning", d.h. beim Schlussfolgern und Begründen, und wir haben uns bald entschieden, für diese Aufgabe ausschliesslich das GPT4-Modell zu verwenden. Allerdings musste Nathalie, um die guten Ergebnisse mit GPT-4 zu erreichen, in den Instruktionen das Modell dazu anleiten "laut zu denken" und die Zwischenschritte in der Antwort auszugeben. Wenn man das Modell nämlich nicht so instruiert, neigt auch GPT-4 dazu, denkfaul zu sein und oberflächlich eine Antwort zu generieren. Dabei übersieht es Informationen oder erfindet nicht zutreffende Aussagen (Halluzinationen). Mit der Instruktion zur Ausgabe der Zwischenschritte klappt das deutlich besser. Diese Technik hat aber den Nachteil, dass das Modell sehr ausführliche Antworten generiert, wobei die Form der Antwort oft variiert, denn das Modell bemüht sich, keine eintönigen Texte zu erzeugen. Das macht es schwierig, die Antworten anschliessend weiterzuverarbeiten. Zu versuchen, im Prompt immer genauere Anweisungen zu geben, in welcher Reihenfolge, Form und Länge das Modell die Teile der Antwort ausgeben soll, führt zu keinen besseren Ergebnissen. Wir haben stattdessen eine elegante Lösung aus dem LangChain Framework nachgebaut. In LangChains gibt es die Möglichkeit eine Instruktion erstellen zu lassen welche das Sprachmodell anweist, die Antwort gemäss einem JSON-Schema zu generieren. Diese Instruktion fügt man dann gegen Ende des Prompts als zusätzliche Anweisung ein. Unser Nachbau dieser Technik sieht als Skizze etwa so aus:

FORMAT_INSTRUCTIONS = """Deine Antwort muss als JSON Instanz formatiert sein, welche dem untenstehenden JSON Schema entspricht.

Beispiele: Für das Schema {{"properties": {{"foo": {{"title": "Foo", "description": "a list of strings", "type": "array", "items": {{"type": "string"}}}}}}, "required": ["foo"]}}}}
ist das Objekt {{"foo": ["bar", "baz"]}} eine wohlgeformte Instanz des Schemas. Das Objekt {{"properties": {{"foo": ["bar", "baz"]}}}} is nicht wohlgeformt.

Hier ist das Schema:
{schema}
"""

Dieses Prompt-Fragment ist übrigens selbst ein Besipiel für eine mächtige Prompting-Technik: Man gibt eine kleine Anzahl von Beispielen im Prompt an, das Modell lernt daraus gleich, was von ihm erwartet wird. Dieses Lernen aus wenigen Beispielen nennt man "few-shot learning".

Das benötigte Schema kann man nun bequem mit Pydantic erzeugen, oder, wem das Spass macht, von Hand. Im Original von LangChain war diese Instruktion in Englisch, aber da der Rest des Prompts in Deutsch gehalten ist, haben wir das übersetzt. Das funktioniert gut so, offenbar werden im Modell Konzepte von einer Sprache in die andere übertragen, denn ich gehe nicht davon aus, dass das Modell mit vielen Deutschsprachigen JSON-Schemata und Python-Files trainiert wurde. Schlussendlich bekommen wir mit dieser Technik eine Antwort, welche dem gegebenen Schema entspricht. Das Schema wählen wir so, dass wir die Daten gut weiterverarbeiten können. In der Live-Demo könnt Ihr den resultierenden Prompt unter "Prompt Gesamtanalyse" sehen. In der Anzeige des Resultats verwenden wir dann nur die Texte in den Feldern Ansage (als Titel) und Begründung (als Fliesstext).

Umlernen ist schwierig: Die Grenzen des Sprachmodells mit Algorithmen austricksen

Als wir schon fast am Ziel waren, kamen wir an eine Grenze des Sprachmodells: Trotz einiger Versuche ist es uns nicht gelungen, das Sprachmodell zum Kopfstand zu bringen und die Welt mal als Undenufe zu betrachten. Die Macht des As war stärker als unsere Prompts. Was tun? Sollen wir es mit einem extra-Prompt nur für Undenufe probieren? Sollen wir die Funktionalität der Demo einschränken und auf Undenufe verzichten? Oder gibt es eine Möglichkeit, das Problem zu umgehen? Wir haben den Weg gewählt, einen klassischen Algorithmus einzubauen, der das Sprachmodell bei dieser Schwäche unterstützt. Wir haben eine kleine Python-Funktion gebaut, welche für das gegebene Kartenblatt die Basis für das Anwenden von Faustregeln berechnet. Die Funktion berechnet die Anzahl sicherer Stiche (d.h. alle Stiche, die man am Anfang beim Undenufe-Spiel ohne Unterbruch ausspielen kann) und ebenso "fast sichere Stiche", also solche, die man mit etwas partnerseitigem Kartenglück auch noch machen kann (z.B. eine 8 zu einer 6 oder ein Banner zu einer 6,7,8er Folge). Diese Information geben wir dem Sprachmodell im Prompt für die Schlussanalyse mit, und dann kann es die Faustregeln, welche auf solchen Stichen beruhen, wieder selbst anwenden. Der Nutzen des Sprachmodells bleibt dabei erhalten: Es kann in der Begründung erklären, auf der Basis von welchen Faustregeln es die Ansage macht, auch wenn es selbst mit dem Erstellen der Basisinformationen überfordert ist. Wenn Ihr in der Demo die Prompts anschaut findet ihr die berechneten Informationen in Form der "Obenabeliste" und "Undenufeliste" .

Das Beste aus zwei Welten

Bei der Entwicklung unserer Jass-Demo sind wir bis an die Grenzen der Sprachmodelle gestossen. Unsere Lösungen reichten von der kreativen Optimierung der Prompts, über die Einbindung von JSON-Schemas zur Strukturierung der Antworten, bis hin zur Kombination von klassischem Algorithmus und dem Sprachmodell. Dabei hat es sich bewährt, Stärken des KI-Sprachmodells und der "klassischen IT" miteinander zu kombinieren. Das Sprachmodell hat es ermöglicht, Entscheidungen auf der Basis von Regeln zu treffen und zu erklären, während die klassischen IT-Techniken die Berechnungen ausführen, die für das Sprachmodell zu komplex waren.

In unserem Projekt hat sich gezeigt, dass ähnlich wie beim Jassen die beste Strategie oft darin besteht, flexibel auf die jeweilige Situation zu reagieren - sei es beim Entscheiden, ob du 'schieben' oder 'ansagen' solltest, oder beim Wählen, ob du Python f-Strings, LangChain oder einen klassischen Algorithmus verwendest. Wie im Jassen sind die beste Entscheidungen oft diejenigen, die sich am flexibelsten an die Herausforderungen anpassen, die dir das Spiel - oder das Projekt - stellt.

Am Ende des Tages ist es nicht nur wichtig, die richtigen Werkzeuge zu haben, sondern auch zu wissen, wie und wann man sie einsetzt. Wir teilen unsere Erfahrungen gern, und Ihr könnt die Tipps in eigenen Projekten anwenden, oder uns kontaktieren, wenn Ihr Unterstützung braucht.

Und falls Ihr immer noch an der Frage hängt, ob Ihr eine Schere zur Bearbeitung der Prompts braucht – die Antwort ist immer noch nein. Aber mit der richtigen Mischung aus KI, klassischer IT und ein bisschen Kreativität kann man die Applikationen auf ein neues Level heben, ganz ohne Papier und Klebstoff.


Lust auf mehr Blogs?

Warum bringt man einer KI das Jassen bei?

Nathalie Portmann macht darauf aufmerksam, dass KI bei Entscheidungsfindungsprozessen eingesetzt werden kann, ohne dass am nächsten Tag die Menschheit untergeht.

Mehr lesen
Wie haben wir poemAI.ch gebaut? Mit KI!

Markus Emmenegger verrät, wie wir künstliche Intelligenz bei der Erstellung der Website von poemAI.ch eingesetzt haben. In diesem Blog gibt es anschauliche Tipps, wie man KI im Arbeitsalltag einsetzen kann.

Mehr lesen
Datenschutzerklärung
I. Allgemeine Informationen

PoemAI stellt unter der Webadresse https://poemai.ch die Webseite und den Blog von poemAI bereit. Die Website dient insbesondere der Information über Produkte und Dienstleistungen von poemAI. Außerdem haben interessierte Personen die Möglichkeit, mit poemAI in Kontakt zu treten.

In dieser Datenschutzerklärung erläutern wir, wie wir personenbezogenen Daten erheben und verarbeiten. Personenbezogen sind dabei alle Daten, die auf eine natürliche Person beziehbar sind. Bei all unseren Datenverarbeitungsprozessen halten wir uns an alle gesetzlichen Vorgaben, insbesondere an jene des Datenschutzgesetzes.

II. Informationen über die Verantwortlichen

PoemAI ist für die Bearbeitung Ihrer personenbezogenen Daten verantwortlich. Für generelle Anliegen sowie Anliegen des Datenschutzes können Sie sich an einen oder beide Co-Founder wenden:

poemAI GmbH
Nathalie Portmann und/oder Markus Emmenegger
nathalie.portmann@poemai.ch und/oder markus.emmenegger@poemai.ch
Rämsiweg 8
6048 Horw
Tel.: 076 559 60 48

III. Datenverarbeitung zu betrieblichen Zwecken

PoemAI verwendet vereinzelte Personendaten (Namen, Adressen, Emailadressen, Telefonnummern) zu folgenden Zwecken:

  • Zum sicheren und stabilen Betrieb der vorliegenden Website
  • Zur Bewirtschaftung unserer Stamm- und Kundendaten im CRM
  • Zur Mitteilung von Neuigkeiten einschließlich Werbemassnahmen, die Sie interessieren könnten
  • Für Marketing-Zwecke
  • Für die Vertragserfüllung.

Diese Daten fallen beim Ausfüllen unseres Kontaktformulars oder einer andersweitigen Kontaktaufnahme an. Oder sie stammen aus öffentlich einsehbaren Websiten und Verzeichnissen.

Jegliche Verwendung von Cookies - oder anderer Tracking-Tools - durch diese Webseite oder Anbieter von Drittdiensten, die durch diese Webseite genutzt werden, dient dem Zweck, den von Ihnen gewünschten Dienst zu erbringen. Wir messen mit Google Analytics einzig die Besucherströme auf unserer Website. Wir können und wollen keine Besucher/innen unserer Website identifizieren.

IV. Stand der Datenschutzerklärung

Die Datenschutzerklärung ist aktuell gültig und hat den Stand 28.9.2023.