Vom technischen Anspruch und der Architektur ist diese Lösung nicht sehr komplex und daher ideal für den Einstieg in das Thema der großen Sprachenmodelle geeignet. Auch stellt sich garantiert ein erstes Erfolgserlebnis sehr schnell ein. Alle verwendeten Komponenten lassen sich leicht installieren und miteinander kombinieren. Ich bin der nachfolgenden Beschreibung gefolgt und habe diese noch um ein paar Informationen ergänzt die mir wichtig waren.
Quelle: https://blog.duy-huynh.com/build-your-own-rag-and-run-them-locally/
Im Beitrag „Ollama Ubuntu Installation und Konfiguration“ haben wir ja bereits Ollama installiert. Jetzt möchte ich ganz kurz und knapp auf die Architektur der Anwendung eingehen die wir jetzt zusammen bauen werden. Das folgende Bild zeigt kurz den Aufbau. Wir haben einmal den LLM Server Ollama und die virtuelle Umgebung in der alle Komponenten installiert sind die unsere RAG-Anwendung benötigt damit wir mit einer PDF-Datei chatten können. Streamlit stellt die Benutzeroberfläche zur Verfügung. Auch haben wir den Anwender der eine PDF-Datei hochladen muss um mit dieser dann interagieren zu können.
Video Einführungskurs
Um sich mit den Grundprinzipien einer Retrieval-augmented generation (RAG) Applikation vertraut zu machen, empfehle ich, die nachfolgenden Videos einmal in Ruhe anzuschauen.
- RAG From Scratch: Part 1 (Overview)
- RAG From Scratch: Part 2 (Indexing)
- RAG From Scratch: Part 3 (Retrieval)
- RAG From Scratch: Part 4 (Geneartion)
Mein Beitrag hier steigt direkt in die Entwicklung der Anwendung ein und geht nicht weiter darauf ein warum etwas wie gemacht wird. Erwähnen möchte ich noch einmal, dass bei dieser RAG-Anwendung die Daten nicht in das Internet übertragen werden sondern alles lokal auf ihrem Rechner läuft.
Software Installation
Ich setze ja immer eine virtuelle Anaconda Umgebung für meine Projekte auf. So mache ich das auch hier und wenn ihr Anaconda unter eurem Ubuntu noch nicht installiert habt könnt ihr hier nachlesen wie ihr Anaconda einrichtet.
URL: https://ai-box.eu/software/installation-von-anaconda-auf-ubuntu-lts-version/1170/
Mit dem folgenden Befehl wird eine Anaconda Umgebung mit dem Namen ollama_rag
angelegt.
Befehl: conda create --name ollama_rag
Die frisch angelegte Umgebung müsst ihr noch aktivieren. Dazu bitte den folgenden befehl ausführen.
Befehl: conda activate ollama_rag
Jetzt installiert ihr bitte mit dem folgenden Befehl in die virtuelle Umgebung ollama_rag
die Erweiterungen mit dem folgenden Befehl.
Befehl: pip install langchain langchain-community chromadb fastembed streamlit streamlit_chat
Fehlermeldungen:
Nach der Installation der Pakete wie oben beschrieben habe ich die folgenden Fehlermeldungen erhalten. Ich werde jetzt weiter machen um zu sehen wie relevant diese sind. Denn nur als Beispiel, dass openai>=0.26.4
nicht installiert ist sollte jetzt kein Problem sein für den weiteren Fortschritt des Projektes.
ERROR: pip's dependency resolver does not currently take into account all the packages that are installed. This behaviour is the source of the following dependency conflicts.
llama-index 0.6.12 requires openai>=0.26.4, which is not installed.
auto-gptq 0.3.0+cu117 requires datasets, which is not installed.
transformers 4.26.1 requires tokenizers!=0.11.3,<0.14,>=0.11.1, but you have tokenizers 0.15.1 which is incompatible.
llama-index 0.6.12 requires typing-extensions==4.5.0, but you have typing-extensions 4.9.0 which is incompatible.
clip-interrogator 0.6.0 requires transformers>=4.27.1, but you have transformers 4.26.1 which is incompatible.
auto-gptq 0.3.0+cu117 requires transformers>=4.29.0, but you have transformers 4.26.1 which is incompatible.
Der Programmcode
Diese kleine Anwendung besteht aus zwei Python Dateien. Eine bildet die Logik ab für die Interaktion mit dem großen Sprachenmodell mistral über den Ollama Server. Die andere Python-Datei verkörpert das User-Interface und setzt auf dem Python Programm mit der Logik auf. Ich habe mir einen Ordner mit dem Namen rag
angelegt und in diesen die beiden jetzt folgenden Python-Programme abgelegt.
Das original Programm findet ihr hier auf GitHub.
URL: https://gist.github.com/vndee/7776debe50b5e6c2b174add8646a4625
Den jetzt folgenden Quellcode kopiert ihr in eine Python-Datei mit dem Namen rag.py
.
from langchain.vectorstores import Chroma
from langchain.chat_models import ChatOllama
from langchain.embeddings import FastEmbedEmbeddings
from langchain.schema.output_parser import StrOutputParser
from langchain.document_loaders import PyPDFLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.schema.runnable import RunnablePassthrough
from langchain.prompts import PromptTemplate
from langchain.vectorstores.utils import filter_complex_metadata
class ChatPDF:
vector_store = None
retriever = None
chain = None
def __init__(self):
self.model = ChatOllama(model="mistral")
self.text_splitter = RecursiveCharacterTextSplitter(chunk_size=1024, chunk_overlap=100)
self.prompt = PromptTemplate.from_template(
"""
<s> [INST] You are an assistant for question-answering tasks. Use the following pieces of retrieved context
to answer the question. If you don't know the answer, just say that you don't know. Use three sentences
maximum and keep the answer concise. [/INST] </s>
[INST] Question: {question}
Context: {context}
Answer: [/INST]
"""
)
def ingest(self, pdf_file_path: str):
docs = PyPDFLoader(file_path=pdf_file_path).load()
chunks = self.text_splitter.split_documents(docs)
chunks = filter_complex_metadata(chunks)
vector_store = Chroma.from_documents(documents=chunks, embedding=FastEmbedEmbeddings())
self.retriever = vector_store.as_retriever(
search_type="similarity_score_threshold",
search_kwargs={
"k": 3,
"score_threshold": 0.5,
},
)
self.chain = ({"context": self.retriever, "question": RunnablePassthrough()}
| self.prompt
| self.model
| StrOutputParser())
def ask(self, query: str):
if not self.chain:
return "Please, add a PDF document first."
return self.chain.invoke(query)
def clear(self):
self.vector_store = None
self.retriever = None
self.chain = None
rag.py
Datei einbindet. Auch diese habe ich mir aus dem folgenden Projekt auf GitHub kopiert.rag-app.py
.import os
import tempfile
import streamlit as st
from streamlit_chat import message
from rag import ChatPDF
st.set_page_config(page_title="ChatPDF")
def display_messages():
st.subheader("Chat")
for i, (msg, is_user) in enumerate(st.session_state["messages"]):
message(msg, is_user=is_user, key=str(i))
st.session_state["thinking_spinner"] = st.empty()
def process_input():
if st.session_state["user_input"] and len(st.session_state["user_input"].strip()) > 0:
user_text = st.session_state["user_input"].strip()
with st.session_state["thinking_spinner"], st.spinner(f"Thinking"):
agent_text = st.session_state["assistant"].ask(user_text)
st.session_state["messages"].append((user_text, True))
st.session_state["messages"].append((agent_text, False))
def read_and_save_file():
st.session_state["assistant"].clear()
st.session_state["messages"] = []
st.session_state["user_input"] = ""
for file in st.session_state["file_uploader"]:
with tempfile.NamedTemporaryFile(delete=False) as tf:
tf.write(file.getbuffer())
file_path = tf.name
with st.session_state["ingestion_spinner"], st.spinner(f"Ingesting {file.name}"):
st.session_state["assistant"].ingest(file_path)
os.remove(file_path)
def page():
if len(st.session_state) == 0:
st.session_state["messages"] = []
st.session_state["assistant"] = ChatPDF()
st.header("ChatPDF")
st.subheader("Upload a document")
st.file_uploader(
"Upload document",
type=["pdf"],
key="file_uploader",
on_change=read_and_save_file,
label_visibility="collapsed",
accept_multiple_files=True,
)
st.session_state["ingestion_spinner"] = st.empty()
display_messages()
st.text_input("Message", key="user_input", on_change=process_input)
if __name__ == "__main__":
page()
Programm RAG Chat-Applikation ausführen
Wechselt jetzt in der Konsole in den Ordner rag auf eurem Rechner und führ die Python-Datei rag-app.py
mit dem folgenden Befehl aus.
Befehl: streamlit run rag-app.py
Wenn ihr jetzt im Browser die IP Adresse mit dem Port 8501 aufruft dann sollte sich die Web-Oberfläche der kleinen Anwendung aufbauen.
URL: <Eure IP-Adresse>:8501
Bei mir sieht die Web-Oberfläche jetzt wie folgt aus.
Jetzt müsst ihr eine PDF Datei hochladen.
Erste Tests bzw. Chat-Versuche
Für den ersten Versuch habe ich einen Reiseführer über NewYork herunter geladen. Diesen findet ihr hier.
URL: https://guides.tripomatic.com/download/tripomatic-free-city-guide-new-york-city.pdf
Jetzt ladet ihr die Datei in die kleine App und wartet kurz bis die Vektor DB aufgebaut ist. Anschließend habe ich die folgende Frage gestellt die aus der PDF Datei heraus beantwortet werden können müsste.
Frage: „I need your help as an travel guide for NewYork. I woul like to visit NewYork in March. Please tell what going on in NewYork in March.“
Die Antwort die zurück kam war richtig und findet sich auch exakt so wieder in der PDF-Datei.
Text-Embeddings erstellen – Hintergrundwissen
Jetzt möchte ich noch mit euch kurz auf einen sehr wichtigen Punkt bei dieser RAG Chat-Applikation eingehen. Die PDF-Datei wird ja in Text-Embeddings zerlegt und als Vektoren in der Chroma Vektor-DB gespeichert. Jetzt ist das Erstellen der Vektoren gar nicht so leicht da diese idealerweise Abschnitte des Textes enthalten sollten die geschlossen zusammenhängen. Zerpflückt man jetzt den Text recht ungünstig kann es sein, dass die RAG-Anwendung keine guten Ergebnisse liefert. Damit man sich das besser vorstellen kann gibt es von Greg Kamradt die folgende Mini-Anwendung mit der man sich das schneiden des Textes in Text-Embeddings bildlich ganz gut vorstellen kann.
URL: https://chunkviz.up.railway.app/
Im Programm rag.py wird der Text nach der Methode RecursiveCharacterTextSplitter
zerlegt und als Text-Embedding mit einer Länge von 1024 Zeichen mit einer Überschneidung von 100 Zeichen als Vektoren abgelegt.
self.text_splitter = RecursiveCharacterTextSplitter(chunk_size=1024, chunk_overlap=100)
Ich habe die Übersicht mit den Events in NewYork aus der PDF-Datei heraus kopiert und in ChunkViz eingefügt. ChunkViz erzeugt dann je nach Einstellung eine Übersicht wie die Chunks erstellt werden würden und diese sieht dann wie folgt gezeigt aus. Interessant zu sehen ist auch, dass wohl der Parameter chunk_overlap=100
bei der Methode RecursiveCharacterTextSplitter
keine Anwendung findet.
Aber probiert hier einfach am besten selber einmal aus wie ihr euren Text am besten in die Vektor DB überführt.
Hierarchical Contextual Augmentation
Für alle die diesen Ausflug interessant fanden empfehle ich noch die folgende Arbeit „A Hierarchical Contextual Augmentation RAG for Massive Documents QA“ zu lesen.
Der Text diskutiert die Einschränkungen des traditionellen RAG (Retrieval-Augmented Generation)-Ansatzes bei der genauen Informationssuche in großen Dokumenten mit Texten, Tabellen und Bildern wie z. B. einem HomeDepot Produktkatalog oder eben dem Makita Werkzeugktalog. Um diese Herausforderungen zu bewältigen, stellt das Papier einen hierarchischen kontextuellen Augmentierungsansatz (HCA) vor und führt das MasQA-Datenset zur Bewertung von Multi-Document Question Answering (MDQA)-Systemen ein.
Der HCA-Ansatz besteht aus drei Hauptschritten:
- Markdown-Formatierer: Verwendet Language Model (LLM) zur Analyse von Dokumenten im Markdown-Format und behandelt jedes Kapitel als Überschrift der ersten Ebene mit einer numerischen Kennung. Es generiert auch Tabellen und extrahiert Bilder mithilfe von PDFImageSearcher.
- Hierarchischer kontextueller Augmentor (HCA): Verarbeitet strukturelle Metadaten, wandelt Segmente in Einbettungsvektoren um und bettet Bildunterschriften ein, die von Very Large Models (VLMs) generiert wurden. Dabei werden Datenfelder innerhalb von Tabellen beim Einbetten ausgelassen.
- Mehrwegesuche: Kombiniert Vektorsuche, Elastic Search und Schlüsselwortübereinstimmung, um die Präzision der Informationssuche zu verbessern.
Die Bewertung des Ansatzes führt das Log-Rank Index-Maß zur Bewertung der Ranglistenwirksamkeit ein. Das MasQA-Datenset umfasst eine Vielzahl von Materialien, darunter technische Handbücher und Finanzberichte, mit einer vielfältigen Auswahl an Fragetypen wie Einzel- und Mehrfachauswahl, beschreibende, Tabellen- und Berechnungsfragen.
- Das Wissenschaftliche Papier gibt es hier: A Hierarchical Contextual Augmentation RAG for Massive Documents QA
- Die Software gibt es hier auf GitHub: Hierarchical Contextual Augmentation RAG for Massive Documents QA
Summarizing a text with LangChain and Ollama and StableLM 2
Ein weiteres interessantes Projekt das ich noch selber ausprobieren möchte ist dieses hier: ollamalangchainsummary.py
- Setup:
- My MacBookPro M3Max with 48 GB GPU.
- Ollama as Language Model host.
- Stability AI’s StableLM.
- LangChain as the underlying toolbox.
Video Kurs – Advanced QA over a lot of Tabular Data (combine text-to-SQL with RAG)
Zusammenfassung
Mir hat der Bau bzw. die kleine Entwicklung dieser RAG Chat-Applikation mit dem Ollama Server und den beiden Python Programmen sehr viel Freude gemacht. Im Vergleich zu August 2023 und jetzt Februar 2024 ist die Entwicklung schon wieder deutlich weiter und der Bau von solchen kleinen Anwendungen zusammen mit einem der großen Sprachenmodelle macht viel Freude. Auch ist es nicht mehr so schwierig alles zum Laufen zu bekommen. Es gibt sehr viele aktuelle Videos auf YouTube und Anleitungen auf GitHub & Co. So freue ich mich in was für einer tollen Zeit ich leben darf und werde hier noch einiges ausprobieren.
Hallo, ich habe ihre Anleitung befolgt und bekomme im letzten Schritt leider immer folgende Meldung im Terminal: bash <(wget -qO-…
Hi Dennis, vielen Dank für den Hinweis, ich habe die URLs angepasst so das diese wieder gehen. Ich bin gespannt…
Ich weiss, es ist noch im Aufbau. Aber hier ist ein Broken Link: https://www.ai-box.eu/smurf_training_data_small.zip Freue mich drauf, die ganze Serie…