"Weiß ich, Dave." (Linux-Magazin, März 2023)

Jeden Monat, kurz vor Redaktionsschluss der abzugebenden Ausgabe des Programmier-Snapshots, halte ich geheime Riten ab und führe Regentänze auf, um in letzter Minute ein interessantes Thema für meine Leser auszubaldowern. Mit Interesse habe ich deshalb in letzter Zeit den kometenhaften Aufstieg des KI-Chat-Roboters ChatGPT ([2]) verfolgt, der laut Alarmistenpresse so schlau ist, dass er bald Google als Suchmaschine den Rang ablaufen soll. Könnte dieses System mir vielleicht helfen, neue brandheiße Artikelthemen zu finden, die die Leser mit Begeisterung aufschnappen und gierig den darin verpackten Lernstoff aufsaugen?

Das "GPT" in ChatGPT steht für "Generative Pre-trained Transformer". Das KI-System analysiert eingehenden Text, findet heraus, welche Art von Informationen der User wünscht, fieselt passende Antworten aus einem massiven mit Internetinformationen trainierten Datenmodell, und verpackt diese wieder als Textantwort. Damit stellt sich die Frage, ob das Elektronengehirn mir vielleicht dabei helfen könnte, ein interessantes Thema für diese Kolumne zu finden? Ich machte die Probe aufs Exempel, und richtete die Frage an den Chat-Automaten [2]. Dazu tippte ich die Frage auf Englisch in das Suchfeld der Webseite des Chat-Roboters (Abbildung 1), und zu meinem Erstaunen wusste das Elektronengehirn sogar brauchbare Themen. Eine Einführung in Rust oder Go, oder ein Deep-Dive in die parallele Programmierung, das klingt doch interessant!

Abbildung 1: Der Roboter hilft bei der Themensuche für neue Ausgaben dieser Kolumne.

ELIZA 2.0

Das Interface fühlt sich an wie eine Zukunftsversion von ELIZA, dem Standardprogramm aus der Steinzeit (1960) dieses Genres [4], und seitdem hat sich ja leider in punkto KI und Textverarbeitung ein halbes Jahrhundert lang trotz vollmundiger Ankündigungen so gut wie nichts getan. Mit Deep Learning und den zugehörigen neuronalen Netzwerken erfuhr der Bereich in den letzen zehn Jahren allerdings einen derartig gewaltigen Schub, dass man heute Mühe hat, einen plaudernden Computer von einem Menschen zu unterscheiden. Professionelle Romanschreiber und Zeitungsfritzen geben unumwunden zu, für wortreiche Passagen und Plotentwicklung gerne mal Kollege Computer ans Steuer zu lassen ([5], [6]).

Im Browser-Interface auf [2] dürfen User Fragen an den Chat-Bot eintippen, und nach kurzer Bedenkpause antwortet das System mit ruckelnder Schrift, ganz so, als spräche ein Sachbearbeiter mit einem anfragenden Kunden. Dabei beantwortet der Bot nicht nur Fragen zum Allgemeinwissen, sondern schreibt auf Befehl auch gleich ganze Zeitungsartikel. Innerhalb einer halben Minute rieselte zum Beispiel auf das Kommando "Schreibe einen Artikel ..." aus Abbildung 2 der darunterstehende Text zur Einführung in die parallele Programmierung mit Go aus dem Browser heraus. Erstaunlich professionell, und weil die Frage auf deutsch gestellt worden war, kam auch die Antwort auf deutsch zurück. Auf eine Anfrage in Englisch oder einer anderen Sprache kommt auch die Antwort in dieser. Das Ganze ist erstaunlich ausgereift, und man vermutet, dass ganze Heerscharen von Sport- und Finanzjournalisten bereits seit Jahren ähnliche Systeme genutzt haben, um die immer gleichen Artikelformate zu Drittligaspielen oder Börsenkursschwankungen im Akkord zu produzieren.

Abbildung 2: Auf Wunsch schreibt das KI-System gleich fertige Artikel.

Terminal als Life-Coach

