LangChain: Chatten Sie mit Ihren Dokumenten!

Dieser Beitrag wurde mittels KI automatisch aus dem englischen Original übersetzt. Original lesen

LangChain: Chatten Sie mit Ihren Dokumenten!

In einem kürzlich erschienenen Artikel haben wir LangChain vorgestellt, ein leistungsstarkes Open-Source-Framework für die Entwicklung von Anwendungen, die auf Sprachmodellen (LLMs) basieren. In diesem Artikel widmen wir uns einem komplexeren Beispiel und zeigen Ihnen, wie Sie einen Chatbot entwickeln, der Ihre Dokumente versteht.

Problemstellung

Das Hauptproblem, das wir lösen müssen, besteht darin, dass das Sprachmodell den Inhalt unserer Dokumente nicht kennt. Wenn wir also eine Frage zu unseren Dokumenten stellen, kann das Sprachmodell keine sinnvolle Antwort geben.

Um dieses Problem zu lösen, müssen wir einen Mechanismus entwickeln, der den Inhalt unserer Dokumente in die Wissens- und Schlussfolgerungsprozesse des Sprachmodells integriert. Diese Integration soll es dem Modell ermöglichen, den Dokumentinhalt effektiv zu verstehen und zu nutzen, um so relevante, aufschlussreiche und kontextuell angemessene Antworten auf Fragen zu diesen Dokumenten zu liefern.

Schauen wir uns einige mögliche Lösungsansätze an.

Die einfachste Lösung: das Dokument als Kontext zusammen mit dem Prompt übergeben

Wir können den Text unseres Dokuments einfach als Kontext zusammen mit dem Prompt an das Sprachmodell übergeben. Das ist natürlich die einfachste Lösung, hat aber einige Nachteile:

  • Jedes LLM hat ein Token-Limit, d. h. die maximale Anzahl an Tokens, die es auf einmal verarbeiten kann (4k bei GPT-3), sodass wir sehr eingeschränkt sind, wie viel Kontext wir mitgeben können
  • Selbst wenn wir dieses Limit umgehen, indem wir ein Modell mit einem 16k-Token-Limit wählen, muss bedacht werden, dass der (umfangreiche) Kontext bei jeder Anfrage mitgesendet wird und die Abrechnung pro Token erfolgt.

Wenn Sie diese Nachteile für akzeptabel halten, können Sie diesen Ansatz ausprobieren.

Die kostspielige Lösung: das Sprachmodell auf Ihren Dokumenten feintunen

Das Feintuning des Sprachmodells mit einem sorgfältig kuratierten Datensatz, der Ihre Dokumente enthält, kann bemerkenswerte Leistungsverbesserungen erzielen. Dieser Prozess passt das Modell an, indem eine neue Version auf Basis einer bestehenden erstellt wird, die es dem Modell ermöglicht, die spezifische Domäne und Sprache Ihrer Dokumente zu verstehen, was zu relevanteren und kontextuell angemesseneren Antworten führt. Um ein großes Sprachmodell für aufschlussreiche und kohärente Gespräche über Ihre Dokumente auszustatten, müssen die Herausforderungen des Feintunings, der Bereitstellung und des Betriebs des Modells effektiv und effizient bewältigt werden.

In Fällen, in denen Sie einen großen Dokumentendatensatz haben, kann dieser Ansatz sehr effektiv sein. Es ist jedoch wichtig zu beachten, dass diese Technik sehr kostspielig ist, da sie viel Rechenleistung und Zeit für das Feintuning eines Sprachmodells erfordert.

OpenAI bietet ebenfalls die Möglichkeit zum Feintuning ihrer LLMs an, was ein sehr interessanter Ansatz ist, wenn Ihr Dokument nicht vertraulich ist und Sie es mit OpenAI teilen können.

Wir werden hier nicht ins Detail gehen, behalten dies aber für einen zukünftigen Artikel im Hinterkopf.

Die intelligente Lösung: Retrieval-Augmented Generation (RAG)

Schauen wir uns zunächst die Definition von Retrieval-Augmented Generation an:

