Skip to content

Repository files navigation

logo

DSRs

A high-performance DSPy rewrite in Rust for building LM-powered applications

LicenseRustCrates.ioDocumentationBuild Status

DocumentationAPI ReferenceExamplesIssuesDiscord


🚀 Overview

DSRs (DSPy Rust) is a ground-up rewrite of the DSPy framework in Rust, designed for building robust, high-performance applications powered by Language Models. Unlike a simple port, DSRs leverages Rust's type system, memory safety, and concurrency features to provide a more efficient and reliable foundation for LM applications.

📦 Installation

Add DSRs to your Cargo.toml:

[dependencies]
# Option 1: Use the shorter alias (recommended)dsrs = { package = "dspy-rs", version = "0.7.3" }
# Option 2: Use the full namedspy-rs = "0.7.3"

Or use cargo:

# Option 1: Add with alias (recommended)
cargo add dsrs --package dspy-rs
# Option 2: Add with full name
cargo add dspy-rs

🔧 Quick Start

Here's a simple example to get you started:

use anyhow::Result;use dspy_rs::{configure, init_tracing,LM,Predict,Signature};#[derive(Signature,Clone)]structSentimentAnalyzer{/// Predict the sentiment of the given text 'Positive', 'Negative', or 'Neutral'.#[input]pubtext:String,#[output]pubsentiment:String,}#[tokio::main]asyncfnmain() -> Result<()>{init_tracing()?;// API key automatically read from OPENAI_API_KEY env varconfigure(LM::builder().model("gpt-4o-mini".to_string()).temperature(0.5).build().await?,);// Create a predictorlet predictor = Predict::<SentimentAnalyzer>::new();// Prepare typed inputlet input = SentimentAnalyzerInput{text:"Acme is a great company with excellent customer service.".to_string(),};// Execute predictionlet result = predictor.call(input).await?;println!("Answer: {}", result.sentiment);Ok(())}

Result:

Answer: "Positive"

🏗️ Architecture

DSRs follows a modular architecture with clear separation of concerns:

dsrs/
├── core/ # Core abstractions (LM, Module, Signature)
├── adapter/ # LM provider adapters (OpenAI, etc.)
├── data/ # Data structures (Example, Prediction)
├── predictors/ # Built-in predictors (Predict, Chain, etc.)
├── evaluate/ # Evaluation framework and metrics
└── macros/ # Derive macros for signatures

Core Components

1. Signatures - Define Input/Output Specifications

#[derive(Signature,Clone)]structTranslationSignature{/// Translate the text accurately while preserving meaning#[input]pubtext:String,#[input]pubtarget_language:String,#[output]pubtranslation:String,}

2. Modules - Composable Pipeline Components

#[derive(Builder, facet::Facet)]#[facet(crate = facet)]pubstructCustomModule{predictor:Predict<TranslationSignature>,}implModuleforCustomModule{typeInput = TranslationSignatureInput;typeOutput = TranslationSignatureOutput;asyncfnforward(&self,input:TranslationSignatureInput) -> Result<Predicted<TranslationSignatureOutput>,PredictError>{self.predictor.call(input).await}}

3. Predictors - Pre-built LM Interaction Patterns

// Get predictionlet predict = Predict::<MySignature>::new();

4. Language Models - Configurable LM Backends

// Configure with OpenAI (API key read from OPENAI_API_KEY env var)let lm = LM::builder().model("gpt-4o-mini".to_string()).temperature(0.7).max_tokens(1000).build().await?;// For local models (e.g., vLLM, Ollama)let lm = LM::builder().base_url("http://localhost:11434".to_string()).model("llama3".to_string()).build().await?;

5. Evaluation - Evaluating your Modules

structExactMatchMetric;implTypedMetric<MySignature,MyModule>forExactMatchMetric{asyncfnevaluate(&self,example:&Example<MySignature>,prediction:&Predicted<MySignatureOutput>,) -> Result<MetricOutcome>{let expected = example.output.answer.trim().to_lowercase();let actual = prediction.answer.trim().to_lowercase();Ok(MetricOutcome::score((expected == actual)asu8asf32))}}// Evaluate your modulelet test_examples = load_test_data();let module = MyModule::new();let metric = ExactMatchMetric;// Automatically runs predictions and computes average metriclet outcomes = evaluate_trainset(&module,&test_examples,&metric).await?;let score = average_score(&outcomes);println!("Average score: {}", score);

6. Optimization - Optimize your Modules

DSRs provides two powerful optimizers:

COPRO (Collaborative Prompt Optimization)

#[derive(Builder, facet::Facet)]#[facet(crate = facet)]pubstructMyModule{predictor:Predict<MySignature>,}// Create and configure the optimizerlet optimizer = COPRO::builder().breadth(10)// Number of candidates per iteration.depth(3)// Number of refinement iterations.build();// Prepare training datalet train_examples = load_training_data();let metric = ExactMatchMetric;// Compile optimizes the module in-placeletmut module = MyModule::new();
optimizer.compile(&mut module, train_examples,&metric).await?;

MIPROv2 (Multi-prompt Instruction Proposal Optimizer v2) - Advanced optimizer using LLMs

// MIPROv2 uses a 3-stage process:// 1. Generate execution traces// 2. LLM generates candidate prompts with best practices// 3. Evaluate and select the best promptlet optimizer = MIPROv2::builder().num_candidates(10)// Number of candidate prompts to generate.num_trials(20)// Number of evaluation trials.minibatch_size(25)// Examples per evaluation.temperature(1.0)// Temperature for prompt generation.build();
optimizer.compile(&mut module, train_examples,&metric).await?;