Wer sich nun nicht dauernd auf openai.com mit Email, Passwort und nervigen "I am not a robot"-Challenges einloggen will, um Anfragen zu starten, darf sich auf der Developerseite registrieren und einen API-Key entgegennehmen ([3], Abbildung 3). Gibt ein Programm diese Zeichenkette als Authentisierungstoken bei API-Requests mit, antwortet das Elektronengehirn hinter openai.com per HTTP-Response, die ein einfaches Kommandozeilen-Tool aufschnappen und ausgeben kann. Fertig ist die Kommandozeilenschnittstelle zum Wissen der ganzen bekannten Welt. Für den Auth-Token zum Herumspielen ist auf [3] lediglich eine Registrierung mit gültiger Email und Telefonnummer erforderlich. Die Firma hinter openai.com bietet zwar ein Bezahlmodell mit Kreditkartenzahlung an, aber die im kostenlosen Modus gewährten Rate-Limits sind so großzügig ausgelegt, dass bei normalem Gebrauch keine Kosten anfallen und deshalb gar keine Kartendaten hinterlegt werden müssen.

Abbildung 3: Den API-Token zum Ausprobieren gibt's kostenlos.

Mittels des Pakets go-gpt3 von Github kommuniziert das Go-Programm in Listing 1 mit dem AI-Modell von GPT. Die Fragen (sogenannte "Prompts") schickt das Programm per API-Request an den GPT-Server, und falls dieser den Inhalt versteht und eine Antwort findet, verpackt er diese "Completion" in eine API-Antwort und gibt sie zurück an den API-Client.

Listing 1: openai.go

    01 package main
    02 import (
    03   "context"
    04   "fmt"
    05   gpt3 "github.com/PullRequestInc/go-gpt3"
    06   "os"
    07 )
    08 type openAI struct {
    09   Ctx context.Context
    10   Cli gpt3.Client
    11 }
    12 func NewAI() *openAI {
    13   return &openAI{}
    14 }
    15 func (ai *openAI) init() {
    16   ai.Ctx = context.Background()
    17 
    18   apiKey := os.Getenv("APIKEY")
    19   if apiKey == "" {
    20     panic("Set APIKEY=xxx")
    21   }
    22 
    23   ai.Cli = gpt3.NewClient(apiKey)
    24 }
    25 func (ai openAI) printResp(prompt string) {
    26   req := gpt3.CompletionRequest{
    27     Prompt:      []string{prompt},
    28     MaxTokens:   gpt3.IntPtr(1000),
    29     Temperature: gpt3.Float32Ptr(0),
    30   }
    31   err := ai.Cli.CompletionStreamWithEngine(
    32     ai.Ctx, gpt3.TextDavinci003Engine, req,
    33     func(resp *gpt3.CompletionResponse) {
    34       fmt.Print(resp.Choices[0].Text)
    35     },
    36   )
    37   if err != nil {
    38     panic(err)
    39   }
    40   fmt.Println("")
    41 }

Listing 1 verpackt die Logik zur Kommunikation in einen Konstruktor NewAI() und eine Funktion init() ab Zeile 15, die den API-Token aus der Environment-Variablen APIKEY holt und damit in Zeile 23 einen neuen API-Client erzeugt. Damit auch andere Funktionen des Pakets den Client nutzen können, legt Zeile 23 ihn in einer Struktur vom Typ openAI ab, der ab Zeile 8 definiert wurde. Der Konstruktor gibt dem aufrufenden Programm einen Pointer auf die Struktur zurück, und Gos Receiver-Mechanismus packt sie den Funktionsaufrufen bei: Objektorientierung in Go. Das in Zeile 16 erzeugte Context-Objekt dient zur Fernsteuerung des Clients, falls dieser auf einen Timeout läuft oder aus anderen Gründen einen laufenden Request abbrechen soll, was zwar in der einfachen Anwendung in Listing 1 nicht benötigt wird, doch die gpt3-Library braucht ihn trotzdem, also packt Zeile 16 den Context ebenfalls in die openAI-Struktur, damit die Funktion printResp() ab Zeile 25 später darauf zugreifen kann.

Prompts und Completions

