Einführung

Im vorherigen Artikel experimentierten wir mit Coheres Command-R-Modell und Rerank-Modell um Antworten zu generieren und Dokumentquellen neu zu bewerten. Wir haben eine einfache RAG-Pipeline implementiert, die sie verwendet, um Antworten auf Benutzerfragen zu aufgenommenen Dokumenten zu generieren. Was wir implementiert haben, ist jedoch sehr einfach und für den normalen Benutzer ungeeignet, da es keine Benutzeroberfläche für die direkte Interaktion mit dem Chatbot hat. In diesem Artikel werden wir die Codebasis für eine einfache Interpretation und Skalierung modularisieren und eine Streamlit-Anwendung erstellen, die als Schnittstelle für die Interaktion mit der RAG-Pipeline dient. Die Schnittstelle wird eine Chatbot-Schnittstelle sein, die der Benutzer zur Interaktion verwenden kann. Daher werden wir eine zusätzliche Speicherkomponente innerhalb der Anwendung implementieren, die es Benutzern ermöglicht, Folgeabfragen zu vorherigen Antworten zu stellen.

Lernziele

  • Entwickeln Sie mithilfe der Prinzipien der objektorientierten Programmierung (OOP) eine wiederverwendbare, modulare Codebasis für verschiedene RAG-Pipelines.
  • Erstellen Sie eine Aufnahmepipeline für Dokumentaufnahmekomponenten und eine Abfragepipeline für abfragebezogene Komponenten. Beide sind unabhängig und können separat ausgeführt werden.
  • Verbinden Sie nur die Abfrage-Pipeline mit dem Streamlit App für Benutzerabfragen, mit der Choice, die Dokumentaufnahme durch Ändern des Codes hinzuzufügen.
  • Implementieren Sie eine Speicherkomponente, um Folgeabfragen basierend auf vorherigen Antworten zu ermöglichen.
  • Verwandeln Sie Pocket book-Experimente in vorführbare Anwendungen innerhalb des Python Ökosystem.
  • Ermöglichen Sie eine schnellere Prototypenentwicklung mit minimalen Codeänderungen, indem Sie wiederverwendbaren Code für zukünftige RAG-Pipelines erstellen.

Dieser Artikel erschien im Rahmen der Information Science-Blogathon.

Dokumentieren Sie die QnA-Pipeline-Entwicklung

Der erste Schritt beim Erstellen eines Prototyps oder einer bereitstellbaren Anwendung besteht darin, die Konfigurationen und Konstanten zu definieren, die in verschiedenen Anwendungsabschnitten verwendet werden. Die Anwendung verfügt über mehrere konfigurierbare Optionen, wie z. B. Blockgröße und Überlappung in der Ingestion-Pipeline, den API-Schlüssel für Cohere-Endpunkte und die Temperatur für LLM-Generierung. Diese Konfigurationen befinden sich in einer zentralen Konfigurationsdatei, auf die von überall innerhalb der Anwendung zugegriffen werden kann.

Für dieses Projekt müssen wir einer Ordnerstruktur folgen. Wir werden ein „src“-Verzeichnis haben, in dem alle erforderlichen Dateien gespeichert werden, und die Datei app.py befindet sich im Stammverzeichnis. Nachfolgend sehen Sie die Struktur, der wir folgen werden:

.
├── .venv
├── src
│   ├── config.py
│   ├── constants.py
│   ├── ingestion.py
│   └── qna.py
├── app.py
└── necessities.txt

Wir werden zwei Dateien für zwei Zwecke erstellen: Eine config.py-Datei, die die geheimen Schlüssel, einen Vektorspeicherpfad und einige andere Konfigurationen enthält, und eine constants.py-Datei, die alle in der Anwendung verwendeten Konstanten enthält, wie etwa die Blockgröße, die Blocküberlappung und die Eingabeaufforderungsvorlage. Nachfolgend finden Sie den Inhalt der config.py-Datei:

COHERE_EMBEDDING_MODEL_NAME = "embed-english-v3.0" 
COHERE_MODEL_NAME = "command-r" 
COHERE_RERANK_MODEL_NAME = "rerank-english-v3.0" 
DEEPLAKE_VECTORSTORE = "/path/to/doc/vectorstore" 
API_KEY = “”
Under are the contents for constants.py file: 
PDF_CHARSPLITTER_CHUNKSIZE = 1000 
PDF_CHARSPLITTER_CHUNK_OVERLAP = 100 
TEMPERATURE = 0.3 
TOP_K = 25 
CONTEXT_THRESHOLD = 0.8 
PROMPT_TEMPLATE = """
<YOUR PROMPT HERE>
Chat Historical past: {chat_history} Context: {context} Query: {query} Reply:
"""