7. Typed Data Loading - Ingest Directly Into Example<S>

DataLoader now provides typed loaders that return Vec<Example<S>> directly. Default behavior is:

  • Unknown source fields are ignored.
  • Missing signature-required fields return an error with row + field context.
use dspy_rs::{DataLoader,Signature,TypedLoadOptions};#[derive(Signature,Clone,Debug)]structQA{#[input]question:String,#[output]answer:String,}let trainset = DataLoader::load_csv::<QA>("data/train.csv",',',true,TypedLoadOptions::default(),)?;

For custom source schemas, use mapper overloads:

let trainset = DataLoader::load_csv_with::<QA,_>("data/train.csv",',',true,TypedLoadOptions::default(),
|row| {Ok(dspy_rs::Example::new(QAInput{question: row.get::<String>("prompt")?,},QAOutput{answer: row.get::<String>("completion")?,},))},)?;

Migration note:

  • Removed legacy raw signatures that required input_keys / output_keys.
  • save_json / save_csv were removed from DataLoader.
  • Use typed load_* / load_*_with APIs.

See examples/08-optimize-mipro.rs for a complete example (requires parquet feature).

Component Discovery:

#[derive(Builder, facet::Facet)]#[facet(crate = facet)]pubstructComplexPipeline{analyzer:Predict<AnalyzeSignature>,// Additional Predict leaves are also optimizer-visiblesummarizer:Predict<SummarizeSignature>,// Non-predict fields are ignored by optimizersconfig:Config,}let visible = named_parameters_ref(&pipeline)?
.into_iter().map(|(path, _)| path).collect::<Vec<_>>();println!("optimizer-visible leaves: {:?}", visible);

📚 Examples

Example 1: Multi-Step Pipeline

#[derive(Signature,Clone,Debug)]/// Analyze text for sentiment and key points.structAnalyze{#[input]text:String,#[output]sentiment:String,#[output]key_points:String,}#[derive(Signature,Clone,Debug)]/// Summarize the given key points.structSummarize{#[input]key_points:String,#[output]summary:String,}// Chain predictors with typed inputs/outputslet analyzer = Predict::<Analyze>::new();let summarizer = Predict::<Summarize>::new();let analysis = analyzer.call(AnalyzeInput{text: document.into()}).await?;let summary = summarizer.call(SummarizeInput{key_points: analysis.key_points.clone()}).await?;println!("Sentiment: {}", analysis.sentiment);println!("Summary: {}", summary.summary);

🧪 Testing

Run the test suite:

# All tests
cargo test# Specific test
cargo test test_predictors
# With output
cargo test -- --nocapture
# Run examples
cargo run --example 01-simple

🛠️ Other Features

Chain of Thought (CoT) Reasoning

use dspy_rs::ChainOfThought;// ChainOfThought wraps any signature, adding a `reasoning` fieldlet cot = ChainOfThought::<QA>::new();let result = cot.call(QAInput{question:"What is 2+2?".into(),}).await?;println!("Reasoning: {}", result.reasoning);println!("Answer: {}", result.answer);

Tracing System

DSRs includes a tracing system that captures the dataflow through modules as a Directed Acyclic Graph (DAG). Wrap any execution in trace::trace() to capture the graph, then inspect its nodes and edges.

See examples/12-tracing.rs for a complete example.

Optimizer Comparison

FeatureCOPROMIPROv2GEPA
ApproachIterative refinementLLM-guided generationEvolutionary search with textual feedback
ComplexitySimpleAdvancedAdvanced
Best ForQuick optimizationBest resultsComplex tasks with subtle failure modes
Training DataUses scoresUses traces & descriptionsUses rich textual feedback
Prompting TipsNoYes (15+ best practices)No
Program UnderstandingBasicLLM-generated descriptionsLLM-judge feedback
Few-shot ExamplesNoYes (auto-selected)No

When to use COPRO:

  • Fast iteration needed
  • Simple tasks
  • Limited compute budget

When to use MIPROv2:

  • Best possible results needed
  • Complex reasoning tasks
  • Have good training data (15+ examples recommended)

When to use GEPA:

  • Tasks where score alone doesn't explain what went wrong
  • Need an LLM judge to provide actionable feedback
  • Want Pareto-optimal exploration of the instruction space

📈 Project Status

⚠️Beta Release - DSRs is in active development. The API is stabilizing but may have breaking changes.

🤝 Contributing

We welcome contributions! Please see our Contributing Guide for details.

Development Setup

# Clone the repository
git clone https://github.com/krypticmouse/dsrs.git
cd dsrs
# Build the project
cargo build
# Run tests
cargo test# Run with examples
cargo run --example 01-simple
# Check formatting
cargo fmt -- --check
# Run clippy
cargo clippy -- -D warnings

📄 License

This project is licensed under the Apache License 2.0 - see the LICENSE file for details.

🙏 Acknowledgments

  • Inspired by the original DSPy framework
  • Built with the amazing Rust ecosystem
  • Special thanks to the DSPy community for the discussion and ideas
  • MIPROv2 implementation

🔗 Resources


Built with 🦀 by the DSPy x Rust community
Star ⭐ this repo if you find it useful!

About

Performance centered DSPy rewrite to(not port) Rust

Resources

Stars

306 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages