18  ChatGPT-API

18.1 Vorab

18.1.1 Lernziele

  • Sie können gängige Anfragen an die OpenAI-ChatGPT-API senden.

18.1.2 Benötigte Software (R+Python)

library(stringr)
library(httr)  # Wrapper um curl
import os

18.1.3 Spielzeug-Daten

tweets <- c(
'@corinnamilborn Liebe Corinna, wir würden dich gerne als Moderatorin für uns gewinnen! Wärst du begeisterbar?', '@Martin28a Sie haben ja auch Recht. Unser Tweet war etwas missverständlich. Dass das BVerfG Sachleistungen nicht ausschließt, kritisieren wir.')

18.2 Grundlagen

18.2.1 ChatGPT ist auch ein Transformer

👨‍🏫 Bist du ein Transformer?

🤖 Ja, ich bin ein Modell, das auf der GPT-3.5-Architektur von OpenAI basiert, und diese Architektur wird als “Transformer” bezeichnet. Der Begriff “Transformer” bezieht sich auf die spezifische Architektur des neuronalen Netzwerks, die von Vaswani et al. in ihrem Paper “Attention is All You Need” vorgestellt wurde. Die Transformer-Architektur hat sich als äußerst leistungsfähig für natürliche Sprachverarbeitungsaufgaben erwiesen und bildet die Grundlage für Modelle wie GPT (Generative Pre-trained Transformer), einschließlich GPT-3.5, auf dem meine Fähigkeiten basieren.

[Quelle: ChatGPT-3.5, 2023-11-23]

18.2.2 Alles ist API

Die Python- (oder R-)Pakete zum Zugriff zur ChatGPT-API sind letztlich nur Wrapper um den “direkten” Zugriff auf die API.

Definition 18.1 (API (Application Programming Interface)) Eine API stellt genormte Befehle zur Verfügung, um von einem Dienst (über eine Webseite) Daten abzufragen oder einzustellen. \(\square\)

18.2.3 curl

curl ist eine gängige Methode (auf der Kommondozeile), um Daten von einer Webseite abzufragen (oder einzustellen).

Laut Dokumentation der API lautet der CURL-Befehl wie folgt (Pseudocode):

```{zsh}
#| eval: false
curl https://api.openai.com/v1/chat/completions \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $OPENAI_API_KEY" \
  -d '{
  "model": "gpt-3.5-turbo",
  "messages": [{"role": "user", "content": "Was ist die Frage zur Antwort '42'?"}] ,
  "temperature": 0,
  "max_tokens": 256
}'
```

Entscheidend ist der “Endpunkt” der URL: completions.

Hinweis

OpenAi stellt eine Reihe von spezialisierten Diensten zur Verfügung, z.B. zur Sentimentanalyse von Tweets oder, natürlich, Textgeneration, und vieles mehr. \(\square\)

Obige Syntax übersetzt sich so nach Python:

# This code is for v1 of the openai package: pypi.org/project/openai
from openai import OpenAI
client = OpenAI()

response = client.chat.completions.create(
  model="gpt-3.5-turbo",
  messages=[],
  temperature=0,
  max_tokens=256
)

18.2.4 Prompting

Als Prompt kann man z.B. übergeben (bezeichnet als “System”):

🧑‍🦰 You will be provided with a tweet, and your task is to classify its sentiment as positive, neutral, or negative. USER

Dann kommt der zu klassifizierende Textschnipsel (bezeichent als “user”):

📄 I loved the new Batman movie!

Und schließlich antwortet der Bot:

🤖 positive

Es ist günstig, dem Bot zu sagen, in welcher Sprache der Tweet ist. Außerdem ist es nützlich, den Prompt (die Anweisung) bereits in der Zielsprache zu formulieren.

prompt_stem <- "Nach dem Doppelpunkt folgt ein Tweet in deutscher Sprache. Klassifizieren Sie das Sentiment dieses Tweets als positiv, neutral oder negativ. Antworten Sie nur mit einem Wort. Hier ist der Tweet: "
prompts <- 
  str_c(prompt_stem, tweets)

prompts
## [1] "Nach dem Doppelpunkt folgt ein Tweet in deutscher Sprache. Klassifizieren Sie das Sentiment dieses Tweets als positiv, neutral oder negativ. Antworten Sie nur mit einem Wort. Hier ist der Tweet: @corinnamilborn Liebe Corinna, wir würden dich gerne als Moderatorin für uns gewinnen! Wärst du begeisterbar?"                                 
## [2] "Nach dem Doppelpunkt folgt ein Tweet in deutscher Sprache. Klassifizieren Sie das Sentiment dieses Tweets als positiv, neutral oder negativ. Antworten Sie nur mit einem Wort. Hier ist der Tweet: @Martin28a Sie haben ja auch Recht. Unser Tweet war etwas missverständlich. Dass das BVerfG Sachleistungen nicht ausschließt, kritisieren wir."

18.2.5 Anmelden an der API

Die API erlaubt nur Zugriffe angemeldeter Nutzer.

OPENAI_API_KEY <- Sys.getenv("OPENAI_API_KEY")

Damit eine Environment-Variable OPENAI_API_KEY ausgelesen werden kann, muss sie in .Rprofile definiert sein. Alternativ kann man aber die Variable auch auf anderen Wegen definieren, etwa aus einer Textdatei einlesen.

Wichtig

Lassen Sie sensible Daten, wie API-Keys, niemals auf öffentlichen Ordnern oder Repos (etwa auf Github) herumliegen. Stellen Sie sich vor, Sie haben bei dem Dienst ihre Kreditkarte hinterlege und ein übelwollender Dritter nutzt kostenpflichtige Dienste mit sehr hohem Budget. 🤯 \(\square\)

18.3 Wrapper um curl

Dieser Abschnitt basiert auf einem Blogpost bei R-Bloggers von Rasmus Bååth.

response <- POST(
  # curl https://api.openai.com/v1/chat/completions 
  url = "https://api.openai.com/v1/chat/completions", 
  # -H "Authorization: Bearer $OPENAI_API_KEY"
  add_headers(Authorization = 
                paste("Bearer", Sys.getenv("OPENAI_API_KEY"))),
  # -H "Content-Type: application/json"
  content_type_json(),
  # -d '{
  #   "model": "gpt-3.5-turbo",
  #   "messages": [{"role": "user", "content": "What is a banana?"}] 
  # }'
  encode = "json",
  body = list(
    model = "gpt-3.5-turbo",
    messages = list(list(role = "user", content = prompts[1]))
  ))
content(response)
$object
[1] "chat.completion"

$created
[1] 1700753610

$model
[1] "gpt-3.5-turbo-0613"

$choices
$choices[[1]]
$choices[[1]]$index
[1] 0

$choices[[1]]$message
$choices[[1]]$message$role
[1] "assistant"

$choices[[1]]$message$content
[1] "Das Sentiment dieses Tweets ist positiv. "

$choices[[1]]$finish_reason
[1] "stop"

$usage
$usage$prompt_tokens
[1] 76

$usage$completion_tokens
[1] 10

$usage$total_tokens
[1] 86

Der für uns entscheidende Punkt ist:

str_trim(content(response)$choices[[1]]$message$content)
Das Sentiment dieses Tweets ist positiv. 

18.3.1 Curl-Wrapper in eine Funktion gebracht

ask_chatgpt <- function(prompt) {
response <- POST(
  # curl https://api.openai.com/v1/chat/completions 
  url = "https://api.openai.com/v1/chat/completions", 
  # -H "Authorization: Bearer $OPENAI_API_KEY"
  add_headers(Authorization = 
                paste("Bearer", Sys.getenv("OPENAI_API_KEY"))),
  # -H "Content-Type: application/json"
  content_type_json(),
  # -d '{
  #   "model": "gpt-3.5-turbo",
  #   "messages": [{"role": "user", "content": "What is a banana?"}] 
  # }'
  encode = "json",
  body = list(
    model = "gpt-3.5-turbo",
    messages = list(list(role = "user", content = prompt))
  ))
  str_trim(content(response)$choices[[1]]$message$content)
}

18.3.2 Schleife

prompts |> 
  sapply(ask_chatgpt)

sapply ist sehr ähnlich wie map aus purrr, braucht aber kein Extrapaket.

Nach dem Doppelpunkt folgt ein Tweet in deutscher Sprache. Klassifizieren Sie das Sentiment dieses Tweets als positiv, neutral oder negativ. Antworten Sie nur mit einem Wort. Hier ist der Tweet: @corinnamilborn Liebe Corinna, wir würden dich gerne als Moderatorin für uns gewinnen! Wärst du begeisterbar? 

"positiv" 
 
Nach dem Doppelpunkt folgt ein Tweet in deutscher Sprache. Klassifizieren Sie das Sentiment dieses Tweets als positiv, neutral oder negativ. Antworten Sie nur mit einem Wort. Hier ist der Tweet: @Martin28a Sie haben ja auch Recht. Unser Tweet war etwas missverständlich. Dass das BVerfG Sachleistungen nicht ausschließt, kritisieren wir. 

 "neutral"

18.4 Einfache Anfrage an die OpenAI-API

Wichtig

Der API-Aufruf von ChatGPT kostet Geld 💸. \(\square\)

18.4.1 Authentifizierung

Wir müssen uns zunächst bei der API anmelden:

openai_key_r <- Sys.getenv("OPENAI_API_KEY")
openai_key_py = os.environ.get("OPENAI_API_KEY")
Vorsicht

Speichern Sie keine sensiblen Daten in geteilten Ordner/Repos. Achten Sie auch auf Log-Dateien wir .Rhistory, in der u.U. Ihre sensiblen Daten enthalten sein können. \(\square\)

Eine sichere Variante als das unverschlüsselte Speichern von Passwörtern ist es, sensible Daten mit einem Passwort zu schützen. Dazu kann man z.B. in R das Paket keyring nutzen. Dieses Paket greift auf die Schlüsselbundverwaltung Ihres Betriebssystems zurück (sowohl Windows, Mac, als auch (einige?) Linux-Distrubitionen).

library(keyring)
openai_key_r <- key_get("OPENAI_API_KEY")

18.4.2 Setup

sentiment_scores = []
sentiment_analysis = []
text = '@Martin28a Sie haben ja auch Recht. Unser Tweet war etwas missverständlich. Dass das BVerfG Sachleistungen nicht ausschließt, kritisieren wir.'

18.4.3 Anfrage an die API

prompt = f"Analysiere das Sentiment des folgenden Texts: \n{text}"

response = openai.Completion.create(
        prompt=prompt,
        engine="davinci",
        max_tokens=100,
        temperature=0.5,
    )

18.5 Vertiefung

Mit etwas Zusatzaufwand kann man den Kontext bzw. den Verlauf der Konversation mit dem Bot berücksichtigen, wie dieser Post zeigt.

Die OpenAI-API bietet ebenfalls Fine-Tuning an, wie in diesem Post beschrieben.

18.6 Aufgaben

Schauen Sie sich die Aufgaben mit dem Tag ‘Transformer’ auf dem Datenwerk an.