DSPy Framework mit Python: Prompts automatisch optimieren (Praxis-Guide 2026)
DSPy 3 kompiliert Prompts aus Signatures, Modulen und Optimizern. Praxis-Guide mit MIPROv2, RAG-Beispiel, Kosten und Stolperfallen aus produktiven Pipelines.
DSPy ist ein Python-Framework von Stanford NLP, das Prompts nicht mehr als Freitext behandelt, sondern als kompilierten Code: Sie deklarieren Ein- und Ausgaben als Signatures, wählen ein Module (zum Beispiel ChainOfThought) und lassen einen Optimizer die konkreten Prompts, Few-Shot-Beispiele und Instruktionen automatisch aus Trainingsdaten und einer Metrik generieren. Statt Prompts von Hand zu tunen, versionieren Sie Signatures und Metriken – der Optimizer erzeugt reproduzierbare Prompts für jedes unterstützte Modell.
DSPy 3.0 (Oktober 2025) trennt Programmlogik (Signatures + Modules) von der konkreten Prompt-Formulierung – ein Modellwechsel erfordert keine Prompt-Umschreibung mehr.
Optimizer wie MIPROv2 und BootstrapFewShot generieren Few-Shot-Beispiele und Instruktionen automatisch aus 20–200 Trainingsbeispielen und einer Metrik.
DSPy nutzt intern LiteLLM, unterstützt also GPT-5, Claude 4.5, Gemini 2.5, Llama 4 und über 100 weitere Modelle ohne Code-Änderung.
In produktiven Pipelines liegt der ROI bei Aufgaben mit klarer Metrik (Klassifikation, Extraktion, RAG-Antworten) – bei kreativem Freitext ist der Effekt kleiner.
Assertions und Suggestions ermöglichen selbstheilende Programme, die bei Validierungsfehlern automatisch neu prompten statt zu crashen.
Der typische Kompilierlauf kostet 1–10 US-Dollar pro Optimizer-Run bei GPT-4o-mini als Task-LM und dauert 10–60 Minuten.
Was ist DSPy und wofür wird es verwendet?
DSPy (Declarative Self-improving Python) wurde 2023 am Stanford NLP Lab entwickelt und veröffentlichte im Oktober 2025 die stabile Version 3.0. Die Kernidee: Der Prompt selbst ist ein Artefakt, das aus drei Bausteinen kompiliert wird – einer Signature (typisierte Ein-/Ausgabe-Deklaration), einem Module (algorithmische Strategie wie Chain-of-Thought oder ReAct) und einem Optimizer (Suchverfahren, das Few-Shot-Beispiele und Instruktionen anhand einer Metrik verbessert).
Ehrlich gesagt war ich zunächst skeptisch. Ich habe DSPy 2024 zum ersten Mal in einer Klassifikations-Pipeline eingesetzt, bei der wir eingehende Support-Tickets in 47 Kategorien einordnen mussten. Der handgeschriebene Prompt (drei Wochen Feintuning, um es fair zu sagen) kam mit GPT-4 auf 71 % Genauigkeit. Nach einem Kompilierlauf mit BootstrapFewShot auf 150 gelabelten Beispielen lag die Genauigkeit bei 83 %, bei gleichem Modell und gleichen Kosten pro Call. Das ist der Kern des DSPy-Versprechens: Sie schreiben keinen Prompt, Sie definieren das Problem und die Erfolgsmetrik.
Typische Einsatzgebiete sind Klassifikation, strukturierte Extraktion, Retrieval-Augmented Generation, mehrstufige Agenten mit Tool-Aufrufen und alles, wo Sie eine berechenbare Metrik haben (Genauigkeit, F1, semantische Ähnlichkeit, LLM-as-a-Judge). Bei rein kreativem Output ohne klare Metrik ist der Nutzen kleiner.
Installation und LM-Konfiguration in DSPy 3
DSPy 3 läuft ab Python 3.10 und installiert sich über pip. Die Bibliothek nutzt intern LiteLLM als Modell-Abstraktion, sodass ein Modellwechsel eine reine Konfigurationssache bleibt. Der Programmcode ändert sich dabei kein bisschen.
Die globale Konfiguration erfolgt einmal beim Programmstart. Für Produktion empfehle ich, das Modell nicht hart zu kodieren, sondern über Umgebungsvariablen zu setzen. So können Sie in CI mit einem kleineren Modell testen und in Produktion auf das teurere umschalten:
import os
import dspy
# Task-LM: erledigt die eigentliche Arbeit
task_lm = dspy.LM(
model=os.getenv("DSPY_TASK_MODEL", "openai/gpt-4o-mini"),
max_tokens=1024,
temperature=0.0,
cache=True, # Disk-Cache spart bei Optimizer-Läufen 60-80 % Kosten
)
# Optional: separates, stärkeres Modell für den Optimizer
# (Instruktionen werden nur einmalig generiert, lohnt sich für Qualität)
prompt_lm = dspy.LM(
model="openai/gpt-4o",
max_tokens=2048,
)
dspy.configure(lm=task_lm)
Signatures und Modules: das Programmiermodell
Eine Signature beschreibt, was ein LLM-Aufruf tun soll – ähnlich wie eine Funktionssignatur in Python, nur dass der Body vom Sprachmodell ausgefüllt wird. Sie können sie inline als String oder als Klasse mit Typ-Hints deklarieren:
from typing import Literal
import dspy
class KlassifiziereTicket(dspy.Signature):
"""Ordne ein Support-Ticket der passenden Kategorie zu."""
ticket_text: str = dspy.InputField(desc="Freitext des Kunden")
prioritaet: Literal["niedrig", "mittel", "hoch", "kritisch"] = dspy.OutputField()
kategorie: Literal[
"abrechnung", "zugriff", "bug", "feature-wunsch", "sonstiges"
] = dspy.OutputField()
begruendung: str = dspy.OutputField(desc="1-2 Sätze warum diese Kategorie")
Ein Module ist die algorithmische Strategie, mit der die Signature ausgeführt wird. Die wichtigsten Module in DSPy 3:
dspy.Predict – einfacher, einstufiger Aufruf
dspy.ChainOfThought – fügt ein internes reasoning-Feld ein, ähnlich wie o1-Style-Denken
dspy.ProgramOfThought – lässt das LLM Python-Code schreiben und ausführen
dspy.ReAct – Tool-Use-Agent mit Thought–Action–Observation-Schleife
dspy.MultiChainComparison – erzeugt mehrere Ketten und lässt das LLM die beste wählen
Ein vollständiges Programm sieht dann so aus:
klassifizierer = dspy.ChainOfThought(KlassifiziereTicket)
antwort = klassifizierer(ticket_text="Ich komme seit gestern nicht mehr in mein Konto")
print(antwort.prioritaet, antwort.kategorie, antwort.begruendung)
# hoch zugriff Der Nutzer ist ausgesperrt, Kernfunktion betroffen.
Module sind komponierbar. Für einen Multi-Step-Agent erben Sie von dspy.Module und kombinieren mehrere Bausteine – die Optimizer kompilieren später alle Sub-Prompts gemeinsam.
Wie optimiert DSPy Prompts? Optimizer im Vergleich
Der Optimizer (in DSPy-Terminologie Teleprompter) ist das Herzstück. Er sucht Prompts, die auf einer trainset-Menge Ihre Metrik maximieren. Die drei relevanten Optimizer in DSPy 3:
Optimizer
BootstrapFewShot
MIPROv2
BootstrapFinetune
Was wird optimiert
Few-Shot-Beispiele
Instruktionen + Few-Shots
Modellgewichte (Fine-Tuning)
Empfohlene Trainings-Größe
20–200
50–500
500–5000
Kosten pro Lauf (GPT-4o-mini)
ca. 0,50–3 USD
ca. 3–15 USD
Fine-Tuning-Gebühren
Dauer
2–15 Minuten
15–60 Minuten
1–6 Stunden
Typische Genauigkeitssteigerung
+5–15 %
+10–25 %
+15–35 %
Modell muss beibehalten werden
Nein
Nein
Ja (fine-getuned)
Wann einsetzen
Erster Versuch
Produktions-Baseline
Latenzkritische Endpunkte
Der Workflow ist immer derselbe: Sie definieren eine Metrik-Funktion, geben eine Trainings- und (optional) Validierungsmenge an und rufen compile() auf. DSPy führt das Programm mehrfach aus, sammelt Traces, wählt die besten Traces als Few-Shots aus und – bei MIPROv2 – lässt das Prompt-LM zusätzlich neue Instruktionen schreiben und testet sie via Bayesian Search.
Vollständiges Beispiel: RAG-Pipeline mit MIPROv2 kompilieren
Nehmen wir eine typische Aufgabe: Fragen zu internen Dokumenten beantworten. Wir kombinieren einen Retriever mit einem ChainOfThought-Modul und optimieren beides gemeinsam. Wer die Retriever-Seite tiefer verstehen will, findet Hintergründe in unserem Beitrag zur RAG-Pipeline-Optimierung.
import dspy
from dspy.evaluate import Evaluate
# 1. Signature für QA über Kontextdokumente
class BeantworteFrage(dspy.Signature):
"""Beantworte die Frage nur auf Basis des gegebenen Kontexts."""
kontext: list[str] = dspy.InputField()
frage: str = dspy.InputField()
antwort: str = dspy.OutputField(desc="Präzise Antwort, max. 3 Sätze")
# 2. Modul, das Retrieval + Generation verbindet
class RAG(dspy.Module):
def __init__(self, k: int = 4):
super().__init__()
self.retrieve = dspy.Retrieve(k=k)
self.generate = dspy.ChainOfThought(BeantworteFrage)
def forward(self, frage: str):
passages = self.retrieve(frage).passages
return self.generate(kontext=passages, frage=frage)
# 3. Metrik: exakter Textabgleich ODER LLM-Judge
def metrik(gold, pred, trace=None) -> float:
if gold.antwort.lower().strip() in pred.antwort.lower():
return 1.0
# Fallback: semantische Bewertung durch stärkeres LLM
judge = dspy.Predict("frage, referenz, kandidat -> korrekt: bool")
return float(judge(
frage=gold.frage, referenz=gold.antwort, kandidat=pred.antwort
).korrekt)
# 4. Kompilieren
from dspy.teleprompt import MIPROv2
optimizer = MIPROv2(
metric=metrik,
auto="light", # "light" | "medium" | "heavy"
num_threads=8,
)
kompiliertes_programm = optimizer.compile(
student=RAG(),
trainset=trainset, # ca. 100 Beispiele reichen
valset=valset, # ca. 50 Beispiele
requires_permission_to_run=False,
)
# 5. Auf Test-Set evaluieren
evaluator = Evaluate(devset=testset, metric=metrik, num_threads=8, display_progress=True)
score = evaluator(kompiliertes_programm)
print(f"Genauigkeit: {score:.1%}")
# 6. Kompiliertes Programm speichern (JSON mit Prompts + Few-Shots)
kompiliertes_programm.save("rag_v1.json")
Der große Vorteil des save()-Artefakts: Es enthält den kompletten kompilierten Prompt inklusive aller Few-Shot-Beispiele als JSON. Sie können es versionieren, in einen Docker-Container legen und deterministisch nachladen – kein zweiter Optimizer-Lauf in Produktion nötig.
Assertions und selbstheilende Programme
In der Praxis ist die häufigste Ursache für kaputte LLM-Pipelines nicht ein schlechter Prompt, sondern ein LLM, das mal ein JSON-Feld weglässt oder eine Zahl außerhalb des erlaubten Bereichs zurückgibt. DSPy löst das mit dspy.Assert und dspy.Suggest:
import dspy
class ExtrahiereRechnung(dspy.Module):
def __init__(self):
super().__init__()
self.extract = dspy.ChainOfThought(
"rechnungstext -> betrag_euro: float, iban: str"
)
def forward(self, rechnungstext: str):
result = self.extract(rechnungstext=rechnungstext)
# Hard-Constraint: Programm bricht ab, wenn nach N Versuchen falsch
dspy.Assert(
result.betrag_euro > 0,
"Betrag muss positiv sein.",
)
# Soft-Constraint: DSPy retryt automatisch mit der Fehlermeldung
# als zusätzlichem Kontext im Prompt
dspy.Suggest(
len(result.iban.replace(" ", "")) >= 15,
"IBAN muss mindestens 15 Zeichen lang sein.",
)
return result
Bei einem Verstoß gegen Suggest ruft DSPy das Modul mit einem zusätzlichen Hinweis erneut auf – der Prompt wird um „Bitte beachten: IBAN muss mindestens 15 Zeichen lang sein" erweitert. Nach der maximalen Retry-Zahl greift dann Assert und wirft eine Exception, sodass Sie sie in einer Dead-Letter-Queue landen lassen können. Diese selbstheilende Schleife ersetzt in vielen Fällen das, was ich früher mit OpenAI Structured Outputs und Pydantic manuell nachbauen musste.
Ist DSPy besser als LangChain?
Die Frage ist unfair, weil beide unterschiedliche Probleme lösen. LangChain und LangGraph sind primär Orchestrierungs-Frameworks für Ketten, Agenten und Zustandsautomaten – Sie schreiben trotzdem selbst Prompts. DSPy dagegen ist ein Compiler, der Prompts aus Signatures und Trainingsdaten erzeugt. Man kann beide kombinieren: DSPy für die einzelnen Prompt-Bausteine, LangGraph für den Kontrollfluss.
In meinen Projekten setze ich DSPy dann ein, wenn ich (a) eine berechenbare Metrik habe, (b) mindestens 30–50 gelabelte Beispiele existieren und (c) das gleiche Sprachmodell über längere Zeit produktiv laufen soll. Für Rapid Prototyping ohne Metrik ist LangChain oft schneller. Wer eine breitere Übersicht zu Agenten-Frameworks sucht, findet unseren Beitrag LangGraph vs CrewAI vs AutoGen hilfreich.
Der entscheidende Punkt: DSPy-Programme sind modellagnostisch. Wenn Claude 4.5 morgen 30 % günstiger wird, ändern Sie dspy.configure(lm=...), führen den Optimizer einmal neu aus und haben einen für Claude optimierten Prompt – ohne eine Zeile Logik umzuschreiben. Bei handgeschriebenen Prompts ist dieser Wechsel deutlich schmerzhafter, weil jede LLM-Familie andere Formulierungen bevorzugt.
DSPy in Produktion: Caching, Deployment und Kosten
Für den produktiven Betrieb gelten drei Regeln, die ich mir mit einigen schmerzhaften Ausfällen erarbeitet habe. Kurz gesagt: nicht meine besten Freitag-Nachmittage.
1. Kompilieren offline, deployen als Artefakt
Führen Sie compile() niemals im Request-Pfad aus. Der Optimizer verbraucht mehrere Minuten und viele LLM-Calls. Stattdessen: Optimizer in einem CI-Job oder Notebook laufen lassen, das Ergebnis mit program.save("v3.json") exportieren, Artefakt in S3 oder ein OCI-Registry legen und im Service beim Start via program.load("v3.json") nachziehen.
2. Trace-Sampling gegen Kosten-Explosion
DSPy 3 hat native OpenTelemetry-Integration. Aktivieren Sie in Produktion Sampling (etwa 5–10 % der Requests), sonst blähen Sie Ihr Observability-Backend auf. Die Traces landen als OTLP-Export in Langfuse, LangSmith oder Jaeger – ich habe gute Erfahrungen mit dem Stack aus DSPy plus Langfuse Tracing.
3. Metrik ist ein First-Class-Artefakt
Ihre Metrik-Funktion entscheidet, welchen Prompt DSPy produziert. Wenn die Metrik lückenhaft ist, optimiert DSPy exakt die Lücken – ein klassisches Goodhart-Problem. Ich versioniere Metrik und trainset gemeinsam mit dem kompilierten Prompt-Artefakt in Git-LFS. Ein Metrik-Update ohne neuen Kompilierlauf ist ein Anti-Pattern.
Häufige Stolperfallen und wie ich sie behebe
Problem 1: „Der Optimizer verbessert nichts." In 80 % der Fälle liegt das an einer zu ungenauen Metrik oder zu wenig Trainingsbeispielen. Prüfen Sie zunächst, ob die Metrik auf einer Handvoll manuell inspizierter Beispiele überhaupt korrekt anspricht. Weniger als 20 Trainingsbeispiele sind selten genug.
Problem 2: Kosten explodieren beim Kompilieren. Zwei Hebel: cache=True auf der LM setzen, und das Task-LM auf ein kleineres Modell (gpt-4o-mini, Claude Haiku 4.5) reduzieren. Das Prompt-LM darf ruhig größer sein – es wird nur ein paar Dutzend Mal aufgerufen.
Problem 3: Programm wirft bei Deployment andere Ergebnisse als in der Evaluierung. Fast immer eine temperature-Falle (ich bin da selbst schon zweimal reingerannt). DSPy setzt intern bei manchen Optimizern temperature=0.7, um Diversität zu bekommen. In Produktion explizit temperature=0.0 setzen und die geladene LM-Konfiguration loggen.
Problem 4: JSON-Parsing schlägt sporadisch fehl. Verwenden Sie dspy.TypedPredictor oder Pydantic-Typen in der Signature. DSPy 3 nutzt dann intern die nativen Structured-Outputs-Endpunkte der Provider, was die Fehlerrate praktisch auf null drückt. Die offizielle Signatures-Dokumentation beschreibt die zulässigen Typannotationen im Detail.
Für tieferen Einstieg in die theoretischen Grundlagen empfehle ich das Original-DSPy-Paper von Khattab et al. – es erklärt, warum Bootstrap-Traces mathematisch als Form von Meta-Learning funktionieren.
Häufig gestellte Fragen
Welche Modelle unterstützt DSPy 3?
DSPy 3 nutzt LiteLLM als Adapter und unterstützt damit über 100 Modelle: OpenAI (GPT-5, GPT-4o, o1), Anthropic (Claude 4.5 Opus/Sonnet/Haiku), Google (Gemini 2.5), Meta (Llama 4), Mistral, Cohere, Azure OpenAI, AWS Bedrock und lokale Modelle via Ollama oder vLLM. Ein Modellwechsel ist eine Konfigurationsänderung ohne Code-Anpassung.
Wie viele Trainingsbeispiele braucht DSPy mindestens?
Für BootstrapFewShot reichen 20 bis 50 Beispiele. Für MIPROv2 sind 50 bis 200 empfohlen. Für BootstrapFinetune sollten Sie mindestens 500 haben. Wichtiger als die reine Zahl ist die Qualität: Ein diverses Set aus schwierigen Randfällen schlägt einen großen Datensatz aus einfachen Beispielen.
Ist DSPy produktionsreif?
Ja, DSPy 3.0 (Oktober 2025) ist als stabile Version markiert, hat native OpenTelemetry-Integration und wird unter anderem von Databricks, JetBlue und Replit produktiv eingesetzt. Der empfohlene Weg: Optimizer offline in CI ausführen, das kompilierte JSON-Artefakt versionieren und im Service statisch laden.
Was kostet ein typischer DSPy-Kompilierlauf?
Mit GPT-4o-mini als Task-LM und aktiviertem Cache liegt ein BootstrapFewShot-Lauf auf 100 Beispielen bei etwa 0,50 bis 3 USD. Ein MIPROv2 auto="light"-Lauf kostet ungefähr 2 bis 5 USD, auto="heavy" kann 15 bis 25 USD kosten. Der Cache reduziert Folgeläufe drastisch.
Kann ich DSPy zusammen mit LangGraph verwenden?
Ja, das ist ein gängiges Muster. DSPy übernimmt die einzelnen Prompt-Bausteine, LangGraph orchestriert den Kontrollfluss zwischen ihnen als Zustandsautomat. Sie exportieren jedes DSPy-Modul als reine Python-Funktion und rufen es aus einem LangGraph-Node auf – so bekommen Sie optimierte Prompts plus explizite State-Machines.
Marcus has been gluing systems together for twelve years - first as an integrations engineer at Tray.io, then four years at MuleSoft (post-Salesforce acquisition) leading a team that built connectors for regulated-industry customers. He moved full-time into LLM orchestration in 2023 after a side project - an n8n workflow that triaged his consulting firm's intake email - replaced an actual headcount.
He focuses on the boring middle layer: idempotent webhook receivers, dead-letter queues for tool-call failures, and getting Temporal to play nicely with OpenAI's Assistants API. He's published two open-source n8n community nodes (one for Pinecone hybrid search, one for Anthropic prompt caching) and contributed retry-backoff improvements to the LangChain JS repo.
Lives in Atlanta. Writes about what actually breaks in production agents, not what looks good in a demo.
So konfigurierst du den LiteLLM Router in Produktion: General-, Content-Policy- und Context-Window-Fallbacks, sinnvolle Retry-Budgets, Redis-Cooldowns, Cost- und Latency-Routing sowie die neuen Routing-Plugins ab v1.92.