
Dspy Assertions
- 2 installs
- 11 repo stars
- Updated June 28, 2026
- lebsral/dspy-programming-not-prompting-lms-skills
Legacy documentation for dspy.Assert and dspy.Suggest, which were removed in DSPy 3.x; provides a migration guide to dspy.Refine and dspy.BestOfN.
About
Documents the removed dspy.Assert/dspy.Suggest runtime-constraint API for maintaining existing codebases and maps each old pattern to its dspy.Refine or dspy.BestOfN equivalent. A developer uses it to migrate legacy DSPy constraint code to the current API.
- dspy.Assert and dspy.Suggest are removed in DSPy 3.x; use Refine or BestOfN
- Migration table maps hard/soft rules, retry counts, and error feedback to new equivalents
Dspy Assertions by the numbers
- 2 all-time installs (skills.sh)
- Ranked #13,958 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-assertionsAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 2 |
|---|---|
| repo stars | ★ 11 |
| Last updated | June 28, 2026 |
| Repository | lebsral/dspy-programming-not-prompting-lms-skills ↗ |
What it does
Legacy documentation for dspy.Assert and dspy.Suggest, which were removed in DSPy 3.x; provides a migration guide to dspy.Refine and dspy.BestOfN.
Files
Enforce Constraints with dspy.Assert and dspy.Suggest
REMOVED IN DSPy 3.x.dspy.Assertanddspy.Suggesthave been removed from the DSPy codebase (noassertions.py, no imports in__init__.py,retry.pycommented out, no docs page). Use `dspy.Refine` or `dspy.BestOfN` instead — see/dspy-refineand/dspy-best-of-n. This skill documents the legacy API for maintaining existing codebases only.
>
Migration guide:
| Old pattern | New equivalent |
|-------------|---------------|
|dspy.Assert(condition, msg)(hard rule, retry) |dspy.Refine(module, N=3, reward_fn=..., threshold=0.8)|
| dspy.Suggest(condition, msg) (soft rule, continue) | Lower weight in reward function (penalize but don't block) ||max_backtrack_attempts=2|N=3in Refine/BestOfN |
|DSPyAssertionErroron exhaustion |fail_countparameter in Refine/BestOfN |
| Error message as feedback | Refine auto-generates feedback from reward scores |
Guide the user through adding runtime constraints to DSPy programs. Assertions let you declare what valid output looks like — DSPy handles retrying, backtracking, and feeding error messages back to the LM automatically.
Two kinds of constraints
dspy.Assert | dspy.Suggest | |
|---|---|---|
| Severity | Hard — must pass | Soft — should pass |
| On failure | Retries with feedback, then raises error | Logs a warning, continues execution |
| Use for | Format requirements, safety checks, non-negotiable rules | Style preferences, quality nudges, nice-to-haves |
import dspy
class QA(dspy.Module):
def __init__(self):
self.answer = dspy.ChainOfThought("question -> answer")
def forward(self, question):
result = self.answer(question=question)
# Hard constraint — retries if violated
dspy.Assert(
len(result.answer) > 0,
"Answer must not be empty",
)
# Soft constraint — logs warning but continues
dspy.Suggest(
len(result.answer.split()) >= 10,
"Answer should be at least 10 words for completeness",
)
return resultdspy.Assert(condition, message)
Call dspy.Assert with a boolean condition and a message. When the condition is False, DSPy:
1. Catches the failure 2. Appends your message to the LM's context as feedback 3. Retries the LM call that produced the failing output 4. Repeats up to max_backtrack_attempts times (default: 2) 5. If all retries fail, raises DSPyAssertionError
dspy.Assert(
result.answer != "I don't know",
"You must provide a substantive answer based on the context",
)Write specific messages. The message is injected back into the prompt on retry, so "Answer was 350 words, must be under 200" is far more useful than "too long."
dspy.Suggest(condition, message)
Same signature as Assert, but non-blocking. When the condition is False:
1. The message is logged as a warning 2. Execution continues normally 3. During optimization, suggestions guide the optimizer toward better prompts
dspy.Suggest(
"however" not in result.answer.lower(),
"Avoid hedging language like 'however' — be direct",
)Use Suggest when the constraint improves quality but isn't a hard requirement.
How backtracking works
When dspy.Assert fails inside a module's forward(), DSPy doesn't just retry the same call. It modifies the signature by injecting the error message as additional context, so the LM has feedback about what went wrong:
# Original prompt (simplified)
Question: What is DSPy?
Answer: [LM generates here]
# After assertion failure, retry prompt becomes:
Question: What is DSPy?
Previous attempt failed: "Answer was 350 words, must be under 200. Be concise."
Answer: [LM generates here with feedback]This is why assertion messages should be actionable instructions, not just error descriptions.
Targeting a specific module for backtracking
By default, DSPy backtracks to the most recent LM call. Use the backtrack_module parameter to target a specific module instead:
dspy.Assert(
is_valid_json(result.output),
"Output must be valid JSON. Check for missing braces or trailing commas.",
backtrack_module=self.generate, # retry this specific module
)Common validation patterns
Length constraints
dspy.Assert(
len(result.summary.split()) <= 50,
f"Summary is {len(result.summary.split())} words, must be under 50",
)Format validation
import re
dspy.Assert(
re.match(r"^\d{4}-\d{2}-\d{2}$", result.date or ""),
"Date must be in YYYY-MM-DD format",
)Content checks
dspy.Assert(
not any(phrase in result.answer.lower() for phrase in ["as an ai", "i cannot"]),
"Do not include AI self-references in the answer",
)List output validation
dspy.Assert(
len(result.tags) >= 1,
"Must assign at least one tag",
)
dspy.Assert(
all(tag in VALID_TAGS for tag in result.tags),
f"All tags must be from the valid set: {VALID_TAGS}",
)Grounding in sources
# Check that the answer references at least one key term from the context
context_terms = set(word.lower() for p in context for word in p.split() if len(word) > 5)
answer_terms = set(word.lower() for word in result.answer.split())
overlap = context_terms & answer_terms
dspy.Assert(
len(overlap) >= 3,
"Answer must reference specific terms from the source passages",
)Using assertions with optimizers
Assertions work with all DSPy optimizers. During optimization:
- `dspy.Assert` failures cause the training example to be retried. If the program can't satisfy the constraint after retries, that example is skipped.
- `dspy.Suggest` failures are tracked as soft signals. Optimizers like
BootstrapFewShotWithRandomSearchandMIPROv2prefer demo sets where suggestions are satisfied.
This means the optimizer learns prompts and demos that satisfy your constraints on the first try, reducing retries in production:
program = QA()
optimizer = dspy.BootstrapFewShotWithRandomSearch(
metric=my_metric,
max_bootstrapped_demos=4,
num_candidate_programs=10,
)
optimized = optimizer.compile(program, trainset=trainset)After optimization, the program will have few-shot demos that naturally produce outputs satisfying your assertions.
Catching assertion errors
When all retries are exhausted, dspy.Assert raises DSPyAssertionError. Handle it at the call site:
from dspy.primitives.assertions import DSPyAssertionError
try:
result = program(question="...")
except DSPyAssertionError as e:
# Log the failure, return a fallback, etc.
print(f"Output failed validation: {e}")When to use Assert vs. Suggest
| Scenario | Use |
|---|---|
| Output must be valid JSON | Assert |
| Answer should be concise | Suggest |
| No PII in output | Assert |
| Prefer active voice | Suggest |
| Must cite sources | Assert |
| Avoid hedging language | Suggest |
| Output matches expected schema | Assert |
| Include a confidence score | Suggest |
Rule of thumb: If a bad output reaching users would be a bug, use Assert. If it would just be suboptimal, use Suggest.
Migration to dspy.Refine / dspy.BestOfN
Assert/Suggest have been removed from DSPy 3.x. All constraint enforcement should use dspy.Refine (iterative with feedback) or dspy.BestOfN (independent sampling).
The key shift is from inline boolean checks to reward functions that score the full output:
# OLD (removed in DSPy 3.x)
dspy.Assert(len(result.answer.split()) <= 50, "Too long")
dspy.Suggest("however" not in result.answer, "Avoid hedging")
# NEW — reward function + Refine
def quality_reward(args, pred):
score = 1.0
if len(pred.answer.split()) > 50: # hard rule
score -= 0.4
if "however" in pred.answer.lower(): # soft rule
score -= 0.1
return max(score, 0.0)
refined = dspy.Refine(module=my_module, N=3, reward_fn=quality_reward, threshold=0.8)For full migration patterns, see /dspy-refine and /dspy-best-of-n.
Gotchas
- Claude writes vague assertion messages like "Invalid output". The message is injected back into the LM prompt on retry — it IS the feedback. Write actionable instructions: "Summary is {len(words)} words, must be under 50. Remove examples and keep only the key conclusion." The more specific, the more likely the retry succeeds.
- Claude puts assertions outside `forward()`.
dspy.Assertanddspy.Suggestonly work inside adspy.Module.forward()method because DSPy needs the module context for backtracking. Calling them at the top level or in a standalone function silently skips the retry mechanism. - Claude uses `Assert` for style preferences. Hard assertions that fail after all retries raise
DSPyAssertionErrorand crash the program. Usedspy.Suggestfor subjective quality preferences (tone, style, verbosity) and reserveAssertfor objective constraints (format validity, safety, schema compliance). - Claude does not handle `DSPyAssertionError` at the call site. When all retry attempts are exhausted,
AssertraisesDSPyAssertionError. In production code, always wrap the program call in a try/except to handle validation failures gracefully with a fallback response. - Claude chains too many assertions, making all retries fail. Each assertion that fails triggers a retry with feedback, but stacking 5+ strict assertions means the LM must satisfy all constraints simultaneously. If the success rate per constraint is 80%, five independent constraints yield ~33% joint success. Group related checks into one assertion with a combined message, or relax secondary constraints to
Suggest.
Additional resources
- DSPy assertions guide — upstream documentation
- reference.md — Assert/Suggest signatures, parameters, backtracking behavior, deprecation notes
Cross-references
Install any skill: npx skills add lebsral/DSPy-Programming-not-prompting-LMs-skills --skill <name>- dspy.Refine (recommended replacement) — see
/dspy-refine - Problem-first framing with worked examples — see
/ai-checking-outputs - Stopping hallucinations with grounding and citations — see
/ai-stopping-hallucinations - Enforcing business rules and content policies — see
/ai-following-rules - Optimizers that learn to satisfy constraints — see
/dspy-bootstrap-rs,/dspy-miprov2 - 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
[
{
"prompt": "I have a DSPy program that extracts dates from text. Sometimes the LM returns dates in the wrong format (like 'January 5th' instead of '2024-01-05'). How can I force it to use YYYY-MM-DD format?",
"expected_output": "Code using dspy.Refine with a reward function that validates date format with regex, or Pydantic Field constraints. Mentions that dspy.Assert is removed in DSPy 3.x.",
"assertions": [
"Uses dspy.Refine with a reward function or Pydantic validation (not removed dspy.Assert)",
"Reward function or validation checks the date format with regex or parsing",
"Mentions that dspy.Assert/Suggest are removed in DSPy 3.x and recommends Refine/BestOfN",
"Uses regex or string validation to check the date format"
]
},
{
"prompt": "My DSPy summarization module sometimes produces summaries that are too long. I want it to prefer short summaries but not crash if it goes over. How do I add that as a soft constraint?",
"expected_output": "Code using dspy.Refine with a graduated reward function that penalizes length violations with partial credit. Mentions that dspy.Suggest is removed in DSPy 3.x.",
"assertions": [
"Uses dspy.Refine or dspy.BestOfN with a graduated reward function (not removed dspy.Suggest)",
"Reward function gives partial credit for near-misses (graduated float, not binary)",
"Mentions that dspy.Suggest is removed in DSPy 3.x",
"Explains that the reward function penalizes over-length without making it a hard failure"
]
},
{
"prompt": "I have multiple constraints on my DSPy program and it keeps failing. The LM cannot satisfy all 6 rules at once. What should I do?",
"expected_output": "Advice to use a graduated reward function with different weights for hard vs soft rules, and to use dspy.Refine instead of removed dspy.Assert",
"assertions": [
"Identifies the problem: too many simultaneous hard constraints reduce joint success probability",
"Recommends using a single graduated reward function with different penalty weights per constraint",
"Recommends heavy penalties for critical constraints and light penalties for preferences",
"Recommends dspy.Refine or dspy.BestOfN as the enforcement mechanism (not removed dspy.Assert/Suggest)",
"Does NOT suggest using dspy.Assert or dspy.Suggest without noting they are removed in DSPy 3.x"
]
}
]
Condensed from dspy.ai/learn/programming/7-assertions/ and dspy/primitives/assertions.py. Verify against upstream for latest.
dspy.Assert and dspy.Suggest — API Reference
REMOVED IN DSPy 3.x.dspy.Assertanddspy.Suggesthave been removed from the DSPy codebase — noassertions.pyfile, no imports in any__init__.py,retry.pyis entirely commented out, and the docs page is gone. This reference is kept for maintaining legacy codebases only. For new code, use `dspy.Refine` or `dspy.BestOfN` — see/dspy-refineand/dspy-best-of-n.
dspy.Assert
dspy.Assert(
condition, # bool (required)
message="", # str
backtrack_module=None, # dspy.Module | None
)| Parameter | Type | Default | Description |
|---|---|---|---|
condition | bool | required | The constraint to enforce. False triggers retry/backtrack. |
message | str | "" | Feedback message injected into the LM prompt on retry. Should be actionable — the LM reads this to fix its output. |
backtrack_module | `dspy.Module | None` | None |
Behavior when `condition` is `False`:
1. Raises DSPyAssertionError internally 2. DSPy catches it and injects message into the LM's context 3. Retries the target module (most recent call, or backtrack_module if specified) 4. Repeats up to max_backtrack_attempts times (default: 2) 5. If all retries fail, raises DSPyAssertionError to the caller
During optimization: Failed assertions cause the training example to be retried. If constraints cannot be satisfied after retries, the example is skipped.
dspy.Suggest
dspy.Suggest(
condition, # bool (required)
message="", # str
backtrack_module=None, # dspy.Module | None
)| Parameter | Type | Default | Description |
|---|---|---|---|
condition | bool | required | The soft constraint to check. False logs a warning. |
message | str | "" | Warning message logged when condition fails. |
backtrack_module | `dspy.Module | None` | None |
Behavior when `condition` is `False`:
1. Raises DSPySuggestionError internally 2. DSPy catches it and logs a warning 3. Execution continues normally — no retry, no error raised to caller
During optimization: Suggestion failures are tracked as soft signals. Optimizers prefer demo sets where suggestions are satisfied, but violations do not prevent examples from being used.
DSPyAssertionError
from dspy.primitives.assertions import DSPyAssertionErrorRaised when dspy.Assert exhausts all retry attempts. Catch this at the call site for graceful error handling:
try:
result = program(question="...")
except DSPyAssertionError as e:
print(f"Validation failed: {e}")DSPySuggestionError
from dspy.primitives.assertions import DSPySuggestionErrorRaised internally by dspy.Suggest when the condition fails. DSPy catches this automatically — you do not normally need to handle it.
Backtracking mechanics
- Assertions only work inside
dspy.Module.forward()— DSPy needs the module context to manage retries - On failure, DSPy modifies the signature by appending the error message as additional context
- The default
max_backtrack_attemptsis 2 (configurable viadspy.settings) - Each retry uses a fresh LM call with the feedback message included
- If
backtrack_moduleis specified, DSPy retries that specific module instead of the most recent one
Key differences summary
dspy.Assert | dspy.Suggest | |
|---|---|---|
| Severity | Hard — must pass | Soft — should pass |
| On failure | Retries with feedback, then raises error | Logs warning, continues |
| Exception type | DSPyAssertionError | DSPySuggestionError (caught internally) |
| Backtracking | Yes — retries the target module | No — execution continues |
| Optimization effect | Failed examples retried, then skipped | Soft signal for demo selection |
| Use for | Format, safety, schema constraints | Style, quality, preference nudges |