Now liveThe Skillselion MCP - thousands of ranked skills, loaded into your agent mid-task. No install.Get it →
lebsral avatar

Dspy Refine

  • 3 installs
  • 11 repo stars
  • Updated June 28, 2026
  • lebsral/dspy-programming-not-prompting-lms-skills

Helps with ai & agent building tasks.

About

dspy-refine is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted coding.

  • dspy-refine
  • AI & Agent Building
  • AI-coding skill

Dspy Refine by the numbers

  • 3 all-time installs (skills.sh)
  • Ranked #13,657 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
  • Data as of Aug 2, 2026 (Skillselion catalog sync)
npx skills add https://github.com/lebsral/dspy-programming-not-prompting-lms-skills --skill dspy-refine

Add your badge

Show developers this skill is listed on Skillselion. Paste this into your README.

Listed on Skillselion
Installs3
repo stars11
Last updatedJune 28, 2026
Repositorylebsral/dspy-programming-not-prompting-lms-skills

What it does

Helps with ai & agent building tasks.

Files

SKILL.mdMarkdownGitHub ↗

Iterative Self-Improvement with dspy.Refine

Guide the user through using dspy.Refine to build pipelines that automatically retry and improve outputs until they meet a quality threshold.

What is dspy.Refine

dspy.Refine is a DSPy module wrapper that runs another module up to N times, scoring each attempt with a reward function. It returns the first output that meets a threshold -- or the best output if none do. When an attempt fails to meet the threshold, Refine generates feedback that gets fed into the next attempt, enabling genuine iterative improvement rather than just random retries.

Key properties:

  • Wraps any DSPy module -- ChainOfThought, Predict, ReAct, or your custom modules
  • Scores each attempt with a reward function you define
  • Generates feedback when an attempt falls short, improving subsequent tries
  • Returns early as soon as an output meets the threshold (saves LM calls)
  • Falls back gracefully -- returns the best attempt even if none hit the threshold

When to use Refine

Use dspy.Refine when:

  • Outputs must meet measurable quality criteria (format, length, accuracy)
  • You can write a function that scores output quality as a number
  • You want the LM to learn from its mistakes within a single request
  • Quality is worth the extra LM calls (2-5x cost for N attempts)

Do not use Refine when:

  • You have no clear way to score outputs -- use dspy.ChainOfThought instead
  • You need human-in-the-loop feedback -- build a custom module with dspy.Suggest
  • Speed matters more than quality -- use a single dspy.Predict call
  • You just want multiple independent attempts without feedback -- use dspy.BestOfN (see comparison below)

Basic usage

Three things are needed: a module to wrap, a reward function, and a threshold.

import dspy

dspy.configure(lm=dspy.LM("openai/gpt-4o-mini"))

# 1. Define the module to refine
qa = dspy.ChainOfThought("question -> answer")

# 2. Define a reward function
# Takes (args_dict, prediction) -> float
def concise_answer(args, pred):
    """Reward one-word answers."""
    return 1.0 if len(pred.answer.split()) == 1 else 0.0

# 3. Wrap with Refine
refined_qa = dspy.Refine(
    module=qa,
    N=3,
    reward_fn=concise_answer,
    threshold=1.0,
)

# Use it -- same interface as the wrapped module
result = refined_qa(question="What is the capital of Belgium?")
print(result.answer)  # "Brussels"

Constructor parameters

dspy.Refine(
    module,       # The DSPy module to refine (required)
    N,            # Max number of attempts (required, int)
    reward_fn,    # Callable(args_dict, prediction) -> float (required)
    threshold,    # Target reward score to accept an output (required, float)
    fail_count,   # Max failures before raising an error (optional, defaults to N)
)
ParameterTypeDescription
moduledspy.ModuleThe module whose outputs you want to refine
NintMaximum number of attempts. Each attempt uses temperature=1.0 with a different rollout ID
reward_fnCallableScores a prediction. Receives (args, pred) where args is the input kwargs dict and pred is the module's output. Must return a float
thresholdfloatTarget score. Refine returns immediately when an attempt meets or exceeds this value
fail_countintOptional. Maximum allowed failures before raising an error. Defaults to N

Writing reward functions

The reward function is the core of Refine. It receives two arguments:

1. `args` -- a dict of the inputs passed to the module (e.g., {"question": "What is..."}) 2. `pred` -- the module's prediction object (access fields like pred.answer, pred.reasoning)

It must return a float. Higher is better.

Simple binary reward

def valid_json(args, pred):
    """Accept only valid JSON outputs."""
    import json
    try:
        json.loads(pred.output)
        return 1.0
    except (json.JSONDecodeError, TypeError):
        return 0.0

