
Dspy Infer Rules
- 4 installs
- 11 repo stars
- Updated June 28, 2026
- lebsral/dspy-programming-not-prompting-lms-skills
Helps with ai & agent building tasks.
About
dspy-infer-rules is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted coding.
- dspy-infer-rules
- AI & Agent Building
- AI-coding skill
Dspy Infer Rules by the numbers
- 4 all-time installs (skills.sh)
- Ranked #13,372 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-infer-rulesAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 4 |
|---|---|
| repo stars | ★ 11 |
| Last updated | June 28, 2026 |
| Repository | lebsral/dspy-programming-not-prompting-lms-skills ↗ |
What it does
Helps with ai & agent building tasks.
Files
Extracting Decision Rules with dspy.InferRules
Guide the user through using dspy.InferRules to discover explicit, human-readable rules from labeled examples and inject them into program instructions.
What is dspy.InferRules
dspy.InferRules is a DSPy optimizer that analyzes your training examples and extracts natural-language rules describing the decision patterns it finds. These rules are then appended to the instructions of each predictor in your program. The result is a compiled program whose prompts contain explicit, interpretable decision logic -- not opaque few-shot examples.
It inherits from BootstrapFewShot, so it first bootstraps demonstrations and then goes further by inducing rules from those demonstrations.
Key properties:
- Extracts human-readable rules -- the discovered logic is plain English, not weights or embeddings
- Builds on BootstrapFewShot -- bootstraps demonstrations first, then induces rules from them
- Generates multiple candidates -- creates several rule-enhanced programs and picks the best one on a validation set
- Enhances instructions -- appends discovered rules directly to each predictor's signature instructions
- Gracefully handles context limits -- iteratively removes examples if they exceed the LM's context window
When to use InferRules
Use dspy.InferRules when:
- You have labeled examples and want to understand the patterns behind them
- Interpretability matters -- you need to explain decisions to stakeholders or auditors
- Your task has consistent, describable rules (classification, routing, moderation, triage)
- You want to improve a program's instructions without manually writing rules
- You need a compiled program that works without few-shot demonstrations at inference time
Do not use InferRules when:
- You have very few examples (fewer than ~20) -- rules need enough data to generalize
- The task has no consistent patterns (creative writing, open-ended generation)
- You want to tune few-shot examples only -- use
dspy.BootstrapFewShotinstead - You want full prompt + demo optimization -- use
dspy.MIPROv2instead - You need weight tuning -- use
dspy.BootstrapFinetune
Basic usage
Three things are needed: a DSPy program, a metric function, and a training set.
import dspy
dspy.configure(lm=dspy.LM("openai/gpt-4o-mini"))
# 1. Define a program
classify = dspy.ChainOfThought("text -> label")
# 2. Define a metric
def exact_match(example, pred, trace=None):
return pred.label.strip().lower() == example.label.strip().lower()
# 3. Prepare training data
trainset = [
dspy.Example(text="Server is down again", label="urgent").with_inputs("text"),
dspy.Example(text="Update my billing info", label="normal").with_inputs("text"),
dspy.Example(text="Site is completely broken", label="urgent").with_inputs("text"),
dspy.Example(text="How do I change my password?", label="normal").with_inputs("text"),
# ... more labeled examples
]
# 4. Compile with InferRules
optimizer = dspy.InferRules(metric=exact_match, num_rules=10)
compiled = optimizer.compile(classify, trainset=trainset)
# 5. Use the compiled program -- instructions now contain discovered rules
result = compiled(text="Database connection pool exhausted")
print(result.label)After compilation, inspect the rules that were injected:
# View the enhanced instructions for each predictor
for name, predictor in compiled.named_predictors():
print(f"Predictor: {name}")
print(f"Instructions: {predictor.signature.instructions}")
print()How InferRules extracts rules
The compilation process has five stages:
1. Data splitting -- Splits trainset 50/50 into training and validation sets (unless you provide valset separately) 2. Bootstrap demonstrations -- Runs the parent BootstrapFewShot.compile() to collect successful input-output demonstrations 3. Rule induction -- For each predictor, feeds the bootstrapped demonstrations into a RulesInductionProgram that generates natural-language rules describing the patterns 4. Candidate generation -- Repeats the rule induction num_candidates times with different samples to produce diverse rule sets 5. Validation and selection -- Scores each candidate program on the validation set using your metric and returns the highest-scoring one
The induced rules look like plain English statements, for example:
"If the text mentions system failures, outages, or data loss, classify as urgent."
"If the text is a routine account or billing question, classify as normal."
These rules are appended to the predictor's existing instructions, giving the LM explicit decision logic to follow.
Constructor parameters
dspy.InferRules(
num_candidates=10, # Number of candidate programs to evaluate
num_rules=10, # Number of rules to induce per predictor
num_threads=None, # Thread count for parallel evaluation
teacher_settings=None, # Config for the teacher model
metric=..., # Evaluation metric (required, via kwargs)
max_errors=..., # Max allowed errors during evaluation (optional, via kwargs)
)| Parameter | Type | Default | Description |
|---|---|---|---|
num_candidates | int | 10 | Number of candidate rule-enhanced programs to generate. More candidates increase the chance of finding better rules but cost more LM calls |
num_rules | int | 10 | Number of rules to induce per predictor. More rules capture finer patterns but risk overfitting or exceeding context limits |
num_threads | int | None | Number of threads for parallel evaluation. None uses the default |
teacher_settings | dict | None | Configuration for the teacher model used during bootstrapping |
metric | Callable | -- | Evaluation function (example, prediction, trace) -> float. Passed via kwargs |
max_errors | int | -- | Maximum errors allowed before stopping evaluation. Passed via kwargs |
The compile method
compiled_program = optimizer.compile(
student, # Your DSPy program to optimize (required)
trainset=trainset, # Training examples (required)
valset=None, # Validation examples (optional -- auto-split if not provided)
)If valset is not provided, compile automatically splits trainset 50/50 into training and validation sets. Providing your own valset gives you more control over evaluation.
Interpretability benefits
InferRules stands apart from other optimizers because its output is human-readable:
| Optimizer | Output | Interpretable? |
|---|---|---|
BootstrapFewShot | Few-shot examples in the prompt | Somewhat -- you can read the examples |
MIPROv2 | Optimized instructions + few-shot | Partially -- instructions are readable but auto-generated |
BootstrapFinetune | Updated model weights | No -- weights are opaque |
| `InferRules` | Explicit natural-language rules | Yes -- you can read, audit, and edit the rules |
This makes InferRules a good fit for:
- Regulated industries where you must explain how decisions are made
- Debugging -- read the rules to understand what the optimizer learned
- Human-in-the-loop refinement -- edit or remove rules that are wrong before deploying
- Documentation -- the rules serve as a specification of your system's behavior
Tuning num_candidates and num_rules
`num_candidates` controls how many different rule sets are generated and compared:
| Value | Use case |
|---|---|
| 3-5 | Quick iteration, prototyping |
| 10 (default) | Good balance of quality and cost |
| 15-20 | High-stakes applications, when you need the best possible rules |
`num_rules` controls how many rules are induced per predictor:
| Value | Use case |
|---|---|
| 3-5 | Simple binary tasks (spam/not-spam) |
| 10 (default) | Multi-class tasks, moderate complexity |
| 15-20 | Tasks with many edge cases or subtle distinctions |
More rules is not always better. Too many rules can overwhelm the LM's context or introduce contradictions. Start with the defaults and adjust based on validation scores.
Providing a separate validation set
For more control, provide your own validation set:
optimizer = dspy.InferRules(metric=exact_match, num_rules=10, num_candidates=10)
compiled = optimizer.compile(
classify,
trainset=train_examples,
valset=val_examples,
)This is recommended when:
- Your dataset has a natural train/val split
- You want to ensure specific edge cases appear in validation
- You want a larger training set for rule induction (the 50/50 auto-split may leave too few training examples)
Saving and loading compiled programs
# Save the compiled program (includes the discovered rules in instructions)
compiled.save("compiled_with_rules.json")
# Load it later
from your_module import YourProgram
loaded = YourProgram()
loaded.load("compiled_with_rules.json")
# The loaded program has the same enhanced instructions
result = loaded(text="New input here")Tips
- Start with 20+ diverse examples -- rules need enough variety to capture real patterns
- Inspect the rules after compilation -- read what InferRules discovered and remove any that are wrong or unhelpful
- Use a separate validation set when you have enough data -- the auto 50/50 split may waste training examples
- Combine with ChainOfThought -- rules in the instructions plus step-by-step reasoning is a strong combination
- Compare against BootstrapFewShot -- if few-shot examples alone match InferRules' accuracy, the simpler approach may be better
- Watch for overfitting -- if validation scores are much lower than training scores, reduce
num_rules
Cross-references
- Bootstrapping few-shot examples as the foundation -- see
/ai-improving-accuracy - Full prompt optimization with MIPROv2 -- see
/ai-improving-accuracy - Evaluating your program to measure rule quality -- see
/dspy-evaluate - Data preparation for training and validation sets -- see
/dspy-data - Signatures and instructions that InferRules modifies -- see
/dspy-signatures - For worked examples, see examples.md
- Not sure which skill to use next? Try
/ai-doto get routed to the right one
dspy.InferRules Examples
Example 1: Extracting classification rules from labeled data
A support ticket classifier that uses InferRules to discover the decision logic behind priority labels. After compilation, the program's instructions contain explicit rules like "tickets mentioning outages or data loss are urgent."
import dspy
from typing import Literal
dspy.configure(lm=dspy.LM("openai/gpt-4o-mini"))
# Define a typed signature for ticket classification
class ClassifyTicket(dspy.Signature):
"""Classify a support ticket by priority level."""
ticket_text: str = dspy.InputField(desc="The support ticket text")
priority: Literal["critical", "high", "normal", "low"] = dspy.OutputField(
desc="Priority level for the ticket"
)
# Build the program
classifier = dspy.ChainOfThought(ClassifyTicket)
# Prepare labeled training data
trainset = [
# Critical -- system-wide outages, data loss
dspy.Example(
ticket_text="Production database is down. All users getting 500 errors. Revenue impact.",
priority="critical",
).with_inputs("ticket_text"),
dspy.Example(
ticket_text="Complete site outage. No pages loading for any customer.",
priority="critical",
).with_inputs("ticket_text"),
dspy.Example(
ticket_text="Data corruption detected in user accounts table. Backups may be affected.",
priority="critical",
).with_inputs("ticket_text"),
dspy.Example(
ticket_text="Payment processing system is completely unresponsive.",
priority="critical",
).with_inputs("ticket_text"),
# High -- degraded service, security issues
dspy.Example(
ticket_text="API response times spiked to 10s. Some requests timing out.",
priority="high",
).with_inputs("ticket_text"),
dspy.Example(
ticket_text="Possible unauthorized access detected on admin panel.",
priority="high",
).with_inputs("ticket_text"),
dspy.Example(
ticket_text="Image upload feature broken. Users can't attach files to tickets.",
priority="high",
).with_inputs("ticket_text"),
dspy.Example(
ticket_text="SSL certificate expiring in 2 days. Needs immediate renewal.",
priority="high",
).with_inputs("ticket_text"),
# Normal -- feature requests, non-urgent bugs
dspy.Example(
ticket_text="Would like to export reports as PDF in addition to CSV.",
priority="normal",
).with_inputs("ticket_text"),
dspy.Example(
ticket_text="Typo on the pricing page. 'Annualy' should be 'Annually'.",
priority="normal",
).with_inputs("ticket_text"),
dspy.Example(
ticket_text="Can we add dark mode to the dashboard?",
priority="normal",
).with_inputs("ticket_text"),
dspy.Example(
ticket_text="The date picker widget doesn't work well on mobile Safari.",
priority="normal",
).with_inputs("ticket_text"),
# Low -- questions, cosmetic issues
dspy.Example(
ticket_text="How do I change my notification preferences?",
priority="low",
).with_inputs("ticket_text"),
dspy.Example(
ticket_text="The font on the settings page looks slightly different from the rest.",
priority="low",
).with_inputs("ticket_text"),
dspy.Example(
ticket_text="Is there documentation for the API rate limits?",
priority="low",
).with_inputs("ticket_text"),
dspy.Example(
ticket_text="Can you update the copyright year in the footer?",
priority="low",
).with_inputs("ticket_text"),
]
# Separate validation set for better evaluation
valset = [
dspy.Example(
ticket_text="All microservices in us-east-1 are unreachable. Full region outage.",
priority="critical",
).with_inputs("ticket_text"),
dspy.Example(
ticket_text="Login is intermittently failing for about 30% of users.",
priority="high",
).with_inputs("ticket_text"),
dspy.Example(
ticket_text="Could we add two-factor authentication as an option?",
priority="normal",
).with_inputs("ticket_text"),
dspy.Example(
ticket_text="What browsers do you officially support?",
priority="low",
).with_inputs("ticket_text"),
]
# Define the metric
def priority_match(example, pred, trace=None):
return pred.priority.strip().lower() == example.priority.strip().lower()
# Compile with InferRules
optimizer = dspy.InferRules(
metric=priority_match,
num_rules=10, # extract up to 10 rules
num_candidates=5, # try 5 different rule sets
)
compiled_classifier = optimizer.compile(
classifier,
trainset=trainset,
valset=valset,
)
# Inspect the discovered rules
for name, predictor in compiled_classifier.named_predictors():
print(f"--- Predictor: {name} ---")
print(predictor.signature.instructions)
print()
# Use the compiled classifier
test_tickets = [
"Entire checkout flow is broken. Customers cannot complete purchases.",
"Would be nice to have keyboard shortcuts for common actions.",
"Memory leak in the worker process causing gradual slowdown.",
"Where can I find the changelog for the latest release?",
]
for ticket in test_tickets:
result = compiled_classifier(ticket_text=ticket)
print(f"Ticket: {ticket[:60]}...")
print(f"Priority: {result.priority}")
print(f"Reasoning: {result.reasoning}")
print()What this demonstrates:
- Typed classification --
Literaloutput field constrains predictions to valid priority levels - Separate validation set -- avoids the automatic 50/50 split, giving more training data for rule induction
- Inspecting discovered rules -- after compilation, reading the enhanced instructions shows exactly what patterns InferRules found
- Practical ticket triage -- the kind of task where explicit rules are valuable for auditing and stakeholder communication
Example 2: Rule discovery for content moderation
A content moderation pipeline that uses InferRules to extract moderation policies from labeled examples. The discovered rules become an explicit, auditable moderation policy that can be reviewed by a trust-and-safety team.
import dspy
from typing import Literal
from pydantic import BaseModel
dspy.configure(lm=dspy.LM("openai/gpt-4o-mini"))
# Structured output for moderation decisions
class ModerationDecision(BaseModel):
action: str # "allow", "flag", "remove"
policy_reason: str # which policy applies
class ModerateContent(dspy.Signature):
"""Decide whether user-generated content should be allowed, flagged for review, or removed."""
content: str = dspy.InputField(desc="User-generated content to moderate")
context: str = dspy.InputField(desc="Where the content was posted (e.g., 'product review', 'forum post', 'profile bio')")
decision: ModerationDecision = dspy.OutputField(desc="Moderation decision with action and policy reason")
class ContentModerator(dspy.Module):
def __init__(self):
self.moderate = dspy.ChainOfThought(ModerateContent)
def forward(self, content, context):
return self.moderate(content=content, context=context)
# Labeled moderation examples
trainset = [
# Allow -- normal content
dspy.Example(
content="This product works great for cleaning kitchen counters. Highly recommend!",
context="product review",
decision_action="allow",
).with_inputs("content", "context"),
dspy.Example(
content="Has anyone tried using this library with Python 3.12? I'm getting import errors.",
context="forum post",
decision_action="allow",
).with_inputs("content", "context"),
dspy.Example(
content="Software engineer based in Portland. Love hiking and open source.",
context="profile bio",
decision_action="allow",
).with_inputs("content", "context"),
dspy.Example(
content="I disagree with the previous reviewer. The battery life is actually quite poor.",
context="product review",
decision_action="allow",
).with_inputs("content", "context"),
# Flag -- borderline content needing human review
dspy.Example(
content="This is the WORST company ever. They should be SHUT DOWN. Total scam artists!!!",
context="product review",
decision_action="flag",
).with_inputs("content", "context"),
dspy.Example(
content="I can show you how to get around the paywall. DM me for details.",
context="forum post",
decision_action="flag",
).with_inputs("content", "context"),
dspy.Example(
content="Check out my amazing crypto investment opportunity! 10x guaranteed returns!",
context="forum post",
decision_action="flag",
).with_inputs("content", "context"),
dspy.Example(
content="The CEO is personally responsible for this disaster. Name and shame!",
context="forum post",
decision_action="flag",
).with_inputs("content", "context"),
# Remove -- clear policy violations
dspy.Example(
content="Buy cheap followers and likes at spamsite.example.com. Best prices!",
context="forum post",
decision_action="remove",
).with_inputs("content", "context"),
dspy.Example(
content="Here is John Smith's home address and phone number: 123 Main St...",
context="forum post",
decision_action="remove",
).with_inputs("content", "context"),
dspy.Example(
content="You are an absolute idiot and I hope terrible things happen to you.",
context="product review",
decision_action="remove",
).with_inputs("content", "context"),
dspy.Example(
content="CLICK HERE FOR FREE iPHONE >>> spamlink.example.com <<< ACT NOW!!!",
context="profile bio",
decision_action="remove",
).with_inputs("content", "context"),
]
valset = [
dspy.Example(
content="Solid product. Does exactly what the description says. 4/5 stars.",
context="product review",
decision_action="allow",
).with_inputs("content", "context"),
dspy.Example(
content="This competitor's product is way better. Don't waste your money here.",
context="product review",
decision_action="flag",
).with_inputs("content", "context"),
dspy.Example(
content="Visit my profile for adult content links and premium subscriptions.",
context="profile bio",
decision_action="remove",
).with_inputs("content", "context"),
]
# Metric: check the action field of the structured output
def moderation_match(example, pred, trace=None):
try:
predicted_action = pred.decision.action.strip().lower()
except AttributeError:
return 0.0
return predicted_action == example.decision_action.strip().lower()
# Compile with InferRules
optimizer = dspy.InferRules(
metric=moderation_match,
num_rules=15, # more rules to capture nuanced moderation policies
num_candidates=8, # more candidates for a high-stakes task
)
compiled_moderator = optimizer.compile(
ContentModerator(),
trainset=trainset,
valset=valset,
)
# Extract and display the discovered moderation policy
print("=== Discovered Moderation Policy ===\n")
for name, predictor in compiled_moderator.named_predictors():
print(predictor.signature.instructions)
print()
# Test on new content
test_cases = [
("Great tutorial! Saved me hours of debugging.", "forum post"),
("Everyone in this thread is so dumb. You all deserve to fail.", "forum post"),
("I found a way to exploit the referral system. Here's how...", "forum post"),
("Honest review: decent product, overpriced for what it is.", "product review"),
]
for content, context in test_cases:
result = compiled_moderator(content=content, context=context)
print(f"Content: {content[:60]}...")
print(f"Context: {context}")
print(f"Action: {result.decision.action}")
print(f"Reason: {result.decision.policy_reason}")
print()What this demonstrates:
- Structured moderation output -- uses a Pydantic
BaseModelto return both the action and the policy reason, making decisions auditable - Three-tier moderation (allow/flag/remove) -- InferRules discovers the boundary between each tier
- Context-aware rules -- the
contextfield lets the model learn that the same content might be handled differently in a product review vs. a profile bio - Higher `num_rules` and `num_candidates` -- content moderation is high-stakes, so investing more LM calls to find better rules is worthwhile
- Extracting an auditable policy -- after compilation, the discovered rules can be printed, reviewed by a trust-and-safety team, and edited before deployment
- Custom metric on structured output --
moderation_matchextracts theactionfield from the Pydantic model to compare against the labeled ground truth