Skip to main content
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

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
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:
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:
See Custom Types for the full BamlType reference.

Demos (few-shot examples)

Attach examples for few-shot prompting via the predictor builder:
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

#[format] - Input serialization

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

#[render] - Custom input rendering (Jinja)

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

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

#[assert] - Hard constraint

Fails parsing if violated. Label is optional. See Constraints for the expression language.

Where it fits

  • A Signature doesn’t call the LM by itself
  • An Adapter turns the signature into a prompt and parses LM responses back into typed outputs
  • A Predict orchestrates this: signature + adapter + LM
  • Signatures are data - this separation supports reuse, testing, and optimizer integration