Überspringen und zum Inhalt gehen →

30 Tage DSPy-Challenge – Tag 19: Umgang mit widersprüchlichen Informationen

In komplexen RAG-Systemen (Retrieval Augmented Generation) stammt der Kontext oft aus heterogenen Quellen. Dies führt zwangsläufig zu Situationen, in denen abgerufene Informationen inkonsistent sind oder sich direkt widersprechen. Ein robustes System darf diese Widersprüche nicht ignorieren oder willkürlich eine Information priorisieren, sondern muss sie erkennen und transparent machen.

Strategien zur Konfliktbewältigung in RAG-Systemen

Sprachmodelle tendieren dazu, Antworten zu glätten. Werden einem LLM widersprüchliche Fakten im Kontext präsentiert (z. B. „Die Hauptstadt ist X“ und „Die Hauptstadt ist Y“), versucht das Modell oft, eine der beiden Optionen ohne Begründung zu wählen oder beide unlogisch zu vermischen. Dies führt zu Halluzinationen oder faktisch falschen Aussagen.

Um die Zuverlässigkeit zu erhöhen, müssen Strategien implementiert werden, die den Umgang mit Ambivalenz steuern:

Explizite Instruktion (Prompting)
Die Signatur des Modells wird so angepasst, dass das Erkennen von Widersprüchen explizit Teil der Aufgabenstellung ist. Das Modell wird angewiesen, Inkonsistenzen zu melden, anstatt sie aufzulösen.

Quellen-Priorisierung
Metadaten (z. B. Zeitstempel oder Vertrauenswürdigkeit der Quelle) können genutzt werden, um Konflikte programmatisch vor der Generierung zu filtern.

Multi-Perspektiven-Antworten
Anstatt eine definitive Antwort zu geben, generiert das System eine Antwort, die die unterschiedlichen Standpunkte oder Faktenlagen darstellt (z. B. „Quelle A besagt X, während Quelle B behauptet, es sei Y“).

Im Folgenden wird die Strategie der expliziten Instruktion mittels DSPy umgesetzt. Hierbei wird die Signatur der Antwortgenerierung modifiziert, um Sensibilität für Konflikte zu erzwingen.

Implementierung einer Konflikterkennung

Die bestehende Multi-Hop-Pipeline wird angepasst. Ziel ist es, künstliche Widersprüche in die Datenbasis einzufügen und das Modell dazu zu bringen, diese in der finalen Antwort transparent zu machen.

Anpassung der Datenbasis

Um einen Widerspruch zu simulieren, wird der Dokumenten-Korpus (docs) erweitert. Neben den korrekten Fakten werden widersprüchliche Informationen hinzugefügt.

# Erweiterte Dokumentenbasis mit Widersprüchen
docs = [
    "Der Gründer von Microsoft ist Bill Gates.",
    "Bill Gates wurde in den USA geboren.",
    "Die Hauptstadt der USA ist Washington, D.C..",
    # Widersprüchliche Informationen:
    "Bill Gates wurde in Frankreich geboren.",
    "Die Hauptstadt der USA ist New York City."
]

Modifikation der Signatur

Die ursprüngliche Signatur GenerateAnswer forderte lediglich eine Antwort. Diese wird durch GenerateAnswerWithConflictCheck ersetzt. Der Docstring (die Instruktion an das Modell) wird präzisiert, um das Verhalten bei Inkonsistenzen zu definieren.

class GenerateAnswerWithConflictCheck(dspy.Signature):
    """
    Beantwortet die Frage basierend auf dem gesammelten Kontext.
    Analysiere den Kontext auf Widersprüche. 
    Falls widersprüchliche Informationen vorliegen (z.B. unterschiedliche Geburtsorte oder Hauptstädte),
    weise explizit darauf hin und nenne beide Varianten.
    """

    context = dspy.InputField(desc="Alle relevanten Informationen aus mehreren Suchschritten.")
    question = dspy.InputField(desc="Die zu beantwortende Frage.")
    answer = dspy.OutputField(desc="Die finale Antwort, inklusive Hinweis auf eventuelle Widersprüche.")

Aktualisierung der Pipeline

Die Klasse MultiHopRAG muss nun die neue Signatur verwenden. Der Rest der Logik (Abruf und Kontextaufbau) bleibt bestehen, da das Retrieval-Modul lediglich relevante (und damit potenziell widersprüchliche) Texte liefert.

Vollständiger Code

Der folgende Code zeigt die vollständige, modifizierte Pipeline.

#!/usr/bin/env python
# coding: utf-8

import dspy

# Konfiguration des Sprachmodells
local_llm = dspy.LM(
    "openai/qwen2.5:14b", # Beispielmodell, Anpassung je nach verfügbarem Modell nötig
    api_base="http://localhost:11434/v1", 
    api_key="no_key_needed"
)

dspy.configure(lm=local_llm)

# 1. Definition der Signaturen
class GenerateSearchQuery(dspy.Signature):
    """Generiert eine Suchanfrage basierend auf einer Frage und dem bisherigen Kontext, um fehlende Informationen zu finden."""
    context = dspy.InputField(desc="Die bisher gesammelten Fakten.")
    question = dspy.InputField(desc="Die ursprüngliche Frage.")
    query = dspy.OutputField(desc="Eine präzise Suchanfrage für den nächsten Informationsschritt.")

