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,ChatAdapter,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?,ChatAdapter,);// 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)]pubstructCustomModule{predictor:Predict<TranslationSignature>,}implModuleforCustomModule{asyncfnforward(&self,inputs:Example) -> Result<Prediction>{// Your custom logic hereself.predictor.forward(inputs).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

implEvaluatorforMyModule{asyncfnmetric(&self,example:&Example,prediction:&Prediction) -> f32{// Define your custom metric logiclet expected = example.get("answer",None);let predicted = prediction.get("answer",None);// Example: Exact match metricif expected.to_lowercase() == predicted.to_lowercase(){1.0}else{0.0}}}// Evaluate your modulelet test_examples = load_test_data();let module = MyModule::new();// Automatically runs predictions and computes average metriclet score = module.evaluate(test_examples).await;println!("Average score: {}", score);

6. Optimization - Optimize your Modules

DSRs provides two powerful optimizers:

COPRO (Collaborative Prompt Optimization)

#[derive(Optimizable)]pubstructMyModule{#[parameter]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();// Compile optimizes the module in-placeletmut module = MyModule::new();
optimizer.compile(&mut module, train_examples).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).await?;

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

Component Freezing:

// The Optimizable derive macro automatically implements the trait and marks Module Optimizable#[derive(Builder,Optimizable)]pubstructComplexPipeline{#[parameter]// Mark optimizable componentsanalyzer:Predict<AnalyzeSignature>,// Non-parameter fields won't be optimizedsummarizer:Predict<SummarizeSignature>,// Non-parameter fields won't be optimizedconfig:Config,}

📚 Examples

Example 1: Multi-Step Reasoning Pipeline

use dsrs::prelude::*;#[Signature]structAnalyzeSignature{#[input]pubtext:String,#[output]pubsentiment:String,#[output]pubkey_points:String,}#[Signature]structSummarizeSignature{#[input]pubkey_points:String,#[output]pubsummary:String,}#[derive(Builder)]pubstructAnalysisPipeline{analyzer:Predict,summarizer:Predict,}implModuleforAnalysisPipeline{asyncfnforward(&self,inputs:Example) -> Result<Prediction>{// Step 1: Analyze the textlet analysis = self.analyzer.forward(inputs).await?;// Step 2: Summarize key pointslet summary_input = example!{"key_points":"input" => analysis.get("key_points",None),};let summary = self.summarizer.forward(summary_input).await?;// Combine resultsOk(prediction!{"sentiment" => analysis.get("sentiment",None),"key_points" => analysis.get("key_points",None),"summary" => summary.get("summary",None),})}}

🧪 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

#[Signature(cot)]// Enable CoT with attributestructComplexReasoningSignature{#[input(desc="Question")pubproblem:String,#[output]pubsolution:String,}

Tracing System

The tracing system allows you to capture the dataflow through modules and build a Directed Acyclic Graph (DAG) representation of the execution flow.

Overview

The tracing system consists of:

  1. Graph: A DAG structure representing nodes (modules/predictors) and edges (data dependencies)
  2. Trace Context: Captures execution traces and builds the DAG using tokio::task_local
  3. Executor: Executes captured graphs with new inputs

Basic Usage

Use trace::trace() to wrap your execution and capture the DAG:

use dspy_rs::{trace, example,Predict,Signature};#[Signature]structQASignature{#[input]pubquestion:String,#[output]pubanswer:String,}let predictor = Predict::new(QASignature::new());let example = example!{"question":"input" => "Hello",};// Trace the executionlet(result, graph) = trace::trace(|| async{
predictor.forward(example).await}).await;// Inspect the graphprintln!("Graph Nodes: {}", graph.nodes.len());for node in&graph.nodes{println!("Node {}: Type={:?}, Inputs={:?}", node.id, node.node_type, node.inputs);}// Execute the graph with new inputlet executor = trace::Executor::new(graph);let new_input = example!{"question":"input" => "What is the capital of France?",};let predictions = executor.execute(new_input).await?;

Tracked Values

When building pipelines, use get_tracked() to preserve data lineage:

let prediction = predictor.forward(inputs).await?;let answer = prediction.get_tracked("answer");// Preserves source node info// The example! macro automatically detects tracked values and records Map nodeslet next_input = example!{"answer":"input" => answer.clone(),};

Graph Structure

Node: Represents a single execution step:

  • id: Unique identifier
  • node_type: Type of node (Root, Predict, Map, Operator)
  • inputs: IDs of parent nodes
  • output: Output Prediction
  • input_data: Input Example (for root nodes)

Graph: Contains all nodes and provides execution capabilities:

  • nodes: Vector of all nodes
  • Executor: Can execute the graph with new inputs

Modifying the Graph

The graph is fully modifiable - you can:

  • Split nodes (add intermediate steps)
  • Remove nodes
  • Fuse nodes (combine operations)
  • Insert nodes between existing ones
  • Modify node configurations (signatures, instructions)
// Example: Modify a node's signatureifletSome(node) = graph.nodes.get_mut(1){ifletNodeType::Predict{ signature, .. } = &mut node.node_type{// Modify signature instruction, demos, etc.}}

Example

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

  • Tracing module execution
  • Inspecting the DAG
  • Executing graphs with new inputs
  • Modifying graph structure

Optimizer Comparison

FeatureCOPROMIPROv2
ApproachIterative refinementLLM-guided generation
ComplexitySimpleAdvanced
Best ForQuick optimizationBest results
Training DataUses scoresUses traces & descriptions
Prompting TipsNoYes (15+ best practices)
Program UnderstandingBasicLLM-generated descriptions
Few-shot ExamplesNoYes (auto-selected)

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)

📈 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

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages