In der heutigen, schnelllebigen digitalen Welt haben dialogorientierte Anwendungen mit dem Aufstieg Großer Sprachmodelle (LLMs) enorm an Bedeutung gewonnen. Chatbots haben die Art und Weise verändert, wie wir im Alltag mit Apps, Websites und sogar mit dem Kundenservice interagieren.
Die Fähigkeit, einen eigenen Chatbot zu bauen, ist ein echter Game-Changer. Sie ermöglicht es Unternehmen und Einzelpersonen, persönliche, effiziente und überzeugende Gesprächsoberflächen zu entwickeln, die exakt auf ihre Bedürfnisse zugeschnitten sind.
Dieser Artikel zeigt dir Schritt für Schritt, wie du in Python einen Chatbot mit der Power von LLMs entwickelst. Wir bauen einen eigenen Chatbot mit ChatGPT und optimieren ihn anschließend mit dem beliebten LangChain-Framework.
Wenn du neu bei ChatGPT und LangChain bist, schau dir unser Introduction to Large Language Models with GPT & LangChain AI Code-along unten an:
Dieser Artikel deckt alle wichtigen Aspekte für die Entwicklung eines optimierten Chatbots ab, der ChatGPT als zugrunde liegendes Modell nutzt. Wir arbeiten in einem Jupyter Notebook mit den Bibliotheken openai und langchain. Du kannst im DataLab-Workbook mitmachen.
In den nächsten Abschnitten nutzen wir die OpenAI API für grundlegende Aufrufe an ChatGPT als Bausteine unseres Dialogs. Außerdem implementieren wir Kontextbewusstsein für unseren Chatbot mit einfachen Python-Strukturen. Zum Schluss optimieren wir sowohl die Chatbot-Implementierung als auch die Gesprächshistorie mit LangChain und schauen uns dessen Memory-Funktionen an.
Grundlegende ChatGPT-API-Aufrufe
Wir verwenden ChatGPT als Modell hinter unserem Chatbot. Deshalb besteht unsere erste praktische Aufgabe darin, einen einfachen, aber vollständigen API-Aufruf an ChatGPT zu schreiben, um eine erste Interaktion herzustellen.
Die folgende Python-Funktion kapselt unseren gewünschten API-Aufruf an ChatGPT und nutzt dafür ausschließlich die openai-Bibliothek.
import os
import openai
openai_api_key = os.environ["OPENAI_API_KEY"]
def chatgpt_call(prompt, model="gpt-3.5-turbo"):
response = openai.ChatCompletion.create(
model=model,
messages=[{"role": "user", "content": prompt}]
)
return response.choices[0].message["content"]
Beachte, dass wir hier das Modell gpt-3.5-turbo verwenden, da es das leistungsfähigste GPT-3.5-Modell ist, für Chats optimiert und zudem eines der günstigsten Modelle. Mehr Informationen zu den verfügbaren Modellen findest du in der OpenAI-Dokumentation.
Brauchst du Hilfe beim Einrichten deiner OpenAI API-Keys? Dann ist das Training Getting Started with the OpenAI API and ChatGPT genau richtig für dich.
Schauen wir uns die Implementierung Schritt für Schritt an!
Die Funktion chatgpt_call() ist ein Wrapper um den ursprünglichen OpenAI-Aufruf ChatCompletion.create, der eine Chat-Vervollständigung anfordert.
Bei einem gegebenen Prompt liefert die Funktion die entsprechende ChatGPT-Antwort. Für einen erfolgreichen Aufruf müssen lediglich die Modell-ID (model) und der gewünschte Prompt (prompt) übergeben werden.
Die von ChatGPT zurückgegebene Completion enthält Metadaten zusammen mit der eigentlichen Antwort. Die Antwort selbst steht jedoch immer im Feld response.choices[0].message[“content”]. Mehr Informationen zum kompletten Antwortobjekt findest du in der OpenAI-Dokumentation.
Mit dieser Implementierung können wir ChatGPT mit nur einer Zeile ansprechen:
prompt = "My name is Andrea"
response = chatgpt_call(prompt)
print(response)
# Output: Nice to meet you, Andrea! How can I assist you today?
Der Aufruf als Einzelinteraktion
Fällt dir ein Haken an der obigen Implementierung auf?
Als Hinweis machen wir eine Folgefrage:
prompt = "Do you remember my name?"
response = chatgpt_call(prompt)
print(response)
# Output: I'm sorry, as an AI language model, I don't have the ability to remember specific information about individual users.
Genau, wie du siehst, ist jeder API-Aufruf eine eigenständige Interaktion. Das Modell erinnert sich nicht an frühere Prompts oder Antworten. Jedes Mal, wenn wir chatgpt_call() aufrufen, beginnt eine völlig neue Unterhaltung.
Das ist beim Bau eines Chatbots problematisch, denn ohne Gedächtnis ist keine echte Unterhaltung möglich. Deshalb müssen wir als Nächstes dafür sorgen, dass das Modell frühere Interaktionen kennt.
Kontextbewusstsein
Kontextbewusstsein ermöglicht es Chatbots, eine laufende Unterhaltung zu verstehen und passend zu reagieren.
Mit wachsendem Kontext lassen sich künftige Interaktionen auf Basis der bisherigen personalisieren. So bleibt der rote Faden erhalten und Wiederholungen werden vermieden. Außerdem können Nutzende mit kontextbewussten Chatbots gezielt nachfragen oder Missverständnisse ausräumen.
Insgesamt steigert Kontextbewusstsein Natürlichkeit, Wirksamkeit und Nutzererlebnis von Chatbots. Ab jetzt nennen wir die Sammlung früherer Interaktionen Gesprächshistorie oder Memory.
Die originale OpenAI-API für ChatGPT erlaubt es bereits, die Gesprächshistorie zusammen mit jedem neuen Prompt zu senden. Zusätzlich bietet das LangChain-Framework Möglichkeiten, das Gedächtnis von ChatGPT effizienter und kostengünstiger zu verwalten.
System-, Assistant- und User-Nachrichten verstehen
Struktur der ChatGPT-Gesprächshistorie
Aktuell können wir chatgpt_call() einfach mit dem gewünschten Prompt aufrufen. Ein Blick in die Implementierung zeigt: Der Prompt wird als Teil der Liste messages formatiert. Diese Liste dient eigentlich dazu, die Gesprächshistorie abzubilden, und muss daher in einer ganz bestimmten Struktur vorliegen.
Schauen wir genauer hin!
Bislang haben wir die Nachrichtenliste in chatgpt_call() so formatiert:
messages = [{"role": "user", "content": prompt}]
Die Liste messages speichert die Historie als Sammlung von Dictionaries. Jedes Dictionary repräsentiert eine Nachricht in der Unterhaltung mit zwei Schlüssel-Wert-Paaren.
- Der Schlüssel
rolemit dem Wertusergibt an, dass die Nachricht vom Nutzer stammt. - Der Schlüssel
contentmit dem Wertpromptenthält den eigentlichen Prompt.
Das ist ein sehr einfaches Beispiel mit nur einer Interaktion. Während content immer den Nutzerprompt oder die Antwort des Chatbots enthält, ist role deutlich vielseitiger.
System-, Assistant- und User-Rollen
Der Schlüssel role kennzeichnet den Absender der Nachricht in content. Mit ChatGPT gibt es bis zu drei Rollen:
- User. Wie besprochen, steht die Rolle
userfür die Person, die mit dem Chatbot interagiert. - Assistant. Die Rolle
assistantsteht für den Chatbot. - System. Die Rolle
systemdient dazu, dem Chatbot Anweisungen, Leitplanken und Kontext zu geben. Systemnachrichten steuern das Verhalten, liefern Kontextinformationen und regeln spezielle Interaktionen innerhalb des Dialogs. Man kann sie als übergeordnete, für den Nutzer transparente Instruktionen verstehen.