In die Datei config.py habe ich den Cohere-API-Schlüssel, die Namen aller verwendeten Modelle und den Pfad zum Dokumentvektorspeicher eingetragen. In die Datei constants.py habe ich die Eingabeaufforderungsvorlage und andere Aufnahme- und Generierungskonfigurationen wie Chunk-Größe und Chunk-Überlappungswerte, Temperatur für die LLM-Generierung, top_k für die wichtigsten relevanten Chunks und den Kontextschwellenwert zum Herausfiltern relevanter Chunks eingetragen.
Punktzahl unter 0,8. Der Inhalt der Dateien config.py und constants.py kann je nach Anwendungsfall geändert werden.

Teil 1 – Einnahme

Als nächstes werden wir uns ansehen, wie wir die Ingestion-Pipeline modularisieren können. Wir werden eine einzelne Klasse namens Ingestion erstellen und eine Methode hinzufügen, um Einbettungen zu generieren und sie im Vektorspeicher zu speichern. Hinweis

dass wir für unseren Anwendungsfall für jede Pipeline einzelne Dateien haben werden. Wenn die Komplexität des Anwendungsfalls zunimmt, können mehrere Dateien erstellt werden, um jede Pipeline-Komponente zu handhaben. Dadurch wird sichergestellt,
Lesbarkeit des Codes und einfache weitere Änderungen und Aktualisierungen.

Unten sehen Sie den Code für die Ingestion-Klasse:

import timeimport time
import src.constants as fixed
import src.config as cfg

from langchain_cohere import CohereEmbeddings
from langchain.document_loaders.pdf import PyPDFLoader
from langchain.text_splitter import CharacterTextSplitter


class Ingestion:
    def __init__(self):
        self.text_vectorstore = None
        self.embeddings = CohereEmbeddings(
            mannequin=cfg.COHERE_EMBEDDING_MODEL_NAME,
            cohere_api_key=cfg.API_KEY,
        )

    def create_and_add_embeddings(
        self,
        file_path: str,
    ):
        self.text_vectorstore = DeepLake(
            dataset_path=cfg.DEEPLAKE_VECTORSTORE,
            embedding=self.embeddings,
            verbose=False,
            num_workers=4,
        )

        loader = PyPDFLoader(file_path=file_path)

        text_splitter = CharacterTextSplitter(
            separator="n",
            chunk_size=fixed.PDF_CHARSPLITTER_CHUNKSIZE,
            chunk_overlap=fixed.PDF_CHARSPLITTER_CHUNK_OVERLAP,
        )
        pages = loader.load()
        chunks = text_splitter.split_documents(pages)
        _ = self.text_vectorstore.add_documents(paperwork=chunks)
import src.constants as fixed
import src.config as cfg

from langchain_cohere import CohereEmbeddings
from langchain.document_loaders.pdf import PyPDFLoader
from langchain.text_splitter import CharacterTextSplitter


class Ingestion:
    def __init__(self):
        self.text_vectorstore = None
        self.embeddings = CohereEmbeddings(
            mannequin=cfg.COHERE_EMBEDDING_MODEL_NAME,
            cohere_api_key=cfg.API_KEY,
        )

    def create_and_add_embeddings(
        self,
        file_path: str,
    ):
        self.text_vectorstore = DeepLake(
            dataset_path=cfg.DEEPLAKE_VECTORSTORE,
            embedding=self.embeddings,
            verbose=False,
            num_workers=4,
        )

        loader = PyPDFLoader(file_path=file_path)

        text_splitter = CharacterTextSplitter(
            separator="n",
            chunk_size=fixed.PDF_CHARSPLITTER_CHUNKSIZE,
            chunk_overlap=fixed.PDF_CHARSPLITTER_CHUNK_OVERLAP,
        )
        pages = loader.load()
        chunks = text_splitter.split_documents(pages)
        _ = self.text_vectorstore.add_documents(paperwork=chunks)

Lassen Sie uns jeden Teil des obigen Codes verstehen. Zuerst importieren wir alle erforderlichen Pakete, einschließlich der Konstanten und Konfigurationsdateien. Dann definieren wir die Klasse Ingestion und ihren Klassenkonstruktor mit der Methode __init__. Wir setzen die Variable text_vectorstore auf None, die später mit der Vektorspeicherinstanz initialisiert wird. Dann initialisieren wir die Embeddings-Modellinstanz mit dem Modellnamen und dem API-Schlüssel aus der Konfiguration.

