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 reasoning and the answer. `ChainOfThought` prepends the `reasoning` output. */
  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.

Some models give their reasoning apart from their answer. To use that reasoning, set nativeReasoning = true, and declare the capability on the model with capabilities = Some(LmCapabilities.openAi.copy(reasoning = true)). The prompt then does not ask for the field, the request has reasoning_effort, and the field takes the reasoning of the reply. On a model with no such capability, the model writes the field as text.

A signature can also have its own output of the type Reasoning, which is a String with the same rule.

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, one after the other, 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.fromString("question -> answer"))
  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

RLM answers by writing Python code and running it in a sandbox. The inputs are variables in that sandbox, so the model can explore a context that is too large for one prompt. The sandbox runs on Deno. See ReAct vs RLM.

Parallel runs several program calls concurrently.

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.
RLM Write and run code in a sandbox. The answer needs real computation, or the context is too large for one prompt.
BestOfN / Refine Sample and rank against a reward. You can score outputs and want the best.
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.