Dort findet der eigentliche Webzugriff statt. Zeile 26 erzeugt eine Struktur vom Typ CompletionRequest und setzt den Text der Anfrage in deren Attribut Prompt. Der Parameter MaxTokens legt fest, wie tief der Sprachprozessor in den Text einsteigt. Die Preisliste auf openai.com definiert etwas nebulös was ein Token ist ([7]), angeblich entsprechen 1.000 Tokens "etwa 750 Worten" in einem Text, und im kostenpflichtigen Modus zahlt der Kunde $0.02 pro tausend Tokens. Der Wert für MaxTokens bezieht sich sowohl auf die Frage als auch auf die Antwort. Wer zuviele Tokens verbraucht, läuft schneller auf ein Rate-Limit im kostenlosen Modus. Falls der Wert allerdings zu niedrig angesetzt wurde, kommt bei längeren Ausgaben (zum Beispiel automatisch geschriebenen Zeitungsartikeln) nur ein Teil der Antwort zurück. Der in Zeile 28 eingestellte Wert von 1.000 Tokens funktioniert für typische Anwendungsfälle.

Der Wert für Temperature in Zeile 29 gibt an, wie hitzköpfig das Gehirn Antworten gibt, ein höherer Wert als 0 lässt die Antworten variieren (auch auf Kosten der Genauigkeit), dazu weiter unten mehr.

Die eigentliche Anfrage an den API-Server setzt erst die Funktion CompletionStreamWithEngine ab. Ihr letzter Parameter ist eine Callback-Funktion, die der Client immer dann anspringt, wenn Antworthappen vom Server eintrudeln. Zeile 34 gibt sie lediglich auf der Standardausgabe aus.

Endlos-Bot

Um einen Chat-Bot zu implementieren, der endlos Fragen entgegennimmt und jeweils eine Antwort ausgibt, packt Listing 2 einen auf der Standardeingabe lauschenden Text-Scanner in eine Endlos-For-Schleife und ruft zu jeder eingetippten Frage in Zeile 16 die Funktion printResp() auf, die den openai-Server kontaktiert und anschließend dessen Textantwort auf der Standardausgabe ausgibt. Anschließend springt das Programm wieder an den Anfang der Endlosschleife und wartet auf die Eingabe der nächsten Frage.

Listing 2: chat.go

    01 package main
    02 import (
    03   "bufio"
    04   "fmt"
    05   "os"
    06 )
    07 func main() {
    08   ai := NewAI()
    09   ai.init()
    10   scanner := bufio.NewScanner(os.Stdin)
    11   for {
    12     fmt.Print("Ask: ")
    13     if !scanner.Scan() {
    14       break
    15     }
    16     ai.printResp(scanner.Text())
    17   }
    18 }

Abbildung 4 zeigt die Ausgabe des interaktiven Chatprogramms, das die Frage des Users auf dem Terminal entgegennimmt, zur Beantwortung an den Server von OpenAI schickt und dessen Antwort auf der Standardausgabe ausgibt. Der Bot wartet jeweils auf eine mit Enter abgeschickte Frage des Users, druckt die ankommende Antwort und springt zum nächsten Eingabe-Prompt. Ein Ende des Programms läutet CTRL-D oder CTRL-C ein.

Abbildung 4: Chat-Bot als Terminal-Applikation mit openai.com als Backend.

Wichtig ist es noch, in die Umgebung des aufgerufenen Programms den API-Token in der Environment-Variablen APIKEY einzupflanzen, sonst bricht das Programm mit einer Fehlermeldung ab. Abbildung 4 zeigt den Aufruf des Programms chat mit vorangestelltem Setting APIKEY=xxx, was die Shell dazu veranlasst, den Token ins Environment des Programms zu platzieren. Anschließend fragt der User das AI-Modell nach den Vor- und Nachteilen von Wiener Schnitzel und erhält jeweils drei Pro- und drei Kontrapunkte als Antwort. Es zeigt sich, dass das Elektronengehirn erstaunlich präzise Kenntnisse über alles verfügt, was sich irgendwo auf dem Internet aufstöbern lässt, bevorzugt auf Wikipedia, und diese Inhalte tatsächlich semantisch aufbereiten kann, sodass es auch die abstrusesten Fragen dazu sinnvoll beantworten kann.

Das Binary chat mit dem fertige Chat-Bot erzeugt wie üblich der Dreisatz

    $ go mod init chat
    $ go mod tidy
    $ go build chat.go openai.go