Selbst erstellte Grafik. Zusammenspiel der Rollen in der Gesprächskette beim Bau eines Chatbots mit ChatGPT.
Eine Gesprächshistorie aufbauen
Wir können unsere Gesprächshistorie aufbauen, indem wir die drei Rollen user, assistant und system in der messages-Liste nutzen.
Ausgehend von unseren Testinteraktionen könnte eine korrekt formatierte Historie mit allen drei Rollen so aussehen:
messages = [
{'role':'system', 'content':'You are friendly chatbot.'},
{'role':'user', 'content':'Hi, my name is Andrea'},
{'role':'assistant', 'content': "Nice to meet you, Andrea! How can I assist you today?"},
{'role':'user', 'content':'Do you remember my name?'} ]
Wir können unsere Funktion call_chatgpt() leicht anpassen, um die Gesprächshistorie als Eingabeparameter zu übergeben:
def call_chatgpt_with_memory(messages, model="gpt-3.5-turbo"):
response = openai.ChatCompletion.create(
model=model,
messages=messages,
)
return response.choices[0].message["content"]
Probieren wir die neue Implementierung aus!
messages = [
{'role':'system', 'content':'You are friendly chatbot.'},
{'role':'user', 'content':'Hi, my name is Andrea'},
{'role':'assistant', 'content': "Nice to meet you, Andrea! How can I assist you today?"},
{'role':'user', 'content':'Do you remember my name?'} ]
response = call_chatgpt_with_memory(messages)
print(response)
# Output: Yes, your name is Andrea.
Super! Das Modell kennt nun unsere bisherigen Interaktionen und kann wirklich mit uns sprechen. Jetzt brauchen wir noch eine Möglichkeit, die Liste messages automatisch zu aktualisieren.
Automatische Gesprächshistorie
Die automatische Sammlung der Nachrichten erreichen wir, indem wir unsere API-Aufruffunktion etwas erweitern:
def chatgpt_conversation(prompt):
context.append({'role':'user', 'content':f"{prompt}"})
response = call_chatgpt_with_memory(context)
context.append({'role':'assistant',
'content':f"{response}"})
return response
Und los geht’s!
chatgpt_conversation("Hello, my name is Andrea")
# Output: 'Nice to meet you, Andrea! How can I assist you today?'
Und eine Folgefrage:
chatgpt_conversation("Do you remember my name?")
# Output: 'Yes, your name is Andrea.'
Jetzt wird die Gesprächshistorie automatisch gepflegt. Du kannst dir die Historie – und ihre Struktur – jederzeit ansehen, indem du die Liste messages ausgibst.
print(messages)
# Output:
[{'role': 'user', 'content': 'Hello, my name is Andrea'},
{'role': 'assistant', 'content': 'Nice to meet you, Andrea! How can I assist you today?'},
{'role': 'user', 'content': 'Do you remember my name?'},
{'role': 'assistant', 'content': 'Yes, your name is Andrea.'}]
Das LangChain-Framework
Bis hierhin haben wir einen nützlichen Chatbot, aber noch nicht die optimierteste Variante.
Wenn wir bei jedem neuen Aufruf alle bisherigen Prompts und Antworten mitsenden, wächst die Zahl der Tokens schnell stark an, und ChatGPT muss sie vor jeder neuen Antwort komplett verarbeiten.
Bisher haben wir per .append die Historie zusammengebaut, damit der Chatbot ein Gedächtnis hat. Eine effektivere Verwaltung gelingt jedoch mit spezialisierten Paketen wie LangChain.
LangChain ist ein Framework für Anwendungen auf Basis von Sprachmodellen. Da Chatbots heute zu den beliebtesten Anwendungen zählen, bietet LangChain eine aufgeräumte Implementierung für die Gesprächshistorie und weitere Optionen für deren effiziente Handhabung.
Mehr zum LangChain-Framework findest du im einführenden Artikel Introduction to LangChain for Data Engineering & Data Applications.
Einfache Gesprächshistorie mit LangChain
In diesem Abschnitt zeigen wir, wie du den Chatbot stattdessen mit dem LangChain-Framework aufbaust.
Die Bibliothek langchain kann die zuvor genutzte openai-Bibliothek ersetzen, da das Modul langchain.llms bereits die Interaktion mit ChatGPT in zwei Codezeilen erlaubt:
# OpenAI key from environment
from langchain.llms import OpenAI
llm = OpenAI()
Sobald das Modell llm geladen ist, importieren wir das Memory-Objekt, das unsere Gesprächshistorie speichert. Zum Ablegen jeder vergangenen Interaktion stellt LangChain die ConversationBufferMemory bereit:
from langchain.memory import ConversationBufferMemory
memory = ConversationBufferMemory()
Jetzt importieren wir die ConversationChain als Wrapper, der llm und memory nutzt, um den Nutzerprompt an ChatGPT zu senden und die Antworten zurückzugeben:
from langchain.chains import ConversationChain
conversation = ConversationChain(
llm=llm,
memory=memory
)
Zum Senden eines Prompts an ChatGPT verwenden wir die Methode .predict wie folgt:
conversation.predict(input="Hello, my name is Andrea")
# Output: "Hi Andrea, I'm an AI created to help you with whatever you may need. What can I do for you today?"
Setzen wir verbose=True, um zu sehen, welche Informationen das Modell für seine Antwort heranzieht. Probieren wir es in einer Folgeinteraktion:

