ChatGPT als Helfer in der Shell

ChatGPT ist schon ohnehin ein sehr mächtiges Werkzeug, über die Integration in die Kommandozeile, können wir es aber noch einen Ticken effektiver nutzen. In einer Schritt-für-Schritt Anleitung erstellen wir drei Scripte, die direkt die ChatGPT API in die zsh Shell einbinden. Und das (fast) ohne zusätzliche Bibliotheken und mit nur wenigen Zeilen Code.

Dieser Artikel basiert auf dem Blogpost "become a 1000x engineer or die tryin" (https://kadekillary.work/posts/1000x-eng/)" in dem die Anbindung der OpenAi API mit der Fish Shell aufgezeigt wurde. In diesem Artikel implementieren wir die Beispiele mit der Zsh Shell nach. Und es war tatsächlich etwas mehr Arbeit als nur ChatGPT danach höflich zu fragen die Skripte umzuschreiben.

Voraussetzung für die Implementierung ist ein OpenAi API-Key und die jq-Bibliothek für JSON. Ersteres können wir mit einem gültigen OpenAi Account einfach von der Webseite bekommen. Die jq-Library muss von https://stedolan.github.io/jq/ heruntergeladen und in den PATH eingefügt werden, zB. für zsh (oder bash) in der .zshrc Datei:

export PATH=$PATH:/Users/matthias/Apps/apache-maven/bin:/Users/matthias/Apps/jq-osx-amd64
export OPENAI_KEY=XXXXX
# Wie man sieht, habe ich neben jq auch noch maven im "Path".

Die "jq" Datei muss auch mit "chmod +x" ausführbar geamcht werden.

Hinweis zum Bearbeiten der "dot"-Dateien in OSX: Die Dateien sind im Finder per default nicht sichtbar (also auch, wenn man sie in einem Texteditor öffnen möchte). Über die Tastenkobination "⌘ cmd + ⇧ shift + ." können wir sie aber einblenden.

Jetzt können wir auch schon gleich mit dem ersten Script loslegen:

function gpt_ask() {
  local prompt="'$(echo "$*" | sed "s/'/\\\\'/g")'"
  local gpt=$(curl https://api.openai.com/v1/chat/completions -s \
    -H "Content-Type: application/json" \
    -H "Authorization: Bearer $OPENAI_KEY" \
    -d '{
        "model": "gpt-3.5-turbo",
        "messages": [{"role": "user","content": "'"$prompt"'"}],
        "temperature": 0.7,
        "stream": true
        }')

  while read -r text; do
    # "data: [DONE]" markiert das Ende des Outputs
    if [[ $text == "data: [DONE]" ]]; then
      break
    # Mit "role" startet die Ausgabe
    elif [[ $text =~ role ]]; then
      continue
    # Alles was "content" ist, beinhaltet die Antwort
    elif [[ $text =~ content ]]; then
      # Das Schlüsselwort "data: " ist dem eigentlichen JSON Response vorangestellt - weg damit
      text=${text#"data: "}
      # Mit jq können wir die Ausgabe elegant extrahieren und über echo (mit dem E Parameter) oder print ausgeben
      #printf "%s" "$text" | jq -r -j '.choices[0].delta.content'
      echo -E "$text" | jq -r -j '.choices[0].delta.content'
    else
      continue
    fi
  done <<< "$gpt"
}

Wir machen mit curl ein Request zu der OpenAi API, dabei müssen wir natürlich unseren API Key als Barer Token angeben und konfigurieren, welches Model und mit welchen Parametern verwenden wird: Neben dem "gpt-3.5-turbo" gibt es noch eine Reihe weiterer Modele/Optionen, bitte am besten hierfür die Dokumentation konsultieren (https://platform.openai.com/docs/api-reference/models).

Die Antwort von der API kommt als JSON-String, es interessieren uns hier nur Knoten, die die Elemente "choices", "delte" und "content" beinhalten. Das Element "data: DONE" markiert das Ende des Streams. Mit "jq" extrahieren wir die einzelnen Bestandteile des Responses und geben sie über "echo" in der Konsole zurück. Das war es schon! Jetzt nur noch über "source .zshrc" das Environment wieder einlesen und wir können gleich loslegen:

gpt_ask "What is the latest Version of Java?"
As an AI language model, I don't have access to the latest information on the internet. However, as of August 2021, the latest stable version of Java is Java 16.0.2

Noch interessanter ist die Nutzung von ChatGPT, wenn wir neben unserer Anfrage auch noch Daten mitschicken können:

function gpt_data() {
  curl https://api.openai.com/v1/chat/completions -s \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $OPENAI_KEY" \
  -d '{
    "model": "gpt-3.5-turbo",
    "messages": [{"role": "user","content": "'"$1: $(echo -n "$2" | sed 's/$/\\n/g' | tr -d '\n')"'"}],
    "temperature": 0.7
    }' | jq -r '.choices[0].message.content'
}

Das Script ist fast identisch mit dem vorherigen, der wesentliche Unterschied ist, dass wir jetzt eben auch zusätzlich zu dem "Prompt" Daten senden (Der Parameter $1 ist die "prompt" Eingabe, mit $2 kommen die Daten). Die Daten werden samt des Prompt-Inputs im Feld "content" im JSON Body des Requests geschickt. Das hat zur Folge, dass Sonderzeichen, vor allem der Zeilenumbruch, escaped werden müssen (sed 's/$/\\n/g' | tr -d '\n' ). Im Fall einer CSV Datei funktioniert das recht gut, jedoch wird es hier sicher paar Fälle geben, wo das Script nicht funktioniert.

Hier exemplarischer Anwendugnsfall: Zuerst beschaffen wir uns mit "gpt_ask" Daten der Aktienkurse einer fiktiven Firma aus den letzen 100 Tage und speichern das in einer csv Datei:

gpt_ask "Create a csv with the imaginary stock data for 100 days containing the columns date, open and close - please include only the data, nothing else" > stock_data.csv

Und jetzt können wir die Daten mit ChatGPT auswerten:

$gpt_data "What was the average Open-Price?" "$(cat stock_data.csv)"
The average Open-Price is 272.5.

Mit "$(cat stock_data.csv)" inkludieren wir die davor generierten Daten.

Als letztes Beispiel nutzen wir den "Image"-Endpoint zur Generierung von Bildern:

function gpt_image() {
  local prompt="$1"
  local gpt=$(curl https://api.openai.com/v1/images/generations -s \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $OPENAI_KEY" \
  -d '{
      "n": 1,
      "prompt": "'"$prompt"'",
      "size": "1024x1024"
      }')
  echo $gpt
  local url=$(echo $gpt | jq -r '.data[0].url')

  local filename=$(echo "$prompt" | tr ' ' '_')
  filename=$(echo "$filename" | tr -cd '[:alnum:]_-')

  fname_length=${#filename}
  if [[ $fname_length -gt 150 ]]; then
    filename=${filename:0:150}
  fi

  curl -s $url -o img-"$filename".png
}

Der Aufruf der OpenAi API mit curl ist ähnlich wie schon bei den vorherigen Beispielen. Natürlich haben wir hier die spieziellen Parameter für die Bildgenerierung:

Wir versuchen in dem Script das Bild zu speichern, als Bildname wird die prompt-Eingabe verwendet (dabei bereinigen wir auch Sonderzeichen mit tr -cd '[:alnum:]_-' ) und die maximale Länge des Namens ist auf 150 Zeichen begrenzt.

Kleiner Test:

gpt_image "Happy code monkey"

JBerries mit ChatGPT - Happy code monkeyJBerries - Happy code monkey