Python-Implementierung von RAG und Wissensabruf

Dieses Kapitel führt in die Technologie der Retrieval-Augmented Generation (RAG) ein.

RAG ist eine der Kerntechnologien zum Aufbau wissensintensiver Agenten.

Durch die Kombination von Vektorabruf mit Sprachgenerierungsmodellen verleiht RAG Agenten die Fähigkeit, auf externes Wissen zuzugreifen und es zu nutzen, statt sich ausschließlich auf Inhalte zu verlassen, die in den Modellparametern "erinnert" werden.


RAG-Grundlagen

Die Kernidee von RAG besteht darin, zwei Phasen zu kombinieren, "Informationsabruf" und "Sprachgenerierung", um eine vollständige Frage-Antwort-Pipeline zu bilden.

Wenn ein Benutzer eine Frage stellt, ruft das System zuerst aus der Wissensbasis Inhalte ab, die für die Frage relevant sind.

Dann stellt das System dem Generierungsmodell den abgerufenen Inhalt als Kontext bereit und hilft ihm, genauere und besser belegte Antworten zu erzeugen.

Warum RAG benötigt wird

Sprachmodelle haben einen Wissensstichtag und können Ereignisse, die nach Abschluss des Trainings eingetreten sind, nicht wahrnehmen.

Modellparameter sind endlich, und es ist unmöglich, das gesamte Wissen der Welt darin auswendig zu lernen.

Wenn das Modell sich vollständig auf das „Erinnern“ von Wissen aus Parametern verlässt, erfindet es oft mit unbewegter Miene Antworten, wenn es auf Inhalte stößt, an die es sich nicht genau erinnern kann oder die es nie gelernt hat. Das nennt man gemeinhin „Halluzination“.

Der Ansatz von RAG besteht darin, das Modell echte Daten konsultieren zu lassen, bevor es antwortet, wodurch „Antworten aus dem Gedächtnis“ in „Antworten mit offenem Buch“ verwandelt werden. Die Wissensquelle kann außerdem jederzeit aktualisiert werden, ohne das Modell neu zu trainieren.

Kernarchitektur

Ein vollständiges RAG-System besteht in der Regel aus drei Kernkomponenten:

Retriever: Verantwortlich dafür, die relevantesten Informationsschnipsel aus der Wissensbasis zu finden.

Vektorspeicher: Speichert Vektordarstellungen von Dokumentinhalten und unterstützt eine groß angelegte, latenzarme Ähnlichkeitssuche.

Generator: Erzeugt die endgültige natürlichsprachliche Antwort basierend auf den abgerufenen Inhalten und der ursprünglichen Frage des Nutzers.

Die Beziehung zwischen den drei lässt sich so verstehen: Der Retriever ist für das „Finden von Material“ zuständig, der Vektorspeicher für das „Speichern von Material und das schnelle Finden ähnlicher Materialien“ und der Generator für das „Organisieren der Antwort, während er das Material betrachtet“.

Workflow

Schritt 1:Indexierungsphase. Teilen Sie die Originaldokumente in mehrere Textabschnitte auf, vektorisieren Sie sie einzeln und speichern Sie sie in der Vektordatenbank. Dieser Schritt erfolgt normalerweise offline und im Voraus.

Schritt 2:Abrufphase. Wenn ein Benutzer eine Anfrage stellt, wird auch die Anfrage vektorisiert und eine Ähnlichkeitssuche in der Vektordatenbank durchgeführt, um die relevantesten Textabschnitte zu finden.

Schritt 3:Augmentierungsphase. Verketten Sie die abgerufenen relevanten Textabschnitte mit der ursprünglichen Frage des Benutzers, um einen vollständigeren Prompt zu erstellen.

Schritt 4:Generierungsphase. Das Generierungsmodell erzeugt die endgültige Antwort basierend auf diesem augmentierten Kontext.

Von diesen vier Schritten ist nur der erste eine Offline-Vorverarbeitung; die anderen drei finden in Echtzeit statt, wenn der Benutzer eine Anfrage stellt. Daher bestimmen Abrufgeschwindigkeit und Abrufqualität direkt die Antwortgeschwindigkeit und Antwortqualität des gesamten Systems.

Code-Implementierung

Grundlegende RAG-Implementierung

class SimpleRAG:
    """
Vereinfachte RAG-Systemimplementierung
Enthält drei Kernkomponenten: Embedder, Vektorspeicher, Generator
    """


    def __init__(self, embedder, vector_store, generator):
        # Embedder: verantwortlich für die Umwandlung von Text in Vektoren
        self.embedder = embedder
        # Vektordatenbank: verantwortlich für das Speichern von Vektoren und die Unterstützung der Ähnlichkeitssuche
        self.vector_store = vector_store
        # Generator: erzeugt Antworten basierend auf dem Kontext
        self.generator = generator

    def add_documents(self, documents):
        """
Dokumente zur Wissensbasis hinzufügen
:param documents: ursprüngliche Dokumentliste (jedes Element ist ein vollständiger Text)
        """

        # Schritt 1: Lange Dokumente in kleinere Textabschnitte aufteilen für präzises Retrieval
        chunks = self.chunk_documents(documents)

        # Schritt 2: Alle Textabschnitte in Vektoren umwandeln
        embeddings = self.embedder.embed(chunks)

        # Schritt 3: Die Vektoren zusammen mit dem entsprechenden Originaltext in der Vektordatenbank speichern
        self.vector_store.add(embeddings, chunks)

    def chunk_documents(self, documents, chunk_size=500, overlap=50):
        """
Dokumentaufteilung: Lange Dokumente in Textabschnitte fester Länge mit etwas Überlappung aufteilen.

:param documents: Liste der Rohdokumente
:param chunk_size: Anzahl der Zeichen pro Textabschnitt
:param overlap: Anzahl der überlappenden Zeichen zwischen benachbarten Textabschnitten (um zu vermeiden, dass die Semantik an der Trennstelle abgeschnitten wird)
:return: Liste der Textabschnitte nach der Aufteilung
        """

        chunks = []
        for doc in documents:
            # Gleitende Aufteilung mit einer Schrittweite von (chunk_size - overlap), wobei ein bestimmter überlappender Bereich beibehalten wird
            for i in range(0, len(doc), chunk_size - overlap):
                chunk = doc[i:i + chunk_size]
                chunks.append(chunk)
        return chunks

    def query(self, question, top_k=5):
        """
Benutzeranfrage verarbeiten und die generierte Antwort zurückgeben

:param question: vom Benutzer gestellte Frage
:param top_k: Anzahl der relevanten Textabschnitte, die beim Abruf zurückgegeben werden
:return: endgültige Antwort des Generierungsmodells
        """

        # 1. Die Frage in einen Vektor umwandeln, damit sie mit Vektoren in der Wissensdatenbank verglichen werden kann
        question_embedding = self.embedder.embed([question])[0]

        # 2. Die top_k ähnlichsten Textabschnitte aus der Vektordatenbank abrufen
        results = self.vector_store.search(question_embedding, top_k)

        # 3. Die abgerufenen Textabschnitte zu einem Kontext verketten
        context = "\n".join([doc for doc, _ in results])

        # 4. Den Kontext und die ursprüngliche Frage zu einem Prompt zusammenstellen
        prompt = f"Beantworte die Frage basierend auf folgendem Inhalt:\n{context}\n\nFrage: {question}"

        # 5. Es dem Generierungsmodell übergeben, um die endgültige Antwort zu erzeugen
        return self.generator.generate(prompt)

Diese Basisimplementierung kann bereits die vollständige RAG-Pipeline ausführen, ist in realen Projekten jedoch oft nicht ausreichend: Die Chunking-Methode ist zu grob, und das Retrieval-Ranking stützt sich nur auf eine einzige Vektorähnlichkeitsberechnung, was zu begrenzter Genauigkeit führt. Im Folgenden werden einige gängige Optimierungsideen vorgestellt.


Fortgeschrittenes RAG

Häufige Probleme bei Basis-RAG sind: schlechte Qualität der abgerufenen Inhalte, unzureichende Ranking-Genauigkeit und irrelevante Informationen, die in den Kontext eingemischt werden.

Fortgeschrittenes RAG optimiert die Basispipeline weiter, indem Techniken wie Reranking und Hybrid-Retrieval eingeführt werden, wodurch die Genauigkeit der endgültigen Antwort verbessert wird.