Screenshot des verfügbaren Kontexts für ChatGPT bei der Antworterzeugung. Verbose-Ausgabe in Grün.
Zum Schluss prüfen wir, dass sich der Chatbot perfekt an frühere Interaktionen erinnert:

Screenshot des verfügbaren Kontexts für ChatGPT bei der Antworterzeugung. Verbose-Ausgabe in Grün.
Die Gesprächshistorie befüllen
Zusätzlichen Kontext übergibst du mit memory.save_context(). So machst du den Chatbot mit beliebigen Inhalten vertraut, ohne sie im Dialog selbst nennen zu müssen. In LangChain heißen die Schlüssel dafür input und output. Zum Beispiel:
memory.save_context({"input": "Hi"},
{"output": "What's up"})
memory.save_context({"input": "Not much, just hanging"},
{"output": "Cool"})
Außerdem kannst du den Speicherinhalt jederzeit einsehen, etwa mit print(memory.buffer) oder memory.load_memory_variables({}).
LangChain-Memory-Typen
LangChain implementiert verschiedene Memory-Varianten für ChatGPT. Die oben genutzte ConversationBufferMemory hält jede einzelne Interaktion fest. Wie bereits erwähnt, kann das Mitsenden der gesamten Historie jedoch schnell zu vielen Tokens pro Interaktion führen.
Beachte, dass ChatGPT neben einem Token-Limit pro Interaktion auch nach genutzten Tokens abgerechnet wird. Die gesamte Historie bei jedem neuen Austausch zu verarbeiten, wird auf Dauer wahrscheinlich teuer.
Um das zu vermeiden, bietet LangChain weitere, optimierte Möglichkeiten für das Gedächtnismanagement. Eine der fortgeschrittensten Varianten schauen wir uns im nächsten Abschnitt an.
Erweiterte Gesprächshistorie mit LangChain
Ein fortgeschrittener Ansatz speichert statt jeder einzelnen Nachricht nur eine Zusammenfassung der bisherigen Interaktionen.
Diese Variante ist mein Favorit, da sie ein deutlich optimiertes Gedächtnismanagement ermöglicht, ohne Inhalte zu verlieren.
Legen wir los!
# OpenAI key from environment
from langchain.llms import OpenAI
llm = OpenAI()
from langchain.memory import ConversationSummaryBufferMemory
memory = ConversationSummaryBufferMemory(llm=llm, max_token_limit=100)
Beachte, dass dieser Memory-Typ das definierte llm nutzt, um die Zusammenfassung der vorherigen Interaktionen zu erzeugen.
Außerdem speichert dieser Speicher vollständige Interaktionen bis zu einer maximalen Token-Anzahl (max_token_limit). Oberhalb dieses Limits fließen die Informationen in die Zusammenfassung ein.
Zum Testen erzeugen wir eine Interaktion mit vielen Tokens. Angenommen, folgender schedule:
schedule = "There is a meeting at 8am with your product team. \
You will need your powerpoint presentation prepared. \
9am-12pm have time to work on your LangChain \
project which will go quickly because Langchain is such a powerful tool. \
At Noon, lunch at the italian resturant with a customer who is driving \
from over an hour away to meet you to understand the latest in AI. \
Be sure to bring your laptop to show the latest LLM demo."
memory = ConversationSummaryBufferMemory(llm=llm, max_token_limit=100)
memory.save_context({"input": "Hello"}, {"output": "What's up"})
memory.save_context({"input": "Not much, just hanging"}, {"output": "Cool"})
memory.save_context({"input": "What is on the schedule today?"}, {"output": f"{schedule}"})
Jetzt deklarieren wir – wie zuvor – eine neue Conversation Chain mit llm und memory.
conversation = ConversationChain(
llm=llm,
memory=memory,
verbose=True
)
Mit .predict geben wir Prompts an ChatGPT:

