Vektordatenbanken für Retrieval-Augmented Generation (RAG): Implementierungsleitfaden

KI-Engineering

Technischer Leitfaden zur Implementierung von RAG-Systemen mit Vektordatenbanken. Vergleichen Sie Pinecone, Weaviate, Milvus und pgvector. Lernen Sie über Embeddings, Ähnlichkeitssuche und Produktionsarchitektur.

Vektordatenbanken für Retrieval-Augmented Generation (RAG): Implementierungsleitfaden

Retrieval-Augmented Generation (RAG) verbessert LLM-Antworten durch Abrufen relevanten Kontexts aus Wissensbasen. Vektordatenbanken ermöglichen effiziente Ähnlichkeitssuche, die für RAG-Systeme entscheidend ist.

Überblick über die RAG-Architektur

Komponenten

  • 1. Dokumenten-Ingestion: Quelldokumente verarbeiten und in Chunks aufteilen
  • 2. Embedding-Generierung: Chunks in Vektor-Embeddings umwandeln
  • 3. Vektorspeicherung: Embeddings in Vektordatenbank speichern
  • 4. Abfrageverarbeitung: Benutzerabfragen in Embeddings umwandeln
  • 5. Ähnlichkeitssuche: Relevante Chunks finden
  • 6. Kontext-Assemblierung: Abgerufene Chunks kombinieren
  • 7. LLM-Generierung: Antwort mit Kontext generieren

Vorteile

  • Aktuelle Informationen ohne Retraining
  • Quellenangabe für Antworten
  • Reduzierte Halluzinationen
  • Domänenspezifisches Wissen
  • Kosteneffektiv im Vergleich zu Fine-Tuning
  • Einfache Aktualisierung der Wissensbasis

Vektordatenbank-Vergleich

Pinecone

Vollständig verwaltete Vektordatenbank als Service.

Vorteile:

  • Keine Infrastrukturverwaltung
  • Hohe Leistung für großflächige Bereitstellungen
  • Automatische Skalierung
  • Echtzeit-Updates
  • 99,9% SLA-Verfügbarkeit

Überlegungen:

  • Nur SaaS (kein Self-Hosting)
  • Pro-Vektor-Preise können im großen Maßstab teuer sein
  • Daten extern gespeichert (DSGVO-Überlegungen)

Weaviate

Open-Source-Vektordatenbank mit Cloud- und Self-Hosting-Optionen.

Vorteile:

  • Flexible Bereitstellungsoptionen
  • Integrierte ML-Modelle für Vektorisierung
  • GraphQL-API
  • Hybride Suche (Vektor + Schlüsselwort)
  • Starke Community-Unterstützung

Überlegungen:

  • Erfordert Infrastrukturverwaltung bei Self-Hosting
  • Lernkurve für GraphQL
  • Performance-Tuning für Skalierung erforderlich

Milvus

Open-Source-Vektordatenbank optimiert für Milliarden-Maßstab-Bereitstellungen.

Vorteile:

  • Exzellente Leistung in großem Maßstab
  • Mehrere Indextypen für Optimierung
  • Cloud-native Architektur (Kubernetes)
  • Aktive Entwicklung und Community
  • GPU-Beschleunigungsunterstützung

Überlegungen:

  • Komplexe Einrichtung und Konfiguration
  • Erfordert erhebliche Ops-Expertise
  • Ressourcenintensiv

pgvector (PostgreSQL-Erweiterung)

Vektor-Ähnlichkeitssuche als PostgreSQL-Erweiterung.

Vorteile:

  • Bestehende PostgreSQL-Infrastruktur nutzen
  • Vektorsuche mit relationalen Daten kombinieren
  • Vertraute PostgreSQL-Tools und -Expertise
  • ACID-Transaktionen
  • Geringere operative Komplexität

Überlegungen:

  • Leistung im Vergleich zu spezialisierten Datenbanken begrenzt
  • Am besten für kleine bis mittlere Datensätze (<1M Vektoren)
  • Weniger optimiert für reine Vektoroperationen

Embedding-Modelle

OpenAI text-embedding-3-large

  • 3072 Dimensionen
  • Exzellente Allzweckleistung
  • Kosten: $0,13 pro 1M Token (Oktober 2025)
  • Einfache API-Integration

Cohere Embed v3

  • 1024 Dimensionen
  • Mehrsprachige Unterstützung
  • Wettbewerbsfähige Preise
  • Gut für semantische Suche