Reranking

Die Vektorsuche in Basis-RAG ist schnell, aber die Ranking-Genauigkeit ist begrenzt, und sie kann Inhalte, die semantisch weniger relevant sind, leicht höher einordnen.

Der Reranking-Ansatz ist: Zuerst wird mithilfe der Vektorsuche schnell eine Menge von Kandidatentexten herausgefiltert (z. B. top-20), dann wird ein rechenintensiverer, aber genauerer Cross-Encoder (Cross-Encoder) verwendet, um jeden Kandidaten einzeln zu bewerten, sie neu zu ordnen und die wirklich relevantesten wenigen (z. B. top-5) herauszuziehen.

Dieser zweistufige Ansatz aus "zuerst grobem Filtern, dann präzisem Ranking" bringt Abrufgeschwindigkeit und Abrufgenauigkeit in ein ausgewogenes Verhältnis.

RAG mit Reranking

class AdvancedRAG:
    """
Fortgeschrittenes RAG-System
Ergänzt die Basispipeline um einen Reranking-Schritt
    """


    def __init__(self, embedder, vector_store, reranker, generator):
        # Embedder
        self.embedder = embedder
        # Vektordatenbank
        self.vector_store = vector_store
        # Reranking-Modell (Cross-Encoder) für feingranulares Ranking
        self.reranker = reranker
        # Generator
        self.generator = generator

    def query(self, question, initial_k=20, final_k=5):
        """
Verarbeitet die Anfrage, einschließlich des zweistufigen Abrufs von "grobem Filtern + präzisem Ranking"

: :param question: Benutzerfrage
: :param initial_k: Die Anzahl der Kandidaten, die von der ersten Stufe der Vektorsuche zurückgegeben werden.
:param final_k: Die Anzahl der Elemente, die nach dem Re-Ranking letztendlich beibehalten werden.
        """

        # ==================== Phase 1: Initiale Abfrage (Grobauswahl) ====================
        Das Problem in einen Vektor umwandeln.
        question_embedding = self.embedder.embed([question])[0]

        # Schnelles Abrufen einer Charge von Kandidatentexten mithilfe von Vektorähnlichkeit (schnell, durchschnittliche Genauigkeit)
        initial_results = self.vector_store.search(
            question_embedding,
            initial_k
        )

        # ==================== Phase 2: Re-Ranking (Feinranking) ====================
        (Frage, Kandidatendokument)-Paare bilden und sie nacheinander dem Cross-Encoder zur Bewertung zuführen.
        doc_pairs = [(question, doc) for doc, _ in initial_results]

        Cross-Encoder kodieren die Frage und das Dokument gleichzeitig, was eine genauere Beurteilung ihrer Relevanz ermöglicht.
        reranked = self.reranker.rerank(doc_pairs)

        # Die wirklich relevantesten final_k Elemente nach dem Re-Ranking nehmen.
        final_context = "\n".join([doc for doc, _ in reranked[:final_k]])

        # ==================== Stufe 3: Generierung =====================
        prompt = fKontext: {final_context}\n\nFrage: {question}
        return self.generator.generate(prompt)

Hybrides Retrieval

Hybrides Retrieval kombiniert die Vorteile von Dense Retrieval und Sparse Retrieval.

Dense Retrieval basiert auf Vektorähnlichkeit und zeichnet sich durch die Erfassung semantischer Relevanz aus; es gleicht Benutzeranfragen und Dokumente ab, selbst wenn sie nicht exakt dieselben Wörter verwenden.

Sparse Retrieval (z. B. BM25) basiert auf Schlüsselwort-Häufigkeitsstatistiken, zeichnet sich durch exakte wörtliche Übereinstimmung aus und ist oft zuverlässiger als Vektor-Retrieval für Szenarien wie Eigennamen, Zahlen und Codeausschnitte.

Beide haben blinde Flecken: Vektor-Retrieval kann "die Bedeutung verstehen, aber die genauen Schlüsselwörter nicht finden", während Schlüsselwort-Retrieval "die Wörter abgleichen, aber die Semantik nicht verstehen" kann. Hybrides Retrieval fusioniert die Ergebnisse der beiden Retrieval-Pfade und ergänzt so die jeweiligen Stärken und Schwächen.

Hybrides Retrieval RAG

class HybridRAG:
    """
Hybrides Retrieval RAG
Kombination von Dense Retrieval (Vektor) und Sparse Retrieval (Schlüsselwort)
    """


    def __init__(self, dense_retriever, sparse_retriever, fusion_fn):
        # Dense Retriever: Basiert auf Vektorähnlichkeit, zeichnet sich durch semantisches Matching aus.
        self.dense_retriever = dense_retriever
        # Sparse Retriever: Basiert auf Schlüsselwortstatistiken (z. B. BM25), zeichnet sich durch exaktes Matching aus.
        self.sparse_retriever = sparse_retriever
        # Fusion-Funktion: Wird verwendet, um zwei Retrieval-Ergebnisse zusammenzuführen, z. B. RRF (siehe Implementierung unten)
        self.fusion_fn = fusion_fn

    def query(self, question, top_k):
        """
Hybrides Retrieval durchführen und die fusionierten Ranglistenergebnisse zurückgeben.

: :param question: Benutzerfrage
:param top_k: Die endgültige Anzahl der zurückzugebenden Ergebnisse.
        """

        # ==================== Dense Retrieval =====================
        # Mehr Kandidaten abrufen (top_k * 2), um Spielraum für die Filterung während der Fusion zu lassen
        dense_results = self.dense_retriever.search(question, top_k * 2)

        # ==================== Sparse Retrieval ====================
        # Schlüsselwortbasiertes Retrieval, ebenfalls mehr Kandidaten abrufen
        sparse_results = self.sparse_retriever.search(question, top_k * 2)

        # ==================== Ergebnis-Fusion ====================
        # Strategien wie Reciprocal Rank Fusion (RRF) verwenden, um die Ranking-Ergebnisse beider Kanäle zu fusionieren
        fused = self.fusion_fn(dense_results, sparse_results)

        # Die top_k Elemente mit dem höchsten fusionierten Ranking nehmen
        return fused[:top_k]


def reciprocal_rank_fusion(results_a, results_b, k=60):
    """
Reciprocal Rank Fusion (RRF)-Algorithmus
Wird verwendet, um die Ranking-Ergebnisse aus zwei (oder mehreren) Retrieval-Kanälen zu einem einheitlichen Ranking zu fusionieren

Kernidee: Betrachte nur den "Rang" jedes Dokuments in den Ergebnissen jedes Kanals, nicht den konkreten Score,
Dies vermeidet das Problem, dass Vektorähnlichkeits-Scores und BM25-Scores unterschiedliche Skalen haben und nicht direkt verglichen werden können.

:param results_a: Abrufergebnisse A, formatiert als [(doc, score), ...]
:param results_b: Abrufergebnisse B, formatiert als [(doc, score), ...]
:param k: Glättungskonstante, verwendet, um den Gewichtsunterschied für niedriger eingestufte Dokumente zu reduzieren, normalerweise auf 60 gesetzt
:return: Liste von [(doc, score), ...], sortiert nach fusioniertem Score von hoch nach niedrig
    """

    scores = {}

    # Ergebnisse A verarbeiten: Je höher der Rang, desto höher der beigesteuerte Score
    for rank, (doc, _) in enumerate(results_a):
        scores[doc] = scores.get(doc, 0) + 1 / (k + rank + 1)

    # Ergebnisse B verarbeiten, Scores ebenfalls nach Rang akkumulieren
    for rank, (doc, _) in enumerate(results_b):
        scores[doc] = scores.get(doc, 0) + 1 / (k + rank + 1)

    # Nach dem akkumulierten fusionierten Score von hoch nach niedrig sortieren
    sorted_docs = sorted(scores.items(), key=lambda x: -x[1])

    return sorted_docs

Weitere Optimierungsstrategien

Query-Erweiterung: Die ursprüngliche Abfrage durch Synonyme oder verwandte Begriffe erweitern, um den Recall-Bereich zu vergrößern und die Trefferquote zu verbessern.

Query-Umschreibung: Modelle verwenden, um die wahre Absicht des Nutzers zu verstehen und umgangssprachliche oder mehrdeutige Fragen in für die Suche besser geeignete Formulierungen umzuschreiben.

Kontextkomprimierung: Bevor Abrufergebnisse an das Generierungsmodell übergeben werden, redundante und irrelevante Teile entfernen und nur die Schlüsselinformationen behalten. Dies senkt die Kosten und verringert auch das Risiko, dass das Generierungsmodell durch irrelevanten Inhalt gestört wird.


Vektordatenbank

Eine Vektordatenbank ist die Infrastruktur des RAG-Systems, verantwortlich für die Speicherung massiver hochdimensionaler Vektoren und die Unterstützung groß angelegter Ähnlichkeitssuche mit niedriger Latenz.

Kernkonzepte

Embedding: Der Prozess, Text (oder Bilder, Audio usw.) in eine Menge dichter numerischer Vektoren umzuwandeln, wobei semantisch ähnliche Inhalte im Vektorraum näher beieinander liegen.

VektordimensionDie Länge des Embedding-Vektors. Je höher die Dimension, desto reichhaltiger sind theoretisch die semantischen Informationen, die ausgedrückt werden können, aber die Speicher- und Rechenkosten steigen entsprechend.

Ähnlichkeitsmaß.: Eine spezifische Berechnungsmethode zur Messung der "Ähnlichkeit" oder "Distanz" zwischen zwei Vektoren. Unterschiedliche Metriken eignen sich für unterschiedliche Szenarien.

Gängige Ähnlichkeitsmetriken

Messmethode Formel Funktionen Anwendbare Szenarien
Kosinusähnlichkeit. cos(A,B) = A·B/(|A||B|) Misst nur die Richtungsähnlichkeit, unabhängig von der Vektorlänge, mit einem Wertebereich von [-1,1] Dokument-/Textähnlichkeit
Euklidische Distanz d(A,B) = sqrt(sum((Ai-Bi)^2)) Misst den absoluten räumlichen Abstand zwischen Vektoren, mit einem Wertebereich von [0,+∞) Bildmerkmalsabgleich
Skalarprodukt A·B = sum(Ai*Bi) Wird sowohl von Richtung als auch Länge beeinflusst, mit hoher Recheneffizienz Empfehlungssystem

Vergleich gängiger Vektordatenbanken

Datenbank Type Funktionen Anwendbare Szenarien
Pinecone Cloud-Dienst Vollständig verwalteter Dienst, einfach einzurichten, automatische Skalierung, keine manuellen Eingriffe erforderlich Produktionsumgebungen, die schnell live gehen müssen
Weaviate Open Source Unterstützt nativ hybride Suche (Vektor + Schlüsselwort), hochgradig modular Szenarien, die eine flexible Anpassung der Abruflogik erfordern
Milvus Open Source Hohe Verfügbarkeit, einfache horizontale Skalierung, kann Vektorskalen von Milliarden und mehr unterstützen Vektorsuche im extrem großen Maßstab
Chroma Open Source Leichtgewichtig, einfache API, mit nur wenigen Codezeilen einsatzbereit Prototypenentwicklung, Lehre und lokale Tests
Qdrant Open Source Basiert auf Rust, exzellente Leistung, unterstützt umfangreiche Filterbedingungen Szenarien, die hohen Durchsatz und komplexe Filterung erfordern

Codebeispiele

Verwendung der Chroma-Vektordatenbank

import chroma_client
from chroma_client import ChromaClient

# Einen Client erstellen (kann sich mit einer lokalen Instanz oder einem Remote-Dienst verbinden)
client = ChromaClient()

# Eine Collection erstellen oder abrufen, entspricht einer 'Tabelle' in einer relationalen Datenbank
collection = client.get_or_create_collection("knowledge_base")

# Dokumente hinzufügen: ID, Vektor, Originaltext und Metadaten müssen eins zu eins übereinstimmen
collection.add(
    ids=["doc1", "doc2", "doc3"],  # Eindeutige Kennung für jedes Dokument
    embeddings=[
        [0.1, 0.2, 0.3],  # Vektor, der Dokument 1 entspricht (die tatsächliche Dimension ist normalerweise viel höher als 3 Dimensionen)
        [0.4, 0.5, 0.6],  # Vektor, der Dokument 2 entspricht
        [0.7, 0.8, 0.9],  # Vektor, der Dokument 3 entspricht
    ],
    documents=[
        Künstliche Intelligenz ist ein Teilgebiet der Informatik.,
        "Maschinelles Lernen ist ein Teilgebiet der künstlichen Intelligenz",
        "Deep Learning ist ein Teilgebiet des maschinellen Lernens"
    ],
    metadatas=[
        {"source": “textbook”},  # Quellinformation von Dokument 1, kann für spätere Filterung verwendet werden
        {"source": Paper},
        {"source": Paper}
    ]
)

