agentsclimarketplace

Dspy copro

Skill lebsral/DSPy-Programming-not-prompting-LMs-skills/skills/dspy-copro

AI skills for Claude Code, Cursor, and other coding agents. Build reliable AI features with DSPy — classification, RAG, parsing, agents, and more. Just type /ai-do.

Install
npx -y skills add lebsral/DSPy-Programming-not-prompting-LMs-skills --skill dspy-copro

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

2 things to look at

  • no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
  • 11 stars11 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

Use when you want to optimize instructions by generating many candidates and picking the best — useful when few-shot demos alone are not enough and you want to tune the task description itself. Common scenarios - your current task instructions produce mediocre results, you want to automatically generate and test many instruction variants, the task is hard to describe in one sentence, or few-shot examples alone are not improving quality enough. Related - ai-improving-accuracy, dspy-gepa, dspy-miprov2. Also used for dspy.COPRO, instruction optimization, optimize task description, generate better prompts automatically, prompt engineering automation, find the best instruction for my task, automatic prompt generation, instruction tuning without fine-tuning, COPRO vs MIPROv2, when to optimize instructions vs demos, instruction search, prompt optimization by generating candidates, systematic prompt improvement.

SKILL.md

13.6 KB, as published. Nobody here has run it

Instruction Optimization with dspy.COPRO

Guide the user through using dspy.COPRO to automatically generate, evaluate, and select the best instructions for their DSPy program's signatures.

Step 1 — Gather context

Before writing code, ask:

  1. What does your program do? Which module does it use — Predict, ChainOfThought, a custom dspy.Module? What are the input/output fields?
  2. How many labeled training examples do you have? COPRO needs 20-200. Below 20 the search is unreliable; above 200, consider MIPROv2 for joint instruction + demo optimization.
  3. Do you have an evaluation metric already, or do you need help writing one? COPRO requires a metric — exact match, semantic similarity, LM-as-judge, etc.
  4. Do you want instructions only, or also few-shot demos? If demos would help, use dspy.MIPROv2 instead — it jointly optimizes both and typically outperforms COPRO.

What is COPRO

dspy.COPRO (Collaborative Prompting) is a DSPy optimizer that improves your program by finding better instructions for each signature. Instead of you hand-writing prompt instructions, COPRO generates many candidate instructions, evaluates each one against your metric, and keeps the best.

Key properties:

  • Generates instruction candidates -- uses an LM to propose alternative instructions for each predictor in your program
  • Evaluates each candidate -- scores every candidate against your metric on the training set
  • Iterates in depth -- runs multiple rounds, using top performers from round N to inform candidates in round N+1
  • Tunes instructions and prefixes -- optimizes both the signature docstring (instruction) and output field prefixes
  • Works with any program -- optimizes all predictors in a program sequentially

When to use COPRO

Use dspy.COPRO when:

  • You want to systematically search for better instructions rather than hand-tuning prompts
  • You have a metric and 20-200 training examples
  • Your program has one or a few predictors that need better instructions
  • You want to explore a wide range of instruction phrasings (use high breadth)

Do not use COPRO when:

  • You also want to optimize few-shot examples -- use dspy.MIPROv2 instead (it tunes both instructions and demos)
  • You have very few examples (<20) and want a lightweight optimizer -- use dspy.GEPA instead
  • You want to fine-tune model weights -- use dspy.BootstrapFinetune
  • You just need few-shot examples without instruction changes -- use dspy.BootstrapFewShot

Basic usage

Three things are needed: a program, a metric, and training data.

import dspy

dspy.configure(lm=dspy.LM("openai/gpt-4o-mini"))  # or "anthropic/claude-sonnet-4-5-20250929", etc.

# 1. Define a program
classify = dspy.ChainOfThought("text -> label")

# 2. Define a metric
def metric(example, prediction, trace=None):
    return prediction.label.lower() == example.label.lower()

# 3. Prepare training data
trainset = [
    dspy.Example(text="Love this product!", label="positive").with_inputs("text"),
    dspy.Example(text="Terrible experience.", label="negative").with_inputs("text"),
    # ... 20-200 examples
]