class GenerateAnswerWithConflictCheck(dspy.Signature):
    """
    Beantwortet die Frage basierend auf dem gesammelten Kontext.
    Analysiere den Kontext auf Widersprüche. 
    Falls widersprüchliche Informationen vorliegen, weise explizit darauf hin und nenne die konkurrierenden Informationen.
    """
    context = dspy.InputField(desc="Alle relevanten Informationen aus mehreren Suchschritten.")
    question = dspy.InputField(desc="Die zu beantwortende Frage.")
    answer = dspy.OutputField(desc="Die finale Antwort, inklusive Hinweis auf eventuelle Widersprüche.")

# 2. Definition des Retrievers
class LocalRetriever(dspy.Retrieve):
    def __init__(self, docs, k=3):
        super().__init__(k=k)
        self.docs = docs

    def forward(self, query_or_queries, k=None):
        k = k if k is not None else self.k
        query = query_or_queries if isinstance(query_or_queries, str) else query_or_queries[0]

        # Einfache Keyword-Suche
        results = [doc for doc in self.docs if any(word.lower() in doc.lower() for word in query.split())]

        # Fallback, falls keine Treffer
        if not results:
            results = self.docs

        predictions = [dspy.Example(long_text=doc) for doc in results[:k]]
        return predictions

# 3. Das Multi-Hop RAG Modul
class MultiHopRAG(dspy.Module):
    def __init__(self, max_hops=2):
        super().__init__()
        self.max_hops = max_hops

        self.generate_query = dspy.ChainOfThought(GenerateSearchQuery)
        self.retrieve = dspy.Retrieve(k=5) # K erhöht, um Wahrscheinlichkeit für Widersprüche im Kontext zu erhöhen
        self.generate_answer = dspy.ChainOfThought(GenerateAnswerWithConflictCheck)

    def forward(self, question):
        context = []

        for hop in range(self.max_hops):
            query_result = self.generate_query(context=context, question=question)
            search_query = query_result.query

            raw_passages = self.retrieve(search_query).passages
            passages = [psg for psg in raw_passages]

            # Kontext erweitern (Deduplizierung)
            context = list(set(context + passages))

        prediction = self.generate_answer(context=context, question=question)
        return prediction

# 4. Datenbasis mit Widersprüchen
docs = [
    "Der Gründer von Microsoft ist Bill Gates.",
    "Bill Gates wurde in den USA geboren.",
    "Die Hauptstadt der USA ist Washington, D.C..",
    # Künstliche Widersprüche
    "Bill Gates wurde in Frankreich geboren.",
    "Die Hauptstadt der USA ist New York City."
]

# 5. Instanziierung und Konfiguration
rm = LocalRetriever(docs)
dspy.configure(rm=rm)

multi_hop_program = MultiHopRAG(max_hops=2)

# Frage, die auf die widersprüchlichen Daten abzielt
question = "Wie heißt die Hauptstadt des Geburtslandes des Gründers von Microsoft?"

# Ausführung
response = multi_hop_program(question=question)

print(f"Frage: {question}")
print(f"Antwort: {response.answer}")

# Optional: Inspektion der Gedankengänge
# local_llm.inspect_history(n=1)

Ergebnisse und Interpretation

Die Ausführung des modifizierten DSPy-Programms liefert eine präzise Antwort, die die widersprüchlichen Informationen im Kontext explizit identifiziert – im Gegensatz zu einem „halluzinierenden“ Modell, das eine eindeutige Antwort erzeugen würde. Die Ausgabe lautet:

Frage: Wie heißt die Hauptstadt des Geburtslandes des Gründers von Microsoft?
Antwort: Der Kontext widerspricht sich: Bill Gates wurde entweder in den USA (Hauptstadt: Washington D.C.) oder in Frankreich (Hauptstadt: Paris) geboren. Aufgrund der widersprüchlichen Angaben [2] und [3] ist die Antwort nicht eindeutig.

Analyse der Ergebnisse

Das Modell erkennt die Konflikte (Geburtsland USA vs. Frankreich) und vermeidet es, eine „falsche“ Einzelantwort zu erzeugen (z. B. „Washington D.C.“ als alleinige Lösung). Stattdessen wird die Quelleninkonsistenz transparent gemacht. Die Antwort bezieht sich ausschließlich auf die im Kontext gegebenen Daten (nicht auf externe Wissensbasis).

  • Quelle [2]: „Bill Gates wurde in den USA geboren“ → Hauptstadt: Washington D.C.
  • Quelle [3]: „Bill Gates wurde in Frankreich geboren“ → Hauptstadt: Paris.

Die vorherigen Schritte zeigen, wie das Modell intern die Inkonsistenz analysiert und erst nach der Identifizierung der Widersprüche zur finalen Antwort übergeht.

Zusammenfassung

Durch die explizite Modifikation der Signatur (Docstring mit der Anweisung Analysiere den Kontext auf Widersprüche) wird das DSPy-Programm gezwungen, Datenqualitätsprobleme zur Laufzeit zu erkennen – statt eine Antwort zu erzwingen, die der Wirklichkeit widerspricht. Dies ist entscheidend für Anwendungen, bei denen Wahrheitstreue und Transparenz über das bloße Erzeugen einer Antwort entscheiden. So erhalten Nutzer klare Hinweise auf Konflikte in den Daten, nicht nur eine „falsch klingende“ Antwort. System bleibt unbeeinflusst von inkonsistenten Datenquellen was die Qualität der Anwendung deutlich erhöht. Diese Methode lässt sich auf beliebige Anwendungen übertragen, bei denen Dateninkonsistenzen kritisch sind (z. B. medizinische Diagnosen, Finanzanalysen).

Veröffentlicht in Allgemein