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

# Signatures

> Define task specifications for LM interactions

A signature defines the contract for an LM task: what goes in, what comes out, and what instruction to follow.

You write a struct with `#[derive(Signature)]`, mark fields as `#[input]` or `#[output]`, and the macro generates everything needed to work with predictors.

## Basic syntax

```rust theme={null}
use dspy_rs::Signature;

/// Answer questions accurately and concisely.
#[derive(Signature, Clone, Debug)]
struct QA {
    /// The question to answer
    #[input]
    question: String,

    /// A clear, direct answer
    #[output]
    answer: String,
}
```

That's it. The macro generates `QAInput` which you'll use to call the predictor.

## Docstrings add meaning

Doc comments turn into prompt instructions. They add context that types and variable names can't express:

* **Struct docstring** → The main task instruction
* **Field docstrings** → What each field means and what makes a good one

```rust theme={null}
/// Extract action items from meeting notes.
/// Return only concrete, assignable tasks.
#[derive(Signature, Clone, Debug)]
struct ExtractActions {
    /// Raw meeting transcript or notes
    #[input]
    notes: String,

    /// Who requested this extraction (for context on relevance)
    #[input]
    requester_role: String,

    /// Concrete tasks that can be assigned to a person.
    /// Each should start with a verb and be completable within a week.
    #[output]
    action_items: Vec<String>,
}
```

For **inputs**, docstrings explain why this data matters and any formatting the LM should expect.

For **outputs**, docstrings specify exactly what you want - not just the shape, but the qualities of a good response.

Only add a docstring if it says something the variable name doesn't. `/// The question` on a field called `question` adds nothing.

## Using Rust's type system

You get real types, not string parsing:

```rust theme={null}
#[derive(Signature, Clone, Debug)]
/// Analyze text for spam detection.
struct SpamAnalysis {
    #[input]
    text: String,

    #[output]
    is_spam: bool,  // actual bool

    #[output]
    confidence: f64,  // actual float

    #[output]
    keywords: Vec<String>,  // actual vec

    #[output]
    category: Option<String>,  // nullable
}
```

**Built-in types** (no extra work needed):

* `String`, `bool`
* `i8`, `i16`, `i32`, `i64`, `f32`, `f64`
* `Option<T>`, `Vec<T>`, `HashMap<String, T>`

## Custom types

When you have a non-standard type in a field, add `#[BamlType]` on it:

```rust theme={null}
use dspy_rs::{Signature, BamlType};

#[BamlType]
#[derive(Clone, Debug)]
enum Sentiment {
    Positive,
    Negative,
    Neutral,
}

#[BamlType]
#[derive(Clone, Debug)]
struct Citation {
    /// Document ID
    doc_id: String,
    /// Relevant quote from the document
    quote: String,
}

#[derive(Signature, Clone, Debug)]
/// Analyze sentiment and find supporting citations.
struct Analysis {
    #[input]
    text: String,

    #[output]
    sentiment: Sentiment,

    #[output]
    citations: Vec<Citation>,
}
```

See [Custom Types](/docs/building-blocks/types) for the full `BamlType` reference.

## Demos (few-shot examples)

Attach examples for few-shot prompting via the [predictor](/docs/building-blocks/predictors) builder:

```rust theme={null}
let predict = Predict::<QA>::builder()
    .demo(QA {
        question: "What is 2+2?".into(),
        answer: "4".into(),
    })
    .demo(QA {
        question: "What color is the sky?".into(),
        answer: "Blue".into(),
    })
    .build();
```

Demos are full signature structs with both input and output fields populated.

## Field attributes

Beyond `#[input]` and `#[output]`, you can use:

### `#[alias]` - Rename for LLM

```rust theme={null}
#[input]
#[alias = "user_question"]  // LLM sees "user_question", Rust uses "question"
question: String,
```

### `#[format]` - Input serialization

```rust theme={null}
#[input]
#[format("yaml")]  // serialize this input as YAML in the prompt
context: Vec<Document>,
```

Options: `"json"`, `"yaml"`, `"toon"`

### `#[render]` - Custom input rendering (Jinja)

```rust theme={null}
#[input]
#[render(jinja = "{{ this.title }}\n{{ this.body | truncate(300) }}")]
ticket: Ticket,
```

Use this when you need custom text rendering for an input field.

* Only valid on `#[input]` fields
* Template must be a string literal
* Jinja syntax is validated at compile time
* Cannot be combined with `#[format]` on the same field
* In `ChatAdapter`, built-ins + BAML parity helpers (`regex_match`, `sum`) + `truncate` are available

Template context:

* `this` - Current field value as JSON-like data
* `input` - Full input object (with top-level alias overlays)
* `field` - Metadata object with `name`, `rust_name`, and `type`
* `vars` - Adapter/surface variables (currently empty in `ChatAdapter`)

### `#[check]` - Soft constraint

```rust theme={null}
#[output]
#[check("this >= 0.0 and this <= 1.0", label = "valid_range")]
confidence: f64,
```

Recorded in metadata, doesn't fail parsing. Label is required.
Use Jinja boolean operators (`and`, `or`) in expressions.

### `#[assert]` - Hard constraint

```rust theme={null}
#[output]
#[assert("this.len() > 0")]
answer: String,
```

Fails parsing if violated. Label is optional.

See [Constraints](/docs/building-blocks/constraints) for the expression language.

## Where it fits

* A `Signature` doesn't call the LM by itself
* An [`Adapter`](/docs/building-blocks/adapter) turns the signature into a prompt and parses LM responses back into typed outputs
* A [`Predict`](/docs/building-blocks/predictors) orchestrates this: signature + adapter + LM
* Signatures are data - this separation supports reuse, testing, and optimizer integration

```
Signature + Input → Adapter (formats prompt) → LM → Adapter (parses response) → Typed Output
```
