DRAFT ONLY

Introduction: Programming AI Like Software—Not Just Prompting

Ever had a prompt break after a model update? You’re not alone. Traditional prompt engineering is like baking with invisible ingredients: you keep tweaking the recipe, but results remain unpredictable. Each change in the model or data can break your carefully crafted prompts.

DSPy flips this script. Instead of fragile trial-and-error, you program your AI in Python using clear, reusable modules with defined input and output contracts. This brings software engineering discipline to generative AI.

In this chapter, you’ll dive into DSPy’s two core building blocks: modules and signatures. These abstractions let you build AI systems that are understandable, testable, and easy to extend. Moving from prompt engineering to modular AI programming unlocks reliability and scalability.

Let’s see the difference between the old way and the DSPy way, using real examples.

The Problem with Prompt Engineering

Imagine rewriting a cake recipe every time you bake, never sure how the oven or ingredients will behave. That’s prompt engineering: you write a prompt, it works—until it doesn’t. There’s no clear contract for what goes in or out, and even small changes can break your system.

DSPy: Programming AI with Reusable Logic

DSPy brings order. You define your AI logic as reusable modules—just like Python functions or classes. Each module has a signature: a clear definition of required inputs and outputs. Think of signatures as your recipe’s ingredient list and expected result.

Suppose you want to build a simple Q&A system. Here’s how it looks with traditional prompt engineering:

Traditional Prompt Engineering for Q&A

# Example of a raw prompt for Q&A (not using DSPy)
context = "Stanford University is located in California."
question = "Where is Stanford University located?"

prompt = f"Context: {context}\\\\nQuestion: {question}\\\\nAnswer:"

# Send this prompt to your LLM and hope for the best!

This approach works—until you tweak the format or want to reuse the logic. There’s no guarantee about inputs or outputs, and every change risks breaking the system.

Now, here’s how DSPy lets you program the same logic as a clear, reusable module with an explicit signature:

Defining a Q&A Module with DSPy

import dspy

# Step 1: Define the signature (contract for inputs and outputs)
class QA(dspy.Signature):
    """Signature for question answering."""
    context: str = dspy.InputField()
    question: str = dspy.InputField()
    answer: str = dspy.OutputField()

# Step 2: Define the module (encapsulates the AI logic)
class QAModule(dspy.Module):
    def __init__(self):
        super().__init__()
        self.predict = dspy.Predict(QA)  # Uses the signature to generate answers

    def forward(self, context, question):
        return self.predict(context=context, question=question)

Let’s break it down:

  1. Signature: The QA class defines what the module expects (context, question) and what it returns (answer). This is your contract—no surprises.
  2. Module: QAModule encapsulates the logic. In the constructor, self.predict = dspy.Predict(QA) sets up the engine that uses the signature to generate answers. The forward method is your entry point, just like a function call.