Screenshot des verfügbaren Kontexts für ChatGPT bei der Antworterzeugung. Verbose-Ausgabe in Grün.
Wie zu sehen, hält der Chatbot eine Zusammenfassung der ursprünglichen Historie vor und reduziert so die Tokens, die ChatGPT bei jeder Folgeinteraktion verarbeiten muss.
Das war’s! Jetzt kannst du stundenlang mit deinem optimierten Chatbot weiterreden!
Setze verbose=False, um in deinem Notebook eine angenehmere Q&A-Erfahrung zu haben.
Fazit
In diesem Artikel hast du gelernt, wie du mit dem bekannten ChatGPT-Modell einen kontextbewussten Chatbot aufbaust.
Du hast gesehen, wie ein eigener, kontextsensitiver Chatbot kontinuierliche Interaktionen ermöglicht. Dieser iterative Ansatz fördert eine ständige Feedback-Schleife, sodass du Bedürfnisse und Präferenzen der Nutzenden im Verlauf des Gesprächs berücksichtigst.
Wir haben zwei Hauptansätze für das Management der Gesprächshistorie betrachtet: Erstens über die native ChatGPT-API für einen funktionsfähigen Chatbot. Zweitens über das spezialisierte LangChain-Framework mit einer optimierten Memory-Implementierung.
Jetzt bist du dran! Nutze die Bausteine aus diesem Artikel, um einen Chatbot für deinen konkreten Use Case zu entwickeln.
Wenn du weitere Anwendungen mit LangChain und ChatGPT bauen möchtest, ist das Webinar Building AI Applications with LangChain and GPT ideal. Willst du bei NLP aufholen? Dann schau dir die Module zu Natural Language Processing in Python an!
Andrea Valenzuela arbeitet derzeit am CMS-Experiment am Teilchenbeschleuniger (CERN) in Genf, Schweiz. Seit sechs Jahren ist sie Expertin für Datentechnik und -analyse. Zu ihren Aufgaben gehören Datenanalyse und Softwareentwicklung. Mit der Medium-Publikation ForCode'Sake setzt sie sich für die Demokratisierung des Lernens von datenbezogenen Technologien ein.
Sie hat einen BS in technischer Physik von der Polytechnischen Universität von Katalonien und einen MS in intelligenten interaktiven Systemen von der Universität Pompeu Fabra. Zu ihren Forschungserfahrungen gehört die professionelle Arbeit mit früheren OpenAI-Algorithmen zur Bilderzeugung, wie Normalizing Flows.
