RAG mit lokalen LLMs: Dein Schritt-für-Schritt Guide zur „Vector Database“

Lerne Vektor-Datenbanken für RAG von Grund auf! Praktischer Guide zur Erstellung, Befüllung und Nutzung mit lokalen LLMs für kontextreiche KI-Antworten.
Picture of Ivan Lenic

Ivan Lenic

Schluss mit Ahnungslosigkeit: So gibst Du Deinem lokalen LLM externes Wissen mit Vektor-Datenbanken für RAG

Stell Dir vor, Dein Large Language Model (LLM) könnte nicht nur allgemeine Texte generieren, sondern wäre ein echter Spezialist für Deine spezifischen Daten, Projekte oder Dein Fachgebiet. Standard-LLMs sind zwar beeindruckend, aber ihr Wissen ist oft zu generisch, nicht aktuell genug oder ihnen fehlt schlicht der Kontext zu Deinen internen Dokumenten oder der Nische, in der Du arbeitest. Das Ergebnis? Antworten, die an der Oberfläche kratzen oder im schlimmsten Fall ungenau sind.

Genau hier kommt Retrieval Augmented Generation (RAG) ins Spiel – eine mächtige Technik, um LLMs gezielt mit externem, maßgeschneidertem Wissen anzureichern und sie so für spezifische Anwendungsfälle zu optimieren. Der Schlüssel zu einem erfolgreichen RAG-System sind dabei Vektor-Datenbanken. Sie fungieren als das intelligente „externe Gedächtnis“ Deines LLMs und ermöglichen es ihm, auf Deine spezifischen Informationen zuzugreifen und darauf basierend präzise, kontextbezogene Antworten zu liefern.

Dieser Artikel ist Dein praktischer Leitfaden. Er zeigt Dir nicht nur, was Vektor-Datenbanken sind und warum sie für RAG so entscheidend sind, sondern führt Dich Schritt für Schritt durch den Prozess, wie Du sie selbst mit Deinen Daten erstellst und für ein lokal laufendes LLM nutzt. Von der Theorie direkt in die anwendbare Praxis – damit Dein LLM genau das weiß, was Du brauchst.

Das „Was“: Vektor-Datenbanken und RAG entmystifiziert

Bevor wir uns in die praktische Umsetzung stürzen, wollen wir die Kernkonzepte verstehen. Was genau verbirgt sich hinter Vektor-Embeddings, Vektor-Datenbanken und dem RAG-Ansatz? Es ist weniger kompliziert, als es vielleicht klingt.

Stell Dir vor, Du könntest die Bedeutung von Wörtern, Sätzen oder ganzen Textabschnitten in Form von Zahlen ausdrücken. Genau das leisten Vektor-Embeddings. Es sind im Grunde Listen von Zahlen (Vektoren), die in einem hochdimensionalen Raum eine semantische Repräsentation von Text darstellen. Das Besondere daran: Texte mit ähnlicher Bedeutung haben auch ähnliche Vektoren und liegen in diesem „Bedeutungsraum“ nahe beieinander.

Diese Umwandlung von Text in aussagekräftige Zahlenvektoren geschieht mithilfe spezieller Machine-Learning-Modelle, sogenannter Embedding-Modelle (z.B. BERT, RoBERTa, oder modernere Modelle wie die von Sentence Transformers). Die Fähigkeit, semantische Ähnlichkeit mathematisch messbar zu machen (z.B. über den Kosinus-Abstand zwischen Vektoren), ist der Grundstein für viele fortgeschrittene KI-Anwendungen, insbesondere für die semantische Suche – also das Finden von Inhalten basierend auf ihrer Bedeutung, nicht nur auf exakten Schlüsselwörtern.

Wenn Du nun Tausende oder Millionen solcher Vektor-Embeddings für Deine Dokumente generiert hast, brauchst Du einen Ort, um sie effizient zu speichern und vor allem blitzschnell nach Ähnlichkeiten durchsuchen zu können. Hier kommen Vektor-Datenbanken ins Spiel.

Eine Vektor-Datenbank ist eine spezialisierte Datenbank, die dafür optimiert ist, hochdimensionale Vektoren (unsere Embeddings) zu speichern, zu indizieren und extrem schnelle Ähnlichkeitssuchen (Nearest Neighbor Searches) durchzuführen. Im Gegensatz zu traditionellen relationalen Datenbanken, die primär mit strukturierten Daten in Tabellen arbeiten, sind Vektor-Datenbanken darauf ausgelegt, die geometrischen Beziehungen zwischen Vektoren im hochdimensionalen Raum effizient zu managen. Sie verwenden spezielle Indizierungsalgorithmen (wie HNSW, IVFADC), um die Suche auch bei sehr großen Datenmengen performant zu gestalten. Bekannte Beispiele sind ChromaDB, FAISS, Weaviate, Pinecone oder Qdrant.

Nun zum Kernstück: Retrieval Augmented Generation (RAG). Wie der Name schon andeutet, geht es darum, den Generierungsprozess eines LLMs durch einen vorgeschalteten Abruf (Retrieval) von relevanten Informationen zu verbessern. Anstatt dass das LLM nur auf sein vortrainiertes (und möglicherweise veraltetes oder zu allgemeines) Wissen zurückgreift, wird es mit spezifischem Kontext aus einer externen Wissensquelle versorgt.

Der typische RAG-Workflow sieht so aus:

  1. Nutzeranfrage: Ein Nutzer stellt eine Frage oder gibt einen Prompt ein.
  2. Retrieval-Phase: Die Nutzeranfrage wird ebenfalls in ein Vektor-Embedding umgewandelt. Mit diesem Embedding wird dann in der Vektor-Datenbank nach den semantisch ähnlichsten Textstücken (Chunks) aus der Wissensbasis gesucht.
  3. Augmentation-Phase: Die gefundenen relevanten Text-Chunks werden zusammen mit der ursprünglichen Nutzeranfrage als erweiterter Kontext an das LLM übergeben.
  4. Generierungs-Phase: Das LLM nutzt nun diesen zusätzlichen, spezifischen Kontext, um eine fundiertere, präzisere und relevantere Antwort zu generieren.

Die unverzichtbare Rolle der Vektor-DB im RAG-Prozess

Wie Du siehst, ist die Vektor-Datenbank das Herzstück der Retrieval-Phase im RAG-Prozess. Ohne sie wäre eine effiziente und semantisch präzise Suche nach relevantem Kontext in großen Mengen unstrukturierter Daten kaum denkbar. Sie ist das Bindeglied, das es dem LLM ermöglicht, auf das Wissen zuzugreifen, das Du ihm über Deine Dokumente zur Verfügung stellst. Sie sorgt dafür, dass das LLM nicht im Dunkeln tappt, sondern die richtigen Puzzleteile für eine qualitativ hochwertige Antwort erhält.

Das „Warum“: Unschlagbare Vorteile von Vektor-Datenbanken für RAG

Nachdem wir geklärt haben, was Vektor-Datenbanken und RAG sind, stellt sich die Frage: Warum solltest Du diesen Aufwand betreiben? Die Antwort liegt in den signifikanten Vorteilen, die dieser Ansatz bietet, insbesondere wenn Du LLMs für spezifische, wissensintensive Aufgaben einsetzen möchtest.

Einer der größten Vorteile ist die semantische Suche. Traditionelle Suchmethoden basieren oft auf Keyword-Matching. Das bedeutet, sie finden nur Dokumente, die exakt die eingegebenen Suchbegriffe enthalten. Vektor-Datenbanken hingegen ermöglichen eine Suche basierend auf der semantischen Ähnlichkeit. Dank der Vektor-Embeddings versteht das System die Bedeutung und den Kontext Deiner Anfrage. Es findet also auch relevante Informationen, die vielleicht andere Worte verwenden, aber inhaltlich passen. Für ein RAG-System ist das Gold wert, da das LLM so mit wirklich relevantem Kontext versorgt wird, auch wenn Deine Frage nicht exakt die Formulierungen aus den Quelldokumenten trifft.

LLMs neigen bekanntermaßen dazu, Fakten zu erfinden oder „Halluzinationen“ zu produzieren, wenn sie keine genaue Antwort in ihrem Trainingswissen finden oder wenn die Anfrage mehrdeutig ist. Durch RAG und die Bereitstellung von relevantem Kontext aus Deiner Vektor-Datenbank gibst Du dem LLM eine solide Wissensgrundlage für seine Antwort. Es muss weniger „raten“ oder auf unsichere interne Informationen zurückgreifen, was die Wahrscheinlichkeit von faktisch falschen oder irreführenden Aussagen deutlich reduziert. Die Antworten werden verlässlicher und vertrauenswürdiger.

