Your own modules: steps in plain Python
A dspy.Module is a class whose forward method calls other modules with ordinary Python. DSPy finds the predictors inside, so an optimizer can tune them.
class Desk(dspy.Module):
def __init__(self):
super().__init__()
self.triage = dspy.Predict(Triage)
self.reply = dspy.Predict("ticket, category -> reply")
def forward(self, ticket):
category = self.triage(ticket=ticket).category
reply = self.reply(ticket=ticket, category=category).reply
return dspy.Prediction(category=category, reply=reply)Sub-modules are created in __init__ and used in forward. Here the ticket is sorted first, and the category is passed to a second step that writes the reply. forward returns a Prediction with whatever fields the caller needs.
desk = Desk()
result = desk(ticket="My parcel never arrived")
print(result.category)
print(result.reply)You call the module, not forward: desk(ticket=...) runs forward inside DSPy's bookkeeping, which records usage and calls callbacks. Calling desk.forward(...) directly skips it.
for name, predictor in desk.named_predictors():
print(name, "->", predictor.signature.signature)named_predictors walks the attributes and finds every Predict, including the one inside a ChainOfThought. Optimizers use exactly this to decide what to change. Assigning a module to self is all it takes to register it.
Any Python in between
forward is normal code: loops, if statements, calls to a database or an API. A ticket sorted as account could skip the reply step and go straight to a person. DSPy only needs the model calls to go through modules.
- Skip the reply step when the category is
account, and returnreply=None. - Replace
self.triagewithdspy.ChainOfThought(Triage)and printnamed_predictorsagain. - Call
desk.forward(ticket=...)and compare.
Slow is fine. Stopping is the only problem.