Open-Source: BGE-large

  • 1024 Dimensionen
  • Self-Hosting möglich (keine API-Kosten)
  • Starke Leistung in Benchmarks
  • Erfordert Rechenressourcen

Auswahlkriterien

  • Aufgabenspezifische Leistung: Auf Ihren Daten testen
  • Kosten: API vs. Self-Hosting
  • Dimensionalität: Speicher vs. Leistungs-Trade-off
  • Sprachunterstützung: Mehrsprachige Anforderungen
  • Latenz: Embedding-Generierungsgeschwindigkeit

Dokumentenverarbeitung

Chunking-Strategien

Chunking mit fester Größe:

  • Dokumente in feste Token-Anzahlen aufteilen (z.B. 512 Token)
  • Einfach zu implementieren
  • Kann mitten im Satz oder Konzept aufteilen

Semantisches Chunking:

  • An natürlichen Grenzen aufteilen (Absätze, Abschnitte)
  • Bewahrt semantische Kohärenz
  • Variable Chunk-Größen

Überlappende Chunks:

  • Überlappung zwischen Chunks einschließen (z.B. 50 Token)
  • Verhindert Informationsverlust an Grenzen
  • Erhöht Speicheranforderungen

Metadaten

Metadaten mit Vektoren speichern:

  • Quelldokument-ID und -Titel
  • Chunk-Position im Dokument
  • Zeitstempel der letzten Aktualisierung
  • Autor, Kategorie, Tags
  • Zugriffskontroll-Metadaten

Ähnlichkeitssuche

Distanzmetriken

Kosinus-Ähnlichkeit:

  • Am häufigsten für Text-Embeddings
  • Misst Winkel zwischen Vektoren
  • Bereich: -1 bis 1 (1 = identisch)

Euklidische Distanz:

  • Geometrische Distanz zwischen Punkten
  • Empfindlich für Vektormagnitude
  • Verwenden, wenn Magnitude wichtig ist

Punktprodukt:

  • Schnelle Berechnung
  • Erfordert normalisierte Vektoren für Ähnlichkeit
  • Üblich in Produktionssystemen

Abrufparameter

  • Top-k: Anzahl der abzurufenden Ergebnisse (typisch: 3-10)
  • Ähnlichkeitsschwellenwert: Minimaler Ähnlichkeitswert
  • Filter: Metadaten-basierte Filterung vor oder nach Suche
  • Re-Ranking: Nachbearbeitung der Ergebnisse für Relevanz

Code-Beispiel: Vollständige RAG-Pipeline mit Pinecone

python
import pinecone
from openai import OpenAI
from langchain.text_splitter import RecursiveCharacterTextSplitter
from typing import List, Dict
import numpy as np

