DSPy Module Parameterization
Wrap a signature in a callable module whose LM choice, instructions, and demonstrations are explicit parameters a compiler can search over, so published prompting techniques become swappable, signature-agnostic components.
A module implements a signature as a
callable: given input fields, it assembles a prompt (including any
demonstrations), calls the LM, and parses the output fields back out. The
base module, Predict, holds three explicit parameters: which LM to call,
the prompt instructions and each field's string prefix, and — the most
consequential lever in practice — the list of demonstrations used as few-shot
examples (for a frozen LM) or as training data (for finetuning). Making these
ordinary parameters rather than buried string literals is what lets an
optimizer treat prompting as parameter search instead of manual rewriting; see
instruction vs demonstration optimization
for which of these two levers tends to matter more in practice.
Built-in modules generalize published prompting techniques into
signature-agnostic, drop-in components implemented as a thin transform over
Predict: ChainOfThought prepends a rationale output field with a
"let's think step by step" prefix before the real answer field
(implementing chain-of-thought prompting for
any signature with no per-task code); ProgramOfThought and
MultiChainComparison generalize other published techniques the same way;
ReAct implements the ReAct loop as a module. Because a
module only needs to honor its declared signature, swapping Predict for
ChainOfThought in a one-line program makes the model reason before
committing to an output field with no other code change — the technique
becomes an implementation choice, not a rewrite of the surrounding pipeline.
Tools are the non-LM counterpart: modules that execute computation instead
of calling an LM — retrieval, SQL, or a sandboxed code interpreter — composed
into a pipeline the same way. A program declares the modules it needs
in __init__ (so a compiler can find and optimize them) and expresses the
pipeline as ordinary imperative code — including if, for, exceptions — in
a forward method, directly modeled on PyTorch/Chainer's define-by-run style.
Because the signature and the module implementing it are separate, changing
only the signature string (e.g. from "context, question -> answer" to
"context, question -> search_query") repurposes the same module from an
answerer into a query generator with no structural change to the surrounding
program.
Sources
- DSPy (Khattab et al.), §3