> ## Documentation Index
> Fetch the complete documentation index at: https://dsrs.herumbshandilya.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Modules

> The Module trait, batch execution, predictor discovery via Predictors, ChainOfThought, and signature augmentation

A module is a prompting strategy over a signature. Everything callable in dsrs implements `Module`: the bare LM call ([`Predict<S>`](/docs/components/predict)), `ChainOfThought<S>`, and any struct you compose from them. Swapping `Predict<QA>` for `ChainOfThought<QA>` changes the output type, and the compiler surfaces every downstream site that must change.

## Usage

Given the QA signature from [Signatures](/docs/components/signatures), the strategy is the only thing that changes between these two calls:

```rust theme={null}
use dspy_rs::{ChainOfThought, Predict};

let predict = Predict::<QA>::new();
let plain = predict.call(QAInput { question: "Why is the sky blue?".into() }).await?;
println!("{}", plain.answer);

let cot = ChainOfThought::<QA>::new();
let reasoned = cot.call(QAInput { question: "Why is the sky blue?".into() }).await?;
println!("{}", reasoned.reasoning); // the field ChainOfThought adds
println!("{}", reasoned.answer);    // the QA output field, through Deref
```

`ChainOfThought<QA>` returns `WithReasoning<QAOutput>` instead of `QAOutput`, so code that consumes the extra field is type-checked.

A custom module is a struct holding predictor fields plus a `Module` impl whose `forward` body is ordinary Rust:

```rust theme={null}
use dspy_rs::{ChainOfThought, Module, Predict, PredictError, Predicted, Signature, WithReasoning};

/// Condense the context down to what the question needs.
#[derive(Signature, Clone, Debug)]
struct Condense {
    #[input]
    question: String,
    #[input]
    context: String,

    #[output]
    notes: String,
}

/// Answer the question from the notes.
#[derive(Signature, Clone, Debug)]
struct Answer {
    #[input]
    question: String,
    #[input]
    notes: String,

    #[output]
    answer: String,
}

struct Rag {
    condense: Predict<Condense>,
    answer: ChainOfThought<Answer>,
}

dspy_rs::predictors!(Rag { condense, answer });

impl Module for Rag {
    type Input = CondenseInput;
    type Output = WithReasoning<AnswerOutput>;

    async fn forward(&self, input: CondenseInput) -> Result<Predicted<Self::Output>, PredictError> {
        let question = input.question.clone();
        let notes = self.condense.call(input).await?;

        self.answer
            .call(AnswerInput { question, notes: notes.notes.clone() })
            .await
    }
}

let rag = Rag { condense: Predict::new(), answer: ChainOfThought::new() };
```

