Skip to main content
Predict<S> is the leaf module. One Predict is one prompt template and one LM call: it formats a signature’s fields, instruction, and demos into a prompt, sends it to the configured LM, and parses the response into S::Output. Every other module ultimately delegates to one or more Predict leaves, and optimizers tune a program by rewriting Predict state (instruction override and demos). See How DSRs thinks for the mental model.

Usage

The derive generates QAInput and QAOutput from the field markers. .call() returns Result<Predicted<QAOutput>, PredictError>, and Predicted<O> implements Deref<Target = O>, so output fields read directly off the result. Everything beyond the defaults, such as demos, an instruction override, tools, or a per-instance LM, goes through the builder.

Construction

PredictBuilder<S>

Demo<S> is the typed input/output pair for few-shot prompting: Demo::new(input, output) with public fields input: S::Input and output: S::Output. Demos render as user/assistant exchanges in the prompt, and the types guarantee a demo matches the signature — a Demo<QA> cannot be attached to a Predict<SummarizeSig>. To seed a demo from a labeled trainset row, project the row through its ToInput/ToOutput impls: Demo::new(row.to_input()?, row.to_output()?), see Data. Tools are settable only at build time. Demos and the instruction override are also writable after construction through the state-install seam (PredictorInfo::load_state, used by ModuleState::apply and the optimizer’s final install of the winning candidate), see State. Every state mutation invalidates the cached instance overlay.

Calling

Predict<S> executes as a 1-node IR Program: a predict leaf named after the component, over SignatureDef::of::<S>() — or an agent leaf when tools are attached (the IR says Predict carries no tools; a tooled predictor is an agent loop). Each call executes this pipeline:
  1. Build (once, then cache) the 1-node program. The leaf name is the component name, so span identity and capture/replay keying are unchanged.
  2. Resolve the LM — the per-instance .lm(...) if set, otherwise the global configure() LM — and load the Interpreter against it (cached; reloaded when the resolved LM changes).
  3. Compose the effective ir::Overlay: instance state (instruction override + demos, minted once as an overlay against the cached program) plus any ambient optimizer candidate (fx::with_params / fx::with_overlay), ambient values winning per slot.
  4. Run the interpreter (run_collecting), which renders the prompt, consults any active replay scope, calls the LM (for an agent leaf: the tool loop, with the default StopSpecuntil_parse, max_turns = 8), and parses the response through the [[ ## field ## ]] protocol, evaluating #[check] and #[assert] constraints.
  5. Reassemble the run’s per-leaf metadata (LeafOutcome) into CallMetadata, and record a trace span when inside a capture() scope.
build_chat/call_and_parse are the conversation surface: the caller owns the Chat between turns. Both are thin wrappers over the interpreter’s conversation entry (Runtime): build_chat renders the opening turn through Interpreter::conversation_opening, and call_and_parse sends the chat through Interpreter::run_conversation — the same overlay-resolved rendering, span recording, and replay interception as the typed call path, so a conversation turn and a typed call over the same state produce byte-identical prompts. A turn is not a run: each call_and_parse records one trace span, and seq increments per turn. When tools are attached, the turn runs the same AgentLoop as the typed path, dispatching tool calls through the attached executors; for the “return me the tool calls, I’ll execute them” pattern, use Interpreter::run_conversation_caller_managed directly.

Replay interception

Before any provider call, the interpreter consults the active replay scope — typed calls and conversation turns alike. A Serve directive returns the recorded span with zero provider calls and zero tool re-executions; a Refuse directive returns PredictError::Replay; Live (or no scope) proceeds normally. Served predictions carry no per-field parse metadata. See Traces.

Predicted<O>

Every call returns Predicted<O>: the typed output plus runtime bookkeeping. It implements Deref<Target = O>, so output fields read directly (result.answer).

CallMetadata

Each FieldMeta records raw_text (the text the LM produced for that field), flags (Vec<Flag>, non-fatal coercion observations such as a stripped code fence), and checks (Vec<ConstraintResult> with label, expression, passed). Accessors: field_meta(), field_raw(field), field_flags(field), field_checks(field), field_names(), and has_failed_checks() for a quick scan across all fields. Failed #[check] constraints land here; failed #[assert] constraints become a Parse error instead.

PredictError

PredictError::class() buckets into ErrorClass (BadRequest, Temporary, BadResponse, Internal); is_retryable() drives retry logic. Parse errors include the raw response and the token usage: failed parses still consume tokens.

ToolSet

ToolSet is pre-fetched tool definitions plus name-indexed executors, built once and reused across calls. Predict builds and caches one ToolSet per instance from its builder tools on first use. To put Code Mode on a predictor, add the CodeModeTool (re-exported by dspy_rs under the code-mode feature) as the single tool:
A ToolSet::code_mode(...) set drops into LM::call_with_toolset directly. See Code Mode.

See also