class RAGPipeline:
    """Produktions-RAG-System mit Pinecone und OpenAI"""
    
    def __init__(self, pinecone_api_key: str, openai_api_key: str, index_name: str = "knowledge-base"):
        # Pinecone initialisieren
        pinecone.init(api_key=pinecone_api_key)
        self.index = pinecone.Index(index_name)
        
        # OpenAI initialisieren
        self.openai_client = OpenAI(api_key=openai_api_key)
        
        # Text-Splitter initialisieren
        self.text_splitter = RecursiveCharacterTextSplitter(
            chunk_size=512,
            chunk_overlap=50,
            length_function=len
        )
    
    def embed_text(self, text: str) -> List[float]:
        """Embeddings mit OpenAI generieren"""
        response = self.openai_client.embeddings.create(
            model="text-embedding-3-large",
            input=text
        )
        return response.data[0].embedding
    
    def ingest_documents(self, documents: List[Dict[str, str]]):
        """Dokumente in Chunks aufteilen und in Pinecone indizieren"""
        vectors_to_upsert = []
        
        for doc_id, doc in enumerate(documents):
            # Dokument in Chunks aufteilen
            chunks = self.text_splitter.split_text(doc["content"])
            
            for chunk_id, chunk in enumerate(chunks):
                # Embedding generieren
                embedding = self.embed_text(chunk)
                
                # Metadaten vorbereiten
                metadata = {
                    "text": chunk,
                    "source": doc.get("source", "unknown"),
                    "doc_id": doc_id,
                    "chunk_id": chunk_id,
                    "title": doc.get("title", "")
                }
                
                # Vektor-ID erstellen
                vector_id = f"doc{doc_id}_chunk{chunk_id}"
                
                vectors_to_upsert.append((
                    vector_id,
                    embedding,
                    metadata
                ))
            
            # Batch-Upsert alle 100 Vektoren
            if len(vectors_to_upsert) >= 100:
                self.index.upsert(vectors=vectors_to_upsert)
                vectors_to_upsert = []
        
        # Verbleibende Vektoren hochladen
        if vectors_to_upsert:
            self.index.upsert(vectors=vectors_to_upsert)
    
    def retrieve_context(self, query: str, top_k: int = 5, min_score: float = 0.7) -> List[Dict]:
        """Relevanten Kontext für Abfrage abrufen"""
        # Abfrage-Embedding generieren
        query_embedding = self.embed_text(query)
        
        # Pinecone durchsuchen
        results = self.index.query(
            vector=query_embedding,
            top_k=top_k,
            include_metadata=True
        )
        
        # Nach Mindestwert filtern und Kontext extrahieren
        context_chunks = []
        for match in results.matches:
            if match.score >= min_score:
                context_chunks.append({
                    "text": match.metadata["text"],
                    "source": match.metadata["source"],
                    "score": match.score
                })
        
        return context_chunks
    
    def generate_response(self, query: str, context_chunks: List[Dict]) -> str:
        """Antwort mit abgerufenem Kontext generieren"""
        # Kontext assemblieren
        context_text = "\n\n".join([
            f"[Quelle: {chunk['source']}]\n{chunk['text']}"
            for chunk in context_chunks
        ])
        
        # Prompt erstellen
        prompt = f"""Beantworte die Frage basierend auf dem bereitgestellten Kontext. Falls der Kontext nicht genügend Informationen enthält, sage dies.

Kontext:
{context_text}

Frage: {query}

Antwort:"""
        
        # Antwort generieren
        response = self.openai_client.chat.completions.create(
            model="gpt-5",
            messages=[{"role": "user", "content": prompt}],
            temperature=0.3,
            max_tokens=500
        )
        
        return response.choices[0].message.content
    
    def query(self, question: str, top_k: int = 5) -> Dict[str, any]:
        """Vollständige RAG-Pipeline: Abrufen und Generieren"""
        # Relevanten Kontext abrufen
        context_chunks = self.retrieve_context(question, top_k=top_k)
        
        if not context_chunks:
            return {
                "answer": "Ich konnte keine relevanten Informationen finden, um Ihre Frage zu beantworten.",
                "sources": [],
                "confidence": "low"
            }
        
        # Antwort generieren
        answer = self.generate_response(question, context_chunks)
        
        # Eindeutige Quellen extrahieren
        sources = list(set([chunk["source"] for chunk in context_chunks]))
        
        return {
            "answer": answer,
            "sources": sources,
            "context_chunks": context_chunks,
            "confidence": "high" if context_chunks[0]["score"] > 0.85 else "medium"
        }

# Verwendungsbeispiel
rag = RAGPipeline(
    pinecone_api_key="ihr-pinecone-schlüssel",
    openai_api_key="ihr-openai-schlüssel"
)

# Dokumente einpflegen
documents = [
    {
        "title": "KI-Sicherheitsrichtlinien",
        "content": "KI-Systeme sollten mit Sicherheit als Priorität entwickelt werden...",
        "source": "sicherheitsdokument.pdf"
    },
    {
        "title": "Produktions-Deployment",
        "content": "Beim Bereitstellen von KI-Systemen in der Produktion...",
        "source": "deployment_leitfaden.pdf"
    }
]

rag.ingest_documents(documents)

# System abfragen
result = rag.query("Wie sollte ich KI-Systeme sicher bereitstellen?")
print(f"Antwort: {result['answer']}")
print(f"Quellen: {result['sources']}")
print(f"Vertrauen: {result['confidence']}")

Code-Beispiel: RAG mit pgvector (PostgreSQL)

python
import psycopg2
from psycopg2.extras import execute_values
from openai import OpenAI
from typing import List, Dict
import numpy as np

class PgVectorRAG:
    """RAG-System mit PostgreSQL und pgvector-Erweiterung"""
    
    def __init__(self, db_config: Dict[str, str], openai_api_key: str):
        # Mit PostgreSQL verbinden
        self.conn = psycopg2.connect(**db_config)
        self.cursor = self.conn.cursor()
        
        # OpenAI initialisieren
        self.openai_client = OpenAI(api_key=openai_api_key)
        
        # Tabelle mit Vektor-Erweiterung erstellen
        self._init_database()
    
    def _init_database(self):
        """Datenbankschema mit pgvector initialisieren"""
        self.cursor.execute("CREATE EXTENSION IF NOT EXISTS vector;")
        
        self.cursor.execute("""
            CREATE TABLE IF NOT EXISTS documents (
                id SERIAL PRIMARY KEY,
                content TEXT NOT NULL,
                embedding vector(3072),  -- text-embedding-3-large Dimensionen
                metadata JSONB,
                created_at TIMESTAMP DEFAULT NOW()
            );
        """)
        
        # Vektorindex für schnelle Ähnlichkeitssuche erstellen
        self.cursor.execute("""
            CREATE INDEX IF NOT EXISTS documents_embedding_idx 
            ON documents USING ivfflat (embedding vector_cosine_ops)
            WITH (lists = 100);
        """)
        
        self.conn.commit()
    
    def embed_text(self, text: str) -> List[float]:
        """Embeddings generieren"""
        response = self.openai_client.embeddings.create(
            model="text-embedding-3-large",
            input=text
        )
        return response.data[0].embedding
    
    def add_document(self, content: str, metadata: Dict = None):
        """Dokument zur Vektordatenbank hinzufügen"""
        embedding = self.embed_text(content)
        
        self.cursor.execute(
            """
            INSERT INTO documents (content, embedding, metadata)
            VALUES (%s, %s, %s)
            """,
            (content, embedding, metadata or {})
        )
        self.conn.commit()
    
    def semantic_search(self, query: str, top_k: int = 5, threshold: float = 0.7) -> List[Dict]:
        """Semantische Suche mit Kosinus-Ähnlichkeit durchführen"""
        query_embedding = self.embed_text(query)
        
        self.cursor.execute(
            """
            SELECT 
                id,
                content,
                metadata,
                1 - (embedding <=> %s::vector) as similarity
            FROM documents
            WHERE 1 - (embedding <=> %s::vector) > %s
            ORDER BY embedding <=> %s::vector
            LIMIT %s;
            """,
            (query_embedding, query_embedding, threshold, query_embedding, top_k)
        )
        
        results = []
        for row in self.cursor.fetchall():
            results.append({
                "id": row[0],
                "content": row[1],
                "metadata": row[2],
                "similarity": float(row[3])
            })
        
        return results
    
    def generate_answer(self, query: str, top_k: int = 5) -> Dict:
        """Vollständiges RAG: Suchen und Generieren"""
        # Relevante Dokumente abrufen
        results = self.semantic_search(query, top_k=top_k)
        
        if not results:
            return {"answer": "Keine relevanten Informationen gefunden.", "sources": []}
        
        # Kontext aufbauen
        context = "\n\n".join([doc["content"] for doc in results])
        
        # Antwort generieren
        prompt = f"""Beantworte die Frage basierend auf folgendem Kontext.

Kontext:
{context}

Frage: {query}

Antwort:"""
        
        response = self.openai_client.chat.completions.create(
            model="gpt-5",
            messages=[{"role": "user", "content": prompt}],
            temperature=0.3
        )
        
        return {
            "answer": response.choices[0].message.content,
            "sources": results,
            "num_sources": len(results)
        }
    
    def close(self):
        """Datenbankverbindung schließen"""
        self.cursor.close()
        self.conn.close()

# Verwendung
db_config = {
    "host": "localhost",
    "database": "rag_db",
    "user": "postgres",
    "password": "ihr-passwort"
}

rag = PgVectorRAG(db_config, openai_api_key="ihr-openai-schlüssel")

# Dokumente hinzufügen
rag.add_document(
    "Vektordatenbanken ermöglichen effiziente Ähnlichkeitssuche für RAG-Systeme.",
    metadata={"source": "rag_leitfaden.pdf", "page": 1}
)

# Abfrage
result = rag.generate_answer("Wofür werden Vektordatenbanken verwendet?")
print(result["answer"])

rag.close()

Produktionsarchitektur

Ingestion-Pipeline

  • 1. Dokumenten-Warteschlange (z.B. SQS, RabbitMQ)
  • 2. Verarbeitungs-Worker: Parsen, in Chunks aufteilen, bereinigen
  • 3. Embedding-Service: Vektoren generieren (Batch für Effizienz)
  • 4. Vektordatenbank-Schreibvorgang: Mit Metadaten speichern
  • 5. Überwachung: Ingestion-Metriken verfolgen

Abfrage-Pipeline

  • 1. Benutzerabfrage empfangen
  • 2. Abfragevorverarbeitung (Normalisierung, Erweiterung)
  • 3. Abfrage-Embedding generieren
  • 4. Vektor-Ähnlichkeitssuche mit Filtern
  • 5. Ergebnisse re-ranken (optional)
  • 6. Kontext für LLM assemblieren
  • 7. Antwort generieren
  • 8. Nachbearbeiten und zurückgeben

Caching-Schichten

  • Abfrage-Cache: Beliebte Abfrageergebnisse speichern
  • Embedding-Cache: Embeddings für häufige Abfragen wiederverwenden
  • LLM-Antwort-Cache: Vollständige Antworten cachen
  • TTL basierend auf Aktualisierungsfrequenz des Inhalts

Optimierungsstrategien

Indizierung

  • HNSW (Hierarchical Navigable Small World): Schnelle approximative Suche
  • IVF (Inverted File Index): Raum für Effizienz partitionieren
  • Trade-off: Geschwindigkeit vs. Genauigkeit
  • Indexparameter basierend auf Datensatzgröße anpassen

Hybride Suche

Vektor- und Schlüsselwortsuche kombinieren:

  • Vektorsuche für semantische Ähnlichkeit
  • Schlüsselwortsuche für exakte Übereinstimmungen
  • Ergebnisse mit gewichteter Bewertung kombinieren
  • Verbessert Recall und Precision

Abfrageerweiterung

  • Mehrere Abfragevariationen generieren
  • Für jede Variation abrufen
  • Ergebnisse deduplizieren und re-ranken
  • Verbessert Recall für mehrdeutige Abfragen

Evaluierungsmetriken

Retrieval-Metriken

  • Recall@k: Prozentsatz relevanter Docs in Top-k
  • Precision@k: Prozentsatz abgerufener Docs, die relevant sind
  • MRR (Mean Reciprocal Rank): Position des ersten relevanten Ergebnisses
  • NDCG: Normalized Discounted Cumulative Gain

End-to-End-Metriken

  • Antwortrelevanz: Behandelt LLM-Antwort die Abfrage?
  • Treue: Ist Antwort im abgerufenen Kontext begründet?
  • Kontextrelevanz: Ist abgerufener Kontext nützlich?
  • Latenz: Gesamtzeit von Abfrage bis Antwort

Häufige Fallstricke

  • Chunk-Größe zu groß: Verwässert Relevanz
  • Chunk-Größe zu klein: Verliert Kontext
  • Unzureichendes Top-k: Verpasst relevante Informationen
  • Exzessives Top-k: Rauschen im Kontext
  • Keine Metadaten-Filterung: Ruft irrelevanten aber ähnlichen Inhalt ab
  • Kontextfenster-Limits ignorieren: Gekürzter Kontext
  • Retrieval-Qualität nicht überwachen: Degradierende Leistung

Kostenverwaltung

  • Embedding-Generierung in Batches zur Reduzierung von API-Aufrufen
  • Embeddings für häufig zugeriffene Dokumente cachen
  • Kleinere Embedding-Modelle verwenden bei akzeptabler Leistung
  • Abfrageduplizierung implementieren
  • Kosten pro Abfrage überwachen
  • Vektordatenbank-Infrastruktur richtig dimensionieren

Sicherheitsüberlegungen

  • Zugriffskontrolle: Ergebnisse basierend auf Benutzerberechtigungen filtern
  • Datenisolation: Multi-Tenant-Vektortrennung
  • Verschlüsselung: Vektoren im Ruhezustand und während der Übertragung schützen
  • Audit-Protokollierung: Verfolgen, wer was abgerufen hat
  • PII-Handhabung: Vorsichtig mit personenbezogenen Daten in Dokumenten

RAG-Systeme bieten eine praktische Möglichkeit, LLMs mit Domänenwissen zu erweitern. Ordnungsgemäße Implementierung von Vektordatenbanken, Embeddings und Retrieval-Strategien ist entscheidend für Produktionsqualität.

Autor

21medien

Zuletzt aktualisiert