Skip to main content
COPRO (Collaborative Prompt Optimizer) is a simple but effective optimizer that iteratively refines prompts through generation and evaluation cycles.

How it Works

COPRO uses a straightforward approach:
  1. Generate candidates: Create multiple prompt variations using an LLM
  2. Evaluate: Test each candidate on your training data
  3. Refine: Use the best candidates to generate improved versions
  4. Repeat: Continue for a fixed number of depth iterations

Configuration

let copro = COPRO::builder()
    .breadth(10)              // Number of candidates per iteration
    .depth(3)                 // Number of refinement iterations
    .init_temperature(1.4)    // Temperature for generation
    .track_stats(false)       // Track optimization statistics
    .build();

Usage Example

use anyhow::Result;
use bon::Builder;
use facet;
use dspy_rs::{
    COPRO, ChatAdapter, Example, LM, MetricOutcome, Module, Optimizer, Predict, PredictError,
    Predicted, Signature, TypedMetric, configure, init_tracing,
};

#[derive(Signature, Clone, Debug)]
struct QA {
    #[input]
    question: String,

    #[output]
    answer: String,
}

#[derive(Builder, facet::Facet)]
#[facet(crate = facet)]
struct MyModule {
    #[builder(default = Predict::<QA>::new())]
    predictor: Predict<QA>,
}

impl Module for MyModule {
    type Input = QAInput;
    type Output = QAOutput;

    async fn forward(&self, inputs: QAInput) -> Result<Predicted<QAOutput>, PredictError> {
        self.predictor.call(inputs).await
    }
}

struct ExactMatchMetric;

impl TypedMetric<QA, MyModule> for ExactMatchMetric {
    async fn evaluate(&self, example: &Example<QA>, prediction: &Predicted<QAOutput>) -> Result<MetricOutcome> {
        let expected = example.output.answer.trim().to_lowercase();
        let actual = prediction.answer.trim().to_lowercase();
        Ok(MetricOutcome::score((expected == actual) as u8 as f32))
    }
}

#[tokio::main]
async fn main() -> Result<()> {
    init_tracing()?;

    // API key automatically read from OPENAI_API_KEY env var
    configure(
        LM::builder()
            .model("gpt-4o-mini".to_string())
            .build()
            .await?,
        ChatAdapter,
    );
    
    let mut module = MyModule::builder().build();
    let trainset = vec![
        Example::new(
            QAInput {
                question: "What is 2+2?".to_string(),
            },
            QAOutput {
                answer: "4".to_string(),
            },
        ),
        Example::new(
            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::<QA, _, _>(&mut module, trainset, &metric).await?;
    
    Ok(())
}

Typed Data Loading

Use the shared data ingress guide: DataLoader.

When to Use COPRO

Best for:
  • Quick iteration cycles
  • Simple tasks
  • Limited compute budget
  • When you need results fast
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

FeatureCOPROMIPROv2GEPA
SpeedFastSlowMedium
QualityGoodBetterBest
FeedbackScoreScoreScore + Text
DiversityLowMediumHigh
SetupSimpleModerateModerate

Configuration Details

Breadth

Number of candidate prompts generated at each iteration. Higher breadth means more exploration but more compute. Recommended: 5-15

Depth

Number of refinement iterations. Each iteration builds on the best candidates from the previous one. Recommended: 2-5

Temperature

Controls randomness in prompt generation. Higher temperature means more diverse candidates. Recommended: 1.0-1.5

Track Stats

When enabled, COPRO tracks detailed statistics about all evaluated candidates and their scores over time.

Implementation Notes

COPRO maintains:
  • All evaluated candidates with their scores
  • Best candidates from each iteration
  • History of improvements over iterations
The algorithm avoids re-evaluating candidates it has already seen by caching results.

Examples

COPRO Examples

See examples 03-evaluate-hotpotqa.rs and 04-optimize-hotpotqa.rs