# dspy_rs::adapter Source: https://dsrs.herumbshandilya.com/docs/api/adapter Prompt formatting and LM response parsing. Generated from rustdoc JSON at `dspy-rs v0.7.3` (commit `f7d67a6`). Do not edit by hand; regenerate with `python3 docs/scripts/gen_api.py` (see the script header for the rustdoc commands). Item links lead to full signatures and method docs on docs.rs. Prompt formatting and LM response parsing. ## Re-exports | Item | Description | | --------- | --------------- | | `chat::*` | Glob re-export. | ## Modules | Item | Description | | ------------------------------------------------------------------------ | ----------- | | [`chat`](https://docs.rs/dspy-rs/latest/dspy_rs/adapter/chat/index.html) | | ## `adapter::chat` ### Structs | Item | Description | | -------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------- | | [`ChatAdapter`](https://docs.rs/dspy-rs/latest/dspy_rs/adapter/chat/struct.ChatAdapter.html) | Builds prompts and parses responses using the `[ ## field ## ]` delimiter protocol. | # dspy_rs::augmentation Source: https://dsrs.herumbshandilya.com/docs/api/augmentation Public API of the augmentation module Generated from rustdoc JSON at `dspy-rs v0.7.3` (commit `f7d67a6`). Do not edit by hand; regenerate with `python3 docs/scripts/gen_api.py` (see the script header for the rustdoc commands). Item links lead to full signatures and method docs on docs.rs. ## Structs | Item | Description | | ---------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------- | | [`Augmented`](https://docs.rs/dspy-rs/latest/dspy_rs/augmentation/struct.Augmented.html) | Type-level combinator: signature `S` with augmentation `A` applied to its output. | ## Traits | Item | Description | | --------------------------------------------------------------------------------------------- | ------------------------------------------------------------------ | | [`Augmentation`](https://docs.rs/dspy-rs/latest/dspy_rs/augmentation/trait.Augmentation.html) | Adds fields to a signature's output that the LM actually produces. | ## Type aliases | Item | Description | | -------------------------------------------------------------------------------------------------- | -------------------------------------------------------- | | [`AugmentedOutput`](https://docs.rs/dspy-rs/latest/dspy_rs/augmentation/type.AugmentedOutput.html) | Convenience alias: the output type of `Augmented`. | # dspy_rs::core Source: https://dsrs.herumbshandilya.com/docs/api/core The foundational abstractions everything else is built on. Generated from rustdoc JSON at `dspy-rs v0.7.3` (commit `f7d67a6`). Do not edit by hand; regenerate with `python3 docs/scripts/gen_api.py` (see the script header for the rustdoc commands). Item links lead to full signatures and method docs on docs.rs. The foundational abstractions everything else is built on. ## Re-exports | Item | Description | | -------------------------------------------------------------------------------------------------------- | ------------------------------------------- | | [`CallMetadata`](https://docs.rs/dspy-rs/latest/dspy_rs/core/predicted/struct.CallMetadata.html) | Re-export of `predicted::CallMetadata`. | | [`ConstraintResult`](https://docs.rs/dspy-rs/latest/dspy_rs/core/predicted/struct.ConstraintResult.html) | Re-export of `predicted::ConstraintResult`. | | [`ConversionError`](https://docs.rs/dspy-rs/latest/dspy_rs/core/errors/enum.ConversionError.html) | Re-export of `errors::ConversionError`. | | [`ErrorClass`](https://docs.rs/dspy-rs/latest/dspy_rs/core/errors/enum.ErrorClass.html) | Re-export of `errors::ErrorClass`. | | [`FieldMeta`](https://docs.rs/dspy-rs/latest/dspy_rs/core/predicted/struct.FieldMeta.html) | Re-export of `predicted::FieldMeta`. | | [`FieldMetadataSpec`](https://docs.rs/dspy-rs/latest/dspy_rs/core/schema/struct.FieldMetadataSpec.html) | Re-export of `schema::FieldMetadataSpec`. | | [`FieldPath`](https://docs.rs/dspy-rs/latest/dspy_rs/core/schema/struct.FieldPath.html) | Re-export of `schema::FieldPath`. | | [`FieldSchema`](https://docs.rs/dspy-rs/latest/dspy_rs/core/schema/struct.FieldSchema.html) | Re-export of `schema::FieldSchema`. | | [`InputRenderSpec`](https://docs.rs/dspy-rs/latest/dspy_rs/core/schema/enum.InputRenderSpec.html) | Re-export of `schema::InputRenderSpec`. | | [`JsonishError`](https://docs.rs/dspy-rs/latest/dspy_rs/core/errors/struct.JsonishError.html) | Re-export of `errors::JsonishError`. | | `lm::*` | Glob re-export. | | [`LmError`](https://docs.rs/dspy-rs/latest/dspy_rs/core/errors/enum.LmError.html) | Re-export of `errors::LmError`. | | `module::*` | Glob re-export. | | [`ModuleState`](https://docs.rs/dspy-rs/latest/dspy_rs/core/state/struct.ModuleState.html) | Re-export of `state::ModuleState`. | | [`ParseError`](https://docs.rs/dspy-rs/latest/dspy_rs/core/errors/enum.ParseError.html) | Re-export of `errors::ParseError`. | | [`Predicted`](https://docs.rs/dspy-rs/latest/dspy_rs/core/predicted/struct.Predicted.html) | Re-export of `predicted::Predicted`. | | [`PredictError`](https://docs.rs/dspy-rs/latest/dspy_rs/core/errors/enum.PredictError.html) | Re-export of `errors::PredictError`. | | [`PredictState`](https://docs.rs/dspy-rs/latest/dspy_rs/core/state/struct.PredictState.html) | Re-export of `state::PredictState`. | | `settings::*` | Glob re-export. | | `signature::*` | Glob re-export. | | [`SignatureSchema`](https://docs.rs/dspy-rs/latest/dspy_rs/core/schema/struct.SignatureSchema.html) | Re-export of `schema::SignatureSchema`. | | [`ToInput`](https://docs.rs/dspy-rs/latest/dspy_rs/core/example/trait.ToInput.html) | Re-export of `example::ToInput`. | | [`ToOutput`](https://docs.rs/dspy-rs/latest/dspy_rs/core/example/trait.ToOutput.html) | Re-export of `example::ToOutput`. | ## Modules | Item | Description | | ------------------------------------------------------------------------------- | ------------------------------------- | | [`example`](https://docs.rs/dspy-rs/latest/dspy_rs/core/example/index.html) | How trainset rows connect to modules. | | [`lm`](https://docs.rs/dspy-rs/latest/dspy_rs/core/lm/index.html) | | | [`module`](https://docs.rs/dspy-rs/latest/dspy_rs/core/module/index.html) | | | [`settings`](https://docs.rs/dspy-rs/latest/dspy_rs/core/settings/index.html) | | | [`signature`](https://docs.rs/dspy-rs/latest/dspy_rs/core/signature/index.html) | | ## `core::example` How trainset rows connect to modules. ### Traits | Item | Description | | ------------------------------------------------------------------------------------- | ------------------------------------------------------------------------ | | [`ToInput`](https://docs.rs/dspy-rs/latest/dspy_rs/core/example/trait.ToInput.html) | Projects a trainset row into a module's input type. | | [`ToOutput`](https://docs.rs/dspy-rs/latest/dspy_rs/core/example/trait.ToOutput.html) | Projects a trainset row into a signature's output type — the gold label. | ### Functions | Item | Description | | -------------------------------------------------------------------------------- | ----------------------------------------------------------------------- | | [`project`](https://docs.rs/dspy-rs/latest/dspy_rs/core/example/fn.project.html) | Field-name projection: serializes `row` and deserializes `U` out of it. | ## `core::lm` ### Re-exports | Item | Description | | -------------------- | --------------- | | `chat::*` | Glob re-export. | | `client_registry::*` | Glob re-export. | | `usage::*` | Glob re-export. | ### Structs | Item | Description | | ----------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | | [`LM`](https://docs.rs/dspy-rs/latest/dspy_rs/core/lm/struct.LM.html) | The live half: an `LMConfig` plus the initialized provider client and response cache. Constructed via `LM::builder()` or `LM::from_config`. | | [`LMConfig`](https://docs.rs/dspy-rs/latest/dspy_rs/core/lm/struct.LMConfig.html) | The data half of an LM: every generation parameter, no live state. | | [`LMConfigBuilder`](https://docs.rs/dspy-rs/latest/dspy_rs/core/lm/struct.LMConfigBuilder.html) | Use builder syntax to set the inputs and finish with `__internal_build()`). | | [`LMResponse`](https://docs.rs/dspy-rs/latest/dspy_rs/core/lm/struct.LMResponse.html) | | | [`ToolSet`](https://docs.rs/dspy-rs/latest/dspy_rs/core/lm/struct.ToolSet.html) | Pre-fetched tool definitions plus name-indexed executors. | ### Enums | Item | Description | | --------------------------------------------------------------------------------------- | ----------- | | [`ToolLoopMode`](https://docs.rs/dspy-rs/latest/dspy_rs/core/lm/enum.ToolLoopMode.html) | | ## `core::lm::chat` ### Structs | Item | Description | | ------------------------------------------------------------------------------------ | ----------- | | [`Chat`](https://docs.rs/dspy-rs/latest/dspy_rs/core/lm/chat/struct.Chat.html) | | | [`Message`](https://docs.rs/dspy-rs/latest/dspy_rs/core/lm/chat/struct.Message.html) | | ### Enums | Item | Description | | -------------------------------------------------------------------------------------------- | ----------- | | [`ContentBlock`](https://docs.rs/dspy-rs/latest/dspy_rs/core/lm/chat/enum.ContentBlock.html) | | | [`Role`](https://docs.rs/dspy-rs/latest/dspy_rs/core/lm/chat/enum.Role.html) | | ## `core::lm::client_registry` ### Structs | Item | Description | | ----------------------------------------------------------------------------------------------------------------------- | ----------- | | [`TestCompletionModel`](https://docs.rs/dspy-rs/latest/dspy_rs/core/lm/client_registry/struct.TestCompletionModel.html) | | ### Enums | Item | Description | | ----------------------------------------------------------------------------------------------- | ----------- | | [`LMClient`](https://docs.rs/dspy-rs/latest/dspy_rs/core/lm/client_registry/enum.LMClient.html) | | ### Traits | Item | Description | | -------------------------------------------------------------------------------------------------------------------- | ----------- | | [`CompletionProvider`](https://docs.rs/dspy-rs/latest/dspy_rs/core/lm/client_registry/trait.CompletionProvider.html) | | ## `core::lm::usage` ### Structs | Item | Description | | ------------------------------------------------------------------------------------- | ----------- | | [`LmUsage`](https://docs.rs/dspy-rs/latest/dspy_rs/core/lm/usage/struct.LmUsage.html) | | ## `core::module` ### Traits | Item | Description | | ---------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- | | [`Module`](https://docs.rs/dspy-rs/latest/dspy_rs/core/module/trait.Module.html) | Strategy-swapping interface for prompting modules. | | [`PredictorInfo`](https://docs.rs/dspy-rs/latest/dspy_rs/core/module/trait.PredictorInfo.html) | What optimizers read from — and, at explicit boundaries, write to — a `Predict` leaf. | | [`Predictors`](https://docs.rs/dspy-rs/latest/dspy_rs/core/module/trait.Predictors.html) | Explicit predictor-leaf discovery: a module *names* its optimizable `Predict` leaves. | ### Functions | Item | Description | | --------------------------------------------------------------------------------------- | ------------------------------------------ | | [`forward_all`](https://docs.rs/dspy-rs/latest/dspy_rs/core/module/fn.forward_all.html) | Runs a module on many inputs concurrently. | ## `core::settings` ### Structs | Item | Description | | --------------------------------------------------------------------------------------- | ----------- | | [`Settings`](https://docs.rs/dspy-rs/latest/dspy_rs/core/settings/struct.Settings.html) | | ### Functions | Item | Description | | ------------------------------------------------------------------------------------- | ----------- | | [`configure`](https://docs.rs/dspy-rs/latest/dspy_rs/core/settings/fn.configure.html) | | | [`get_lm`](https://docs.rs/dspy-rs/latest/dspy_rs/core/settings/fn.get_lm.html) | | ### Statics | Item | Description | | ----------------------------------------------------------------------------------------------------- | ----------- | | [`GLOBAL_SETTINGS`](https://docs.rs/dspy-rs/latest/dspy_rs/core/settings/static.GLOBAL_SETTINGS.html) | | ## `core::signature` ### Structs | Item | Description | | ---------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------- | | [`ConstraintSpec`](https://docs.rs/dspy-rs/latest/dspy_rs/core/signature/struct.ConstraintSpec.html) | A compile-time constraint declared on a signature field via `#check(...)` or `#assert(...)`. | ### Enums | Item | Description | | -------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------ | | [`ConstraintKind`](https://docs.rs/dspy-rs/latest/dspy_rs/core/signature/enum.ConstraintKind.html) | Whether a constraint is a soft check (reported but not fatal) or a hard assert (fails the call). | ### Traits | Item | Description | | ----------------------------------------------------------------------------------------- | ---------------------------------------------------------------------- | | [`Signature`](https://docs.rs/dspy-rs/latest/dspy_rs/core/signature/trait.Signature.html) | Declares the input/output fields and instruction for a prompting task. | # dspy_rs::data Source: https://dsrs.herumbshandilya.com/docs/api/data Data loading. Generated from rustdoc JSON at `dspy-rs v0.7.3` (commit `f7d67a6`). Do not edit by hand; regenerate with `python3 docs/scripts/gen_api.py` (see the script header for the rustdoc commands). Item links lead to full signatures and method docs on docs.rs. Data loading. ## Re-exports | Item | Description | | --------------- | --------------- | | `dataloader::*` | Glob re-export. | | `utils::*` | Glob re-export. | ## Modules | Item | Description | | --------------------------------------------------------------------------------- | ----------- | | [`dataloader`](https://docs.rs/dspy-rs/latest/dspy_rs/data/dataloader/index.html) | | | [`utils`](https://docs.rs/dspy-rs/latest/dspy_rs/data/utils/index.html) | | ## `data::dataloader` ### Structs | Item | Description | | --------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------- | | [`DataLoader`](https://docs.rs/dspy-rs/latest/dspy_rs/data/dataloader/struct.DataLoader.html) | Typed dataset ingress for JSON/CSV/Parquet/HuggingFace sources. | | [`RowRecord`](https://docs.rs/dspy-rs/latest/dspy_rs/data/dataloader/struct.RowRecord.html) | Raw parsed row passed to custom mapper closures in `load_*_with` APIs. | | [`TypedLoadOptions`](https://docs.rs/dspy-rs/latest/dspy_rs/data/dataloader/struct.TypedLoadOptions.html) | Options for shape-driven typed loading. | ### Enums | Item | Description | | ----------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- | | [`DataLoadError`](https://docs.rs/dspy-rs/latest/dspy_rs/data/dataloader/enum.DataLoadError.html) | Row-aware errors produced by typed data loading. | | [`UnknownFieldPolicy`](https://docs.rs/dspy-rs/latest/dspy_rs/data/dataloader/enum.UnknownFieldPolicy.html) | Controls how typed loaders handle source fields that are not part of the target row struct. | ## `data::utils` ### Functions | Item | Description | | ---------------------------------------------------------------------------- | ------------------------------------------------------- | | [`is_url`](https://docs.rs/dspy-rs/latest/dspy_rs/data/utils/fn.is_url.html) | Returns `true` if the string looks like an HTTP(S) URL. | # dspy_rs (crate root) Source: https://dsrs.herumbshandilya.com/docs/api/dspy-rs Everything importable directly from dspy_rs Generated from rustdoc JSON at `dspy-rs v0.7.3` (commit `f7d67a6`). Do not edit by hand; regenerate with `python3 docs/scripts/gen_api.py` (see the script header for the rustdoc commands). Item links lead to full signatures and method docs on docs.rs. The crate root re-exports the surface most programs use, so `use dspy_rs::{Predict, Signature, configure}` works without module paths. Items are listed here once with their home module linked; the module pages list everything else. ## Re-exports | Item | Description | | -------------------------------------------------------------------------------------------------------- | -------------------------------------------- | | `adapter::chat::*` | Glob re-export. | | `augmentation::*` | Glob re-export. | | [`begin_span`](https://docs.rs/dspy-rs/latest/dspy_rs/trace/capture/fn.begin_span.html) | Re-export of `trace::begin_span`. | | [`Capability`](https://docs.rs/dspy-rs/latest/dsrs_tools/capability/struct.Capability.html) | Re-export of `dsrs_tools::Capability`. | | [`capture_with_meta`](https://docs.rs/dspy-rs/latest/dspy_rs/trace/capture/fn.capture_with_meta.html) | Re-export of `trace::capture_with_meta`. | | [`capture`](https://docs.rs/dspy-rs/latest/dspy_rs/trace/capture/index.html) | Re-export of `trace::capture`. | | [`capture`](https://docs.rs/dspy-rs/latest/dspy_rs/trace/capture/fn.capture.html) | Re-export of `trace::capture`. | | [`CodeModeTool`](https://docs.rs/dspy-rs/latest/dsrs_tools/code_mode/struct.CodeModeTool.html) | Re-export of `dsrs_tools::CodeModeTool`. | | [`CompId`](https://docs.rs/dspy-rs/latest/dspy_rs/trace/span/struct.CompId.html) | Re-export of `trace::CompId`. | | [`Constraint`](https://docs.rs/dspy-rs/latest/dspy_rs/typesys/constraint/struct.Constraint.html) | Re-export of `typesys::Constraint`. | | [`ConstraintLevel`](https://docs.rs/dspy-rs/latest/dspy_rs/typesys/constraint/type.ConstraintLevel.html) | Re-export of `typesys::ConstraintLevel`. | | `core::*` | Glob re-export. | | `data::dataloader::*` | Glob re-export. | | `data::utils::*` | Glob re-export. | | `dsrs_macros::*` | Glob re-export. | | [`Eval`](https://docs.rs/dspy-rs/latest/dspy_rs/trace/span/struct.Eval.html) | Re-export of `trace::Eval`. | | `evaluate::*` | Glob re-export. | | [`Facet`](https://docs.rs/dspy-rs/latest/facet_core/trait.Facet.html) | Re-export of `facet::Facet`. | | [`Facet`](https://docs.rs/dspy-rs/latest/facet_macros/derive.Facet.html) | Re-export of `facet::Facet`. | | [`FieldType`](https://docs.rs/dspy-rs/latest/dspy_rs/typesys/schema/enum.FieldType.html) | Re-export of `typesys::FieldType`. | | [`Flag`](https://docs.rs/dspy-rs/latest/dspy_rs/typesys/coerce/enum.Flag.html) | Re-export of `typesys::Flag`. | | [`is_capturing`](https://docs.rs/dspy-rs/latest/dspy_rs/trace/capture/fn.is_capturing.html) | Re-export of `trace::is_capturing`. | | [`is_replaying`](https://docs.rs/dspy-rs/latest/dspy_rs/trace/replay/fn.is_replaying.html) | Re-export of `trace::is_replaying`. | | [`JsonMap`](https://docs.rs/dspy-rs/latest/dspy_rs/trace/span/type.JsonMap.html) | Re-export of `trace::JsonMap`. | | [`ModelEntry`](https://docs.rs/dspy-rs/latest/dspy_rs/trace/span/struct.ModelEntry.html) | Re-export of `trace::ModelEntry`. | | [`ModelId`](https://docs.rs/dspy-rs/latest/dspy_rs/trace/span/struct.ModelId.html) | Re-export of `trace::ModelId`. | | `modules::*` | Glob re-export. | | `optimizer::*` | Glob re-export. | | [`OutputSchema`](https://docs.rs/dspy-rs/latest/dspy_rs/typesys/schema/struct.OutputSchema.html) | Re-export of `typesys::OutputSchema`. | | `predictors::*` | Glob re-export. | | [`PrefixEntry`](https://docs.rs/dspy-rs/latest/dspy_rs/trace/span/struct.PrefixEntry.html) | Re-export of `trace::PrefixEntry`. | | [`PrefixId`](https://docs.rs/dspy-rs/latest/dspy_rs/trace/span/struct.PrefixId.html) | Re-export of `trace::PrefixId`. | | [`replay`](https://docs.rs/dspy-rs/latest/dspy_rs/trace/replay/index.html) | Re-export of `trace::replay`. | | [`replay`](https://docs.rs/dspy-rs/latest/dspy_rs/trace/replay/fn.replay.html) | Re-export of `trace::replay`. | | [`ReplayError`](https://docs.rs/dspy-rs/latest/dspy_rs/trace/replay/enum.ReplayError.html) | Re-export of `trace::ReplayError`. | | [`ReplayMode`](https://docs.rs/dspy-rs/latest/dspy_rs/trace/replay/enum.ReplayMode.html) | Re-export of `trace::ReplayMode`. | | [`ReplayReport`](https://docs.rs/dspy-rs/latest/dspy_rs/trace/replay/struct.ReplayReport.html) | Re-export of `trace::ReplayReport`. | | [`RUN_JS_TOOL_NAME`](https://docs.rs/dspy-rs/latest/dsrs_tools/code_mode/constant.RUN_JS_TOOL_NAME.html) | Re-export of `dsrs_tools::RUN_JS_TOOL_NAME`. | | [`SandboxConfig`](https://docs.rs/dspy-rs/latest/dsrs_tools/quickjs/struct.SandboxConfig.html) | Re-export of `dsrs_tools::SandboxConfig`. | | [`Schema`](https://docs.rs/dspy-rs/latest/dspy_rs/typesys/schema/trait.Schema.html) | Re-export of `typesys::Schema`. | | [`Shape`](https://docs.rs/dspy-rs/latest/facet_core/types/shape/struct.Shape.html) | Re-export of `facet::Shape`. | | [`Span`](https://docs.rs/dspy-rs/latest/dspy_rs/trace/span/struct.Span.html) | Re-export of `trace::Span`. | | [`SpanError`](https://docs.rs/dspy-rs/latest/dspy_rs/trace/span/struct.SpanError.html) | Re-export of `trace::SpanError`. | | [`SpanErrorKind`](https://docs.rs/dspy-rs/latest/dspy_rs/trace/span/enum.SpanErrorKind.html) | Re-export of `trace::SpanErrorKind`. | | [`SpanEvent`](https://docs.rs/dspy-rs/latest/dspy_rs/trace/span/enum.SpanEvent.html) | Re-export of `trace::SpanEvent`. | | [`SpanGuard`](https://docs.rs/dspy-rs/latest/dspy_rs/trace/capture/struct.SpanGuard.html) | Re-export of `trace::SpanGuard`. | | [`SpanId`](https://docs.rs/dspy-rs/latest/dspy_rs/trace/span/struct.SpanId.html) | Re-export of `trace::SpanId`. | | [`SpanOutcome`](https://docs.rs/dspy-rs/latest/dspy_rs/trace/capture/struct.SpanOutcome.html) | Re-export of `trace::SpanOutcome`. | | [`SpanRequest`](https://docs.rs/dspy-rs/latest/dspy_rs/trace/capture/struct.SpanRequest.html) | Re-export of `trace::SpanRequest`. | | [`Trace`](https://docs.rs/dspy-rs/latest/dspy_rs/trace/span/struct.Trace.html) | Re-export of `trace::Trace`. | | [`TraceMeta`](https://docs.rs/dspy-rs/latest/dspy_rs/trace/span/struct.TraceMeta.html) | Re-export of `trace::TraceMeta`. | | [`TraceOutcome`](https://docs.rs/dspy-rs/latest/dspy_rs/trace/span/struct.TraceOutcome.html) | Re-export of `trace::TraceOutcome`. | | `utils::*` | Glob re-export. | ## Macros | Item | Description | | ---------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- | | [`predictors`](https://docs.rs/dspy-rs/latest/dspy_rs/macro.predictors.html) | Implements `Predictors` for a module struct from a list of predictor fields, using each field's identifier as its leaf name. | ## Modules | Module | Description | | ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ | | [`adapter`](/docs/api/adapter) | Prompt formatting and LM response parsing. | | [`augmentation`](/docs/api/augmentation) | | | [`core`](/docs/api/core) | The foundational abstractions everything else is built on. | | [`data`](/docs/api/data) | Data loading. | | [`evaluate`](/docs/api/evaluate) | Evaluation and metrics for measuring module performance. | | [`fx`](/docs/api/fx) | Functional DSRs (experimental): harnesses as plain async functions. | | [`ir`](/docs/api/ir) | The intermediate representation (RFC 0002). | | [`modules`](/docs/api/modules) | | | [`optimizer`](/docs/api/optimizer) | Automatic prompt optimization. | | [`predictors`](/docs/api/predictors) | | | [`prelude`](/docs/api/prelude) | The curated core surface — the recommended import for DSRs programs. | | [`trace`](/docs/api/trace) | Execution trace capture (RFC 0001). | | [`typesys`](/docs/api/typesys) | In-house type system that replaces the vendored BAML stack (`bamltype`, `baml_types`, `internal_baml_jinja`, `jsonish`). | | [`utils`](/docs/api/utils) | LM response caching. | # dsrs_syntax Source: https://dsrs.herumbshandilya.com/docs/api/dsrs-syntax Shared syntax layer for the `.dsrs` text format (RFC 0002 §4). Generated from rustdoc JSON at `dsrs-syntax v0.1.0` (commit `f7d67a6`). Do not edit by hand; regenerate with `python3 docs/scripts/gen_api.py` (see the script header for the rustdoc commands). Item links lead to full signatures and method docs on docs.rs. Shared syntax layer for the `.dsrs` text format (RFC 0002 §4). ## Re-exports | Item | Description | | --------------------------------------------------------------------------------- | -------------------------------- | | [`check`](https://docs.rs/dsrs-syntax/latest/dsrs_syntax/structure/fn.check.html) | Re-export of `structure::check`. | ## Modules | Item | Description | | ---------------------------------------------------------------------- | ------------------------------------------------ | | [`lex`](https://docs.rs/dsrs-syntax/latest/dsrs_syntax/lex/index.html) | Lexer for the `.dsrs` text format (RFC 0002 §4). | ## Structs | Item | Description | | ------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | | [`ParseError`](https://docs.rs/dsrs-syntax/latest/dsrs_syntax/struct.ParseError.html) | A parse failure with the source position and what was expected — designed to be actionable feedback for a model regenerating the program. | ## `dsrs_syntax::lex` ### Structs | Item | Description | | ------------------------------------------------------------------------------- | --------------------------------------------------------- | | [`Lexed`](https://docs.rs/dsrs-syntax/latest/dsrs_syntax/lex/struct.Lexed.html) | One lexed token with its source position and byte extent. | | [`Lexer`](https://docs.rs/dsrs-syntax/latest/dsrs_syntax/lex/struct.Lexer.html) | | | [`Span`](https://docs.rs/dsrs-syntax/latest/dsrs_syntax/lex/struct.Span.html) | A source position, 1-based. | ### Enums | Item | Description | | ------------------------------------------------------------------------- | ----------- | | [`Tok`](https://docs.rs/dsrs-syntax/latest/dsrs_syntax/lex/enum.Tok.html) | | # dsrs_tools Source: https://dsrs.herumbshandilya.com/docs/api/dsrs-tools # dsrs-tools: sandboxed tool execution for DSRs Generated from rustdoc JSON at `dsrs-tools v0.1.0` (commit `f7d67a6`). Do not edit by hand; regenerate with `python3 docs/scripts/gen_api.py` (see the script header for the rustdoc commands). Item links lead to full signatures and method docs on docs.rs. # dsrs-tools: sandboxed tool execution for DSRs ## Re-exports | Item | Description | | ------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | | [`CacheStats`](https://docs.rs/dsrs-tools/latest/dsrs_tools/quickjs/struct.CacheStats.html) | Re-export of `quickjs::CacheStats`. | | [`Capability`](https://docs.rs/dsrs-tools/latest/dsrs_tools/capability/struct.Capability.html) | Re-export of `capability::Capability`. | | [`CapabilityHandler`](https://docs.rs/dsrs-tools/latest/dsrs_tools/capability/type.CapabilityHandler.html) | Re-export of `capability::CapabilityHandler`. | | [`code_mode_description`](https://docs.rs/dsrs-tools/latest/dsrs_tools/code_mode/fn.code_mode_description.html) | Re-export of `code_mode::code_mode_description`. | | [`CodeModeTool`](https://docs.rs/dsrs-tools/latest/dsrs_tools/code_mode/struct.CodeModeTool.html) | Re-export of `code_mode::CodeModeTool`. | | [`ExecError`](https://docs.rs/dsrs-tools/latest/dsrs_tools/error/enum.ExecError.html) | Re-export of `error::ExecError`. | | [`Executor`](https://docs.rs/dsrs-tools/latest/dsrs_tools/executor/trait.Executor.html) | Re-export of `executor::Executor`. | | [`js_identifier`](https://docs.rs/dsrs-tools/latest/dsrs_tools/capability/fn.js_identifier.html) | Re-export of `capability::js_identifier`. | | [`QuickJsExecutor`](https://docs.rs/dsrs-tools/latest/dsrs_tools/quickjs/struct.QuickJsExecutor.html) | Re-export of `quickjs::QuickJsExecutor`. | | [`QuickJsExecutorBuilder`](https://docs.rs/dsrs-tools/latest/dsrs_tools/quickjs/struct.QuickJsExecutorBuilder.html) | Re-export of `quickjs::QuickJsExecutorBuilder`. | | [`RegisteredTool`](https://docs.rs/dsrs-tools/latest/dsrs_tools/executor/struct.RegisteredTool.html) | Re-export of `executor::RegisteredTool`. | | [`RegisterError`](https://docs.rs/dsrs-tools/latest/dsrs_tools/error/enum.RegisterError.html) | Re-export of `error::RegisterError`. | | [`RigToolError`](https://docs.rs/dsrs-tools/latest/rig/tool/enum.ToolError.html) | Re-export of the rig tool traits that sandbox tools plug into, so downstream users don't need a direct (version-matched) rig dependency. | | [`run_js_parameters`](https://docs.rs/dsrs-tools/latest/dsrs_tools/code_mode/fn.run_js_parameters.html) | Re-export of `code_mode::run_js_parameters`. | | [`RUN_JS_TOOL_NAME`](https://docs.rs/dsrs-tools/latest/dsrs_tools/code_mode/constant.RUN_JS_TOOL_NAME.html) | Re-export of `code_mode::RUN_JS_TOOL_NAME`. | | [`run_script`](https://docs.rs/dsrs-tools/latest/dsrs_tools/quickjs/fn.run_script.html) | Re-export of `quickjs::run_script`. | | [`SandboxConfig`](https://docs.rs/dsrs-tools/latest/dsrs_tools/quickjs/struct.SandboxConfig.html) | Re-export of `quickjs::SandboxConfig`. | | [`SandboxTool`](https://docs.rs/dsrs-tools/latest/dsrs_tools/rig_tool/struct.SandboxTool.html) | Re-export of `rig_tool::SandboxTool`. | | [`ToolApi`](https://docs.rs/dsrs-tools/latest/dsrs_tools/code_mode/struct.ToolApi.html) | Re-export of `code_mode::ToolApi`. | | [`ToolDyn`](https://docs.rs/dsrs-tools/latest/rig/tool/trait.ToolDyn.html) | Re-export of the rig tool traits that sandbox tools plug into, so downstream users don't need a direct (version-matched) rig dependency. | | [`ToolInvocation`](https://docs.rs/dsrs-tools/latest/dsrs_tools/executor/struct.ToolInvocation.html) | Re-export of `executor::ToolInvocation`. | | [`ToolSource`](https://docs.rs/dsrs-tools/latest/dsrs_tools/source/struct.ToolSource.html) | Re-export of `source::ToolSource`. | # dspy_rs::evaluate Source: https://dsrs.herumbshandilya.com/docs/api/evaluate Evaluation and metrics for measuring module performance. Generated from rustdoc JSON at `dspy-rs v0.7.3` (commit `f7d67a6`). Do not edit by hand; regenerate with `python3 docs/scripts/gen_api.py` (see the script header for the rustdoc commands). Item links lead to full signatures and method docs on docs.rs. Evaluation and metrics for measuring module performance. ## Re-exports | Item | Description | | -------------- | --------------- | | `evaluator::*` | Glob re-export. | ## Modules | Item | Description | | ----------------------------------------------------------------------------------- | ----------- | | [`evaluator`](https://docs.rs/dspy-rs/latest/dspy_rs/evaluate/evaluator/index.html) | | ## `evaluate::evaluator` ### Re-exports | Item | Description | | ---------------------------------------------------------------------------- | ---------------------------------- | | [`Eval`](https://docs.rs/dspy-rs/latest/dspy_rs/trace/span/struct.Eval.html) | Re-export of `crate::trace::Eval`. | ### Traits | Item | Description | | ------------------------------------------------------------------------------------------------- | --------------------------------------------- | | [`TypedMetric`](https://docs.rs/dspy-rs/latest/dspy_rs/evaluate/evaluator/trait.TypedMetric.html) | How you tell the optimizer what "good" means. | ### Functions | Item | Description | | -------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------- | | [`average_score`](https://docs.rs/dspy-rs/latest/dspy_rs/evaluate/evaluator/fn.average_score.html) | Arithmetic mean of scores from a slice of `Eval`s. | | [`evaluate_trainset_with_concurrency`](https://docs.rs/dspy-rs/latest/dspy_rs/evaluate/evaluator/fn.evaluate_trainset_with_concurrency.html) | `evaluate_trainset` with an explicit concurrency level. | | [`evaluate_trainset`](https://docs.rs/dspy-rs/latest/dspy_rs/evaluate/evaluator/fn.evaluate_trainset.html) | Runs a module on every example in a trainset and scores each with a metric. | ### Type aliases | Item | Description | | ---------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- | | [`Rollout`](https://docs.rs/dspy-rs/latest/dspy_rs/evaluate/evaluator/type.Rollout.html) | One evaluated rollout: the metric result plus the execution trace that produced it (with `Trace::outcome` filled in). | ### Constants | Item | Description | | ------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------- | | [`DEFAULT_EVAL_CONCURRENCY`](https://docs.rs/dspy-rs/latest/dspy_rs/evaluate/evaluator/constant.DEFAULT_EVAL_CONCURRENCY.html) | Default number of examples evaluated concurrently by `evaluate_trainset`. | # dspy_rs::fx Source: https://dsrs.herumbshandilya.com/docs/api/fx Functional DSRs (experimental): harnesses as plain async functions. Generated from rustdoc JSON at `dspy-rs v0.7.3` (commit `f7d67a6`). Do not edit by hand; regenerate with `python3 docs/scripts/gen_api.py` (see the script header for the rustdoc commands). Item links lead to full signatures and method docs on docs.rs. Functional DSRs (experimental): harnesses as plain async functions. ## Re-exports | Item | Description | | --------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | [`with_overlay`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/bridge/fn.with_overlay.html) | Runs a future with an `ir::Overlay` as the ambient candidate — the overlay is unbound against the program into `Params` and scoped exactly like `with_params`. See `ir::bridge`. | ## Structs | Item | Description | | ---------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | | [`FnModule`](https://docs.rs/dspy-rs/latest/dspy_rs/fx/struct.FnModule.html) | Adapts a plain async function into a `Module`, so functional harnesses plug into `evaluate_trainset`, metrics, and every other module consumer. | | [`Params`](https://docs.rs/dspy-rs/latest/dspy_rs/fx/struct.Params.html) | The optimizable state of a functional harness: named `PredictState`s — instructions and demos keyed by the names passed to `predict`. | ## Functions | Item | Description | | ------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------- | | [`module`](https://docs.rs/dspy-rs/latest/dspy_rs/fx/fn.module.html) | Wraps an async function as a `Module`. See `FnModule`. | | [`predict`](https://docs.rs/dspy-rs/latest/dspy_rs/fx/fn.predict.html) | The atomic LM call of functional DSRs: one signature, one named parameter slot, one prediction. | | [`with_params`](https://docs.rs/dspy-rs/latest/dspy_rs/fx/fn.with_params.html) | Runs a future with `params` as the ambient parameter set for every `predict` call inside it. | # API Reference Source: https://dsrs.herumbshandilya.com/docs/api/index Auto-generated index of every public item in the DSRs crates, linked to full rustdoc This tab is an auto-generated index of the public API: every public struct, enum, trait, function, macro, and re-export across the DSRs crates, with its doc summary. It is produced from rustdoc JSON, the compiler's own view of the code, so it cannot drift from the source. Every item links to its page on docs.rs, which carries the full signatures, methods, trait implementations, and long-form documentation: The main crate: signatures, predictors, modules, IR, traces, optimizers Sandboxed tool execution and Code Mode The proc-macro crate behind the derive and attribute surface The shared .dsrs lexer and structural grammar both frontends read from For explanations and usage, the [component pages](/docs/components/signatures) are the right place; this tab answers "what exists and where does it live." ## Regenerating The pages are rebuilt from the working tree, not fetched from a registry, so they track your branch. From the repository root: ```bash theme={null} RUSTC_BOOTSTRAP=1 cargo rustdoc -p dspy-rs --lib --all-features -- -Z unstable-options --output-format json RUSTC_BOOTSTRAP=1 cargo rustdoc -p dsrs-tools --lib -- -Z unstable-options --output-format json RUSTC_BOOTSTRAP=1 cargo rustdoc -p dsrs_macros --lib -- -Z unstable-options --output-format json RUSTC_BOOTSTRAP=1 cargo rustdoc -p dsrs-syntax --lib -- -Z unstable-options --output-format json python3 docs/scripts/gen_api.py ``` Each generated page carries the crate version and commit it was built from. Run the five commands after any public-API change; CI can run them and fail on a dirty diff to keep this tab honest. # dspy_rs::ir Source: https://dsrs.herumbshandilya.com/docs/api/ir The intermediate representation (RFC 0002). Generated from rustdoc JSON at `dspy-rs v0.7.3` (commit `f7d67a6`). Do not edit by hand; regenerate with `python3 docs/scripts/gen_api.py` (see the script header for the rustdoc commands). Item links lead to full signatures and method docs on docs.rs. The intermediate representation (RFC 0002). ## Re-exports | Item | Description | | ------------------------------------------------------------------------------------------------------------- | -------------------------------------------------- | | [`agent`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/builder/fn.agent.html) | Re-export of `builder::agent`. | | [`AgentLoopNode`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/graph/struct.AgentLoopNode.html) | Re-export of `graph::AgentLoopNode`. | | [`AgentStepOpts`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/step/struct.AgentStepOpts.html) | Re-export of `step::AgentStepOpts`. | | [`ApplyError`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/edit/enum.ApplyError.html) | Re-export of `edit::ApplyError`. | | [`AsNodeName`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/builder/trait.AsNodeName.html) | Re-export of `builder::AsNodeName`. | | [`BakeError`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/graph/enum.BakeError.html) | Re-export of `graph::BakeError`. | | [`Binding`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/graph/struct.Binding.html) | Re-export of `graph::Binding`. | | [`Budget`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/interp/struct.Budget.html) | Re-export of `interp::Budget`. | | [`BudgetMeter`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/interp/struct.BudgetMeter.html) | Re-export of `interp::BudgetMeter`. | | [`BudgetPolicy`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/graph/enum.BudgetPolicy.html) | Re-export of `graph::BudgetPolicy`. | | [`build_module_program`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/module_build/fn.build_module_program.html) | Re-export of `module_build::build_module_program`. | | [`BuildError`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/builder/enum.BuildError.html) | Re-export of `builder::BuildError`. | | [`CapSet`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/graph/struct.CapSet.html) | Re-export of `graph::CapSet`. | | [`carried`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/builder/fn.carried.html) | Re-export of `builder::carried`. | | [`ClassDef`](https://docs.rs/dspy-rs/latest/dspy_rs/typesys/schema/struct.ClassDef.html) | Re-export of `crate::typesys::ClassDef`. | | [`code_hash`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/params/fn.code_hash.html) | Re-export of `params::code_hash`. | | [`CodeK`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/params/enum.CodeK.html) | Re-export of `params::CodeK`. | | [`CodeLang`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/params/enum.CodeLang.html) | Re-export of `params::CodeLang`. | | [`ConstraintDef`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/sig/struct.ConstraintDef.html) | Re-export of `sig::ConstraintDef`. | | [`ContextK`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/params/enum.ContextK.html) | Re-export of `params::ContextK`. | | [`ContextPolicy`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/params/struct.ContextPolicy.html) | Re-export of `params::ContextPolicy`. | | [`ConversationTurn`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/interp/enum.ConversationTurn.html) | Re-export of `interp::ConversationTurn`. | | [`cot`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/builder/fn.cot.html) | Re-export of `builder::cot`. | | [`current_overlay`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/bridge/fn.current_overlay.html) | Re-export of `bridge::current_overlay`. | | [`default_lm`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/module_build/fn.default_lm.html) | Re-export of `module_build::default_lm`. | | [`DemoRow`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/params/struct.DemoRow.html) | Re-export of `params::DemoRow`. | | [`Demos`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/params/enum.Demos.html) | Re-export of `params::Demos`. | | [`DsrsFileError`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/text/enum.DsrsFileError.html) | Re-export of `text::DsrsFileError`. | | [`Edit`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/edit/enum.Edit.html) | Re-export of `edit::Edit`. | | [`EditError`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/edit/enum.EditError.html) | Re-export of `edit::EditError`. | | [`EditKind`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/edit/enum.EditKind.html) | Re-export of `edit::EditKind`. | | [`EnumDef`](https://docs.rs/dspy-rs/latest/dspy_rs/typesys/schema/struct.EnumDef.html) | Re-export of `crate::typesys::EnumDef`. | | [`EnumValueDef`](https://docs.rs/dspy-rs/latest/dspy_rs/typesys/schema/struct.EnumValueDef.html) | Re-export of `crate::typesys::EnumValueDef`. | | [`Exhausted`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/interp/struct.Exhausted.html) | Re-export of `interp::Exhausted`. | | [`extern_hole`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/builder/fn.extern_hole.html) | Re-export of `builder::extern_hole`. | | [`FieldDef`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/sig/struct.FieldDef.html) | Re-export of `sig::FieldDef`. | | [`FieldType`](https://docs.rs/dspy-rs/latest/dspy_rs/typesys/schema/enum.FieldType.html) | Re-export of `crate::typesys::FieldType`. | | [`fork`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/builder/fn.fork.html) | Re-export of `builder::fork`. | | [`ForkJoinNode`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/graph/struct.ForkJoinNode.html) | Re-export of `graph::ForkJoinNode`. | | [`hole`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/builder/fn.hole.html) | Re-export of `builder::hole`. | | [`HoleImpl`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/graph/enum.HoleImpl.html) | Re-export of `graph::HoleImpl`. | | [`HoleNode`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/graph/struct.HoleNode.html) | Re-export of `graph::HoleNode`. | | [`HoleReport`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/step/struct.HoleReport.html) | Re-export of `step::HoleReport`. | | [`HostHoleFn`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/interp/type.HostHoleFn.html) | Re-export of `interp::HostHoleFn`. | | [`input_schema_of`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/interp/fn.input_schema_of.html) | Re-export of `interp::input_schema_of`. | | [`input`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/builder/fn.input.html) | Re-export of `builder::input`. | | [`Instruction`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/params/enum.Instruction.html) | Re-export of `params::Instruction`. | | [`Interner`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/graph/struct.Interner.html) | Re-export of `graph::Interner`. | | [`Interpreter`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/interp/struct.Interpreter.html) | Re-export of `interp::Interpreter`. | | [`KindTag`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/params/trait.KindTag.html) | Re-export of `params::KindTag`. | | [`LeafOutcome`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/interp/struct.LeafOutcome.html) | Re-export of `interp::LeafOutcome`. | | [`Lineage`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/graph/struct.Lineage.html) | Re-export of `graph::Lineage`. | | [`lit`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/builder/fn.lit.html) | Re-export of `builder::lit`. | | [`LoadError`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/interp/enum.LoadError.html) | Re-export of `interp::LoadError`. | | [`loop_`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/builder/fn.loop_.html) | Re-export of `builder::loop_`. | | [`LoopNode`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/graph/struct.LoopNode.html) | Re-export of `graph::LoopNode`. | | [`migrate_overlay`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/edit/fn.migrate_overlay.html) | Re-export of `edit::migrate_overlay`. | | [`ModelDef`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/graph/struct.ModelDef.html) | Re-export of `graph::ModelDef`. | | [`ModelId`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/graph/struct.ModelId.html) | Re-export of `graph::ModelId`. | | [`ModelRefK`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/params/enum.ModelRefK.html) | Re-export of `params::ModelRefK`. | | [`ModuleBuildError`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/module_build/enum.ModuleBuildError.html) | Re-export of `module_build::ModuleBuildError`. | | [`ModuleSpec`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/module_build/struct.ModuleSpec.html) | Re-export of `module_build::ModuleSpec`. | | [`ModuleStep`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/module_build/struct.ModuleStep.html) | Re-export of `module_build::ModuleStep`. | | [`ModuleStepKind`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/module_build/enum.ModuleStepKind.html) | Re-export of `module_build::ModuleStepKind`. | | [`Node`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/graph/enum.Node.html) | Re-export of `graph::Node`. | | [`NodeBudget`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/graph/struct.NodeBudget.html) | Re-export of `graph::NodeBudget`. | | [`NodeId`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/graph/struct.NodeId.html) | Re-export of `graph::NodeId`. | | [`NodeSpec`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/builder/struct.NodeSpec.html) | Re-export of `builder::NodeSpec`. | | [`out`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/builder/fn.out.html) | Re-export of `builder::out`. | | [`Overlay`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/params/struct.Overlay.html) | Re-export of `params::Overlay`. | | [`OverlayError`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/params/enum.OverlayError.html) | Re-export of `params::OverlayError`. | | [`ParamId`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/params/struct.ParamId.html) | Re-export of `params::ParamId`. | | [`ParamKind`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/params/enum.ParamKind.html) | Re-export of `params::ParamKind`. | | [`ParamOwner`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/params/enum.ParamOwner.html) | Re-export of `params::ParamOwner`. | | [`ParamSlot`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/params/struct.ParamSlot.html) | Re-export of `params::ParamSlot`. | | [`ParamValue`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/params/enum.ParamValue.html) | Re-export of `params::ParamValue`. | | [`ParseError`](https://docs.rs/dspy-rs/latest/dsrs_syntax/struct.ParseError.html) | Re-export of `text::ParseError`. | | [`Port`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/builder/enum.Port.html) | Re-export of `builder::Port`. | | [`PortRef`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/graph/enum.PortRef.html) | Re-export of `graph::PortRef`. | | [`PortSpec`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/module_build/enum.PortSpec.html) | Re-export of `module_build::PortSpec`. | | [`predict`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/builder/fn.predict.html) | Re-export of `builder::predict`. | | [`PredictNode`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/graph/struct.PredictNode.html) | Re-export of `graph::PredictNode`. | | [`Program`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/graph/struct.Program.html) | Re-export of `graph::Program`. | | [`ProgramBuilder`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/builder/struct.ProgramBuilder.html) | Re-export of `builder::ProgramBuilder`. | | [`ProgramMeta`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/graph/struct.ProgramMeta.html) | Re-export of `graph::ProgramMeta`. | | [`refine`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/builder/fn.refine.html) | Re-export of `builder::refine`. | | [`RefineNode`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/graph/struct.RefineNode.html) | Re-export of `graph::RefineNode`. | | [`RenderSpec`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/sig/enum.RenderSpec.html) | Re-export of `sig::RenderSpec`. | | [`retry`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/builder/fn.retry.html) | Re-export of `builder::retry`. | | [`RetryNode`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/graph/struct.RetryNode.html) | Re-export of `graph::RetryNode`. | | [`route`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/builder/fn.route.html) | Re-export of `builder::route`. | | [`RouteNode`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/graph/struct.RouteNode.html) | Re-export of `graph::RouteNode`. | | [`RunError`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/interp/enum.RunError.html) | Re-export of `interp::RunError`. | | [`RunOutput`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/interp/struct.RunOutput.html) | Re-export of `interp::RunOutput`. | | [`RuntimeEnv`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/interp/struct.RuntimeEnv.html) | Re-export of `interp::RuntimeEnv`. | | [`seq`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/builder/fn.seq.html) | Re-export of `builder::seq`. | | [`SeqNode`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/graph/struct.SeqNode.html) | Re-export of `graph::SeqNode`. | | [`SigError`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/sig/enum.SigError.html) | Re-export of `sig::SigError`. | | [`SigId`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/graph/struct.SigId.html) | Re-export of `graph::SigId`. | | [`SigMismatch`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/sig/struct.SigMismatch.html) | Re-export of `sig::SigMismatch`. | | [`SignatureBuilder`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/sig/struct.SignatureBuilder.html) | Re-export of `sig::SignatureBuilder`. | | [`SignatureDef`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/sig/struct.SignatureDef.html) | Re-export of `sig::SignatureDef`. | | [`Slot`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/params/struct.Slot.html) | Re-export of `params::Slot`. | | [`StepDef`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/step/struct.StepDef.html) | Re-export of `step::StepDef`. | | [`StepKind`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/step/enum.StepKind.html) | Re-export of `step::StepKind`. | | [`StopSpec`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/graph/struct.StopSpec.html) | Re-export of `graph::StopSpec`. | | [`SwapTarget`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/edit/enum.SwapTarget.html) | Re-export of `edit::SwapTarget`. | | [`Sym`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/graph/struct.Sym.html) | Re-export of `graph::Sym`. | | [`ToolDef`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/graph/struct.ToolDef.html) | Re-export of `graph::ToolDef`. | | [`ToolDesc`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/params/enum.ToolDesc.html) | Re-export of `params::ToolDesc`. | | [`ToolId`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/graph/struct.ToolId.html) | Re-export of `graph::ToolId`. | | [`ToolKind`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/graph/enum.ToolKind.html) | Re-export of `graph::ToolKind`. | | [`ToolSetK`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/params/enum.ToolSetK.html) | Re-export of `params::ToolSetK`. | | [`ToolStepDef`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/step/struct.ToolStepDef.html) | Re-export of `step::ToolStepDef`. | | [`ToolSuspension`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/interp/struct.ToolSuspension.html) | Re-export of `interp::ToolSuspension`. | | [`TypeTable`](https://docs.rs/dspy-rs/latest/dspy_rs/typesys/schema/struct.TypeTable.html) | Re-export of `crate::typesys::TypeTable`. | | [`unbound_model_config`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/module_build/fn.unbound_model_config.html) | Re-export of `module_build::unbound_model_config`. | | [`ValidateError`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/validate/enum.ValidateError.html) | Re-export of `validate::ValidateError`. | | [`with_ambient_overlay`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/bridge/fn.with_ambient_overlay.html) | Re-export of `bridge::with_ambient_overlay`. | | [`with_overlay`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/bridge/fn.with_overlay.html) | Re-export of `bridge::with_overlay`. | ## Modules | Item | Description | | ----------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | [`bridge`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/bridge/index.html) | The fx/ModuleState ↔ `Overlay` bridge (RFC 0002 §2.4 migration contract). | | [`builder`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/builder/index.html) | The Rust builder frontend (RFC 0002 §4.3–4.4): constructs the same runtime `Program` value the text parser will. | | [`edit`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/edit/index.html) | The graph-edit calculus: the *structural* mutation half of the IR. | | [`graph`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/graph/index.html) | The IR graph core (RFC 0002 §2): entity ids, the `Interner`, the closed `Node` enum, field-level `Binding`/`PortRef` dataflow, and `Program` — arenas over value-level signatures. | | [`interp`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/interp/index.html) | The IR interpreter (RFC 0002 §3): async evaluation of a loaded `Program`. | | [`module_build`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/module_build/index.html) | RFC 0003 stage M-3 library support: the module "linker". | | [`params`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/params/index.html) | Parameters (RFC 0002 §2.4): every mutable thing is a named, addressable slot; a candidate is an `Overlay` read through at render time, never a mutation. | | [`sig`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/sig/index.html) | Value-level signatures (RFC 0002 §1) — `SignatureDef` and friends. | | [`step`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/step/index.html) | RFC 0003 stage M-2: step metadata. | | [`text`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/text/index.html) | The `.dsrs` text format (RFC 0002 §4, stage IR-5). | | [`validate`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/validate/index.html) | Load-time validation (RFC 0002 §2.3): builder `finish()`, the loader, and `Program::validate()` run the same code path. Nothing is checked lazily at call time. | ## `ir::sig` Value-level signatures (RFC 0002 §1) — `SignatureDef` and friends. ### Structs | Item | Description | | ------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------- | | [`ConstraintDef`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/sig/struct.ConstraintDef.html) | Owned runtime form of `ConstraintSpec` (which stays `&'static` for the derive). | | [`FieldDef`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/sig/struct.FieldDef.html) | One input or output field of a `SignatureDef`. | | [`SigMismatch`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/sig/struct.SigMismatch.html) | Structural mismatch reported by `SignatureDef::matches`. | | [`SignatureBuilder`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/sig/struct.SignatureBuilder.html) | Builder for `SignatureDef`; validation happens at `finish`. | | [`SignatureDef`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/sig/struct.SignatureDef.html) | A signature as an owned value: what the derive macro knows at compile time, available at runtime with no `'static` requirement (RFC 0002 §1.1). | ### Enums | Item | Description | | ---------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | | [`RenderSpec`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/sig/enum.RenderSpec.html) | Owned runtime form of `InputRenderSpec`. | | [`SigError`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/sig/enum.SigError.html) | Validation failure from `SignatureBuilder::finish`. What the derive macro rejects, this rejects — same closed subset in both lanes (RFC 0002 §1.3). | ## `ir::bridge` The fx/ModuleState ↔ `Overlay` bridge (RFC 0002 §2.4 migration contract). ### Functions | Item | Description | | ------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | | [`current_overlay`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/bridge/fn.current_overlay.html) | The ambient overlay, if a `with_ambient_overlay` scope is active on this task. Read by `#module`-generated executable fns immediately before `Interpreter::run`. | | [`with_ambient_overlay`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/bridge/fn.with_ambient_overlay.html) | Runs `fut` with `overlay` as the ambient candidate for every `#module` fn called on this task — the interpreter-lane sibling of `fx::with_params`. | | [`with_overlay`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/bridge/fn.with_overlay.html) | Runs `fut` with `overlay` (unbound against `program`) as the ambient `fx::Params` scope — the fx-lane equivalent of passing the overlay to `Interpreter::run`. | ## `ir::builder` The Rust builder frontend (RFC 0002 §4.3–4.4): constructs the same runtime `Program` value the text parser will. ### Structs | Item | Description | | ------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | | [`NodeSpec`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/builder/struct.NodeSpec.html) | An unregistered node: the builder-side mirror of `Node` with name-based ports and inline children. | | [`ProgramBuilder`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/builder/struct.ProgramBuilder.html) | Builds a `Program`. Declarations (caps, models, sigs, tools) accumulate; `main` lowers the node tree, materializes param slots, seals the hash, and validates. | ### Enums | Item | Description | | -------------------------------------------------------------------------------------- | ------------------------------------------------------- | | [`BuildError`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/builder/enum.BuildError.html) | | | [`Port`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/builder/enum.Port.html) | A name-based port, resolved to a `PortRef` at `main()`. | ### Traits | Item | Description | | --------------------------------------------------------------------------------------- | ---------------------------------------------------------- | | [`AsNodeName`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/builder/trait.AsNodeName.html) | Anything that names a node: a string, or a leaf/step spec. | ### Functions | Item | Description | | -------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- | | [`agent`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/builder/fn.agent.html) | The LLM+tool loop. | | [`carried`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/builder/fn.carried.html) | `^field` — the previous iteration's carried value (Loop bodies only). | | [`cot`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/builder/fn.cot.html) | Chain-of-thought sugar: a Predict over `sig.augmented_with(reasoning)`. | | [`extern_hole`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/builder/fn.extern_hole.html) | An extern (host-backed) typed hole (RFC 0003 §4): a native fn bound by leaf name from the runtime environment at load. | | [`fork`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/builder/fn.fork.html) | Concurrent branches, joined all-success / fail-fast. | | [`hole`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/builder/fn.hole.html) | A typed hole: sandboxed JS with a declared signature and capability set. | | [`input`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/builder/fn.input.html) | `$.field` — the enclosing scope's input. | | [`lit`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/builder/fn.lit.html) | A JSON literal port. | | [`loop_`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/builder/fn.loop_.html) | Bounded loop. | | [`out`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/builder/fn.out.html) | `node.field` — an earlier node's output. `node` accepts a name string or a leaf spec (anything `AsNodeName`). | | [`predict`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/builder/fn.predict.html) | One LM call over `sig`. | | [`refine`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/builder/fn.refine.html) | Judge-gated refinement of a child. | | [`retry`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/builder/fn.retry.html) | Bounded retry of a child. | | [`route`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/builder/fn.route.html) | Enum-discriminated branching. | | [`seq`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/builder/fn.seq.html) | Sequential composition. | ## `ir::edit` The graph-edit calculus: the *structural* mutation half of the IR. ### Enums | Item | Description | | ----------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | | [`ApplyError`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/edit/enum.ApplyError.html) | A locally-checkable application failure. | | [`Edit`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/edit/enum.Edit.html) | One structural edit. Serde values: an optimizer's proposal is data, not code — it can be logged, replayed against the same parent, and diffed. | | [`EditError`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/edit/enum.EditError.html) | Why `Program::edited` refused. | | [`EditKind`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/edit/enum.EditKind.html) | A lightweight, serializable descriptor of an edit kind admissible at a node — the menu `Program::legal_edits` returns, suitable for prompting an LLM proposer. | | [`SwapTarget`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/edit/enum.SwapTarget.html) | Target kind of `Edit::SwapLeaf`. | ### Functions | Item | Description | | ------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | [`migrate_overlay`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/edit/fn.migrate_overlay.html) | Carries tuned values across a structural edit: for every entry in `overlay` (minted against `parent`), re-mint it against `child` when the child has a slot at the same `ParamPath`... | ## `ir::graph` The IR graph core (RFC 0002 §2): entity ids, the `Interner`, the closed `Node` enum, field-level `Binding`/`PortRef` dataflow, and `Program` — arenas over value-level signatures. ### Structs | Item | Description | | -------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | | [`AgentLoopNode`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/graph/struct.AgentLoopNode.html) | The LLM+tool loop as the first-class unit. | | [`Binding`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/graph/struct.Binding.html) | One field-level wire: `dst` input field (or exported name) fed from `src`. | | [`CapSet`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/graph/struct.CapSet.html) | Capability names: namespaced, colon-separated (`"net:search"`, `"fs:read"`). `BTreeSet`: set ops happen at load only, never on the hot path. | | [`ForkJoinNode`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/graph/struct.ForkJoinNode.html) | | | [`HoleNode`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/graph/struct.HoleNode.html) | LACUNA-style typed hole: opaque-but-typed code. The optimizer sees a signature (and, when sandboxed, a Code gene); the type system sees a normal node. | | [`Interner`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/graph/struct.Interner.html) | Graph-side string interner. Signatures speak strings (`SignatureDef` is constructible with zero context); the *graph* speaks `Sym`s. | | [`Lineage`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/graph/struct.Lineage.html) | | | [`LoopNode`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/graph/struct.LoopNode.html) | | | [`ModelDef`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/graph/struct.ModelDef.html) | `LMConfig` is already serde with `api_key` `#serde(skip)` — reused verbatim: model entries carry provider/URL/sampling only, never secrets. | | [`ModelId`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/graph/struct.ModelId.html) | Model reference id. Distinct from the trace format's per-trace `trace::ModelId` — this one indexes `Program::models`. | | [`NodeBudget`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/graph/struct.NodeBudget.html) | | | [`NodeId`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/graph/struct.NodeId.html) | | | [`PredictNode`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/graph/struct.PredictNode.html) | One LM call. No tools. `cot` in the surface syntax lowers to a Predict over `sig.augmented_with(reasoning)`. | | [`Program`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/graph/struct.Program.html) | A loaded IR program: arenas + interner + capability ceiling. | | [`ProgramMeta`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/graph/struct.ProgramMeta.html) | | | [`RefineNode`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/graph/struct.RefineNode.html) | | | [`RetryNode`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/graph/struct.RetryNode.html) | | | [`RouteNode`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/graph/struct.RouteNode.html) | | | [`SeqNode`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/graph/struct.SeqNode.html) | | | [`SigId`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/graph/struct.SigId.html) | | | [`StopSpec`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/graph/struct.StopSpec.html) | | | [`Sym`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/graph/struct.Sym.html) | Interned string: node names, field references in bindings, tool names. | | [`ToolDef`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/graph/struct.ToolDef.html) | | | [`ToolId`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/graph/struct.ToolId.html) | | ### Enums | Item | Description | | ---------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- | | [`BakeError`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/graph/enum.BakeError.html) | Why `Program::bake` refused. | | [`BudgetPolicy`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/graph/enum.BudgetPolicy.html) | | | [`HoleImpl`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/graph/enum.HoleImpl.html) | How a hole is implemented (RFC 0003 §4.1) — mirrors `ToolKind`. | | [`Node`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/graph/enum.Node.html) | The closed node vocabulary. `Predict` carries no tools — tool use is `AgentLoop`; `cot` is signature sugar, not a node kind. | | [`PortRef`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/graph/enum.PortRef.html) | | | [`ToolKind`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/graph/enum.ToolKind.html) | | ## `ir::interp` The IR interpreter (RFC 0002 §3): async evaluation of a loaded `Program`. ### Structs | Item | Description | | ----------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | [`Budget`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/interp/struct.Budget.html) | Run-level spend limits. `None` = unlimited. | | [`BudgetMeter`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/interp/struct.BudgetMeter.html) | Check-before-call metering: calls and deadline are hard-gated pre-call; token budgets are soft (checked against accumulated usage, since usage is only known post-hoc). | | [`Exhausted`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/interp/struct.Exhausted.html) | Budget reservation failure. | | [`Interpreter`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/interp/struct.Interpreter.html) | A loaded, executable program: validated graph + bound models/tools + registered sandbox code. Cheap to share; run state never lives here. | | [`LeafOutcome`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/interp/struct.LeafOutcome.html) | Parse/coercion metadata from one successful `Predict`-leaf evaluation, collected by `Interpreter::run_collecting` in execution order. | | [`RunOutput`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/interp/struct.RunOutput.html) | Program output plus per-leaf metadata, returned by `Interpreter::run_collecting`. | | [`RuntimeEnv`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/interp/struct.RuntimeEnv.html) | What the host supplies at load: live models, host tool bindings, the sandbox, and the capability grants. | | [`ToolSuspension`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/interp/struct.ToolSuspension.html) | A caller-managed agent turn suspended on pending tool calls (RFC 0004 §2). | ### Enums | Item | Description | | ------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- | | [`ConversationTurn`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/interp/enum.ConversationTurn.html) | One caller-driven conversation turn, returned by `Interpreter::run_conversation_caller_managed` and `Interpreter::resume_conversation`. | | [`LoadError`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/interp/enum.LoadError.html) | | | [`RunError`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/interp/enum.RunError.html) | | ### Functions | Item | Description | | --------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- | | [`input_schema_of`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/interp/fn.input_schema_of.html) | Projects a tool/hole signature's *input* side to a JSON Schema object — the declared interface is the schema the model sees. | ### Type aliases | Item | Description | | ------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | | [`HostHoleFn`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/interp/type.HostHoleFn.html) | A native extern-hole implementation: resolved input map in, JSON value out (coerced against the hole's output signature by the interpreter). | ## `ir::module_build` RFC 0003 stage M-3 library support: the module "linker". ### Structs | Item | Description | | --------------------------------------------------------------------------------------------- | ---------------------------------------- | | [`ModuleSpec`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/module_build/struct.ModuleSpec.html) | The parsed shape of a `#module` fn body. | | [`ModuleStep`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/module_build/struct.ModuleStep.html) | | ### Enums | Item | Description | | ------------------------------------------------------------------------------------------------------- | --------------------------------------------------------- | | [`ModuleBuildError`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/module_build/enum.ModuleBuildError.html) | Why a `#module` body could not be linked into a program. | | [`ModuleStepKind`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/module_build/enum.ModuleStepKind.html) | | | [`PortSpec`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/module_build/enum.PortSpec.html) | A macro-emitted port: name-based, resolved by the linker. | ### Functions | Item | Description | | ------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | [`build_module_program`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/module_build/fn.build_module_program.html) | Links a `ModuleSpec` into a validated `Program`. Deterministic: the same spec produces the same canonical text and therefore the same program hash. | | [`default_lm`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/module_build/fn.default_lm.html) | The globally-configured LM (`configure`), used by generated module code to bind the `default` model ref at load. | | [`unbound_model_config`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/module_build/fn.unbound_model_config.html) | The placeholder config a module program declares for model ref `name`. Loading it unbound fails loudly; the real client arrives by name through `RuntimeEnv::bind_model`. | ## `ir::params` Parameters (RFC 0002 §2.4): every mutable thing is a named, addressable slot; a candidate is an `Overlay` read through at render time, never a mutation. ### Structs | Item | Description | | --------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------- | | [`ContextPolicy`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/params/struct.ContextPolicy.html) | The open-lane optimizable slot (vision §6.6). Minimal v1; additive later. | | [`DemoRow`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/params/struct.DemoRow.html) | One few-shot demonstration row: input and output field maps. | | [`Overlay`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/params/struct.Overlay.html) | A candidate = dense data overlay over a fixed skeleton. Clone is a vec clone. | | [`ParamId`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/params/struct.ParamId.html) | | | [`ParamSlot`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/params/struct.ParamSlot.html) | One optimizable slot: canonical path, owner, kind, and the incumbent value. | | [`Slot`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/params/struct.Slot.html) | Typed slot handle: optimizer-side mutation mistakes are compile errors. | ### Enums | Item | Description | | ----------------------------------------------------------------------------------------- | ----------- | | [`CodeK`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/params/enum.CodeK.html) | | | [`CodeLang`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/params/enum.CodeLang.html) | | | [`ContextK`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/params/enum.ContextK.html) | | | [`Demos`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/params/enum.Demos.html) | | | [`Instruction`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/params/enum.Instruction.html) | | | [`ModelRefK`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/params/enum.ModelRefK.html) | | | [`OverlayError`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/params/enum.OverlayError.html) | | | [`ParamKind`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/params/enum.ParamKind.html) | | | [`ParamOwner`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/params/enum.ParamOwner.html) | | | [`ParamValue`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/params/enum.ParamValue.html) | | | [`ToolDesc`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/params/enum.ToolDesc.html) | | | [`ToolSetK`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/params/enum.ToolSetK.html) | | ### Traits | Item | Description | | -------------------------------------------------------------------------------- | ----------- | | [`KindTag`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/params/trait.KindTag.html) | | ### Functions | Item | Description | | --------------------------------------------------------------------------------- | -------------------------------------------- | | [`code_hash`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/params/fn.code_hash.html) | Stable content hash of a code gene's source. | ## `ir::step` RFC 0003 stage M-2: step metadata. ### Structs | Item | Description | | ------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- | | [`AgentStepOpts`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/step/struct.AgentStepOpts.html) | `#agent(...)` attribute options, mirroring the `AgentLoopNode` surface. | | [`HoleReport`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/step/struct.HoleReport.html) | One hole-ized expression in a `#module` body (RFC 0003 §6) — the opacity report entry. | | [`StepDef`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/step/struct.StepDef.html) | One step declaration — what a `#predict`/`#cot`/`#agent` fn *is*, as data. | | [`ToolStepDef`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/step/struct.ToolStepDef.html) | One `#tool` declaration: metadata plus the host implementation. | ### Enums | Item | Description | | ------------------------------------------------------------------------------- | ----------- | | [`StepKind`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/step/enum.StepKind.html) | | ## `ir::text` The `.dsrs` text format (RFC 0002 §4, stage IR-5). ### Re-exports | Item | Description | | --------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | | [`ParseError`](https://docs.rs/dspy-rs/latest/dsrs_syntax/struct.ParseError.html) | A parse failure with the source position and what was expected — designed to be actionable feedback for a model regenerating the program. | ### Enums | Item | Description | | ----------------------------------------------------------------------------------------- | -------------------------------------------------- | | [`DsrsFileError`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/text/enum.DsrsFileError.html) | Failure loading or saving a `.dsrs` artifact file. | ## `ir::validate` Load-time validation (RFC 0002 §2.3): builder `finish()`, the loader, and `Program::validate()` run the same code path. Nothing is checked lazily at call time. ### Enums | Item | Description | | --------------------------------------------------------------------------------------------- | ----------- | | [`ValidateError`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/validate/enum.ValidateError.html) | | # dspy_rs::modules Source: https://dsrs.herumbshandilya.com/docs/api/modules Public API of the modules module Generated from rustdoc JSON at `dspy-rs v0.7.3` (commit `f7d67a6`). Do not edit by hand; regenerate with `python3 docs/scripts/gen_api.py` (see the script header for the rustdoc commands). Item links lead to full signatures and method docs on docs.rs. ## Re-exports | Item | Description | | ------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------ | | [`ChainOfThought`](https://docs.rs/dspy-rs/latest/dspy_rs/modules/chain_of_thought/type.ChainOfThought.html) | Re-export of `chain_of_thought::ChainOfThought`. | | [`ChainOfThoughtOutput`](https://docs.rs/dspy-rs/latest/dspy_rs/modules/chain_of_thought/type.ChainOfThoughtOutput.html) | Re-export of `chain_of_thought::ChainOfThoughtOutput`. | | [`Reasoning`](https://docs.rs/dspy-rs/latest/dspy_rs/modules/chain_of_thought/struct.Reasoning.html) | Re-export of `chain_of_thought::Reasoning`. | | [`WithReasoning`](https://docs.rs/dspy-rs/latest/dspy_rs/modules/chain_of_thought/struct.WithReasoning.html) | Re-export of `chain_of_thought::WithReasoning`. | ## Modules | Item | Description | | ------------------------------------------------------------------------------------------------ | ----------- | | [`chain_of_thought`](https://docs.rs/dspy-rs/latest/dspy_rs/modules/chain_of_thought/index.html) | | ## `modules::chain_of_thought` ### Structs | Item | Description | | ------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------- | | [`Reasoning`](https://docs.rs/dspy-rs/latest/dspy_rs/modules/chain_of_thought/struct.Reasoning.html) | Augmentation that prepends a `reasoning: String` field to a signature's output. | | [`WithReasoning`](https://docs.rs/dspy-rs/latest/dspy_rs/modules/chain_of_thought/struct.WithReasoning.html) | | ### Type aliases | Item | Description | | ------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------- | | [`ChainOfThought`](https://docs.rs/dspy-rs/latest/dspy_rs/modules/chain_of_thought/type.ChainOfThought.html) | Asks the LM to reason step-by-step before producing the answer. | | [`ChainOfThoughtOutput`](https://docs.rs/dspy-rs/latest/dspy_rs/modules/chain_of_thought/type.ChainOfThoughtOutput.html) | Convenience alias for `ChainOfThought`'s output type. | # dspy_rs::optimizer Source: https://dsrs.herumbshandilya.com/docs/api/optimizer Automatic prompt optimization. Generated from rustdoc JSON at `dspy-rs v0.7.3` (commit `f7d67a6`). Do not edit by hand; regenerate with `python3 docs/scripts/gen_api.py` (see the script header for the rustdoc commands). Item links lead to full signatures and method docs on docs.rs. Automatic prompt optimization. ## Re-exports | Item | Description | | ------------------------------------------------------------------------------------------------------ | -------------------------------------- | | `bootstrap::*` | Glob re-export. | | `copro::*` | Glob re-export. | | `engine::*` | Glob re-export. | | `gepa::*` | Glob re-export. | | [`LeafInfo`](https://docs.rs/dspy-rs/latest/dspy_rs/optimizer/target/struct.LeafInfo.html) | Re-export of `target::LeafInfo`. | | `mipro::*` | Glob re-export. | | [`OptimizeTarget`](https://docs.rs/dspy-rs/latest/dspy_rs/optimizer/target/struct.OptimizeTarget.html) | Re-export of `target::OptimizeTarget`. | | [`ProgramMetric`](https://docs.rs/dspy-rs/latest/dspy_rs/optimizer/target/trait.ProgramMetric.html) | Re-export of `target::ProgramMetric`. | | `simba::*` | Glob re-export. | | `structural::*` | Glob re-export. | ## Modules | Item | Description | | -------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- | | [`bootstrap`](https://docs.rs/dspy-rs/latest/dspy_rs/optimizer/bootstrap/index.html) | BootstrapFewShot: the minimal end-to-end exercise of the optimizer contract (vision §5. | | [`copro`](https://docs.rs/dspy-rs/latest/dspy_rs/optimizer/copro/index.html) | | | [`engine`](https://docs.rs/dspy-rs/latest/dspy_rs/optimizer/engine/index.html) | The shared evaluation engine (vision §5.4): every optimizer is a thin strategy over this core. | | [`gepa`](https://docs.rs/dspy-rs/latest/dspy_rs/optimizer/gepa/index.html) | | | [`mipro`](https://docs.rs/dspy-rs/latest/dspy_rs/optimizer/mipro/index.html) | | | [`simba`](https://docs.rs/dspy-rs/latest/dspy_rs/optimizer/simba/index.html) | SIMBA: Stochastic Introspective Mini-Batch Ascent (vision §4.3) — the cheap agentic default. | | [`structural`](https://docs.rs/dspy-rs/latest/dspy_rs/optimizer/structural/index.html) | Structural: LM-guided hill-climbing over the graph-edit calculus (RFC 0004 §6) — the sixth strategy over the shared `Engine`. | | [`target`](https://docs.rs/dspy-rs/latest/dspy_rs/optimizer/target/index.html) | What an optimizer optimizes: `OptimizeTarget`, the lane-erased pair of (thing under optimization, evaluation harness). | ## Structs | Item | Description | | ------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | | [`OptimizerCommon`](https://docs.rs/dspy-rs/latest/dspy_rs/optimizer/struct.OptimizerCommon.html) | The engine/RNG knobs shared by every optimizer builder: evaluation concurrency, budget caps, cache salt, and the sampling seed. | ## Enums | Item | Description | | ----------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- | | [`Report`](https://docs.rs/dspy-rs/latest/dspy_rs/optimizer/enum.Report.html) | What an optimization run produced. Strategy-specific payloads for the optimizers that report more than "done". | ## Traits | Item | Description | | ------------------------------------------------------------------------------------ | ------------------------------------------- | | [`Optimizer`](https://docs.rs/dspy-rs/latest/dspy_rs/optimizer/trait.Optimizer.html) | A tuning strategy over the shared `Engine`. | ## `optimizer::bootstrap` BootstrapFewShot: the minimal end-to-end exercise of the optimizer contract (vision §5. ### Structs | Item | Description | | --------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------- | | [`BootstrapFewShot`](https://docs.rs/dspy-rs/latest/dspy_rs/optimizer/bootstrap/struct.BootstrapFewShot.html) | Few-shot demo bootstrapper — the simplest complete optimizer. | | [`BootstrapFewShotBuilder`](https://docs.rs/dspy-rs/latest/dspy_rs/optimizer/bootstrap/struct.BootstrapFewShotBuilder.html) | Use builder syntax to set the inputs and finish with `build()`). | | [`BootstrapReport`](https://docs.rs/dspy-rs/latest/dspy_rs/optimizer/bootstrap/struct.BootstrapReport.html) | What a `BootstrapFewShot` run did. | ## `optimizer::copro` ### Structs | Item | Description | | ------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------- | | [`COPRO`](https://docs.rs/dspy-rs/latest/dspy_rs/optimizer/copro/struct.COPRO.html) | Breadth-first instruction optimizer. | | [`COPROBuilder`](https://docs.rs/dspy-rs/latest/dspy_rs/optimizer/copro/struct.COPROBuilder.html) | Use builder syntax to set the inputs and finish with `build()`). | ## `optimizer::engine` The shared evaluation engine (vision §5.4): every optimizer is a thin strategy over this core. ### Structs | Item | Description | | ---------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- | | [`Budget`](https://docs.rs/dspy-rs/latest/dspy_rs/optimizer/engine/struct.Budget.html) | Hard caps on evaluation spend. `None` = unlimited. | | [`Candidate`](https://docs.rs/dspy-rs/latest/dspy_rs/optimizer/engine/struct.Candidate.html) | A candidate is *data*: name-keyed per-leaf overlays plus a stable content hash (`Candidate::stable_hash`). | | [`CandidateEval`](https://docs.rs/dspy-rs/latest/dspy_rs/optimizer/engine/struct.CandidateEval.html) | A candidate's results over one evaluation batch, in request order. | | [`CandidateSlot`](https://docs.rs/dspy-rs/latest/dspy_rs/optimizer/engine/struct.CandidateSlot.html) | One leaf's slice of a `Candidate`: which optimizable values to inject. | | [`Engine`](https://docs.rs/dspy-rs/latest/dspy_rs/optimizer/engine/struct.Engine.html) | The shared evaluation core (vision §5.4). | | [`EngineConfig`](https://docs.rs/dspy-rs/latest/dspy_rs/optimizer/engine/struct.EngineConfig.html) | Engine tuning knobs. | | [`ParetoStatistics`](https://docs.rs/dspy-rs/latest/dspy_rs/optimizer/engine/struct.ParetoStatistics.html) | Snapshot of the Pareto frontier at a point in the search. | | [`ParetoView`](https://docs.rs/dspy-rs/latest/dspy_rs/optimizer/engine/struct.ParetoView.html) | Dominance snapshot computed from a `ScoreMatrix`: which candidates win (or tie, within tolerance) on at least one example. | | [`RolloutCache`](https://docs.rs/dspy-rs/latest/dspy_rs/optimizer/engine/struct.RolloutCache.html) | In-memory rollout cache: `(baseline, candidate, example, salt)` → `Eval`. | | [`RolloutOutcome`](https://docs.rs/dspy-rs/latest/dspy_rs/optimizer/engine/struct.RolloutOutcome.html) | One evaluated (or cache-served) rollout. | | [`ScoreMatrix`](https://docs.rs/dspy-rs/latest/dspy_rs/optimizer/engine/struct.ScoreMatrix.html) | Per-instance score matrix: candidates × examples. | | [`Spend`](https://docs.rs/dspy-rs/latest/dspy_rs/optimizer/engine/struct.Spend.html) | What the engine has consumed so far. Reported to strategies. | ### Enums | Item | Description | | -------------------------------------------------------------------------------------------------------- | ----------------------------------- | | [`BatchEvalOutcome`](https://docs.rs/dspy-rs/latest/dspy_rs/optimizer/engine/enum.BatchEvalOutcome.html) | Result of `Engine::evaluate_many`. | | [`EvalOutcome`](https://docs.rs/dspy-rs/latest/dspy_rs/optimizer/engine/enum.EvalOutcome.html) | Result of `Engine::evaluate`. | | [`GateOutcome`](https://docs.rs/dspy-rs/latest/dspy_rs/optimizer/engine/enum.GateOutcome.html) | Result of `Engine::evaluate_gated`. | ## `optimizer::gepa` ### Structs | Item | Description | | ---------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------- | | [`GEPA`](https://docs.rs/dspy-rs/latest/dspy_rs/optimizer/gepa/struct.GEPA.html) | Genetic-Pareto instruction optimizer with feedback-driven evolution. | | [`GEPABuilder`](https://docs.rs/dspy-rs/latest/dspy_rs/optimizer/gepa/struct.GEPABuilder.html) | Use builder syntax to set the inputs and finish with `build()`). | | [`GEPACandidate`](https://docs.rs/dspy-rs/latest/dspy_rs/optimizer/gepa/struct.GEPACandidate.html) | A single instruction candidate tracked through GEPA's evolutionary search. | | [`GEPAResult`](https://docs.rs/dspy-rs/latest/dspy_rs/optimizer/gepa/struct.GEPAResult.html) | Full report from a `GEPA` optimization run. | | [`ReflectOnInstructionOutput`](https://docs.rs/dspy-rs/latest/dspy_rs/optimizer/gepa/struct.ReflectOnInstructionOutput.html) | | ## `optimizer::mipro` ### Structs | Item | Description | | ----------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------ | | [`MIPROv2`](https://docs.rs/dspy-rs/latest/dspy_rs/optimizer/mipro/struct.MIPROv2.html) | Trace-guided instruction and demo optimizer. | | [`MIPROv2Builder`](https://docs.rs/dspy-rs/latest/dspy_rs/optimizer/mipro/struct.MIPROv2Builder.html) | Use builder syntax to set the inputs and finish with `build()`). | | [`PromptingTips`](https://docs.rs/dspy-rs/latest/dspy_rs/optimizer/mipro/struct.PromptingTips.html) | Library of general prompting best practices used to seed candidate generation. | ## `optimizer::simba` SIMBA: Stochastic Introspective Mini-Batch Ascent (vision §4.3) — the cheap agentic default. ### Structs | Item | Description | | ------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------- | | [`IntrospectRolloutsOutput`](https://docs.rs/dspy-rs/latest/dspy_rs/optimizer/simba/struct.IntrospectRolloutsOutput.html) | | | [`SIMBA`](https://docs.rs/dspy-rs/latest/dspy_rs/optimizer/simba/struct.SIMBA.html) | Minibatch introspective ascent — the cheap agentic default (vision §4.3). | | [`SIMBABuilder`](https://docs.rs/dspy-rs/latest/dspy_rs/optimizer/simba/struct.SIMBABuilder.html) | Use builder syntax to set the inputs and finish with `build()`). | | [`SimbaReport`](https://docs.rs/dspy-rs/latest/dspy_rs/optimizer/simba/struct.SimbaReport.html) | What a `SIMBA` run did. | | [`SimbaStep`](https://docs.rs/dspy-rs/latest/dspy_rs/optimizer/simba/struct.SimbaStep.html) | What one SIMBA step did. | ### Enums | Item | Description | | ----------------------------------------------------------------------------------------- | ------------------------------------------- | | [`SimbaMove`](https://docs.rs/dspy-rs/latest/dspy_rs/optimizer/simba/enum.SimbaMove.html) | Which of SIMBA's two moves a step proposed. | ## `optimizer::structural` Structural: LM-guided hill-climbing over the graph-edit calculus (RFC 0004 §6) — the sixth strategy over the shared `Engine`. ### Structs | Item | Description | | ---------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------ | | [`ChooseEditOutput`](https://docs.rs/dspy-rs/latest/dspy_rs/optimizer/structural/struct.ChooseEditOutput.html) | | | [`Structural`](https://docs.rs/dspy-rs/latest/dspy_rs/optimizer/structural/struct.Structural.html) | Structural optimizer over the graph-edit calculus (RFC 0004 §6). | | [`StructuralBuilder`](https://docs.rs/dspy-rs/latest/dspy_rs/optimizer/structural/struct.StructuralBuilder.html) | Use builder syntax to set the inputs and finish with `build()`). | | [`StructuralReport`](https://docs.rs/dspy-rs/latest/dspy_rs/optimizer/structural/struct.StructuralReport.html) | What a `Structural` run did. The winner is returned, not installed: bake it (\`report.program.bake(\&report. | | [`StructuralStep`](https://docs.rs/dspy-rs/latest/dspy_rs/optimizer/structural/struct.StructuralStep.html) | What one Structural generation did. | ## `optimizer::target` What an optimizer optimizes: `OptimizeTarget`, the lane-erased pair of (thing under optimization, evaluation harness). ### Structs | Item | Description | | ------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------- | | [`LeafInfo`](https://docs.rs/dspy-rs/latest/dspy_rs/optimizer/target/struct.LeafInfo.html) | The read surface strategies build candidates from: one optimizable leaf's name, current values, and field contract. | | [`OptimizeTarget`](https://docs.rs/dspy-rs/latest/dspy_rs/optimizer/target/struct.OptimizeTarget.html) | The thing an `Optimizer` optimizes: a module or a program, packaged with its example set and metric. See the module docs for the two lanes. | ### Traits | Item | Description | | --------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | [`ProgramMetric`](https://docs.rs/dspy-rs/latest/dspy_rs/optimizer/target/trait.ProgramMetric.html) | How a program-lane strategy tells the engine what "good" means: score one interpreter output (`JsonMap` of the program's output signature fields) against a labeled example. | # dspy_rs::predictors Source: https://dsrs.herumbshandilya.com/docs/api/predictors Public API of the predictors module Generated from rustdoc JSON at `dspy-rs v0.7.3` (commit `f7d67a6`). Do not edit by hand; regenerate with `python3 docs/scripts/gen_api.py` (see the script header for the rustdoc commands). Item links lead to full signatures and method docs on docs.rs. ## Re-exports | Item | Description | | ------------ | --------------- | | `predict::*` | Glob re-export. | ## Modules | Item | Description | | --------------------------------------------------------------------------------- | ----------- | | [`predict`](https://docs.rs/dspy-rs/latest/dspy_rs/predictors/predict/index.html) | | ## `predictors::predict` ### Structs | Item | Description | | -------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------- | | [`AgentLoopSpec`](https://docs.rs/dspy-rs/latest/dspy_rs/predictors/predict/struct.AgentLoopSpec.html) | Loop options for a tooled predictor's 1-node `agent` program. | | [`Demo`](https://docs.rs/dspy-rs/latest/dspy_rs/predictors/predict/struct.Demo.html) | A typed input/output pair for few-shot prompting. | | [`Predict`](https://docs.rs/dspy-rs/latest/dspy_rs/predictors/predict/struct.Predict.html) | The leaf module. The only thing in the system that actually calls the LM. | | [`PredictBuilder`](https://docs.rs/dspy-rs/latest/dspy_rs/predictors/predict/struct.PredictBuilder.html) | Builder for `Predict` with demos, tools, and instruction override. | # dspy_rs::prelude Source: https://dsrs.herumbshandilya.com/docs/api/prelude The curated core surface — the recommended import for DSRs programs. Generated from rustdoc JSON at `dspy-rs v0.7.3` (commit `f7d67a6`). Do not edit by hand; regenerate with `python3 docs/scripts/gen_api.py` (see the script header for the rustdoc commands). Item links lead to full signatures and method docs on docs.rs. The curated core surface — the recommended import for DSRs programs. ## Re-exports | Item | Description | | -------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------- | | [`average_score`](https://docs.rs/dspy-rs/latest/dspy_rs/evaluate/evaluator/fn.average_score.html) | Re-export of `crate::evaluate::average_score`. | | [`BootstrapFewShot`](https://docs.rs/dspy-rs/latest/dspy_rs/optimizer/bootstrap/struct.BootstrapFewShot.html) | Re-export of `crate::optimizer::BootstrapFewShot`. | | [`CallMetadata`](https://docs.rs/dspy-rs/latest/dspy_rs/core/predicted/struct.CallMetadata.html) | Re-export of `crate::core::CallMetadata`. | | [`Candidate`](https://docs.rs/dspy-rs/latest/dspy_rs/optimizer/engine/struct.Candidate.html) | Re-export of `crate::optimizer::Candidate`. | | [`capture_with_meta`](https://docs.rs/dspy-rs/latest/dspy_rs/trace/capture/fn.capture_with_meta.html) | Re-export of `crate::trace::capture_with_meta`. | | [`capture`](https://docs.rs/dspy-rs/latest/dspy_rs/trace/capture/index.html) | Re-export of `crate::trace::capture`. | | [`capture`](https://docs.rs/dspy-rs/latest/dspy_rs/trace/capture/fn.capture.html) | Re-export of `crate::trace::capture`. | | [`ChainOfThought`](https://docs.rs/dspy-rs/latest/dspy_rs/modules/chain_of_thought/type.ChainOfThought.html) | Re-export of `crate::modules::ChainOfThought`. | | [`configure`](https://docs.rs/dspy-rs/latest/dspy_rs/core/settings/fn.configure.html) | Re-export of `crate::core::settings::configure`. | | [`COPRO`](https://docs.rs/dspy-rs/latest/dspy_rs/optimizer/copro/struct.COPRO.html) | Re-export of `crate::optimizer::COPRO`. | | [`DataLoader`](https://docs.rs/dspy-rs/latest/dspy_rs/data/dataloader/struct.DataLoader.html) | Re-export of `crate::data::dataloader::DataLoader`. | | [`Demo`](https://docs.rs/dspy-rs/latest/dspy_rs/predictors/predict/struct.Demo.html) | Re-export of `crate::predictors::Demo`. | | [`Edit`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/edit/enum.Edit.html) | Re-export of `crate::ir::Edit`. | | [`Engine`](https://docs.rs/dspy-rs/latest/dspy_rs/optimizer/engine/struct.Engine.html) | Re-export of `crate::optimizer::Engine`. | | [`Eval`](https://docs.rs/dspy-rs/latest/dspy_rs/trace/span/struct.Eval.html) | Re-export of `crate::trace::Eval`. | | [`evaluate_trainset_with_concurrency`](https://docs.rs/dspy-rs/latest/dspy_rs/evaluate/evaluator/fn.evaluate_trainset_with_concurrency.html) | Re-export of `crate::evaluate::evaluate_trainset_with_concurrency`. | | [`evaluate_trainset`](https://docs.rs/dspy-rs/latest/dspy_rs/evaluate/evaluator/fn.evaluate_trainset.html) | Re-export of `crate::evaluate::evaluate_trainset`. | | [`Example`](https://docs.rs/dspy-rs/latest/dsrs_macros/derive.Example.html) | Re-export of `dsrs_macros::Example`. | | [`GEPA`](https://docs.rs/dspy-rs/latest/dspy_rs/optimizer/gepa/struct.GEPA.html) | Re-export of `crate::optimizer::GEPA`. | | [`init_tracing`](https://docs.rs/dspy-rs/latest/dspy_rs/utils/telemetry/fn.init_tracing.html) | Re-export of `crate::utils::init_tracing`. | | [`Interpreter`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/interp/struct.Interpreter.html) | Re-export of `crate::ir::Interpreter`. | | [`is_capturing`](https://docs.rs/dspy-rs/latest/dspy_rs/trace/capture/fn.is_capturing.html) | Re-export of `crate::trace::is_capturing`. | | [`is_replaying`](https://docs.rs/dspy-rs/latest/dspy_rs/trace/replay/fn.is_replaying.html) | Re-export of `crate::trace::is_replaying`. | | [`LM`](https://docs.rs/dspy-rs/latest/dspy_rs/core/lm/struct.LM.html) | Re-export of `crate::core::lm::LM`. | | [`LMConfig`](https://docs.rs/dspy-rs/latest/dspy_rs/core/lm/struct.LMConfig.html) | Re-export of `crate::core::lm::LMConfig`. | | [`MIPROv2`](https://docs.rs/dspy-rs/latest/dspy_rs/optimizer/mipro/struct.MIPROv2.html) | Re-export of `crate::optimizer::MIPROv2`. | | [`Module`](https://docs.rs/dspy-rs/latest/dspy_rs/core/module/trait.Module.html) | Re-export of `crate::core::Module`. | | [`ModuleState`](https://docs.rs/dspy-rs/latest/dspy_rs/core/state/struct.ModuleState.html) | Re-export of `crate::core::ModuleState`. | | [`Optimizer`](https://docs.rs/dspy-rs/latest/dspy_rs/optimizer/trait.Optimizer.html) | Re-export of `crate::optimizer::Optimizer`. | | [`OptimizeTarget`](https://docs.rs/dspy-rs/latest/dspy_rs/optimizer/target/struct.OptimizeTarget.html) | Re-export of `crate::optimizer::OptimizeTarget`. | | [`Overlay`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/params/struct.Overlay.html) | Re-export of `crate::ir::Overlay`. | | [`Predict`](https://docs.rs/dspy-rs/latest/dspy_rs/predictors/predict/struct.Predict.html) | Re-export of `crate::predictors::Predict`. | | [`Predicted`](https://docs.rs/dspy-rs/latest/dspy_rs/core/predicted/struct.Predicted.html) | Re-export of `crate::core::Predicted`. | | [`PredictError`](https://docs.rs/dspy-rs/latest/dspy_rs/core/errors/enum.PredictError.html) | Re-export of `crate::core::PredictError`. | | [`Predictors`](https://docs.rs/dspy-rs/latest/dspy_rs/core/module/trait.Predictors.html) | Re-export of `crate::core::Predictors`. | | [`predictors`](https://docs.rs/dspy-rs/latest/dspy_rs/predictors/index.html) | Re-export of `crate::predictors`. | | [`predictors`](https://docs.rs/dspy-rs/latest/dspy_rs/macro.predictors.html) | Re-export of `crate::predictors`. | | [`PredictState`](https://docs.rs/dspy-rs/latest/dspy_rs/core/state/struct.PredictState.html) | Re-export of `crate::core::PredictState`. | | [`Program`](https://docs.rs/dspy-rs/latest/dspy_rs/ir/graph/struct.Program.html) | Re-export of `crate::ir::Program`. | | [`replay`](https://docs.rs/dspy-rs/latest/dspy_rs/trace/replay/index.html) | Re-export of `crate::trace::replay`. | | [`replay`](https://docs.rs/dspy-rs/latest/dspy_rs/trace/replay/fn.replay.html) | Re-export of `crate::trace::replay`. | | [`ReplayMode`](https://docs.rs/dspy-rs/latest/dspy_rs/trace/replay/enum.ReplayMode.html) | Re-export of `crate::trace::ReplayMode`. | | [`ReplayReport`](https://docs.rs/dspy-rs/latest/dspy_rs/trace/replay/struct.ReplayReport.html) | Re-export of `crate::trace::ReplayReport`. | | [`Signature`](https://docs.rs/dspy-rs/latest/dspy_rs/core/signature/trait.Signature.html) | Re-export of `crate::core::signature::Signature`. | | [`Signature`](https://docs.rs/dspy-rs/latest/dsrs_macros/derive.Signature.html) | Re-export of `dsrs_macros::Signature`. | | [`SIMBA`](https://docs.rs/dspy-rs/latest/dspy_rs/optimizer/simba/struct.SIMBA.html) | Re-export of `crate::optimizer::SIMBA`. | | [`SpanId`](https://docs.rs/dspy-rs/latest/dspy_rs/trace/span/struct.SpanId.html) | Re-export of `crate::trace::SpanId`. | | [`Structural`](https://docs.rs/dspy-rs/latest/dspy_rs/optimizer/structural/struct.Structural.html) | Re-export of `crate::optimizer::Structural`. | | [`Trace`](https://docs.rs/dspy-rs/latest/dspy_rs/trace/span/struct.Trace.html) | Re-export of `crate::trace::Trace`. | | [`TypedLoadOptions`](https://docs.rs/dspy-rs/latest/dspy_rs/data/dataloader/struct.TypedLoadOptions.html) | Re-export of `crate::data::dataloader::TypedLoadOptions`. | | [`TypedMetric`](https://docs.rs/dspy-rs/latest/dspy_rs/evaluate/evaluator/trait.TypedMetric.html) | Re-export of `crate::evaluate::TypedMetric`. | | [`WithReasoning`](https://docs.rs/dspy-rs/latest/dspy_rs/modules/chain_of_thought/struct.WithReasoning.html) | Re-export of `crate::modules::WithReasoning`. | # dspy_rs::trace Source: https://dsrs.herumbshandilya.com/docs/api/trace Execution trace capture (RFC 0001). Generated from rustdoc JSON at `dspy-rs v0.7.3` (commit `f7d67a6`). Do not edit by hand; regenerate with `python3 docs/scripts/gen_api.py` (see the script header for the rustdoc commands). Item links lead to full signatures and method docs on docs.rs. Execution trace capture (RFC 0001). ## Re-exports | Item | Description | | ------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------- | | `capture::*` | Glob re-export. | | [`is_replaying`](https://docs.rs/dspy-rs/latest/dspy_rs/trace/replay/fn.is_replaying.html) | Re-export of `replay::is_replaying`. | | [`replay`](https://docs.rs/dspy-rs/latest/dspy_rs/trace/replay/fn.replay.html) | Re-export of `replay::replay`. | | [`ReplayError`](https://docs.rs/dspy-rs/latest/dspy_rs/trace/replay/enum.ReplayError.html) | Re-export of `replay::ReplayError`. | | [`ReplayMode`](https://docs.rs/dspy-rs/latest/dspy_rs/trace/replay/enum.ReplayMode.html) | Re-export of `replay::ReplayMode`. | | [`ReplayReport`](https://docs.rs/dspy-rs/latest/dspy_rs/trace/replay/struct.ReplayReport.html) | Re-export of `replay::ReplayReport`. | | `span::*` | Glob re-export. | | [`TRACE_FORMAT_VERSION`](https://docs.rs/dspy-rs/latest/dspy_rs/trace/serialize/constant.TRACE_FORMAT_VERSION.html) | Re-export of `serialize::TRACE_FORMAT_VERSION`. | ## Modules | Item | Description | | -------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------- | | [`capture`](https://docs.rs/dspy-rs/latest/dspy_rs/trace/capture/index.html) | Task-local capture scope for the unified trace format. | | [`replay`](https://docs.rs/dspy-rs/latest/dspy_rs/trace/replay/index.html) | Replay scope: serve `Predict` calls from a recorded `Trace` (RFC 0001 §4d/§4e). | | [`serialize`](https://docs.rs/dspy-rs/latest/dspy_rs/trace/serialize/index.html) | JSONL wire format for `Trace`: header line, span lines, optional footer. | | [`span`](https://docs.rs/dspy-rs/latest/dspy_rs/trace/span/index.html) | The unified trace format (RFC 0001): one `Span` per `Predict` invocation, one `Trace` per rollout. | ## `trace::capture` Task-local capture scope for the unified trace format. ### Structs | Item | Description | | --------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | [`SpanGuard`](https://docs.rs/dspy-rs/latest/dspy_rs/trace/capture/struct.SpanGuard.html) | Owns a `SpanId` — event/close attribution travels with the guard, never through "innermost open span" lookups, so interleaved `Predict`s on one task (`futures::join!`) cannot cross... | | [`SpanOutcome`](https://docs.rs/dspy-rs/latest/dspy_rs/trace/capture/struct.SpanOutcome.html) | Everything recorded lazily at span close. | | [`SpanRequest`](https://docs.rs/dspy-rs/latest/dspy_rs/trace/capture/struct.SpanRequest.html) | Everything recorded eagerly at span open. | | [`TraceSink`](https://docs.rs/dspy-rs/latest/dspy_rs/trace/capture/struct.TraceSink.html) | Shared handle to an in-progress trace. | ### Functions | Item | Description | | ----------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | | [`begin_span`](https://docs.rs/dspy-rs/latest/dspy_rs/trace/capture/fn.begin_span.html) | Opens a span in the active capture scope. Returns `None` when no scope is active — the caller does nothing else in that case (zero-overhead path). | | [`capture_with_meta`](https://docs.rs/dspy-rs/latest/dspy_rs/trace/capture/fn.capture_with_meta.html) | `capture` with caller-provided rollout metadata (input, candidate hash, tags). Missing `trace_id`/`started_at_us` are minted at scope start. | | [`capture`](https://docs.rs/dspy-rs/latest/dspy_rs/trace/capture/fn.capture.html) | Runs `f` while recording every `Predict` call on this task into a `Trace`. | | [`is_capturing`](https://docs.rs/dspy-rs/latest/dspy_rs/trace/capture/fn.is_capturing.html) | Returns `true` if the current task is inside a `capture` scope. | ## `trace::replay` Replay scope: serve `Predict` calls from a recorded `Trace` (RFC 0001 §4d/§4e). ### Structs | Item | Description | | ---------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------- | | [`ReplayReport`](https://docs.rs/dspy-rs/latest/dspy_rs/trace/replay/struct.ReplayReport.html) | What a replay scope did, returned by `replay` alongside the closure's result. | ### Enums | Item | Description | | ------------------------------------------------------------------------------------------ | ------------------------------------------------------------------- | | [`ReplayError`](https://docs.rs/dspy-rs/latest/dspy_rs/trace/replay/enum.ReplayError.html) | Why a call could not be served from the recording. | | [`ReplayMode`](https://docs.rs/dspy-rs/latest/dspy_rs/trace/replay/enum.ReplayMode.html) | How a replay scope treats a call that does not match its recording. | ### Functions | Item | Description | | ------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | [`is_replaying`](https://docs.rs/dspy-rs/latest/dspy_rs/trace/replay/fn.is_replaying.html) | Returns `true` if the current task is inside a `replay` scope. | | [`replay`](https://docs.rs/dspy-rs/latest/dspy_rs/trace/replay/fn.replay.html) | Runs `f` with `trace` as the canned-response source for every `Predict` call on this task. Returns the closure's result and a `ReplayReport` of what was served versus live. | ## `trace::serialize` JSONL wire format for `Trace`: header line, span lines, optional footer. ### Constants | Item | Description | | ------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------- | | [`TRACE_FORMAT_VERSION`](https://docs.rs/dspy-rs/latest/dspy_rs/trace/serialize/constant.TRACE_FORMAT_VERSION.html) | Highest format version this reader understands. | ## `trace::span` The unified trace format (RFC 0001): one `Span` per `Predict` invocation, one `Trace` per rollout. ### Structs | Item | Description | | -------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | [`CompId`](https://docs.rs/dspy-rs/latest/dspy_rs/trace/span/struct.CompId.html) | Per-trace interned component index. The component *name* is the same string the params system addresses: an fx slot name (`"drafter"`) or a facet dotted path (`"pipeline.rater"`). | | [`Eval`](https://docs.rs/dspy-rs/latest/dspy_rs/trace/span/struct.Eval.html) | The metric result type: one score, optional textual feedback. | | [`ModelEntry`](https://docs.rs/dspy-rs/latest/dspy_rs/trace/span/struct.ModelEntry.html) | Interned model configuration: `LMConfig` minus live state, minus secrets. | | [`ModelId`](https://docs.rs/dspy-rs/latest/dspy_rs/trace/span/struct.ModelId.html) | Per-trace interned model configuration. | | [`PrefixEntry`](https://docs.rs/dspy-rs/latest/dspy_rs/trace/span/struct.PrefixEntry.html) | | | [`PrefixId`](https://docs.rs/dspy-rs/latest/dspy_rs/trace/span/struct.PrefixId.html) | Per-trace interned prompt prefix (system message + demo turns). One entry per distinct (component, candidate) configuration — demos serialize once per trace, not once per span. | | [`Span`](https://docs.rs/dspy-rs/latest/dspy_rs/trace/span/struct.Span.html) | One `Predict` invocation: one rendered prompt in, one parsed output out, with the tool loop's inner structure as ordered events. | | [`SpanError`](https://docs.rs/dspy-rs/latest/dspy_rs/trace/span/struct.SpanError.html) | | | [`SpanId`](https://docs.rs/dspy-rs/latest/dspy_rs/trace/span/struct.SpanId.html) | Index of a span within its trace. Dense, assigned in insertion order. | | [`Trace`](https://docs.rs/dspy-rs/latest/dspy_rs/trace/span/struct.Trace.html) | One rollout: ordered spans plus per-trace intern tables. | | [`TraceMeta`](https://docs.rs/dspy-rs/latest/dspy_rs/trace/span/struct.TraceMeta.html) | | | [`TraceOutcome`](https://docs.rs/dspy-rs/latest/dspy_rs/trace/span/struct.TraceOutcome.html) | | ### Enums | Item | Description | | -------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- | | [`SpanErrorKind`](https://docs.rs/dspy-rs/latest/dspy_rs/trace/span/enum.SpanErrorKind.html) | | | [`SpanEvent`](https://docs.rs/dspy-rs/latest/dspy_rs/trace/span/enum.SpanEvent.html) | Ordered events inside a span. Tagged; readers skip unknown tags — this is the streaming seam (RFC 0001 §6). | ### Type aliases | Item | Description | | -------------------------------------------------------------------------------- | ----------- | | [`JsonMap`](https://docs.rs/dspy-rs/latest/dspy_rs/trace/span/type.JsonMap.html) | | # dspy_rs::typesys Source: https://dsrs.herumbshandilya.com/docs/api/typesys In-house type system that replaces the vendored BAML stack (`bamltype`, `baml_types`, `internal_baml_jinja`, `jsonish`). Generated from rustdoc JSON at `dspy-rs v0.7.3` (commit `f7d67a6`). Do not edit by hand; regenerate with `python3 docs/scripts/gen_api.py` (see the script header for the rustdoc commands). Item links lead to full signatures and method docs on docs.rs. In-house type system that replaces the vendored BAML stack (`bamltype`, `baml_types`, `internal_baml_jinja`, `jsonish`). ## Re-exports | Item | Description | | ------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------- | | [`ClassDef`](https://docs.rs/dspy-rs/latest/dspy_rs/typesys/schema/struct.ClassDef.html) | Re-export of `schema::ClassDef`. | | [`coerce`](https://docs.rs/dspy-rs/latest/dspy_rs/typesys/coerce/fn.coerce.html) | Re-export of `coerce::coerce`. | | [`Coerced`](https://docs.rs/dspy-rs/latest/dspy_rs/typesys/coerce/struct.Coerced.html) | Re-export of `coerce::Coerced`. | | [`Constraint`](https://docs.rs/dspy-rs/latest/dspy_rs/typesys/constraint/struct.Constraint.html) | Re-export of `constraint::Constraint`. | | [`ConstraintKind`](https://docs.rs/dspy-rs/latest/dspy_rs/typesys/constraint/enum.ConstraintKind.html) | Re-export of `constraint::ConstraintKind`. | | [`ConstraintLevel`](https://docs.rs/dspy-rs/latest/dspy_rs/typesys/constraint/type.ConstraintLevel.html) | Re-export of `constraint::ConstraintLevel`. | | [`EnumDef`](https://docs.rs/dspy-rs/latest/dspy_rs/typesys/schema/struct.EnumDef.html) | Re-export of `schema::EnumDef`. | | [`EnumValueDef`](https://docs.rs/dspy-rs/latest/dspy_rs/typesys/schema/struct.EnumValueDef.html) | Re-export of `schema::EnumValueDef`. | | [`evaluate_expression`](https://docs.rs/dspy-rs/latest/dspy_rs/typesys/constraint/fn.evaluate_expression.html) | Re-export of `constraint::evaluate_expression`. | | [`field_type_from_shape`](https://docs.rs/dspy-rs/latest/dspy_rs/typesys/schema/fn.field_type_from_shape.html) | Re-export of `schema::field_type_from_shape`. | | [`FieldDef`](https://docs.rs/dspy-rs/latest/dspy_rs/typesys/schema/struct.FieldDef.html) | Re-export of `schema::FieldDef`. | | [`FieldType`](https://docs.rs/dspy-rs/latest/dspy_rs/typesys/schema/enum.FieldType.html) | Re-export of `schema::FieldType`. | | [`Flag`](https://docs.rs/dspy-rs/latest/dspy_rs/typesys/coerce/enum.Flag.html) | Re-export of `coerce::Flag`. | | [`internal_name_for_shape`](https://docs.rs/dspy-rs/latest/dspy_rs/typesys/schema/fn.internal_name_for_shape.html) | Re-export of `schema::internal_name_for_shape`. | | [`OutputSchema`](https://docs.rs/dspy-rs/latest/dspy_rs/typesys/schema/struct.OutputSchema.html) | Re-export of `schema::OutputSchema`. | | [`schema_block`](https://docs.rs/dspy-rs/latest/dspy_rs/typesys/render/fn.schema_block.html) | Re-export of `render::schema_block`. | | [`Schema`](https://docs.rs/dspy-rs/latest/dspy_rs/typesys/schema/trait.Schema.html) | Re-export of `schema::Schema`. | | [`type_name`](https://docs.rs/dspy-rs/latest/dspy_rs/typesys/render/fn.type_name.html) | Re-export of `render::type_name`. | | [`TypeTable`](https://docs.rs/dspy-rs/latest/dspy_rs/typesys/schema/struct.TypeTable.html) | Re-export of `schema::TypeTable`. | ## Modules | Item | Description | | ------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------- | | [`coerce`](https://docs.rs/dspy-rs/latest/dspy_rs/typesys/coerce/index.html) | Tolerant coercion of raw LM field text into `serde_json::Value`, guided by `FieldType`. | | [`constraint`](https://docs.rs/dspy-rs/latest/dspy_rs/typesys/constraint/index.html) | In-house constraint model + evaluation, replacing BAML's `Constraint` / `run_user_checks`. | | [`render`](https://docs.rs/dspy-rs/latest/dspy_rs/typesys/render/index.html) | Prompt rendering for the in-house type model, replacing BAML's jinja `OutputFormatContent::render` and the adapter's backtick-token pipeline. | | [`schema`](https://docs.rs/dspy-rs/latest/dspy_rs/typesys/schema/index.html) | In-house type model that replaces BAML's `TypeIR` / `OutputFormatContent`. | ## Functions | Item | Description | | ------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------- | | [`format_value`](https://docs.rs/dspy-rs/latest/dspy_rs/typesys/fn.format_value.html) | Renders a value for an input field, honoring an explicit `#format(...)` hint. | ## `typesys::coerce` Tolerant coercion of raw LM field text into `serde_json::Value`, guided by `FieldType`. ### Structs | Item | Description | | -------------------------------------------------------------------------------------- | --------------------------------------------------- | | [`Coerced`](https://docs.rs/dspy-rs/latest/dspy_rs/typesys/coerce/struct.Coerced.html) | The result of coercing raw text into a typed value. | ### Enums | Item | Description | | ------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------- | | [`Flag`](https://docs.rs/dspy-rs/latest/dspy_rs/typesys/coerce/enum.Flag.html) | A non-fatal observation made while coercing a value (e.g. a code fence was stripped). | ### Functions | Item | Description | | -------------------------------------------------------------------------------- | --------------------------------------------------------------- | | [`coerce`](https://docs.rs/dspy-rs/latest/dspy_rs/typesys/coerce/fn.coerce.html) | Coerces `raw` into a `serde_json::Value` matching `field_type`. | ## `typesys::constraint` In-house constraint model + evaluation, replacing BAML's `Constraint` / `run_user_checks`. ### Structs | Item | Description | | ------------------------------------------------------------------------------------------------ | ----------------------------------------------------------- | | [`Constraint`](https://docs.rs/dspy-rs/latest/dspy_rs/typesys/constraint/struct.Constraint.html) | A single `#check`/`#assert` constraint attached to a field. | ### Enums | Item | Description | | ------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------- | | [`ConstraintKind`](https://docs.rs/dspy-rs/latest/dspy_rs/typesys/constraint/enum.ConstraintKind.html) | Whether a constraint is a soft `check` (reported) or a hard `assert` (fails the call). | ### Functions | Item | Description | | ------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------- | | [`evaluate_constraint_expression`](https://docs.rs/dspy-rs/latest/dspy_rs/typesys/constraint/fn.evaluate_constraint_expression.html) | Evaluates a `'static` constraint expression against `value`, compiling it at most once per process. | | [`evaluate_expression`](https://docs.rs/dspy-rs/latest/dspy_rs/typesys/constraint/fn.evaluate_expression.html) | Evaluates a runtime (non-`'static`) constraint expression against `value`, binding it as `this`. | ### Type aliases | Item | Description | | -------------------------------------------------------------------------------------------------------- | ------------------------------------------ | | [`ConstraintLevel`](https://docs.rs/dspy-rs/latest/dspy_rs/typesys/constraint/type.ConstraintLevel.html) | Back-compat alias for the old public name. | ## `typesys::render` Prompt rendering for the in-house type model, replacing BAML's jinja `OutputFormatContent::render` and the adapter's backtick-token pipeline. ### Functions | Item | Description | | -------------------------------------------------------------------------------------------- | --------------------------------------------------- | | [`schema_block`](https://docs.rs/dspy-rs/latest/dspy_rs/typesys/render/fn.schema_block.html) | Renders the expanded schema block for a field type. | | [`type_name`](https://docs.rs/dspy-rs/latest/dspy_rs/typesys/render/fn.type_name.html) | Renders the short, inline type label for a field. | ## `typesys::schema` In-house type model that replaces BAML's `TypeIR` / `OutputFormatContent`. ### Structs | Item | Description | | ------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | [`ClassDef`](https://docs.rs/dspy-rs/latest/dspy_rs/typesys/schema/struct.ClassDef.html) | A struct definition reachable from a signature's output type. | | [`EnumDef`](https://docs.rs/dspy-rs/latest/dspy_rs/typesys/schema/struct.EnumDef.html) | A unit-enum definition reachable from a signature's output type. | | [`EnumValueDef`](https://docs.rs/dspy-rs/latest/dspy_rs/typesys/schema/struct.EnumValueDef.html) | A single value of a unit enum. | | [`FieldDef`](https://docs.rs/dspy-rs/latest/dspy_rs/typesys/schema/struct.FieldDef.html) | A single field inside a `ClassDef`. | | [`OutputSchema`](https://docs.rs/dspy-rs/latest/dspy_rs/typesys/schema/struct.OutputSchema.html) | The full type description for a value: the root `FieldType` plus the `TypeTable` of every class/enum definition it references. Replaces BAML's `OutputFormatContent`. | | [`TypeTable`](https://docs.rs/dspy-rs/latest/dspy_rs/typesys/schema/struct.TypeTable.html) | Owned registry of the class/enum definitions reachable from a signature (RFC 0002 §1.3). | ### Enums | Item | Description | | ---------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- | | [`FieldType`](https://docs.rs/dspy-rs/latest/dspy_rs/typesys/schema/enum.FieldType.html) | Structural type of a signature/nested field, mirroring the subset of BAML's `TypeIR` that DSRs actually uses. | ### Traits | Item | Description | | ----------------------------------------------------------------------------------- | --------------------------------------------------------------------------------- | | [`Schema`](https://docs.rs/dspy-rs/latest/dspy_rs/typesys/schema/trait.Schema.html) | Runtime trait exposing the type model + serde-backed value conversion for a type. | ### Functions | Item | Description | | ------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------- | | [`field_type_from_shape`](https://docs.rs/dspy-rs/latest/dspy_rs/typesys/schema/fn.field_type_from_shape.html) | Builds just the `FieldType` for a shape, discarding the class/enum registry. | | [`internal_name_for_shape`](https://docs.rs/dspy-rs/latest/dspy_rs/typesys/schema/fn.internal_name_for_shape.html) | Computes the internal name for a shape: module path + type identifier when available. | # dspy_rs::utils Source: https://dsrs.herumbshandilya.com/docs/api/utils LM response caching. Generated from rustdoc JSON at `dspy-rs v0.7.3` (commit `f7d67a6`). Do not edit by hand; regenerate with `python3 docs/scripts/gen_api.py` (see the script header for the rustdoc commands). Item links lead to full signatures and method docs on docs.rs. LM response caching. ## Re-exports | Item | Description | | ----------------------------------------------------------------------------------------------------------- | --------------------------------------------- | | [`CacheEntry`](https://docs.rs/dspy-rs/latest/dspy_rs/utils/cache/struct.CacheEntry.html) | Re-export of `cache::CacheEntry`. | | [`CacheKey`](https://docs.rs/dspy-rs/latest/dspy_rs/utils/cache/type.CacheKey.html) | Re-export of `cache::CacheKey`. | | [`init_tracing`](https://docs.rs/dspy-rs/latest/dspy_rs/utils/telemetry/fn.init_tracing.html) | Re-export of `telemetry::init_tracing`. | | [`ResponseCache`](https://docs.rs/dspy-rs/latest/dspy_rs/utils/cache/struct.ResponseCache.html) | Re-export of `cache::ResponseCache`. | | [`TelemetryInitError`](https://docs.rs/dspy-rs/latest/dspy_rs/utils/telemetry/enum.TelemetryInitError.html) | Re-export of `telemetry::TelemetryInitError`. | | [`truncate`](https://docs.rs/dspy-rs/latest/dspy_rs/utils/telemetry/fn.truncate.html) | Re-export of `telemetry::truncate`. | ## Modules | Item | Description | | -------------------------------------------------------------------------------- | ----------------------------------------------------------------- | | [`cache`](https://docs.rs/dspy-rs/latest/dspy_rs/utils/cache/index.html) | | | [`hash`](https://docs.rs/dspy-rs/latest/dspy_rs/utils/hash/index.html) | Stable 64-bit hashing for trace request hashes and LM cache keys. | | [`telemetry`](https://docs.rs/dspy-rs/latest/dspy_rs/utils/telemetry/index.html) | | ## `utils::cache` ### Structs | Item | Description | | ----------------------------------------------------------------------------------------------- | --------------------------------------- | | [`CacheEntry`](https://docs.rs/dspy-rs/latest/dspy_rs/utils/cache/struct.CacheEntry.html) | A cached prompt-response pair. | | [`ResponseCache`](https://docs.rs/dspy-rs/latest/dspy_rs/utils/cache/struct.ResponseCache.html) | Hybrid memory + disk LM response cache. | ### Type aliases | Item | Description | | ----------------------------------------------------------------------------------- | -------------------------------------------------------------------------- | | [`CacheKey`](https://docs.rs/dspy-rs/latest/dspy_rs/utils/cache/type.CacheKey.html) | Response-cache key: a 64-bit hash over the prompt + generation parameters. | ## `utils::hash` Stable 64-bit hashing for trace request hashes and LM cache keys. ### Structs | Item | Description | | -------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | | [`HashWriter`](https://docs.rs/dspy-rs/latest/dspy_rs/utils/hash/struct.HashWriter.html) | Adapts a `Hasher` into a `std::fmt::Write` sink so values can be hashed through their `Debug`/`Display` representation without materializing a string. | | [`StableHasher`](https://docs.rs/dspy-rs/latest/dspy_rs/utils/hash/struct.StableHasher.html) | FNV-1a 64-bit hasher: deterministic across platforms and Rust versions. | ### Functions | Item | Description | | -------------------------------------------------------------------------------------------------- | -------------------------------------------------------- | | [`stable_hash_debug`](https://docs.rs/dspy-rs/latest/dspy_rs/utils/hash/fn.stable_hash_debug.html) | Hashes a `Debug`-formatted value with the stable hasher. | ## `utils::telemetry` ### Enums | Item | Description | | ----------------------------------------------------------------------------------------------------------- | ----------- | | [`TelemetryInitError`](https://docs.rs/dspy-rs/latest/dspy_rs/utils/telemetry/enum.TelemetryInitError.html) | | ### Functions | Item | Description | | --------------------------------------------------------------------------------------------- | -------------------------------------------------------- | | [`init_tracing`](https://docs.rs/dspy-rs/latest/dspy_rs/utils/telemetry/fn.init_tracing.html) | Installs process-global, pretty tracing output for DSRs. | | [`truncate`](https://docs.rs/dspy-rs/latest/dspy_rs/utils/telemetry/fn.truncate.html) | | # Adapters Source: https://dsrs.herumbshandilya.com/docs/components/adapters Turn signatures into prompts and parse LM responses An adapter sits between a signature and the LM: it formats inputs into the prompt the model sees and parses the LM's response back into typed outputs. The default adapter is `ChatAdapter`, and [Predict](/docs/components/predict) drives it for you (via the IR interpreter), so you touch it directly only to inspect what the model sees. ## What adapters do ``` SignatureDef + Input → Adapter.format() → Prompt for LM LM Response → Adapter.parse() → Typed Output ``` `ChatAdapter` handles both formatting and parsing. It has one lane: every method is parameterized by an owned [`SignatureDef`](/docs/components/program-and-nodes) — the IR's value-level signature — whether the def came from a derived `Signature` type (`SignatureDef::of::()`) or from a loaded `.dsrs` program. There is no separate "static" formatting path. ## ChatAdapter ### Prompt structure The system message has four parts (in this order): 1. **Field descriptions** - Lists input/output fields with types and descriptions 2. **Field structure** - Shows the marker format + type schemas 3. **Response instructions** - "Respond with `[[ ## field ## ]]`..." 4. **Task description** - Your instruction from the signature docstring (at the **end**) Example system message (simplified): ``` Your input fields are: 1. `question` (string): The question to answer Your output fields are: 1. `answer` (string): A clear, direct answer All interactions will be structured in the following way... [[ ## question ## ]] question [[ ## answer ## ]] Output field `answer` should be of type: string [[ ## completed ## ]] Respond with the corresponding output fields, starting with `[[ ## answer ## ]]`, and then ending with the marker for `[[ ## completed ## ]]`. In adhering to this structure, your objective is: Answer questions accurately and concisely. ``` ### Marker protocol Fields are delimited by markers: ``` [[ ## field_name ## ]] value here [[ ## another_field ## ]] another value [[ ## completed ## ]] ``` This protocol (inspired by DSPy) allows mixing natural language with structured output. ### Type schemas For complex types, the adapter renders full schemas with `typesys::render`: `type_name` produces the short inline label, and `schema_block` expands class field layouts and enum value lists: ``` [[ ## analysis ## ]] Output field `analysis` should be of type: Analysis Definitions (used below): Sentiment ---- - Positive - Negative - Neutral { "sentiment": Sentiment, "confidence": float, "keywords": string[] } ``` Type names are simplified for readability: * Module paths stripped: `my_crate::Sentiment` → `Sentiment` * `class`/`enum` prefixes removed * `" | "` → `" or "` (more natural) ### Tolerant parsing with typesys::coerce Response parsing uses `typesys::coerce`, a tolerant coercer that turns raw LM field text into typed values, guided by the expected field type. It handles: * **Markdown fences** - Strips \`\`\`json fences before parsing * **Lists from prose** - Parses bulleted or numbered lists into arrays * **Type coercion** - `"42"` → `42`, `"yes"` → `true` (loose bool and number spellings) * **Surrounding text** - Ignores extra prose around a JSON object or array * **Partial recovery** - Per-field failures aggregate; you get every error plus what did parse ```rust theme={null} // Internally, per output field: typesys::coerce(raw_text, field_type, &output_schema.types) ``` Each non-fatal fix (a stripped fence, a coerced scalar) is recorded as a `Flag` on the returned `Coerced` value. ### Constraint evaluation After parsing, the adapter runs [constraints](/docs/components/signatures): * `#[check]` results → stored in metadata * `#[assert]` failures → returns `ParseError::AssertFailed` ### Error handling Parse errors aggregate - you get all problems, not just the first: ```rust theme={null} ParseError::Multiple { errors: vec![...], // all field errors partial: Some(serde_json::Value::Object(...)), // what did parse } ``` ## Using the adapter directly Usually you do not touch the adapter - `Predict` handles it. The public surface is five building blocks, all parameterized by `&SignatureDef`: | Method | Produces | | ---------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- | | `build_system_def(def, types, instruction_override)` | The system message (field descriptions, structure template, response instructions, task description) | | `format_input_def(def, input)` | The user message: input fields with `[[ ## field ## ]]` markers plus response instructions. `input` is a `JsonMap`; absent fields are skipped | | `format_output_def(def, output)` | An assistant message for few-shot demos, ending with `[[ ## completed ## ]]` | | `parse_output_def(def, types, response)` | `(JsonMap, IndexMap)` — the parsed output map plus per-field parse metadata | | `ChatAdapter::parse_sections(content)` | Ordered map of `field_name → section_content` split by the delimiters | For debugging what the LM sees from a derived signature: ```rust theme={null} use dspy_rs::{ChatAdapter, ir::SignatureDef}; let adapter = ChatAdapter; let def = SignatureDef::of::(); let types = SignatureDef::types_of::(); let system = adapter.build_system_def(def, types, None); println!("System:\n{system}"); ``` ## Input formatting options Input fields support two rendering paths: ```rust theme={null} #[derive(Signature, Clone, Debug)] struct Search { #[input] query: String, // plain text #[input] #[format("yaml")] filters: Vec, // serialized as YAML #[input] #[render(jinja = "{{ this.text }}\nQuestion: {{ input.query }}")] context: Context, // custom Jinja rendering #[output] results: Vec, } ``` * `#[format("json" | "yaml" | "toon")]`: serialize a field using that format. * `#[render(jinja = "...")]`: render a field with a MiniJinja template. * `#[format]` and `#[render]` are mutually exclusive on the same field. `#[render]` context in `ChatAdapter`: * `this`: current field value * `input`: full input object (plus top-level alias overlays) * `field`: `{ name, rust_name, type }` * `vars`: adapter/surface vars (currently `{}`) `ChatAdapter` configures MiniJinja with strict undefined behavior. Available filters include: * MiniJinja built-ins * `regex_match` and `sum` helpers * `truncate` for length-limited string rendering If template rendering fails at runtime (for example, missing variables), `ChatAdapter` panics. Templates are compiled per call — defs own their template strings, so there is no process-global template cache to leak per loaded program. ## Real example: Insurance claim extraction This is what a prompt looks like for a complex nested type (from [`examples/16-insurance-claim-prompt.rs`](https://github.com/krypticmouse/DSRs/blob/main/crates/dspy-rs/examples/16-insurance-claim-prompt.rs)): ```rust theme={null} #[derive(Signature, Clone, Debug)] /// Extract the insurance claim information from the following text. /// - If you are unsure about a field, leave it as null. pub struct InsuranceClaimInfo { #[input] claim_text: String, #[output] claim: InsuranceClaim, // complex nested struct with enums } ``` ```rust theme={null} type NaiveDate = String; // dates as YYYY-MM-DD strings /// Basic claim information (metadata about the claim intake). #[derive(Debug, Clone, PartialEq, Eq)] #[Schema] pub struct ClaimHeader { /// Claim ID in format `CLM-XXXXXX`, where `X` is a digit. pub claim_id: Option, /// Date claim was reported in `YYYY-MM-DD` format. pub report_date: Option, /// Date incident occurred in `YYYY-MM-DD` format. pub incident_date: Option, /// Full name of person reporting claim. pub reported_by: Option, /// Channel used to report claim. pub channel: Option, } /// Channel used to report a claim. #[derive(Debug, Clone, PartialEq, Eq)] #[Schema] pub enum ClaimChannel { Email, Phone, Portal, InPerson, } /// Policy information if available. #[derive(Debug, Clone, PartialEq, Eq)] #[Schema] pub struct PolicyDetails { /// Policy number in format `POL-XXXXXXXXX`, where `X` is a digit. pub policy_number: Option, /// Full legal name on policy. pub policyholder_name: Option, /// Type of insurance coverage. pub coverage_type: Option, /// Policy effective start date in `YYYY-MM-DD` format. pub effective_date: Option, /// Policy expiration end date in `YYYY-MM-DD` format. pub expiration_date: Option, } /// Type of insurance coverage. #[derive(Debug, Clone, PartialEq, Eq)] #[Schema] pub enum CoverageType { Property, Auto, Liability, Health, Travel, Other, } /// An insured object involved in the claim (vehicle, building, person, etc.). #[derive(Debug, Clone, PartialEq, Eq)] #[Schema] pub struct InsuredObject { /// Unique identifier for insured object. /// /// For vehicles, use VIN format (e.g., `VIN12345678901234567`). /// For buildings, use `PROP-XXXXXX` format. /// For liability, use `LIAB-XXXXXX` format. /// For other objects, use `OBJ-XXXXXX` format, /// where `X` is a digit. pub object_id: Option, /// Type of insured object. pub object_type: InsuredObjectType, /// Make and model for vehicles, or building type for property. pub make_model: Option, /// Year for vehicles or year built for buildings. pub year: Option, /// Full street address where object is located or originated from. pub location_address: Option, /// Estimated monetary value in USD without currency symbol. pub estimated_value: Option, } /// Type of insured object. #[derive(Debug, Clone, PartialEq, Eq)] #[Schema] pub enum InsuredObjectType { Vehicle, Building, Person, Other, } /// Structured incident details. #[derive(Debug, Clone, PartialEq, Eq)] #[Schema] pub struct IncidentDescription { /// Specific standardized incident type. pub incident_type: IncidentType, /// Standardized location type where incident occurred. pub location_type: LocationType, /// Estimated damage in USD without currency symbol. pub estimated_damage_amount: Option, /// Police report number if applicable. pub police_report_number: Option, } /// Specific standardized incident type. #[derive(Debug, Clone, PartialEq, Eq)] #[Schema] pub enum IncidentType { RearEndCollision, SideImpactCollision, HeadOnCollision, ParkingLotCollision, HouseFire, KitchenFire, ElectricalFire, BurstPipeFlood, StormDamage, RoofLeak, SlipAndFall, PropertyInjury, ProductLiability, TheftBurglary, Vandalism, } /// Standardized location type where incident occurred. #[derive(Debug, Clone, PartialEq, Eq)] #[Schema] pub enum LocationType { Intersection, Highway, ParkingLot, Driveway, ResidentialStreet, ResidenceInterior, ResidenceExterior, CommercialProperty, PublicProperty, } /// Top-level insurance claim object aggregating all extracted fields. #[derive(Debug, Clone, PartialEq, Eq)] #[Schema] pub struct InsuranceClaim { /// Basic claim information. pub header: ClaimHeader, /// Policy information if available. pub policy_details: Option, /// List of insured objects involved, if applicable. pub insured_objects: Option>, /// Structured incident details. pub incident_description: Option, } ``` **Generated system message:** ``` Your input fields are: 1. `claim_text` (string) Your output fields are: 1. `claim` (InsuranceClaim) All interactions will be structured in the following way, with the appropriate values filled in. [[ ## claim_text ## ]] claim_text [[ ## claim ## ]] Output field `claim` should be of type: InsuranceClaim Definitions (used below): // Specific standardized incident type. IncidentType ---- - RearEndCollision - SideImpactCollision - HeadOnCollision - ParkingLotCollision - HouseFire - KitchenFire - ElectricalFire - BurstPipeFlood - StormDamage - RoofLeak - SlipAndFall - PropertyInjury - ProductLiability - TheftBurglary - Vandalism // Standardized location type where incident occurred. LocationType ---- - Intersection - Highway - ParkingLot - Driveway - ResidentialStreet - ResidenceInterior - ResidenceExterior - CommercialProperty - PublicProperty { // Top-level insurance claim object aggregating all extracted fields. // Basic claim information. header: { // Basic claim information (metadata about the claim intake). // Claim ID in format `CLM-XXXXXX`, where `X` is a digit. claim_id: string or null, // Date claim was reported in `YYYY-MM-DD` format. report_date: string or null, // Date incident occurred in `YYYY-MM-DD` format. incident_date: string or null, // Full name of person reporting claim. reported_by: string or null, // Channel used to report claim. channel: 'Email' or 'Phone' or 'Portal' or 'InPerson' or null, }, // Policy information if available. policy_details: { // Policy information if available. // Policy number in format `POL-XXXXXXXXX`, where `X` is a digit. policy_number: string or null, // Full legal name on policy. policyholder_name: string or null, // Type of insurance coverage. coverage_type: 'Property' or 'Auto' or 'Liability' or 'Health' or 'Travel' or 'Other' or null, // Policy effective start date in `YYYY-MM-DD` format. effective_date: string or null, // Policy expiration end date in `YYYY-MM-DD` format. expiration_date: string or null, } or null, // List of insured objects involved, if applicable. insured_objects: [ { // An insured object involved in the claim (vehicle, building, person, etc.). // Unique identifier for insured object. // // For vehicles, use VIN format (e.g., `VIN12345678901234567`). // For buildings, use `PROP-XXXXXX` format. // For liability, use `LIAB-XXXXXX` format. // For other objects, use `OBJ-XXXXXX` format, // where `X` is a digit. object_id: string or null, // Type of insured object. object_type: 'Vehicle' or 'Building' or 'Person' or 'Other', // Make and model for vehicles (use standardized manufacturer names and models), // or building type for property. make_model: string or null, // Year for vehicles or year built for buildings. year: int or null, // Full street address where object is located or originated from. location_address: string or null, // Estimated monetary value in USD without currency symbol. estimated_value: int or null, } ] or null, // Structured incident details. incident_description: { // Structured incident details. // Specific standardized incident type. incident_type: IncidentType, // Standardized location type where incident occurred. location_type: LocationType, // Estimated damage in USD without currency symbol. estimated_damage_amount: int or null, // Police report number if applicable. police_report_number: string or null, } or null, } [[ ## completed ## ]] Respond with the corresponding output fields, starting with the field `[[ ## claim ## ]]`, and then ending with the marker for `[[ ## completed ## ]]`. In adhering to this structure, your objective is: Extract the insurance claim information from the following text. - If you are unsure about a field, leave it as null. ``` **Generated user message:** ``` [[ ## claim_text ## ]] A raccoon bumped a parked scooter in a driveway. Reported by Taylor P. via phone. ``` Notice how: * Enums with many variants become "Definitions" at the top * Doc comments become inline `//` comments in the schema * `Option` renders as `T or null` * Nested objects show their full structure * The instruction comes from the signature docstring ## Design notes * **DSPy inspiration:** Marker protocol from DSPy's ChatAdapter * **BAML heritage:** Schema rendering and tolerant parsing were originally built on the vendored BAML stack (`jsonish` and its schema renderer). The in-house `typesys` module (`typesys::render`, `typesys::coerce`) replaced that stack while keeping the same prompt-facing schema style. * **Separation of concerns:** Adapter does not know about LMs - just formatting/parsing ## See also * [Signatures](/docs/components/signatures) for field types, `#[Schema]`, and constraints * [Predict](/docs/components/predict) for the caller that drives the adapter * [LM](/docs/components/lm) for the client the formatted prompt is sent to * Example: [16-insurance-claim-prompt.rs](https://github.com/krypticmouse/DSRs/blob/main/crates/dspy-rs/examples/16-insurance-claim-prompt.rs) # Capabilities Source: https://dsrs.herumbshandilya.com/docs/components/capabilities Named permissions: programs declare a ceiling, tools and holes declare needs, hosts grant, and load checks the subset A capability is a named permission: `orders:read`, `net:search`, `fs:read`. Nothing more mysterious than that, a short label for one kind of access to the world. Three parties each state their side in writing, and the statements are checked against each other at load, before a single step runs. ## Three statements, one check | Party | Statement | Where it is written | | ------------------ | ----------------------------------------- | -------------------------------------------------------------------------- | | The program | Its ceiling: the most it could ever touch | `caps { ... }` in the artifact, `#[module(caps(...))]` in source | | Each tool and hole | Its needs | `#[tool(caps("..."))]` in source, `caps [...]` on the printed tool or hole | | The host | Its grants | `--allow` on the `dsrs` host, `RuntimeEnv::grant` in an embedding binary | **The program declares its ceiling.** At the top of the artifact: ```text theme={null} caps { kb:read orders:read } ``` This is not what the program will do on a given run. It is the most it could ever do. Because the declaration is a line in a reviewable text file, it is diffable data: a program that never declared network access cannot quietly grow it, because the file would change and the change would show in review like any other line. **Every tool and hole declares its needs.** A host tool names them in its attribute: ```rust theme={null} /// Look up an order's status by id. #[tool(caps("orders:read"))] async fn order_lookup(order_id: String) -> Result { // your implementation } ``` A tool or hole that touches nothing declares `caps []`. Each piece's needs must fit under the program's ceiling as a subset, and this is checked at load: a ceiling that says only `kb:read` cannot contain a tool that needs `orders:read`, and the violation is a load error, not an incident in production. **The host states its grants.** Whoever runs the program has the final word. Serve the file but grant only the FAQ capability: ```bash theme={null} dsrs serve frontdesk.dsrs --allow kb:read ``` ```text theme={null} program caps exceed host grants: missing ["orders:read"] ``` The program's caps must be a subset of the host's grants or loading refuses, up front and by name, before a token is spent. In an embedding binary the same grants come from `RuntimeEnv::grant(cap)`, and the same subset check runs inside `Interpreter::load`. the declaration declared needs, written in the artifact caps \{ kb:read orders:read } the grants the host has the final word --allow kb:read --allow orders:read load needs ⊆ grants, checked before a single step runs the sandbox room kb:read orders:read an ungranted capability is not a locked door: there is no door ## The empty sandbox Permission systems usually fail by omission: a deny list forgets an entry, or a check gets bypassed by a code path nobody audited. The DSRs sandbox inverts this. When a sandboxed hole or a [Code Mode](/docs/components/code-mode) script starts, its environment contains only what the host granted. An ungranted capability is not a blocked call inside the sandbox; the function for it does not exist there at all. Ungrant `orders:read` and the sandbox simply contains no `order_lookup`, so no script, however creative, can invoke it. There is nothing to bypass. Deny lists require perfect memory; empty sandboxes do not. This is why a model writing JavaScript in the sandbox is not being trusted. It is being contained by construction: the sandbox starts empty, and only grants get placed inside. ## Local modules and loaded files When you run your own `#[module]` directly, nobody asks you to `--allow` anything. The native parts are already in your binary, compiled from your own source, and a gate between you and code you wrote yourself withholds nothing. The module's generated `env()` therefore grants its own declared ceiling, and the declaration serves as documentation plus the fence around any sandboxed parts. The moment the same program travels as a `.dsrs` file, trust resets to zero. A host loading the file re-checks the program's caps against its own grants, and the program gets exactly what the host allows. Same declaration, two enforcement postures, both consistent with who wrote what. ## Declarations are data Every capability statement lives in the artifact as plain text: the `caps { ... }` ceiling at the top, and `caps [...]` on each printed tool and hole. That makes the permission surface reviewable with the same machinery as any other change. A pull request that widens a program's ceiling is a visible diff on one line, and a load failure names the exact missing set, as in the error above. ## See also * [Tools and agents](/docs/components/tools-and-agents) for declaring needs on host and sandboxed tools * [Holes](/docs/components/holes) for the `caps []` printed on holes * [Runtime](/docs/components/runtime) for `RuntimeEnv::grant` and the load-time subset check * [The .dsrs file](/docs/components/dsrs-file) for the caps grammar * [Code Mode](/docs/components/code-mode) for the sandbox that scripts run inside # CLI Source: https://dsrs.herumbshandilya.com/docs/components/cli Reference for the dsrs binary: check, fmt, the HTTP serving host, and the full print-and-serve workflow The `dsrs` binary is the `.dsrs` toolchain: check, fmt, serve. It ships as the `dsrs-cli` crate; every subcommand is a plain library function (`dsrs_cli::check`, `dsrs_cli::fmt`, `dsrs_cli::serve`), and the binary itself is argument parsing plus process exit codes. All subcommands exit 0 on success and non-zero on failure. ```bash theme={null} dsrs check qa.dsrs # parse, validate, print the program summary dsrs fmt qa.dsrs --write # rewrite the file in canonical form dsrs serve qa.dsrs --port 8080 # serve it over HTTP ``` The toolchain covers the whole journey of a program file: printed from your code, checked, served on another host, and embeddable back into a Rust build. bake home: tuned and baked frontdesk .dsrs lineage inside dsrs check parse, types, ports, caps: customs for files dsrs serve :8080/run any host that can read the map can make the journey include\_program!("frontdesk.dsrs") the map comes home, checked at the border of your build ## `dsrs check ` Parses and validates a `.dsrs` artifact via `Program::load_dsrs`, running the full pipeline: lex and parse with positions, lowering, `Program::validate`, hash sealing. This is exactly what `Interpreter::load` would accept, which makes `check` a pre-commit gate for a human and a regeneration signal for a model loop. On success it prints one line to stdout: `ok: program` with the program name, its 16-hex hash, the node, signature, model, and tool counts, and the caps set when non-empty. On failure the parser's error goes to stderr with the artifact path and a `line N, column M: expected ...` position, and the exit code is non-zero. ## `dsrs fmt [--write]` Prints (or rewrites) the canonical form of an artifact. The canonical form is `Program::to_dsrs`, the same text that seals `program_hash` and that `bake` writes. Formatting is parse then print, never token shuffling, so an artifact that does not parse does not format. | Flag | Effect | | --------- | ------------------------------------------------------------------------------------------------------------------------------------- | | (none) | Prints the canonical text to stdout. | | `--write` | Rewrites the file in place, only when the bytes differ. Reports ``formatted `` `` or `` `` already canonical`` on stderr. | ## `dsrs serve [flags]` Serves a `.dsrs` program over HTTP. Startup is fail-fast, before the port binds: parse, apply the optional overlay (named form, verified against the program's hash and slot kinds), then `Interpreter::load` with the grants from `--allow`. Models are constructed from the artifact configs with secrets from provider environment variables; a QuickJS sandbox is added when none was supplied. Once bound, the address is printed on stderr. | Flag | Default | Meaning | | ------------------ | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `--host ` | `127.0.0.1` | Bind address. | | `--port ` | `8080` | Bind port (0 binds an ephemeral port). | | `--overlay ` | none | Overlay JSON in the named form (`{"": , ...}`), applied read-through on every run. | | `--allow ` | none | Capability grant, repeatable: `--allow net:search --allow fs:read`. Load is refused (with the missing set printed) when the program's caps exceed the grants. | Host tools cannot be served: a program declaring a host tool needs an embedding host that supplies the binding ([`include_program!`](/docs/components/runtime) plus your own code, or a sandboxed `js` tool instead). The load surfaces that refusal with a hint rather than a bare unbound-tool error. ### HTTP endpoints | Endpoint | What it returns | | -------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `POST /run` | Body is a JSON object of input fields; the response is `{"output": ...}`. With `?trace=1` (or `?trace=true`) the run executes inside a capture scope and the response adds `"trace_jsonl"`, the exact trace artifact text with `param_ids` attached. | | `GET /schema` | The program's external interface: `{"program", "program_hash", "signature", "types"}`, the main signature and the type table in their serde forms. | | `GET /program` | The canonical `.dsrs` text, as `text/plain; charset=utf-8`. | | `GET /healthz` | `{"status": "ok"}` plus the program name and hash. | Error responses are always `{"error": ...}` with the interpreter's own message: a non-object request body and input-surface rejections are 400, everything else (LM failures, parse failures, budget, routing) is 500. ## From code to endpoint The full workflow: print a program out of your code, check it, format it, serve it. ### 1. Print the program Every [`#[module]`](/docs/components/module-macro) exposes `program()`. Call `to_dsrs()` on it to get the canonical text, then save it: ```rust theme={null} let text = qa::program().to_dsrs(); std::fs::write("qa.dsrs", text)?; ``` One note before serving: a file printed from a `#[module]` holds a placeholder model line like `model default = "unbound:default"`, because the real model was bound in your code. Open the file and point each model line at a real model, for example: ``` model default = "openai:gpt-4o-mini" ``` ### 2. Check it Errors come with line and column positions, so `check` works as a pre-commit gate: ```bash theme={null} dsrs check qa.dsrs # ok: program `qa` (3f9c...) - 4 nodes, 4 sigs, 1 models, 0 tools ``` ### 3. Format it ```bash theme={null} dsrs fmt qa.dsrs # print the canonical form to stdout dsrs fmt qa.dsrs --write # rewrite the file in place ``` A file that does not parse does not format; fix `check` errors first. ### 4. Serve it ```bash theme={null} dsrs serve qa.dsrs --port 8080 ``` Model API keys come from the usual provider environment variables (for example `OPENAI_API_KEY`). The flag table above lists `--host`, `--port`, `--overlay`, and `--allow`. ### 5. Call it ```bash theme={null} curl -s localhost:8080/run \ -H 'content-type: application/json' \ -d '{"question": "What is DSRs?"}' # {"output": {"answer": "...", "upper": "..."}} ``` Add `?trace=1` to `POST /run` to also get a full trace of the run; the endpoint table above lists `/schema`, `/program`, and `/healthz`. ## Capabilities: needs versus allows The program states what it needs in its `caps { ... }` block. The host states what it allows with `--allow`. Serving refuses to start when the program asks for more than the host allows, and it prints the missing set. This is on purpose: a program can never quietly get more access than you granted. Two more refusals to know about: * A program with host [holes](/docs/components/holes) cannot be served by `dsrs serve`, because the Rust code lives in the binary that defined it. Embed the program with `include_program!` instead; see [Runtime](/docs/components/runtime). * Same story for host tools: the server has no implementation to bind. Sandboxed JavaScript tools serve fine, because their code travels inside the file. ## Common mistakes **Serving a file with an unbound placeholder model.** Edit the `model` lines to real models first. **Forgetting `--allow`.** If the file declares `caps { net:search }`, serving without `--allow net:search` refuses at startup. That is the design working, not a bug. **Editing the file by hand and skipping `dsrs check`.** Always check after hand edits. The parser's error messages carry positions and are meant to be followed. ## See also * [The .dsrs file](/docs/components/dsrs-file): the artifact format the toolchain operates on. * [Program and nodes](/docs/components/program-and-nodes): what `check` validates, what `fmt` prints, and how `bake` produces new files. * [Runtime](/docs/components/runtime): `Interpreter`, `RuntimeEnv`, capability grants, overlays, and embedding with `include_program!`. * [Traces](/docs/components/traces): the trace artifact `POST /run?trace=1` returns. # Code Mode Source: https://dsrs.herumbshandilya.com/docs/components/code-mode Reference for the run_js meta-tool, the QuickJS sandbox, ToolSource, Capability, and every ExecError and RegisterError variant Plain JSON tool calling pays one model round trip per call: the model emits a single tool invocation, the host executes it, and the result goes back to the model before it can decide the next step. A task that needs several tool results spends a full LM turn of latency and tokens on each one, and every intermediate result flows through the model's context even when only the final value matters. Code Mode replaces JSON tool calling with code execution. Instead of advertising N tool schemas and paying one model round trip per call, the model sees a single meta-tool named `run_js`. Its description lists your tools as a JavaScript API; the model writes a script that calls them as plain global functions, composes their results, and returns one value. One script replaces many round trips. Everything on this page lives in the `dsrs-tools` crate. The `dspy-rs` crate re-exports `Capability`, `CodeModeTool`, `RUN_JS_TOOL_NAME`, and `SandboxConfig` behind the `code-mode` cargo feature, which is on by default. The rest of the surface (executor, sources, errors) is imported from `dsrs_tools` directly. `dsrs_tools` also re-exports `rig::tool::ToolDyn` and `rig::tool::ToolError` (as `RigToolError`) so downstream crates do not need a version-matched `rig` dependency. ## Two-tier design | Tier | Status | Engine | Properties | | ---- | ----------- | ----------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | 1 | Implemented | QuickJS (quickjs-ng via `rquickjs`), in process | Fresh runtime and context per call (lifecycle on the order of 100 microseconds), per-call memory limit, interrupt-driven wall-clock deadline, no ambient authority: no filesystem, network, environment, or module loader. Host access happens only through injected `Capability` functions. | | 2 | Planned | Wasmtime components | Pooled instantiation, epoch interruption, typed WIT interfaces, for tools that graduate from ephemeral to durable. | The `Executor` trait is the seam between the tiers: subprocess, microVM, and remote executors can implement the same contract. ## `ToolSource` and the tool lifecycle `ToolSource` is the raw material for an ephemeral tool, before validation. | Field | Type | Meaning | | ------------- | ------------------- | -------------------------------------------------------------------------------- | | `name` | `String` | Unique tool name, `[A-Za-z0-9_-]{1,64}`. | | `description` | `String` | Natural-language description shown to the model. | | `params` | `serde_json::Value` | JSON Schema for the arguments (an object schema). | | `js_source` | `String` | JavaScript source per the contract below. | | `self_test` | `Option` | Optional self-test program. A tool with a failing self-test is never registered. | | Method | Signature | What it does | | ----------------- | ------------------------------------------------ | ------------------------------------------------------- | | `new` | `(name, description, params, js_source) -> Self` | Constructor; `self_test` starts as `None`. | | `with_self_test` | `(self, self_test) -> Self` | Attaches a self-test program. | | `validate_shape` | `(&self) -> Result<(), RegisterError>` | Cheap synchronous checks on the name and params schema. | | `required_params` | `(&self) -> Vec` | Names listed in the schema's `required` array. | `js_source` must be a single expression that evaluates to a function taking one argument (the parsed JSON args object) and returning a JSON-serializable value or a promise of one. Helpers go inside an IIFE that returns the tool function. Named `function` declarations are wrapped in parentheses and become expressions; one trailing `;` is tolerated. The self-test runs with the global `tool` bound to the compiled function; it fails if it throws or completes with `false`. A source only becomes callable after passing every stage of `Executor::register`: | Stage | Check | Failure | | ----------- | ----------------------------------------------------------------- | ------------------------------------------- | | Shape | Name charset and length, params-schema structure, duplicate name. | `InvalidName`, `InvalidSchema`, `Duplicate` | | Compile | The source must parse. Bytecode is cached by BLAKE3 content hash. | `Compile` | | Instantiate | The module must evaluate to a function, in a sandbox. | `NotAFunction` | | Self-test | If present, the test must pass inside the sandbox. | `SelfTest` | ## `Executor`, `ToolInvocation`, `RegisteredTool` `ToolInvocation` is one call: `{ name: String, args: Value }`, built with `ToolInvocation::new(name, args)`. `RegisteredTool` is the metadata of a tool that survived the lifecycle: | Field | Type | Meaning | | ------------- | -------- | -------------------------------------------------------------------------------- | | `name` | `String` | Registered name. | | `description` | `String` | Description from the source. | | `parameters` | `Value` | Arguments JSON Schema from the source. | | `source_hash` | `String` | Hex BLAKE3 hash of the JavaScript source; the bytecode-cache key. | | `self_tested` | `bool` | `true` if an explicit self-test passed; `false` means no self-test was provided. | `Executor` is `Send + Sync` and deliberately narrow: | Method | Signature | What it does | | ------------ | -------------------------------------------------------------------- | --------------------------------------------------- | | `validate` | `(&self, &ToolSource) -> Result<(), RegisterError>` | Synchronous structural validation only; no sandbox. | | `register` | `async (&self, ToolSource) -> Result` | The full four-stage lifecycle. | | `execute` | `async (&self, ToolInvocation) -> Result` | Runs a registered tool with JSON args. | | `tool` | `(&self, &str) -> Option` | Metadata for one registered tool. | | `tools` | `(&self) -> Vec` | Metadata for every registered tool. | | `deregister` | `(&self, &str) -> bool` | Removes a tool; `true` if it was registered. | ## `QuickJsExecutor` The Tier-1 executor. Cheap to share: wrap it in an `Arc` and clone across tasks. It requires a Tokio runtime for its blocking pool. | Method | What it does | | ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `new()` | Default `SandboxConfig`, no capabilities. | | `with_config(config)` | Explicit `SandboxConfig`. | | `builder()` | Returns a `QuickJsExecutorBuilder`. | | `config()` | The active `SandboxConfig`. | | `add_capability(cap)` | Injects a host capability; its name becomes a global JS function in every sandbox created afterward. A duplicate name is refused with `RegisterError::InvalidCapability`. | | `capability_names()` | Injected capability names, in registration order. | | `cache_stats()` | Bytecode-cache counters, as `CacheStats`. | | `rig_tool(name)` | On `Arc`: wraps a registered tool as `Arc`; `None` if not registered. | | `register_rig(source)` | On `Arc`, async: `register` plus `rig_tool` in one step. | | `execute_blocking(invocation)` | Synchronous execution on the current thread, skipping the blocking pool. Capabilities still need a reachable Tokio runtime. Do not call from inside an async task. | The builder sets limits and capabilities before construction: | Builder method | What it sets | | --------------------- | ------------------------------------------ | | `memory_limit(bytes)` | Max heap per call. | | `deadline(duration)` | Wall-clock budget per call. | | `max_stack(bytes)` | Max JS stack per call. | | `capability(cap)` | Queues a capability, validated at `build`. | | `build()` | `Result`. | ### `SandboxConfig` Resource limits applied to every sandbox instance. `Copy`, so it is passed by value everywhere. | Field | Type | Default | Meaning | | -------------- | ---------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `memory_limit` | `usize` | 32 MiB | Max heap for one call, in bytes. Exceeding it kills the call as `MemoryExceeded`. | | `deadline` | `Duration` | 500 ms | Wall-clock budget for one call. JS execution is interrupted by the engine's interrupt handler; a capability call is host code the interrupt handler cannot reach, so the executor bounds it with the *remaining* budget via `tokio::time::timeout` — a handler that runs past it is dropped and the call surfaces in JS as a deadline timeout. | | `max_stack` | `usize` | 512 KiB | Max JS stack, in bytes. | ### `CacheStats` Sources compile once per unique content (BLAKE3-keyed) and the bytecode is shared across calls and tool names. `CacheStats` carries `entries: usize`, `hits: u64`, `misses: u64`. The cache is bounded at 128 entries with a deliberately simple cap-and-clear eviction (an optimizer generating thousands of candidate tool bodies stays bounded instead of leaking them all); registered tools hold their own reference to their bytecode, so eviction only costs a recompile on the next miss. `deregister` evicts the tool's entry unless another registered tool shares the same source hash. ### `run_script` The Code Mode execution primitive: a free async function, not a method. ```rust theme={null} pub async fn run_script( source: &str, capabilities: Vec, config: SandboxConfig, ) -> Result ``` The source runs as the body of an async IIFE in a fresh, fully fenced sandbox: top-level `return` produces the result, `undefined` maps to `null`, and `await` is tolerated but only microtask-resolvable promises settle (there is no event loop; a promise waiting on timers or IO reports `PendingPromise`). Capabilities appear as plain global functions. Errors are attributed to the pseudo-tool name `RUN_JS_TOOL_NAME`; a syntax error surfaces as `ExecError::Js` so a generating model can repair the script. ## Capabilities A `Capability` is an async Rust function injected into the sandbox as a global JS function: the only doorway out. From JavaScript the call looks synchronous (`const rows = query({q: "..."})`); the executor bridges it onto the host Tokio runtime and blocks the sandbox thread until it resolves. `Err(String)` from the handler surfaces to JS as a catchable exception. | Constructor | Signature | What it does | | -------------------------- | --------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | | `Capability::new` | `(name, description, f)` where `f: Fn(Value) -> Future>` | Capability from an async closure. | | `Capability::from_tool` | `async (Arc) -> Self` | Wraps an existing DSRs tool. Name and description come from the tool's definition, fetched once at wrap time; the name is mangled per `js_identifier`. | | `Capability::from_toolset` | `async (&[Arc]) -> Result, RegisterError>` | `from_tool` for a whole set; errors if two tool names mangle to the same JS identifier. | | `Capability::wrap_tool` | `(js_name, description, tool_name, tool) -> Self` | Lower-level `from_tool`: caller supplies the JS name and description. `tool_name` is the original name, used in error messages. | Accessors: `name()` and `description()`. `CapabilityHandler` is the public handler alias: `Arc BoxFuture<'static, Result> + Send + Sync>`. For wrapped tools, the args object is serialized to JSON, handed to `ToolDyn::call`, and the result string is parsed back to JSON (or returned as a plain string if it is not valid JSON). A tool error becomes a JS exception whose message names the original tool: ``tool `` failed: ``. Capability names become JS globals, so they must be valid identifiers; the `__dsrs` prefix is reserved by the runtime, and a name that collides with a JavaScript reserved word or ambient global (`class`, `JSON`, `Object`, ...) is refused. `js_identifier(name)` mangles an arbitrary tool name into a valid identifier, in order: 1. Every character outside `[A-Za-z0-9_$]` becomes `_` (`my-tool.v2` becomes `my_tool_v2`). 2. A leading digit gets a `_` prepended (`2fast` becomes `_2fast`). 3. An empty name becomes `_tool`. 4. A result starting with `__dsrs` gets one more leading `_`. 5. A result colliding with a JavaScript reserved word or ambient global gets a `_tool` suffix (`JSON` becomes `JSON_tool`, `class` becomes `class_tool`) — declaring a `const` named `class` would break the sandbox contract outright, and shadowing `JSON` would sabotage every script that touches it. The mapping is not injective: distinct names can mangle to the same identifier, so every batch wrapper refuses collisions at registration or load time instead of silently shadowing a tool. ## The `run_js` surface `RUN_JS_TOOL_NAME` is the constant `"run_js"`. `run_js_parameters()` returns its argument schema: one required string property, `code`, described as an async function body that must `return` a JSON-serializable value. `ToolApi` is one entry of the JS API listing shown to the model: | Field | Type | Meaning | | ------------- | -------- | ------------------------------------------------------------ | | `js_name` | `String` | The global the tool is callable under (per `js_identifier`). | | `description` | `String` | Tool description. | | `parameters` | `Value` | Arguments JSON Schema. | `code_mode_description(apis: &[ToolApi]) -> String` generates the default `run_js` description: the execution contract (async function body, global functions, one arguments object each, failed calls throw, no filesystem or network or imports) plus a token-compact listing such as `- search(args): Find documents. args: {query: string, limit?: integer}` and a short example. It is deliberately a plain function: in the IR this description is an optimizable `ToolDesc` parameter, and this function supplies its default value. `CodeModeTool` packages the whole surface as one `rig::tool::ToolDyn`: | Method | Signature | What it does | | ------------------ | ----------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | | `new` | `async (Vec>, SandboxConfig) -> Result` | Fetches each tool definition once, mangles names, refuses identifier collisions, wraps every tool as a capability, and generates the description. | | `with_description` | `(self, description) -> Self` | Replaces the auto-generated description (the optimizable-description seam). | | `description` | `(&self) -> &str` | Current description. | | `config` | `(&self) -> SandboxConfig` | Sandbox config scripts run under. | Its `ToolDyn` implementation advertises itself as `run_js` with `run_js_parameters()`. The call contract: model-repairable failures (script errors, tool failures, deadline and memory kills, bad arguments) are returned as `Ok` with the typed error serialized to JSON, so an outer tool loop feeds them back to the model instead of aborting. Only `ExecError::Internal` surfaces as `Err`. `SandboxTool` is the other `ToolDyn` bridge: one validated ephemeral tool (not the collapsed surface) exposed under its registered name. Obtain one via `QuickJsExecutor::rig_tool` or `register_rig`; construct directly with `SandboxTool::new(executor, meta)` and read metadata with `meta()`. Every call round-trips through the owning `Executor`, so limits, capabilities, and the bytecode cache apply. Empty argument strings are treated as `{}`. Unlike `CodeModeTool`, it surfaces every failure as `Err(ToolError::ToolCallError)` carrying the structured error JSON. ## Errors Both enums serialize to tagged JSON, and both have a `to_llm_json()` method that produces the string fed back to the model for self-repair. That JSON shape is part of the contract: the tag tells a generating loop which artifact to regenerate. `ExecError` tags on `kind`, `RegisterError` tags on `stage`, both in `snake_case`. Example: `{"kind":"timeout","name":"add","deadline_ms":500}`. ### `ExecError` Raised while executing an already-registered tool or a `run_js` script. | Variant | `kind` | Fields | Meaning | | ---------------- | ----------------- | ------------------------------- | ------------------------------------------------------------------------------------------------- | | `NotFound` | `not_found` | `name` | No tool with this name is registered. | | `Timeout` | `timeout` | `name`, `deadline_ms` | The call ran past its wall-clock deadline and was killed by the interrupt handler. | | `MemoryExceeded` | `memory_exceeded` | `name`, `limit_bytes` | The call exceeded the sandbox memory limit and was killed. | | `Js` | `js` | `name`, `message` | The JavaScript threw an uncaught exception (including syntax errors in `run_script`). | | `InvalidArgs` | `invalid_args` | `name`, `reason` | Arguments were rejected before the sandbox was entered (not an object, or missing required keys). | | `Capability` | `capability` | `name`, `capability`, `message` | An injected host capability returned an error and the script let it propagate. | | `PendingPromise` | `pending_promise` | `name` | A returned promise never settled: the sandbox has no event loop, only microtasks. | | `Internal` | `internal` | `message` | The executor itself failed (thread pool, serialization). Not model-repairable. | ### `RegisterError` Raised during the validate-then-register lifecycle. | Variant | `stage` | Fields | Meaning | | ------------------- | -------------------- | -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `InvalidName` | `invalid_name` | `name`, `reason` | Name is empty, longer than 64 characters, or outside `[A-Za-z0-9_-]`. | | `Duplicate` | `duplicate` | `name` | A tool with this name is already registered. | | `InvalidSchema` | `invalid_schema` | `reason` | The params JSON Schema is structurally invalid. | | `InvalidCapability` | `invalid_capability` | `name`, `reason` | The capability name is not a valid JS identifier, is reserved, is a duplicate, or two tool names mangle to the same identifier. | | `Compile` | `compile` | `message` | The JavaScript source failed to parse. | | `NotAFunction` | `not_a_function` | `evaluated_type` | The source compiled but did not evaluate to a function; the type is reported in `typeof` vocabulary. | | `SelfTest` | `self_test` | `message` | The self-test threw or completed with `false`. | | `Execution` | `execution` | wraps an `ExecError` | The sandbox itself failed during validation (timeout or memory kill during module evaluation or self-test). The serialized JSON carries both the `stage` tag and the inner `kind` tag. | ## Integration: the two lanes The sandbox exposes exactly the injected capability globals and nothing else; which tools become capabilities is decided per lane. **Module lane.** `ToolSet::code_mode(tools, config)` (async, behind the `code-mode` feature) collapses a `Vec>` into a `ToolSet` containing a single `CodeModeTool`. Drop it into any tool loop, `LM::call_with_toolset` or `Predict`, exactly like a normal `ToolSet`. It errors if two tool names mangle to the same JS identifier. See [LM](/docs/components/lm). **IR lane.** `RuntimeEnv::with_code_mode(config)` enables Code Mode for every `AgentLoop` in a loaded program: the loop's non-stop tools are presented as one `run_js` definition instead of N JSON definitions, while stop tools keep their plain definitions so the loop can still terminate. Host tools are wrapped with `Capability::wrap_tool`; sandboxed tools route through the environment's bound executor under their registered names. The `run_js` description is generated from the overlay-resolved tool descriptions, so optimizable `ToolDesc` parameters keep flowing into the surface the model sees. Identifier collisions are refused at load. This is a `RuntimeEnv` binding option, not a `ToolKind` variant: Code Mode is a host presentation strategy, not program semantics, so the same artifact (same tools, same program hash) runs identically either way. See [Runtime](/docs/components/runtime). In the IR lane the usual [capability](/docs/components/capabilities) gates still apply on top of the sandbox fence: `program.caps` must be a subset of the environment's grants at load, and a run that reaches for an unpermitted capability fails with `RunError::CapabilityDenied`. ## See also * [LM](/docs/components/lm): `ToolSet` and the tool loop that `run_js` drops into. * [Runtime](/docs/components/runtime): `RuntimeEnv`, `with_sandbox`, `with_code_mode`, and the load-time checks. * [Tools and agents](/docs/components/tools-and-agents): writing host and sandboxed tools. * [Capabilities](/docs/components/capabilities): the grants that gate what sandboxed code may reach. * Runnable example: [https://github.com/krypticmouse/DSRs/blob/main/crates/dspy-rs/examples/15-tools.rs](https://github.com/krypticmouse/DSRs/blob/main/crates/dspy-rs/examples/15-tools.rs) * Sandbox latency microbench: [https://github.com/krypticmouse/DSRs/blob/main/crates/dsrs-tools/examples/bench.rs](https://github.com/krypticmouse/DSRs/blob/main/crates/dsrs-tools/examples/bench.rs) # Data Source: https://dsrs.herumbshandilya.com/docs/components/data Trainset rows as plain structs: #[derive(Example)], ToInput/ToOutput, DataLoader, TypedLoadOptions, RowRecord, and DataLoadError A trainset is `Vec` where `E` is any struct you define: the row. Rows are the unit of trainsets and of metric ground truth, and they are signature-independent — a row can carry gold labels and metric-only fields the module never sees (HotpotQA supporting facts, difficulty tags, source ids). The connection between a row and a module is the `ToInput` trait: the evaluation loop and optimizers project each row into the module's input with `to_input()` and hand the full row to your metric. `DataLoader` is the ingestion path that produces `Vec` from JSON, CSV, Parquet, and HuggingFace sources. There is no untyped row type in the public contract: custom mappers work with `RowRecord` at the load boundary. ```rust theme={null} use dspy_rs::{DataLoader, Example, ToInput, ToOutput, TypedLoadOptions, UnknownFieldPolicy}; ``` Few-shot demos are a separate type: `Demo` is the signature-bound input/output pair rendered into the prompt, and it lives with the predictor. See [Predict](/docs/components/predict) for `Demo`, `.demo(...)`, and `.with_demos(...)`. ## Row structs A row struct is plain data. It names no signature and marks no fields: which fields matter is decided at the call site, by name. ```rust theme={null} #[derive(Example, Clone, Debug, serde::Serialize)] struct HotpotRow { question: String, // fills QAInput.question answer: String, // fills QAOutput.answer when seeding demos supporting_facts: Vec, // matches nothing in QA: metric-only } ``` A field is used when the target type declares a field of that name; fields the target does not declare are ignored. `supporting_facts` above is metric-only for `QA` not because it is marked, but because `QAInput` and `QAOutput` have no such field. The same row type serves any signature whose input it can fill. The same row type flows through the whole loop: | Consumer | Signature | Row bound | | ------------------------------------------------------------- | ------------------------------------------------------ | -------------------------------------------------------------------------------------------------- | | [`evaluate_trainset`](/docs/components/evaluation) | `evaluate_trainset(&module, &[E], &metric)` | `E: ToInput + Sync` | | [`compile_module`](/docs/components/optimizers) | `optimizer.compile_module(&mut module, &[E], &metric)` | `E: ToInput + serde::Serialize + Send + Sync` | | [`TypedMetric`](/docs/components/evaluation) | `evaluate(&self, example: &E, prediction, trace)` | none — the metric receives the full row | | [`OptimizeTarget::module`](/docs/components/optimizer-engine) | `OptimizeTarget::module(&mut module, &[E], &metric)` | `E: ToInput + Serialize + Sync` (rollout-cache uids are content hashes of the whole row) | | Demo seeding | `Demo::new(row.to_input()?, row.to_output()?)` | `E: ToInput + ToOutput` | Because the metric sees the row rather than a signature-shaped pair, ground truth does not have to fit the module's output type: a metric can score a `QAOutput` prediction against supporting facts the module never produced. ## ToInput and ToOutput ```rust theme={null} pub trait ToInput { fn to_input(&self) -> anyhow::Result; } pub trait ToOutput { fn to_output(&self) -> anyhow::Result; } ``` `ToInput` projects a row into a module's input type. `ToOutput` is its counterpart for gold output, used to seed labeled few-shot demos (`Demo::new(row.to_input()?, row.to_output()?)`); optimizers that harvest demos from traces do not need it. Both are fallible because the derived impl resolves fields by name at runtime; the tuple impls never fail. Three ways to get an impl: | Source | Provides | Use when | | -------------------- | ---------------------------------------------------------------------------- | ----------------------------------------------- | | `#[derive(Example)]` | `ToInput`/`ToOutput` for every `I`/`O` the row can fill, by field name | The row's field names line up with the target's | | `(I, O)` tuples | `ToInput` and `ToOutput` | Inline trainsets with no extra fields | | Hand-written impl | Whatever you write | Field names or shapes that do not line up | Tuple rows make zero-boilerplate inline trainsets: ```rust theme={null} let trainset = vec![( QAInput { question: "What is 2+2?".into() }, QAOutput { answer: "4".into() }, )]; ``` ## `#[derive(Example)]` The derive marks a struct as a trainset row. It takes no arguments and no field attributes, and generates blanket `ToInput`/`ToOutput` impls: the row projects into *any* target type by serializing itself and deserializing the target out of it, matching fields by name. ```rust theme={null} #[derive(Example, Clone, Debug, serde::Serialize)] struct HotpotRow { question: String, answer: String, supporting_facts: Vec, } ``` Requirements and behavior: * The row must be a struct with named fields and must implement `serde::Serialize` (derive it alongside `Example`). * Fields the target type does not declare are ignored, so one row type can serve several signatures and carry metric-only columns. * Resolution happens at runtime, not compile time. A field the target requires but the row lacks — or one whose type does not deserialize — returns an error naming both types, propagated as an evaluation or compile error. Use tuple rows where you want the check at compile time. Because the impls are blanket, the target type comes from the call site: the module's `Input` for `to_input()`, the signature's `Output` for `to_output()`. Nothing on the row names a signature. ```rust theme={null} let row = HotpotRow { /* … */ }; let input: QAInput = row.to_input()?; // target inferred from the binding let demo = Demo::::new(row.to_input()?, row.to_output()?); ``` A row whose gold fields do not line up with the signature's `Output` needs no special handling: nothing is generated per-signature, so `to_input()` works regardless and only a `to_output()` call that cannot be satisfied fails. Metrics read gold fields from the row directly, so rows labeling a subset of a multi-field output (a dataset with a gold answer against a signature that also outputs `reasoning`) simply never call `to_output()`. `dspy_rs::core::example::project` is the underlying helper (`project::(&T) -> Result`) if you need the same field-name projection outside a row impl. ## DataLoader `DataLoader` is a unit struct whose associated functions load JSON, CSV, Parquet, and HuggingFace sources. All loaders are generic over the row struct and return `anyhow::Result>` where `E: serde::de::DeserializeOwned + facet::Facet`. The row's Facet shape determines required fields and drives type-aware coercion; derive it alongside `Deserialize`: ```rust theme={null} #[derive(Example, Clone, Debug, facet::Facet, serde::Serialize, serde::Deserialize)] #[facet(crate = facet)] struct HotpotRow { question: String, answer: String, supporting_facts: Vec, } let trainset: Vec = DataLoader::load_json("data/train.jsonl", true, TypedLoadOptions::default())?; ``` | Method | Parameters | Source notes | | --------------------------- | ---------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `load_json::` | `(path, lines: bool, opts)` | JSON array/object, or JSONL when `lines = true`. `path` may be a local file or an HTTP(S) URL | | `load_csv::` | `(path, delimiter: char, has_headers: bool, opts)` | Local file or HTTP(S) URL. Without headers, fields surface as `column_{idx}` | | `load_parquet::` | `(path, opts)` | Local Parquet file only | | `load_hf::` | `(dataset_name, subset, split, verbose: bool, opts)` | HuggingFace Hub dataset repo. `subset` and `split` are substring filters on artifact filenames. Supports `.parquet`, `.json`, `.jsonl`, and `.csv` artifacts | | `load_hf_from_parquet::` | `(parquet_files: Vec, opts)` | Local Parquet set; deterministic/offline stand-in for `load_hf`. No mapper variant | A source field missing from a row is `DataLoadError::MissingField` unless the row field is `Option<_>`, in which case it deserializes as `None`. Each of `load_json`, `load_csv`, `load_parquet`, and `load_hf` has a `_with` mapper overload (`load_json_with`, `load_csv_with`, `load_parquet_with`, `load_hf_with`) that takes the same parameters plus a closure `Fn(&RowRecord) -> anyhow::Result`, with no `Deserialize` or `Facet` bound on `E`. Mapper overloads bypass shape-driven conversion entirely: `opts` is accepted for API parity but is not applied, so `field_map` and `unknown_fields` have no effect on `_with` calls. ## TypedLoadOptions | Field | Type | Default | Meaning | | ---------------- | ------------------------- | -------- | ------------------------------------------------------------------------------------------------------- | | `field_map` | `HashMap` | empty | Remaps row-struct fields to source columns. Key: row struct field name. Value: source field/column name | | `unknown_fields` | `UnknownFieldPolicy` | `Ignore` | Policy for extra source fields | ```rust theme={null} let mut field_map = HashMap::new(); field_map.insert("question".to_string(), "prompt".to_string()); let trainset = DataLoader::load_csv::( "data/custom.csv", ',', true, TypedLoadOptions { field_map, unknown_fields: UnknownFieldPolicy::Ignore }, )?; ``` `UnknownFieldPolicy` variants: | Variant | Behavior | | ------------------ | -------------------------------------------------------------------------------------------- | | `Ignore` (default) | Extra source fields not consumed by the row struct are dropped | | `Error` | Any unconsumed source field fails the load with `DataLoadError::UnknownField { row, field }` | The shape-driven path also applies tolerant scalar coercion before strict serde deserialization, driven by the row's Facet shape: a `"4"` cell fills a `String` field as `"4"` and an `i64` field as `4`; string cells convert to declared `Int`, `Float`, and `Bool` (`"true"`/`"false"`, case-insensitive) fields when they parse cleanly. Values that do not convert pass through unchanged so serde surfaces a precise `TypeMismatch`. ## RowRecord `RowRecord` is the public raw-row type passed to `_with` mapper closures. | Field | Type | Meaning | | ----------- | ------------------------------------ | ----------------------------------------------------------------- | | `row_index` | `usize` | 1-based row index in the loaded stream after filtering empty rows | | `values` | `HashMap` | Parsed key-value payload for the row | `RowRecord::get::(key)` deserializes a typed value from a field, where `T: DeserializeOwned + 'static`. It returns `DataLoadError::MissingField` when the key is absent and `DataLoadError::TypeMismatch` on deserialization failure. `String` reads coerce scalar JSON numbers and booleans into strings for ergonomic CSV mapping. ```rust theme={null} let trainset = DataLoader::load_json_with( "data/train.jsonl", true, TypedLoadOptions::default(), |row| Ok(HotpotRow { question: row.get::("prompt")?, answer: row.get::("gold")?, supporting_facts: row.get::>("facts")?, }), )?; ``` Mapper closure errors are wrapped as `DataLoadError::Mapper` with the failing row index. ## DataLoadError `DataLoadError` implements `std::error::Error` via `thiserror`. Public loaders return `anyhow::Result`, with `DataLoadError` as the wrapped source. | Variant | Payload | Meaning | | -------------- | --------------------------------- | -------------------------------------------------------- | | `Io` | `anyhow::Error` | Source read or download failure | | `Csv` | `anyhow::Error` | CSV parser failure | | `Json` | `anyhow::Error` | JSON/JSONL parser failure | | `Parquet` | `anyhow::Error` | Parquet parser failure | | `Hf` | `anyhow::Error` | HuggingFace Hub listing or file retrieval failure | | `MissingField` | `{ row: usize, field: String }` | Required row-struct field absent from a row | | `UnknownField` | `{ row: usize, field: String }` | Extra source field under `UnknownFieldPolicy::Error` | | `TypeMismatch` | `{ row, field, message }` | Field existed but could not convert to the required type | | `Mapper` | `{ row: usize, message: String }` | Custom mapper closure returned an error | The module also exposes `is_url(path: &str) -> bool`, the helper the loaders use to decide between filesystem and HTTP(S) fetching. ## Module layout Data loading lives at `data::dataloader` and `data::utils`, re-exported from `data/mod.rs`. The crate root flattens further: `dspy_rs::DataLoader` is the conventional import. (The transitional `data::v1` path alias is gone.) ### Migration note The signature-bound pair `Example { input, output }` was removed. Trainset rows are now plain structs that project into a signature's types through `ToInput`/`ToOutput` (this page), and few-shot demos are `Demo` on [Predict](/docs/components/predict). Loaders that took a signature (`load_json::`) now take the row struct (`load_json::`); `TypedLoadOptions::field_map` keys are row-struct field names instead of signature field names. The pre-v1 raw loaders were also removed. Use the typed `load_*` / `load_*_with` APIs instead of: * `load_json(path, input_keys, output_keys)` * `load_csv(path, delimiter, has_headers, input_keys, output_keys)` * `load_parquet(path, input_keys, output_keys)` * `load_hf(dataset_name, subset, split, input_keys, output_keys, verbose)` * `save_json(...)` and `save_csv(...)` ## See also * [Signatures](/docs/components/signatures) for `Signature`, `Schema`, and the generated `Input`/`Output` structs * [Predict](/docs/components/predict) for `Demo` and demos on the leaf module * [Evaluation](/docs/components/evaluation) for `evaluate_trainset` and `TypedMetric` * [Optimizers](/docs/components/optimizers) for `Optimizer::compile` over a trainset * [Example: evaluate on HotpotQA](https://github.com/krypticmouse/DSRs/blob/main/crates/dspy-rs/examples/03-evaluate-hotpotqa.rs) * [Example: optimize on HotpotQA](https://github.com/krypticmouse/DSRs/blob/main/crates/dspy-rs/examples/04-optimize-hotpotqa.rs) * [Example: MIPROv2 optimization](https://github.com/krypticmouse/DSRs/blob/main/crates/dspy-rs/examples/08-optimize-mipro.rs) # The .dsrs file Source: https://dsrs.herumbshandilya.com/docs/components/dsrs-file Reference for the .dsrs program text format: declarations, node forms, ports, and the hard rules A `.dsrs` file is the canonical text form of a program: its declarations first, then exactly one `main`. The program hash is computed from this canonical text, minus the lineage block, so the file is the program's identity, and any two loads of the same text agree on it. This page lists every declaration and node form with a short example of each. General rules: `//` starts a comment. Whitespace is insignificant except inside ` js``` ``` ` code fences. Strings are JSON strings. Reserved words cannot be used as names: `dsrs program caps model sig class enum tool lineage main in out predict cot agent hole seq fork join route retry refine loop else js demos string int float bool map true false null while carry`. ## File skeleton Declarations may appear in any order; `main` comes last. ``` dsrs 1 program qa caps { net:search } model fast = "openai:gpt-4o-mini" { temperature 0.2 } sig Main { in question: string out answer: string } main: Main = seq { ... out { answer = ... } } ``` ## Declarations ### `dsrs 1` The format pragma. It must be the first line of every file. ### `program` Names the program. ``` program qa ``` ### `caps` The program's capability ceiling: the full set of capabilities anything in the file may use. Omit the block when the program needs none. Capability names are namespaced with a colon. ``` caps { net:search fs:read } ``` ### `model` Declares a model that nodes reference as `@name`. The options block is optional; all keys inside it are optional: `base_url "..."`, `temperature N`, `max_tokens N`, `max_tool_iterations N`, `max_retries N`, `retry_base_delay_ms N`, `cache true|false`. ``` model fast = "openai:gpt-4o-mini" model core = "openai:gpt-4o-mini" { temperature 0.2 max_tokens 1024 cache true } ``` ### `sig` An LM-call interface: the fields going in and coming out, with an optional instruction string first. `alias` renames a field for the LM; `check` and `assert` attach constraints (a `check` always needs a label). ``` sig Draft { "Draft a thorough, factual answer." in question: string out answer: string check("this|length > 0", "non-empty") } ``` **Types**: `string`, `int`, `float`, `bool`; `Name` (a declared class or enum); `"lit"` (a literal string type); `T[]` (list); `T?` (optional); `map` (string-keyed map); `A | B` (union); `(A | B)[]` (grouped union in a list). ### `class` A struct type, referenced by name in signatures. Fields may carry doc strings and constraints. ``` class Profile { "A user profile." name: string "display name" age: int? check("this|int >= 0", "non-negative") tags: string[]? meta: map kind: "gold" | "basic" } ``` ### `enum` A unit enum. Variants may carry doc strings. ``` enum Severity { Low "minor" High } ``` ### `tool` A tool a loop may call: a name, a description, an optional `caps [...]` list, and an in/out interface. A **host** tool has no code block; the runtime binds its implementation by name at load. A **sandboxed** tool carries its JavaScript in the artifact as a ` js``` ``` ` fence. ```` tool fetch "Fetch a URL" caps [net:fetch] { in url: string out body: string } tool shout "Uppercase" { in text: string out loud: string } js``` (a) => ({ loud: a.text.toUpperCase() }) ``` ```` ### `lineage` Optional provenance for optimized artifacts: which optimizer produced this program, on what data, at what cost. `parent` and `overlay` are stamped by `Program::bake`. The lineage block is excluded from the program hash. ``` lineage { optimizer "gepa-0.3" trainset "tickets@v1" budget "100 rollouts" parent "00000000deadbeef" date "2026-08-14" } ``` ## `main` and node forms `main` is the program body: always a `seq` typed by the program's main signature. ``` main: Main = seq { ... } ``` Every step inside a `seq` is `name = `; names are program-unique, and a node may only reference nodes named earlier. The seq exports fields with a final `out { ... }` step, and `main`'s seq must export every `out` field of its signature. `@model` may be omitted when exactly one model is declared. Leaf nodes (`predict`, `cot`, `agent`, `hole`) always need a `name =`; containers in arm or child positions may be anonymous. ### `predict` One LM call over a signature. The optional block sets the instruction and demos. ``` drafter = predict Draft @fast (question = $.question) { instruction "..." demos [...] } ``` ### `cot` A predict with a prepended `reasoning` output. ``` drafter = cot Draft @deep (question = $.question) ``` ### `agent` An LM plus tool loop. The block is required. ``` researcher = agent Research @fast (question = $.question) { tools [fetch shout] tool_set [fetch] stop_tools [shout] max_turns 6 until_parse false budget { calls 5 tokens 40000 deadline_ms 60000 on_exhausted finalize } context { max_history_turns 4 tool_result_max_bytes 2048 playbook "Be brief." } instruction "..." demos [{"input":{"ticket":"x"},"output":{"reply":"y"}}] } ``` `tools` declares which tools the loop *may* carry — it is the loop's capability footprint. `tool_set` is the tuned selection: the subset the loop actually presents to the model, an optimizable parameter like `instruction` or `demos`. It only prints when an optimizer has restricted it; absent means the full `tools` list. ### `hole` Typed opaque code: the type system sees a normal node, the implementation is either sandboxed JavaScript carried in the artifact or a native function the host binds by name. Every hole declares `caps [...]` (empty when it needs none), then either a ` js``` ``` ` fence or `extern ""`. Sandboxed form: ```` checker = hole CiteCheck (draft = drafter.answer) caps [] js``` (a) => ({ answer: a.draft }) ``` ```` Extern (host) form. The hash is the stable content hash of the host implementation and must be exactly 16 hex digits: ``` checker = hole CiteCheck (draft = drafter.answer) caps [] extern "3fa9c2d417b0e6a1" ``` ### `seq` A nested scope with its own exported fields. ``` inner = seq { step = predict Reply (ticket = $.ticket) out { reply = step.reply } } ``` ### `fork` / `join` Concurrent branches that cannot see each other, joined into one set of exported fields. ``` forked = fork { a = predict Summarize (ticket = $.ticket) b = predict Reply (ticket = $.ticket) } join { summary = a.summary, reply = b.reply } ``` ### `route` / `else` Branches on an enum-typed (or literal-union) port. Arms must export identical fields; `else` is required unless the arms cover every variant. ``` router = route classifier.severity { Low -> low = predict Reply (ticket = $.ticket) else -> high = predict Escalate (ticket = $.ticket) } ``` ### `retry` Re-runs a child on retryable failure, with optional backoff and parse-error feedback. ``` audited = retry (attempts 3 backoff_ms 100 feedback true) auditor = predict Audit (reply = router.reply) ``` ### `refine` A body plus a judge: the body re-runs with the judge's feedback until the score passes the threshold or the rounds run out. The judge's signature must output `score: float` and `feedback: string`; `feedback_field` names the string input of the body that receives the feedback. ``` refined = refine (threshold 0.8 max_rounds 3 feedback_field feedback) { body = drafter = predict Draft (ticket = $.ticket, feedback = "start") judge = grader = predict Judge (reply = drafter.reply) } ``` ### `loop` A bounded loop. `^field` reads the previous iteration's carried value; `while` (optional) continues while a bool port is true; `carry` rebinds next-iteration inputs (each carried field must shadow a scope input); `join` names the loop's exported fields. ``` looped = loop (max_iters 3) { improver = predict Improve (ticket = ^ticket) while improver.keep_going carry { ticket = improver.better } join { improved = improver.better } } ``` ## Ports The right side of every binding is a port: | Form | Meaning | | ------------ | --------------------------------------------------------------- | | `$.field` | The enclosing scope's input (the program input at top level). | | `node.field` | An output of an earlier-named node. | | `^field` | The previous loop iteration's carried value (loop bodies only). | | JSON literal | `"text"`, `42`, `1.5`, `true`, `null`, arrays, objects. | Every `in` field of a leaf's signature must be bound exactly once. Types must match; the allowed widenings are `int` to `float`, `T` to `T?`, and `T` to a union containing `T`. ## Hard rules Violations of any of these are compile errors: 1. `dsrs 1` first; `main: = seq { ... }` last. 2. Node names are program-unique; only earlier nodes are referenceable. 3. Every hole and tool `caps [...]` must be a subset of the program `caps { ... }`; an agent's `stop_tools` must come from its `tools`, and its `tool_set` must be a duplicate-free subset of them. 4. `route` needs `else` unless its arms cover every enum variant; arms export identical fields. 5. All loops carry explicit bounds (`max_iters`, `max_turns`, `attempts`, `max_rounds`). 6. Signatures need at least one `in` and one `out` field; `check` needs a label. 7. Class, enum, sig, tool, and model names must be declared before `main` uses them. ## See also * [Program and nodes](/docs/components/program-and-nodes): the in-memory `Program` this text lowers to, and the hash rules * [CLI](/docs/components/cli): `dsrs check`, `dsrs fmt`, and serving a `.dsrs` file over HTTP * [Runtime](/docs/components/runtime): loading and running a program, including `include_program!` * [Capabilities](/docs/components/capabilities): the `caps` ceiling and host grants # The edit calculus Source: https://dsrs.herumbshandilya.com/docs/components/edit-calculus Structural program mutation: the Edit enum, Program::edited, legal_edits, and carrying overlays across an edit with migrate_overlay The edit calculus is the *structural* mutation half of the IR. An [`Overlay`](/docs/components/program-and-nodes) mutates parameter **values** over a fixed skeleton; an `Edit` mutates the skeleton itself — add a reasoning field, swap a `Predict` for an `AgentLoop`, wrap a flaky step in a `Retry`, remove a step. Edits are plain serde values — inspectable, diffable, replayable — and are only ever applied through `Program::edited`, which is pure: it clones the arenas, applies the edits in order, re-runs the same load-time validation the builder and loader use, and seals a **new** content hash. A program value is never mutated in place, so every hash-bound artifact (overlays, traces, caches) minted against the parent stays coherent. All items are exported from `dspy_rs::ir`: `Edit`, `EditKind`, `SwapTarget`, `EditError`, `ApplyError`, `migrate_overlay`. ## The edits An optimizer's structural proposal is data, not code — it can be logged, replayed against the same parent, and diffed. | `Edit` variant | Plain words | | -------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `AugmentSig { leaf, prepend }` | Prepend an output field to a `Predict`/`AgentLoop` leaf's signature — the CoT move (mirrors `SignatureDef::augmented_with`). Copy-on-write: a new `SigId` is created; nodes sharing the old signature keep it. | | `SwapLeaf { leaf, to }` | Swap a leaf's kind: `Predict` → `AgentLoop` (with tools ⊆ `program.tools`, a stop spec, and a budget) or `AgentLoop` → `Predict`. Name, signature, bindings, and the instruction/demos/model param slots are preserved; the agent direction mints `.context` and `.tool_set` slots, the predict direction drops them. | | `WrapRetry { node, max_attempts, backoff_ms, feedback }` | Wrap an existing node in a `Retry`, rewiring the parent reference and redirecting downstream `Out` ports to the wrapper. | | `Remove { node }` | Remove a node from its parent `Seq` body (subtree and its params are garbage-collected). If a later binding still references its outputs, `validate()` rejects the batch. | | `AddTool { agent, tool }` / `RemoveTool { agent, tool }` | Declare or undeclare an existing program tool on an agent leaf. The `.tool_set` default tracks the declaration: adding a tool makes it live, removing one also drops it from `stop_tools` and the tool-set default. | | `SetStop { agent, stop }` | Replace an agent leaf's `StopSpec`. | | `SetInstructionDefault { leaf, text }` | Set the leaf's instruction slot *default* — a bake-like change without an overlay, for structural optimizers that also seed text. | `SwapTarget` is the target kind of `SwapLeaf`: `Agent { tools, stop, budget }` or `Predict`. ## `Program::edited` ```rust theme={null} pub fn edited(&self, edits: &[Edit]) -> Result ``` Applies `edits` in order to a clone of `self` and returns the sealed, validated result. The child gets a **new** content hash and `lineage.parent` set to the parent's hash — exactly like `Program::bake`; the other provenance fields are left empty for the optimizer to fill (an edit is not an optimization run record). Behavior worth knowing: * **NodeIds are positional handles against the parent.** Within one `edited()` batch, ids stay stable (swaps happen in place, removals only detach); dead nodes, signatures, and params are garbage-collected once at the end. Ids in the child may therefore differ from the parent — re-locate leaves by name (`Program::leaf_id(name)`, leaf names are program-unique and survive edits) and params by path. * **Batch validation.** Edits are validated as a *sequence*: intermediate states may be inconsistent (remove a producer, then its consumer); only the final program must pass validation. Apply-time errors cover what is checkable locally; everything data-flow shaped is deliberately left to the load-time validator, so the edit layer and the loader can never disagree. * **Identity is preserved.** `edited(&[])` returns a program with the parent's hash — only lineage differs, and lineage is outside the hash preimage. Signatures that were already unreferenced in the parent are kept; only *newly* orphaned ones are collected. * **CoT re-sugars.** When the prepended field is exactly the `cot` reasoning field on a `Predict`, the augmented signature copy keeps the base name so the canonical printer re-sugars it as `cot `; otherwise it gets a fresh unique name (`_`). ## Errors | Error | Meaning | | ------------------------------------------ | -------------------------------------------------------------------------------------------------------------------- | | `EditError::Apply { index, edit, reason }` | Edit `index` could not be applied to the (partially edited) program; carries the offending edit and an `ApplyError`. | | `EditError::Invalid(ValidateError)` | Every edit applied, but the resulting program failed the load-time rules — the error is the validator's own. | `ApplyError` is the locally-checkable failure set: `StaleNode`, `WrongKind` (e.g. `SetStop` on a `Predict`), `DuplicateField`, `UnknownTool`, `ToolCapsExceedProgram` (a tool's caps exceed the program ceiling), `ToolAlreadyDeclared`, `ToolNotDeclared`, `NotInSeq` (only `Seq` steps can be removed), `Unparented`. ## `legal_edits`: the proposer menu ```rust theme={null} pub fn legal_edits(&self, at: NodeId) -> Vec ``` The menu of edit kinds structurally admissible at a node — lightweight, serializable `EditKind` descriptors suitable for prompting an LLM proposer: | Node | Menu | | ---------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | | `Predict` leaf | `AugmentSig`, `SetInstructionDefault`, `SwapToAgent` | | `AgentLoop` leaf | `AugmentSig`, `SetInstructionDefault`, `SwapToPredict`, `SetStop`, plus one `AddTool { tool }` or `RemoveTool { tool }` entry per program tool | | Any non-root node that is not a `Refine` judge | `WrapRetry` (judges must stay bare leaves) | | Any `Seq` step | `Remove` | The menu is purely structural — data-flow legality (whether a removal orphans a downstream binding) is still `validate()`'s call, surfaced by `edited`. A stale id yields an empty menu. This is exactly how the shipped [Structural optimizer](/docs/optimizers/structural) proposes edits: it serializes the menu, has a reflection LM choose one entry, applies the choice through `edited`, and gates the child against the parent on a shared minibatch. ## `migrate_overlay`: carrying tuned values across an edit ```rust theme={null} pub fn migrate_overlay(parent: &Program, overlay: &Overlay, child: &Program) -> Overlay ``` An edit changes the program hash, so overlays minted against the parent no longer apply to the child. `migrate_overlay` carries value-level progress across the structural change: for every entry in the overlay, it re-mints the entry against the child when the child has a slot at the same path and kind whose owning leaf/tool still has a *carrying* signature — inputs identical (names and types, in order) and every parent output present in the child's outputs. Outputs may widen: that is what lets instruction and demos survive `AugmentSig` (demo rows still map onto the base fields; the new field is simply absent from the row). `ModelRef` entries are re-minted by model *name*, not ordinal. `ToolSet` entries are re-minted by tool name and intersected with what the child's agent still declares — partial survival carries the selection forward; a selection with no survivors is dropped. Entries that no longer fit are dropped; a base-mismatched overlay yields an empty result. ```rust theme={null} use dspy_rs::ir::{Edit, migrate_overlay}; let leaf = program.leaf_id("drafter").expect("leaf exists"); let child = program.edited(&[Edit::SetInstructionDefault { leaf, text: "Answer in one short sentence.".into(), }])?; let carried = migrate_overlay(&program, &tuned_overlay, &child); ``` ## See also * [Program and nodes](/docs/components/program-and-nodes): the value half — params, `Overlay`, and `bake` * [Structural](/docs/optimizers/structural): the shipped optimizer over this calculus — LM-guided edit choice, `migrate_overlay`, minibatch gating * [Optimizer engine](/docs/components/optimizer-engine): how candidates are evaluated; a structural optimizer proposes `Edit`s where a prompt optimizer proposes overlays * [Runtime](/docs/components/runtime): loading and running the edited program * [The .dsrs file](/docs/components/dsrs-file): the canonical text the child prints to # Evaluation Source: https://dsrs.herumbshandilya.com/docs/components/evaluation TypedMetric, Eval, and the trainset evaluation loop Evaluation is how you tell an optimizer what "good" means. The `evaluate` module provides a metric trait, a result type, a concurrent evaluation loop, and helper functions that build rich textual feedback. A metric sees fully typed data on both sides: the trainset row with all its gold data and the prediction as the module actually produced it. ## Writing and running a metric A minimal exact-match metric for a QA task, over a [row struct](/docs/components/data) trainset: ```rust theme={null} use anyhow::Result; use dspy_rs::{Eval, Example, Predict, Predicted, Trace, TypedMetric}; #[derive(Example, Clone, Debug, serde::Serialize)] struct QARow { question: String, answer: String, } struct ExactMatchMetric; impl TypedMetric> for ExactMatchMetric { async fn evaluate( &self, example: &QARow, prediction: &Predicted, _trace: Option<&Trace>, ) -> Result { let expected = example.answer.trim().to_lowercase(); let actual = prediction.answer.trim().to_lowercase(); Ok(Eval::score((expected == actual) as u8 as f64)) } } ``` `evaluate_trainset` runs the module on every example, scores each rollout with the metric, and `average_score` reduces the results to one number: ```rust theme={null} use dspy_rs::{average_score, evaluate_trainset}; let evals = evaluate_trainset(&module, &trainset, &ExactMatchMetric).await?; println!("Average: {:.3}", average_score(&evals)); ``` ## TypedMetric `TypedMetric` is the metric trait. It is generic over `E` (the trainset row — any struct you like, see [Data](/docs/components/data)) and `M: Module`, so the metric sees fully typed data: the row with all its fields, and the prediction as the module actually produced it (for example `WithReasoning` from `ChainOfThought`). Because the metric receives the row rather than a signature-shaped pair, gold data need not fit the module's output type — a row can carry metric-only fields the module never sees, such as HotpotQA supporting facts. ```rust theme={null} pub trait TypedMetric: Send + Sync where M: Module, { async fn evaluate( &self, example: &E, prediction: &Predicted, trace: Option<&Trace>, ) -> Result; // Optional; the default returns no span scores. async fn evaluate_spans( &self, example: &E, prediction: &Predicted, trace: &Trace, ) -> Result> { Ok(Vec::new()) } } ``` | Argument | Meaning | | ------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `example` | The trainset row under evaluation: the full `E`, including gold labels and metric-only fields (the ground truth) | | `prediction` | The module's `Predicted` | | `trace` | The rollout's execution [`Trace`](/docs/components/traces) when the caller captured one. The evaluation loop always passes `Some`; direct callers may pass `None`. Slice it per component with `trace.for_component("retriever")` | Return `Eval::score(f64)` for a numerical score, `Eval::with_feedback(f64, text)` to also explain why. Scores are 0.0 to 1.0 by convention. ## Per-span credit `evaluate` assigns one score to the whole rollout. For a multi-step module that single score over-credits: a good final answer marks every intermediate `Predict` call as good, including a step a later call had to recover from. `evaluate_spans` is the optional hook for per-span credit. The evaluation loop calls it once per traced rollout, after `evaluate`, and stamps each returned `Eval` onto its span (`Span::eval`); pairs whose id is not in the trace are ignored. ```rust theme={null} async fn evaluate_spans( &self, example: &QARow, _prediction: &Predicted, trace: &Trace, ) -> Result> { // Score each draft call on its own answer; the refine step may have // recovered from a bad one. Ok(trace .for_component("draft") .filter_map(|span| { let answer = span.output.as_ref()?.get("answer")?.as_str()?; let score = (answer == example.answer) as u8 as f64; Some((span.id, Eval::score(score))) }) .collect()) } ``` Demo harvesting (`BootstrapFewShot`, `MIPROv2`, `SIMBA`) prefers a span's own eval over the rollout score when gating and ranking demo candidates, so a scored-down span stays out of the demo pool even when its rollout won, and a scored-up span qualifies even when its rollout lost. Spans you leave out keep whole-rollout credit, and a metric that implements only `evaluate` behaves exactly as before. See [Optimizers](/docs/components/optimizers) for the harvesting semantics. ## Eval and Rollout `Eval` is defined in the trace module and re-exported by `evaluate`. | Type | Definition | Notes | | --------- | ------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Eval` | `{ score: f64, feedback: Option }` | Constructors: `Eval::score(score)` and `Eval::with_feedback(score, feedback)` | | `Rollout` | `(Eval, Trace)` | One evaluated rollout: the metric result plus the execution trace that produced it, with `Trace::outcome` filled in (serialized output, the `Eval`, duration) | The public evaluation entry points return `Vec`; the traced `Rollout` path is what optimizers consume internally. ## Evaluation functions | Item | Signature | Behavior | | ------------------------------------ | ------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `evaluate_trainset` | `async fn(module: &M, trainset: &[E], metric: &MT) -> Result>` where `E: ToInput + Sync` | Runs the module on every row (projected via `to_input()`) and scores each with the metric. Results come back in trainset order. Any `Module::call` or `TypedMetric::evaluate` failure propagates immediately; for fault-tolerant batching use `forward_all` instead | | `evaluate_trainset_with_concurrency` | `async fn(module, trainset, metric, max_concurrency: usize) -> Result>` | Same loop with an explicit concurrency level. `max_concurrency` LM calls run in flight at once (clamped to at least 1). Use `1` for strictly sequential evaluation on rate-limited providers | | `DEFAULT_EVAL_CONCURRENCY` | `const usize = 16` | Concurrency used by `evaluate_trainset` | | `average_score` | `fn(evals: &[Eval]) -> f64` | Arithmetic mean of scores; returns `0.0` for an empty slice | Each example runs inside a trace capture scope and the metric receives that rollout's `Trace`. Metric evaluation itself happens outside the scope, so LM-as-judge metrics do not pollute the execution trace. ## Metrics and optimizers Optimizers call the evaluation loop internally; the metric you hand them determines what they can do with the results. | Metric style | Constructor | Sufficient for | | ------------------- | -------------------------------- | ---------------------------------------------------------------------------------------------------------- | | Score-only | `Eval::score(f64)` | `COPRO`, `MIPROv2`, and any optimizer that ranks candidates by `average_score` | | Score plus feedback | `Eval::with_feedback(f64, text)` | Required by `GEPA`, which feeds the textual feedback into its reflection step to guide evolutionary search | The feedback helpers above exist mainly to serve GEPA: a metric that explains why a rollout scored low gives the reflection model something concrete to fix. See [Optimizers](/docs/components/optimizers) for the optimizer-side contract. ## See also * [Optimizers](/docs/components/optimizers) * [Traces](/docs/components/traces) * [Data](/docs/components/data) * [GEPA](/docs/optimizers/gepa) * [Example: evaluate on HotpotQA](https://github.com/krypticmouse/DSRs/blob/main/crates/dspy-rs/examples/03-evaluate-hotpotqa.rs) * [Example: optimize on HotpotQA](https://github.com/krypticmouse/DSRs/blob/main/crates/dspy-rs/examples/04-optimize-hotpotqa.rs) * [Example: GEPA sentiment](https://github.com/krypticmouse/DSRs/blob/main/crates/dspy-rs/examples/09-gepa-sentiment.rs) * [Example: GEPA LLM judge](https://github.com/krypticmouse/DSRs/blob/main/crates/dspy-rs/examples/10-gepa-llm-judge.rs) # Functional DSRs (fx) Source: https://dsrs.herumbshandilya.com/docs/components/fx Author harnesses as plain async functions with named predict call sites and injected Params In the functional lane, a harness is a plain async function. Optimizable parameters live outside the function in a `Params` value, in the spirit of JAX: pure functions over inputs, with a params pytree injected ambiently per call tree. Predictors are addressed by name instead of struct field path, and the same names appear as trace span components. The `fx` lane is experimental. The struct world (`Predict` fields plus `Module::forward`) remains fully supported; `fx` is an additional authoring style layered on the same machinery, and the two styles share the trace format and the `ModuleState` persistence format. ```rust theme={null} use dspy_rs::fx; async fn pipeline(question: String) -> Result, PredictError> { let draft = fx::predict::("drafter", DraftInput { question }).await?; fx::predict::("refiner", RefineInput { draft: draft.answer.clone() }).await } ``` ## `fx::predict` `fx::predict::(name, input)` is the atomic LM call of the lane: one signature, one named parameter slot, one prediction. ```rust theme={null} pub async fn predict(name: &str, input: S::Input) -> Result, PredictError> where S: Signature, S::Input: Schema, S::Output: Schema, ``` Configuration (instruction override plus demos) comes from the ambient `Params`; with no scope active, the signature defaults apply. The LM resolves exactly as struct-based `Predict` calls do, through the globally configured LM. Under a `capture()` scope the span records `name` as its component, so traces from functional harnesses are addressable by the same names an optimizer would mutate. Internally, resolved predictors are cached by `(signature type, name, config hash)`, so a hit reuses a fully warmed `Predict` instead of rebuilding per call. The cache is capped at 1024 entries and cleared when full. If a `Params` entry does not fit the signature, `predict` returns a `PredictError` reporting that the params do not fit. ## `Params` `Params` is the optimizable state of a functional harness: named `PredictState` values keyed by the names passed to `predict`. Evaluating a different candidate means injecting a different `Params` value, never mutating a module in place. | Method | Behavior | | ----------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | | `new()` | Empty parameter set. | | `set(name, state)` | Sets the full `PredictState` (instruction plus demos) for a named predictor. | | `set_instruction(name, instruction)` | Overrides just the instruction, preserving any demos already set for that name. | | `clear_instruction(name)` | Explicitly resets the name's instruction to the signature default — wins over any instance override when injected ambiently. | | `set_demos(name, rows)` | Sets the demo rows (flat JSON objects) as an explicit set: an empty vec means "no demos", overriding instance demos. | | `get(name)` | Returns `Option<&PredictState>` for the name. | | `is_empty()` | True when no entries are set. | | `to_module_state()` | Converts to `ModuleState`, the persistence format shared with struct-based modules. | | `from_module_state(state)` | Builds `Params` from a saved `ModuleState`. | | `bind(program)` (`ir` feature) | Binds name-keyed params against a compiled `Program` into an `ir::Overlay` — how a module-lane `Candidate` becomes evaluable on a program target. | | `from_overlay(program, overlay)` (`ir` feature) | The inverse: unbinds an `ir::Overlay` into `Params`. | Because `Params` round-trips losslessly through `ModuleState::save` and `ModuleState::load`, persistence works across both authoring styles. `Params` is also the optimizer's candidate-injection currency for struct-held modules: each `Predict` leaf consults the ambient `Params` at call time and binds the entry matching its component name (the name stamped by `Predictors` discovery or `PredictBuilder::named`), with ambient values winning over instance state per slot. See [Optimizers](/docs/components/optimizers). ## `with_params` ```rust theme={null} pub async fn with_params(params: Params, fut: Fut) -> Fut::Output ``` Runs a future with `params` as the ambient parameter set for every `predict` call inside it. The scope is a tokio task-local, mirroring trace capture: only `predict` calls on the same task see the params, spawned subtasks do not inherit them, and nesting replaces the outer scope for the inner future. This is what enables concurrent candidate evaluation. The harness function takes nothing by `&mut`; each candidate is a `Params` value injected around an otherwise identical call, so different candidates can run on separate tasks at the same time against the same code. ```rust theme={null} let mut params = fx::Params::new(); params.set_instruction("drafter", "Draft a thorough answer."); let out = fx::with_params(params, pipeline("hi".into())).await?; ``` ## `FnModule` and `fx::module` `fx::module(f)` wraps an async function as a `Module` (returning `FnModule`), so functional harnesses plug into `evaluate_trainset`, metrics, optimizers, and every other module consumer. The input and output types must implement `Schema` and `Facet`, and the function must return `Result, PredictError>`. ```rust theme={null} let module = fx::module(|input: DraftInput| pipeline(input.question)); evaluate_trainset(&module, &trainset, &metric).await?; ``` ## `with_overlay` With the `ir` feature enabled, `fx` re-exports `with_overlay` from the IR bridge: ```rust theme={null} pub async fn with_overlay( program: &Program, overlay: &Overlay, fut: Fut, ) -> Result ``` It unbinds an `ir::Overlay` candidate against a `Program` into `Params`, then scopes those params exactly like `with_params`. This lets IR-level candidates drive functional harnesses without translation code at the call site. ## See also * [State](/docs/components/state) * [Evaluation](/docs/components/evaluation) * [Optimizers](/docs/components/optimizers) * [Traces](/docs/components/traces) * [Program and nodes](/docs/components/program-and-nodes) * [Example: functional harness](https://github.com/krypticmouse/DSRs/blob/main/crates/dspy-rs/examples/14-functional.rs) # Holes Source: https://dsrs.herumbshandilya.com/docs/components/holes Plain Rust inside a module, kept in the program graph as a named, typed black box A `#[module]` lowers each step call in its body to a node in a program graph. Lines that are not step calls, plain Rust like uppercasing a string, cannot be described as steps, so DSRs keeps each one as a hole: a named, typed black box. The graph records the hole's name, its inputs, and its output type; the interior stays private. A typed border around an unknown interior is still a contract, like a blender: you do not know the circuitry inside, but you know what goes in and what pours out, and that is enough to build the rest of the program around it. ## Declaring a hole A type-ascribed `let` becomes a hole. That is the whole mechanism: ```rust theme={null} use dspy_rs::{module, predict}; /// Answer the question. #[predict] fn draft(question: String) -> String; /// Rate the text from 1-10. #[predict] fn rate(text: String) -> String; #[dspy_rs::Schema] #[derive(Debug)] pub struct QaOut { pub answer: String, pub upper: String, } #[module] async fn qa(question: String) -> Result { let drafter = draft(question.clone()).await?; let upper: String = { let d: String = drafter.draft; d.to_uppercase() }; let checker = rate(upper.clone()).await?; Ok(QaOut { answer: checker.rate, upper, }) } ``` `drafter` and `checker` are steps. `upper` is not a step call, so it becomes a hole named `upper`. Its input is `drafter.draft`, found automatically, and its output is a `String`. ## Allowed types The ascribed type becomes a field in the program's interface, so it must be simple: * `String` * integers (`i64`, `i32`, and friends) * floats (`f64`, `f32`) * `bool` * `Vec<...>` and `Option<...>` of the above Anything else is a compile error with a hint to pick one of these. ## Type anchors Values that flow into a hole from earlier steps arrive through a decode step at run time, and the compiler sometimes cannot work out their concrete type on its own. The fix is a small inner `let` with a type, as an anchor: ```rust theme={null} let upper: String = { let d: String = drafter.draft; // the anchor d.to_uppercase() }; ``` The anchor must be a type-ascribed `let`. Calling a method on the incoming value instead, for example `ticket.clone()`, fails inference, because the method resolves against a type the compiler does not yet know. `let t: String = ticket;` states the type first, and everything after it infers normally. ## Two kinds of holes program frontdesk sum drafter in ticket: string out clean: string here be dragons js`...` sandboxed: travels inside the map your binary extern "a90e86e4381f68a9" native: anchored to one binary exact borders, private interior The examples below use a hole named `clean` that scrubs email addresses out of a ticket string. ### Native holes A hole written in a `#[module]` body is compiled Rust, and machine code cannot travel in a text artifact. The printed program therefore records only the typed border and a fingerprint of the code, marked `extern`: ```text theme={null} clean = hole clean_hole (ticket = $.ticket) caps [] extern "a90e86e4381f68a9" ``` Whoever loads this file must supply an implementation for `clean`: a host binds its native holes by name, and the 16-hex fingerprint lets it verify it holds the same code the file was printed against, not merely something with a matching name. Next to the binary that defined it, the program runs. Loaded anywhere else, it fails immediately with a "host hole unbound" error. ### Sandboxed holes A sandboxed hole carries its implementation inside the file, as JavaScript, executed in a sandbox by whatever host loads it: ````text theme={null} clean = hole Scrub (ticket = $.ticket) caps [] js``` (a) => ({ clean: a.ticket .split(/\s+/) .map((w) => (w.includes("@") ? "[email]" : w)) .join(" "), }) ``` ```` Same typed border, but the interior travels with the file. The portability rule: a file with only steps and sandboxed holes runs anywhere; a file with a native hole runs only beside the binary that defined it. The file tells you which kind you are holding. ## Inspecting holes Every module lists its holes in a constant called `OPACITY`. For a module `frontdesk` with the `clean` hole above: ```rust theme={null} for hole in frontdesk::OPACITY { println!("{} ({}): {}", hole.name, hole.kind, hole.excerpt); } ``` ```text theme={null} clean (host): { let t : String = ticket; t.split_whitespace().map(| w | if w.contains('@') { "[email]" } else { w }).collect :: < Vec < & str > > ().join(" ") } ``` Each entry carries the hole's name, its kind, the actual source excerpt, and the reason it could not be lowered to a step. Nothing becomes opaque in silence. ## Forbidding holes `deny_holes` makes any line that is not a step call a compile error: ```rust theme={null} #[module(deny_holes)] async fn qa(question: String) -> Result { // only `let x = step(args).await?;` lines allowed here } ``` ## The optimization cost Holes are opaque to optimizers. An optimizer can rewrite an instruction because an instruction is data with an address; it cannot rewrite the inside of a hole, because there is nothing there to read. Every hole subtracts from the surface an optimizer can improve. Prefer fewer and smaller holes: each line moved out of a hole into a visible step is a line an optimizer can tune and any host can run. Keep the interiors small enough that what stays opaque is genuinely the part with nothing to say. ## Common mistakes **No type on the `let`.** A hole must declare its output type. `let upper = ...;` without a type is a compile error that tells you to write `let upper: String = ...;`. **Using a whole step result inside a hole.** Holes consume output fields, not whole results. Write `drafter.draft`, not `drafter`. **Expecting the file to carry the code.** It does not. The printed program marks a native hole as `extern`. Portability stops at the binary that defined it. If you need a fully portable program, replace the native hole with a sandboxed hole in the `.dsrs` file, or with another step. ## See also * [The module macro](/docs/components/module-macro) for the body rules that decide what becomes a step and what becomes a hole * [The .dsrs file](/docs/components/dsrs-file) for both hole forms in the grammar * [Capabilities](/docs/components/capabilities) for the `caps []` printed on every hole * [Runtime](/docs/components/runtime) for binding native holes and the load-time checks * [Optimizers](/docs/components/optimizers) for the tuning surface that holes subtract from # LM Source: https://dsrs.herumbshandilya.com/docs/components/lm The provider client: model selection, sampling parameters, retries, response caching, and multi-provider support The `LM` struct is the provider client: a thin wrapper over OpenAI-compatible APIs with built-in retries, optional response caching, and history tracking. You rarely call it directly; a [predictor](/docs/components/predict) uses an [adapter](/docs/components/adapters) to format a [signature](/docs/components/signatures) and send the result through the configured LM, which keeps business logic separate from transport. Configure one globally with `configure(lm)`, or attach one per predictor with `PredictBuilder::lm(...)`. ```rust theme={null} use dspy_rs::{init_tracing, LM}; #[tokio::main] async fn main() -> anyhow::Result<()> { init_tracing()?; // OpenAI - API key automatically read from OPENAI_API_KEY env var let lm = LM::builder() .model("gpt-4o-mini".to_string()) .temperature(0.7) .max_tokens(512) .build() .await?; // Or explicitly provide API key let lm = LM::builder() .model("gpt-4o-mini".to_string()) .api_key("your-api-key".into()) .build() .await?; Ok(()) } ``` ## Responsibilities `LM` handles three core responsibilities: 1. **Configuration** - Stores provider credentials, model selection, and inference parameters (eg: temperature) 2. **API Execution** - Takes pre-formatted `Chat` messages and executes HTTP calls to the LLM provider 3. **Response Caching** - Optionally stores input/output pairs to avoid duplicate API calls ## Structure `LM` is built using the builder pattern. The builder collects an `LMConfig`, the serializable data half, and `build()` initializes the live client. The config holds: * `model` - Model identifier (e.g., "gpt-4o-mini" or "openai:gpt-4o-mini") * `api_key` - Provider API credentials (optional for local servers) * `base_url` - API endpoint URL (optional, inferred from model provider) * `temperature` - Sampling temperature (default: 0.7) * `max_tokens` - Maximum completion tokens (default: 512) * `max_tool_iterations` - Upper bound on tool-loop round trips (default: 10) * `max_retries` - Additional attempts after a transient failure (default: 2) * `retry_base_delay_ms` - Base delay for exponential retry backoff (default: 250) * `cache` - Enable response caching (default: false) The live `LM` adds: * `client` - Internal provider client (initialized during build) * `cache_handler` - Optional response cache (initialized during build if enabled) Cloning an `LM` is cheap - clones share the same HTTP client and cache via `Arc`, making them ideal for concurrent use. ## Construction and configuration The `LM::builder()` must be awaited with `.build().await` because client initialization is async. ### Local server usage For local OpenAI-compatible servers (vLLM, Ollama, etc.), provide `base_url` without an `api_key`: ```rust theme={null} let lm = LM::builder() .base_url("http://localhost:11434".to_string()) .model("llama3".to_string()) .build() .await?; ``` ### Custom OpenAI-compatible endpoints For custom endpoints requiring authentication, provide both `base_url` and `api_key`: ```rust theme={null} let lm = LM::builder() .base_url("https://my-custom-api.com/v1".to_string()) .api_key(my_api_key.into()) .model("custom-model".to_string()) .build() .await?; ``` * **Clone semantics:** `LM` implements `Clone`; clones share the underlying client and cache via `Arc`, so they see the same history while carrying their own config copy. ## API Reference You can browse the full `LM` module reference on [docs.rs](https://docs.rs/dspy-rs/latest/dspy_rs/core/lm/index.html). ## Global vs explicit usage * **Global:** `configure(lm)` sets the process-wide default LM used by predictors. * **Per-instance override:** Attach an LM to a specific predictor with `PredictBuilder::lm(...)`, which bypasses the global; or build a second `LM` and call `configure(lm)` before the specific call. ## Async execution and sync entry * **Async:** LM building and calls are `async`; prefer using an async runtime (Tokio). * **Sync-style:** If you need a plain `fn main`, create a runtime and `block_on` the async work. ```rust theme={null} use dspy_rs::{init_tracing, LM}; #[tokio::main] async fn main() -> anyhow::Result<()> { init_tracing()?; let lm = LM::builder() .model("gpt-4o-mini".to_string()) .build() .await?; Ok(()) } ``` ```rust theme={null} fn main() -> anyhow::Result<()> { dspy_rs::init_tracing()?; let rt = tokio::runtime::Runtime::new()?; rt.block_on(async move { let lm = LM::builder() .model("gpt-4o-mini".to_string()) .build() .await?; Ok(()) }) } ``` ## Inspecting history ```rust theme={null} let history = lm.inspect_history(3).await; for entry in history { println!("Prompt: {}", entry.prompt); println!("Raw output: {:?}", entry.raw_output); } ``` > `inspect_history` requires caching to be enabled (`.cache(true)`); it panics on an LM built without caching. Entries are `CacheEntry` values served by `ResponseCache`; see [Utils](/docs/components/utils). Only tool-free calls are cached, since tool loops execute side-effectful user code. ## Configuration options All `LM` builder parameters have sensible defaults, so you only need to override what you need. | Parameter | Type | Default | Notes | | --------------------- | ---------------- | ---------------------- | --------------------------------------------------------------------------------------------- | | `model` | `String` | `"openai:gpt-4o-mini"` | Supports "provider:model" format or bare model name (defaults to OpenAI) | | `api_key` | `Option` | `None` | Provider API key; omit for local servers | | `base_url` | `Option` | `None` | Custom endpoint URL; auto-detected from model provider if not provided | | `temperature` | `f32` | `0.7` | Higher values increase randomness | | `max_tokens` | `u32` | `512` | Upper bound on completion tokens | | `max_tool_iterations` | `u32` | `10` | Upper bound on tool-loop round trips per call | | `max_retries` | `u32` | `2` | Additional attempts after a transient failure (429/5xx/network/timeout); `0` disables retries | | `retry_base_delay_ms` | `u64` | `250` | Base delay for exponential backoff between retries, plus up to 50% jitter | | `cache` | `bool` | `false` | Enables response caching and `inspect_history` support | ### Example with custom settings ```rust theme={null} // API key automatically read from ANTHROPIC_API_KEY env var let lm = LM::builder() .model("anthropic:claude-3-5-sonnet-20241022".to_string()) .temperature(0.3) .max_tokens(1_024) .cache(true) .build() .await?; ``` ### Provider Support DSRs supports multiple LLM providers through [Rig](https://github.com/0xPlaygrounds/rig). Use the `provider:model` format to specify which provider to use. Bare model names default to OpenAI. **Supported providers:** * `openai` - OpenAI models (requires `OPENAI_API_KEY`) * `anthropic` - Anthropic models (requires `ANTHROPIC_API_KEY`) * `gemini` - Google Gemini models (requires `GEMINI_API_KEY`) * `groq` - Groq models (requires `GROQ_API_KEY`) * `openrouter` - OpenRouter (requires `OPENROUTER_API_KEY`) * `ollama` - Local Ollama models (no API key required) **API keys are automatically read from environment variables.** You only need to provide `.api_key()` if you want to override the default environment variable. You can also use `base_url` to connect to any OpenAI-compatible server (vLLM, LiteLLM, etc.). ### Usage examples ```rust theme={null} // Anthropic - reads from ANTHROPIC_API_KEY env var let lm = LM::builder() .model("anthropic:claude-3-5-sonnet-20241022".to_string()) .build() .await?; // Google Gemini - reads from GEMINI_API_KEY env var let lm = LM::builder() .model("gemini:gemini-2.0-flash-exp".to_string()) .build() .await?; // Groq - reads from GROQ_API_KEY env var let lm = LM::builder() .model("groq:mixtral-8x7b-32768".to_string()) .build() .await?; // OpenAI (or just use model name directly) - reads from OPENAI_API_KEY env var let lm = LM::builder() .model("gpt-4o".to_string()) // defaults to OpenAI .build() .await?; // Ollama (local, no API key needed) let lm = LM::builder() .model("ollama:llama3".to_string()) .build() .await?; // OpenRouter - reads from OPENROUTER_API_KEY env var let lm = LM::builder() .model("openrouter:anthropic/claude-3-opus".to_string()) .build() .await?; ``` All provider integrations are powered by [Rig](https://github.com/0xPlaygrounds/rig), which handles the provider-specific API details. ## Tool sets and Code Mode `LM::call` accepts tools directly, but repeated calls with a fixed set of tools should build a `ToolSet` once and reuse it via `LM::call_with_toolset`. A `ToolSet` pre-fetches every tool definition and indexes the executors by name. | Constructor | Signature | Purpose | | --------------------------- | -------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------- | | `ToolSet::build` | `async fn build(tools: &[Arc]) -> ToolSet` | Fetches every tool definition once and indexes executors by name; duplicate names keep the first tool | | `ToolSet::from_definitions` | `fn from_definitions(definitions: Vec) -> ToolSet` | Definitions only, no executors; for caller-managed loops that execute tools themselves | | `ToolSet::code_mode` | `async fn code_mode(tools: Vec>, config: SandboxConfig) -> Result` | Collapses the tools into a single sandboxed `run_js` tool; requires the `code-mode` feature | With `ToolSet::code_mode`, instead of emitting one JSON tool call per step, the model writes JavaScript against the tools as a JS API and composes their results in one execution. The returned set drops into any tool loop (`LM::call_with_toolset`, `Predict`) exactly like a normal `ToolSet`. It errors if two tool names mangle to the same JS identifier. See [Code Mode](/docs/components/code-mode) for the full sandbox surface. ## See also * [Predict](/docs/components/predict) * [Code Mode](/docs/components/code-mode) * [Utils](/docs/components/utils) # The Module Macro Source: https://dsrs.herumbshandilya.com/docs/components/module-macro Author LM calls as bodyless functions and whole pipelines as function bodies: #[predict], #[cot], and #[module] The function-authoring lane declares LM calls as bodyless Rust functions and whole pipelines as ordinary function bodies. `#[predict]` and `#[cot]` turn one function signature into one model call; `#[module]` compiles a function body that chains those calls into an IR [Program](/docs/components/program-and-nodes). The macro reads each function once at expansion and emits both the runnable code and its program form, so the two cannot drift. ## Steps A step is one model call declared as a function with no body. The function is the contract; the framework writes the prompt and parses the answer. ```rust theme={null} use dspy_rs::predict; /// Answer the question. #[predict] fn answer(question: String) -> String; ``` Call it like a normal async function: ```rust theme={null} let out = answer("What is DSRs?".to_string()).await?; println!("{}", out.answer); ``` The mapping is the [signature](/docs/components/signatures) idea applied to a function: | You write | It becomes | | --------------- | ------------------------------------------ | | The doc comment | The instruction the model reads | | Each parameter | An input field, same name and type | | The return type | One output field, named after the function | So `fn answer(question: String) -> String` gives the model an input called `question` and asks for an output called `answer`, which is why the result reads as `out.answer`. A step can take several inputs: ```rust theme={null} /// Judge whether the answer is correct. #[predict] fn judge(question: String, answer: String) -> bool; ``` Rules, each a compile error when broken: the function is bodyless and ends with `;`; do not write `async` (the generated function is async automatically); no generics, no `self`, parameters must be plain identifiers; at least one input parameter and an explicit return type are required. ### Chain of thought `#[cot]` is the chain-of-thought variant of `#[predict]`: the model produces a `reasoning` field before the output. Same rules, same options. The result carries the extra field and auto-derefs to the output. ```rust theme={null} use dspy_rs::cot; /// Summarize the text. #[cot] fn summarize(text: String) -> String; let out = summarize(long_text).await?; println!("{}", out.reasoning); // the model's thinking println!("{}", out.summarize); // the answer ``` ### Model handles Both attributes accept one option, `model = "@name"`, which selects a declared model by reference; the leading `@` is stripped, and anything else in the attribute is a compile error. ```rust theme={null} /// Write a warm, concrete reply to this support ticket. #[cot(model = "@strong")] fn draft(ticket: String, summary: String) -> String; ``` The name is a handle, not a hardcoded model id: which model `strong` really is gets decided by whoever runs the program. Steps with no `model = ...` ride the `default` model from your `configure(...)` line automatically. Named handles have no such fallback; see [Named model binding](#named-model-binding). ### The name is the link The function name follows the step everywhere: [`fx::Params::set_instruction("answer", ...)`](/docs/components/fx) overrides its instruction, trace spans record the same name, and the generated signature lives in a module of the same name (`answer::Sig`). One exception: when a step is called inside a `#[module]` body, the trace and step name is the `let` binding name for that call, so `let drafter = draft(...).await?;` records as `drafter`. ## Modules `#[module]` compiles an ordinary async Rust function body into an IR `Program`. One parse, two projections: the executable function (typed boundary, runs through the interpreter, reads the ambient overlay) and `name::program()`, the same pipeline as a servable, optimizable, printable artifact. async fn frontdesk your function, written once #\[module] one parse, at build time the function callable, awaited, exactly as written the program printable, diffable, servable data two projections of one source: drift is not unlikely, it is impossible ```rust theme={null} use dspy_rs::module; #[dspy_rs::Schema] #[derive(Debug)] pub struct DeskOut { pub summary: String, pub reply: String, } #[module] async fn frontdesk(ticket: String) -> Result { // plain Rust: scrub email addresses before anything leaves let clean: String = { let t: String = ticket; t.split_whitespace() .map(|w| if w.contains('@') { "[email]" } else { w }) .collect::>() .join(" ") }; let sum = summarize(clean.clone()).await?; let drafter = draft(clean.clone(), sum.summarize.clone()).await?; Ok(DeskOut { summary: sum.summarize, reply: drafter.draft, }) } ``` It runs the way it reads: ```rust theme={null} let out = frontdesk(ticket).await?; println!("{}", out.reply); ``` The function must be `async`, cannot be generic, and must return `Result` where `Err: From`. Two options: `caps("...", ...)` declares the program's [capability](/docs/components/capabilities) ceiling, and `deny_holes` makes any non-step expression a compile error instead of a hole. ### Accepted body shapes The body is straight-line only, built from three shapes: * `let x = step(args).await?;` where `step` is a `#[predict]`, `#[cot]`, or [`#[agent]`](/docs/components/tools-and-agents) function. Arguments must be ports: function parameters, prior `binding.field` accesses, or literals; `.clone()` and `&` wrappers are stripped. * `let y: SimpleType = ;` for plain Rust. The type ascription is required and must be simple: `String`, `bool`, an integer width from `i8` through `i64` or `u8` through `u32`, `f32`/`f64`, or `Vec<...>`/`Option<...>` of those. The expression becomes a typed extern [hole](/docs/components/holes), a named boundary around code the IR cannot describe as data; the hole page covers how holes bind and travel. * A tail expression `Ok(Struct { field: port, ... })` giving the program's output bindings. ### The printed program ```rust theme={null} println!("{}", frontdesk::program().to_dsrs()); ``` The output below was captured from a real run of this pipeline: ```text theme={null} dsrs 1 program frontdesk // model and signature declarations trimmed main: Main = seq { clean = hole clean_hole (ticket = $.ticket) caps [] extern "a90e86e4381f68a9" sum = predict summarize @default (ticket = clean.clean) drafter = cot draft @strong (ticket = clean.clean, summary = sum.summarize) out { summary = sum.summarize, reply = drafter.draft } } ``` Every step appears under its `let` binding name (`sum`, `drafter`), so the program speaks in your vocabulary. Every wire is a line of Rust written as a connection: `sum.summarize` flowing into `draft`'s `summary` input is the `sum.summarize.clone()` argument. The `@strong` handle survives intact, and the privacy scrub prints as `hole clean_hole` with an `extern` fingerprint in place of its code. This is the build-time design point stated plainly: the macro reads the function once at expansion and emits both the runnable function and the `Program`. The two are projections of one source, so they cannot drift. The printed text is the canonical `.dsrs` form; its grammar and identity rules live in [The .dsrs file](/docs/components/dsrs-file). ## Generated items For a step `#[predict] fn answer(question: String) -> String;`: | Item | What it is | | ----------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- | | `answer::Sig`, `answer::SigInput`, `answer::SigOutput` | The derived signature and its input and output structs. | | `answer::__dsrs_step()` | Step metadata, consumed when the call is lowered inside a `#[module]` body. | | `async fn answer(question: String) -> Result, PredictError>` | The callable function. It calls [`fx::predict`](/docs/components/fx) with the function's name as its params slot. | `#[cot]` generates the same items; its function returns `Result>, PredictError>`, and `WithReasoning` auto-derefs to the output. For `#[module] async fn frontdesk(...)`, inside a module named after the function: | Item | What it is | | --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `OPACITY` | A constant slice of `HoleReport` entries, one per hole-ized expression (name, kind, source excerpt, reason). | | `program()` | The lowered `&'static Program`, linked at first use. Panics on link errors. | | `try_program()` | The non-panicking form, returning `Result<&'static Program, &'static ModuleBuildError>`. | | `env()` | The [`RuntimeEnv`](/docs/components/runtime) the module needs: its declared caps granted, the `default` model bound from the global settings when configured, `#[tool]` implementations bound, and extracted host holes bound. | | generated test | A `#[cfg(test)]` test named `module_program_links_and_validates` that prints every `OPACITY` entry and fails the suite if `try_program()` errors. | It also emits the executable `async fn` itself, which loads the interpreter once, reads the ambient overlay via `current_overlay()`, and runs with a default budget. ## Named model binding Steps with no `model = ...` ride the `default` model from your `configure(...)` line automatically. A named handle has no such fallback, by design: bind it at load with `frontdesk::env().bind_model("strong", strong)` and run through `Interpreter::load`, or the load refuses, by name. The runnable example [22-frontdesk-module.rs](https://github.com/krypticmouse/DSRs/blob/main/crates/dspy-rs/examples/22-frontdesk-module.rs) does exactly this. ## Common mistakes **Adding `async` to a step.** The generated function is already async. Writing `async fn` on a `#[predict]` or `#[cot]` step is a compile error with a clear message: remove `async`. ```rust theme={null} // Wrong #[predict] async fn draft(question: String) -> String; // Right #[predict] fn draft(question: String) -> String; ``` **Adding a body to a step.** A step has no body; the framework builds the behavior from the signature. If you want to write the body yourself, you want a [tool](/docs/components/tools-and-agents), not a step. **Forgetting the return type.** The return type is the output field. Without it there is nothing for the model to produce, so it is a compile error. **No inputs.** A step needs at least one input parameter. ## See also * [Signatures](/docs/components/signatures) for the struct form of the same contract * [Holes](/docs/components/holes) for what plain Rust inside a module becomes * [Program and nodes](/docs/components/program-and-nodes) for what `program()` returns in memory * [The .dsrs file](/docs/components/dsrs-file) for the printed text form and its grammar * [Runtime](/docs/components/runtime) for `RuntimeEnv`, binding, and `Interpreter::load` * Example: [22-frontdesk-module.rs](https://github.com/krypticmouse/DSRs/blob/main/crates/dspy-rs/examples/22-frontdesk-module.rs) # Modules Source: https://dsrs.herumbshandilya.com/docs/components/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`](/docs/components/predict)), `ChainOfThought`, and any struct you compose from them. Swapping `Predict` for `ChainOfThought` 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::::new(); let plain = predict.call(QAInput { question: "Why is the sky blue?".into() }).await?; println!("{}", plain.answer); let cot = ChainOfThought::::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` returns `WithReasoning` 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, answer: ChainOfThought, } dspy_rs::predictors!(Rag { condense, answer }); impl Module for Rag { type Input = CondenseInput; type Output = WithReasoning; async fn forward(&self, input: CondenseInput) -> Result, 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, PredictError>; async fn call(&self, input: Self::Input) -> Result, 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`](/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( module: &M, inputs: Vec, max_concurrency: usize, ) -> Vec, PredictError>> ``` | Behavior | Detail | | ----------------- | ----------------------------------------------------------------- | | Concurrency | Bounded by `max_concurrency` (`buffer_unordered`). | | Failure isolation | Returns `Vec>`; 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` `ChainOfThought` is pure sugar, a type alias rather than a distinct struct: ```rust theme={null} pub type ChainOfThought = Predict>; pub type ChainOfThoughtOutput = WithReasoning<::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::::new()` or `ChainOfThought::::builder()` (a `PredictBuilder`, see [Predict](/docs/components/predict)). | | Output | `WithReasoning` with fields `reasoning: String` and `inner: S::Output`. | | Access | `Deref`: `result.reasoning` is direct, `result.answer` resolves through deref. | | Demos | `Demo>`; demos must include reasoning text. | | Calls | Single LM call. Reasoning and answer are produced together, not across turns. | 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. ## 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: Schema + Facet + Deref + Send + Sync; }` The GAT maps an inner output `T` to a wrapper carrying the extra fields. | | `Augmented` | A `Signature` with `Input = S::Input` and `Output = A::Wrap`. Instruction, input shape, and field metadata are inherited from `S`; only the output shape changes. | | `AugmentedOutput` | Alias for `::Wrap`. | | Tuple composition | `(A, B)` wraps as `A::Wrap>`; 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 with Deref ``` The derive generates a `With{Name}` 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` 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) # Optimizer Engine Source: https://dsrs.herumbshandilya.com/docs/components/optimizer-engine Engine, OptimizeTarget, Candidate, Budget, RolloutCache, ScoreMatrix, and the Pareto and outcome types every optimizer shares The optimizer engine is the shared evaluation core under every optimizer. Strategies register candidates and ask the engine to evaluate them against an `OptimizeTarget`; the engine handles candidate binding, rollout fan-out, caching, budget accounting, and score bookkeeping. Every optimizer in DSRs (COPRO, GEPA, MIPROv2, SIMBA, bootstrap, Structural) is a thin strategy over this one core. There is **one** `Engine`. What varies is the target — the lane-erased pair of (thing under optimization, evaluation harness): | Lane | Constructor | Candidate currency | Winner | | --------------------------- | --------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------- | | Module lane | `OptimizeTarget::module(&mut module, &trainset, &metric)` | `Candidate` (name-keyed slots), injected *ambiently* per rollout via `fx::with_params` — never applied by mutation | Installed onto the module through `PredictorInfo::load_state`, once, by `OptimizeTarget::install` | | Program lane (`ir` feature) | `OptimizeTarget::program(&interp, &examples, &metric)` | `ir::Overlay` (or a `Candidate` bound through `fx::Params::bind`), read through at render time | Retrievable as an `Arc` via `OptimizeTarget::winner_overlay`, for `Program::bake` | Because candidate injection is ambient in both lanes — nothing is ever applied to shared state during evaluation — rollouts for *different candidates* share one bounded-concurrency fan-out. All items on this page are exported from the crate root except where a feature gate is noted. ## `OptimizeTarget<'a>` The thing an optimizer optimizes: a module or a program, packaged with its example set (by reference) and metric. | Method | What it does | | ------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `module(module, trainset, metric)` | Module-lane target: a typed `Module + Predictors`, a `&[E]` trainset (`E: ToInput + Serialize`), and a `TypedMetric`. Runs the **naming pass**: every leaf declared via `Predictors` is stamped with its declared name (`PredictorInfo::set_trace_name`), so trace spans, candidate entries, and persistence all address the same names. | | `module_with_valset(module, trainset, valset, metric)` | Same, with an optional validation set. When `Some`, the valset examples become the *leading* columns and the trainset the trailing ones — the layout GEPA's Pareto bookkeeping uses. | | `program(interp, examples, metric)` | Program-lane target: an interpreter-loaded `Program`, labeled `DemoRow` examples, and a `ProgramMetric`. | | `leaves() -> &[LeafInfo]` | The optimizable leaves' read surface, snapshotted at construction: per leaf, `name`, current `instruction`, `default_instruction`, `demos` as flat JSON rows, and `input_fields`/`output_fields` as `(lm name, docs)` pairs. `LeafInfo::schema_for_reflection()` renders the field contract for reflection prompts. | | `num_examples()`, `has_valset()` | Example-set access. | | `val_columns()`, `train_columns()` | The scoring columns (validation prefix, or every example) and the minibatch-sampling pool (trainset suffix, or every example). | | `install(&winner)` | Installs the winning `Candidate` — the **one** mutation of the run. Module lane: merges each slot into the named leaf's state through `PredictorInfo::load_state`. Program lane: binds the winner to an overlay. | | `winner_overlay()` | The installed winner as a bound `Arc` (program lane only). | | `candidate_outputs(indices, &candidate)` | Runs the given examples under the candidate and returns bare output values, no metric and no trace capture — GEPA's best-output collection. | `ProgramMetric` is the JSON-native sibling of `TypedMetric`: loaded programs have no static output type, so the metric scores the interpreter's output `JsonMap` against a labeled `DemoRow`. ```rust theme={null} pub trait ProgramMetric: Send + Sync { async fn evaluate( &self, example: &DemoRow, output: &JsonMap, trace: Option<&Trace>, ) -> Result; } ``` ## `Engine` Owns the candidate registry, the score matrix, the rollout cache, and the budget meter. Strategies register candidates and call the evaluate methods against a target; the engine handles binding, fan-out, caching, accounting, and bookkeeping. | Method | What it does | | ----------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `new(config)` | Builds an engine from an `EngineConfig`. Examples and metric live on the target, not the engine — one engine can serve successive targets (a `Box` pipeline sharing one budget). | | `evaluate_many(target, candidates, subset)` | Evaluates N registered candidates over `subset` example indices (`None` = the target's full set) in **one** bounded-concurrency fan-out — candidate-level parallelism in both lanes. Cached rollouts return their `Eval` with `trace: None` and consume no budget. Returns `BatchEvalOutcome`. | | `evaluate(target, candidate, subset)` | Single-candidate convenience over `evaluate_many`. Returns `EvalOutcome`. | | `evaluate_gated(target, candidate, minibatch, threshold)` | The minibatch gate (the GEPA/SIMBA acceptance pattern): evaluates on `minibatch`; only a minibatch mean strictly greater than `threshold` promotes to a full-set evaluation. Returns `GateOutcome`. | | `register(candidate)` | Registers a module-lane `Candidate`, deduplicating by content hash. Returns its index; a duplicate returns the existing index. | | `register_overlay(overlay)` | Registers a program-lane `ir::Overlay`, deduplicating by `Overlay::hash()`. Returns its index. | | `candidate(i) -> Option<&Candidate>`, `candidate_hash(i)`, `num_candidates()` | Candidate registry access (`candidate` is `None` for an overlay entry). | | `config()`, `spend()`, `matrix()`, `cache()` | State access. | | `pareto()`, `pareto_over(columns)` | Dominance views over the score matrix (all columns, or a subset). | | `budget_allows(n)` | Whether `n` more rollouts fit the remaining budget. | | `charge(metric_calls, lm_calls)` | Charges auxiliary spend the engine did not run itself: reflection LM calls, teacher passes. | | `peak_candidate_concurrency()` | High-water mark of *distinct candidates* with rollouts in flight simultaneously — the parallelism gauge. | The metric runs outside the trace capture scope, so LM-as-judge metrics do not pollute the execution trace. If the uncached portion of a batch does not fit the remaining budget, the engine runs nothing, leaves spend unchanged, and returns `BudgetExhausted`. ## `EngineConfig` | Field | Default | Meaning | | -------------------- | --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | | `concurrency: usize` | `16` (`DEFAULT_EVAL_CONCURRENCY`) | Rollouts in flight at once within one evaluation batch. | | `budget: Budget` | `Budget::unlimited()` | Hard spend caps; the engine stops cleanly when a batch would not fit. | | `cache_salt: u64` | `0` | Folded into every cache key. Bump it when changing LM sampling settings outside the candidate; sampling params are not part of candidate identity. | ## Candidates A `Candidate` is data: `slots: BTreeMap` mapping leaf name (the `Predictors` contract name) to a partial per-leaf configuration, plus a stable content hash. It is cheap to clone, serializable, and **never applied by mutation**: the engine scopes it ambiently around each rollout (`fx::with_params`); the single mutating step is the caller-driven final `OptimizeTarget::install`. The empty candidate (`Candidate::default()`) is the baseline, the module exactly as it is. | Item | Signature or fields | | --------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `CandidateSlot` | `instruction: Option`, `clear_instruction: bool`, `demos: Option>`. Unset fields leave the leaf's incumbent value untouched; `clear_instruction` explicitly resets to the signature default, winning over any instance override. Demo rows are flat JSON objects, input and output fields merged. | | `Candidate::new()` | The empty candidate. | | `Candidate::with_instruction(name, text)` | Single-predictor instruction candidate, the COPRO and MIPRO case. | | `set_instruction(name, text)`, `clear_instruction(name)`, `set_demos(name, rows)` | Builder-style mutators. `set_demos` with an empty vec clears the demo set. | | `instruction_of(name)`, `demos_of(name)` | Read accessors. | | `is_empty()`, `stable_hash()` | The hash is canonical: identical content hashes identically across processes and map orderings. It is the cache identity. | | `to_params()` | Converts to the ambient-injection currency: name-keyed [`fx::Params`](/docs/components/fx) with explicit clears preserved. `fx::Params::bind(program)` turns the same value into an `ir::Overlay` for the program lane. | `CandidateSlot` (instruction plus demos per leaf name) is a different type from the IR `ir::Overlay`, which maps `ParamId` to `ParamValue` over a compiled `Program`. The program lane consumes the IR type; `Candidate::to_params()` + `Params::bind` is the bridge between them. ## `Budget` and `Spend` `Budget` sets hard caps on evaluation spend; `None` means unlimited. | `Budget` field | Metered as | | --------------------------------- | ------------------------------------------------------------------------------------------------ | | `max_metric_calls: Option` | One per executed rollout. Cache hits do not re-run the metric. | | `max_lm_calls: Option` | One unit per executed rollout, plus auxiliary charges via `charge`. | | `max_tokens: Option` | Checked against recorded token usage; the engine refuses the next batch once the cap is reached. | `Budget::allows(&spend, upcoming_rollouts)` reports whether the batch fits. Zero upcoming rollouts always fit, so cache-only batches never stall. Call and metric caps are enforced prospectively (the batch must fit under the cap); the token cap is retrospective (a batch may overshoot, and the following batch is refused). `Spend` is what the engine has consumed so far: | `Spend` field | Meaning | | --------------------- | -------------------------------------------------------- | | `metric_calls: usize` | Metric evaluations executed. | | `lm_calls: usize` | LM call units: executed rollouts plus auxiliary charges. | | `lm_spans: usize` | Exact `Predict` spans observed across captured traces. | | `cache_hits: usize` | Rollouts served from the cache instead of executed. | | `tokens: LmUsage` | Token totals summed from captured span usage. | This `Budget` is not the IR runtime `Budget` documented on [Runtime](/docs/components/runtime). The runtime type caps one program execution: `max_lm_calls: Option`, `max_tokens`, and a `deadline: Option`, enforced call by call inside the run by a `BudgetMeter` with parent chaining for nested agent loops. The optimizer type caps an entire optimization search across all rollouts: it adds `max_metric_calls`, has no deadline, and is checked per batch before anything runs, with consumption tracked in `Spend`. The program lane runs each rollout under the runtime `Budget::unlimited()` while the optimizer `Budget` governs the batch. ## `RolloutCache` In-memory map from a rollout key to its `Eval` (score plus optional feedback). A candidate re-evaluated on a seen example returns the cached `Eval` with no LM call and no metric call. The key recipe is `(baseline, candidate, example, salt)`, formatted as four 16-digit hex hashes joined by colons: | Component | Module lane | Program lane | | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------- | | `baseline` | Content hash of the `predictors()` state snapshot (`{name → PredictState}`), computed once at target construction. Installing a winner and building a new target yields a new baseline, invalidating stale entries. | `program.meta.program_hash`. | | `candidate` | `Candidate::stable_hash()` | `Overlay::hash()` | | `example` | Content hash of the example | Content hash of the `DemoRow` | | `salt` | `EngineConfig::cache_salt` | `EngineConfig::cache_salt` | Public surface: `get`, `insert`, `len`, `is_empty`. ## Score bookkeeping **`ScoreMatrix`** is a per-instance matrix of candidates (rows, registration order) by examples (columns). Cells are `None` until scored. Methods: `new(columns)`, `candidates()`, `examples()`, `ensure_rows(n)`, `record(candidate, example, score)`, `score(candidate, example)`, `row(candidate)`, `mean(candidate)`, `best_by_mean()`, `pareto()`, `pareto_over(columns)`. The column-restricted view supports GEPA-style setups where train and validation examples share one matrix. **`ParetoView`** is a dominance snapshot computed from the matrix. `best_scores()` gives the best score per viewed column; `wins(candidate)` counts columns the candidate wins or ties on (tolerance `1e-6`); `frontier()` lists candidates winning on at least one column; `statistics()` summarizes. A candidate with zero wins is dominated. GEPA samples parents proportional to their Pareto coverage directly from this view. **`ParetoStatistics`** fields: `num_candidates`, `num_examples_covered`, `avg_coverage: f32`, `max_coverage`, `min_coverage`. A healthy search grows `num_candidates` slowly while `avg_coverage` rises; `num_candidates == 1` means the search has collapsed. ## Outcome types | Type | Shape | | ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `RolloutOutcome` | `example: usize`, `eval: Eval`, `trace: Option`. The trace is `None` when the rollout was served from the cache. | | `CandidateEval` | `candidate: usize`, `rollouts: Vec` in request order. `mean()` is the arithmetic mean over the batch (`0.0` when empty); `scores()` collects the raw scores. | | `EvalOutcome` | `Complete(CandidateEval)` or `BudgetExhausted { needed }`. When exhausted, nothing ran and spend is unchanged; `needed` is the uncached rollout count. `completed()` converts to `Option`. | | `BatchEvalOutcome` | `Complete(Vec)`, one per requested candidate in request order, or `BudgetExhausted { needed }`. `completed()` converts to `Option>`. | | `GateOutcome` | `BudgetExhausted { needed }`, `Rejected { minibatch }`, or `Promoted { minibatch, full }`. | ## Rollout mechanics Each program-lane rollout runs `interp.run(input, Some(overlay), Budget::unlimited())` under its own capture scope with `TraceMeta.candidate_hash` set to the candidate's hash and a `program` tag carrying the program hash. Each module-lane rollout scopes the candidate's `fx::Params` ambiently around the whole traced rollout (`rollout_traced`), so every `Predict` leaf binds its own entry at call time. After the metric scores a module-lane rollout, any span-level evals it returns from `TypedMetric::evaluate_spans` are stamped onto the trace's spans; demo-harvesting optimizers prefer these over the rollout score (see [Evaluation](/docs/components/evaluation)). Every pending `(candidate, example)` pair — across all candidates, in both lanes — joins one `buffer_unordered` stream bounded by `EngineConfig::concurrency`. ## See also * [Optimizers](/docs/components/optimizers) * [Evaluation](/docs/components/evaluation) * [Runtime](/docs/components/runtime) for the IR `Budget`, `BudgetMeter`, and `Interpreter::run` * [Traces](/docs/components/traces) for `Trace`, `TraceMeta`, and `Eval` * [Program and nodes](/docs/components/program-and-nodes) for `Program` and `ir::Overlay` * [COPRO](/docs/optimizers/copro), [MIPROv2](/docs/optimizers/miprov2), [GEPA](/docs/optimizers/gepa) * Example: [04-optimize-hotpotqa.rs](https://github.com/krypticmouse/DSRs/blob/main/crates/dspy-rs/examples/04-optimize-hotpotqa.rs) * Example: [08-optimize-mipro.rs](https://github.com/krypticmouse/DSRs/blob/main/crates/dspy-rs/examples/08-optimize-mipro.rs) * Example: [09-gepa-sentiment.rs](https://github.com/krypticmouse/DSRs/blob/main/crates/dspy-rs/examples/09-gepa-sentiment.rs) # Optimizers Source: https://dsrs.herumbshandilya.com/docs/components/optimizers The Optimizer trait, every optimizer configuration, and every report type: COPRO, MIPROv2, GEPA, SIMBA, and BootstrapFewShot An optimizer proposes candidates (instruction or demo overlays), evaluates them with your metric on your trainset, and keeps the best. The convenience entry point is each optimizer's `compile_module` method: it takes a module, a training set, and a metric, then searches for better instructions, and in some cases demos, for each `Predict` leaf. After it returns, the winner is installed and calling the module produces better results with no code changes. ```rust theme={null} use dspy_rs::COPRO; let optimizer = COPRO::builder() .breadth(10) .depth(1) .eval_concurrency(16) .build(); optimizer .compile_module(&mut module, &trainset, &metric) .await?; ``` The module must declare its optimizable leaves via [`Predictors`](/docs/components/modules#predictor-discovery-predictors) (one `predictors!` line). All six optimizers are thin strategies over the shared evaluation engine. Candidates are **data, never mutation**: each candidate is a name-keyed `Candidate` injected *ambiently* per rollout (`fx::with_params`) — nothing touches the module during evaluation, so different candidates evaluate concurrently — and the winner is installed exactly once at the end (`OptimizeTarget::install`). The engine types (`Engine`, `Candidate`, `Budget`, `Spend`, `ParetoView`) are documented in [Optimizer engine](/docs/components/optimizer-engine). Five strategies tune parameter values through overlays; the sixth, [`Structural`](#structural), proposes graph edits over the [edit calculus](/docs/components/edit-calculus) and runs on the program lane only. program frontdesk (base, hash 9f3a...) sum drafter drafter.instruction overlay c41 warmth 0.91 overlay c58 warmth 0.82, brevity 0.78 overlay c62 brevity 0.93 each sheet rewrites the same named dial candidates are overlays; the base program is never mutated Step-by-step how-to pages: [COPRO](/docs/optimizers/copro), [MIPROv2](/docs/optimizers/miprov2), [GEPA](/docs/optimizers/gepa), and [Structural](/docs/optimizers/structural). ## The Optimizer trait ```rust theme={null} #[async_trait::async_trait(?Send)] pub trait Optimizer: Send + Sync { fn engine_config(&self) -> EngineConfig { EngineConfig::default() } async fn compile( &self, target: &mut OptimizeTarget<'_>, engine: &mut Engine, ) -> Result; } ``` The trait is **object-safe** by design: optimizers compose (`Box` pipelines can share one `Engine` — one budget, one rollout cache, one score matrix — across stages). The target carries the thing under optimization and its example set *by reference*; the engine carries the spend. `OptimizeTarget` is the lane-erased pair of (thing under optimization, evaluation harness), one of two lanes: * **`OptimizeTarget::module(&mut module, &trainset, &metric)`** — a typed `Module` (+ `Predictors` discovery), a trainset slice, and a `TypedMetric`. The trainset is `&[E]` for any [row type](/docs/components/data) that projects into the module's input via `ToInput`; the `Serialize` bound feeds rollout-cache uids, which content-hash the whole row. `OptimizeTarget::module_with_valset(...)` adds an optional validation set (the layout GEPA's Pareto bookkeeping uses). Construction runs the **naming pass**: every declared leaf is stamped with its declared name, so trace spans, candidate entries, and persistence all address the same names. * **`OptimizeTarget::program(&interp, &examples, &metric)`** — an interpreter-loaded IR [`Program`](/docs/components/program-and-nodes), labeled `DemoRow` examples, and a JSON-native `ProgramMetric`. The winner is retrievable as an `ir::Overlay` (`OptimizeTarget::winner_overlay`) for `Program::bake`. For the common case you never build these by hand — each optimizer's `compile_module(&mut module, &trainset, &metric)` inherent method constructs a module target and a default engine, runs `compile`, and installs the winner. `compile` returns an error when the target has no optimizable leaves, when a metric evaluation fails, or when an LM call fails during candidate evaluation. `compile` returns the `Report` enum (`Report::None`, `Report::Gepa(GEPAResult)`, `Report::Simba(SimbaReport)`, `Report::Bootstrap(BootstrapReport)`, and `Report::Custom(serde_json::Value)` as the third-party extension point), with `into_gepa()`/`into_simba()`/`into_bootstrap()` accessors. The typed `compile_module` sugar unwraps it: | Optimizer | `compile_module` returns | | ------------------ | ------------------------ | | `COPRO` | `()` | | `MIPROv2` | `()` | | `GEPA` | `GEPAResult` | | `SIMBA` | `SimbaReport` | | `BootstrapFewShot` | `BootstrapReport` | `Structural` is the exception to this table and to the trait: it edits program structure, so its input is an interpreter-loaded program rather than a lane-erased target, and its entry point is [`compile_program`](#structural) instead of `compile_module`/`compile`. ## Choosing an optimizer | Optimizer | Strategy | Needs feedback? | Cost | | ------------------ | --------------------------------------------------------- | --------------- | ------------------------------------------ | | `BootstrapFewShot` | One-shot demo harvesting from a teacher pass | No | Low (2 × trainset) | | `COPRO` | Breadth-first instruction search | No | Low (breadth × depth × trainset) | | `SIMBA` | Minibatch introspective ascent (demos + rules) | No | Low (steps × minibatch) | | `GEPA` | Genetic-Pareto evolution with feedback | **Yes** | Medium-high (iterations × eval) | | `MIPROv2` | Trace-guided candidate generation | No | Medium (candidates × trials × trainset) | | `Structural` | LM-guided graph edits over `ir::Edit` (program lane only) | No | Medium (examples + iterations × minibatch) | GEPA is the only optimizer that requires textual feedback from the metric. The others use numerical scores alone. ## COPRO Breadth-first instruction search: generates `breadth` candidate instructions per predictor, evaluates each on the trainset, installs the best, and repeats for `depth` rounds. The base instruction always competes in every round. How-to: [COPRO](/docs/optimizers/copro). | Field | Type | Default | Description | | ------------------ | ------------ | ------- | --------------------------------------------------------------------------------------- | | `breadth` | `usize` | `10` | Candidates per round per predictor. Must be greater than 1; `compile` errors otherwise. | | `depth` | `usize` | `3` | Optimization rounds; each refines the previous best. | | `init_temperature` | `f32` | `1.4` | Currently unused. Reserved for LM-generated candidate diversity. | | `track_stats` | `bool` | `false` | Per-round statistics flag. The report type is `()`, so nothing is returned either way. | | `prompt_model` | `Option` | `None` | Separate LM for generating candidate instructions. | | `eval_concurrency` | `usize` | `16` | Concurrent LM calls during candidate evaluation. | Cost: approximately `breadth × depth × num_predictors × trainset_size` LM calls, minus rollout-cache hits. Runs with an unlimited engine budget. ## MIPROv2 Trace-guided instruction and demo optimizer. Four phases: one traced teacher pass over the trainset; demo bootstrapping from successful spans via the trace name-join; generation of `num_candidates` instruction variants per predictor seeded by prompting tips; evaluation of up to `num_trials` candidates per predictor on one sampled minibatch, keeping the best. Demos are installed before instruction search so candidates are scored against the module as it will actually run. How-to: [MIPROv2](/docs/optimizers/miprov2). | Field | Type | Default | Description | | ------------------------ | ------------- | ------- | ------------------------------------------------------------------------------------------------------------------------ | | `num_candidates` | `usize` | `10` | Instruction variants generated per predictor. | | `num_trials` | `usize` | `20` | Maximum candidates evaluated per predictor. When lower than `num_candidates`, only the first `num_trials` are evaluated. | | `minibatch_size` | `usize` | `25` | Examples per candidate evaluation. | | `max_bootstrapped_demos` | `usize` | `4` | Demos installed per predictor from successful traces. | | `min_demo_score` | `f64` | `0.0` | Minimum score for a span to qualify as a demo source: its own span eval when present, the whole-program score otherwise. | | `eval_concurrency` | `usize` | `16` | Concurrent LM calls during candidate evaluation. | | `seed` | `Option` | `None` | Fixes minibatch sampling. `None` is nondeterministic. | Public helper type: `PromptingTips` (the rotation of prompting best practices appended to candidates, `default_tips()` and `format_for_prompt()`). `compile_module` returns `()`; through the trait, the report is `Report::None`. ## GEPA Genetic-Pareto instruction evolution driven by per-example feedback. Each generation samples a parent proportional to Pareto coverage, re-evaluates it on a trainset minibatch, has a reflection LM (`prompt_model`) rewrite the instruction from the feedback and the mutated component's execution trace, then scores the child on the validation columns. Without a `prompt_model`, mutation degrades to deterministic feedback concatenation. Only instruction space is searched: no demo mutation, no crossover. How-to: [GEPA](/docs/optimizers/gepa). GEPA errors if any `Eval` from the metric has `feedback: None`. Build metrics with `Eval::with_feedback`. | Field | Type | Default | Description | | -------------------- | --------------- | ------- | ----------------------------------------------------------------------------- | | `num_iterations` | `usize` | `20` | Evolutionary generations. | | `minibatch_size` | `usize` | `25` | Examples per parent re-evaluation within each generation. | | `num_trials` | `usize` | `10` | Currently unused. Reserved for multi-child evolution. | | `temperature` | `f32` | `1.0` | Currently unused. Reserved for mutation diversity control. | | `track_stats` | `bool` | `true` | Record all candidates, evolution history, and frontier history in the result. | | `track_best_outputs` | `bool` | `false` | Re-run the best instruction on the eval set and record outputs. | | `max_rollouts` | `Option` | `None` | Hard cap on evaluation rollouts. | | `max_lm_calls` | `Option` | `None` | Hard cap on LM calls (rollouts plus generation). | | `prompt_model` | `Option` | `None` | Reflection LM that rewrites instructions from feedback. Strongly recommended. | | `eval_concurrency` | `usize` | `16` | Concurrent LM calls during candidate evaluation. | | `seed` | `Option` | `None` | Fixes minibatch sampling and parent selection. | GEPA additionally exposes `compile_module_with_valset(module, trainset, valset, metric)` — sugar over `OptimizeTarget::module_with_valset` plus the `Optimizer` trait. With `Some(valset)`, initial evaluation and child scoring use the validation set while parent re-evaluation uses trainset minibatches; with `None`, the trainset serves both roles (this is what `compile_module` does). ### GEPAResult | Field | Type | Description | | ------------------------------------- | -------------------------------- | --------------------------------------------------------------------------------------------- | | `best_candidate` | `GEPACandidate` | Best average score on the Pareto frontier; installed on the module. | | `all_candidates` | `Vec` | Every evaluated candidate. Empty unless `track_stats`. | | `total_rollouts` | `usize` | Evaluation rollouts consumed. | | `total_lm_calls` | `usize` | LM calls consumed (rollouts plus candidate generation). | | `evolution_history` | `Vec<(usize, f32)>` | `(generation, best_average_score)` pairs for plotting convergence. | | `highest_score_achieved_per_val_task` | `Vec` | Best score per validation example across all candidates. | | `best_outputs_valset` | `Option>` | Best outputs on the eval set. `Some` only with `track_best_outputs`. | | `frontier_history` | `Vec` | Frontier snapshot per generation (see [Optimizer engine](/docs/components/optimizer-engine)). | `GEPACandidate` carries `id`, `instruction`, `module_name`, `example_scores: Vec`, `parent_id: Option`, and `generation`, plus `average_score()` and `mutate(new_instruction, generation)`. ## SIMBA Stochastic Introspective Mini-Batch Ascent, the cheap agentic default. SIMBA keeps one current program as a candidate overlay and hill-climbs. Each step: sample a seeded trainset minibatch; pick the best and worst rollout of the current program on it (served from engine bookkeeping, no extra rollouts); propose exactly one move; accept it through the engine's minibatch gate. A child is promoted to a full-trainset evaluation only when its minibatch mean strictly beats the current program's, so rejected moves never pay for a full pass. The winner is installed when `compile` returns. The two moves, in order of preference: | Move | Trigger | Effect | | ----------------------- | ------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `SimbaMove::AppendDemo` | Best rollout scored at least `min_demo_score` and yields new demos | Appends one harvested demo per predictor, capped at `max_demos`, oldest dropped, duplicates skipped. | | `SimbaMove::AppendRule` | Otherwise | A reflection LM distills one rule from the best and worst rollouts, appended to the instruction of the predictor with the most spans in the worst rollout. Without `prompt_model`, the worst rollout's metric feedback is appended verbatim. | | Field | Type | Default | Description | | ------------------ | --------------- | ------- | ---------------------------------------------------------------------------------------------- | | `max_steps` | `usize` | `8` | Ascent steps to attempt. | | `minibatch_size` | `usize` | `8` | Examples sampled per step. | | `max_demos` | `usize` | `4` | Demo-set cap per predictor. | | `min_demo_score` | `f64` | `1.0` | Rollout score needed to qualify as a demo source; below it the step falls back to append-rule. | | `prompt_model` | `Option` | `None` | Reflection LM for append-rule moves. Strongly recommended. | | `max_metric_calls` | `Option` | `None` | Hard cap on metric calls (rollouts). | | `max_lm_calls` | `Option` | `None` | Hard cap on LM call units (rollouts plus reflection). | | `eval_concurrency` | `usize` | `16` | Rollouts in flight during evaluation. | | `seed` | `Option` | `None` | Fixes minibatch sampling. | Cost: `trainset_size` for the baseline pass, then `minibatch_size` rollouts per step, plus the remaining `trainset_size - minibatch_size` only on promotion and one reflection call per rule move. The run stops cleanly when the budget no longer fits a step. ### SimbaStep and SimbaReport Each `SimbaStep` records one step: | Field | Type | Description | | ------------------------ | ------------- | ----------------------------------------------------------- | | `step` | `usize` | Step index, 0-based. | | `move_kind` | `SimbaMove` | `AppendDemo` or `AppendRule`. | | `parent_minibatch_score` | `f64` | Current program's minibatch mean, the gate threshold. | | `child_minibatch_score` | `f64` | Proposed child's mean on the same minibatch. | | `accepted` | `bool` | Whether the gate promoted the child. | | `full_score` | `Option` | Full-trainset mean of the child; `Some` only when promoted. | The `SimbaReport` summarizes the run: | Field | Type | Description | | ---------------- | ---------------- | --------------------------------------------------------------------------------------- | | `baseline_score` | `f64` | Mean metric score of the unmodified module over the trainset. | | `final_score` | `f64` | Full-trainset mean of the final program; equals the baseline when nothing was accepted. | | `steps` | `Vec` | Per-step outcomes, in order. | | `accepted` | `usize` | Steps promoted by the gate. | | `rejected` | `usize` | Steps rejected by the gate. | | `spend` | `Spend` | Engine spend for the whole run, reflection calls included. | ## BootstrapFewShot The simplest complete optimizer: one teacher pass, one candidate, one comparison. It runs the module over the trainset under trace capture, harvests few-shot demos from successful spans of rollouts scoring at least `min_demo_score`, evaluates the demo candidate on the same engine (teacher rollouts already sit in the rollout cache, so the baseline never re-runs), and installs the demos only when the candidate's mean beats the baseline. | Field | Type | Default | Description | | ------------------ | --------------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `max_demos` | `usize` | `4` | Maximum demos installed per predictor. | | `min_demo_score` | `f64` | `1.0` | Minimum score for a span to qualify as a demo: its own span eval when the metric attached one, the whole-rollout score otherwise. The default assumes a 0 to 1 metric; lower it for graded metrics. | | `eval_concurrency` | `usize` | `16` | Concurrent rollouts during evaluation. | | `max_metric_calls` | `Option` | `None` | Hard cap on metric calls (rollouts). | | `max_lm_calls` | `Option` | `None` | Hard cap on LM call units. | ### BootstrapReport | Field | Type | Description | | --------------------- | ------------------------- | -------------------------------------------------------------------------------------------------------------------------- | | `baseline_score` | `f64` | Mean metric score of the unmodified module over the trainset. | | `candidate_score` | `Option` | Mean score with demos attached. `None` when no demos were harvested or the budget stopped before the candidate evaluation. | | `adopted` | `bool` | Whether the demo candidate beat the baseline and was installed. | | `demos_per_predictor` | `BTreeMap` | Demos harvested per predictor, keyed by leaf name. | | `spend` | `Spend` | Engine spend for the whole run. | `adopted: false` with a populated `demos_per_predictor` means demos were harvested but did not beat the baseline; the module is left unchanged. ## Structural LM-guided hill-climbing over the [edit calculus](/docs/components/edit-calculus), program lane only. Each generation gathers the `legal_edits` menu for every leaf, has a reflection LM (`prompt_model`) choose one edit from the serialized menu plus the incumbent's evaluation feedback, applies it with `Program::edited`, carries the incumbent overlay across the change with `migrate_overlay`, loads the child through a caller-supplied `RuntimeEnv` factory, and accepts it through the engine's minibatch gate: only a strict win on the shared minibatch promotes the child to a full-set evaluation and makes it the new incumbent. Edits that fail to apply, children that fail to load, and reflection replies that do not parse are recorded and skipped. How-to: [Structural](/docs/optimizers/structural). Entry points: `compile_program(&interp, &examples, &metric, env)` and `compile_program_with_overlay(&interp, Some(overlay), &examples, &metric, env)`, where `env: Fn() -> RuntimeEnv` supplies fresh bindings for each child load. The winner is returned in the report (program plus migrated overlay), never installed; bake it with `Program::bake`. | Field | Type | Default | Description | | ------------------ | --------------- | ------- | ------------------------------------------------------------------------------------------------- | | `num_iterations` | `usize` | `8` | Generations to attempt; each proposes exactly one edit. | | `minibatch_size` | `usize` | `8` | Examples in the shared minibatch parent and child are compared on. | | `prompt_model` | `Option` | `None` | Reflection LM that chooses an edit from the menu. Without it the choice is a seeded-uniform pick. | | `max_rollouts` | `Option` | `None` | Hard cap on evaluation rollouts. Every child is a fresh program that re-scores from scratch. | | `max_lm_calls` | `Option` | `None` | Hard cap on LM call units (rollouts plus reflection). | | `eval_concurrency` | `usize` | `16` | Concurrent rollouts during evaluation. | | `seed` | `Option` | `None` | Fixes minibatch sampling and the fallback edit choice. | ### StructuralStep and StructuralReport Each `StructuralStep` records one generation: | Field | Type | Description | | ------------------------ | ---------------- | -------------------------------------------------------------------------- | | `generation` | `usize` | Generation index, 0-based. | | `leaf` | `String` | The leaf the chosen edit targets. | | `edit` | `Edit` | The concrete proposed edit (serde data, replayable against `parent_hash`). | | `parent_hash` | `u64` | `program_hash` of the parent the edit was applied to. | | `parent_minibatch_score` | `f64` | Parent's mean on the shared minibatch, the gate threshold. | | `child_minibatch_score` | `Option` | Child's mean on the same minibatch; `None` when the child never scored. | | `accepted` | `bool` | Whether the gate promoted the child. | | `full_score` | `Option` | Full-set mean of the child; `Some` only when accepted. | | `rejection` | `Option` | Why the child never scored (apply/load failure), when it didn't. | The `StructuralReport` summarizes the run: | Field | Type | Description | | ----------------------- | --------------------- | ---------------------------------------------------------------------------------- | | `program` | `Arc` | The winning program (the input program when nothing was accepted). | | `overlay` | `Overlay` | The incumbent overlay re-minted against the winner at every accepted edit. | | `baseline_score` | `f64` | Mean metric score of the input program (plus overlay) over the examples. | | `final_score` | `f64` | Full-set mean of the final program; equals the baseline when nothing was accepted. | | `edits` | `Vec` | The accepted edits, in order (a lineage, not one batch). | | `steps` | `Vec` | Per-generation outcomes, in order. | | `accepted` / `rejected` | `usize` | Generations promoted / not promoted. | | `spend` | `Spend` | Engine spend for the whole run, reflection calls included. | ## Demo harvesting Demo harvesting is a pure name join over captured traces. A rollout trace records one span per `Predict` invocation under the leaf name the module declares via `Predictors` (stamped by the target's naming pass), so successful spans (parsed output present) scoring at least the optimizer's `min_demo_score` become flat demo rows for exactly the predictor that produced them: no pointer identity, identical behavior for fx and struct harnesses. Rows are gated and ranked by their effective score, deduplicated on input fields so repeated inputs do not crowd the demo set, and capped per predictor. `BootstrapFewShot`, `MIPROv2`, and `SIMBA` share this machinery; it is internal to the crate and not part of the public API. A span's effective score is the whole-rollout metric score unless the metric attached a span-level eval through `TypedMetric::evaluate_spans` (see [Evaluation](/docs/components/evaluation)), which then takes precedence in both directions: a span scored down stays out of the demo pool even when its rollout won, and a span scored up qualifies even when its rollout lost. Without span evals the behavior is exactly the whole-rollout join described above. ## See also * [Optimizer engine](/docs/components/optimizer-engine): `Engine`, `OptimizeTarget`, `Candidate`, `Budget`, `Spend`, and `ParetoView` * [Evaluation](/docs/components/evaluation): `TypedMetric`, `Eval`, and `Eval::with_feedback` * [Traces](/docs/components/traces): the span capture that feeds demo harvesting and reflection * [The edit calculus](/docs/components/edit-calculus): the structural moves `Structural` proposes * How-to pages: [COPRO](/docs/optimizers/copro), [MIPROv2](/docs/optimizers/miprov2), [GEPA](/docs/optimizers/gepa), [Structural](/docs/optimizers/structural) * Runnable examples: * COPRO: [https://github.com/krypticmouse/DSRs/blob/main/crates/dspy-rs/examples/04-optimize-hotpotqa.rs](https://github.com/krypticmouse/DSRs/blob/main/crates/dspy-rs/examples/04-optimize-hotpotqa.rs) and [https://github.com/krypticmouse/DSRs/blob/main/crates/dspy-rs/examples/02-module-iteration-and-updation.rs](https://github.com/krypticmouse/DSRs/blob/main/crates/dspy-rs/examples/02-module-iteration-and-updation.rs) * MIPROv2: [https://github.com/krypticmouse/DSRs/blob/main/crates/dspy-rs/examples/08-optimize-mipro.rs](https://github.com/krypticmouse/DSRs/blob/main/crates/dspy-rs/examples/08-optimize-mipro.rs) * GEPA: [https://github.com/krypticmouse/DSRs/blob/main/crates/dspy-rs/examples/09-gepa-sentiment.rs](https://github.com/krypticmouse/DSRs/blob/main/crates/dspy-rs/examples/09-gepa-sentiment.rs) and [https://github.com/krypticmouse/DSRs/blob/main/crates/dspy-rs/examples/10-gepa-llm-judge.rs](https://github.com/krypticmouse/DSRs/blob/main/crates/dspy-rs/examples/10-gepa-llm-judge.rs) * Optimizer interface smoke test: [https://github.com/krypticmouse/DSRs/blob/main/crates/dspy-rs/examples/94-smoke-slice5-optimizer-interface.rs](https://github.com/krypticmouse/DSRs/blob/main/crates/dspy-rs/examples/94-smoke-slice5-optimizer-interface.rs) # Predict Source: https://dsrs.herumbshandilya.com/docs/components/predict Predict, Predicted, CallMetadata, PredictError, and ToolSet: the leaf module that formats a signature into a prompt, calls the LM, and parses the typed output `Predict` is the leaf module. One `Predict` is one prompt template and one LM call: it formats a [signature](/docs/components/signatures)'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](/docs/getting-started/how-dsrs-thinks) for the mental model. ## Usage ```rust theme={null} use dspy_rs::{Predict, Signature}; /// Answer questions accurately. #[derive(Signature, Clone, Debug)] struct QA { #[input] question: String, #[output] answer: String, } let predict = Predict::::new(); let result = predict.call(QAInput { question: "What is the capital of France?".into(), }).await?; println!("{}", result.answer); // "Paris" ``` The derive generates `QAInput` and `QAOutput` from the field markers. `.call()` returns `Result, PredictError>`, and `Predicted` implements `Deref`, 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 | Path | Produces | | -------------------------------------- | ------------------------------------------------------------------------------- | | `Predict::::new()` (also `Default`) | Predictor with no demos, no instruction override, no tools, global LM | | `Predict::::builder()` | `PredictBuilder` for full configuration | | `fx::predict("name", input)` | Functional lane: a named predictor per call slot, see [fx](/docs/components/fx) | ### `PredictBuilder` | Method | Effect | | -------------------- | ----------------------------------------------------------------------------------------------------------------- | | `.named(name)` | Component name recorded on trace spans. Unnamed predictors fall back to the signature type name and log a warning | | `.demo(demo)` | Appends one `Demo` few-shot demo | | `.with_demos(iter)` | Appends demos from an iterator | | `.instruction(text)` | Overrides the signature's docstring instruction | | `.add_tool(tool)` | Adds one `rig` `ToolDyn` the LM may invoke | | `.with_tools(iter)` | Adds `Arc` tools from an iterator | | `.lm(lm)` | Per-instance [LM](/docs/components/lm), bypassing the global `configure()` LM | | `.build()` | Produces the `Predict` | ```rust theme={null} let predict = Predict::::builder() .named("qa") .instruction("Answer in one word.") .demo(Demo::new( QAInput { question: "What is 1+1?".into() }, QAOutput { answer: "2".into() }, )) .build(); ``` `Demo` 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` cannot be attached to a `Predict`. 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](/docs/components/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](/docs/components/state). Every state mutation invalidates the cached instance overlay. ## Calling | Method | Returns | Use | | --------------------------- | ---------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- | | `.call(input)` | `Result, PredictError>` | The typed direct call — runs through the IR interpreter | | `.forward(input)` | Same as `call` | `Module` trait hook; delegates to `call`. Callers should invoke `Module::call`, which exists as the future middleware seam | | `.build_chat(&input).await` | `Result` | Inspect or modify the first-turn prompt before sending | | `.call_and_parse(chat)` | `Result<(Predicted, Chat), PredictError>` | Multi-turn: the caller owns the `Chat` between turns | `Predict` executes as a 1-node IR [Program](/docs/components/program-and-nodes): a `predict` leaf named after the component, over `SignatureDef::of::()` — 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](/docs/components/runtime) 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 `StopSpec` — `until_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](/docs/components/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 `call`s 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](/docs/components/traces). ## `Predicted` Every call returns `Predicted`: the typed output plus runtime bookkeeping. It implements `Deref`, so output fields read directly (`result.answer`). | Method | Returns | | --------------- | ------------------------ | | `.metadata()` | `&CallMetadata` | | `.into_inner()` | `O`, discarding metadata | | `.into_parts()` | `(O, CallMetadata)` | ### `CallMetadata` | Field | Type | Description | | ----------------- | ----------------------------- | ------------------------------------------------------- | | `raw_response` | `String` | Full LM response text before parsing | | `lm_usage` | `LmUsage` | `prompt_tokens`, `completion_tokens`, `total_tokens` | | `tool_calls` | `Vec` | Tool calls the LM requested | | `tool_executions` | `Vec` | Results from executing tool calls | | `span_id` | `Option` | Trace span id, when the call ran inside a capture scope | | `field_meta` | `IndexMap` | Per-field parse details, keyed by field name | Each `FieldMeta` records `raw_text` (the text the LM produced for that field), `flags` (`Vec`, non-fatal coercion observations such as a stripped code fence), and `checks` (`Vec` 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` | Variant | Payload | Fires when | Retryable | | ------------ | ------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------- | | `Lm` | `source: LmError` | The provider failed before returning a usable response. Everything the provider stack reports arrives as `LmError::Provider` (provider name, message, source) | No — the underlying rig client owns transport-level retries | | `Parse` | `source: ParseError`, `raw_response`, `lm_usage` | The LM responded but the expected fields could not be extracted or coerced, or an `#[assert]` failed | Yes | | `Conversion` | `source: ConversionError`, `parsed` | The parsed JSON value does not fit the typed output struct | No | | `Replay` | `source: ReplayError` | A strict replay scope refused the call: the live request diverged from its recording, or the recorded span is unusable | No | `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. | Constructor | Behavior | | ------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `ToolSet::build(&tools)` | Fetches each definition once, indexes executors by name; duplicate names keep the first tool | | `ToolSet::from_definitions(defs)` | Definitions with no executors, for caller-managed loops | | `ToolSet::code_mode(tools, SandboxConfig)` | Requires the `code-mode` feature. Collapses the tools into one sandboxed `run_js` tool: the model writes JavaScript against the tools as JS APIs and composes results in one execution. Errors when two tool names mangle to the same JS identifier | `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: ```rust theme={null} let predict = Predict::::builder() .add_tool(CodeModeTool::new(tools, SandboxConfig::default()).await?) .build(); ``` A `ToolSet::code_mode(...)` set drops into `LM::call_with_toolset` directly. See [Code Mode](/docs/components/code-mode). ## See also * [Signatures](/docs/components/signatures) * [Modules](/docs/components/modules) * [LM](/docs/components/lm) * [Traces](/docs/components/traces) * [Code Mode](/docs/components/code-mode) * [Example: simple predict](https://github.com/krypticmouse/DSRs/blob/main/crates/dspy-rs/examples/01-simple.rs) * [Example: tools](https://github.com/krypticmouse/DSRs/blob/main/crates/dspy-rs/examples/15-tools.rs) * [Example: tracing](https://github.com/krypticmouse/DSRs/blob/main/crates/dspy-rs/examples/12-tracing.rs) * [Example: save and load state](https://github.com/krypticmouse/DSRs/blob/main/crates/dspy-rs/examples/13-save-load-state.rs) # Program and nodes Source: https://dsrs.herumbshandilya.com/docs/components/program-and-nodes Reference for the IR Program, the nine node kinds, tunable parameters, the Overlay API, and baking a candidate The `Program` is the in-memory IR every authoring lane produces: a `#[module]` function and a parsed `.dsrs` file both end at this one value. It is the compiled form of a pipeline: everything the interpreter, the optimizer, and the serializer need, in one place. This page lists what a program holds, the nine node kinds, the parameter (overlay) surface, and how a winning candidate is baked into a new program. ```rust theme={null} // Every #[module] exposes its compiled program: let program: &'static dspy_rs::ir::Program = qa::program(); ``` ## What a Program holds In plain words: the shape of the pipeline, the signatures, the models, the tools, the tunable values, the allowed capabilities, and a fingerprint hash. | Field | What it is | | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `meta` | Program metadata: format version, name, `program_hash`, and optional `Lineage`. | | `nodes` | The node arena: the pipeline shape as a tree of the nine node kinds. | | `sigs` | The signature arena: every LM-call interface used by the program. | | `params` | The parameter arena: every tunable slot with its current default value. | | `models` | Model declarations: the `@ref` name plus its config (never secrets). | | `tools` | Tool declarations: name, description slot, interface, caps, and kind (`ToolKind::Host` bound by the runtime, or `ToolKind::Sandboxed` carrying its code in the artifact). | | `types` | The class and enum definitions reachable from the signatures. | | `syms` | The string interner for node names, field names, and tool names. | | `caps` | The program's capability ceiling (a `CapSet` of names like `net:search`). | | `root` | The root node, always a `Seq` in v1 (`main`). | | `sig` | The program's external interface signature. | `meta.program_hash` is a stable hash over the canonical printed text minus the lineage block, so JSON and text loads of the same program agree on it. Overlays, traces, and state artifacts reference it. Nodes form a tree: one parent, one use. Fan-in happens through field references, never shared nodes. Leaf nodes (`Predict`, `AgentLoop`, `Hole`) carry a mandatory, program-unique name; that name is also the trace component name and the parameter path prefix. Containers are anonymous. ## The nine node kinds | Node | Plain words | Main fields | | ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Predict` | One LM call, no tools. `cot` is sugar: a Predict over a reasoning-augmented signature. | `name`, `sig`, `instruction`, `demos`, `model`, `binding` | | `AgentLoop` | The LM plus tool loop as a first-class unit. | `name`, `sig`, `instruction`, `demos`, `model`, `tools` (the declared table), `tool_set` (the selection gene), `context_policy`, `stop` (max turns, stop tools, until\_parse), `budget`, `binding` | | `Seq` | Runs children in order and exports named fields. | `body`, `out` | | `ForkJoin` | Runs branches concurrently (all succeed or fail fast) and joins their outputs. | `branches`, `join` | | `Route` | Picks one arm by an enum-valued port. | `on`, `arms` (variant, node pairs), `default` | | `Retry` | Re-runs its child on retryable failure, with backoff and optional parse feedback. | `child`, `max_attempts`, `backoff_ms`, `feedback` | | `Refine` | Re-runs its child with judge feedback until a score threshold passes. | `child`, `judge`, `threshold`, `max_rounds`, `feedback_field` | | `Loop` | A bounded loop that carries values between iterations. | `body`, `max_iters`, `while`, `carry`, `out` | | `Hole` | Typed opaque code: the type system sees a normal node, the implementation is sandboxed JS (`HoleImpl::Sandboxed`, code in the artifact) or a native function bound by name (`HoleImpl::Host`, with a stable content hash). | `name`, `sig`, `imp`, `caps`, `binding` | Every node's `binding` (or `out`/`join`/`carry`) is a list of field-level wires: a destination field name fed from a port (`Input` for `$.field`, `Out` for `node.field`, `Carried` for `^field`, or a JSON literal). ## Tunable values (params) Every mutable thing in a program is a named, addressable slot; a candidate is an overlay read through at render time, never a mutation of the program. ### Kinds | `ParamKind` | Plain words | | --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Instruction` | The prompt's task description for a leaf. | | `Demos` | Few-shot demonstration rows (input map plus output map each). | | `ToolDesc` | A tool's description text. | | `ToolSet` | Which of an agent node's *declared* tools the loop carries. Declaration (`AgentLoopNode::tools`) is structural — it is the loop's capability footprint; selection is the gene. An optimizer can drop a distracting tool or bring a declared one back, never add an undeclared one: a value naming a tool outside the declared table is refused at load and on `Overlay::set`. Absent selection = the full declared table. | | `ModelRef` | Which declared model a leaf uses. | | `ContextPolicy` | The agent context policy: history window, tool-result byte cap, playbook text. | | `Code` | Sandboxed JS source (with a stable content hash). Hole and sandboxed-tool implementations are optimizable through this kind. | ### Path naming rule Slots are addressed by canonical string paths. Node-owned slots use the leaf name as prefix; tool-owned slots use a `tool.` prefix: * `".instruction"`, `".demos"`, `".model"`, `".context"`, `".tool_set"`, `".code"` * `"tool..desc"`, `"tool..code"` For example `"drafter.instruction"` or `"tool.search.desc"`. `Program::param_id(path)` resolves a path to its id; after load everything speaks ids. ## The Overlay API An `Overlay` is one candidate: a dense set of parameter values layered over a fixed program. The interpreter reads through it at render time, so many candidates can be evaluated concurrently over one shared program. | Item | What it does | | ------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Overlay::new(&program)` | An empty overlay minted against the program. Records `program.meta.program_hash` as its `base`. | | `base` check | Every apply path (`set`, `Interpreter::run`, `bake`, `from_named`) verifies `overlay.base` equals the program's hash; a mismatch is `OverlayError::BaseMismatch`. This prevents stale candidates from applying to a new skeleton. | | `set(&program, id, value)` | Kind-checked set: writing a `Demos` value into an `Instruction` slot is `OverlayError::KindMismatch`. | | `set_instruction(slot, text)` | Sets an instruction through a typed `Slot` handle. | | `set_demos(slot, rows)` | Sets demo rows through a typed `Slot` handle. | | `set_code(slot, source)` | Sets JS source through a typed `Slot` handle (hash computed automatically). | | `set_tool_set(&program, slot, tools)` | Sets a tool-set gene through a typed `Slot` handle. Unlike the other typed setters it takes the program and can fail: the value must be a subset of the owning agent node's declared tools, anything else is `OverlayError::ToolSetUndeclared`. | | `resolve(&program, id)` | The effective value: the overlay entry if set, otherwise the slot's default. | | `hash()` | Stable hash over the base plus the set entries in id order. This is the trace's `candidate_hash` and the rollout-cache key. | | `to_named(&program)` | The serde boundary: a path-keyed map (`"": ParamValue`). | | `from_named(&program, map)` | Rebuilds an overlay from the path-keyed form, verifying every path and kind. Unknown paths are `OverlayError::UnknownPath`. | Typed slot handles come from `Program::slot_of::(path)`, which returns `None` when the path is unknown or the slot has a different kind. ## Baking a candidate `bake(overlay, note)` promotes a candidate into a new program value: every overlay entry becomes the corresponding slot's default, the lineage is stamped (the caller's note, plus `parent` set to the old program hash and `overlay` set to the overlay hash, both hex), and the program hash is recomputed. The original program is untouched. Failures are `BakeError::Overlay` (stale base or kind mismatch) or `BakeError::Invalid` (the baked program failed validation). The workflow below turns a winning overlay, from an optimizer or from hand tuning, into a new self-contained `.dsrs` file. ### 1. Build an overlay against the program An overlay is created for one specific program. Address each value by its parameter path: the leaf name (the `let` binding in your module body), a dot, and the slot name. ```rust theme={null} use dspy_rs::ir::{Instruction, Overlay}; let program = qa::program(); let mut overlay = Overlay::new(program); let slot = program .slot_of::("drafter.instruction") .expect("drafter.instruction is an instruction slot"); overlay.set_instruction(slot, "Answer in one short sentence."); ``` You can also set demos (worked examples the model sees before your input): ```rust theme={null} use dspy_rs::ir::{Demos, DemoRow}; let demos = program.slot_of::("drafter.demos").unwrap(); let mut input = serde_json::Map::new(); input.insert("question".into(), serde_json::json!("What is 2+2?")); let mut output = serde_json::Map::new(); output.insert("draft".into(), serde_json::json!("4")); overlay.set_demos(demos, vec![DemoRow { input, output }]); ``` ### 2. Bake `bake` returns a new program with the overlay's values folded in as the defaults. ```rust theme={null} use dspy_rs::ir::Lineage; let note = Lineage { optimizer: "hand-tuned".into(), trainset: "my-eval@1".into(), budget: "20 runs".into(), parent: None, // filled in by bake date: "2026-08-14".into(), overlay: None, // filled in by bake }; let baked = program.bake(&overlay, note)?; ``` Lineage is a note stored in the file that says where this version came from: which optimizer produced it, on what data, at what cost, and on what date. `bake` fills in `parent` (the hash of the program you baked from) and `overlay` (the hash of the candidate you promoted), so leave those as `None`. ### 3. Print the new version ```rust theme={null} std::fs::write("qa-v2.dsrs", baked.to_dsrs())?; ``` The new file carries the baked values as its own defaults, plus a `lineage { ... }` block. It runs exactly like the old program plus the overlay, and you can check and serve it with the [CLI](/docs/components/cli) like any other program. ### The base-hash safety rules An overlay refuses to apply to a different program than it was made for. This is on purpose. Every overlay remembers the hash of its base program, and both `bake` and the runtime check it, so you cannot accidentally promote tuning results from one version of a pipeline onto another. The same rule has a second effect worth knowing: baking changes the program's hash. Overlays minted against the old program do not apply to the baked one. Candidates are re-minted against the new skeleton by design, so every round of tuning starts from a clean, known base. ### Common mistakes **Wrong parameter path.** The path is `.`, using the `let` binding name from the module body, not the step function name. If your module says `let drafter = draft(...).await?;`, the path is `drafter.instruction`. **Reusing an old overlay after baking.** It will be refused because the base hash changed. Build a fresh overlay against the baked program. **Filling in `parent` or `overlay` yourself.** `bake` overwrites both with the real hashes. Anything you put there is discarded. ## See also * [The edit calculus](/docs/components/edit-calculus): the structural half of program mutation — `Program::edited`, `legal_edits`, `migrate_overlay` * [The .dsrs file](/docs/components/dsrs-file): the canonical text form a program prints to * [Runtime](/docs/components/runtime): loading and running a program, and how `Interpreter::run` reads through an overlay * [Optimizer engine](/docs/components/optimizer-engine): where candidates and overlays come from during optimization * [CLI](/docs/components/cli): checking and serving the baked file # Runtime Source: https://dsrs.herumbshandilya.com/docs/components/runtime Reference for RuntimeEnv, Interpreter load and run, Budget, ambient overlays, and embedding programs with include_program! The runtime is what turns a `Program` into something that executes: the host supplies bindings through a `RuntimeEnv`, `Interpreter::load` checks everything up front, and `Interpreter::run` evaluates the program on an input map. This page lists the full surface, plus `include_program!` for compiling a `.dsrs` file into a Rust binary. ```rust theme={null} use dspy_rs::ir::{Budget, Interpreter, RuntimeEnv}; let env = RuntimeEnv::new() .bind_model("fast", lm) .grant("net:search"); let interp = Interpreter::load(program, env).await?; let out = interp.run(input, None, Budget::default()).await?; ``` ## `RuntimeEnv` What the host supplies at load: live models, host tool and hole bindings, the sandbox, and the capability grants. Secrets never travel in the artifact; model clients are bound here from host-held keys and env vars. | Method | What it does | | ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `RuntimeEnv::new()` | An empty environment. | | `bind_model(name, lm)` | Pre-binds a live model by declared model name (for example `"fast"`). Models not bound here are constructed from their artifact config at load. | | `bind_host_tool(name, tool)` | Binds a host tool implementation (a `rig` dyn tool) by tool name. Consulted once at load. | | `bind_host_hole(name, f)` | Binds a native implementation for an extern hole by leaf name. The function receives the hole's resolved input map and returns a JSON value coerced against the hole's output signature. | | `with_sandbox(executor)` | Sets the sandbox that executes holes and sandboxed tools (QuickJS in v1). Required if and only if the program carries sandboxed code. | | `grant(cap)` | Grants one capability. The program's `caps` must be a subset of the grants or the load is refused. | | `with_code_mode(config)` | Behind the `code-mode` feature (on by default). When set, every `AgentLoop` presents its non-stop tools as one sandboxed `run_js` tool instead of N JSON tools; the model writes JavaScript that calls them as globals. This is a host presentation choice, not program semantics: the same artifact runs identically either way. See [Code Mode](/docs/components/code-mode). | ## `Interpreter::load` Loading front-loads every check; nothing is lazy and nothing waits for call time. The checks run in this order: 1. `Program::validate` (the structural graph rules). 2. `program.caps` must be a subset of `env.grants` (no ambient authority). 3. Every model must be bindable: pre-bound by name, or client-constructible from its config. 4. Every `ToolKind::Host` tool name must be bound. 5. Every sandboxed tool and hole is registered through the full sandbox lifecycle (parse, compile, register). A hole that does not compile fails the load, not the call. With code mode enabled there is one extra load-time refusal: two non-stop tool names in one loop that mangle to the same JS identifier. ### `LoadError` variants | Variant | Plain words | | ------------------ | -------------------------------------------------------------------------------------------- | | `Invalid` | The program failed validation. | | `CapsExceedGrants` | The program asks for capabilities the host did not grant; carries the missing set. | | `Model` | A model could not be bound; carries the model name and the reason. | | `HostToolUnbound` | A host tool name has no binding in the environment. | | `HostHoleUnbound` | An extern hole's leaf name has no binding in the environment. | | `SandboxMissing` | The program carries sandboxed code but the environment has no sandbox executor. | | `Register` | A piece of sandboxed code failed to register; carries the location and the underlying error. | ## `Interpreter::run` Evaluates the program on an input map, reading parameters through an optional overlay (never mutating the program) and metering spend against a budget. ```rust theme={null} let out: JsonMap = interp.run(input, overlay, budget).await?; ``` * `input` is a JSON object of the program's input fields. It is checked against the program's external signature: a missing required field or a type mismatch is `RunError::Input`. * `overlay` is `Option>`. When present, its `base` must equal the program's hash or the run fails with `RunError::Overlay` before anything executes. * `budget` caps spend for this run. ### `Interpreter::run_collecting` `run_collecting(input, overlay, budget)` is `run` with per-leaf metadata: it returns a `RunOutput` — the same output map plus one `LeafOutcome` per successful `Predict`-leaf evaluation, in execution order (`ForkJoin` branches append in declared branch order). This is the seam `Predict` uses to reassemble `CallMetadata` when it executes through the interpreter. Each `LeafOutcome` carries: `name` (the program-unique leaf name, the trace span component), `raw_response`, `field_meta` (per-field jsonish coercion flags and `#[check]` results, keyed by canonical field name), `usage`, `model_config_hash`, `span_id` (when a capture scope was active), and — for `AgentLoop` leaves — `tool_calls` and `tool_executions`. Scope rules: `Predict` and `AgentLoop` leaves report, `Hole` leaves do not; only *successful* evaluations report (a failed `Retry` attempt leaves no outcome, the succeeding one reports); an agent whose final output came from stop-tool args has empty `field_meta`; a replay-served leaf reports the recorded raw text and usage with empty `field_meta`. ### `Interpreter::run_conversation` `run_conversation(chat, input, overlay, budget)` is the conversation-in/conversation-out entry: one turn with the program's single leaf over a caller-owned `Chat`, returning `(RunOutput, Chat)` — the turn's output and metadata plus the extended conversation. It exists only for single-leaf programs (one `predict` or `agent` node, what `Predict` compiles to); a multi-node graph is refused with `RunError::Input`. ```rust theme={null} // Opening turn: empty chat plus the typed input. let (out, mut chat) = interp .run_conversation(Chat::new(vec![]), Some(input), None, Budget::unlimited()) .await?; // Continuation: append a follow-up and send the chat back. chat.push_message(Message::user("are you sure?")); let (out, chat) = interp.run_conversation(chat, None, None, Budget::unlimited()).await?; ``` The `chat`/`input` combinations: an empty chat with `Some(input)` renders the opening turn (system + demos + the formatted input, identical to `conversation_opening`); a non-empty chat with `None` is sent as-is; a non-empty chat with `Some(input)` appends the formatted input as the next user turn. A turn is not a run: each call records one trace span (`seq` increments per turn) and meters against its own `budget`. On an `agent` leaf the turn runs the full tool loop, dispatching tool calls through their bound executors. Replay works turn by turn — a span keys on the full chat sent, so a recorded conversation serves each turn with tool effects baked in. `conversation_opening(&input, overlay)` renders the opening `Chat` without calling anything: the same overlay-resolved system + demos (+ agent playbook) + input rendering a run would send. Use it to inspect or edit the first turn before `run_conversation`. `Predict::build_chat`/`call_and_parse` are thin wrappers over these two entries. ### Caller-managed tool loops `run_conversation_caller_managed(chat, input, overlay, budget)` is the same turn in suspending mode: when the model requests tool calls on an `agent` leaf, the loop suspends instead of dispatching and returns `ConversationTurn::Suspended(ToolSuspension)`. Execute the calls yourself and feed the results back: ```rust theme={null} let mut turn = interp .run_conversation_caller_managed(Chat::new(vec![]), Some(input), None, Budget::unlimited()) .await?; while let ConversationTurn::Suspended(suspension) = turn { let results = run_my_tools(suspension.calls()).await; // Vec, one per call turn = interp.resume_conversation(suspension, results).await?; } let ConversationTurn::Complete { run, chat } = turn else { unreachable!() }; ``` `ToolSuspension::calls()` is the pending calls in request order; `ToolSuspension::chat()` is the conversation so far, including the assistant tool-call turn. `resume_conversation` records one `ToolRun` event per result (metering the time the suspension was outstanding), pushes one batched tool-result user turn, and continues the loop under the same meters and turn cursor — trace spans, budget metering, and stop-tool semantics are identical to dispatching mode. A stop-tool call completes the turn instead of suspending; a replay scope never suspends (served turns carry every tool effect); Code Mode does not apply, since the caller executes the tools. Dropping a suspension without resuming closes its span as `Cancelled`. Feed a failed tool's error text as its result to keep the conversational-repair behavior of dispatching mode. ## `Budget` Run-level spend limits; `None` means unlimited. `Budget::default()` and `Budget::unlimited()` are the same: no limits. | Field | Type | Meaning | | -------------- | ----------------- | ----------------------------------------------------------------------------------------------- | | `max_lm_calls` | `Option` | Maximum LM calls for the run. Hard-gated before each call. | | `max_tokens` | `Option` | Token ceiling. Soft: checked against accumulated usage, since usage is only known after a call. | | `deadline` | `Option` | Wall-clock cutoff. Hard-gated before each call. | An `AgentLoop`'s per-node budget chains a child meter under the run meter, so node spend also counts against the run. ### `RunError` variants | Variant | Plain words | | ------------------ | -------------------------------------------------------------------------------------- | | `Lm` | The provider call failed at a leaf. | | `Parse` | The response arrived but did not parse as the signature outputs; carries the raw text. | | `Tool` | A tool execution failed; carries the tool name and message. | | `Hole` | A sandboxed hole failed to execute. | | `CapabilityDenied` | Code asked for a capability the run does not permit. | | `Budget` | The budget ran out at a leaf. | | `Route` | A route port produced a value no arm (and no `else`) matches. | | `Cancelled` | The run was cancelled. | | `Overlay` | The overlay was minted against a different program hash. | | `Input` | The run input was rejected against the program signature. | | `Internal` | An interpreter invariant was violated. | | `Replay` | A strict replay scope refused this call. | `RunError::retryable()` is true only for `Lm`, `Parse`, `Tool`, and `Hole`; those are the errors `Retry` and `Refine` may intercept. `Budget` and `CapabilityDenied` are never retried. ## Ambient overlays These two functions let [`#[module]`](/docs/components/module-macro) executable functions pick up a candidate without threading it through every call. * `with_ambient_overlay(overlay, fut)` runs a future with an `Arc` as the ambient candidate for every `#[module]` function called on that task. Scoping is task-local: spawned subtasks do not inherit it, and nesting replaces the outer scope. The overlay's `base` is checked by `Interpreter::run` against each module's program, not here, so one scope can span calls into several modules and only the matching one accepts it. * `current_overlay()` returns the ambient overlay (`Option>`) if a `with_ambient_overlay` scope is active on this task. `#[module]`-generated functions read it immediately before `Interpreter::run`. ## `default_lm` The globally configured LM, used when a module does not name a model. `default_lm()` returns `Option>`: the model set through `configure(...)` in the global settings, or `None` when nothing was configured. Generated `#[module]` environments use it to bind the `default` model ref at load. ## Embedding programs with `include_program!` `include_program!` compiles a `.dsrs` file into your binary: ```rust theme={null} dspy_rs::include_program!("programs/qa.dsrs"); let program: &'static dspy_rs::ir::Program = qa::program(); ``` The macro creates a module named after the file stem (`qa.dsrs` becomes `mod qa`). The path is resolved relative to your crate's `Cargo.toml` directory. The generated module contains: * `qa::SOURCE`: the embedded text. * `qa::program()`: the parsed, validated program (panics on a bad file). * `qa::try_program()`: the same, but returns a `Result`. * A generated test, so `cargo test` fails if the file ever becomes invalid. Validation is layered. **Syntax** is checked at macro expansion through `dsrs-syntax` — the shared `.dsrs` lexer and structural grammar both the macro and the full parser read from — so a malformed file breaks your build. **Semantics** (types, dataflow, capability rules) are checked by the full parser at first use of `program()`, and forced at CI time by the generated test — the sqlx-offline analogue. This is the shipping path for programs with host tools or host holes, which `dsrs serve` cannot bind: embed the file, bind your implementations with `bind_host_tool` and `bind_host_hole`, and serve from your own binary. ## See also * [Program and nodes](/docs/components/program-and-nodes): the `Program` value the runtime loads, and the Overlay API * [The .dsrs file](/docs/components/dsrs-file): the text format `include_program!` embeds * [CLI](/docs/components/cli): `dsrs serve`, the hosted version of this load-and-run path * [Code Mode](/docs/components/code-mode): the `run_js` surface `with_code_mode` enables * [Capabilities](/docs/components/capabilities): grants, caps ceilings, and `CapabilityDenied` # Signatures Source: https://dsrs.herumbshandilya.com/docs/components/signatures Declare an LM interaction as a typed Rust struct: inputs, outputs, instruction, and constraints A signature declares one LM interaction as a plain Rust struct: typed inputs, typed outputs, and an instruction. DSRs compiles the declaration into a prompt and parses the model's response back into your types, which removes prompt strings and response parsing from your code entirely. A signature holds no state and makes no calls; it is the contract that [predictors](/docs/components/predict) execute. ## Declaring a signature ```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, #[output] answer: String, } ``` Three rules cover most of what there is to know: * The struct doc comment becomes the instruction the model receives. * Field doc comments become field descriptions in the prompt. Write one only when it adds something the field name does not; `/// The question` on a field named `question` adds nothing. * The `#[output]` field's type is part of the prompt. Declaring `answer: String` versus `answer: Vec` versus a custom enum changes both what the model is told to produce and what the parser will accept. The derive generates `QAInput` and `QAOutput` structs (each with a `new` constructor) and implements the `Signature` trait: `instruction()`, `input_shape()`, `output_shape()`, and per-field metadata. Calling the signature is the predictor's job: ```rust theme={null} use dspy_rs::Predict; let predict = Predict::::new(); let out = predict.call(QAInput { question: "What is DSRs?".into() }).await?; println!("{}", out.answer); // a String, already parsed ``` ## Richer output types The type system is the main lever for controlling model output. An enum output constrains the model to its variants; an `Option` makes absence a legal answer instead of an invitation to guess; a nested struct is filled field by field; a constraint attaches a rule the value must satisfy: ```rust theme={null} use dspy_rs::{Schema, Signature}; #[Schema] #[derive(Clone, Debug, PartialEq)] enum Sentiment { Positive, Negative, Neutral, } /// Analyze the sentiment of the text. #[derive(Signature, Clone, Debug)] struct Analyze { #[input] text: String, #[output] sentiment: Sentiment, /// A short quote supporting the sentiment, if one exists #[output] evidence: Option, #[output] #[check("this >= 0.0 and this <= 1.0", label = "confidence_range")] confidence: f64, } ``` If the model returns something that cannot be read as a `Sentiment`, the call fails with a typed error carrying the raw response; see [Predict](/docs/components/predict) for error handling and [Adapters](/docs/components/adapters) for how the types are rendered into the prompt and parsed back. ## Field attributes | Attribute | Applies to | Effect | | --------------------------------------- | ---------------- | -------------------------------------------------------------------------------------------------------------------------------- | | `#[input]` / `#[input(desc = "...")]` | any field | Marks an input. `desc` overrides the doc comment. | | `#[output]` / `#[output(desc = "...")]` | any field | Marks an output. `desc` overrides the doc comment. | | `#[alias = "name"]` | input or output | LM-facing rename. Rust code keeps the original name. Aliased names must stay unique per side. | | `#[format("json")]` | input only, once | Serialization hint. Accepted values: `json`, `yaml`, `toon`. `yaml` and `toon` currently fall back to JSON. | | `#[render(jinja = "...")]` | input only, once | Custom Jinja rendering. Template must be a string literal; syntax is validated at compile time. Cannot combine with `#[format]`. | | `#[flatten]` | input or output | Hoists the fields of a nested struct into the signature. Cannot combine with any other field attribute. | | `#[check("expr", label = "l")]` | output | Soft constraint. Label required. Repeatable. | | `#[assert("expr")]` | output | Hard constraint. Label optional. Repeatable. | Every field requires exactly one of `#[input]` or `#[output]`, and the signature requires at least one of each. `#[render(jinja = ...)]` templates see `this` (the field value), `input` (the full input object with alias overlays), `field` (`name`, `rust_name`, `type`), and `vars` (currently empty). Available filters: minijinja builtins plus `regex_match`, `sum`, and `truncate`. ## Supported field types | Category | Types | Rendered as | | -------------- | ----------------------------------------------- | ------------------ | | Strings | `String`, `Cow`, `char` | `string` | | Booleans | `bool` | `bool` | | Integers | `i8` through `i64`, `isize`, `u8`, `u16`, `u32` | `int` | | Floats | `f32`, `f64` | `float` | | Optionals | `Option` | `T or null` | | Lists | `Vec`, `[T; N]`, `HashSet`, `BTreeSet` | `T[]` | | Maps | `HashMap`, `BTreeMap` | `map` | | Smart pointers | `Box`, `Arc`, `Rc` | transparent | | Custom | `#[Schema]` structs and unit enums | rendered type name | ### Rejected shapes (compile errors) | Shape | Reason | | --------------------------------- | ---------------------------------------------------------------- | | Tuple types, tuple/unit structs | Named fields required | | Trait objects, bare `fn` types | No concrete schema | | `serde_json::Value` | Use a concrete typed value | | Non-`String` map keys | Use `HashMap` or `BTreeMap` | | `u64`, `usize`, `i128`, `u128` | Exceed JSON number precision; use `i64`/`isize`/`u32` or smaller | | Duplicate LM names after aliasing | Names must be unique per side | ## Custom types with `#[Schema]` `#[Schema]` marks a struct or enum as usable inside signature fields. It accepts no arguments. It expands to `#[derive(facet::Facet, serde::Serialize, serde::Deserialize)]` with crate-path attributes; enums additionally receive `#[repr(u8)]` when no explicit `repr` is present. The old vendored BAML stack, including its `#[baml(...)]` attribute grammar and the `#[BamlType]` compat alias, was removed. The schema layer reads facet metadata instead. What the schema builder honors on `#[Schema]` types: | Source | Effect | | ------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------- | | Doc comments on the type, fields, and variants | Rendered as `//` comment lines in the schema block | | `#[facet(rename = "...")]` on a field or variant | LM sees the rendered name; the parser accepts both the rendered and the Rust name | | `#[facet(rename_all = "...")]` on the container | Rules: `camelCase`, `snake_case`, `PascalCase`, `SCREAMING_SNAKE_CASE`, `kebab-case`, `SCREAMING-KEBAB-CASE`, `lowercase`, `UPPERCASE` | | `#[facet(skip)]` on a field | Omitted from the model-facing schema; pair with a serde skip or default so the struct still deserializes | | `#[facet(default)]` on a field | Rendered as optional; a missing value parses as null | Enums must be unit-only. Variant matching at parse time is case-insensitive and strips quotes. Data-carrying enums are rejected: schema construction panics with `data-carrying enums are not supported; use a struct`. Model union-shaped data as a struct with optional fields. ## The typesys pipeline The in-house `typesys` module implements the type system in four parts: `schema` (the `FieldType`/`OutputSchema` model, derived from facet `Shape` metadata), `render` (the schema text the model sees), `coerce` (tolerant parsing of model output), and `constraint` (check/assert evaluation). Order per call: render the schema into the prompt, coerce the response text per field, evaluate constraints, then deserialize into the output struct via serde. ### render `typesys::type_name` produces the inline label shown in field descriptions and the `should be of type:` line (unions render as `A or B`, literals as `"value"`). `typesys::schema_block` produces the expanded block for structured types: classes render as `{ field: type, ... }` with doc comments, enums as a `one of:` value list, lists of classes as a bracketed block. Primitives return their label so the adapter skips a redundant block. ### coerce `typesys::coerce(raw, field_type, types)` returns a `Coerced { value, flags }`. Non-fatal observations are recorded as `Flag` values: `StrippedCodeFence`, `ParsedListFromText`, `CoercedFromString`, `ExtraTextIgnored`. | Target | Accepted input | | ---------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | | string | Raw text; trailing newlines trimmed | | int | Plain integers; fractions (`8/10` rounds); thousands separators (`1,000`); bare decimals round to nearest | | float | Direct parse; stray non-numeric characters stripped | | bool | `true`/`false`; also `yes`/`y`/`1` and `no`/`n`/`0` | | optional | Empty text, `null`, `none`, `nil`, `~` parse as null | | list | JSON arrays (code fences stripped); bulleted (`-`, `*`, `+`), numbered (`1.`, `1)`), or one-per-line items; comma-separated fallback for single lines | | map, class | First balanced JSON object in the text, surrounding prose ignored; class keys accepted by rendered or Rust name; missing optional fields become null | | enum | Variant by rendered or Rust name, case-insensitive | Per-field results land in `CallMetadata::field_meta`, an `IndexMap` where `FieldMeta` carries `raw_text`, `flags`, and `checks`. ## Constraints Constraints attach rules to output fields. Use `#[assert]` for rules whose violation should fail the call, and `#[check]` for rules you want recorded without failing. | | `#[check]` | `#[assert]` | | --------------- | ---------------------------------------------------------------------- | --------------------------------------------------------------------------------------------- | | Label | Required | Optional | | On failure | Recorded; the call succeeds | The call fails | | Result location | `FieldMeta::checks` (`ConstraintResult { label, expression, passed }`) | `PredictError::Parse` wrapping `ParseError::AssertFailed { field, label, expression, value }` | ### Expression language Expressions are minijinja expressions evaluated with the parsed field value bound as `this`. Derive-emitted expressions compile once per process. | Works | Example | | ------------------------ | ------------------------------------------------------------------------------ | | Comparisons | `this >= 0.0 and this <= 1.0`, `this == "expected"` | | Boolean logic | `and`, `or`, `not`; `&&` and `\|\|` are normalized to `and`/`or` by the derive | | Length | `this\|length > 0` | | Membership and substring | `"https://" in this`, `"a" in this` | | Indexing | `this[0] == "first"` | | Filters and tests | `this\|lower == "positive"`, `this\|trim\|length > 0`, `this is defined` | Python-style method calls do not evaluate: `this.len()`, `this.startswith(...)`, and `this.contains(...)` always come out false. A failed evaluation counts as not passing, so an assert written that way fails every call. Use `this|length` and `in` instead. ### Inspecting results ```rust theme={null} let result = predict.call(input).await?; for check in result.metadata().field_checks("confidence") { if !check.passed { println!("check `{}` failed: {}", check.label, check.expression); } } let any_failed = result.metadata().has_failed_checks(); ``` ### Compile-time enforcement A `#[check]` without a label is a compile error: ```text theme={null} error: #[check] requires a label: #[check("expr", label = "name")] --> tests/ui/check_missing_label.rs:9:5 | 9 | #[check("this > 0")] | ^^^^^^^^^^^^^^^^^^^^ ``` ## See also * [Predict](/docs/components/predict) for calling signatures and handling `Predicted` output * [Adapters](/docs/components/adapters) for prompt formatting and the parse pipeline * [The module macro](/docs/components/module-macro) for declaring signatures as bodyless functions * Example: [01-simple.rs](https://github.com/krypticmouse/DSRs/blob/main/crates/dspy-rs/examples/01-simple.rs) * Example: [16-insurance-claim-prompt.rs](https://github.com/krypticmouse/DSRs/blob/main/crates/dspy-rs/examples/16-insurance-claim-prompt.rs) # State Source: https://dsrs.herumbshandilya.com/docs/components/state Save and restore optimized module state with ModuleState and PredictState After an optimizer tunes a module, the improved instructions and demos live only in memory. `ModuleState` snapshots the mutable state of every `Predict` leaf in a module into a serializable value, so an optimized program can be saved to disk and reloaded in production without re-running optimization. ```rust theme={null} // After optimization: ModuleState::from_module(&module)?.save("optimized.json")?; // In production: let mut module = MyPipeline::new(); ModuleState::load("optimized.json")?.apply(&mut module)?; ``` ## `ModuleState` A `ModuleState` holds one `PredictState` per predictor, keyed by the leaf name the module declares via [`Predictors`](/docs/components/modules#predictor-discovery-predictors) (`predictors: BTreeMap`). The `BTreeMap` keeps JSON output stable across runs. The names are the same ones optimizer candidates and trace spans use — one naming contract across persistence, optimization, and capture. | Method | Signature | Behavior | | ------------- | ----------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `from_module` | `fn from_module(module: &M) -> Result` | Snapshots every declared `Predict` leaf (instruction override + demos). | | `apply` | `fn apply(&self, module: &mut M) -> Result<()>` | Applies the state in place, stamping each restored leaf's trace name with its declared name. Every name in the state must resolve to a leaf in the module; unknown names are an error. Predictors not named in the state are left untouched. | | `to_json` | `fn to_json(&self) -> Result` | Serializes to pretty-printed JSON. | | `from_json` | `fn from_json(json: &str) -> Result` | Deserializes JSON produced by `to_json`. | | `save` | `fn save(&self, path: impl AsRef) -> Result<()>` | Writes `to_json` output to a file. | | `load` | `fn load(path: impl AsRef) -> Result` | Reads a file written by `save`. | ## `PredictState` and the JSON shape `PredictState` is the serializable snapshot of a single predictor's mutable state. | Field | Type | Meaning | | ---------------------- | ---------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `demos` | `Vec` | Demo rows as flat JSON objects: field name to value, with input and output fields merged into one object. Rows are split back into the predictor's typed `Demo` via the signature schema on load. | | `instruction_override` | `Option` | The instruction override, if any. `null` means the signature default applies. | A saved file therefore looks like: ```json theme={null} { "predictors": { "answerer": { "demos": [ { "question": "2+2?", "answer": "4" } ], "instruction_override": "Answer concisely." } } } ``` ## The install seam State loading reaches predictors through the object-safe per-leaf view `PredictorInfo` (see [Modules](/docs/components/modules#predictor-discovery-predictors)). Its `load_state` method is the install seam: a **full** overwrite of the leaf's optimizable state (`instruction_override: None` clears the override, `demos` replaces the demo set), used by `ModuleState::apply` and by the optimizer's one-shot install of the winning candidate. Candidate *evaluation* never calls it — candidates are injected ambiently per call tree (see [Optimizers](/docs/components/optimizers)). Every `load_state` invalidates the leaf's cached instance overlay, so a candidate is data applied through the seam, never ad hoc field mutation. ## Compatibility behavior There is no version field in the format. Compatibility is field level and structural: | Situation | Result | | ----------------------------------------------------- | -------------------------------------------------------- | | Missing `demos` or `instruction_override` in JSON | Defaults apply (`serde(default)` on both fields). | | State names a leaf absent from the module | `apply` returns an error listing the unknown predictors. | | Demo rows do not fit the predictor's signature schema | `apply` returns `failed to load state for `name\`\`. | | Module has predictors the state does not name | Left untouched, no error. | ## See also * [Predict](/docs/components/predict) * [Optimizers](/docs/components/optimizers) * [Functional lane (fx)](/docs/components/fx) * [Modules](/docs/components/modules) * [Example: save and load state](https://github.com/krypticmouse/DSRs/blob/main/crates/dspy-rs/examples/13-save-load-state.rs) # Tools and Agents Source: https://dsrs.herumbshandilya.com/docs/components/tools-and-agents Host and sandboxed tools, the #[agent] loop and its options, stop tools, and standalone versus in-module execution A tool is a function the model may call while it works: you describe what the tool does, the model decides when to use it. An agent is a step that runs the model in a loop with tools; the model thinks, calls tools, reads the results, and answers when it is done. DSRs has two tool kinds: host tools are Rust functions in your binary, and sandboxed tools are JavaScript snippets that travel inside a `.dsrs` program file. ## Host tools A host tool is a normal Rust function marked `#[tool]`. The body is the implementation: ```rust theme={null} use dspy_rs::tool; /// Uppercase text. #[tool(caps("demo:shout"))] fn shout(text: String) -> String { text.to_uppercase() } ``` The doc comment is the tool's description and the main thing the model uses to pick a tool, so write it for the model, like a short label on a button. `caps("...")` is optional and declares what the tool is allowed to touch, for example `net:search` or `fs:read`; the host checks these labels before it runs a program, so nothing gets network or file access silently. See [Capabilities](/docs/components/capabilities) for the full model. The function stays plain Rust; you can still call `shout("hi".to_string())` yourself in tests. Tools can be async, and they can fail. A fallible tool returns `Result` where `E: Display`: ```rust theme={null} /// Fetch a page and return its text. #[tool(caps("net:http"))] async fn fetch(url: String) -> Result { // your implementation } ``` `#[tool]` generates the original function unchanged, plus a module carrying the tool's signature, a `rig` tool wrapper for host binding, and `__dsrs_tool()` metadata consumed by `#[agent]` and `#[module]`. The rules: no generics, no `self`, plain identifier parameters, at least one parameter, an explicit return type. The attribute accepts only `caps("...", ...)`; anything else is a compile error. ## Declaring an agent An agent is the tool-loop sibling of `#[predict]`: a bodyless function whose doc comment is the instruction, whose parameters are the input fields, and whose return type is a single output field named after the function. ```rust theme={null} use dspy_rs::agent; /// Research the question. Use the shout tool when volume is needed. #[agent(tools(shout), max_turns = 3, budget(tokens = 50_000, on_exhausted = finalize))] fn research(question: String) -> String; ``` The bodyless-fn rules match `#[predict]`: no `async`, no generics, no `self`, plain identifier parameters, at least one parameter, an explicit return type. ### Options | Option | Form | Meaning | | ------------- | ----------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | | `model` | `model = "@name"` | Model reference for the loop. Binds only inside `#[module]` programs; setting it removes the standalone fn (calling one is a compile error). | | `tools` | `tools(a, b)` | The `#[tool]` functions the loop may call, in order. | | `stop_tools` | `stop_tools(a)` | Tools whose call ends the loop. Each must also appear in `tools(...)`. | | `max_turns` | `max_turns = N` | Turn bound for the lowered loop node (IR default is 8 when omitted). | | `until_parse` | `until_parse = bool` | Stop when an assistant turn parses as the signature outputs (IR default is true). | | `budget` | `budget(calls = N, tokens = N, deadline_ms = N, on_exhausted = finalize)` | Per-node spend limits. `on_exhausted` is `fail` (the default, the run fails) or `finalize` (one forced final round trip without tools, then parse). | | `context` | `context(max_history_turns = N, tool_result_max_bytes = N, playbook = "...")` | Context policy: history window, tool-result truncation, and a free-text playbook. | ## Stop tools Sometimes the model should end the loop with one explicit call instead of writing a final message. Declare a tool whose inputs match the agent's output fields and list it in `stop_tools(...)`: ```rust theme={null} /// Submit the final answer. Call this exactly once, when you are done. #[tool] fn submit(research: String) -> String { research } /// Research the question. Call submit when you have the answer. #[agent(tools(shout, submit), stop_tools(submit), max_turns = 6)] fn research(question: String) -> String; ``` When the model calls a stop tool, the loop ends right there. The arguments of that call become the step's output, and the tool body does not run. That is why the stop tool's inputs must be named after the agent's outputs (`research` here). A stop tool missing from `tools(...)` is a compile error: "stop tool `name` is not in tools(...)". ## Standalone or inside a module `#[agent]` generates a standalone `async fn` returning `Result, PredictError>`. Called directly, it executes the same 1-node `AgentLoop` program the `#[module]` lowering produces, with the loop options honored on both paths: `max_turns`/`stop_tools`/`until_parse` land in the node's `StopSpec`, `budget` in its `NodeBudget`, and `context` in its `ContextPolicy`. The one exception is `model`: model refs bind only inside a `#[module]` program, so setting `model = "…"` removes the standalone fn — calling it is a compile error rather than a silent fallback to the globally configured LM. ```rust theme={null} use dspy_rs::module; #[dspy_rs::Schema] #[derive(Debug)] pub struct AOut { pub answer: String, } #[module(caps("demo:shout"))] async fn agentic(question: String) -> Result { let researcher = research(question).await?; Ok(AOut { answer: researcher.research, }) } ``` The module's `caps(...)` ceiling must cover what its tools declare; `caps("demo:shout")` on the module matches `caps("demo:shout")` on the tool, and a tool whose needs exceed the ceiling is a load error. Use the standalone form for a quick agent call, and the module form when the agent is one step in a larger pipeline. ## Sandboxed tools A sandboxed tool is not Rust. It is a small piece of JavaScript stored inside the `.dsrs` text file itself, executed in a sandbox, so it cannot reach the network or the disk unless the file declares a capability and the host grants it. In a `.dsrs` file it looks like this: ```` tool shout "Uppercase the text" { in text: string out shout: string } js``` (args) => ({ shout: args.text.toUpperCase() }) ``` ```` Because the code is inside the file, the program is fully portable: anyone who can run the file gets the tool too. Host tools are the opposite: the file only names them, and the binary that serves the program must supply the implementation. See [The .dsrs file](/docs/components/dsrs-file) for the full text format. ## Related surfaces The struct-lane way to give a model tools is to attach them to a `Predict` (`PredictBuilder::add_tool`/`with_tools`): a tooled predictor executes as a 1-node `agent` program through the interpreter, with the default stop behavior (`until_parse`, `max_turns = 8`). See [Predict](/docs/components/predict). There is no separate `ReAct` module. Code Mode is the many-tools-to-one-script alternative. Instead of advertising N tool schemas and paying one round trip per call, the model sees a single `run_js` meta-tool whose description lists your tools as a JavaScript API; it writes one script that calls them as plain functions and returns one value. See [Code Mode](/docs/components/code-mode). ## Tool membership is optimizable Which tools a loop carries is a tuned value, not just structure. The agent node's `tools` list is the *declaration* — the loop's capability footprint, checked against the program ceiling at load. Which of those tools the loop actually presents to the model is the `ToolSet` parameter (`".tool_set"`), a slot like `instruction` or `demos`: an optimizer's candidate can drop a distracting tool or bring a declared one back, and the descriptions the model sees are themselves `ToolDesc` slots. The alphabet is closed — a candidate can never smuggle in a tool the declaration doesn't cover; that is refused at load, not at call time. Absent selection means the full declared list, so nothing changes until an optimizer says so. See [Program and nodes](/docs/components/program-and-nodes) for the slot machinery. ## Executing tool calls yourself To execute tool calls yourself instead of letting the loop dispatch them (a REPL the agent drives, tools that need caller-side state), run the agent through the interpreter's caller-managed conversation surface: `Interpreter::run_conversation_caller_managed` suspends the loop on tool calls and `resume_conversation` feeds your results back, with the same spans, budgets, and stop-tool behavior as the dispatching loop. The suspended surface presents the ToolSet-selected tools per call, same as the dispatching loop. See [Runtime](/docs/components/runtime). ## Common mistakes **Forgetting the body on a host tool.** `#[tool]` needs a real function with a body. A bodyless function is a step, not a tool. **A stop tool with the wrong input names.** The stop tool's arguments become the agent's output. If the names do not match the agent's output fields, the output cannot be filled in. **Skipping the doc comment.** The doc comment is the tool's description. It is the main thing the model uses to pick a tool, so write it for the model, not for other programmers. ## See also * [Capabilities](/docs/components/capabilities) for tool needs, program ceilings, and host grants * [Code Mode](/docs/components/code-mode) for presenting many tools as one script surface * [Modules](/docs/components/modules) for the struct-lane strategies * [The module macro](/docs/components/module-macro) for the body rules that lower agent calls * [The .dsrs file](/docs/components/dsrs-file) for sandboxed tool syntax in the artifact # Traces Source: https://dsrs.herumbshandilya.com/docs/components/traces Capture a run as a trace and replay it strictly or until divergence A trace records what a run did: one span per `Predict` call, carrying the rendered prompt, the parsed output, and a request fingerprint. Replay serves a later run back from that recording instead of a live provider. The `dsrs` CLI that checks, formats, and serves `.dsrs` artifacts has its own page: [CLI](/docs/components/cli). ## Recording and replaying a run Wrap any call in `capture` to get the run back as a value, then persist it as JSONL: ```rust theme={null} use dspy_rs::trace::capture; let (result, trace) = capture(|| pipeline(input.clone())).await; let result = result?; std::fs::write("run.jsonl", trace.to_jsonl()?)?; ``` `ReplayMode::Strict` proves the pipeline still behaves exactly as recorded: every call is served from the log, with zero provider calls. ```rust theme={null} use dspy_rs::{ReplayMode, Trace, replay}; let trace = Trace::from_jsonl(&std::fs::read_to_string("run.jsonl")?)?; let (replayed, report) = replay(&trace, ReplayMode::Strict, || { pipeline(input.clone()) }) .await; let replayed = replayed?; assert_eq!(report.live, 0, "strict replay never calls a provider"); ``` `ReplayMode::UntilDivergence` replays the unchanged prefix free and goes live only from the first call your change actually touches: ```rust theme={null} let (out, report) = replay(&trace, ReplayMode::UntilDivergence, || { pipeline(input.clone()) }) .await; println!("served {} free, {} live", report.served, report.live); ``` Each span's fingerprint covers the full rendered prompt and the model settings; divergence is detected at the first call whose fingerprint differs from its recording, and only calls from there run live. ## Capture `capture` records every `Predict` call on the current task into a `Trace` while the scope is active. ```rust theme={null} let (result, trace) = dspy_rs::trace::capture(|| module.call(input)).await; ``` * `capture(f)` runs the closure and returns its result plus the recorded `Trace`. * `capture_with_meta(meta, f)` is the same with caller-provided rollout metadata (`TraceMeta`: rollout input, candidate hash, free-form tags). A missing `trace_id` or start time is minted at scope start. Scoping is task-local: spawned subtasks do not inherit the scope, and nested scopes are exclusive (the innermost records). With no scope active the cost is one task-local probe per call. Traces serialize to JSONL via `Trace::to_jsonl` and `Trace::from_jsonl`. ## What a span records One span is one `Predict` invocation. At a high level it records: * **Who ran**: the component name (the same name the params system uses, so spans join back to tunable slots) plus a per-component sequence number; `(component, seq)` is unique per trace and is what replay keys on. * **What went in**: the rendered prompt (an interned system-and-demos prefix plus the live suffix), the typed input fields as JSON, and the redacted model config. * **What happened inside**: ordered events, one `Exchange` per provider round-trip and one `ToolRun` per tool execution. * **What came out**: the raw assistant text, the parsed output fields, aggregated token usage, and any error (kinds: `lm`, `parse`, `tool`, `cancelled`). * **What it was worth** (optional): a span-level `Eval`, present only when a metric assigned per-span credit through `TypedMetric::evaluate_spans` (see [Evaluation](/docs/components/evaluation)). Demo harvesting prefers it over the whole-rollout score; the field is omitted from the JSONL entirely when absent, so eval-free traces serialize exactly as before. * **A request fingerprint**: `request_hash`, a stable hash over the redacted model config plus the full rendered prompt. This is the replay key and the determinism check. * **Timing and completeness**: start time, duration, and a `complete` flag (false when the span was truncated or redacted; replay refuses incomplete spans). ## Replay `replay` serves `Predict` calls from a recorded trace instead of a live provider: each call's `request_hash` is compared against the next recorded span for its component, and on a match the recorded output is served with zero API calls and no tool execution. ```rust theme={null} let (out, report) = dspy_rs::trace::replay(&trace, ReplayMode::Strict, || pipeline(input)).await; ``` ### Modes | `ReplayMode` | Plain words | | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `Strict` | Every call must match its recording; any mismatch is a typed error. For fixtures and CI (`report.live` stays 0). | | `UntilDivergence` | Serve while hashes match; the first mismatching call and every call after it go to the live LM. For counterfactual replay: a mutated parameter changes only the prompts it affects, so the unchanged prefix replays free and only the changed suffix spends tokens. Once diverged, the session stays live even if a later hash happens to match again. | recorded run scrub research draft fingerprint mismatch detected, never declared live from here served from the log: free metered: you pay only from the turn you took ### `ReplayReport` fields | Field | Meaning | | ------------- | -------------------------------------------------------------------------------------------------------------- | | `served` | Calls served from the recording (zero provider calls). | | `live` | Calls that went to the live LM (always 0 in `Strict` mode). | | `diverged_at` | The recorded span at which divergence was first detected, when the mismatch could be pinned to one. | | `divergence` | The first mismatch, verbatim: the error `Strict` mode surfaced, or the reason `UntilDivergence` switched live. | ### Replay error kinds | `ReplayError` | Plain words | | -------------- | --------------------------------------------------------------------------------------------------------------------------------------- | | `Divergence` | The live request's hash differs from the recorded span's; the prompt or model config changed relative to the recording. | | `Incomplete` | The recorded span is unusable: truncated or redacted, or it has no parsed output because the recorded call failed. | | `Exhausted` | The trace has no recorded span for this component and sequence; the live run makes more calls than the recording. | | `OutputDecode` | The recorded span matched but its stored output no longer deserializes into the signature's output type (schema drift since recording). | Replay scoping mirrors capture: task-local, not inherited by spawned subtasks, innermost scope wins. Compose replay outside with capture inside to record a counterfactual rollout while serving its unchanged prefix from the base trace. Traces serialize after a run finishes (`Trace::to_jsonl`). For live, human-readable console output while a program runs, call `init_tracing` (documented on [Utils](/docs/components/utils)): it installs a process-global pretty `tracing` subscriber (respecting `RUST_LOG`, defaulting to `dspy_rs=debug`) and is independent of trace capture. There are no built-in exporters — the JSONL trace format is stable and self-describing, so external projections (observability, RL datasets) are serialization-side work on top of it. ## See also * [CLI](/docs/components/cli): `dsrs serve` returns the capture-scope trace artifact from `POST /run?trace=1` * [Utils](/docs/components/utils): `init_tracing` for live pretty console output * [Optimizers](/docs/components/optimizers): traces as the evidence base for reflective optimization * [Evaluation](/docs/components/evaluation): the evaluation loop that hands each rollout's trace to your metric * Example: [24-frontdesk-replay.rs](https://github.com/krypticmouse/DSRs/blob/main/crates/dspy-rs/examples/24-frontdesk-replay.rs), capture, strict replay, and until-divergence replay end to end * Example: [12-tracing.rs](https://github.com/krypticmouse/DSRs/blob/main/crates/dspy-rs/examples/12-tracing.rs), scoped trace capture for a composed module * Example: [17-pretty-tracing.rs](https://github.com/krypticmouse/DSRs/blob/main/crates/dspy-rs/examples/17-pretty-tracing.rs), `init_tracing` output against an offline LM # Utilities Source: https://dsrs.herumbshandilya.com/docs/components/utils Response caching, telemetry initialization, and stable hashing The `utils` module bundles three small pieces of shared infrastructure: the LM response cache, tracing setup, and the stable hasher used everywhere identity hashes are persisted. Cache and telemetry items are re-exported at the crate root; hash items live under `dspy_rs::utils::hash`. ```rust theme={null} use dspy_rs::init_tracing; use dspy_rs::utils::hash::stable_hash_debug; init_tracing()?; let id: u64 = stable_hash_debug(&value); ``` ## `ResponseCache` A hybrid memory plus disk LM response cache built on [foyer](https://docs.rs/foyer): 256MB in memory and 1GB on disk in a per-process temp directory (if the disk tier cannot be initialized, the cache degrades to memory-only with a warning instead of panicking). It also maintains a sliding window of the 100 most recent entries for `LM::inspect_history`. The cache is created automatically by `LM`; you do not construct it directly. Caching is per LM instance, and entries are not shared across instances. All methods take `&self`: the foyer cache is internally synchronized and the history ring sits behind its own small mutex, so concurrent LM calls never serialize on a cache-wide lock. | Method | Signature | Behavior | | -------------- | ------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------ | | `new` | `async fn new() -> Self` | Builds the hybrid cache and its disk tier. | | `get_entry` | `async fn get_entry(&self, key: CacheKey) -> Result>` | Fetches the full cached entry, including raw output. | | `insert_entry` | `fn insert_entry(&self, key: CacheKey, entry: CacheEntry)` | Synchronous insert, the direct path used by `LM::call`. Also pushes the entry into the history window. | | `get_history` | `fn get_history(&self, n: usize) -> Vec` | Returns the `n` most recent entries, newest first. | ### `CacheEntry` and `CacheKey` `CacheEntry` is a cached prompt-response pair: `prompt` (the formatted prompt sent to the LM), `usage` (token usage recorded for the original uncached call), and `raw_output` (the raw assistant text, so `LM::call` can replay a cached completion through the normal parse path). `CacheKey` is a `u64`. Keys are produced by `LM`, never built by hand: the LM streams the model name, the temperature bits, `max_tokens`, and the `Debug` representation of the full message history through `StableHasher`, with no intermediate JSON tree or string materialized. Demos and instructions live inside the messages, so they are covered automatically. Hashed keys keep foyer lookups and disk serialization O(1) in prompt size. ## Telemetry `init_tracing()` installs process-global, pretty tracing output for DSRs. | Behavior | Detail | | ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | | Filter | Uses `RUST_LOG` when present; falls back to `dspy_rs=debug` when `RUST_LOG` is unset or invalid. | | Idempotence | Repeated calls are no-ops after the first successful init. | | Errors | `TelemetryInitError::InvalidFilter` (bad fallback directive) or `TelemetryInitError::SetGlobalDefault` (a subscriber is already installed globally). | The module also exports `truncate(value: &str, max_chars: usize) -> &str`, a character-boundary-safe prefix truncation helper. Optimizers such as GEPA and SIMBA use it to bound raw span output when building reflection prompts. ## Stable hashing `std::hash::DefaultHasher` is not guaranteed stable across Rust releases. Replay fixtures, on-disk trace files, and cache keys must survive toolchain upgrades, so everything that hashes for identity uses FNV-1a 64-bit with a fixed algorithm. | Item | Purpose | | --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | | `StableHasher` | FNV-1a 64-bit `Hasher`: deterministic across platforms and Rust versions. | | `HashWriter<'a, H>` | Adapts a `Hasher` into a `std::fmt::Write` sink, so values hash through their `Debug` or `Display` representation without materializing a string. | | `stable_hash_debug(&value)` | Hashes a `Debug`-formatted value with the stable hasher, returning `u64`. | This guarantee is load-bearing in three places: trace `request_hash` values (recorded traces replay against live code in later builds), LM cache keys (`cache_key_for` uses the same hasher as the trace format), and optimizer candidate hashes (the engine hashes canonical JSON with `StableHasher`). ## See also * [LM](/docs/components/lm) * [Traces](/docs/components/traces) * [Optimizer engine](/docs/components/optimizer-engine) * [Example: inspect history](https://github.com/krypticmouse/DSRs/blob/main/crates/dspy-rs/examples/07-inspect-history.rs) * [Example: pretty tracing](https://github.com/krypticmouse/DSRs/blob/main/crates/dspy-rs/examples/17-pretty-tracing.rs) # How DSRs thinks Source: https://dsrs.herumbshandilya.com/docs/getting-started/how-dsrs-thinks The mental model behind the library: typed contracts, programs as data, and the three authoring lanes This page explains the ideas the library is built on. Each idea gets a fuller treatment on its component page; this is the map of how they fit together. ## Prompts are typed contracts Every LM interaction has the same shape: inputs go in, outputs come out, and an instruction says what to do. DSRs takes that literally. You declare the interaction as a Rust struct with `#[input]` and `#[output]` fields and a doc-comment instruction, and the library renders the prompt, calls the model, and parses the response back into your types. ```rust theme={null} /// Answer questions accurately and concisely. #[derive(Signature, Clone, Debug)] struct QA { #[input] question: String, #[output] answer: String, } ``` The consequences run deeper than convenience. Typed contracts compose: a step producing `QAOutput` plugs into anything that accepts that shape. The output type is itself part of the prompt: an enum constrains the model to its variants, an `Option` makes absence a legal answer, a constraint attaches a rule the value must satisfy. And the parsing layer absorbs the mess models actually produce, so a bad response is a typed error rather than a corrupted string. See [Signatures](/docs/components/signatures). The contract is stateless. The thing that executes it, a [predictor](/docs/components/predict), carries the changeable parts: few-shot demos, instruction overrides, model choice, tools. This split matters later: it is exactly the surface [optimizers](/docs/components/optimizers) edit. ## Programs are data A pipeline in DSRs is not only code that runs. It can also be held as data: a `Program` value with a canonical text form, the [`.dsrs` file](/docs/components/dsrs-file). the running pipeline compiled Rust: executable, opaque projected at build time one source, so the two cannot drift program qa draft rate out the .dsrs artifact printable, diffable, servable, embeddable The data form is what unlocks the rest of the library. A text artifact can be diffed in review and hashed for identity. A server can load and run it without your source (`dsrs serve`). An optimizer can address every instruction and demo set inside it by name and try candidate values as overlays, without mutating anything. A build can embed one back into a binary with `include_program!`, validated at compile time. The artifact is produced at build time, not recovered at runtime. The `#[module]` macro reads your function once and emits two projections of it: the runnable function and the `Program`. Because both come from a single read of a single source, the artifact cannot drift from the code. DSRs deliberately does not reconstruct structure by observing a run: a recording shows only the path that executed, so branches not taken and parallelism are invisible to it. Structure comes from source; recordings are for [replay](/docs/components/traces). ## Three authoring lanes There are three ways to write a pipeline, differing in how much of it the system can see: | | Structs | fx calls | `#[module]` | | -------------------------------- | ------- | -------- | ----------- | | Run it | Yes | Yes | Yes | | Trace its runs | Yes | Yes | Yes | | Tune its prompts | Yes | Yes | Yes | | Full control of the code | Yes | Some | Some | | Print it as a `.dsrs` file | No | No | Yes | | Serve it from a file | No | No | Yes | | Restructure it with an optimizer | No | No | Yes | The [struct lane](/docs/components/modules) is ordinary Rust: a struct with predictor fields and a `forward` method. Maximum control; the method body is compiled code the system can run but not read. The [fx lane](/docs/components/fx) is for scripts and experiments: predictors as named function calls, tunable by call-site name. The [`#[module]` lane](/docs/components/module-macro) is the recommended one for pipelines: a plain async function whose body is lowered to a `Program` at build time. The bottom rows of the table are constraints, not missing features. A server cannot load a function body that exists only as machine code, and an optimizer cannot restructure steps it cannot see. When part of a `#[module]` body is plain Rust that cannot be expressed as data, it is kept as a typed [hole](/docs/components/holes): named, typed at its boundary, opaque inside. ## The rest of the system Three more mechanisms complete the picture: * **[Capabilities](/docs/components/capabilities)** gate what programs can touch. The program declares its needs in the artifact; the host declares its grants; loading fails on any mismatch, before anything runs. * **[Traces](/docs/components/traces)** record every run. Strict replay reproduces a recorded run with zero live calls, which makes model-dependent tests free and deterministic. Until-divergence replay re-runs only the steps a change actually affects. * **[Evaluation](/docs/components/evaluation) and [optimizers](/docs/components/optimizers)** close the loop: a metric scores outputs (optionally with textual feedback), and optimizers search instruction and demo candidates against it. ## Where to go next Follow the [Quickstart](/docs/getting-started/quickstart) to make a first typed call, then read [Signatures](/docs/components/signatures) and [Predict](/docs/components/predict). The component pages are self-contained; read them in any order. # Quickstart Source: https://dsrs.herumbshandilya.com/docs/getting-started/quickstart Build your first LM pipeline in minutes DSRs lets you call language models with typed Rust structs. Define your inputs and outputs as a struct, and the library handles prompt formatting and response parsing. This guide walks you through building your first typed LM pipeline. Call `init_tracing()` once at startup in your app examples. Add to your `Cargo.toml`: ```toml theme={null} [dependencies] dspy-rs = "0.7" tokio = { version = "1", features = ["full"] } anyhow = "1" ``` Or via cargo: ```bash theme={null} cargo add dspy-rs tokio anyhow ``` Tell DSRs which model to use. This sets a global default that all predictors will use: ```rust theme={null} use dspy_rs::{configure, init_tracing, LM}; #[tokio::main] async fn main() -> anyhow::Result<()> { init_tracing()?; configure( LM::builder() .model("openai:gpt-4o-mini".to_string()) .build() .await?, ); Ok(()) } ``` Set `OPENAI_API_KEY` in your environment. For other providers, use the appropriate prefix (e.g., `anthropic:claude-3-haiku`). A [signature](/docs/components/signatures) declares your task's inputs and outputs: ```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, } ``` The doc comments become: * **Struct docstring** → instruction for the LM * **Field docstrings** → field descriptions in the prompt Create a [predictor](/docs/components/predict) and call it: ```rust theme={null} use dspy_rs::Predict; let predict = Predict::::new(); // QAInput is auto-generated from your #[input] fields let output = predict.call(QAInput { question: "What is the capital of France?".into(), }).await?; println!("Answer: {}", output.answer); ``` The `#[derive(Signature)]` macro generates `QAInput` from your `#[input]` fields. You get back a `QA` struct with both input and output fields filled in - `output.answer` is a typed `String`. ## Complete example ```rust theme={null} use dspy_rs::{configure, init_tracing, LM, Predict, Signature}; /// Answer questions accurately and concisely. #[derive(Signature, Clone, Debug)] struct QA { #[input] question: String, #[output] answer: String, } #[tokio::main] async fn main() -> anyhow::Result<()> { init_tracing()?; configure( LM::builder() .model("openai:gpt-4o-mini".to_string()) .build() .await?, ); let predict = Predict::::new(); let output = predict.call(QAInput { question: "What is the capital of France?".into(), }).await?; println!("Answer: {}", output.answer); Ok(()) } ``` ## Next steps The mental model behind the library Every attribute, every supported type, constraints Builder surface, demos, metadata, errors ChainOfThought, predictor discovery, composition ## Adding complexity ### Input formatting and rendering Use `#[format("json")]` for serialization, or `#[render(jinja = "...")]` for custom field text. See the full attribute reference in [Signatures & types](/docs/components/signatures) and runtime behavior in [Adapters](/docs/components/adapters). ### Custom types When you need more than primitives, add [`#[Schema]`](/docs/components/signatures): ```rust theme={null} use dspy_rs::{Signature, Schema}; #[Schema] #[derive(Clone, Debug)] enum Sentiment { Positive, Negative, Neutral, } #[derive(Signature, Clone, Debug)] /// Analyze the sentiment of the text. struct SentimentAnalysis { #[input] text: String, #[output] sentiment: Sentiment, #[output] confidence: f64, } ``` ### Few-shot demos Add examples to guide the LM: ```rust theme={null} use dspy_rs::Demo; let predict = Predict::::builder() .demo(Demo::::new( QAInput { question: "What is 2+2?".into() }, QAOutput { answer: "4".into() }, )) .build(); ``` ### Constraints Validate outputs with [`#[check]` and `#[assert]`](/docs/components/signatures): ```rust theme={null} #[derive(Signature, Clone, Debug)] struct Rating { #[input] text: String, #[output] #[assert("this >= 1 && this <= 5")] score: i32, } ``` ### Multi-step pipelines Chain [predictors](/docs/components/predict) for complex workflows: ```rust theme={null} struct SummarizeAndRate { summarizer: Predict, rater: Predict, } impl SummarizeAndRate { async fn run(&self, text: String) -> anyhow::Result { let summary = self.summarizer.call(SummarizeInput { text }).await?; let rating = self.rater.call(RateInput { summary: summary.summary, }).await?; Ok(rating.into_inner()) } } ``` See [Modules](/docs/components/modules) for how to make this optimizer-compatible. # Examples Source: https://dsrs.herumbshandilya.com/docs/guides/examples Runnable examples in the repository, from a first typed call to full optimizer runs Every example in this index is a complete, runnable program in [`crates/dspy-rs/examples/`](https://github.com/krypticmouse/DSRs/tree/main/crates/dspy-rs/examples). Most need an `OPENAI_API_KEY`; a few run fully offline against the in-process test client and say so in their header comment. Run any of them with: ```bash theme={null} cargo run --example 01-simple ``` Some examples need a feature flag (noted in the file header), for example `--features dataloaders` for the HotpotQA ones. ## The examples | Example | What it shows | Key APIs | | ------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------- | ---------------------------------------------- | | [01-simple.rs](https://github.com/krypticmouse/DSRs/blob/main/crates/dspy-rs/examples/01-simple.rs) | Typed signatures, chain-of-thought via a `reasoning` field, and module composition | `Signature`, `Predict`, `Module` | | [02-module-iteration-and-updation.rs](https://github.com/krypticmouse/DSRs/blob/main/crates/dspy-rs/examples/02-module-iteration-and-updation.rs) | Optimizing a module end to end with the typed optimizer API | `COPRO`, `compile_module`, `TypedMetric` | | [03-evaluate-hotpotqa.rs](https://github.com/krypticmouse/DSRs/blob/main/crates/dspy-rs/examples/03-evaluate-hotpotqa.rs) | Evaluating a typed QA predictor on a HotpotQA sample | `DataLoader`, `evaluate_trainset`, `Eval` | | [04-optimize-hotpotqa.rs](https://github.com/krypticmouse/DSRs/blob/main/crates/dspy-rs/examples/04-optimize-hotpotqa.rs) | COPRO optimization of a QA module on HotpotQA, then saving the result | `COPRO`, `ModuleState`, `average_score` | | [05-heterogenous-examples.rs](https://github.com/krypticmouse/DSRs/blob/main/crates/dspy-rs/examples/05-heterogenous-examples.rs) | Feeding a typed predictor from messy JSON rows; extra fields ignored, missing ones loud | serde boundary, generated input structs | | [06-other-providers-batch.rs](https://github.com/krypticmouse/DSRs/blob/main/crates/dspy-rs/examples/06-other-providers-batch.rs) | Batch inference across multiple providers via `provider:model` strings | `forward_all`, `LM::builder` | | [07-inspect-history.rs](https://github.com/krypticmouse/DSRs/blob/main/crates/dspy-rs/examples/07-inspect-history.rs) | Inspecting the LM's message history after a call | `get_lm`, LM history | | [08-optimize-mipro.rs](https://github.com/krypticmouse/DSRs/blob/main/crates/dspy-rs/examples/08-optimize-mipro.rs) | MIPROv2 optimization with HuggingFace data loading and a baseline comparison | `MIPROv2`, `DataLoader` | | [09-gepa-sentiment.rs](https://github.com/krypticmouse/DSRs/blob/main/crates/dspy-rs/examples/09-gepa-sentiment.rs) | GEPA with explicit textual feedback and a reflection LM | `GEPA`, `Eval::with_feedback`, `prompt_model` | | [10-gepa-llm-judge.rs](https://github.com/krypticmouse/DSRs/blob/main/crates/dspy-rs/examples/10-gepa-llm-judge.rs) | GEPA where a second LM judges outputs and its verdicts become the feedback | `GEPA`, judge `Predict`, `TypedMetric` | | [11-custom-client.rs](https://github.com/krypticmouse/DSRs/blob/main/crates/dspy-rs/examples/11-custom-client.rs) | Plugging in a custom provider client (Azure) | `LMClient::from_custom` | | [12-tracing.rs](https://github.com/krypticmouse/DSRs/blob/main/crates/dspy-rs/examples/12-tracing.rs) | Scoped trace capture: one span per predictor call, sliceable by component | `trace::capture`, `Trace::for_component` | | [13-save-load-state.rs](https://github.com/krypticmouse/DSRs/blob/main/crates/dspy-rs/examples/13-save-load-state.rs) | Persisting optimized instructions and demos, then reloading them (offline) | `ModuleState::save` / `load` | | [14-functional.rs](https://github.com/krypticmouse/DSRs/blob/main/crates/dspy-rs/examples/14-functional.rs) | The functional lane: pipelines as plain async functions, params injected ambiently (offline) | `fx::predict`, `fx::Params`, `fx::with_params` | | [15-tools.rs](https://github.com/krypticmouse/DSRs/blob/main/crates/dspy-rs/examples/15-tools.rs) | Giving a typed predictor tools to call during generation | tool definitions, `Predict` | | [16-insurance-claim-prompt.rs](https://github.com/krypticmouse/DSRs/blob/main/crates/dspy-rs/examples/16-insurance-claim-prompt.rs) | Printing the exact system prompt a nested extraction signature renders to | `ChatAdapter`, nested `Signature` types | | [17-pretty-tracing.rs](https://github.com/krypticmouse/DSRs/blob/main/crates/dspy-rs/examples/17-pretty-tracing.rs) | Human-readable tracing output against an offline canned LM | `init_tracing`, `TestCompletionModel` | ## The front desk examples These exercise a small support-desk pipeline end to end. Each maps to a component page and is kept compiling by CI. | Example | Component page | What it shows | | --------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------- | --------------------------------------------------------------------------------------- | | [18-code-mode.rs](https://github.com/krypticmouse/DSRs/blob/main/crates/dspy-rs/examples/18-code-mode.rs) | [Tools & agents](/docs/components/tools-and-agents) | Two tools collapsed into the `run_js` meta-tool via `ToolSet::code_mode` | | [20-frontdesk-contract.rs](https://github.com/krypticmouse/DSRs/blob/main/crates/dspy-rs/examples/20-frontdesk-contract.rs) | [Signatures](/docs/components/signatures) | Enums, nested types, constraints, demos, per-predictor models, the printed prompt | | [21-frontdesk-pipeline.rs](https://github.com/krypticmouse/DSRs/blob/main/crates/dspy-rs/examples/21-frontdesk-pipeline.rs) | [Modules](/docs/components/modules) | The pipeline as a struct with a `Module` impl, plus the `fx` variant | | [22-frontdesk-module.rs](https://github.com/krypticmouse/DSRs/blob/main/crates/dspy-rs/examples/22-frontdesk-module.rs) | [Module macro](/docs/components/module-macro) | `#[module]` with a hole; prints the real `.dsrs` artifact and `OPACITY` (offline) | | [23-frontdesk-agent.rs](https://github.com/krypticmouse/DSRs/blob/main/crates/dspy-rs/examples/23-frontdesk-agent.rs) | [Tools & agents](/docs/components/tools-and-agents) | `#[tool]` + `#[agent]` + module `caps(...)` wiring (prints the caps block offline) | | [24-frontdesk-replay.rs](https://github.com/krypticmouse/DSRs/blob/main/crates/dspy-rs/examples/24-frontdesk-replay.rs) | [Traces](/docs/components/traces) | Capture to JSONL, strict replay with zero live calls, until-divergence under an overlay | | [25-frontdesk-evaluate.rs](https://github.com/krypticmouse/DSRs/blob/main/crates/dspy-rs/examples/25-frontdesk-evaluate.rs) | [Evaluation](/docs/components/evaluation) | A labeled trainset, a `TypedMetric`, `evaluate_trainset` + `average_score` | | [26-frontdesk-tune.rs](https://github.com/krypticmouse/DSRs/blob/main/crates/dspy-rs/examples/26-frontdesk-tune.rs) | [Optimizers](/docs/components/optimizers) | Overlay by param path, ambient overlay run, `bake` with lineage (offline path included) | The `9x-` files ([90](https://github.com/krypticmouse/DSRs/blob/main/crates/dspy-rs/examples/90-smoke-slice1-typed-predict.rs) through [94](https://github.com/krypticmouse/DSRs/blob/main/crates/dspy-rs/examples/94-smoke-slice5-optimizer-interface.rs), plus the `97`-`99` benches) are CI surfaces: smoke slices that exercise typed predict, chain of thought, module authoring, and the optimizer interface, and a few performance benchmarks. They are kept deliberately boring and stable; read them as compatibility contracts rather than tutorials. # COPRO Source: https://dsrs.herumbshandilya.com/docs/optimizers/copro Iterative instruction refinement: config, usage, and when to use it COPRO (Collaborative Prompt Optimization) iteratively refines instructions through generation and evaluation cycles. ## How it works COPRO runs a generate-and-evaluate loop: 1. **Generate candidates**: Create `breadth` candidate instructions per predictor 2. **Evaluate**: Test each candidate on your training data 3. **Refine**: Use the best candidate as the seed for the next round 4. **Repeat**: Continue for `depth` rounds The base instruction always competes in every round, so COPRO never installs a candidate that is worse than what you started with. ## Configuration ```rust theme={null} let copro = COPRO::builder() .breadth(10) // Candidates per round (must be > 1) .depth(3) // Number of refinement rounds .build(); ``` Other fields: `prompt_model` (separate LM for generating candidate instructions), `eval_concurrency` (concurrent LM calls during evaluation, default 16), `track_stats`, and `init_temperature` (currently unused, reserved for candidate diversity control). The full field table is in the [optimizers reference](/docs/components/optimizers#copro). ## Usage example ```rust theme={null} use anyhow::Result; use bon::Builder; use dspy_rs::{ COPRO, Eval, LM, Module, Predict, PredictError, Predicted, Signature, Trace, TypedMetric, configure, init_tracing, }; #[derive(Signature, Clone, Debug)] struct QA { #[input] question: String, #[output] answer: String, } #[derive(Builder)] struct MyModule { #[builder(default = Predict::::new())] predictor: Predict, } dspy_rs::predictors!(MyModule { predictor }); impl Module for MyModule { type Input = QAInput; type Output = QAOutput; async fn forward(&self, inputs: QAInput) -> Result, PredictError> { self.predictor.call(inputs).await } } struct ExactMatchMetric; impl TypedMetric<(QAInput, QAOutput), MyModule> for ExactMatchMetric { async fn evaluate( &self, example: &(QAInput, QAOutput), prediction: &Predicted, _trace: Option<&Trace>, ) -> Result { let expected = example.1.answer.trim().to_lowercase(); let actual = prediction.answer.trim().to_lowercase(); Ok(Eval::score((expected == actual) as u8 as f64)) } } #[tokio::main] async fn main() -> Result<()> { init_tracing()?; // API key automatically read from OPENAI_API_KEY env var configure( LM::builder() .model("openai:gpt-4o-mini".to_string()) .build() .await?, ); let mut module = MyModule::builder().build(); let trainset = vec![ ( QAInput { question: "What is 2+2?".to_string(), }, QAOutput { answer: "4".to_string(), }, ), ( QAInput { question: "Capital of France?".to_string(), }, QAOutput { answer: "Paris".to_string(), }, ), ]; let copro = COPRO::builder() .breadth(10) .depth(3) .build(); let metric = ExactMatchMetric; copro.compile_module(&mut module, &trainset, &metric).await?; Ok(()) } ``` The trainset here is a slice of `(QAInput, QAOutput)` tuples, the zero-boilerplate row form. Any row type that implements `ToInput` works, including `#[derive(Example)]` structs carrying gold labels and metric-only fields; see [Data](/docs/components/data). The `predictors!` line names the module's optimizable leaves — without it the module does not satisfy the `Predictors` bound `compile_module` requires. ### Typed data loading Use the shared data ingress reference: [`DataLoader`](/docs/components/data). ## When to use COPRO **Best for:** * Quick iteration cycles * Simple tasks * Limited compute budget * Short turnaround requirements **Avoid when:** * You need best possible quality (use MIPROv2 or GEPA) * Task has complex failure modes (use GEPA) * You want to leverage prompting best practices (use MIPROv2) ## Comparison with other optimizers | Optimizer | Strategy | Needs feedback? | Cost | | ------------------ | --------------------------------------------------------- | --------------- | ------------------------------------------ | | `BootstrapFewShot` | One-shot demo harvesting from a teacher pass | No | Low (2 × trainset) | | `COPRO` | Breadth-first instruction search | No | Low (breadth × depth × trainset) | | `SIMBA` | Minibatch introspective ascent (demos + rules) | No | Low (steps × minibatch) | | `GEPA` | Genetic-Pareto evolution with feedback | **Yes** | Medium-high (iterations × eval) | | `MIPROv2` | Trace-guided candidate generation | No | Medium (candidates × trials × trainset) | | `Structural` | LM-guided graph edits over `ir::Edit` (program lane only) | No | Medium (examples + iterations × minibatch) | GEPA is the only optimizer that requires textual feedback from the metric (`Eval::with_feedback`). The others use numerical scores alone. Full configuration tables for all six live in the [optimizers reference](/docs/components/optimizers). ## Configuration details ### Breadth Number of candidate instructions generated at each round, per predictor. Higher breadth means more exploration but proportionally more LM calls. Must be greater than 1; `compile` errors otherwise. Recommended: 5-15 ### Depth Number of refinement rounds. Each round builds on the best candidate from the previous one, with diminishing returns beyond about 5. Recommended: 2-5 ### Prompt model An optional separate LM used to generate candidate instructions. Falls back to the global LM set with `configure` when unset. ### Track stats A per-round statistics flag. COPRO's `compile_module` returns `()`, so nothing is returned either way; leave it at the default. ## Implementation notes COPRO is a thin strategy over the shared evaluation engine: each candidate instruction is a name-keyed `Candidate` injected ambiently per rollout through a cached, bounded-concurrency fan-out — the module is never mutated during evaluation — and the final winner is installed once through `OptimizeTarget::install`. Repeated candidates are deduplicated by content hash and served from the rollout cache, so re-evaluating an instruction it has already seen costs nothing. See the [optimizer engine reference](/docs/components/optimizer-engine) for the machinery. Cost is approximately `breadth × depth × num_predictors × trainset_size` LM calls, minus cache hits. ## Examples See examples 02-module-iteration-and-updation.rs and 04-optimize-hotpotqa.rs # GEPA Source: https://dsrs.herumbshandilya.com/docs/optimizers/gepa Reflective instruction evolution from textual feedback: config, usage, and when to use it **GEPA** (Genetic-Pareto) is a reflective prompt optimizer: it evolves instructions using the textual feedback your metric returns alongside each score, and it keeps a Pareto frontier of candidates that win on different examples instead of a single best candidate. Reference: "GEPA: Reflective Prompt Evolution Can Outperform Reinforcement Learning" (Agrawal et al., 2025, [arxiv:2507.19457](https://arxiv.org/abs/2507.19457)) ## Overview GEPA adaptively evolves the textual components of your module (its instructions). In addition to the scalar score, your metric returns text feedback explaining why the score is what it is. That feedback gives GEPA visibility into the failure, and a reflection LM uses it to propose a better instruction. Because each rollout carries an explanation rather than just a number, GEPA can find high-performing prompts in comparatively few rollouts. ## What GEPA adds Compared to COPRO and MIPROv2, GEPA changes four things: ### Rich textual feedback Instead of just scalar scores (0.8, 0.9), GEPA uses detailed explanations: ``` Incorrect classification Expected: "positive" Predicted: "negative" Input text: "Great product but shipping was slow" May have misunderstood mixed sentiment ``` ### Pareto-based selection GEPA maintains a diverse set of candidates that excel on different examples, preventing premature convergence: * Candidate A: Best on examples 1, 3, 5 * Candidate B: Best on examples 2, 4, 6 * Both stay in the population (complementary strengths) ### LLM-driven reflection A reflection LM (`prompt_model`) reads the current instruction, the per-example feedback, and the mutated component's execution trace, then proposes a targeted rewrite: ``` "The current instruction doesn't handle mixed sentiments well. Suggest modifying to explicitly consider both positive and negative aspects..." ``` Without a `prompt_model`, mutation degrades to deterministic feedback concatenation, so setting one is strongly recommended. ### Inference-time search GEPA can optimize at test time, not just training time (see [Inference-time search](#inference-time-search)). ## Quick start ### 1. Implement a typed metric with feedback ```rust theme={null} use dspy_rs::*; #[derive(Builder)] struct MyModule { predictor: Predict, } dspy_rs::predictors!(MyModule { predictor }); impl Module for MyModule { type Input = MySignatureInput; type Output = MySignatureOutput; async fn forward(&self, inputs: MySignatureInput) -> Result, PredictError> { self.predictor.call(inputs).await } } // The trainset row: any struct. #[derive(Example)] projects it into the // signature's types by field name. See the Data page for the row model. #[derive(Example, Clone, Debug, serde::Serialize)] struct MyRow { question: String, answer: String, } struct MyMetric; impl TypedMetric for MyMetric { async fn evaluate( &self, example: &MyRow, prediction: &Predicted<::Output>, _trace: Option<&Trace>, ) -> Result { let predicted = prediction.answer.as_str(); let expected = example.answer.as_str(); let correct = predicted == expected; let score = if correct { 1.0 } else { 0.0 }; let feedback = if correct { format!("Correct answer: {predicted}") } else { format!("Incorrect\n Expected: {expected}\n Predicted: {predicted}") }; Ok(Eval::with_feedback(score, feedback)) } } ``` The third parameter is the rollout's execution [`Trace`](/docs/components/traces); metrics that inspect intermediate steps can slice it with `trace.for_component("predictor")`. ### 2. Configure and run GEPA ```rust theme={null} // The reflection LM that rewrites instructions from feedback let reflection_lm = LM::builder().temperature(1.0).build().await?; let gepa = GEPA::builder() .num_iterations(20) .minibatch_size(25) .prompt_model(reflection_lm) .track_stats(true) .max_rollouts(500) // Budget control .build(); let result = gepa.compile_module(&mut module, &trainset, &metric).await?; println!("Best score: {:.3}", result.best_candidate.average_score()); println!("Best instruction: {}", result.best_candidate.instruction); ``` ## Configuration options ```rust theme={null} GEPA::builder() .num_iterations(20) // Evolutionary generations .minibatch_size(25) // Examples per parent re-evaluation .track_stats(true) // Record candidates and evolution history .track_best_outputs(false) // Record best outputs per eval example .max_rollouts(500) // Budget: max evaluation rollouts .max_lm_calls(1000) // Budget: max LM calls .prompt_model(reflection_lm) // Reflection LM (strongly recommended) .eval_concurrency(16) // LM calls in flight during evaluation .seed(42) // Reproducible sampling .build() ``` `num_trials` and `temperature` also exist on the builder but are currently unused (reserved for multi-child evolution and mutation diversity control). The full field table is in the [optimizers reference](/docs/components/optimizers#gepa). Pass validation data at compile time: ```rust theme={null} let result = gepa .compile_module_with_valset(&mut module, &trainset, Some(&valset), &metric) .await?; ``` With `Some(valset)`, initial evaluation and child scoring use the validation set while parent re-evaluation uses trainset minibatches; with `None`, the trainset serves both roles (this is what `compile_module` does). ## Understanding GEPA results ```rust theme={null} let result = gepa.compile_module(&mut module, &trainset, &metric).await?; // Best candidate found (already installed on the module) println!("Best instruction: {}", result.best_candidate.instruction); println!("Average score: {:.3}", result.best_candidate.average_score()); println!("Generation: {}", result.best_candidate.generation); // Resource usage println!("Total rollouts: {}", result.total_rollouts); println!("Total LM calls: {}", result.total_lm_calls); // Evolution over time for (generation, score) in &result.evolution_history { println!("Gen {}: {:.3}", generation, score); } ``` ## Architecture ### Core components **Eval** The metric result type: one score, optional textual feedback. GEPA errors if any `Eval` has `feedback: None`. ```rust theme={null} pub struct Eval { pub score: f64, pub feedback: Option, } ``` **Execution traces** Every rollout runs under trace capture, recording one span per `Predict` invocation. GEPA feeds the mutated component's spans (`trace.for_component(name)`) to the reflection LM alongside the feedback. See [Traces](/docs/components/traces). **Pareto bookkeeping** GEPA uses the engine's score matrix directly: `ParetoView` (see [Optimizer engine](/docs/components/optimizer-engine)) tracks which validation columns each candidate wins on, parent sampling is proportional to that coverage, and candidates with zero wins are dominated. **GEPACandidate** ```rust theme={null} pub struct GEPACandidate { pub id: usize, pub instruction: String, pub module_name: String, pub example_scores: Vec, pub parent_id: Option, pub generation: usize, } ``` ### Evolutionary algorithm 1. **Initialize** the candidate pool with the unoptimized program 2. **Iterate**: * Sample a candidate from the Pareto frontier (proportional to coverage) * Sample a minibatch from the training set * Collect execution traces with feedback * Select a module component for targeted improvement * LLM Reflection: Propose a new instruction using reflective meta-prompting * Roll out the new candidate; if improved, evaluate on the validation columns * Update the Pareto frontier 3. **Continue** until budget is exhausted 4. **Return** best candidate by average score ## Implementing feedback metrics A well-designed metric is central to GEPA's sample efficiency. The DSRs implementation expects the metric to return an `Eval`; for GEPA that means `Eval::with_feedback(score, feedback)` on every example. ### Practical recipe for GEPA-friendly feedback * **Leverage existing artifacts**: Use logs, unit tests, evaluation scripts, profiler outputs * **Decompose outcomes**: Break scores into per-objective components * **Expose trajectories**: Label pipeline stages with pass/fail and errors * **Ground in checks**: Use validators or an [LLM judge](#using-an-llm-judge-for-feedback) for subjective tasks * **Prioritize clarity**: Focus on error coverage and decision points ### Feedback examples by domain **Document retrieval**: List correctly retrieved, incorrect, or missed documents **Multi-objective tasks**: Decompose aggregate scores to reveal contributions from each objective **Stacked pipelines**: Expose stage-specific failures (parse, compile, run, test) ## Best practices ### Design feedback for actionability ```rust theme={null} // BAD: Vague feedback Eval::with_feedback(0.5, "Wrong answer") // GOOD: Specific, actionable feedback Eval::with_feedback(0.5, "Incorrect answer\n\ Expected: 'Paris'\n\ Predicted: 'France'\n\ Issue: Returned country instead of city") ``` ### Leverage domain knowledge * Code generation: Show stage-specific failures * Retrieval: List specific documents missed * QA: Explain reasoning errors ### Balance feedback detail * Too brief: Not actionable * Too verbose: Drowns out signal * Aim for 2 to 5 lines per issue ### Set realistic budgets ```rust theme={null} // For development/testing GEPA::builder() .num_iterations(5) .max_rollouts(100) .build() // For production optimization GEPA::builder() .num_iterations(20) .max_rollouts(1000) .build() ``` ## Using an LLM judge for feedback For tasks where feedback rules are hard to codify, a second LLM can generate the feedback: it reads the task output and writes the evaluation text that becomes the metric's feedback string. ``` Task LM → generates answer + reasoning ↓ Judge LM → analyzes quality and provides feedback ↓ GEPA Reflection LM → reads feedback and improves prompt ↓ Better Task LM prompt ``` A judge fits these situations: * Subjective quality assessment (writing style, helpfulness, clarity) * Complex reasoning evaluation (soundness of the logic) * Tasks where rules are hard to codify * Analyzing reasoning quality beyond answer correctness Prefer deterministic checks over a judge when they exist: * Unit tests or schema validation cover the failure modes * Correctness is verifiable (code compilation, exact matches) * Evaluation must be fast and cheap * The outcome is a simple binary pass/fail ### Task signature with reasoning ```rust theme={null} #[derive(Signature, Clone, Debug)] struct MathWordProblem { /// Solve the problem step by step. #[input] problem: String, #[output] reasoning: String, // We want to optimize this too #[output] answer: String, } ``` ### Judge signature ```rust theme={null} #[derive(Signature, Clone, Debug)] struct MathJudge { /// Evaluate student reasoning and answer quality. #[input(desc = "The original problem")] problem: String, #[input(desc = "Expected answer")] expected_answer: String, #[input(desc = "Student answer")] student_answer: String, #[input(desc = "Student reasoning")] student_reasoning: String, #[output(desc = "Evaluation of the solution quality")] evaluation: String, // This becomes the feedback } ``` ### Optimized module ```rust theme={null} #[derive(Builder)] struct MathSolver { #[builder(default = Predict::::new())] solver: Predict, // This gets optimized } dspy_rs::predictors!(MathSolver { solver }); ``` ### TypedMetric with judge ```rust theme={null} /// A labeled trainset row: `(input, gold output)` tuples are rows out of the /// box — no dedicated struct needed for a small inline trainset. type MathRow = (MathWordProblemInput, MathWordProblemOutput); struct LlmJudgeMetric { judge: Predict, } impl TypedMetric for LlmJudgeMetric { async fn evaluate( &self, example: &MathRow, prediction: &Predicted, _trace: Option<&Trace>, ) -> Result { let (input, gold) = example; let problem = input.problem.clone(); let expected = gold.answer.clone(); let student_answer = prediction.answer.clone(); let student_reasoning = prediction.reasoning.clone(); let exact_match = student_answer.trim() == expected.trim(); let judge_output = self .judge .call(MathJudgeInput { problem: problem.clone(), expected_answer: expected.clone(), student_answer: student_answer.clone(), student_reasoning: student_reasoning.clone(), }) .await; let (score, evaluation_text) = match judge_output { Ok(evaluation) => { let evaluation_text = evaluation.evaluation.clone(); let score = if exact_match { if evaluation_text.to_lowercase().contains("clear") || evaluation_text.to_lowercase().contains("correct") { 1.0 } else { 0.7 } } else if evaluation_text.to_lowercase().contains("partially") || evaluation_text.to_lowercase().contains("good start") { 0.3 } else { 0.0 }; (score, evaluation_text) } Err(err) => { let fallback = format!( "judge call failed: {err}; expected={expected}; predicted={student_answer}" ); ((exact_match as u8 as f64), fallback) } }; Ok(Eval::with_feedback( score, format!( "problem={problem}\nexpected={expected}\npredicted={student_answer}\njudge={evaluation_text}" ), )) } } ``` `GEPA` itself does not own a special `feedback_metric` hook. The feedback function lives in your `TypedMetric` implementation, and GEPA enforces that every evaluation returns `Eval::with_feedback(...)`. That keeps the optimizer generic while preserving full judge-driven behavior. ### What a judge catches * Lucky guesses: a correct answer reached through unsound reasoning is penalized instead of scoring 1.0. * Partial progress: a wrong answer with a correct approach (an arithmetic slip in the final step) earns partial credit instead of 0. * Systematic issues: the judge surfaces recurring patterns such as skipped intermediate steps, confused concepts (area versus perimeter), or missing unit checks, and GEPA's reflection turns them into explicit instructions. In the worked example, the baseline instruction "Solve the math word problem step by step" produced solutions that skipped steps, which the judge flagged as incomplete reasoning. After optimization the instruction requires all intermediate calculations to be shown and each step to be labeled. The judge's analysis is the signal that drives the rewrite. ### Cost considerations LLM judges double your evaluation cost since every prediction requires both a task LM call and a judge LM call. Budget accordingly: ```rust theme={null} GEPA::builder() .num_iterations(3) // Fewer iterations .minibatch_size(3) // Smaller batches .max_lm_calls(100) // Explicit limit .build() ``` Ways to contain cost: * Use a cheaper model for judging (gpt-4o-mini vs gpt-4) * Judge only failed examples (not ones that passed) * Cache judge evaluations for identical outputs * Use parallel evaluation to reduce wall-clock time ### Hybrid metrics Combining explicit checks with LLM judging often gives the best results: ```rust theme={null} impl TypedMetric for HybridMetric { async fn evaluate( &self, example: &MyRow, prediction: &Predicted<::Output>, _trace: Option<&Trace>, ) -> Result { let mut score = 1.0; let mut feedback_parts = vec![]; // Explicit checks first (fast, cheap, deterministic) if !is_valid_json(&prediction.result_json) { feedback_parts.push("Invalid JSON format".to_string()); score = 0.0; } if score > 0.0 && missing_required_fields(&prediction.result_json) { feedback_parts.push("Missing fields: user_id, timestamp".to_string()); score *= 0.5; } // Optional judge pass for qualitative scoring if score > 0.0 { let judge_feedback = self.judge_quality(example, prediction).await?; if judge_feedback.to_lowercase().contains("low quality") { score *= 0.7; } feedback_parts.push(judge_feedback); } Ok(Eval::with_feedback(score, feedback_parts.join("\n"))) } } ``` ### Running the judge example ```bash theme={null} OPENAI_API_KEY=your_key cargo run --example 10-gepa-llm-judge ``` The run shows baseline performance, judge evaluations during optimization, the prompt evolving from feedback, and a final test with judge analysis. See the complete implementation with step-by-step comments ## Examples Basic GEPA usage with explicit feedback for sentiment classification Using an LLM judge to generate feedback ## Comparison with other optimizers | Optimizer | Strategy | Needs feedback? | Cost | | ------------------ | --------------------------------------------------------- | --------------- | ------------------------------------------ | | `BootstrapFewShot` | One-shot demo harvesting from a teacher pass | No | Low (2 × trainset) | | `COPRO` | Breadth-first instruction search | No | Low (breadth × depth × trainset) | | `SIMBA` | Minibatch introspective ascent (demos + rules) | No | Low (steps × minibatch) | | `GEPA` | Genetic-Pareto evolution with feedback | **Yes** | Medium-high (iterations × eval) | | `MIPROv2` | Trace-guided candidate generation | No | Medium (candidates × trials × trainset) | | `Structural` | LM-guided graph edits over `ir::Edit` (program lane only) | No | Medium (examples + iterations × minibatch) | GEPA is the only optimizer that requires textual feedback from the metric (`Eval::with_feedback`). The others use numerical scores alone. Full configuration tables for all six live in the [optimizers reference](/docs/components/optimizers). ### When to use GEPA * Complex tasks with subtle failure modes * When you can provide rich feedback * Multi-objective optimization * Need for diverse solutions * Inference-time search ### When to use alternatives * **COPRO**: Simple tasks, quick iteration * **MIPROv2**: Best prompting practices, single objective ## Troubleshooting ### Issue: GEPA errors because an `Eval` has no feedback GEPA requires feedback for every evaluated example. ```rust theme={null} // Solution: Return Eval::with_feedback(...) from TypedMetric::evaluate impl TypedMetric for MyMetric { async fn evaluate( &self, example: &MyRow, prediction: &Predicted<::Output>, _trace: Option<&Trace>, ) -> Result { Ok(Eval::with_feedback(1.0, "detailed textual feedback")) } } ``` ### Issue: Slow convergence ```rust theme={null} // Increase minibatch size for a better signal per generation GEPA::builder().minibatch_size(50).build() // Make sure a reflection LM is set; without one, mutation is just // deterministic feedback concatenation GEPA::builder().prompt_model(reflection_lm).build() ``` ### Issue: Running out of budget ```rust theme={null} // Reduce iterations or increase budget GEPA::builder() .num_iterations(10) .max_rollouts(2000) .build() ``` ## Inference-time search GEPA can act as a test-time/inference search mechanism. By setting your `valset` to your evaluation batch and enabling `track_best_outputs(true)`, GEPA produces for each batch element the highest-scoring outputs found during the evolutionary search. ```rust theme={null} let gepa = GEPA::builder() .track_stats(true) .track_best_outputs(true) .build(); let result = gepa .compile_module_with_valset(&mut module, &my_tasks, Some(&my_tasks), &metric) .await?; // Access per-task best scores and outputs let best_scores = result.highest_score_achieved_per_val_task; let best_outputs = result.best_outputs_valset; ``` ## Additional resources * [GEPA Paper](https://arxiv.org/abs/2507.19457) * [GEPA GitHub](https://github.com/gepa-ai/gepa) * [Using an LLM judge for feedback](#using-an-llm-judge-for-feedback) * [Example Code](https://github.com/krypticmouse/DSRs/blob/main/crates/dspy-rs/examples/09-gepa-sentiment.rs) # MIPROv2 Source: https://dsrs.herumbshandilya.com/docs/optimizers/miprov2 Trace-guided instruction and demo optimization: config, usage, and when to use it MIPROv2 (Multi-prompt Instruction PRoposal Optimizer v2) is a trace-guided instruction and demo optimizer. It differs from COPRO by running your program first, then using the captured execution traces plus a library of prompting best practices to generate candidate instructions, rather than searching blind. It also bootstraps few-shot demos from successful runs. ## How it works MIPROv2 works in four phases: ### Phase 1: Trace collection One traced teacher pass over the trainset. Every example runs through your module under trace capture, collecting whole-program scores plus per-`Predict` input/output spans. ### Phase 2: Demo bootstrapping Successful spans scoring at least `min_demo_score` become few-shot demos on the predictor that produced them (top `max_bootstrapped_demos` by score, deduplicated on inputs). A span scores as its rollout does, unless the metric attached a span-level eval via `TypedMetric::evaluate_spans` — that score then takes precedence (see [Evaluation](/docs/components/evaluation#per-span-credit)). Demos are installed before instruction search, so candidates are scored against the module as it will actually run. ### Phase 3: Candidate generation Uses the traces and a rotation of prompting tips to generate `num_candidates` instruction variants per predictor. The prompting tips library includes: * Use clear, specific language * Consider chain-of-thought for complex tasks * Specify output formats * Use role-playing when appropriate * Handle edge cases explicitly * Request structured outputs when needed ### Phase 4: Trial evaluation * Evaluates up to `num_trials` candidates per predictor on one sampled minibatch (candidates injected ambiently — the module is never touched during evaluation) * Computes performance scores * Selects the best performing candidate * Installs the accumulated winner (demos + best instructions) once at the end through `OptimizeTarget::install` ## Configuration Default settings: ```rust theme={null} let optimizer = MIPROv2::builder() .num_candidates(10) // Instruction variants per predictor .num_trials(20) // Max candidates evaluated per predictor .minibatch_size(25) // Examples per candidate evaluation .max_bootstrapped_demos(4) // Demos installed per predictor .min_demo_score(0.0) // Score gate for demo-eligible spans .build(); ``` You can also set `eval_concurrency` (concurrent LM calls during evaluation, default 16) and `seed` (fixes minibatch sampling for reproducible runs). The full field table is in the [optimizers reference](/docs/components/optimizers#miprov2). ## Usage example ```rust theme={null} use dspy_rs::MIPROv2; // Create optimizer let optimizer = MIPROv2::builder() .num_candidates(10) .num_trials(20) .minibatch_size(25) .build(); // Typed metric implementing TypedMetric for your trainset row type let metric = ExactMatchMetric; // Optimize your module (MyModule declares its leaves via predictors!) optimizer.compile_module(&mut module, &train_examples, &metric).await?; ``` The metric is the same `TypedMetric` used by every optimizer: `evaluate(&self, example, prediction, trace) -> Result`, where `example` is your full trainset row. MIPROv2 only reads the numerical score; feedback is ignored. See the [evaluation reference](/docs/components/evaluation) for the trait. `train_examples` is a slice of any row type implementing `ToInput` toward the module's input — a `#[derive(Example)]` struct or `(Input, Output)` tuples; see [Data](/docs/components/data). The module must declare its leaves with `predictors!`; see [Modules](/docs/components/modules#predictor-discovery-predictors). ### Typed data loading Use the shared data ingress reference: [`DataLoader`](/docs/components/data). ## Comparison: COPRO vs MIPROv2 vs GEPA | Optimizer | Strategy | Needs feedback? | Cost | | ------------------ | --------------------------------------------------------- | --------------- | ------------------------------------------ | | `BootstrapFewShot` | One-shot demo harvesting from a teacher pass | No | Low (2 × trainset) | | `COPRO` | Breadth-first instruction search | No | Low (breadth × depth × trainset) | | `SIMBA` | Minibatch introspective ascent (demos + rules) | No | Low (steps × minibatch) | | `GEPA` | Genetic-Pareto evolution with feedback | **Yes** | Medium-high (iterations × eval) | | `MIPROv2` | Trace-guided candidate generation | No | Medium (candidates × trials × trainset) | | `Structural` | LM-guided graph edits over `ir::Edit` (program lane only) | No | Medium (examples + iterations × minibatch) | GEPA is the only optimizer that requires textual feedback from the metric (`Eval::with_feedback`). The others use numerical scores alone. Full configuration tables for all six live in the [optimizers reference](/docs/components/optimizers). ### When to use MIPROv2 * You have decent training data (15+ examples recommended) * Quality matters more than speed * Task benefits from prompting best practices and few-shot demos * Need trace-informed candidate generation ### When to use COPRO * You need fast iteration * Compute budget is limited * Task is straightforward ### When to use GEPA * Complex tasks with subtle failure modes * You can provide rich feedback * Multi-objective optimization * Need diverse solutions ## Implementation notes The code follows standard Rust practices: * No unsafe blocks * Results for error handling with context via anyhow * Strong types (`Candidate`, `PromptingTips`) * Builder pattern for configuration * Async throughout, no blocking calls Key public types: * `PromptingTips` - the library of best practices (`default_tips()`, `format_for_prompt()`) * `Candidate` - the shared engine currency MIPROv2 registers its instruction variants as (see [Optimizer engine](/docs/components/optimizer-engine)) `compile_module` returns `()`: MIPROv2 installs the winner on the module and reports nothing further (`Report::None` through the trait). Cost is roughly `num_predictors × (trainset_size + num_trials × minibatch_size)` LM calls, minus rollout-cache hits. ## Testing Run tests: ```bash theme={null} cargo test --test test_miprov2 ``` The suite covers trace selection, candidate generation, configuration, and edge cases. ## Example Complete working example with HuggingFace data loading The example loads data, measures baseline performance, runs optimization, and shows the improvement. ## References * [DSPy Framework](https://github.com/stanfordnlp/dspy) * [DSPy Paper](https://arxiv.org/abs/2310.03714) # Structural Source: https://dsrs.herumbshandilya.com/docs/optimizers/structural LM-guided graph edits over the edit calculus: config, usage, and when to use it **Structural** is the structural optimizer: where the other five strategies tune parameter values (instructions, demos) through overlays, Structural rewrites the program graph itself. Each generation it gathers the [`legal_edits`](/docs/components/edit-calculus#legal_edits-the-proposer-menu) menu, has a reflection LM choose one edit from the serialized menu plus the incumbent's evaluation feedback, applies it with `Program::edited`, carries the tuned overlay across the change with `migrate_overlay`, and keeps the child only if it beats the parent on a shared minibatch. Structural runs on the **program lane only**: it needs an interpreter-loaded [`Program`](/docs/components/program-and-nodes) whose skeleton is data. Typed modules have no editable skeleton, so there is no `compile_module` here. ## Overview The [edit calculus](/docs/components/edit-calculus) makes structural mutation safe: edits are serde values, `Program::edited` is pure and re-validates, and `migrate_overlay` re-mints tuned slot values against the child. Structural is the search loop on top: a GEPA-style reflection step chooses *which* edit to try, and the engine's minibatch gate decides whether to keep the result. The moves it can propose: | Move | Effect | | ------------------------------------------ | ------------------------------------------------------------------------------------------------ | | `AugmentSig` | Prepend the chain-of-thought `reasoning` output field to a leaf's signature (the CoT move). | | `SwapToAgent` / `SwapToPredict` | Swap a `predict` leaf into a tool-using `agent` loop over the program's declared tools, or back. | | `WrapRetry` | Wrap a node in a `Retry` (2 attempts, feedback on). | | `Remove` | Remove a step from its `seq`. | | `AddTool { tool }` / `RemoveTool { tool }` | Declare or undeclare a program tool on an agent leaf. | `SetStop` and `SetInstructionDefault` appear in `legal_edits` but are excluded from Structural's menu: they need free-form values, which is value-level work the other optimizers already own. ## Quick start ### 1. Load a program and implement a `ProgramMetric` ```rust theme={null} use dspy_rs::ir::{DemoRow, Interpreter, Program, RuntimeEnv}; use dspy_rs::trace::JsonMap; use dspy_rs::{Eval, ProgramMetric, Trace}; let program = Program::load_dsrs("qa.dsrs")?; let interp = Interpreter::load(program, RuntimeEnv::new()).await?; struct ExactMatch; impl ProgramMetric for ExactMatch { async fn evaluate( &self, example: &DemoRow, output: &JsonMap, _trace: Option<&Trace>, ) -> anyhow::Result { let correct = output.get("answer") == example.output.get("answer"); Ok(Eval::with_feedback( correct as u8 as f64, if correct { "correct".into() } else { format!("expected {:?}", example.output.get("answer")) }, )) } } ``` Feedback is optional for Structural, but whatever the metric returns is what the reflection LM reads when choosing an edit, so specific feedback buys better proposals. ### 2. Configure and run ```rust theme={null} use dspy_rs::Structural; let reflection_lm = LM::builder().model("openai:gpt-4o".to_string()).build().await?; let structural = Structural::builder() .num_iterations(8) .minibatch_size(8) .prompt_model(reflection_lm) .max_rollouts(400) // every child is a fresh program: cap the spend .seed(42) .build(); let report = structural .compile_program(&interp, &examples, &ExactMatch, || { // A fresh RuntimeEnv per child load: the same model/tool/sandbox // bindings the incumbent was loaded with. RuntimeEnv::new() }) .await?; println!("{:.3} -> {:.3}", report.baseline_score, report.final_score); ``` The closure argument supplies a fresh [`RuntimeEnv`](/docs/components/runtime) every time an edited child needs loading. Only the host knows the live bindings (models, host tools, sandbox, capability grants), so child loading cannot be implicit; return the same bindings you loaded the incumbent with. ### 3. Keep the winner The winner is returned, never installed: the interpreter you passed in is untouched. Bake the migrated overlay into the winning program to get a single self-contained artifact: ```rust theme={null} use dspy_rs::ir::Lineage; let baked = report.program.bake(&report.overlay, Lineage::default())?; baked.save_dsrs("qa-structural.dsrs")?; ``` If you ran a value-level optimizer first (GEPA, COPRO), pass its winning overlay in and Structural carries it across every accepted edit: ```rust theme={null} let report = structural .compile_program_with_overlay(&interp, Some(tuned_overlay), &examples, &ExactMatch, env) .await?; assert_eq!(report.overlay.base, report.program.meta.program_hash); ``` ## Configuration options ```rust theme={null} Structural::builder() .num_iterations(8) // Structural generations to attempt .minibatch_size(8) // Shared minibatch for the parent/child gate .prompt_model(reflection_lm) // Reflection LM that chooses edits (recommended) .max_rollouts(400) // Budget: max evaluation rollouts .max_lm_calls(500) // Budget: max LM calls (rollouts + reflection) .eval_concurrency(16) // Rollouts in flight during evaluation .seed(42) // Reproducible sampling and fallback choice .build() ``` | Field | Type | Default | Description | | ------------------ | --------------- | ------- | ------------------------------------------------------------------------------------------------- | | `num_iterations` | `usize` | `8` | Generations to attempt; each proposes exactly one edit. | | `minibatch_size` | `usize` | `8` | Examples in the shared minibatch parent and child are compared on. | | `prompt_model` | `Option` | `None` | Reflection LM that chooses an edit from the menu. Without it the choice is a seeded-uniform pick. | | `max_rollouts` | `Option` | `None` | Hard cap on evaluation rollouts. | | `max_lm_calls` | `Option` | `None` | Hard cap on LM call units (rollouts plus reflection). | | `eval_concurrency` | `usize` | `16` | Concurrent rollouts during evaluation. | | `seed` | `Option` | `None` | Fixes minibatch sampling and the fallback edit choice. | ## Understanding Structural results `compile_program` returns a `StructuralReport`: | Field | Type | Description | | ----------------------- | --------------------- | ----------------------------------------------------------------------------------------------------------------------------------- | | `program` | `Arc` | The winning program (the input program when nothing was accepted). | | `overlay` | `Overlay` | The incumbent overlay re-minted against the winner at every accepted edit. | | `baseline_score` | `f64` | Mean metric score of the input program (plus overlay) over the examples. | | `final_score` | `f64` | Full-set mean of the final program; equals the baseline when nothing was accepted. | | `edits` | `Vec` | The accepted edits, in order. Node ids are handles against each step's parent (`parent_hash`), so this is a lineage, not one batch. | | `steps` | `Vec` | Per-generation outcomes, in order. | | `accepted` / `rejected` | `usize` | Generations promoted / not promoted by the gate. | | `spend` | `Spend` | Engine spend for the whole run, reflection calls included. | Each `StructuralStep` records `generation`, the targeted `leaf`, the concrete `edit` (serde data, replayable against `parent_hash`), `parent_minibatch_score`, `child_minibatch_score` (`None` when the child never scored), `accepted`, `full_score` (`Some` only when accepted), and `rejection` (why a child never scored, when it didn't). ```rust theme={null} for step in &report.steps { println!( "gen {}: {:?} on `{}` — parent {:.2}, child {:?}, accepted: {}", step.generation, step.edit, step.leaf, step.parent_minibatch_score, step.child_minibatch_score, step.accepted, ); } ``` ## The loop 1. **Baseline** the incumbent (plus overlay) over the full example set. This seeds the engine's rollout cache, so every later parent minibatch read costs nothing. 2. Each generation: * **Sample** a shared minibatch (seeded RNG). The incumbent's minibatch mean is the gate threshold. * **Menu**: gather `legal_edits` for every leaf; keep the materializable kinds. * **Choose**: the reflection LM reads the program's canonical `.dsrs` text, the menu (one JSON object per line, each with an `option` number), and the incumbent's per-example feedback, and answers with one option number. * **Apply**: `Program::edited` mints the child; `migrate_overlay` re-mints the incumbent overlay against it; the child loads through your `RuntimeEnv` factory. * **Gate**: the child is scored on the same minibatch. Only a strict win promotes it to a full-set evaluation and makes it the new incumbent. 3. **Return** the incumbent program and its overlay. Every rejection path degrades gracefully: an edit that fails to apply (`EditError`), a child that fails validation or loading, or a reflection reply that does not parse is recorded in the step and skipped. A run only errors on the engine's own failure modes (a metric error, an LM error during evaluation, a budget too small for the baseline pass). ## Cost model Every child is a fresh program: its hash keys fresh rollout-cache rows, so nothing it does is served from the parent's cache. Per run: * `examples.len()` rollouts for the baseline pass; * per generation, `minibatch_size` rollouts for the gate, plus the remaining `examples.len() - minibatch_size` only on promotion, plus one reflection call when a `prompt_model` is set. Cap the spend with `max_rollouts` / `max_lm_calls`; the run stops cleanly when the next batch would not fit. ## When to use Structural * The program's *shape* is the bottleneck: a leaf that should reason step by step, a step that should be an agent with tools (or should not be), a flaky node that needs a retry. * After a value-level pass: tune instructions first, then let Structural search structure while `migrate_overlay` preserves the tuned text. * You have a labeled example set and budget for whole-program re-evaluation. Prefer the value-level optimizers when instructions and demos are the lever: they are cheaper (cache-friendly, no program reloads) and search a denser space. ## Troubleshooting ### The run accepts nothing The gate requires a strict minibatch win. Small minibatches are noisy; raise `minibatch_size` for a better signal, and set a `prompt_model` so choices are informed rather than uniform. ### Steps show `rejection: Some("edit failed: ...")` Normal. The menu is structural, and data-flow legality is the validator's call: removing a step whose outputs a later binding still references, for example, is refused by `Program::edited` and skipped. See [the edit calculus](/docs/components/edit-calculus#errors). ### The run errors with "budget too small for the baseline pass" The baseline needs `examples.len()` rollouts before the loop can start. Raise `max_rollouts` or shrink the example set. ## Comparison with other optimizers | Optimizer | Strategy | Needs feedback? | Cost | | ------------------ | --------------------------------------------------------- | --------------- | ------------------------------------------ | | `BootstrapFewShot` | One-shot demo harvesting from a teacher pass | No | Low (2 × trainset) | | `COPRO` | Breadth-first instruction search | No | Low (breadth × depth × trainset) | | `SIMBA` | Minibatch introspective ascent (demos + rules) | No | Low (steps × minibatch) | | `GEPA` | Genetic-Pareto evolution with feedback | **Yes** | Medium-high (iterations × eval) | | `MIPROv2` | Trace-guided candidate generation | No | Medium (candidates × trials × trainset) | | `Structural` | LM-guided graph edits over `ir::Edit` (program lane only) | No | Medium (examples + iterations × minibatch) | GEPA is the only optimizer that requires textual feedback from the metric (`Eval::with_feedback`). The others use numerical scores alone. Full configuration tables for all six live in the [optimizers reference](/docs/components/optimizers). ## See also * [The edit calculus](/docs/components/edit-calculus): `Edit`, `Program::edited`, `legal_edits`, `migrate_overlay` * [Optimizers](/docs/components/optimizers): the full configuration and report tables * [Optimizer engine](/docs/components/optimizer-engine): the shared evaluation core Structural gates through * [Runtime](/docs/components/runtime): `Interpreter::load` and `RuntimeEnv`, which child loading goes through * [Program and nodes](/docs/components/program-and-nodes): `Overlay` and `Program::bake` for keeping the winner # DSRs Source: https://dsrs.herumbshandilya.com/index Typed LM programming in Rust: declare contracts instead of writing prompts, hold your pipeline as data, and improve it with optimizers DSRs - typed LM programming in Rust DSRs is a Rust library for building LM-powered applications. You declare each model interaction as a typed contract instead of writing prompts and parsers, compose interactions with ordinary Rust, and get a system where the whole pipeline is also data: printable, diffable, replayable, optimizable, and servable from a single text file. * **Typed contracts.** Inputs, outputs, and instructions are a Rust struct. The library renders the prompt and parses the response into your types; malformed model output becomes a typed error, not a string-parsing bug. * **Programs as data.** A `#[module]` function is read once at build time and projected into both a runnable function and a `.dsrs` artifact that cannot drift from the code. The artifact can be checked, diffed, served over HTTP, and embedded back into a build. * **Recorded runs.** Every run is captured as a trace. Strict replay turns recordings into free, deterministic tests; until-divergence replay re-runs only the steps a change actually affects. * **Optimizers.** COPRO, MIPROv2, GEPA, SIMBA, and BootstrapFewShot tune instructions and few-shot demos against your metric and dataset, without modifying your code. * **Tools, agents, and Code Mode.** Models call typed Rust tools in bounded loops, or write one sandboxed JavaScript program that calls all tools in a single round trip. A capability system gates every access at load time. ## Start here Install, configure a model, and make a typed call in five minutes The mental model: contracts, programs as data, and the three authoring lanes The first component: typed contracts for LM calls Every runnable example in the repository, indexed ## Components Each component has exactly one page covering what it is, how to use it, and its complete API surface. | Area | Pages | | ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Building blocks | [Signatures](/docs/components/signatures), [Predict](/docs/components/predict), [Modules](/docs/components/modules), [Adapters](/docs/components/adapters), [Language models](/docs/components/lm), [Data](/docs/components/data), [fx](/docs/components/fx) | | Programs as data | [The module macro](/docs/components/module-macro), [Holes](/docs/components/holes), [Capabilities](/docs/components/capabilities), [Program & nodes](/docs/components/program-and-nodes), [The edit calculus](/docs/components/edit-calculus), [The .dsrs format](/docs/components/dsrs-file), [Runtime](/docs/components/runtime), [CLI](/docs/components/cli), [State](/docs/components/state) | | Tools & agents | [Tools & agents](/docs/components/tools-and-agents), [Code Mode](/docs/components/code-mode) | | Evaluation & optimization | [Traces](/docs/components/traces), [Evaluation](/docs/components/evaluation), [Optimizers](/docs/components/optimizers), [COPRO](/docs/optimizers/copro), [MIPROv2](/docs/optimizers/miprov2), [GEPA](/docs/optimizers/gepa), [Optimizer engine](/docs/components/optimizer-engine) | | Utilities | [Utilities](/docs/components/utils) | DSRs is a from-scratch Rust rewrite of the ideas in [DSPy](https://github.com/stanfordnlp/dspy), designed around Rust's type system rather than ported from Python. Source and issues: [krypticmouse/DSRs](https://github.com/krypticmouse/DSRs).