Large Language Models (LLMs) wie GPT-4 und Claude sind außerordentlich leistungsfähig, leiden aber unter einer grundlegenden Einschränkung: Ihr Wissen ist zum Zeitpunkt des Trainings eingefroren. Sie können nicht auf deine internen Dokumente, deine Datenbank oder Echtzeitinformationen zugreifen. Retrieval-Augmented Generation (RAG) löst genau dieses Problem, indem sie die generative Kraft von LLMs mit der Fähigkeit kombiniert, Informationen aus externen Quellen abzurufen.
Das Problem: Die Grenzen von LLMs
Bevor wir über RAG sprechen, ist es wichtig zu verstehen, warum wir sie brauchen.
- Statisches Wissen: Ein LLM weiß nur das, was es während des Trainings gesehen hat. Wenn du nach einem Ereignis nach seinem Cutoff fragst, kann es nicht antworten.
- Halluzinationen: Wenn ein LLM die Antwort nicht kennt, neigt es dazu, sie zu erfinden und plausible, aber völlig falsche Informationen zu generieren.
- Kein Zugriff auf private Daten: Ein generisches LLM hat keinen Zugriff auf die interne Dokumentation deines Unternehmens, Tickets oder deine Codebasis.
RAG adressiert alle drei Probleme, indem es dem Modell relevanten Kontext liefert, der zum Zeitpunkt der Abfrage aus externen Quellen abgerufen wird.
Was ist RAG?
Retrieval-Augmented Generation ist eine Architektur, die den an ein LLM gesendeten Prompt mit Informationen aus einer externen Wissensdatenbank anreichert. Anstatt sich ausschließlich auf das parametrische Wissen des Modells zu verlassen, sucht RAG zuerst nach relevanten Informationen und injiziert sie dann in den Prompt, wodurch das Modell genaue, fundierte Antworten generieren kann.
Wie RAG im Detail funktioniert
Die RAG-Architektur besteht aus zwei Hauptphasen: Indexierung (offline) und Retrieval + Generierung (online).
Phase 1: Indexierung (Dokumentenaufnahme)
Die Indexierungsphase bereitet deine Dokumente für die semantische Suche vor. Sie besteht aus vier Schritten.
1. Document Loading
Dokumente können aus jeder Quelle stammen: PDF-Dateien, Webseiten, Datenbanken, Markdown-Dateien, APIs. Der Document Loader liest diese Dokumente und konvertiert sie in strukturierten Text.
2. Text Splitting (Chunking)
LLMs haben ein begrenztes Kontextfenster, und Dokumente können sehr lang sein. Der Text Splitter teilt Dokumente in kleinere Fragmente, sogenannte Chunks. Die Qualität des Chunking ist entscheidend: Zu kleine Chunks verlieren Kontext, zu große verwässern die Relevanz.
Die gängigsten Strategien sind:
- Recursive Character Splitting: Teilt Text rekursiv mit Trennzeichen wie
\n\n,\n,.und respektiert die Dokumentstruktur. - Semantic Splitting: Verwendet Embeddings, um natürliche Bruchpunkte im Text zu finden.
- Chunk Overlap: Enthält eine Überlappung zwischen aufeinanderfolgenden Chunks, um den Kontext an den Grenzen zu bewahren.
3. Embedding
Jeder Chunk wird über ein Embedding-Modell (wie OpenAIs text-embedding-3-small) in einen numerischen Vektor (Embedding) umgewandelt. Diese Vektoren erfassen die semantische Bedeutung des Textes: Sätze mit ähnlichen Bedeutungen haben Vektoren, die im mehrdimensionalen Raum nahe beieinander liegen.
4. Vector Store
Die Vektoren werden in einem Vector Store (oder Vektordatenbank) gespeichert, wie ChromaDB, Pinecone, Weaviate oder FAISS. Diese Datenbank ist für die Ähnlichkeitssuche optimiert: Bei einer Abfrage findet sie die ähnlichsten Vektoren (und damit die relevantesten Text-Chunks).
Phase 2: Retrieval + Generierung
Wenn der Benutzer eine Frage stellt:
- Die Frage wird mit demselben Embedding-Modell in ein Embedding umgewandelt.
- Der Vector Store findet die ähnlichsten Chunks über Ähnlichkeitssuche (typischerweise Kosinusähnlichkeit oder euklidische Distanz).
- Die abgerufenen Chunks werden als Kontext in den Prompt eingefügt.
- Das LLM generiert die Antwort basierend auf dem bereitgestellten Kontext.
Eine RAG-Pipeline mit LangChain aufbauen
LangChain ist das beliebteste Python- (und JavaScript-) Framework zum Erstellen von LLM-basierten Anwendungen. Es bietet High-Level-Abstraktionen für jede Komponente der RAG-Pipeline.
Installation
pip install langchain langchain-openai langchain-community chromadb
Schritt 1: Dokumente laden
LangChain bietet Dutzende von Document Loadern für verschiedene Datenquellen.
from langchain_community.document_loaders import ( PyPDFLoader, WebBaseLoader, DirectoryLoader, TextLoader, ) # Ein PDF laden pdf_loader = PyPDFLoader("docs/handbuch.pdf") pdf_docs = pdf_loader.load() # Eine Webseite laden web_loader = WebBaseLoader("https://docs.example.com/guide") web_docs = web_loader.load() # Alle .md-Dateien aus einem Verzeichnis laden dir_loader = DirectoryLoader("./knowledge_base", glob="**/*.md", loader_cls=TextLoader) md_docs = dir_loader.load() all_docs = pdf_docs + web_docs + md_docs
Schritt 2: Dokumente in Chunks aufteilen
from langchain.text_splitter import RecursiveCharacterTextSplitter text_splitter = RecursiveCharacterTextSplitter( chunk_size=1000, chunk_overlap=200, separators=["\n\n", "\n", ". ", " ", ""], ) chunks = text_splitter.split_documents(all_docs) print(f"Originaldokumente: {len(all_docs)}, Chunks: {len(chunks)}")
Der Parameter chunk_overlap ist entscheidend: Er erzeugt eine Überlappung zwischen aufeinanderfolgenden Chunks, damit der Kontext an den Grenzen nicht verloren geht.
Schritt 3: Embeddings und Vector Store erstellen
from langchain_openai import OpenAIEmbeddings from langchain_community.vectorstores import Chroma embedding_model = OpenAIEmbeddings(model="text-embedding-3-small") vectorstore = Chroma.from_documents( documents=chunks, embedding=embedding_model, persist_directory="./chroma_db", )
Schritt 4: Den Retriever erstellen
Der Retriever ist die Komponente, die bei einer Abfrage die relevantesten Chunks aus dem Vector Store abruft.
retriever = vectorstore.as_retriever( search_type="similarity", search_kwargs={"k": 4}, ) relevant_docs = retriever.invoke("Wie funktioniert die Authentifizierung?") for doc in relevant_docs: print(doc.page_content[:200]) print("---")
Schritt 5: Die RAG-Chain aufbauen
Jetzt fügen wir alles mit einem LLM und einem Prompt-Template zusammen.
from langchain_openai import ChatOpenAI from langchain_core.prompts import ChatPromptTemplate from langchain_core.runnables import RunnablePassthrough from langchain_core.output_parsers import StrOutputParser llm = ChatOpenAI(model="gpt-4o", temperature=0) prompt = ChatPromptTemplate.from_template(""" Beantworte die Frage ausschließlich basierend auf dem bereitgestellten Kontext. Wenn der Kontext nicht genügend Informationen enthält, sage, dass du es nicht weißt. Kontext: {context} Frage: {question} Antwort: """) def format_docs(docs): return "\n\n".join(doc.page_content for doc in docs) rag_chain = ( {"context": retriever | format_docs, "question": RunnablePassthrough()} | prompt | llm | StrOutputParser() ) response = rag_chain.invoke("Wie funktioniert die Authentifizierung im System?") print(response)
Fortgeschrittene RAG-Techniken
Die grundlegende Pipeline funktioniert gut, aber es gibt verschiedene Techniken, um die Antwortqualität erheblich zu verbessern.
Multi-Query Retrieval
Manchmal ist die Abfrage des Benutzers mehrdeutig oder nicht mit der in den Dokumenten verwendeten Sprache abgestimmt. Der Multi-Query Retriever generiert automatisch Varianten der ursprünglichen Frage, um mehrere Perspektiven zu erfassen.
from langchain.retrievers import MultiQueryRetriever multi_retriever = MultiQueryRetriever.from_llm( retriever=vectorstore.as_retriever(), llm=llm, ) docs = multi_retriever.invoke("Was sind die Sicherheits-Best-Practices?")
Contextual Compression
Nicht der gesamte Inhalt eines Chunks ist für die Abfrage relevant. Der Contextual Compression Retriever verwendet ein LLM, um nur die relevanten Teile aus jedem abgerufenen Chunk zu extrahieren.
from langchain.retrievers import ContextualCompressionRetriever from langchain.retrievers.document_compressors import LLMChainExtractor compressor = LLMChainExtractor.from_llm(llm) compression_retriever = ContextualCompressionRetriever( base_compressor=compressor, base_retriever=retriever, )
Hybrid Search
Rein semantische Suche ist nicht immer optimal. Hybrid Search kombiniert semantische Suche (Embeddings) mit lexikalischer Suche (BM25, Keyword-Matching), um bessere Ergebnisse zu erzielen.
from langchain.retrievers import EnsembleRetriever from langchain_community.retrievers import BM25Retriever bm25_retriever = BM25Retriever.from_documents(chunks) bm25_retriever.k = 4 semantic_retriever = vectorstore.as_retriever(search_kwargs={"k": 4}) hybrid_retriever = EnsembleRetriever( retrievers=[bm25_retriever, semantic_retriever], weights=[0.4, 0.6], )
Conversational RAG (mit Gedächtnis)
Um einen RAG-Chatbot zu bauen, der sich an den Gesprächskontext erinnert, musst du ein Gedächtnis hinzufügen, das die Fragen des Benutzers unter Berücksichtigung des Gesprächsverlaufs umformuliert.
from langchain.chains import create_history_aware_retriever from langchain_core.prompts import MessagesPlaceholder contextualize_prompt = ChatPromptTemplate.from_messages([ ("system", "Formuliere angesichts des Chatverlaufs und der letzten Frage des Benutzers " "die Frage so um, dass sie ohne den Verlauf verständlich ist."), MessagesPlaceholder("chat_history"), ("human", "{input}"), ]) history_aware_retriever = create_history_aware_retriever( llm, retriever, contextualize_prompt )
Best Practices
- Wähle die richtige Chunk-Größe: Experimentiere mit verschiedenen Größen (500-1500 Token). Kleinere Chunks für präzise Antworten, größere für breiteren Kontext.
- Verwende Dokument-Metadaten: Füge Quelle, Datum und Kategorie als Metadaten zu Chunks hinzu. Dies ermöglicht das Filtern von Ergebnissen beim Retrieval.
- Bewerte die Qualität: Verwende Frameworks wie RAGAS, um Metriken wie Faithfulness, Relevancy und Context Precision zu messen.
- Verwalte Dokumenten-Updates: Implementiere eine Re-Ingestion-Pipeline, um den Vector Store mit deinen Datenquellen synchron zu halten.
- Füge einen Re-Ranker hinzu: Verwende nach dem initialen Retrieval ein Re-Ranking-Modell (wie Cohere Rerank), um die Ergebnisse nach tatsächlicher Relevanz neu zu ordnen.
Fazit
RAG ist zur Standardarchitektur für den Bau von KI-Anwendungen geworden, die Zugriff auf spezifisches, aktuelles Wissen benötigen. LangChain vereinfacht die Implementierung erheblich, indem es Abstraktionen für jede Komponente der Pipeline bereitstellt.
Nächste Schritte:
- Lokal experimentieren: Beginne mit ChromaDB und wenigen Dokumenten, um dich mit der Pipeline vertraut zu machen.
- LangSmith erkunden: Verwende LangSmith, um deine Chains in der Produktion zu überwachen und zu debuggen.
- Verschiedene Embedding-Modelle testen: Vergleiche Modelle wie
text-embedding-3-small,text-embedding-3-largeund Open-Source-Modelle von Sentence Transformers. - Dokumentation lesen: Die LangChain-Dokumentation ist eine hervorragende und ständig aktualisierte Ressource.