# 4. Optimize with COPRO
optimizer = dspy.COPRO(
    metric=metric,
    breadth=10,
    depth=3,
)
optimized = optimizer.compile(
    classify,
    trainset=trainset,
    eval_kwargs=dict(num_threads=4, display_progress=True),
)

# 5. Use the optimized program
result = optimized(text="The quality exceeded my expectations.")
print(result.label)

Constructor parameters

dspy.COPRO(
    prompt_model=None,      # LM for generating candidates (defaults to configured LM)
    metric=None,            # Evaluation function (required)
    breadth=10,             # Number of candidates per iteration (must be >1)
    depth=3,                # Number of optimization iterations
    init_temperature=1.4,   # Temperature for candidate generation
    track_stats=False,      # Collect optimization statistics
)
ParameterTypeDefaultDescription
prompt_modeldspy.LMNoneLM used to generate instruction candidates. If None, uses the globally configured LM
metricCallableNoneScoring function with signature (example, prediction, trace=None) -> float/bool. Required
breadthint10Number of candidate instructions generated per iteration. Higher = wider search, more LM calls
depthint3Number of optimization rounds. Each round refines candidates from the previous round
init_temperaturefloat1.4Temperature for generating candidates. Higher = more diverse candidates
track_statsboolFalseWhen True, collects per-iteration statistics (max, average, min, std dev of scores)

Compile method

optimized = optimizer.compile(
    student,           # Program to optimize (modified in-place)
    trainset=trainset, # Training examples
    eval_kwargs={},    # Extra kwargs for dspy.Evaluate
)
ParameterTypeDescription
studentdspy.ModuleThe program to optimize. COPRO modifies it in-place and also returns it
trainsetlist[dspy.Example]Training examples for evaluating candidates
eval_kwargsdictPassed to dspy.Evaluate -- commonly num_threads, display_progress, display_table

The returned program has additional metadata:

  • optimized.candidate_programs -- dict of all evaluated candidates with their scores
  • optimized.total_calls -- total LM API calls made during optimization
  • optimized.results_best / optimized.results_latest -- per-iteration best/latest scores (only present when track_stats=True)

The breadth parameter

breadth controls how many instruction candidates COPRO generates per iteration. It is the most important tuning knob.

BreadthCandidates per roundTotal candidates (depth=3)Use case
54 new + 1 base~15Quick test, cheap
10 (default)9 new + 1 base~30Good balance
2019 new + 1 base~60Thorough search
5049 new + 1 base~150Exhaustive, expensive

The first iteration generates breadth - 1 new candidates from the base instruction. Subsequent iterations generate new candidates informed by the best performers so far.

Cost note: Each candidate is evaluated on the full trainset, so total LM calls scale as breadth * depth * len(trainset). With breadth=10, depth=3, and 100 training examples, expect roughly 3,000 evaluation calls plus candidate generation calls.

How COPRO generates candidates

COPRO follows a seeding-and-refinement loop:

  1. Seed phase (iteration 0): Takes the existing instruction from each signature. Generates breadth - 1 alternative instructions using temperature-controlled sampling from the prompt model.

  2. Evaluate phase: Scores every candidate instruction by swapping it into the program and running the metric against the full training set. Duplicate (instruction, prefix) pairs are skipped.

  3. Refine phase (iterations 1 through depth-1): Takes the top-performing candidates from the previous round. Generates new candidates informed by what worked and what did not.

  4. Multi-predictor handling: When a program has multiple predictors, COPRO optimizes them sequentially. It locks in the best instruction for predictor 1 before moving to predictor 2, so later predictors benefit from earlier improvements.

  5. Selection: After all iterations, the instruction with the highest metric score is selected for each predictor.

Tracking optimization statistics

Enable track_stats=True to see how candidates perform across iterations:

optimizer = dspy.COPRO(
    metric=metric,
    breadth=15,
    depth=3,
    track_stats=True,
)
optimized = optimizer.compile(
    my_program,
    trainset=trainset,
    eval_kwargs=dict(num_threads=4),
)

When track_stats is enabled, COPRO logs per-iteration statistics including max, average, min, and standard deviation of candidate scores. This helps you understand whether the search is converging or whether more breadth/depth would help.