# Abrufen: Abfragevektor übergeben, die ähnlichsten n_results Ergebnisse zurückgeben
results = collection.query(
    query_embeddings=[[0.15, 0.25, 0.35]],  # Vektor, der der Abfrage entspricht
    n_results=2  # Top-2 ähnlichste Ergebnisse zurückgeben
)

# Abrufergebnisse ausgeben
print(results["documents"])   # Übereinstimmender Originaltextinhalt
print(results["distances"])   # Entsprechender Distanz-/Ähnlichkeitswert; kleinere Werte weisen normalerweise auf eine höhere Ähnlichkeit hin

Bei der Auswahl einer Vektordatenbank müssen Datenumfang, Anforderungen an die Abfragelatenz, Bereitstellungsmethode (cloudgehostet oder selbst gehostet) und die Betriebsfähigkeiten des Teams umfassend berücksichtigt werden. Für die Entwicklung kleiner Prototypen ist Chroma vorzuziehen; für eine sofort einsatzbereite Produktionsumgebung wählen Sie Pinecone; für ultragroße Szenarien, die Autonomie und Kontrolle erfordern, sind Milvus oder Qdrant besser geeignet.


GraphRAG

GraphRAG ist eine fortgeschrittene Lösung, die einen Wissensgraphen mit RAG kombiniert.

Es verbessert die Abruf- und Schlussfolgerungsfähigkeiten des Systems, indem es eine aus Entitäten und Beziehungen bestehende Graphstruktur aufbaut.

Warum GraphRAG notwendig ist

Traditionelles RAG basiert auf Ähnlichkeitsabruf über Textabschnitte, wodurch es schwierig ist, Probleme zu behandeln, die „Multi-Hop-Schlussfolgerung“ erfordern (d. h. Antworten, die das Verbinden mehrerer Informationen erfordern).

Traditionelles RAG hat außerdem Schwierigkeiten, semantische Beziehungen zwischen Entitäten explizit zu erfassen, etwa strukturierte Informationen wie „wer wessen Vorgesetzter ist“ oder „welches Unternehmen welches Unternehmen übernommen hat“.

GraphRAG organisiert diese Entitäten und Beziehungen explizit in einer Graphstruktur, sodass das System entlang von Beziehungspfaden schlussfolgern kann, anstatt nur „ähnlichen Text zu finden“.

Kernvorteile

Kann semantische Beziehungen zwischen Entitäten erfassen und bildet ein strukturiertes Wissensnetzwerk statt verstreuter Textfragmente.

Unterstützt pfadbasierte Schlussfolgerungen und kann relationale Fragen besser beantworten.

Kann komplexe Probleme bewältigen, die Multi-Hop-Schlussfolgerungen erfordern, wie „wer der Betreuer des Betreuers von jemandem ist“, bei denen mehrere Beziehungsknoten nacheinander durchlaufen werden müssen, um die Antwort abzuleiten.

Code-Implementierung

GraphRAG-Implementierung