The `predictors!` line is what makes the module optimizable and persistable: it names the `Predict` leaves for optimizer discovery (see [Predictor discovery](#predictor-discovery-predictors) below). `forward` is plain async Rust, so branching, loops, and early returns between the LM calls need no framework support.

Source: `crates/dspy-rs/src/core/module.rs`, `modules/chain_of_thought.rs`, `augmentation.rs`. All items below are re-exported at the crate root unless noted.

## The `Module` trait

```rust theme={null}
pub trait Module: Send + Sync {
    type Input: Schema + for<'a> Facet<'a> + Send + Sync;
    type Output: Schema + for<'a> Facet<'a> + Send + Sync;

    async fn forward(&self, input: Self::Input) -> Result<Predicted<Self::Output>, PredictError>;

    async fn call(&self, input: Self::Input) -> Result<Predicted<Self::Output>, PredictError> {
        self.forward(input).await // default implementation
    }
}
```

| Item          | Role                                                                                                                                                                                                                              |
| ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `type Input`  | What the module receives. Usually a signature's generated input struct.                                                                                                                                                           |
| `type Output` | What the LM is asked to produce. Strategies that modify the prompt change it (`ChainOfThought` yields `WithReasoning<_>`); wrappers that do not modify the prompt keep the inner output and record bookkeeping on `CallMetadata`. |
| `forward`     | The implementation hook. Module authors override this.                                                                                                                                                                            |
| `call`        | The caller-facing entry point. Delegates to `forward`; the split reserves a place for hooks, tracing, and middleware without breaking implementations.                                                                            |

Every call returns [`Predicted<Output>`](/docs/components/predict): the output struct (accessible directly via `Deref`) plus `CallMetadata` (token counts, raw response, tool traces) via `.metadata()`. Errors are always `PredictError`.

`forward` takes `input` by value. This is deliberate: pipeline authors move fields into sub-module inputs with zero clones. The cost is one input clone per example in evaluation loops that reuse a trainset.

To author a module: define a struct holding `Predict`/`ChainOfThought` fields, declare those fields with `predictors!` so optimizers and `ModuleState` can address them by name, and implement `forward`, as in the usage example above.

## Predictor discovery: `Predictors`

Optimizable leaves are declared **explicitly** — there is no reflection walker and no derive magic. A module that wants to be optimizable (or persistable via [`ModuleState`](/docs/components/state)) implements the `Predictors` trait, almost always through the `predictors!` macro:

```rust theme={null}
dspy_rs::predictors!(Rag { condense, answer });
```

expands to

```rust theme={null}
impl Predictors for Rag {
    fn predictors(&self) -> Vec<(String, &dyn PredictorInfo)> { /* ("condense", &self.condense), ... */ }
    fn predictors_mut(&mut self) -> Vec<(String, &mut dyn PredictorInfo)> { /* ... */ }
}
```

Each field's identifier becomes its leaf name. The names are the *canonical identity* of each leaf — the trace-name contract:

1. They become the leaf's trace-span component name (the optimizer stamps them via `PredictorInfo::set_trace_name` once per run).
2. Optimizer candidates address leaves by these names (ambient `fx::Params` entries bind per leaf at call time).
3. `ModuleState` persists per-leaf state under them.

Names must be unique within a module and stable across `predictors()`/`predictors_mut()`. `PredictorInfo` is the typed, object-safe per-leaf view: read methods (`schema()`, `instruction()`, `default_instruction()`, `demos_as_json()`, `dump_state()`) plus two boundary mutations — `set_trace_name` (the naming pass) and `load_state` (the install seam, used by `ModuleState::apply` and the optimizer's one-shot install of the winning candidate; candidate *evaluation* never calls it). See [Optimizers](/docs/components/optimizers).

## Batch execution: `forward_all`

`forward_all` is a free function, not a trait method.

```rust theme={null}
pub async fn forward_all<M: Module + ?Sized>(
    module: &M,
    inputs: Vec<M::Input>,
    max_concurrency: usize,
) -> Vec<Result<Predicted<M::Output>, PredictError>>
```

| Behavior          | Detail                                                            |
| ----------------- | ----------------------------------------------------------------- |
| Concurrency       | Bounded by `max_concurrency` (`buffer_unordered`).                |
| Failure isolation | Returns `Vec<Result<...>>`; one failure does not abort the batch. |
| Ordering          | Results preserve input order regardless of completion order.      |
| Tracing           | Instrumented as `dsrs.forward_all` at debug level.                |

## `ChainOfThought<S>`

`ChainOfThought` is pure sugar, a type alias rather than a distinct struct:

```rust theme={null}
pub type ChainOfThought<S> = Predict<Augmented<S, Reasoning>>;
pub type ChainOfThoughtOutput<S> = WithReasoning<<S as Signature>::Output>;
```

`Reasoning` is an augmentation that prepends `reasoning: String` as the first output field. The LM generates the reasoning text before the answer fields, so the chain of thought is in context when subsequent fields are produced.

| Aspect       | Detail                                                                                                                          |
| ------------ | ------------------------------------------------------------------------------------------------------------------------------- |
| Construction | `ChainOfThought::<S>::new()` or `ChainOfThought::<S>::builder()` (a `PredictBuilder`, see [Predict](/docs/components/predict)). |
| Output       | `WithReasoning<S::Output>` with fields `reasoning: String` and `inner: S::Output`.                                              |
| Access       | `Deref<Target = S::Output>`: `result.reasoning` is direct, `result.answer` resolves through deref.                              |
| Demos        | `Demo<Augmented<S, Reasoning>>`; demos must include reasoning text.                                                             |
| Calls        | Single LM call. Reasoning and answer are produced together, not across turns.                                                   |

<Note>
  For reasoning models (o1, o3, DeepSeek-R1) prefer bare `Predict`. An explicit `reasoning` field on top of internal thinking is redundant and can hurt quality.
</Note>

## Agent loops

There is no `ReAct` module. The tool-loop strategy lives in the IR instead: attach tools to a `Predict` (which executes as a 1-node `agent` program, see [Predict](/docs/components/predict)), or declare the loop as a first-class `AgentLoop` node with the `#[agent]` macro inside a `#[module]`. See [Tools and agents](/docs/components/tools-and-agents).

## Augmentation

Signature augmentation adds output fields that the LM actually generates. It is a prompt schema modification, not metadata and not data synthesis: the added field appears in the rendered output format, and the model fills it in.

| Item                    | Definition                                                                                                                                                                   |
| ----------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `Augmentation`          | `trait Augmentation: Send + Sync + 'static { type Wrap<T>: Schema + Facet + Deref + Send + Sync; }` The GAT maps an inner output `T` to a wrapper carrying the extra fields. |
| `Augmented<S, A>`       | A `Signature` with `Input = S::Input` and `Output = A::Wrap<S::Output>`. Instruction, input shape, and field metadata are inherited from `S`; only the output shape changes. |
| `AugmentedOutput<S, A>` | Alias for `<A as Augmentation>::Wrap<S::Output>`.                                                                                                                            |
| Tuple composition       | `(A, B)` wraps as `A::Wrap<B::Wrap<T>>`; auto-deref chains for field reads.                                                                                                  |

Augmentations are usually derived:

```rust theme={null}
#[derive(Augmentation, Clone, Debug)]
#[augment(output, prepend)]
struct Confidence {
    #[output] confidence: f64,
}
// Generates WithConfidence<O> with Deref<Target = O>
```

The derive generates a `With{Name}<O>` wrapper struct: the augmentation fields plus a flattened `inner: O`. With `prepend` the added fields come before `inner` in the output schema (how `Reasoning` guarantees reasoning is generated first); without it they follow. Every field must be `#[output]` (`#[input]` is rejected), descriptions come from doc comments or `#[output(desc = "...")]`, and `#[alias("...")]` renames the serialized field. `WithReasoning<O>` is exactly this expansion for `Reasoning`.

## See also

* [Predict and PredictBuilder](/docs/components/predict) for the leaf module and its builder methods
* [Signatures](/docs/components/signatures) for `Signature`, `Schema`, and generated input/output structs
* [The module macro](/docs/components/module-macro) for declaring modules as bodyless functions
* [Optimizers](/docs/components/optimizers) for how `Predict` leaves are discovered through modules
* [How DSRs thinks](/docs/getting-started/how-dsrs-thinks) for the call path from module to LM
* [ChainOfThought smoke example](https://github.com/krypticmouse/DSRs/blob/main/crates/dspy-rs/examples/91-smoke-slice2-chain-of-thought.rs)
* [Module authoring smoke example](https://github.com/krypticmouse/DSRs/blob/main/crates/dspy-rs/examples/92-smoke-slice3-module-authoring.rs)
* [Module iteration example](https://github.com/krypticmouse/DSRs/blob/main/crates/dspy-rs/examples/02-module-iteration-and-updation.rs)
