> ## 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, combinators, ChainOfThought, ReAct, 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>`, `ReAct<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,
}

#[derive(facet::Facet)]
#[facet(crate = facet)]
struct Rag {
    condense: Predict<Condense>,
    answer: ChainOfThought<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 `facet::Facet` derive is what lets optimizer discovery find the `Predict` leaves inside the struct. `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`, `core/module_ext.rs`, `modules/chain_of_thought.rs`, `modules/react.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, derive `facet::Facet` so the optimizer's walker can discover the `Predict` leaves, and implement `forward`, as in the usage example above.

## 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.      |
| Progress          | Renders a progress bar on stderr.                                 |
| Tracing           | Instrumented as `dsrs.forward_all` at debug level.                |

## Combinators: `ModuleExt`

`ModuleExt` is blanket-implemented for every `Module`. It post-processes output without a full `impl Module`.

| Method         | Wrapper         | Closure                                    | Semantics                                       |
| -------------- | --------------- | ------------------------------------------ | ----------------------------------------------- |
| `.map(f)`      | `Map<M, T>`     | `Fn(M::Output) -> T`                       | Infallible output transform.                    |
| `.and_then(f)` | `AndThen<M, T>` | `Fn(M::Output) -> Result<T, PredictError>` | Fallible output transform; an `Err` propagates. |

Both wrappers keep `Input = M::Input`, set `Output = T`, and pass `CallMetadata` through unchanged. The wrapper structs derive `Facet` with the inner module as a real field (the closure is `#[facet(opaque, skip)]`), so the inner `Predict` leaves remain visible to optimizer discovery through the wrapper.

## `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>

## `ReAct<S>`

`ReAct<S>` runs a thought, action, observation loop over a set of tools, then extracts a typed answer. Bounds: `S: Signature`, `S::Input: Schema + Clone`, `S::Output: Schema`. As a `Module`: `Input = S::Input`, `Output = S::Output` (no wrapper type).

Internally it holds two `Predict` leaves: an action step (inputs `input`, `trajectory`; outputs `thought`, `action`, `action_input`, all strings) and an extract step (inputs `input`, `trajectory`; output `S::Output`). Both are visible to optimizer discovery.

### Loop semantics

1. The input struct is serialized to JSON and becomes the `input` field of both internal signatures.
2. The trajectory is seeded with a tool manifest (`Available tools:` with each tool's name and description, or `(none)`).
3. Each step, up to `max_steps` (default 4): the action predictor produces `thought`, `action`, `action_input`. The action name is trimmed of whitespace and surrounding quotes.
4. If the action is `finish`, `final`, or `done` (case insensitive), the loop stops.
5. Otherwise the named tool runs with `action_input` as its argument string. Matching is case insensitive: exact name, or substring containment in either direction. An unknown name yields the observation `tool_not_found: {name}`; a tool error yields `tool_error: {err}`. The step (thought, action, input, observation) is appended to the trajectory.
6. After the loop ends (terminal action or steps exhausted), the extract predictor reads the full trajectory and returns `S::Output`.

Metadata: each executed tool is recorded in `CallMetadata::tool_calls` with id `react-step-{n}`, and the manifest plus formatted per-step traces appear in `CallMetadata::tool_executions`, merged with the extract call's metadata.

### Builder

`ReAct::<S>::new()` equals `ReAct::<S>::builder().build()`. The builder type is `ReActBuilder<S>`; it is not re-exported at the crate root (full path `dspy_rs::modules::react::ReActBuilder`), so obtain it through `ReAct::<S>::builder()`.

| Method                | Signature                                                            | Effect                                                                            |
| --------------------- | -------------------------------------------------------------------- | --------------------------------------------------------------------------------- |
| `action_instruction`  | `(impl Into<String>) -> Self`                                        | Instruction override for the action predictor.                                    |
| `extract_instruction` | `(impl Into<String>) -> Self`                                        | Instruction override for the extract predictor.                                   |
| `max_steps`           | `(usize) -> Self`                                                    | Loop bound; clamped to at least 1. Default 4.                                     |
| `add_tool`            | `(impl ToolDyn + 'static) -> Self`                                   | Registers one rig tool.                                                           |
| `with_tools`          | `(impl IntoIterator<Item = Arc<dyn ToolDyn>>) -> Self`               | Registers many tools.                                                             |
| `tool`                | `(name, description, Fn(String) -> Future<Output = String>) -> Self` | Registers a closure as a tool; arguments arrive as a raw string (typically JSON). |
| `lm`                  | `(LM) -> Self`                                                       | Per-instance LM for both predictors, bypassing the global.                        |
| `build`               | `() -> ReAct<S>`                                                     | Constructs the module.                                                            |

Tools implement rig's `ToolDyn`. 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)
* [ReAct operational smoke example](https://github.com/krypticmouse/DSRs/blob/main/crates/dspy-rs/examples/93-smoke-slice4-react-operational.rs)
* [Module iteration example](https://github.com/krypticmouse/DSRs/blob/main/crates/dspy-rs/examples/02-module-iteration-and-updation.rs)