Das Wissen der meisten großen, vortrainierten LLMs ist auf den Zeitpunkt ihres Trainingsdatensatzes beschränkt. Informationen können also veraltet sein. Mit einer eigenen Vektor-Datenbank für RAG hast Du die volle Kontrolle darüber, welches Wissen dem LLM zur Verfügung steht. Du kannst Deine Wissensbasis jederzeit mit neuen Dokumenten aktualisieren, veraltete Informationen entfernen oder spezifische Versionen von Dokumenten verwenden. Das LLM greift somit immer auf die von Dir kuratierten und aktuellen Daten zu.

Möchtest Du, dass Dein LLM ein Experte für Deine spezielle Nische, Deine Unternehmensprozesse oder Deine Forschungsergebnisse wird? Durch das Befüllen einer Vektor-Datenbank mit Deinen eigenen Dokumenten, Fachartikeln, internen Richtlinien oder Produktbeschreibungen trainierst Du das RAG-System quasi auf Dein Spezialgebiet. Das LLM kann dann hochspezifische Fragen beantworten, die weit über sein allgemeines Trainingswissen hinausgehen.

Wenn Du RAG mit einem lokal laufenden LLM und einer lokalen Vektor-Datenbank umsetzt, behältst Du die volle Kontrolle über Deine Daten. Es müssen keine sensiblen Informationen an externe APIs gesendet werden, was besonders für Unternehmen mit strengen Datenschutzrichtlinien entscheidend ist. Zudem können die laufenden Kosten im Vergleich zur Nutzung großer kommerzieller LLM-APIs für jede Anfrage, die domänenspezifisches Wissen erfordert, deutlich geringer sein. Du investierst einmal in den Aufbau des Systems und kannst es dann weitgehend unabhängig nutzen.

Zusammenfassend lässt sich sagen, dass der Einsatz von Vektor-Datenbanken in RAG-Systemen die Qualität, Relevanz, Genauigkeit und Anpassbarkeit von LLM-Antworten erheblich steigert. Sie transformieren generische Sprachmodelle in maßgeschneiderte Wissensarbeiter.

Das „Wie“: Schritt-für-Schritt zur RAG-Anwendung mit Vektor-DB und lokalem LLM (Praxis-Guide)

Jetzt wird es konkret! Wir bauen gemeinsam eine einfache RAG-Anwendung mit einer lokalen Vektor-Datenbank und einem lokal laufenden LLM. Dieser Guide verwendet Python und einige gängige Bibliotheken. Ziel ist es, Dir die grundlegenden Schritte zu zeigen, damit Du eine Basis für eigene Experimente hast.

Vorbereitung: Werkzeuge und Voraussetzungen

Bevor wir starten, stelle sicher, dass Du folgende Dinge eingerichtet hast:

  1. Python-Umgebung: Eine aktuelle Python-Version (z.B. 3.9 oder höher). Es wird dringend empfohlen, ein virtuelles Environment (z.B. mit venv oder conda) zu verwenden, um Abhängigkeitskonflikte zu vermeiden.

    python -m venv rag_env
    source rag_env/bin/activate # Für Linux/macOS
    # rag_env\Scripts\activate # Für Windows

  2. Notwendige Bibliotheken: Installiere die benötigten Python-Pakete. Wir verwenden sentence-transformers, chromadb, transformers, torch und langchain (für das komfortable Chunking).

    pip install sentence-transformers chromadb transformers torch langchain
    # Falls Du torch mit CUDA-Support benötigst (für NVIDIA GPUs):
    # Konsultiere die offizielle PyTorch-Website für den passenden Installationsbefehl.

  3. Einrichtung eines lokalen LLMs: Für diesen Guide gehen wir davon aus, dass Du Zugriff auf ein lokal laufendes LLM hast. Hier gibt es mehrere beliebte Optionen:
    • Ollama: Ein sehr benutzerfreundliches Tool, um verschiedene Open-Source-LLMs (wie Llama 2, Mistral, etc.) lokal auszuführen und über eine API anzusprechen. (Sehr empfehlenswert für den Einstieg!)
    • LM Studio: Eine weitere Desktop-Anwendung, die das Herunterladen und Ausführen von LLMs vereinfacht und einen lokalen Server bereitstellt.
    • Hugging Face Transformers: Für fortgeschrittenere Nutzer kannst Du Modelle direkt über die transformers-Bibliothek laden und ausführen (benötigt oft mehr Konfiguration und Ressourcen).
      Stelle sicher, dass Dein lokales LLM läuft und Du weißt, wie Du Anfragen an seine API (falls vorhanden, z.B. bei Ollama) senden kannst oder wie Du es direkt in Python instanziierst.
  4. Deine Quelldaten: Erstelle einen Ordner (z.B. data) und lege dort einige Textdateien (.txt) mit dem Wissen ab, das Du Deinem LLM zugänglich machen möchtest. Für unser Beispiel reichen 2-3 kurze Textdateien mit unterschiedlichen Inhalten.