Retrieval-Augmented Generation ist eine ausgefeilte Technik in der natürlichen Sprachverarbeitung (NLP), die Informationsabruf und generative Sprachmodelle kombiniert. Sie nutzt einen bereits vorhandenen Datensatz oder eine Wissensbasis, um relevante Informationen abzurufen, die dann in den Generierungsprozess integriert werden. Dieser Ansatz verbessert die Qualität und Relevanz des generierten Textes und ermöglicht es dem Modell, kontextuell genauere und informativere Antworten zu erzeugen. Durch die nahtlose Verbindung von Abruf und Generierung befähigt es NLP-Systeme, bei Aufgaben wie Fragebeantwortung, Inhaltserstellung und Chatbots hervorragende Leistungen zu erbringen, bei denen kontextuelles Verständnis und faktische Genauigkeit von entscheidender Bedeutung sind.

Einfach ausgedrückt: Retrieval-Augmented Generation ist eine Methode, um bessere und genauere Sätze oder Antworten zu generieren, indem ein Abrufsystem verwendet wird, das relevante Informationen („semantische Suche”) beschafft und diese an das Sprachmodell weitergibt, welches dann die endgültige Antwort basierend auf dem Prompt und den abgerufenen Informationen generiert. Es ist eine elegante Möglichkeit, computergenerierten Text intelligenter und nützlicher zu machen, ohne vollständige Dokumente und Informationen an das Sprachmodell übergeben zu müssen.

Mit einem leistungsstarken Framework wie LangChain wird die Entwicklung eines Chatbots unter Verwendung eines bestehenden LLM zu einem nahtlosen Prozess. Durch die Nutzung der bereitgestellten Abstraktionen für Embeddings, Speicherung und Abruf mit Vektordatenbanken und Konversations-Chains können Sie problemlos einen Chatbot entwickeln, der Ihre Dokumente versteht. Die LangChain-Implementierungen stellen sicher, dass nur relevanter Kontext an das LLM übergeben wird, wodurch unsere Herausforderung, sinnvolle Gespräche über Ihre Dokumente zu führen, effektiv gelöst wird.

Schauen wir uns an, wie das im Detail funktioniert.

Überblick über Retrieval-Augmented Generation (RAG)

Um mit unseren Dokumenten chatten zu können, sind einige Vorbereitungen nötig. Wir erstellen einen Ingester, der unsere Dokumente liest, sie in sinnvolle Chunks/Fragmente aufteilt und Embeddings dafür erstellt. Diese Embeddings werden in einer Vektordatenbank gespeichert. Wenn wir später über unsere Dokumente chatten und eine Frage stellen, können wir ein Embedding für unsere Frage erstellen und die Vektordatenbank nach den ähnlichsten (= relevantesten) Textfragmenten aus unseren Dokumenten abfragen. Diese Fragmente werden dann zusammen mit dem Prompt als Kontext an das LLM übergeben. Das LLM hat dann die Frage und (hoffentlich) genügend relevanten Kontext, um eine sinnvolle Antwort zu generieren, die dann an den erfreuten Nutzer zurückgegeben wird.

Überblick
Überblick

Das Erstellen von Embeddings für Textfragmente aus unseren Dokumenten und deren Speicherung in einer Vektordatenbank für den späteren Abruf ermöglicht einen effizienten und schnelleren Zugriff auf relevante Informationen während der Gespräche. Dieser Ansatz hilft uns, relevante Fragmente Ihrer Dokumente schnell und effizient zu finden, die wir dann an das LLM übergeben, was zu kontextuell angemesseneren Antworten führt.

Embeddings

Aber halt, was sind Embeddings? Embeddings sind eine Methode, um Wörter, Phrasen oder sogar ganze Dokumente als numerische Vektoren in einem mehrdimensionalen Raum darzustellen. Das OpenAI-Embedding-Modell „text-embedding-ada-002” erstellt beispielsweise Vektoren mit 1536 Dimensionen. Diese Vektoren sind so konzipiert, dass sie bedeutungsvolle Beziehungen und Ähnlichkeiten zwischen den Textfragmenten erfassen, die sie repräsentieren.

Stellen Sie sich vor, Sie haben ein Wörterbuch und möchten jedes Wort darin als einen einzigartigen Satz von Zahlen darstellen. Bei guten Embeddings hätten Wörter, die in Bedeutung oder Kontext ähnlich sind, ähnliche numerische Darstellungen, und Wörter, die sich unterscheiden, hätten unterschiedliche Darstellungen.

