07. DSPy Introduction
DSPy (Declarative Self-Programming) reframes prompt engineering from writing prompt strings to declaring computational traces that the system compiles into prompts. The motivation: hand-written prompts are brittle, hard to evaluate, and don't transfer well across model changes. DSPy treats prompts as compiled artifacts optimized against data.
The core concept: a "module" wraps language model calls. Rather than writing ollama.generate(prompt), you use dspy.Predict(MySignature). The module has a signature—an abstract specification of what inputs to what outputs. DSPy's compiler determines how to prompt the model to implement that signature for your specific data.
This matters because it separates concerns. The user declares what the module should do (signature). DSPy decides how to make it do that (compiled prompt + chain-of-thought if needed). The user evaluates whether the result is good. If not, DSPy re-optimizes. This is closer to how traditional compiled languages work: you specify behavior; the compiler figures out machine instructions.
import dspy
from dspy.evaluate import Evaluate
# Configure DSPy to use Ollama
llm = dspy.OllamaLocal(
model='llama3.2',
base_url='http://localhost:11434'
)
dspy.settings.configure(lm=llm)
# A basic Signature: define input and output fields
class ExtractPersonInfo(dspy.Signature):
"""Extract structured information from unstructured text about a person."""
text = dspy.InputField()
name = dspy.OutputField()
role = dspy.OutputField()
organization = dspy.OutputField()
# Create a module that implements this signature
extract = dspy.Predict(ExtractPersonInfo)
# Use it like a function
result = extract(text="Dr. Sarah Chen is the Chief Technology Officer at InnovateTech Labs. She previously led engineering at two startups.")
print(f"Name: {result.name}")
print(f"Role: {result.role}")
print(f"Organization: {result.organization}")
The signature declares outputs without specifying format, instructions, or examples. DSPy's default behavior generates a basic prompt that asks the model to produce those fields. The compilation process improves that prompt based on your data and evaluation criteria.
DSPy introduces the concept of programs: sequences of modules combined into larger workflows. A dspy.ChainOfThought module adds reasoning. A dspy.ChainOfThoughtVote votes across multiple responses. Programs compose: you can chain modules, add feedback loops, and create complex behaviors from simple building blocks.
# A more complex program: extract then validate
class ValidateExtraction(dspy.Signature):
"""Verify extracted information against original text."""
original_text = dspy.InputField()
extracted_name = dspy.InputField()
extracted_role = dspy.InputField()
extracted_org = dspy.InputField()
is_valid = dspy.OutputField()
correction_notes = dspy.OutputField()
extract = dspy.Predict(ExtractPersonInfo)
validate = dspy.Predict(ValidateExtraction)
# Simple composition
def extract_and_validate(text: str):
extraction = extract(text=text)
validation = validate(
original_text=text,
extracted_name=extraction.name,
extracted_role=extraction.role,
extracted_org=extraction.organization
)
return {
'extraction': extraction,
'validation': validation,
'is_valid': validation.is_valid.lower() == 'true'
}
result = extract_and_validate("Your meeting with COO Maria Santos is confirmed for 2pm.")
if not result['is_valid']:
print(f"Notes: {result['validation'].correction_notes}")
The library currently has rough edges. The Ollama local integration works but documentation assumes OpenAI API access. Error messages are cryptic. The optimizer system (discussed in Chapter 9) requires labeled data that takes effort to produce. DSPy is a effective direction but requires tolerance for partial documentation and experimentation.
Install DSPy and run the basic signature example against your Ollama instance. Identify one discrepancy between the documentation and your actual experience. Document whether DSPy modules improve or complicate your workflow for simple extraction tasks.