Schritt 1: Daten aufbereiten und „Chunking“

LLMs haben ein begrenztes Kontextfenster, d.h. sie können nur eine bestimmte Menge an Text auf einmal verarbeiten. Außerdem führt das Aufteilen Deiner Dokumente in kleinere, thematisch kohärente Abschnitte (Chunks) oft zu besseren und präziseren Ergebnissen bei der Ähnlichkeitssuche.

Hier ein Python-Beispiel zum Laden von Textdateien und deren Aufteilung in überlappende Chunks mithilfe von Langchain:

import os
from langchain.text_splitter import RecursiveCharacterTextSplitter

def load_and_chunk_texts(directory_path, chunk_size=500, chunk_overlap=50):
    documents = []
    for filename in os.listdir(directory_path):
        if filename.endswith(".txt"):
            file_path = os.path.join(directory_path, filename)
            try:
                with open(file_path, 'r', encoding='utf-8') as f:
                    documents.append({"text": f.read(), "source": filename})
            except Exception as e:
                print(f"Fehler beim Lesen der Datei {filename}: {e}")

    text_splitter = RecursiveCharacterTextSplitter(
        chunk_size=chunk_size,
        chunk_overlap=chunk_overlap,
        length_function=len
    )

    all_chunks = []
    for doc in documents:
        chunks = text_splitter.split_text(doc["text"])
        for i, chunk_text in enumerate(chunks):
            all_chunks.append({
                "text": chunk_text,
                "metadata": {"source": doc["source"], "chunk_id": f"{doc['source']}_chunk_{i}"}
            })
    return all_chunks

# Beispielaufruf:
data_directory = "data"  # Erstelle diesen Ordner und lege .txt-Dateien hinein
chunks_with_metadata = load_and_chunk_texts(data_directory)

if chunks_with_metadata:
    print(f"Anzahl der generierten Chunks: {len(chunks_with_metadata)}")
    print(f"Beispiel Chunk: {chunks_with_metadata[0]}")
else:
    print("Keine Chunks generiert. Stelle sicher, dass Textdateien im 'data'-Ordner sind.")

Schritt 2: Vektor-Embeddings generieren

Nachdem wir unsere Daten in handhabbare Chunks zerlegt haben, müssen wir diese in Vektor-Embeddings umwandeln.

Python-Beispiel zum Generieren von Embeddings für unsere Chunks:

from sentence_transformers import SentenceTransformer
import torch # Für Device Check

# Stelle sicher, dass chunks_with_metadata aus dem vorherigen Schritt vorhanden ist
if chunks_with_metadata:
    # Lade das Embedding-Modell
    # Das Modell wird beim ersten Mal heruntergeladen und lokal zwischengespeichert.
    try:
        # Optional: GPU-Unterstützung aktivieren (falls eine CUDA-fähige GPU verfügbar ist)
        device = 'cuda' if torch.cuda.is_available() else 'cpu'
        embedding_model = SentenceTransformer('all-MiniLM-L6-v2', device=device)
        print(f"Embedding-Modell geladen (verwendet Gerät: {embedding_model.device}).")

        # Extrahiere nur die Texte der Chunks für das Embedding
        chunk_texts = [chunk['text'] for chunk in chunks_with_metadata]

        # Generiere die Embeddings
        embeddings = embedding_model.encode(chunk_texts, show_progress_bar=True)
        print(f"Embeddings generiert. Shape: {embeddings.shape}")

        # Füge die Embeddings zu unseren Chunk-Daten hinzu
        for i, chunk in enumerate(chunks_with_metadata):
            chunk['embedding'] = embeddings[i]

    except Exception as e:
        print(f"Fehler beim Laden des Embedding-Modells oder Generieren der Embeddings: {e}")
        print("Stelle sicher, dass Du eine Internetverbindung für den ersten Download des Modells hast.")
        embeddings = [] 
else:
    print("Keine Chunks zum Generieren von Embeddings vorhanden.")
    embeddings = []

Schritt 3: Vektor-Datenbank auswählen und einrichten

Jetzt ist es an der Zeit, eine Vektor-Datenbank einzurichten. Wir verwenden ChromaDB.

import chromadb

