Dieser Beitrag behandelt die grundlegende Struktur eines DSPy-Programms und dessen praktische Umsetzung. Das Ziel ist die Erstellung einer einfachen Frage-Antwort-Anwendung. Das habe ich zwar schon gestern erledigt, aber das war nur zum Testen ob alles wie erwartet funktioniert.
Theorie: Die Grundstruktur eines DSPy-Programms
Ein DSPy-Programm besteht aus drei Kernkomponenten, die ein modulares und programmatisches Arbeiten mit Sprachmodellen (LMs) ermöglichen.
- Language Model (LM)
Dies ist das ausführende Sprachmodell. DSPy abstrahiert die Anbindung, sodass verschiedene Modelle von Anbietern wie OpenAI, Anthropic oder lokale Modelle über eine einheitliche Schnittstelle konfiguriert und ausgetauscht werden können. Die Konfiguration erfolgt einmalig und legt das Standardmodell für alle nachfolgenden Operationen fest. - Signatur (Signature)
Die Signatur ist eine deklarative Beschreibung der Aufgabe. Sie definiert, welche Eingabefelder das Programm erwartet und welche Ausgabefelder es generieren soll, ohne die genaue Formulierung des Prompts vorzugeben. Eine einfache Signatur für eine Frage-Antwort-Aufgabe wäre"Frage -> Antwort". Dies informiert DSPy, dass ein Feld namensFrageals Input dient und ein Feld namensAntwortals Output produziert werden soll. - Modul (Module)
Module sind die Bausteine, die eine bestimmte Prompting-Technik kapseln. Das grundlegendste Modul istdspy.Predict. Es nimmt eine Signatur entgegen und erzeugt auf deren Basis einen einfachen Prompt, um eine Vorhersage vom konfigurierten LM zu erhalten. Komplexere Module wiedspy.ChainOfThoughtermöglichen anspruchsvollere Argumentationsketten.
Die Kombination dieser drei Komponenten erlaubt es, LLM-basierte Anwendungen strukturiert zu programmieren, anstatt manuelle und oft fehleranfällige Prompts zu erstellen.
Anbindung von LLMs
Hier sind 10 Beispiele für die Anbindung verschiedener Language Models von unterschiedlichen Anbietern an DSPy.
OpenAI
import dspy
lm = dspy.LM("openai/gpt-4o-mini", api_key="YOUR_OPENAI_API_KEY")
dspy.configure(lm=lm)
Anthropic
import dspy
lm = dspy.LM("anthropic/claude-sonnet-4-5-20250929", api_key="YOUR_ANTHROPIC_API_KEY")
dspy.configure(lm=lm)
Databricks
import dspy
lm = dspy.LM(
"databricks/databricks-llama-4-maverick",
api_key="YOUR_DATABRICKS_ACCESS_TOKEN",
api_base="YOUR_DATABRICKS_WORKSPACE_URL", # e.g.: https://dbc-64bf4923-e39e.cloud.databricks.com/serving-endpoints
)
dspy.configure(lm=lm)
Gemini
import dspy
lm = dspy.LM("gemini/gemini-2.5-flash", api_key="YOUR_GEMINI_API_KEY")
dspy.configure(lm=lm)
Weitere Provider
DSPy erweitert seine Kompatibilität durch die Integration von LiteLLM, wodurch Dutzende weiterer LLM-Anbieter angebunden werden können. Die Anbindung eines via LiteLLM unterstützten Providers erfolgt über den generischen dspy.LM-Konstruktor. Das Modell wird dabei mit einer Zeichenkette im Format {provider_name}/{model_name} spezifiziert.
Die notwendigen API-Schlüssel müssen als Umgebungsvariablen entsprechend den Vorgaben des jeweiligen Anbieters gesetzt werden (z. B. ANYSCALE_API_KEY, TOGETHER_API_KEY).
Beispiele:
- Anyscale
anyscale/mistralai/Mistral-7B-Instruct-v0.1- Benötigt die Umgebungsvariable ANYSCALE_API_KEY.
- Together AI
together_ai/togethercomputer/llama-2-70b-chat- Benötigt die Umgebungsvariable TOGETHERAI_API_KEY.
- Amazon SageMaker
sagemaker/- Benötigt AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY und AWS_REGION_NAME.
- Azure
azure/- Benötigt AZURE_API_KEY, AZURE_API_BASE und AZURE_API_VERSION.
Nutzung von OpenAI-kompatiblen Endpunkten
Für Anbieter, die eine OpenAI-kompatible API bereitstellen, vereinfacht sich die Konfiguration. Dem Modellnamen wird das Präfix openai/ vorangestellt, und die URL des Endpunkts wird über den Parameter api_base übergeben wie ich es gestern (Tag-1) schon gemacht habe.
Beispiel für einen lokalen Server oder einen Custom Provider:
import dspy
# Konfiguration für einen beliebigen OpenAI-kompatiblen Endpunkt
custom_lm = dspy.LM(
"openai/your-model-name",
api_key="YOUR_PROVIDER_API_KEY",
api_base="YOUR_PROVIDER_URL"
)
dspy.configure(lm=custom_lm)
Dieses Vorgehen ermöglicht die nahtlose Einbindung einer Vielzahl von gehosteten und lokalen Sprachmodellen in den DSPy-Workflow, ohne dass für jeden Anbieter eine spezifische Implementierung erforderlich ist.
Praxis: Erstellung eines Frage-Antwort-Programms
Die Umsetzung erfolgt in drei Schritten: Initialisierung des LMs, Definition der Signatur und Anwendung des dspy.Predict-Moduls. In diesem Beispiel wird wieder Qwen3-VL-8B-Instruct verwendet.
local_llm = dspy.LM(
"openai/Qwen3-VL-8B-Instruct-Q4_K_M.gguf",
api_base="http://localhost:8080/v1",
api_key="no_key_needed"
)
dspy.configure(lm=local_llm)
Definition der Signatur
Die Signatur beschreibt die Aufgabe. Für eine einfache Frage-Antwort-Anwendung wird eine Signatur definiert, die ein Eingabefeld frage und ein Ausgabefeld antwort spezifiziert.
# 1. Eine einfache Frage-Antwort-Signatur definieren (das ist die Logik von BasicQA)
class FrageAntwortSignatur(dspy.Signature):
"""Beantworte die gestellte Frage."""
frage = dspy.InputField(desc="eine Frage an das Modell")
antwort = dspy.OutputField(desc="oft zwischen 1 und 5 Wörter")
Diese klassenbasierte Definition bietet mehr Klarheit und ermöglicht die Ergänzung von Beschreibungen, die dem LM als zusätzliche Anweisung dienen.
Erstellung und Verwendung des dspy.Predict-Moduls
Das dspy.Predict-Modul wird mit der zuvor definierten Signatur initialisiert. Dieses Modul übernimmt die Aufgabe, die Eingabe (Frage) an das LM zu senden und die Ausgabe (Antwort) zu extrahieren.
# 2. Ein Predict-Modul erstellen, das sich nun wie BasicQA verhält
basic_qa_modul = dspy.Predict(FrageAntwortSignatur)
# 3. Das Modul ausführen
question_to_model = "Wer ist der berühmteste Skateboard Fahrer weltweit?"
ergebnis = basic_qa_modul(frage=question_to_model)
print(f"Question: {question_to_model}")
print(f"Answer: {ergebnis.antwort}")
Ergebnis und Interpretation
Nach Ausführung des Programms wird die vom Sprachmodell generierte Antwort ausgegeben.
Beispielhafte Ausgabe:
Question: Wer ist der berühmteste Skateboard Fahrer weltweit?
Answer: Tony Hawk
Das Ergebnis zeigt, dass das Programm die gestellte Frage korrekt verarbeitet und eine faktisch richtige Antwort generiert hat. dspy.Predict hat die in der Signatur definierte Struktur frage -> antwort erfolgreich in einen für das LLM verständlichen Prompt übersetzt. Das zurückgegebene Objekt pred_antwort enthält die Antwort im Attribut .antwort, entsprechend dem definierten Ausgabefeld der Signatur.
Bisher unterscheidet sich nichts vom normalen Prompten eines LLM. Sieht man sich aber an, was an das LLM gesendet wurde, wird klar, dass doch ein paar Sachen anders laufen. Mit dem Befehl local_llm.inspect_history(n=1) kann man sich die letzte Interaktion mit dem Sprachmodell anzeigen lassen. Das beinhaltet konkret:
Die Antwort (Completion), die vom LLM zurückgegeben wurde.
Den vollständigen Prompt, der an das Sprachmodell (LLM) gesendet wurde.
Dies ist besonders nützlich für das Debugging, um zu verstehen, wie die Anweisungen und Beispiele aus einer dspy.Signature in den finalen Prompt umgewandelt werden, den das Modell tatsächlich erhält. Führe ich den Befehl „local_llm.inspect_history(n=1)“ im jupyter lab aus erhalte ich folgendes Ergebnis:
System message:
Your input fields are:
1. `frage` (str): eine Frage an das Modell
Your output fields are:
1. `antwort` (str): oft zwischen 1 und 5 Wörter
All interactions will be structured in the following way, with the appropriate values filled in.
[[ ## frage ## ]]
{frage}
[[ ## antwort ## ]]
{antwort}
[[ ## completed ## ]]
In adhering to this structure, your objective is:
Beantworte die gestellte Frage.
User message:
[[ ## frage ## ]]
Wer ist der berühmteste Skateboard Fahrer weltweit?
Respond with the corresponding output fields, starting with the field `[[ ## antwort ## ]]`, and then ending with the marker for `[[ ## completed ## ]]`.
Response:
[[ ## antwort ## ]]
Tony Hawk
[[ ## completed ## ]]
Mit dem Befehl inspect_history kann man den DSPy Prozess nachvollziehbar dokumentieren, welcher Prompt tatsächlich ans LLM ging, welche Antwort es zurückgab, und wie gut die Reaktion mit der Signatur übereinstimmt.
Neben der eigentlichen Frage „Wer ist der berühmteste Skateboard Fahrer weltweit?“ hat DSPy eine Reihe weiterer Informationen an das LLM gesendet, die Struktur, Bedeutung und Zweck der Aufgabe festlegen. Diese sogenannten System- und Formatierungsanweisungen sorgen dafür, dass das LLM die Eingabe im gewünschten Format verarbeitet und eine Ausgabe erzeugt, die zur definierten dspy.Signature passt. Dadurch ist sichergestellt, dass das Modell die Rollen (System vs. User), die erwarteten Felder (frage, antwort) sowie den Abschlussmarker ([[ ## completed ## ]]) beachtet. Im gezeigten Beispiel wird also deutlich, dass DSPy intern den Prompt so aufbereitet, dass das LLM:
- Die Eingabefelder erkennt (z. B.
frageals Textfeld), - Weiß, welche Ausgabefelder es erzeugen soll (
antwort), - Die Formatierung strikt einhält, was später das automatische Auslesen der Ergebnisse erleichtert, und
- Den Anweisungszweck („Beantworte die gestellte Frage“) explizit kennt, damit das Modell zielgerichtet reagiert.
Das Ergebnis ist somit nicht einfach nur eine Frage-Antwort-Interaktion, sondern eine klar strukturierte Kommunikation zwischen DSPy und dem Sprachmodell.
Zusammenfassung
Anhand eines einfachen Frage-Antwort-Beispiels wurde die grundlegende Programmstruktur in DSPy demonstriert. Durch die klare Trennung von Modellkonfiguration (dspy.configure), Aufgabenbeschreibung (dspy.Signature) und Ausführungslogik (dspy.Predict) entsteht ein modularer und gut wartbarer Code. Dieser Ansatz bildet die Basis für die Entwicklung komplexerer und optimierbarer LLM-Anwendungen mit DSPy.
Die programmatische Definition der Predict-Signatur fühlt sich jetzt schon sauberer an als ein f-string. Allerdings sind die Prompts die an das LLM gesendet werden auch deutlich länger als die eigentliche Frage. Das muss man immer im Hinterkopf behalten, wenn man nicht mit einem lokalen LLM arbeitet und eine API Benutzt, bei der man pro Token bezahlen muss.
dspy.Predict ist das grundlegendste „prädiktive Modul“ in DSPy. Seine Aufgabe ist es, eine Eingabe gemäß einer von Ihnen definierten Signatur (dspy.Signature) zu verarbeiten und eine Ausgabe zu generieren.
https://dspy.ai/api/modules/Predict/
Im DSPy Cheatsheet unter https://dspy.ai/cheatsheet/ findet man neben der einfachen dspy.Signature noch ein Reihe anderer programmatischer Abstraktionen. Dazu gehören unter anderem:
- dspy.ChainOfThought: Ein Modul, das den Denkprozess in Zwischenschritten abbildet, um komplexere Antworten zu generieren.
dspy.ProgramOfThought: Dieses Modul ermöglicht es dem Sprachmodell, Code zu generieren und auszuführen, um Fragen zu beantworten.
dspy.ReAct: Kombiniert Reasoning (Denkschritte) und Acting (Handeln, z.B. Werkzeugnutzung), um interaktive und dynamische Aufgaben zu lösen.
dspy.CodeAct: Ein Modul, das die Generierung und Ausführung von Code zur Beantwortung von Fragen ermöglicht.
Diese Module werden in den nächsten Tagen sicher noch eine Rolle spielen.