class GraphRAG:
    """
GraphRAG-System
Kombiniert Wissensgraph-Abruf und Vektorabruf und gleicht relationale Schlussfolgerung und semantische Ähnlichkeit aus.
    """


    def __init__(self, kg_builder, embedder, vector_store, generator):
        # Wissensgraph-Builder: zuständig für Entitäts-/Relationsextraktion und Graphabfragen
        self.kg_builder = kg_builder
        # Embedder: wird verwendet, um Dokumente und Fragen in Vektoren umzuwandeln
        self.embedder = embedder
        # Vektordatenbank: dient als ergänzender Kanal für das Wissensgraph-Retrieval
        self.vector_store = vector_store
        # Generator: generiert die endgültige Antwort basierend auf dem fusionierten Kontext
        self.generator = generator

    def index_documents(self, documents):
        """
Dokumente sowohl in den Wissensgraphen als auch in die Vektordatenbank indexieren
:param documents: Liste von Dokumenten
        """

        for doc in documents:
            # Entitäten aus Dokumenten extrahieren
            # Zum Beispiel aus "OpenAI wurde 2015 gegründet" extrahieren
            # Entität: OpenAI (Organisation), 2015 (Zeit)
            entities = self.kg_builder.extract_entities(doc)

            # Beziehungen zwischen Entitäten aus Dokumenten extrahieren
            # Zum Beispiel ein Beziehungstripel extrahieren: (OpenAI, gegründet in, 2015)
            relations = self.kg_builder.extract_relations(doc)

            # Entitäten und Beziehungen in den Wissensgraphen schreiben
            self.kg_builder.add_entities(entities)
            self.kg_builder.add_relations(relations)

            # Gleichzeitig den Originaltext vektorisieren und in einer Vektordatenbank speichern, um die semantische Suche zu ergänzen
            embedding = self.embedder.embed([doc])
            self.vector_store.add(embedding, [doc])

    def query(self, question):
        """
Abfragen verarbeiten: Wissensgraph-Reasoning und Vektor-Retrieval kombinieren
        """

        # ==================== Wissensgraph-Retrieval ====================
        # Zuerst die relevanten Entitäten identifizieren, die in der Frage erwähnt werden
        # Zum Beispiel "Wer hat OpenAI gegründet?" -> die Entität "OpenAI" identifizieren
        relevant_entities = self.kg_builder.query_entities(question)

        # Basierend auf diesen Entitäten den direkt mit ihnen verbundenen Teilgraphen aus dem Graphen extrahieren
        # Der Teilgraph enthält relevante Entitäten sowie ihre benachbarten Knoten und verbindenden Kanten
        subgraph = self.kg_builder.get_subgraph(relevant_entities)

        # ==================== Vektor-Retrieval ====================
        # Als ergänzender Kanal semantisch ähnlichen Textinhalt abrufen
        vector_results = self.vector_store.search(question, top_k=5)

        # ==================== Ergebnis-Fusion ====================
        # Die Ergebnisse des Graph-Reasonings und des Vektor-Retrievals zu einem einheitlichen Kontext fusionieren
        combined_context = self.fuse_results(subgraph, vector_results)

        # ==================== Generierung ====================
        return self.generator.generate(combined_context, question)

    def fuse_results(self, subgraph, vector_results):
        """
Den Wissensgraph-Teilgraphen und die Vektor-Retrieval-Ergebnisse zu einem für ein generatives Modell lesbaren Kontexttext fusionieren
        """

        # Den Teilgraphen in natürlichsprachliche Beschreibungen umwandeln (z. B. "OpenAI -gegründet in-> 2015")
        kg_text = subgraph.to_text()

        # Die durch Vektor-Retrieval abgerufenen Originaltextsegmente verketten
        vector_text = "\n".join([doc for doc, _ in vector_results])

        return f"{kg_text}\n{vector_text}"


class KnowledgeGraphBuilder:
    """Wissensgraph-Builder (illustrative Implementierung; in realen Projekten werden üblicherweise spezialisierte NER-/Relationsextraktionsmodelle aufgerufen)"""

    def extract_entities(self, text):
        """
# Benannte Entitäten aus Text extrahieren, üblicherweise mit NER-Modellen (Named Entity Recognition)
# Häufige Entitätstypen sind PERSON (Person), ORG (Organisation), TIME (Zeit), usw.
        """

        entities = []
        return entities

    def extract_relations(self, text):
        """
# Beziehungen zwischen Entitäten aus Text extrahieren, üblicherweise mit Relationsextraktionsmodellen
# Beziehungen werden im Allgemeinen als Tripel dargestellt: (Kopf-Entität, Beziehungstyp, End-Entität)
        """

        relations = []
        return relations

    def query_entities(self, question):
        """Basierend auf der Nutzerfrage die Entitäten identifizieren, die an der Frage beteiligt sind, für den anschließenden Subgraph-Abruf"""
        return []

Anwendungsszenarien

GraphRAG eignet sich besonders für die folgenden Szenarien:

Fragebeantwortung, die das Verständnis von Entitätsbeziehungen erfordert, z. B. "Wer ist der CEO dieses Unternehmens?"

Fragen, die Multi-Hop-Reasoning erfordern, um zu einer Antwort zu gelangen, z. B. "Wer ist der Berater des Beraters von Zhang San?"

Szenarien, die das Erschließen impliziter Beziehungen in der Wissensbasis erfordern, die nicht explizit niedergeschrieben sind.

Es ist zu beachten, dass der Aufbau und die Pflege eines Wissensgraphen selbst einen gewissen Engineering-Aufwand verursachen (Genauigkeit der Entitäts-/Relationsextraktion, Graphaktualisierung und -wartung usw.), sodass er eher für Szenarien mit klaren Anforderungen an relationales Reasoning geeignet ist und nicht als Standardausgangspunkt für alle RAG empfohlen wird.


Lösungsvergleich und Empfehlungen

Lösung Kernansatz Vorteile Einschränkungen Geeignete Szenarien
Basis-RAG Vektorabruf + direkte Generierung Einfach zu implementieren, geringe Kosten, schneller Einstieg Begrenzte Abrufpräzision, neigt dazu, nicht ausreichend relevanten Inhalt abzurufen Einfache Q&A, schnelle Prototypvalidierung
Erweitertes RAG (Reranking) Vektor-Grobauswahl + Cross-Encoder-Feinranking Verbessert die Präzision des Abruf-Rankings erheblich Fügt einen zusätzlichen Modellinferenzdurchlauf hinzu, was Latenz und Kosten erhöht Szenarien mit hohen Anforderungen an die Antwortgenauigkeit
Hybrides Retrieval Fusion von Vektorabruf und Keyword-Abruf Balanciert semantisches Matching und präzises Keyword-Matching Erfordert die gleichzeitige Pflege zweier Abrufindizes Wissensbasen mit vielen Eigennamen, Code und Bezeichnern
GraphRAG Wissensgraph + Vektorabruf-Fusion Unterstützt relationales Reasoning und Multi-Hop-Q&A Hohe Kosten für Graphaufbau und -wartung Szenarien mit klaren Anforderungen an relationales und Multi-Hop-Reasoning

Kapitelzusammenfassung

Dieses Kapitel führt in die Kerntechnologien von RAG und Wissensabruf ein.

RAG-Grundprinzipien: Durch die Kombination von Abruf und Generierung können Agenten Fragen mithilfe externen Wissens beantworten, anstatt sich vollständig auf das in den Modellparametern gespeicherte Gedächtnis zu verlassen.

Erweitertes RAG: Durch Techniken wie Reranking und Hybrid-Retrieval verbessert es die Abrufqualität und Antwortgenauigkeit zusätzlich auf der Basis-Pipeline.

Vektordatenbank: Sie dient als Infrastruktur von RAG-Systemen und ist für die effiziente Speicherung und den Abruf von Vektordarstellungen von Dokumenten verantwortlich; die Wahl der Lösung sollte anhand von Datenumfang und Bereitstellungsmethode getroffen werden.

GraphRAG: In Kombination mit Wissensgraphen verbessert es relationales Reasoning und Multi-Hop-Q&A-Fähigkeiten, bringt aber auch zusätzliche Aufbau- und Wartungskosten mit sich.

Der Schlüssel zur Auswahl einer geeigneten RAG-Lösung liegt darin, zunächst die tatsächlichen Anforderungen des Geschäftsszenarios zu verstehen: Für einfache Frage-Antwort-Szenarien ist grundlegendes RAG oft ausreichend; wenn eine höhere Retrieval-Präzision erforderlich ist, kann Reranking eingeführt werden; wenn die Wissensbasis viele Eigennamen/Schlüsselwörter enthält, ist Hybrid-Retrieval geeignet; wenn ein hohes Maß an relationalem Reasoning und Multi-Hop-Q&A involviert ist, sollte die Einführung von GraphRAG in Betracht gezogen werden. Je komplexer die Technologie, desto höher die Engineering- und Wartungskosten, daher ist die Auswahl basierend auf tatsächlichen Anforderungen wichtiger als das blinde Streben nach "fortschrittlicheren Lösungen".

Andere Erweiterungen