Zum Beispiel könnten in einem gut konzipierten Word-Embedding-Raum die Vektoren für „König” und „Königin” sehr nahe beieinander liegen, da sie in Bedeutung und Kontext verwandt sind, während der Vektor für „König” weit entfernt vom Vektor für „Apfel” wäre, da diese nicht verwandt sind.

Embeddings in einem zweidimensionalen Raum
Embeddings in einem zweidimensionalen Raum

Diese numerischen Darstellungen sind äußerst nützlich für verschiedene Aufgaben der natürlichen Sprachverarbeitung wie maschinelle Übersetzung, Sentimentanalyse und Dokumentenähnlichkeit, da sie es Computern ermöglichen, Textdaten auf eine strukturiertere und bedeutungsvollere Weise zu verstehen und zu verarbeiten.

Wir nutzen Embeddings und die Ähnlichkeitsabruf-Mechanismen von Vektordatenbanken für unseren Chatbot, um semantische Suche durchzuführen. Dies ermöglicht es uns, relevante Informationen zur aktuellen Frage effektiver zu identifizieren.

Herausforderung: Token-Limit

Dieser Ansatz ist kein Allheilmittel, und es gibt einige Herausforderungen, die wir bewältigen müssen: Das Token-Limit ist nach wie vor ein Thema, selbst wenn wir kleinere Textfragmente (z. B. 1000 Zeichen) an das LLM übergeben. Wir müssen die Größe und Anzahl der Fragmente so planen, dass alles in das Token-Limit passt, das auch den Chat-Verlauf und die Frage selbst umfasst.

LangChain implementiert mehrere Strategien, um dieses Problem in seinen DocumentChains zu lösen:

  • „stuff”: alles unverändert hineinstopfen
  • „refine”: ein Dokument/Fragment nach dem anderen an das LLM übergeben und das Ergebnis mit dem nächsten Dokument/Fragment verfeinern
  • „map reduce”: die Essenz jedes einzelnen Dokuments (bezogen auf die Frage) extrahieren, die Ergebnisse kombinieren und das kombinierte Ergebnis als Kontext zusammen mit der Frage an das LLM für die endgültige Antwort übergeben
  • „map rerank”: versuchen, die Frage mit jedem Dokument/Fragment einzeln zu beantworten und die Antworten bewerten, die am höchsten bewertete Antwort auswählen und als endgültige Antwort an den Nutzer zurückgeben

Die Verwendung dieser Lösung ist eine intelligente Möglichkeit, mit Ihren Dokumenten zu „chatten” und gleichzeitig das Token-Limit und das Kostenproblem zu adressieren. Mit Hilfe von LangChain ist die Implementierung zudem relativ einfach.

Schreiben wir etwas Code.

Implementierung

Für die Implementierung des Chats mit den Dokumenten mittels RAG benötigen wir eine Handvoll Komponenten:

  • einen PDF-Parser für die Dokumente zur Textextraktion (bildbasierte PDFs funktionieren nicht)
  • eine Komponente zum Aufteilen der Dokumente in Fragmente oder Chunks
  • einen Embeddings-Anbieter zur Erstellung von Embeddings für die Fragmente und die Fragen
  • eine Vektordatenbank zum Speichern und Abrufen der Embeddings und Dokumenten-Chunks
  • ein LLM zur Generierung der Antworten auf die Fragen und die gegebenen Dokumenten-Chunks

Wir nutzen die leistungsstarken LangChain-Abstraktionen, die uns bereits Zugang zu den erforderlichen Komponenten bieten und unsere Implementierung deutlich einfacher und kompakter machen. Um die Implementierung einer Benutzeroberfläche im nächsten Blogartikel zu erleichtern, kapseln wir die Funktionalität in eine Klasse, die geeignete Methoden für den Chat bereitstellt.

Wir haben uns für OpenAI für Embeddings und Chat sowie Chroma als Vektordatenbank entschieden. LangChain enthält Implementierungen für eine Vielzahl von LLMs und Vektordatenbanken, sodass Sie die für Ihre Anforderungen am besten geeignete wählen können.

Die ConversationalRetrievalChain erledigt die Hauptarbeit für uns: Sie basiert auf der RetrievalQAChain, die sich um den Abruf und die Generierung der Antwort kümmert, und fügt die Möglichkeit hinzu, den Chat-Verlauf des Gesprächs bereitzustellen. Wir müssen lediglich das LLM, den Embedding-Anbieter und die Vektordatenbank bereitstellen.