Das Wissen der KI ist übrigens nicht immer ganz korrekt. Auf die Frage wer Mike Schilli ist, gibt die englische Version zwei Perl-Bücher an, die ich allerdings nie geschrieben habe, korrekt wären "Perl Power" und "Go To Perl" gewesen! Es ist auch schwierig, herauszufinden, warum sich ein Fehler eingeschlichen hat, weil Quellverweise in den Antworten gänzlich fehlen.

Abbildung 5: Die KI kennt sogar den Autor dieser Kolumne.

Kreativ mit Temperatur

Die API kann auch die Vielfalt der "Completions" steuern, die die KI zum Besten gibt. Sollten die Antworten sehr präzise ausfallen, oder vielleicht spielerisch auf die gleiche Frage mehrere, variierend Antworten geben? Dieses Verhalten steuert der Parameter Temperature in Zeile 29 von Listing 1. Ein Wert von 0 (also gpt3.Float32Ptr(0)), und die KI gibt roboterhaft immer die gleichen (und präzisen) Antworten. Bei einem Wert von 1 hingegen kommt Leben in die Bude und die KI formuliert stetig um und produziert interessante neue Variationen. Beim Höchstwert 2 hat die KI allerdings zu tief ins Glas geguckt und gibt auch gelallten Unsinn mit zum Teil inkorrekter Grammatik zum Besten. In Abbildung 6 soll der Roboter einen neuen Slogan ("tagline") für die Go-Kolumne erfinden und liefert mit einem Temperature-Setting von 1 fünf erstaunlich gute Vorschläge.

Abbildung 6: Mit einem Temperature-Wert von 1 kommt die KI mit immer neuen Vorschlägen daher.

Übersetzen auf Kommando

Da die Kommunikation mit dem KI-Backend per Textbaustein und nicht über unterschiedliche API-Calls erfolgt, kann der Anwender nun problemlos weitere, spezialisierte Tools bauen, die dem Muster von Listing 2 folgen.

Abbildung 7: Das Elektronengehirn übersetzt auch aus und in verschiedenste Sprachen.

Wie wäre es mit einem Übersetzungs-Roboter, der Texte aus verschiedenen Sprachen auf Französisch übersetzt? Listing 3 liest Texte ein, und schickt sie mit dem Auftrag "Translate to French:" an die API.

Listing 3: french.go

    01 package main
    02 import (
    03   "bufio"
    04   "os"
    05 )
    06 func main() {
    07   ai := NewAI()
    08   ai.init()
    09   scanner := bufio.NewScanner(os.Stdin)
    10   text := ""
    11   for scanner.Scan() {
    12     text += scanner.Text()
    13   }
    14   ai.printResp("Translate to French:\n" + text)
    15 }

Da die der Server Anfragen in vielen verschiedenen Sprachen analysieren kann, muss der API-Request gar nicht spezifizieren, in welcher Ausgangssprache der zu übersetzende Text vorliegt, das findet die AI automatisch beim Lesen der Frage heraus. Abbildung 7 zeigt denn auch, dass das Elektronengehirn sowohl die deutsche als auch die englische Touristenfrage nach dem Weg zum Eiffelturm gut auf Französisch übersetzt. Für längere Texte muss Listing 1 den Wert für MaxTokens allerdings hochsetzen, aber das führt irgendwann dazu, dass OpenAI für die Arbeit ein paar Pfennig Geld sehen will. Dann muss dem Account ein Zahlungsmittel in Form einer Kreditkarte hinzugefügt werden. Kompiliert wird das Binary french mittels go build french.go openai.go, was das neue Programm mit der Library aus Listing 1 verlinkt.

Harte Grenze

Ebenfalls erstaunlich ist, dass das Modell den Kontext zwischen den verschiedenen Fragen einer Chat-Session behält. Solange ein Programm den gleichen Client benutzt, behält das Gehirn vorangegangene Fragen im Gedächtnis und nachfolgende können darauf aufbauen.