Als Nächstes erstellen wir die Methode create_and_add_embeddings, die den Dateipfad übernimmt, in den das Dokument aufgenommen wird. Innerhalb dieser Methode initialisieren wir zuerst den Vektorspeicher mithilfe des Vektorspeicherpfads und der Einbettungen. Wir haben auch num_workers auf 4 gesetzt, damit 4 CPU-Kerne für eine schnellere Verarbeitung genutzt werden. Dann initialisieren wir das PDF Loader-Objekt mithilfe des Dateipfads und verwenden dann den Character Splitter, um die Blöcke aufzuteilen. Anschließend laden wir die PDF-Datei und teilen die Seiten in weitere Blöcke auf. Die endgültigen Blöcke werden dann dem Vektorspeicher hinzugefügt.

Teil 2 – Fragen und Antworten

Nachdem wir nun die Aufnahmepipeline eingerichtet haben, erstellen wir die QnA-Pipeline. Unten sehen Sie den Code für die QnA-Klasse:

import time
import src.constants as fixed
import src.config as cfg
from pymongo import MongoClient
from langchain_cohere import CohereEmbeddings
from langchain_cohere import ChatCohere
from langchain.reminiscence.chat_message_histories.sql import SQLChatMessageHistory
from langchain.reminiscence import ConversationBufferWindowMemory
from langchain.chains.conversational_retrieval.base import ConversationalRetrievalChain
from langchain.prompts import PromptTemplate
from langchain.retrievers import ContextualCompressionRetriever
from langchain_cohere import CohereRerank


class QnA:
    def __init__(self):
        self.embeddings = CohereEmbeddings(
            mannequin=cfg.COHERE_EMBEDDING_MODEL_NAME,
            cohere_api_key=cfg.API_KEY,
        )
        self.mannequin = ChatCohere(
            mannequin=cfg.COHERE_MODEL_NAME,
            cohere_api_key=cfg.API_KEY,
            temperature=fixed.TEMPERATURE,
        )
        self.cohere_rerank = CohereRerank(
            cohere_api_key=cfg.API_KEY,
            mannequin=cfg.COHERE_RERANK_MODEL_NAME,
        )
        self.text_vectorstore = None
        self.text_retriever = None

    def ask_question(
        self,
        question,
        session_id,
        verbose: bool = False,
    ):
        start_time = time.time()
        self.init_vectorstore()

        memory_key = "chat_history"
        historical past = SQLChatMessageHistory(
            session_id=session_id,
            connection_string="sqlite:///reminiscence.db",
        )

        PROMPT = PromptTemplate(
            template=fixed.PROMPT_TEMPLATE,
            input_variables=("chat_history", "context", "query"),
        )
        reminiscence = ConversationBufferWindowMemory(
            memory_key=memory_key,
            output_key="reply",
            input_key="query",
            chat_memory=historical past,
            okay=2,
            return_messages=True,
        )
        chain_type_kwargs = {"immediate": PROMPT}
        qa = ConversationalRetrievalChain.from_llm(
            llm=self.mannequin,
            combine_docs_chain_kwargs=chain_type_kwargs,
            retriever=self.text_retriever,
            verbose=verbose,
            reminiscence=reminiscence,
            return_source_documents=True,
            chain_type="stuff",
        )
        response = qa.invoke({"query": question})
        exec_time = time.time() - start_time

        return response

    def init_vectorstore(self):
        self.text_vectorstore = DeepLake(
            dataset_path=cfg.DEEPLAKE_VECTORSTORE,
            embedding=self.embeddings,
            verbose=False,
            read_only=True,
            num_workers=4,
        )

        self.text_retriever = ContextualCompressionRetriever(
            base_compressor=self.cohere_rerank,
            base_retriever=self.text_vectorstore.as_retriever(
                search_type="similarity",
                search_kwargs={
                    "fetch_k": 20,
                    "okay": fixed.TOP_K,
                },
            ),
        )

Wir haben eine QnA-Klasse mit einem Initialisierer erstellt, der das Frage-Antwort-System einrichtet. Sie erstellt eine Instanz der Klasse CohereEmbeddings zum Generieren von Textual content-Embeddings unter Verwendung des Modellnamens und des API-Schlüssels. Sie initialisiert außerdem die Klasse ChatCohere für Konversationsaufgaben mit einem Temperaturwert für die Textzufälligkeit und die Klasse CohereRerank zum Neurangieren von Antworten basierend auf Relevanz.