Graduated reward

Return partial scores to help Refine pick the best attempt even when none fully succeed:

def quality_score(args, pred):
    """Score answer quality on multiple criteria."""
    score = 0.0
    answer = pred.answer

    # Criterion 1: not empty
    if answer.strip():
        score += 0.3

    # Criterion 2: reasonable length (20-200 words)
    word_count = len(answer.split())
    if 20 <= word_count <= 200:
        score += 0.4

    # Criterion 3: addresses the question
    if args["question"].split()[0].lower() in answer.lower():
        score += 0.3

    return score

Using external validation

import re

def valid_email_extraction(args, pred):
    """Reward valid email addresses extracted from text."""
    emails = pred.emails if isinstance(pred.emails, list) else []
    if not emails:
        return 0.0
    email_pattern = r'^[\w.-]+@[\w.-]+\.\w+$'
    valid_count = sum(1 for e in emails if re.match(email_pattern, e))
    return valid_count / len(emails)

How iteration count (N) works

Each attempt runs the wrapped module at temperature=1.0 with a different rollout ID, producing diverse outputs. Refine's selection logic:

1. Run the module and score the output with reward_fn 2. If the score meets or exceeds threshold, return immediately 3. If not, generate feedback from the failure and try again 4. After N attempts, return the attempt with the highest reward score

Choosing N:

N valueUse caseCost
2-3Format validation, simple constraintsLow overhead
3-5Quality criteria, multi-factor scoringModerate
5-10High-stakes outputs, strict requirementsHigher cost, better results

The sweet spot for most use cases is N=3 to N=5. Beyond 5, diminishing returns are common unless the reward function is very specific.

The feedback mechanism

What makes Refine different from random retries is feedback generation. When an attempt fails to meet the threshold:

1. Refine examines why the attempt scored below the threshold 2. It generates natural-language feedback describing the shortcoming 3. This feedback is included in the prompt for the next attempt 4. The LM uses this feedback to produce a better output

This means later attempts are informed by earlier failures. Attempt 3 knows what went wrong in attempts 1 and 2.

You do not write the feedback logic -- Refine handles it automatically based on your reward function's scores.

Refine vs BestOfN -- when to use which

Both modules run a wrapped module multiple times and select the best output, but they work differently:

Aspectdspy.Refinedspy.BestOfN
FeedbackGenerates feedback from failures, improving subsequent attemptsNo feedback -- each attempt is independent
AttemptsSequential (each informed by previous)Can be parallel (independent)
Early stoppingReturns on first success meeting thresholdRuns all N, picks best
Best forIterative improvement, complex quality criteriaSampling diversity, simple pass/fail
Cost patternOften fewer LM calls (stops early)Always N calls

Use Refine when the LM can improve with feedback -- writing tasks, format compliance, multi-criteria quality.

Use BestOfN when attempts are independent and feedback would not help -- creative generation, sampling diverse options, simple binary checks.

Wrapping custom modules

Refine works with any dspy.Module, not just built-in ones:

class Summarizer(dspy.Module):
    def __init__(self):
        self.summarize = dspy.ChainOfThought("article -> summary")

    def forward(self, article):
        return self.summarize(article=article)


def good_summary(args, pred):
    """Score summary quality."""
    summary = pred.summary
    article = args["article"]
    score = 0.0

    # Shorter than original
    if len(summary) < len(article) * 0.3:
        score += 0.5

    # At least 2 sentences
    if summary.count('.') >= 2:
        score += 0.5

    return score


refined_summarizer = dspy.Refine(
    module=Summarizer(),
    N=3,
    reward_fn=good_summary,
    threshold=0.8,
)

result = refined_summarizer(article="Long article text here...")
print(result.summary)

Tips

  • Start with N=3 and increase only if outputs consistently miss the threshold
  • Use graduated rewards (0.0 to 1.0) rather than binary (0 or 1) so Refine can pick the best near-miss
  • Keep reward functions fast -- they run on every attempt, so avoid expensive operations like LM calls inside them
  • Set threshold realistically -- if your reward function rarely returns 1.0, set the threshold to 0.8 or similar
  • Use `fail_count` to limit retries on genuinely impossible inputs rather than burning through all N attempts

Cross-references

  • Chain of thought reasoning as the inner module -- see /dspy-chain-of-thought
  • Checking and validating outputs with assertions -- see /ai-checking-outputs
  • Improving accuracy with optimization -- see /ai-improving-accuracy
  • For worked examples, see examples.md

Related skills

This week in AI coding

Five minutes, every Monday - the tools, releases and tactics for developers.

unsubscribe anytime.