Using a separate prompt model

You can use a stronger (or cheaper) LM specifically for generating instruction candidates:

# Use a strong model to generate candidates, evaluate with the production model
candidate_lm = dspy.LM("openai/gpt-4o")  # or "anthropic/claude-sonnet-4-5-20250929", etc.
production_lm = dspy.LM("openai/gpt-4o-mini")  # or "anthropic/claude-haiku-4-5-20251001", etc.

dspy.configure(lm=production_lm)

optimizer = dspy.COPRO(
    prompt_model=candidate_lm,
    metric=metric,
    breadth=10,
    depth=3,
)
optimized = optimizer.compile(my_program, trainset=trainset, eval_kwargs={})

This is useful when you want a capable model to brainstorm instructions but evaluate and run with a cheaper model.

Comparison with GEPA and MIPROv2

AspectCOPROGEPAMIPROv2
What it tunesInstructions + prefixesInstructionsInstructions + few-shot demos
Search strategyBreadth-first candidate generationEvolutionary (genetic programming)Bayesian optimization
Data needed20-200 examples20-100 examples50-500 examples
Key parameterbreadth (candidates per round)Population/generationsauto ("light"/"medium"/"heavy")
CostModerate (breadth * depth * trainset evals)Low-moderateModerate-high
Best forExploring many instruction variantsFew examples, feedback-driven instruction tuningBest overall prompt optimization

When to pick COPRO over alternatives:

  • You want explicit control over the search process (breadth, depth, temperature)
  • You want to inspect all candidate instructions and their scores
  • You care about instructions specifically, not few-shot examples
  • You want a middle ground between GEPA (feedback-driven, competitive with MIPROv2) and MIPROv2 (heavyweight)

When to pick MIPROv2 instead:

  • You want the best overall results (MIPROv2 optimizes both instructions and demos)
  • You prefer an auto setting over manual tuning of search parameters
  • You have enough data (200+) for MIPROv2 to shine

When to pick GEPA instead:

  • You have very few examples (<50)
  • You want evolutionary search rather than breadth-first generation
  • You want something lightweight and fast

Gotchas

  • Claude sets breadth=1 which silently breaks optimization. breadth must be greater than 1 — with breadth=1 there are no alternative candidates to evaluate. Use at least breadth=5 for a meaningful search.
  • Claude forgets that compile() modifies the student in-place. Unlike most optimizers, COPRO mutates the program you pass to compile(). If you need the original program for baseline comparison, clone it first or create a fresh instance before calling compile().
  • Claude passes eval_kwargs as positional instead of keyword. The compile() signature is compile(student, *, trainset, eval_kwargs)trainset and eval_kwargs are keyword-only. Always use optimizer.compile(program, trainset=trainset, eval_kwargs={}).
  • Claude uses COPRO when MIPROv2 would be better. COPRO only optimizes instructions. If the task also benefits from few-shot demonstrations (most tasks do), MIPROv2 optimizes both and typically outperforms COPRO. Use COPRO when you specifically want instruction-only optimization or need to inspect all candidate instructions.
  • Claude skips the eval_kwargs parameter. COPRO requires eval_kwargs to be passed to compile(), even if empty. Omitting it causes a TypeError. Always include eval_kwargs={} or eval_kwargs=dict(num_threads=4).

Additional resources

Cross-references

Install any skill: npx skills add lebsral/DSPy-Programming-not-prompting-LMs-skills --skill <name>

  • Improving accuracy end-to-end (metrics, evaluation, optimizer selection) -- see /ai-improving-accuracy
  • MIPROv2 for combined instruction + demo optimization -- see /dspy-miprov2
  • GEPA for lightweight instruction tuning -- see /dspy-gepa
  • Writing evaluation metrics -- see /dspy-evaluate
  • Preparing training data -- see /dspy-data
  • Signatures and instructions -- see /dspy-signatures
  • Install /ai-do if you do not have it — it routes any AI problem to the right skill and is the fastest way to work: npx skills add lebsral/DSPy-Programming-not-prompting-LMs-skills --skill ai-do

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.