# Stelle sicher, dass embeddings und chunks_with_metadata vorhanden sind
if 'embeddings' in locals() and len(embeddings) > 0:
    try:
        # WICHTIG: Die Wahl des Clients bestimmt, wie die Daten gespeichert werden.
        # chromadb.Client() (In-Memory): Daten gehen beim Beenden des Skripts verloren. Gut für schnelle Tests.
        # chromadb.PersistentClient(path="./chroma_db_persistent"): Daten werden auf der Festplatte im Ordner ./chroma_db_persistent gespeichert und bleiben erhalten.

        # Für dieses Tutorial verwenden wir den In-Memory-Client für Einfachheit.
        # Für produktive Anwendungen oder wenn Du Deine Daten behalten möchtest, nutze PersistentClient!
        client = chromadb.Client() 
        print("ChromaDB Client initialisiert (In-Memory). Für persistente Speicherung 'PersistentClient' verwenden.")

        collection_name = "my_rag_collection_final" 
        try:
            collection = client.get_collection(name=collection_name)
            print(f"Bestehende Collection '{collection_name}' geladen.")
        except: 
            # metadata={"hnsw:space": "cosine"} ist der Default für viele Embedding-Modelle,
            # explizite Angabe kann bei anderen Modellen oder gewünschten Metriken nötig sein.
            collection = client.create_collection(name=collection_name)
            print(f"Neue Collection '{collection_name}' erstellt.")

    except Exception as e:
        print(f"Fehler bei der Initialisierung von ChromaDB oder der Collection-Erstellung: {e}")
        collection = None
else:
    print("Keine Embeddings für ChromaDB vorhanden.")
    collection = None

Schritt 4: Embeddings (und Metadaten) in die Datenbank laden

Mit unserer initialisierten Collection können wir nun die generierten Embeddings zusammen mit den zugehörigen Text-Chunks und Metadaten speichern.

# Stelle sicher, dass collection und chunks_with_metadata (mit 'embedding' Key) vorhanden sind
if 'collection' in locals() and collection is not None and chunks_with_metadata and 'embedding' in chunks_with_metadata[0]:
    try:
        # Bereite die Daten für ChromaDB vor
        ids_to_add = [chunk['metadata']['chunk_id'] for chunk in chunks_with_metadata]
        embeddings_to_add = [chunk['embedding'].tolist() for chunk in chunks_with_metadata] 
        documents_to_add = [chunk['text'] for chunk in chunks_with_metadata]
        metadata_to_add = [chunk['metadata'] for chunk in chunks_with_metadata]

        # Füge die Daten zur Collection hinzu
        collection.add(
            ids=ids_to_add,
            embeddings=embeddings_to_add,
            documents=documents_to_add,
            metadatas=metadata_to_add
        )
        print(f"{collection.count()} Elemente zur Collection '{collection_name}' hinzugefügt.")

    except Exception as e:
        print(f"Fehler beim Hinzufügen der Daten zu ChromaDB: {e}")
else:
    print("ChromaDB Collection nicht initialisiert oder keine Daten zum Hinzufügen vorhanden.")

Schritt 5: Ähnlichkeitssuche durchführen (Retrieval)

Wir nehmen eine Nutzeranfrage, wandeln sie mit demselben Embedding-Modell in ein Embedding um und suchen dann in unserer Vektor-Datenbank nach den ähnlichsten Chunks.

# Stelle sicher, dass collection, embedding_model vorhanden sind
if 'collection' in locals() and collection is not None and 'embedding_model' in locals():
    user_query = "Was sind die Hauptvorteile von RAG?" 

    try:
        query_embedding = embedding_model.encode(user_query).tolist()
        num_results = 3
        results = collection.query(
            query_embeddings=[query_embedding], 
            n_results=num_results,
            include=['documents', 'metadatas', 'distances'] 
        )

        print(f"\nSuchergebnisse für die Anfrage: '{user_query}'")
        if results and results.get('documents') and results.get('documents')[0]:
            retrieved_documents = results['documents'][0]
            retrieved_metadatas = results['metadatas'][0]
            retrieved_distances = results['distances'][0]

            for i, doc_text in enumerate(retrieved_documents):
                print(f"\n--- Ergebnis {i+1} (Distanz: {retrieved_distances[i]:.4f}) ---")
                print(f"Quelle: {retrieved_metadatas[i].get('source', 'N/A')}")
                print(f"Text: {doc_text[:200]}...") 
        else:
            print("Keine relevanten Dokumente gefunden.")
            retrieved_documents = [] # Sicherstellen, dass die Variable existiert

    except Exception as e:
        print(f"Fehler bei der Ähnlichkeitssuche: {e}")
        retrieved_documents = []