Die Methode ask_question verwendet eine Abfrage, eine Sitzungs-ID und ein optionales ausführliches Flag. Die Methode init_vectorstore initialisiert die Vektordatenbank und die Retrieverkomponenten. Ein Speicherschlüssel und eine Instanz von SQLChatMessageHistory verwalten den Konversationsverlauf. Das PromptTemplate formatiert die Abfrage und den Verlauf, und das ConversationBufferWindowMemory verwaltet den Konversationspufferspeicher.

Die Klasse ConversationalRetrievalChain kombiniert den Retriever und das Sprachmodell für die Beantwortung von Fragen. Sie wird mit dem Sprachmodell, der Eingabeaufforderungsvorlage, dem Retriever und anderen Einstellungen initialisiert. Die Invoke-Methode generiert eine Antwort basierend auf der Abfrage und dem Verlauf und berechnet die Ausführungszeit von ask_question.

Die Methode init_vectorstore richtet die Vektordatenbank und den Retriever ein. Die DeepLake-Instanz initialisiert die Vektordatenbank mit dem Pfad, dem Einbettungsmodell und anderen Parametern. Der ContextualCompressionRetriever verwaltet die Retriever-Komponente mit dem Reranking-Modell und der Vektordatenbank und gibt den Suchtyp und die Parameter an.

Teil 3 – Streamlit-Benutzeroberfläche

Nachdem nun sowohl die Ingestion- als auch die QnA-Pipelines bereit sind, erstellen wir die Streamlit-Schnittstelle, die die Pipelines nutzt. Unten finden Sie den gesamten Code für die Streamlit-Schnittstelle:

import streamlit as st

from src.qna import QnA
from dataclasses import dataclass

@dataclass
class Message:
    actor: str
    payload: str


def major():
    st.set_page_config(
        page_title="KnowledgeGPT",
        page_icon="📖",
        structure="centered",
        initial_sidebar_state="collapsed",
    )
    st.header("📖KnowledgeGPT")

    USER = "person"
    ASSISTANT = "ai"
    MESSAGES = "messages"

    with st.spinner(textual content="Initializing..."):
        st.session_state("qna") = QnA()

    qna = st.session_state("qna")
    if MESSAGES not in st.session_state:
        st.session_state(MESSAGES) = (
            Message(
                actor=ASSISTANT,
                payload="Hello! How can I provide help to?",
            )
        )
    msg: Message
    for msg in st.session_state(MESSAGES):
        st.chat_message(msg.actor).write(msg.payload)

    immediate: str = st.chat_input("Enter a immediate right here")

    if immediate:
        st.session_state(MESSAGES).append(Message(actor=USER, payload=immediate))
        st.chat_message(USER).write(immediate)
        with st.spinner(textual content="Pondering..."):
            response = qna.ask_question(
                question=immediate, session_id="AWDAA-adawd-ADAFAEF"
            )

        st.session_state(MESSAGES).append(Message(actor=ASSISTANT, payload=response))
        st.chat_message(ASSISTANT).write(response)

if __name__ == "__main__":
    major()

Streamlit UI-Funktionalität

Die Streamlit-Benutzeroberfläche dient als benutzerorientierte Komponente unserer Anwendung. Hier ist eine Aufschlüsselung ihrer Funktionalität:

  • Seitenkonfiguration: Die Funktion st.set_page_config legt den Seitentitel, das Image, das Format und den Anfangsstatus der Seitenleiste fest.
  • Konstanten: Wir definieren Konstanten für den Benutzer (USER), den Assistenten (ASSISTANT) und die Nachrichten (MESSAGES), um die Lesbarkeit des Codes zu verbessern.
  • Initialisierung der QnA-Instanz: Wir initialisieren die QnA-Instanz und speichern sie im st.session_state-Wörterbuch. Dadurch wird sichergestellt, dass die Instanz über verschiedene App-Sitzungen hinweg bestehen bleibt.
  • Initialisierung von Chat-Nachrichten: Wenn MESSAGES in st.session_state nicht vorhanden ist, initialisieren wir es mit einer Willkommensnachricht vom Assistenten.
  • Chat-Nachrichten anzeigen: Der Code durchläuft die Liste „NACHRICHTEN“ und zeigt jede Nachricht zusammen mit dem Absender (Benutzer oder Assistent) an.
  • Benutzereingabe: Fordern Sie den Benutzer auf, mit st.chat_input eine Eingabeaufforderung einzugeben.
  • Benutzereingaben verarbeiten: Wenn der Benutzer eine Eingabeaufforderung bereitstellt, hängt der Code diese an die Liste MESSAGES an und generiert die Antwort des Assistenten mithilfe der Methode ask_question der QnA-Instanz.
  • Antwort des Anzeigeassistenten: Hängen Sie die Antwort des Assistenten an die Liste „NACHRICHTEN“ an und zeigen Sie sie dem Benutzer an.