Wir implementieren die Klasse in einer Datei namens docchat.py:

import os
import sys

import openai
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.chains import ConversationalRetrievalChain
from langchain.document_loaders import PyPDFLoader

# LLM und Embeddings
from langchain.chat_models import ChatOpenAI
from langchain.embeddings.openai import OpenAIEmbeddings

# Alternatives LLM und Embeddings: GPT4All
# from langchain.llms import GPT4All
# from langchain.embeddings import GPT4AllEmbeddings

# Vektordatenbank - Embeddings speichern und abrufen
from langchain.vectorstores import Chroma

# Alternative für kleine Dokumente: DocArrayInMemorySearch statt Chroma, siehe DocChat._load_and_process_document
# from langchain.vectorstores import DocArrayInMemorySearch

openai.api_key = os.environ['OPENAI_API_KEY']
llm_name = "gpt-3.5-turbo"

# OpenAI für Chat-Completion verwenden
llm = ChatOpenAI(model_name=llm_name, temperature=0)
# OpenAI zur Erstellung von Embeddings für Chunks und Fragen verwenden
embeddings = OpenAIEmbeddings()

# Alternatives GPT4All, model muss auf die gewünschte Modelldatei zeigen
# llm = GPT4All(model="/home/alex/.local/share/nomic.ai/GPT4All/wizardlm-13b-v1.1-superhot-8k.ggmlv3.q4_0.bin", n_threads=8)
# embeddings = GPT4AllEmbeddings()

# Vektordatenbank erstellen zum Einfügen/Abfragen von Embeddings - aktuell werden die Embeddings nicht auf der Festplatte gespeichert
vectordb = Chroma(embedding_function=embeddings)


class Document:
    """
    Document-Klasse zum Speichern von Dokumentname und Dateipfad
    """
    def __init__(self, name: str, filepath: str):
        self.name = name
        self.filepath = filepath


class ChatResponse:
    """
    ChatResponse-Klasse zum Speichern der Chatbot-Antwort.
    """
    answer: str = None
    db_question: str = None
    db_source_chunks: list = None

    def __init__(self, answer: str, db_question: str = None, db_source_chunks: list = None):
        self.answer = answer
        self.db_question = db_question
        self.db_source_chunks = db_source_chunks

    def __str__(self) -> str:
        return f"{self.db_question}: {self.answer} with {self.db_source_chunks}"


class DocChat:
    """
    DocChat-Klasse zum Speichern der Informationen für das aktuelle Dokument und Bereitstellung von Chat-Methoden.
    """
    _qa: ConversationalRetrievalChain = None

    # String-Liste des Chat-Verlaufs
    _chat_history: list[(str, str)] = []

    # Enthält das aktuelle Dokument, über das wir chatten können
    doc: Document = None

    def process_doc(self, doc: Document) -> int:
        """
        Dokument verarbeiten (aufteilen, Embeddings erstellen und speichern) und die Chatbot-QA-Chain erstellen.
        :param doc:
        :return: Anzahl der erstellten Embeddings
        """
        self.doc = doc
        (self._qa, embeddings_count) = self._load_and_process_document(doc.filepath, chain_type="stuff", k=3)
        # Chat-Verlauf löschen, damit Prompts und Antworten vorheriger Dokumente nicht als Kontext berücksichtigt werden
        self.clear_history()
        return embeddings_count

    @staticmethod
    def _load_and_process_document(file: str, chain_type: str, k: int) -> (ConversationalRetrievalChain, int):
        loader = PyPDFLoader(file)
        documents = loader.load()
        # Dokument in Chunks von 1000 Zeichen mit 150 Zeichen Überlappung aufteilen - nach Bedarf anpassen
        text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=150)
        docs = text_splitter.split_documents(documents)

        # Sicherstellen, dass wir die Embeddings vorheriger Dokumente vergessen
        db = Chroma()
        db.delete_collection()
        # Vektordatenbank mit Embeddings für das aktuelle Dokument befüllen
        db = Chroma.from_documents(docs, embeddings)

        # Alternative: siehe https://python.langchain.com/docs/integrations/vectorstores/docarray_in_memory
        # db = DocArrayInMemorySearch.from_documents(docs, embeddings)

        # Retriever erstellen, `k` ähnlichste Chunks für jede Frage zurückgeben
        retriever = db.as_retriever(search_type="similarity", search_kwargs={"k": k})
        # Chatbot-Chain erstellen, Konversations-Memory wird extern verwaltet
        qa = ConversationalRetrievalChain.from_llm(
            llm=llm,
            chain_type=chain_type,
            retriever=retriever,
            return_source_documents=True,
            return_generated_question=True,
            verbose=True,
        )
        return qa, len(docs)

    def get_response(self, query) -> ChatResponse:
        if not query:
            return ChatResponse(answer="Please enter a question")
        if not self._qa:
            return ChatResponse(answer="Please upload a document first")
        # LLM die Antwort generieren lassen und im Chat-Verlauf speichern
        result = self._qa({"question": query, "chat_history": self._chat_history})
        answer = result["answer"]
        self._chat_history.extend([(query, answer)])
        return ChatResponse(answer, result["generated_question"], result["source_documents"])

    def clear_history(self):
        self._chat_history = []
        return


# Main-Methode nur zum Testen - bitte beachten: Embeddings werden nicht persistiert
if __name__ == "__main__":
    if len(sys.argv) != 3:
        print("Usage: python docchat.py <file_path> <question>")
        sys.exit(1)

    file_path = sys.argv[1]
    question = sys.argv[2]

    doc_chat = DocChat()
    file_name = os.path.basename(file_path)
    embeddings_count = doc_chat.process_doc(Document(name=file_name, filepath=file_path))
    print(f"Created {embeddings_count} embeddings")
    response = doc_chat.get_response(question)
    print(response.answer)
    sources = [x.metadata for x in response.db_source_chunks]
    print(sorted(sources, key=lambda s: s['page']))

In den letzten Zeilen haben wir eine Main-Methode implementiert, um unseren DocChat zu testen. Wir erstellen eine DocChat-Instanz und verarbeiten das Dokument, über das wir chatten möchten. Dann stellen wir zwei Fragen und geben die Antworten aus.

Je nach gewähltem PDF erhalten Sie unterschiedliche Antworten, aber die Antworten sollten sinnvoll und kontextuell angemessen sein.

Wir verwenden pipenv für die Verwaltung der virtuellen Umgebung und das Abhängigkeitsmanagement. Sie können es einfach mit pip install pipenv installieren. Wir spezifizieren die Anforderungen in einem Pipfile und verwenden pipenv install, um in einem Schritt eine virtuelle Umgebung zu erstellen und die Abhängigkeiten zu installieren.

Dies wäre unser Pipfile mit den Abhängigkeiten:

[[source]]
url = "https://pypi.org/simple"
verify_ssl = true
name = "pypi"

[packages]
langchain = {version = "==v0.0.308", extras = ["llms"]}
chromadb = "~=0.4.13"
pypdf = "~=3.16.2"
tiktoken = "~=0.5.1"
unstructured = "~=0.10.19"

# Falls Sie GPT4All ausprobieren möchten, siehe unten
# gpt4all = "==1.0.12"

[requires]
python_version = "3.10"

Um unsere Klasse auszuführen, müssen wir die Anforderungen installieren und den OpenAI-API-Key setzen. Dann sind wir bereit, unseren Code auszuführen.

Als Beispiel habe ich das Transkript von Andrew NGs CS229 Machine Learning-Kurs im PDF-Format heruntergeladen und in einem „/docs”-Ordner im aktuellen Verzeichnis gespeichert. Sie können jedoch jedes PDF Ihrer Wahl verwenden, vorausgesetzt es enthält Text und besteht nicht nur aus Bildern.

# Abhängigkeiten installieren
pipenv install

Sie können eine Subshell mit der virtuellen Umgebung mit pipenv shell starten und Ihren Python-Interpreter in dieser Shell ausführen:

export OPENAI_API_KEY=<YOUR_API_KEY>

# Subshell in der virtuellen Umgebung starten
pipenv shell

#                 |-- Pfad zu Ihrem Dokument hier  --| |--   Ihre Frage    --|
python docchat.py "docs/MachineLearning-Lecture01.pdf" "What is CS229 all about?"

Alternativ können Sie pipenv run verwenden, um Ihren Python-Interpreter in der virtuellen Umgebung auszuführen:

export OPENAI_API_KEY=<YOUR_API_KEY>