else:
    print("ChromaDB Collection oder Embedding-Modell nicht verfügbar für die Suche.")
    retrieved_documents = []

Schritt 6: Kontext an das lokale LLM übergeben und Antwort generieren (Generation)

Nachdem wir die relevantesten Chunks abgerufen haben, kombinieren wir diese mit der Nutzeranfrage und übergeben alles als Kontext an unser lokales LLM. Hier ein Beispiel mit einer Funktion für Ollama:

import requests 
import json     

def query_ollama_model(prompt_text, model_name="mistral:latest", ollama_base_url="http://localhost:11434"):
    """
    Sendet einen Prompt an die Ollama API und gibt die Antwort des LLMs zurück.
    """
    api_url = f"{ollama_base_url}/api/generate"
    payload = {
        "model": model_name,
        "prompt": prompt_text,
        "stream": False 
    }
    try:
        response = requests.post(api_url, json=payload, timeout=60) 
        response.raise_for_status() 

        response_data = response.json()
        if "response" in response_data:
            return response_data["response"].strip()
        elif "error" in response_data:
            return f"Ollama API Fehler: {response_data['error']}"
        else:
            return f"Unbekanntes Antwortformat von Ollama. Antwort: {response.text}"

    except requests.exceptions.Timeout:
        return "Fehler: Timeout bei der Anfrage an Ollama."
    except requests.exceptions.ConnectionError:
        return "Fehler: Konnte keine Verbindung zum Ollama-Server herstellen. Läuft Ollama und ist der Port korrekt?"
    except requests.exceptions.RequestException as e:
        return f"Fehler bei der LLM-Abfrage (Ollama): {e}"
    except json.JSONDecodeError:
        return f"Fehler: Ungültige JSON-Antwort von Ollama erhalten. Antworttext: {response.text}"


if 'retrieved_documents' in locals() and retrieved_documents and 'user_query' in locals():
    context_string = "\n\n---\n\n".join(retrieved_documents) 

    final_prompt = f"""System-Anweisung: Du bist ein hilfreicher KI-Assistent. Deine Aufgabe ist es, die Frage des Nutzers präzise und ausschließlich basierend auf dem folgenden bereitgestellten Kontext zu beantworten. Wenn die Informationen im Kontext nicht ausreichen, um die Frage zu beantworten, gib dies klar an (z.B. "Basierend auf dem bereitgestellten Kontext kann ich diese Frage nicht beantworten."). Erfinde keine Informationen.

Bereitgestellter Kontext:
{context_string}

Nutzerfrage:
{user_query}

Antwort:"""

    print("\n--- Finaler Prompt für das LLM ---")
    print(final_prompt)

    # Stelle sicher, dass Du ein Modell in Ollama geladen hast, z.B. 'mistral:latest'
    llm_response = query_ollama_model(final_prompt, model_name="mistral:latest") 

    print("\n--- LLM Antwort (Ollama) ---")
    print(llm_response)

else:
    print("Keine abgerufenen Dokumente oder Nutzeranfrage für das LLM vorhanden, um einen Prompt zu erstellen.")

Wichtige Überlegungen, Optimierung und Evaluation

Nachdem Du die grundlegenden Schritte gemeistert hast, gibt es viele Möglichkeiten, Dein RAG-System zu verfeinern und seine Leistung zu bewerten.

Wahl des Embedding-Modells und Ähnlichkeitsmetriken

  • Domänenspezifität, Dimensionalität, Sprache: Wähle Modelle, die zu Deinen Daten passen.
  • Ähnlichkeitsmetriken:
    • Kosinus-Ähnlichkeit (Cosine Similarity): Misst den Winkel, gut für Text-Embeddings. Standard für viele Sentence Transformers. (ChromaDB: hnsw:space: "cosine")
    • Euklidischer Abstand (L2-Distanz): Geradliniger Abstand. Kleinere Werte = höhere Ähnlichkeit. (ChromaDB: hnsw:space: "l2")
    • Innenprodukt (Inner Product / Dot Product): Äquivalent zu Kosinus bei normalisierten Vektoren. (ChromaDB: hnsw:space: "ip")
      Die Wahl hängt vom Embedding-Modell ab; die meisten sind für Kosinus-Ähnlichkeit optimiert.

Optimierung der Chunking-Strategie

  • Chunk-Größe & Überlappung: Experimentiere für optimalen Kontext.
  • Semantisches Chunking: Aufteilung entlang thematischer Grenzen (z.B. Absätze).