Zum Schluss führen wir die Hauptmethode aus, um die App zu starten. Wir können die App mit dem folgenden Befehl starten:

streamlit run app.py

Funktionsweise der App

Nachfolgend finden Sie eine kurze Demo der Funktionsweise der App:

Lappenauftrag

So funktioniert KnowledgeGPT:

Knowledge GPT-Demo

Abschluss

In diesem Artikel haben wir unsere anfänglichen RAG-Leitung Experimentieren Sie mit einer robusteren und benutzerfreundlicheren Anwendung. Durch die Änderung der Codebasis wurden Lesbarkeit, Wartbarkeit und Skalierbarkeit verbessert. Separate Aufnahme- und Abfragepipelines ermöglichen eine unabhängige Entwicklung und Wartung und verbessern so die allgemeine Skalierbarkeit der Anwendung.

Die Integration eines modularen Backends mit einer Streamlit-Schnittstelle schafft ein nahtloses Benutzererlebnis durch eine Chatbot-Schnittstelle, die Folgeanfragen unterstützt und Interaktionen dynamisch und dialogorientiert macht. Unter Verwendung objektorientierter Programmierprinzipien haben wir unseren Code so strukturiert, dass er klar und wiederverwendbar ist, was für die Skalierung und Anpassung an neue Anforderungen unerlässlich ist.

Unsere Implementierung von Konfigurationen und Konstantenmanagement sowie die Einrichtung von Aufnahme- und QnA-Pipelines bieten Entwicklern einen klaren Weg. Dieses Setup vereinfacht den Übergang von einem Jupyter-Notizbuch Experiment zu einer einsetzbaren Anwendung, wobei das Projekt innerhalb der Python Ökosystem.

Dieser Artikel bietet eine umfassende Anleitung zum Erstellen einer interaktiven Dokument-QnA-Anwendung mit den Modellen von Cohere. Durch die Verbindung theoretischer Experimente und praktischer Umsetzung können Entwickler effiziente und skalierbare Lösungen erstellen. Mit dem bereitgestellten Code und klaren Anweisungen sind Sie nun bereit, Ihre eigenen RAG-basierten Anwendungen zu entwickeln, anzupassen und zu starten und so die Erstellung intelligenter Dokumentabfragesysteme zu beschleunigen.

Die zentralen Thesen

  • Verbessert die Wartbarkeit und Skalierbarkeit durch die Trennung von Aufnahme- und Abfrage-Pipelines.
  • Bietet eine benutzerfreundliche Chatbot-Schnittstelle für dynamische Interaktionen.
  • Stellt eine strukturierte, wiederverwendbare und skalierbare Codebasis sicher.
  • Zentralisierte Konfigurationen in dedizierten Dateien für Flexibilität und einfache Verwaltung.
  • Behandelt die Dokumentenaufnahme und Benutzerabfragen effizient mithilfe der Modelle von Cohere.
  • Ermöglicht die Bearbeitung von Folgeanfragen für kohärente, kontextbezogene Interaktionen.
  • Ermöglicht schnelles Prototyping und die Entwicklung anderer RAG-Pipelines.

Die in diesem Artikel gezeigten Medien sind nicht Eigentum von Analytics Vidhya und werden nach Ermessen des Autors verwendet.

Häufig gestellte Fragen

F1. Kann ich die Aufnahmepipeline mit REST-API mithilfe von Flask/FastAPI umschließen?

A. Absolut! Tatsächlich ist das der ideale Weg, um Pipelines für die KI-Era zu erstellen. Sobald die Pipelines bereit sind, sollten sie mit einer RESTful-API umschlossen werden, damit sie vom Frontend aus verwendet werden können.

F2. Was ist der Zweck der Streamlit-Schnittstelle?

A. Die Streamlit-Schnittstelle bietet eine benutzerfreundliche Chatbot-Schnittstelle für die Interaktion mit der RAG-Pipeline, sodass Benutzer ganz einfach Fragen stellen und Antworten erhalten können.

F3. Kann ich die Gradio-Schnittstelle anstelle von Streamlit verwenden?

Antwort: Ja. Der Zweck des Aufbaus einer modularisierten Pipeline besteht darin, sie an jede Frontend-Benutzeroberfläche anfügen zu können, sei es Streamlit, Gradio oder JavaScript-basierte Benutzeroberflächen-Frameworks.

Von admin

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert