Modules¶
A signature says what a task is. A module decides how a language model answers it. You wrap a signature in a module to get a runnable program.
dspy4s ships a few modules. They all share the same shape: construct one from a signature, then call it with its input type.
Predict¶
Predict answers the signature directly, in a single model call. You have
already used it in the Quickstart and on the
Signatures page:
It is the module to reach for when the task is a direct mapping from inputs to outputs.
ChainOfThought¶
ChainOfThought asks the model to reason before it answers. It adds a
reasoning: String field to the front of the output, so you get both the
explanation and the answer with direct dot access:
object QaReasoningExample:
val classify = ChainOfThought(Signature.fromString("question -> answer"))
/** Returns the corrected reasoning and the answer (ChainOfThought prepends `reasoning`). */
def call(question: String)(using RuntimeContext): Either[DspyError, (String, String)] =
classify((question = question)).map(p => (p.output.reasoning, p.output.answer))
The signature did not change. Swapping Predict for ChainOfThought is the
only edit, and the extra reasoning field appears on the output.
Verified snippet
This example is extracted from
Modules.scala.
ReAct¶
ReAct answers by calling tools in a loop: the model thinks, picks a tool, sees
the result, and repeats until it has an answer. It has its own page,
Tools & ReAct.
Refining outputs¶
Two modules wrap another module to improve its output against a reward function:
BestOfNsamples several completions in parallel and keeps the best one.Refinedoes the same sequentially, feeding each attempt's score back in.
Both take a reward function (input, prediction) => Double:
def bestOfN(question: String)(using RuntimeContext): Either[DspyError, String] =
val qa = ChainOfThought(Signature.of[BasicQA])
BestOfN(
module = qa,
n = AttemptCount(3),
rewardFn = (_, pred) => if pred.output.answer.length == 1 then 1.0 else 0.0,
threshold = 1.0
)((question = question)).map(_.output.answer)
Other modules¶
A few more modules cover specific strategies:
CodeActandProgramOfThoughtanswer by generating and running code through aCodeInterpreter, so the computation happens in a sandbox rather than in the model's head.Parallelruns several program calls concurrently.
def codeAct(n: Int)(using RuntimeContext): Either[DspyError, String] =
val program = CodeAct(Signature.fromString("n: int -> factorial"), interpreter = new SubprocessPythonInterpreter())
program((n = n)).map(_.output.factorial)
Choosing a module¶
| Module | Strategy | Use when |
|---|---|---|
Predict |
One call, direct answer. | The task maps inputs to outputs. |
ChainOfThought |
Reason, then answer. | The task benefits from step-by-step thinking. |
ReAct |
Call tools in a loop. | The model needs external information or actions. |
BestOfN / Refine |
Sample and rank against a reward. | You can score outputs and want the best. |
CodeAct / ProgramOfThought |
Generate and run code. | The answer needs real computation. |
Parallel |
Run calls concurrently. | You have many independent calls. |
Modules are ordinary Scala values, so you can also compose them into larger programs. That is the next step.
Next: Tools & ReAct.