Zusammenfassend lässt sich sagen, dass es sich bei dem Modell "Davinci-003", das Listing 1 in Zeile 32 als Elektronengehirn eingestellt hat, erstaunlich gut über Fakten der realen Welt Bescheid weiß, allerdings stoppt das Wissen abrupt im Jahr 2021, denn aktuellere Ereignisse wurden noch nicht eingefüttert. So muss der Server zum Beispiel bei allen Fragen über die Fußball-WM 2022 in Katar passen. Das nächste Modell wird das hoffentlich aktualisieren.

Verstörende Welten

Abbildung 8: Aus diesem Bild erzeugte die KI ...

Abbildung 9: ... diese Variation.

Doch die künstliche Intelligenz kann nicht nur Texte verstehen, Probleme analysieren und Fragen beantworten oder Ausgaben produzieren. Auch Bildverarbeitung beherrscht die KI. Zum Beispiel kann sie mittels der API hochgeladene Bilder analysieren, einer Kategorie zuordnen oder erstaunlichen Transformationen unterwerfen.

Listing 4: var.sh

    1 curl https://api.openai.com/v1/images/variations \
    2   -H "Authorization: Bearer sk-XXXXXXXXXXXXX" \
    3   -F image='@test.png' \
    4   -F n=3 \
    5   -F size="1024x1024"

Listing 4 nutzt zum Beispiel den API-Endpunkt images/variations mittels eines Curl-Kommandos und modelt das Originalfoto eines von mir selbst gekochten Wiener Schnitzels auf verstörende Weise um. Zu beachten ist, dass die KI nur quadratische Fotos annimmt (also mit gleichvielen Pixeln in X- und Y-Richtung), die im .png-Format vorliegen müssen und nicht größer als 4MB sein dürfen. Mittels des Tools convert aus der ImageMagick-Kollektion hatte ich mein Schnitzelfoto mit den Optionen -crop und -resize in ein .png-Bild der Dimension 1000x1000 umformatiert. Mit einem gültigen API-Token aufgerufen liefert das Curl-Kommando (das die Datei test.png einschnupft und hochlädt) in der Json-Antwort drei verschiedene URLs mit aus dem Original generierten Foto-Variationen, die curl ebenfalls problemlos vom Server auf die lokale Platte holt.

Abbildung 8 zeigt das hochgeladene Original, Abbildung 9 eine der drei Varianten, die OpenAI zum Herunterladen anbot. Dort liegt das Schnitzel auf anderem Geschirr, schwimmt in hellbrauner Soße und daneben steht ein Bierglas voll Salat. Und statt gutem amerikanischem IPA ist links oben eine Flasche zu sehen, die wie Sake-Wein aussieht.

Bei genauem Hinsehen ist auch das Schnitzel nicht mehr das Original. Vielmehr scheint der von der KI genutzte Algorithmus mein Foto mit einem aus seinem Archiv, das wahrscheinlich von einem japanischen Tonkatsu-Restaurant stammt, kombiniert zu haben. Wir leben in erstaunlichen Zeiten.

Infos

[1]

Listings zu diesem Artikel: http://www.linux-magazin.de/static/listings/magazin/2023/02/snapshot/

[2]

Chat mit dem chatGPT-Roboter: https://chat.openai.com/chat

[3]

API-Key für OpenGPT abholen: https://beta.openai.com/account/api-keys

[4]

ELIZA, https://de.wikipedia.org/wiki/ELIZA

[5]

Andrew Mayne, "Collaborative Creative Writing with OpenAI's ChatGPT", https://andrewmayneblog.wordpress.com/2022/11/30/collaborative-creative-writing-with-openais-chatgpt/

[6]

Josh Dzieza, "How Kindle novelists are using ChatGPT", https://www.theverge.com/23520625/chatgpt-openai-amazon-kindle-novel

[7]

Openai Pricing, https://openai.com/api/pricing/

Michael Schilli

arbeitet als Software-Engineer in der San Francisco Bay Area in Kalifornien. In seiner seit 1997 laufenden Kolumne forscht er jeden Monat nach praktischen Anwendungen verschiedener Programmiersprachen. Unter mschilli@perlmeister.com beantwortet er gerne Ihre Fragen.

POD ERRORS

Hey! The above document had some coding errors, which are explained below:

Around line 5:

Unknown directive: =desc