Qualität und Umfang der Metadaten

  • Quellenverfolgung: Immer Originalquelle speichern.
  • Filterung: Nutze Metadaten (Datum, Kategorie etc.), um die Suche in der Vektor-DB einzuschränken.

Evaluation Deines RAG-Systems: Wie gut ist es wirklich?

  • Qualität der abgerufenen Chunks (Retrieval):
    • Context Precision: Wie viele abgerufene Chunks sind relevant?
    • Context Recall: Wie viele relevante Chunks wurden gefunden?
  • Qualität der generierten Antwort (Generation):
    • Faithfulness / Groundedness: Basiert die Antwort nur auf dem Kontext?
    • Answer Relevance: Ist die Antwort gut und relevant für die Frage?
  • Frameworks zur Evaluation: Tools wie RAGAS, DeepEval oder ARES (von UpTrain) bieten Frameworks und Metriken zur Bewertung.
  • End-to-End-Tests: Definiere Testfragen und prüfe Dein System regelmäßig.

Iteratives Vorgehen

Beginne einfach, teste, identifiziere Schwachstellen, optimiere, wiederhole.

Troubleshooting: Häufige Probleme und Lösungen

  • Problem: Modell-Download schlägt fehl.
    • Lösung: Internetverbindung, Speicherplatz, Proxy-Einstellungen prüfen. Hugging Face Hub Verfügbarkeit checken.
  • Problem: Ollama-Server nicht erreichbar (ConnectionError).
    • Lösung: Sicherstellen, dass Ollama läuft (ollama serve oder Desktop-App). URL/Port und Firewall prüfen.
  • Problem: ChromaDB PersistentClient Pfadfehler.
    • Lösung: Pfadexistenz und Schreibrechte sicherstellen.
  • Problem: Dimension Mismatch (Embeddings).
    • Lösung: Dasselbe Embedding-Modell (oder gleiche Dimension) für Indexierung und Query verwenden.
  • Problem: Schlechte Suchergebnisse.
    • Lösung: Chunking, Embedding-Modell, Klarheit der Suchanfrage, Ähnlichkeitsmetrik überprüfen.
  • Problem: LLM halluziniert/ignoriert Kontext.
    • Lösung: Prompt optimieren (klare Anweisung!), Kontextrelevanz und -länge prüfen, LLM-Parameter (z.B. niedrige temperature) anpassen.
  • Problem: Langsame Performance.
    • Lösung: GPU für Embeddings/LLM nutzen, Batch-Verarbeitung, Vektor-DB-Indizes optimieren, ggf. robustere Vektor-DB oder quantisierte/kleinere LLMs erwägen.

Ausblick: Den RAG-Code strukturieren und erweitern

Für komplexere Anwendungen könntest Du die Logik in einer Klasse organisieren. Hier ein konzeptioneller Entwurf:

# class SimpleRAGSystem:
#     def __init__(self, 
#                  data_directory="data", 
#                  embedding_model_name='all-MiniLM-L6-v2', 
#                  # ... weitere Parameter ...
#                  ollama_model_name="mistral:latest"): # LLM Modellname hinzugefügt
#         
#         self.data_directory = data_directory
#         self.embedding_model_name = embedding_model_name
#         self.ollama_model_name = ollama_model_name # LLM Modell speichern
#         # ... (Rest der Initialisierung wie im vorherigen Entwurf) ...
#         
#         # Erwäge, die query_ollama_model Funktion als Methode in die Klasse zu integrieren
#         # oder sie so zu gestalten, dass sie das Modell als Parameter akzeptiert.
#         print("SimpleRAGSystem initialisiert.")
# 
#     # ... (_load_and_chunk_texts, ingest_data, search_context Methoden wie zuvor) ...
# 
#     def _query_llm(self, prompt_text): # Private Methode für LLM-Anbindung
#         # Nutzt die globale query_ollama_model Funktion oder eine integrierte Logik
#         return query_ollama_model(prompt_text, model_name=self.ollama_model_name)
#
#     def ask(self, user_query):
#         print(f"Anfrage: {user_query}")
#         retrieved_context_list = self.search_context(user_query)
# 
#         if not retrieved_context_list:
#             print("Kein Kontext gefunden.")
#             return "Ich konnte keinen relevanten Kontext in meinen Daten finden, um diese Frage zu beantworten."
# 
#         context_string = "\n\n---\n\n".join(retrieved_context_list)
#         
#         final_prompt = f"""System-Anweisung: Du bist ein hilfreicher KI-Assistent... (wie zuvor) ...
# 
# Bereitgestellter Kontext:
# {context_string}
# 
# Nutzerfrage:
# {user_query}
# 
# Antwort:"""
#         
#         llm_response = self._query_llm(final_prompt) 
#         return llm_response

