Skip to content

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:

val classify = Predict(Signature.fromType[(sentence: String) => (sentiment: Boolean)])

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:

  • BestOfN samples several completions in parallel and keeps the best one.
  • Refine does 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:

  • CodeAct and ProgramOfThought answer by generating and running code through a CodeInterpreter, so the computation happens in a sandbox rather than in the model's head.
  • Parallel runs 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.