In der Entwicklung von Anwendungen auf Basis von Large Language Models (LLMs) stellt die Konsistenz der Ausgaben eine zentrale Herausforderung dar. Während LLMs kreativ und flexibel Text generieren, erfordern Software-Schnittstellen strikte Datenformate und logische Konsistenz. Tag 24 der DSPy-Challenge widme ich mich dem Konzept der Assertions und Validierungen. Ich werde prüfen, wie durch definierte Constraints (Einschränkungen) und Validierungslogik sichergestellt wird, dass extrahierte Daten – wie beispielsweise Preise – den formalen Anforderungen entsprechen.
Theorie: Assertions und Selbstkorrektur
Der Begriff „Assertion“ beschreibt in der Programmierung eine Aussage, die zu einem bestimmten Zeitpunkt wahr sein muss. Im Kontext von DSPy und LLMs dienen Assertions dazu, Annahmen über die Modellausgabe zu überprüfen. Erfüllt eine Ausgabe die definierten Kriterien nicht (z. B. „Der Preis muss eine Zahl sein“), wird ein Fehler signalisiert.
DSPy bietet Mechanismen, um auf solche Validierungsfehler dynamisch zu reagieren. Anstatt die Ausführung lediglich abzubrechen, kann das System das Modell dazu zwingen, die Ausgabe unter Berücksichtigung des Fehlers zu korrigieren. Dies geschieht z.B. durch Module wie dspy.Refine. Dieses Modul nutzt eine Validierungsfunktion (Reward Function), um die Qualität der Ausgabe zu bewerten. Schlägt die Validierung fehl, initiiert dspy.Refine einen erneuten Versuch (Retry), oft unter Einbeziehung des vorherigen Fehlversuchs, um das Modell zur Selbstkorrektur anzuleiten.
Praxis: Implementierung einer Preis-Validierung
Im folgenden praktischen Beispiel wird ein Extraktionsprogramm erstellt, das Produktdaten aus einem unstrukturierten Werbetext in ein strukturiertes JSON-Format überführt. Der kritische Aspekt ist hierbei die Validierung des Preises: Dieser muss zwingend als numerischer Wert (Float oder Int) vorliegen.
Konfiguration und Datenmodell
Zunächst erfolgt die Definition des Datenmodells mittels Pydantic. Dies legt die Zielstruktur fest. Das Feld price wird explizit als float definiert.
import dspy
from pydantic import BaseModel, Field
from typing import List
# Konfiguration des LLM
local_llm = dspy.LM(
"openai/qwen3:30b",
api_base="http://localhost:11434/v1",
api_key="no_key_needed"
)
dspy.configure(lm=local_llm)
# Definition des Ziel-Datenmodells
class ProductSchema(BaseModel):
name: str = Field(..., description="Der genaue Name des Produkts.")
price: float = Field(..., description="Der Preis des Produkts als Zahl.")
features: List[str] = Field(..., description="Eine Liste der wichtigsten technischen Merkmale.")
Signatur und Modul
Die Signatur beschreibt die Transformationsaufgabe: Aus einem description (Input) werden product_data (Output) generiert. Das Modul ProductExtractor kapselt den Aufruf von dspy.Predict.
# Definition der Signatur
class ProductExtraction(dspy.Signature):
"""
Analysiert eine Produktbeschreibung und extrahiert strukturierte Daten gemäß dem definierten Schema.
"""
description: str = dspy.InputField(desc="Der unstrukturierte Werbetext des Produkts.")
product_data: ProductSchema = dspy.OutputField(desc="Die extrahierten Produktinformationen als Objekt auf deutsch.")
# Das Extraktions-Modul (Standard Predictor)
class ProductExtractor(dspy.Module):
def __init__(self):
super().__init__()
self.extractor = dspy.Predict(ProductExtraction)
def forward(self, description):
return self.extractor(description=description)
Die Validierungslogik (Assertion)
Dies ist der zentrale Code-Abschnitt. Die Funktion prüft, ob die generierte Vorhersage den fachlichen Anforderungen genügt. Hier wird explizit verifiziert, ob das Objekt existiert und ob der Preis ein positiver numerischer Wert ist. Diese Funktion fungiert als logische Assertion.
def validate_product_data(example, pred, trace=None):
try:
print("Validierung läuft...")
# Zugriff auf das extrahierte Objekt
data = pred.product_data
# Die Constraint-Prüfung:
# Existiert das Objekt?
if not data:
return False
# Ist der Preis ein Float/Int und positiv?
if not isinstance(data.price, (float, int)) or data.price <= 0:
return False
return True
except Exception:
return False
Integration von dspy.Refine
Um die Selbstkorrektur zu erzwingen, wird das ursprüngliche Modul in dspy.Refine gewickelt. Refine ruft das Modul auf und überprüft das Ergebnis mit validate_product_data. Gibt diese False zurück, wiederholt Refine den Prozess (bis zu N-mal) und versucht, eine Ausgabe zu erzeugen, die die Validierung besteht. Wenn die Anzahl der Versuche (N) sehr hoch gewählt ist und es viele Fehlversuche gibt, kann der Aufruf sehr lange dauern. Ist das der Fall, sollte man in der ProductExtraction Signatur den Prompt anpassen und/oder die Struktur des ProductSchema Datenmodells anpassen, bis man das gewünschte Ergebnis mit 2-3 Versuchen reproduzierbar erreicht. Ist das nicht möglich, muss vermutlich das Modell ausgetauscht werden.
extractor_module = ProductExtractor()
extractor = dspy.Refine(
module=extractor_module,
N=3, # Maximale Anzahl der Versuche (Retries)
reward_fn=validate_product_data, # Die Validierungsfunktion
threshold=1.0 # Erwarteter Score (True entspricht oft 1.0)
)
Ausführung und Ergebnis
Das System wird nun mit einem Beispieltext getestet. Der Text enthält den Preis „149,95 Euro“. Das Modell muss diesen String korrekt in die Zahl 149.95 konvertieren, um die Assertion zu erfüllen.
raw_text = """
Hol dir das Santa Cruz Classic Dot 80s Cruiser Skateboard für dein nächstes Abenteuer.
Dieses Board kostet aktuell 149,95 Euro und bietet echtes Retro-Feeling.
Es besteht aus 7-lagigem nordamerikanischem Ahorn und ist mit weichen
60mm Slime Balls Rollen ausgestattet, die perfekt für rauen Asphalt sind.
Zudem verfügt es über hochwertige Krux Achsen und das ikonische Logo-Design auf der Unterseite.
"""
print("Starte Extraktion mit Refine-Logik...")
try:
# Der Aufruf erfolgt über den Refine-Wrapper
response = extractor(description=raw_text)
# Zugriff auf das Ergebnis
extracted_data = response.product_data
print("-" * 30)
print(extracted_data.model_dump_json(indent=2))
print("-" * 30)
print(f"Preis erfolgreich validiert: {extracted_data.price} €")
except Exception as e:
print(f"Fehler bei der Extraktion: {e}")
Ausgabe
Starte Extraktion mit Refine-Logik...
validate_product_data
------------------------------
{
"name": "Santa Cruz Classic Dot 80s Cruiser Skateboard",
"price": 149.95,
"features": [
"7-lagiger nordamerikanischer Ahorn",
"60mm Slime Balls Rollen",
"hochwertige Krux Achsen",
"ikoniales Logo-Design auf der Unterseite",
"Retro-Feeling aus den 80er Jahren"
]
}
------------------------------
Preis erfolgreich validiert: 149.95 €
Das Programm gibt ein valides JSON-Objekt zurück. Sollte der erste Versuch scheitern (z. B. weil der Preis als String „149,95 Euro“ statt als Float extrahiert wurde), greift die Validierung, und dspy.Refine fordert eine Korrektur an, bis der Datentyp korrekt ist. Etwas merkwürdig fine ich es, dass ich die Informationen die zwischen dem LLM um dem DSPy Programm ausgetauscht wurden nicht mit local_llm.inspect_history() ausgeben lassen konnte. Bisher hat das immer gut geklappt. Dieses mal komischerweise nicht????
Zusammenfassung
Durch die Kombination von Validierungsfunktionen und dspy.Refine lassen sich robuste Constraints in DSPy-Pipelines implementieren. Anstatt fehlerhafte Ausgaben ungeprüft zu übernehmen, wird das Modell durch die Feedback-Schleife dazu befähigt, formatgetreue und logisch korrekte Daten zu liefern. Dies erhöht die Zuverlässigkeit von LLM-Anwendungen in produktiven Umgebungen signifikant kann aber die Laufzeit erheblich steigern.