#                            |-- Pfad zu Ihrem Dokument hier  --| |--   Ihre Frage    --|
pipenv run python docchat.py "docs/MachineLearning-Lecture01.pdf" "What is CS229 all about?"

In beiden Fällen sollte die Ausgabe in etwa so aussehen:

CS229 is a machine learning class taught by Andrew Ng. It covers the technical content of machine learning,
including math and equations. The class assumes that students have a basic understanding of linear algebra
and matrix operations. Homework assignments and solutions are posted online, and there is a newsgroup for
class discussions. The class is televised and can be watched at home.

Bitte beachten Sie, dass die Embeddings für Ihr Dokument nicht auf der Festplatte gespeichert werden. Daher müssen Sie sie bei jedem Neustart der Anwendung neu erstellen, was je nach Dokumentgröße und Anzahl der Segmente Kosten verursachen kann. Die Möglichkeit, unsere Klasse über die Kommandozeile zu nutzen, dient ausschließlich einem schnellen Test unserer Basisklasse für unseren Chatbot – wir werden in unserem nächsten Artikel eine Web-Oberfläche mit dieser Klasse erstellen. Je nach Anwendungsfall ist es ratsam, die Embeddings zu speichern und wiederzuverwenden, um Effizienz und Kosten zu optimieren. Sie können diese Funktionalität je nach Bedarf einfach implementieren – Sie können alle Dokumente in einer einzigen Vektordatenbank speichern und einen Chatbot erstellen, der über alle chatten kann. Oder Sie entscheiden sich, die Embeddings für jedes Dokument in einer separaten Collection innerhalb einer Vektordatenbank oder sogar in einer separaten Vektordatenbank zu speichern, sodass Sie den Umfang Ihres Chats steuern können.

Optional: GPT4All für Embeddings und Chat

Wie wir in unserem vorherigen Beitrag über GPT4All erwähnt haben, können wir GPT4All-Modelle lokal verwenden, um Embeddings zu erstellen und leistungsstarke LLMs zu nutzen. Wir können denselben Code wie oben verwenden, indem wir lediglich das gpt4all-Paket installieren und wenige Zeilen ändern (dank der LangChain-Abstraktionen)!

# -- Zeilen mit OpenAI auskommentieren
# llm = ChatOpenAI(model_name=llm_name, temperature=0)
# embeddings = OpenAIEmbeddings()

# ++ Zeilen mit GPT4All einkommentieren
from langchain.llms import GPT4All
from langchain.embeddings import GPT4AllEmbeddings

# Sie müssen ein Modell herunterladen und den Pfad angeben (hier: Ubuntu, Modell heruntergeladen mit der GPT4All Chat App - alternativ
# können Sie Modelle manuell unter https://gpt4all.io/ herunterladen, scrollen Sie nach unten zu "Model Explorer")
llm = GPT4All(model="/home/alex/.local/share/nomic.ai/GPT4All/wizardlm-13b-v1.1-superhot-8k.ggmlv3.q4_0.bin", n_threads=8)
# Das Embedding-Modell wird automatisch heruntergeladen
embeddings = GPT4AllEmbeddings()

Bitte beachten Sie, dass GPT4All 1.0.9 einen Bug zu haben scheint, sodass Sie entweder bei gpt4all = "==1.0.8" bleiben oder in Ihrem Pipfile auf gpt4all = "==1.0.12" aktualisieren müssen.

Wenn die Leistung der GPT4All-Completion-Modelle für Ihren Anwendungsfall nicht ausreicht, können Sie kombinieren und OpenAIs Completion-Modell zusammen mit GPT4All für die Embeddings verwenden, wodurch die Stärken beider Ansätze effektiv kombiniert werden.

Fazit

In diesem Artikel haben wir das Fundament für einen Chatbot gelegt, der Ihre Dokumente mithilfe von Retrieval-Augmented Generation (RAG) versteht. Wir haben die leistungsstarken LangChain-Abstraktionen genutzt, um die Basisklasse für den Chatbot in nur wenigen Zeilen zu implementieren.

Im nächsten Artikel werden wir erklären, wie Sie eine einfache, aber funktionale Benutzeroberfläche für unseren Chatbot erstellen können – bleiben Sie dran!

Autoren

Alex Bloss

Autor

Alex Bloss

Alex Bloss - Leiter attempto-Lab.

Ähnliche Beiträge

Teilen