# Beispielverwendung (auskommentiert):
# rag_system = SimpleRAGSystem(data_directory="data", vector_db_path="./my_persistent_rag_db")
# # Beim ersten Mal oder bei neuen Daten:
# # rag_system.ingest_data() 
# 
# response = rag_system.ask("Was sind die Hauptvorteile von RAG?")
# print(f"\nAntwort vom RAG-System:\n{response}")

Hinweis zur Klassenstruktur: Dies ist ein konzeptioneller Vorschlag. Die Fehlerbehandlung und weitere Details müssten für eine produktionsreife Klasse noch ausgebaut werden.

Schlussfolgerung: Dein LLM, Dein Wissen!

Wir sind am Ende unseres praktischen Leitfadens zur Nutzung von Vektor-Datenbanken für Retrieval Augmented Generation (RAG) mit lokalen LLMs angelangt. Du hast gesehen, was hinter diesen Technologien steckt, warum sie so transformative Kraft besitzen und vor allem, wie Du die einzelnen Komponenten Schritt für Schritt selbst aufsetzen kannst – von der Datenaufbereitung über die Erstellung von Embeddings und deren Speicherung in einer Vektor-Datenbank bis hin zur Übergabe des relevanten Kontexts an Dein lokal laufendes LLM.

Die Fähigkeit, Large Language Models mit Deinem eigenen, spezifischen und aktuellen Wissen anzureichern, ist ein echter Game-Changer. Vektor-Datenbanken sind dabei der unverzichtbare Schlüssel, um dieses Wissen effizient zugänglich zu machen und die „Ahnungslosigkeit“ generischer LLMs zu überwinden. Statt auf oft vage oder veraltete Informationen angewiesen zu sein, ermöglichst Du Deinem LLM, fundierte, kontextbezogene und damit deutlich wertvollere Antworten zu generieren.

Die Demokratisierung von KI schreitet unaufhaltsam voran. Mit Tools wie frei verfügbaren Embedding-Modellen, einfach zu nutzenden Vektor-Datenbanken wie ChromaDB und der wachsenden Zahl an leistungsfähigen, lokal ausführbaren LLMs liegt die Macht, maßgeschneiderte KI-Lösungen zu entwickeln, zunehmend in Deiner Hand. RAG ist nicht länger nur ein Konzept für große Forschungslabore, sondern eine greifbare Technik, die Du für Deine Projekte, Dein Unternehmen oder Deine persönlichen Lernziele nutzen kannst.

Dieser Guide hat Dir das Rüstzeug an die Hand gegeben. Die Reise mag mit Experimenten und Anpassungen verbunden sein, aber die Belohnung – ein LLM, das wirklich Dein Wissen versteht und nutzt – ist den Aufwand wert.

Erstelle Deine erste Vektor-Datenbank, füttere sie mit Deinen Daten und erlebe selbst, wie RAG die Fähigkeiten Deines lokalen LLMs erweitert. Welche spannenden Anwendungen fallen Dir für Dein eigenes RAG-System ein? Lass es uns wissen!

Disclaimer zum Code:
Die in diesem Artikel bereitgestellten Code-Beispiele sind als Starthilfe und zur Illustration der grundlegenden Prinzipien von RAG mit Vektor-Datenbanken gedacht. Sie wurden für Klarheit und Verständlichkeit optimiert. Für den Einsatz in kritischen oder produktiven Systemen sind in der Regel weitergehende Maßnahmen wie umfassende Fehlerbehandlung, Sicherheitsaspekte, Performance-Optimierung und gründliche Tests unerlässlich.


Bei der Erstellung des Beitrags wurde der Autor von KI unterstützt.

Ähnliche Artikel

23.06.2025 – Free Tech Newsletter

TLDR Tech Newsletter – by Beenovate Dein wöchentlicher Tech-Fix – kurz & bündig

RAG mit lokalen LLMs: Dein Schritt-für-Schritt Guide zur „Vector Database“

Lerne Vektor-Datenbanken für RAG von Grund auf! Praktischer Guide zur Erstellung, Befüllung und Nutzung mit lokalen LLMs für kontextreiche KI-Antworten.