
Ai Tracking Experiments
- 18 installs
- 11 repo stars
- Updated June 28, 2026
- lebsral/dspy-programming-not-prompting-lms-skills
Helps with ai & agent building tasks.
About
ai-tracking-experiments is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted coding.
- ai-tracking-experiments
- AI & Agent Building
- AI-coding skill
Ai Tracking Experiments by the numbers
- 18 all-time installs (skills.sh)
- Ranked #10,736 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 ai-tracking-experimentsAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 18 |
|---|---|
| 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
Track Which Optimization Experiment Was Best
Guide the user through logging, comparing, and managing optimization experiments. The pattern: run experiments systematically, log everything, compare results, promote the winner to production.
When you do NOT need this
- You have run only 1-2 experiments — just compare outputs directly, no tracking infrastructure needed
- You are still iterating on the program itself — stabilize your module and metric first, then track experiments
- You just want to optimize once and deploy — use
/ai-improving-accuracyinstead
When you need this
- You've run 5+ optimization experiments and lost track of which was best
- "The intern ran experiments, which .json file is the good one?"
- You need to justify to stakeholders why you picked a specific approach
- You want to reproduce last week's best experiment with more data
- You're comparing optimizers, models, or hyperparameters
How it's different from improving accuracy
Improving accuracy (/ai-improving-accuracy) | Tracking experiments (this skill) | |
|---|---|---|
| Focus | Running a single optimization pass | Managing the full experimental lifecycle |
| Output | An optimized program | A comparison of all runs with the winner promoted |
| Question | "How do I make this better?" | "Which of our 8 optimization runs was best?" |
Step 1: Understand the setup
Ask the user: 1. How many experiments have you run? (2-3 → file-based tracking. 10+ → consider W&B Weave or LangWatch) 2. What varied between runs? (optimizer, model, training data, hyperparameters?) 3. Do you have an existing tracking tool? (W&B, MLflow, etc.) 4. Do multiple people run experiments? (solo → file-based. Team → shared tool)
Step 2: Lightweight experiment tracking (no extra tools)
A JSONL file is all you need to start. Each line records one experiment run:
import json
from datetime import datetime
EXPERIMENT_LOG = "experiments.jsonl"
def log_experiment(run):
"""Log a single experiment run."""
run["timestamp"] = datetime.now().isoformat()
with open(EXPERIMENT_LOG, "a") as f:
f.write(json.dumps(run) + "\n")
def load_experiments(path=EXPERIMENT_LOG):
"""Load all experiment runs."""
with open(path) as f:
return [json.loads(line) for line in f]What to log for each run
run = {
"name": "mipro-medium-gpt4o-mini", # Human-readable name
"optimizer": "MIPROv2", # Which optimizer
"optimizer_config": {"auto": "medium"}, # Optimizer settings
"model": "openai/gpt-4o-mini", # or "anthropic/claude-sonnet-4-5-20250929", etc.
"trainset_size": 200, # Training examples used
"devset_size": 50, # Evaluation examples
"metric": "answer_quality", # Which metric
"score": 0.84, # Score on devset
"baseline_score": 0.65, # Score before optimization
"improvement": 0.19, # Delta
"cost_usd": 4.50, # API cost for this run
"duration_minutes": 12, # Wall clock time
"artifact_path": "artifacts/mipro_medium_gpt4o_mini.json", # Saved program
"notes": "Best so far. Instruction quality seems high.",
}
log_experiment(run)Step 3: Run and log experiments systematically
Template function that runs one experiment end-to-end:
import dspy
import time
from dspy.evaluate import Evaluate
def run_experiment(
name,
program_class,
optimizer_class,
optimizer_kwargs,
trainset,
devset,
metric,
model="openai/gpt-4o-mini", # or "anthropic/claude-sonnet-4-5-20250929", etc.
artifact_dir="artifacts",
):
"""Run one optimization experiment and log results."""
import os
os.makedirs(artifact_dir, exist_ok=True)
# Configure
lm = dspy.LM(model) # or "anthropic/claude-sonnet-4-5-20250929", etc.
dspy.configure(lm=lm)
program = program_class()
# Baseline
evaluator = Evaluate(devset=devset, metric=metric, num_threads=4)
baseline_score = evaluator(program)
# Optimize
start = time.time()
optimizer = optimizer_class(**optimizer_kwargs)
optimized = optimizer.compile(program, trainset=trainset)
duration = (time.time() - start) / 60
# Evaluate optimized
score = evaluator(optimized)
# Save artifact
artifact_path = f"{artifact_dir}/{name}.json"
optimized.save(artifact_path)
# Log
run = {
"name": name,
"optimizer": optimizer_class.__name__,
"optimizer_config": optimizer_kwargs,
"model": model,
"trainset_size": len(trainset),
"devset_size": len(devset),
"metric": metric.__name__,
"baseline_score": baseline_score,
"score": score,
"improvement": score - baseline_score,
"duration_minutes": round(duration, 1),
"artifact_path": artifact_path,
}
log_experiment(run)
print(f"[{name}] {baseline_score:.1f}% -> {score:.1f}% (+{score - baseline_score:.1f}%)")
return optimized, runRun a batch of experiments
experiments = [
{
"name": "bootstrap-4demos",
"optimizer_class": dspy.BootstrapFewShot,
"optimizer_kwargs": {"metric": metric, "max_bootstrapped_demos": 4},
},
{
"name": "bootstrap-8demos",
"optimizer_class": dspy.BootstrapFewShot,
"optimizer_kwargs": {"metric": metric, "max_bootstrapped_demos": 8},
},
{
"name": "mipro-light",
"optimizer_class": dspy.MIPROv2,
"optimizer_kwargs": {"metric": metric, "auto": "light"},
},
{
"name": "mipro-medium",
"optimizer_class": dspy.MIPROv2,
"optimizer_kwargs": {"metric": metric, "auto": "medium"},
},
]
results = []
for exp in experiments:
optimized, run = run_experiment(
name=exp["name"],
program_class=MyProgram,
optimizer_class=exp["optimizer_class"],
optimizer_kwargs=exp["optimizer_kwargs"],
trainset=trainset,
devset=devset,
metric=metric,
)
results.append(run)Step 4: Compare experiments
Display comparison table
def compare_experiments(path=EXPERIMENT_LOG, sort_by="score"):
"""Load experiments and display a comparison table."""
runs = load_experiments(path)
runs.sort(key=lambda r: r.get(sort_by, 0), reverse=True)
# Header
print(f"{'Name':<30} {'Optimizer':<20} {'Model':<22} {'Score':>7} {'Improve':>8} {'Cost':>7}")
print("-" * 120)
for r in runs:
name = r.get("name", "?")[:29]
opt = r.get("optimizer", "?")[:19]
model = r.get("model", "?")[:21]
score = r.get("score", 0)
improvement = r.get("improvement", 0)
cost = r.get("cost_usd", 0)
print(f"{name:<30} {opt:<20} {model:<22} {score:>6.1f}% {improvement:>+7.1f}% ${cost:>5.2f}")
compare_experiments()
# Name Optimizer Model Score Improve Cost
# ------------------------------------------------------------------------------------------------------------------------
# mipro-medium MIPROv2 openai/gpt-4o-mini 84.0% +19.0% $4.50
# mipro-light MIPROv2 openai/gpt-4o-mini 78.0% +13.0% $1.20
# bootstrap-8demos BootstrapFewShot openai/gpt-4o-mini 74.0% +9.0% $0.30
# bootstrap-4demos BootstrapFewShot openai/gpt-4o-mini 71.0% +6.0% $0.15Filter experiments
def filter_experiments(path=EXPERIMENT_LOG, **filters):
"""Filter experiments by any field."""
runs = load_experiments(path)
for key, value in filters.items():
if key == "min_score":
runs = [r for r in runs if r.get("score", 0) >= value]
elif key == "optimizer":
runs = [r for r in runs if r.get("optimizer") == value]
elif key == "model":
runs = [r for r in runs if r.get("model") == value]
return runs
# Only MIPROv2 runs
mipro_runs = filter_experiments(optimizer="MIPROv2")
# Runs scoring above 80%
good_runs = filter_experiments(min_score=80.0)Step 5: Promote best experiment to production
import shutil
def promote_experiment(name, production_path="production/optimized.json"):
"""Copy the winning experiment's artifact to the production path."""
import os
runs = load_experiments()
run = next((r for r in runs if r["name"] == name), None)
if not run:
print(f"Experiment '{name}' not found")
return
os.makedirs(os.path.dirname(production_path), exist_ok=True)
shutil.copy2(run["artifact_path"], production_path)
# Log the promotion
promotion = {
"event": "promotion",
"experiment_name": name,
"score": run["score"],
"source_artifact": run["artifact_path"],
"production_path": production_path,
"timestamp": datetime.now().isoformat(),
}
with open("promotions.jsonl", "a") as f:
f.write(json.dumps(promotion) + "\n")
print(f"Promoted '{name}' (score: {run['score']:.1f}%) to {production_path}")
# Promote the best experiment
promote_experiment("mipro-medium")
# Promoted 'mipro-medium' (score: 84.0%) to production/optimized.jsonLoad the promoted program in production
# In your production code
program = MyProgram()
program.load("production/optimized.json")Step 6: Use W&B Weave (for teams)
For teams running many experiments, W&B Weave adds visual dashboards and collaboration:
pip install weaveimport weave
weave.init("my-project")
@weave.op()
def run_optimization(optimizer_name, model, trainset, devset, metric):
"""Tracked optimization run — Weave logs inputs, outputs, and cost."""
lm = dspy.LM(model)
dspy.configure(lm=lm)
program = MyProgram()
optimizer = dspy.MIPROv2(metric=metric, auto="medium")
optimized = optimizer.compile(program, trainset=trainset)
evaluator = Evaluate(devset=devset, metric=metric, num_threads=4)
score = evaluator(optimized)
return {"score": score, "optimizer": optimizer_name, "model": model}
# Weave auto-tracks everything — view at wandb.ai
result = run_optimization("mipro-medium", "openai/gpt-4o-mini", trainset, devset, metric)For in-depth Weave setup, see /dspy-weave. For MLflow experiment tracking, see /dspy-mlflow.
Step 7: Use LangWatch (for real-time optimizer progress)
LangWatch shows optimizer progress as it runs — useful for long optimization runs:
pip install langwatchimport langwatch
langwatch.init()
# LangWatch tracks DSPy optimizer steps in real-time
optimizer = dspy.MIPROv2(metric=metric, auto="heavy")
optimized = optimizer.compile(program, trainset=trainset)
# Watch progress at app.langwatch.aiFor the full LangWatch guide (auto-tracing, optimizer dashboard, self-hosted), see /dspy-langwatch.
Gotchas
- GEPA takes metric in the constructor, not compile(). Unlike BootstrapFewShot and MIPROv2, GEPA accepts
metriconly as a constructor parameter. Passingmetric=metrictocompile()raises a TypeError. Always pass metric when instantiating:dspy.GEPA(metric=metric, auto="light"). - Comparing scores across different devsets is meaningless. Claude sometimes generates experiments that evaluate on different subsets. All experiments being compared must use the exact same devset, loaded once and passed to every run. If devset changes, scores are not comparable.
- Forgetting to save the artifact path makes experiments irreproducible. Claude logs the score but skips
optimized.save(). Without the saved .json artifact, you cannot reload or deploy the winning experiment. Always calloptimized.save(path)and log the path. - MIPROv2 auto default is "light", not "medium". Claude often writes
dspy.MIPROv2(metric=metric)assuming medium optimization. The defaultauto="light"runs fewer trials. Explicitly setauto="medium"orauto="heavy"when you want more thorough optimization. - Logging cost requires manual tracking — DSPy does not auto-report it. Claude sometimes writes
run["cost"] = optimizer.costas if DSPy tracks API costs. It does not. Track cost via your LM provider dashboard or by wrapping calls with a cost-tracking callback.
Key patterns
- Log from day one: even if you only have 2 experiments now, you'll have 20 next month
- Log the artifact path: an experiment without a saved .json file is useless
- Compare on the same devset: scores from different devsets aren't comparable
- Track cost: "20% better accuracy for 10x the cost" is a real tradeoff
- Promote explicitly: don't just copy files — log which experiment is in production
- Start file-based, upgrade later: JSONL tracking works fine until you have a team
Cross-references
Install any skill: npx skills add lebsral/DSPy-Programming-not-prompting-LMs-skills --skill <name>- Run optimization passes — see
/ai-improving-accuracy - Compare the same optimizer across models — see
/ai-switching-models - Reduce experiment costs — see
/ai-cutting-costs - Monitor promoted experiments in production — see
/ai-monitoring - W&B Weave setup (team dashboards, run comparison) — see
/dspy-weave - MLflow setup (experiment tracking, model registry) — see
/dspy-mlflow - LangWatch setup (real-time optimizer progress) — see
/dspy-langwatch - MIPROv2 optimizer — see
/dspy-miprov2 - BootstrapFewShot optimizer — see
/dspy-bootstrap-few-shot - 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
Additional resources
- For worked examples, see examples.md
last_audit:
date: 2026-05-01
score: 38/38
versions:
dspy: 3.2.0
[
{
"prompt": "I ran 6 different optimization experiments with MIPROv2 and BootstrapFewShot. I need to figure out which one was the best and deploy it.",
"expected_output": "Code that logs experiments to a JSONL file, compares them in a table sorted by score, and promotes the winner to a production path using optimized.save() and shutil.copy2.",
"assertions": [
"Uses JSONL or structured logging for experiment records",
"Logs at minimum: name, optimizer, score, artifact_path",
"Includes a comparison function that sorts by score",
"Includes a promote/deploy step that copies the artifact",
"Uses optimized.save() to persist the winning program",
"Does not pass metric to GEPA compile() if GEPA is used"
]
},
{
"prompt": "Our team runs experiments on different machines. We need a shared experiment tracking setup so everyone can see results.",
"expected_output": "Code that integrates with W&B Weave or MLflow for shared experiment tracking, with @weave.op() or mlflow.dspy.autolog() decorating optimization runs.",
"assertions": [
"Recommends W&B Weave, MLflow, or LangWatch for team use",
"Shows actual integration code, not just file-based tracking",
"Includes initialization (weave.init or mlflow setup)",
"Cross-references /dspy-weave, /dspy-mlflow, or /dspy-langwatch for details",
"All experiments use the same devset for comparable scores"
]
},
{
"prompt": "I want to compare how GPT-4o-mini, GPT-4o, and Claude Sonnet perform with MIPROv2 optimization on my classification task.",
"expected_output": "Code that runs the same optimizer across multiple models, logs each run, and produces a comparison table showing score, cost, and cost-per-point of improvement.",
"assertions": [
"Uses dspy.LM() with provider-agnostic model strings",
"Runs the same optimizer config across all models",
"Evaluates all models on the same devset",
"Includes cost tracking or notes that cost must be tracked manually",
"Produces a comparison table or summary"
]
}
]
Experiment Tracking Examples
Example 1: Comparing 5 optimizer configs for a classification task
You're building a ticket classifier and want to find the best optimization approach.
Setup
import dspy
from dspy.evaluate import Evaluate
# The program
class TicketClassifier(dspy.Module):
def __init__(self):
self.classify = dspy.ChainOfThought(
"ticket_text -> category: Literal['bug', 'feature', 'question', 'billing']"
)
def forward(self, ticket_text):
return self.classify(ticket_text=ticket_text)
# Metric
def accuracy(example, prediction, trace=None):
return prediction.category.lower() == example.category.lower()
# Data (200 train, 50 dev)
trainset = [
dspy.Example(ticket_text="App crashes on login", category="bug").with_inputs("ticket_text"),
dspy.Example(ticket_text="Can you add dark mode?", category="feature").with_inputs("ticket_text"),
# ... 200 examples
]
devset = trainset[150:] # hold out 50 for evaluation
trainset = trainset[:150]Run all experiments
experiments = [
{
"name": "baseline-no-opt",
"optimizer_class": None,
"optimizer_kwargs": {},
},
{
"name": "bootstrap-4",
"optimizer_class": dspy.BootstrapFewShot,
"optimizer_kwargs": {"metric": accuracy, "max_bootstrapped_demos": 4},
},
{
"name": "bootstrap-8",
"optimizer_class": dspy.BootstrapFewShot,
"optimizer_kwargs": {"metric": accuracy, "max_bootstrapped_demos": 8},
},
{
"name": "mipro-light",
"optimizer_class": dspy.MIPROv2,
"optimizer_kwargs": {"metric": accuracy, "auto": "light"},
},
{
"name": "mipro-medium",
"optimizer_class": dspy.MIPROv2,
"optimizer_kwargs": {"metric": accuracy, "auto": "medium"},
},
]
for exp in experiments:
if exp["optimizer_class"] is None:
# Just evaluate baseline
lm = dspy.LM("openai/gpt-4o-mini") # or "anthropic/claude-sonnet-4-5-20250929", etc.
dspy.configure(lm=lm)
evaluator = Evaluate(devset=devset, metric=accuracy, num_threads=4)
score = evaluator(TicketClassifier())
log_experiment({
"name": exp["name"],
"optimizer": "none",
"model": "openai/gpt-4o-mini",
"score": score,
"baseline_score": score,
"improvement": 0,
"cost_usd": 0.02,
"artifact_path": None,
})
else:
run_experiment(
name=exp["name"],
program_class=TicketClassifier,
optimizer_class=exp["optimizer_class"],
optimizer_kwargs=exp["optimizer_kwargs"],
trainset=trainset,
devset=devset,
metric=accuracy,
)Compare results
compare_experiments()
# Name Optimizer Model Score Improve Cost
# ------------------------------------------------------------------------------------------------------------------------
# mipro-medium MIPROv2 openai/gpt-4o-mini 91.0% +23.0% $5.80
# mipro-light MIPROv2 openai/gpt-4o-mini 86.0% +18.0% $1.50
# bootstrap-8 BootstrapFewShot openai/gpt-4o-mini 82.0% +14.0% $0.40
# bootstrap-4 BootstrapFewShot openai/gpt-4o-mini 78.0% +10.0% $0.20
# baseline-no-opt none openai/gpt-4o-mini 68.0% +0.0% $0.02
# Decision: mipro-medium wins at 91% accuracy
promote_experiment("mipro-medium")Example 2: Model migration experiment (GPT-4o to Claude Sonnet)
You're considering switching models and need to know the impact.
Run the same optimizer on both models
models = [
"openai/gpt-4o-mini",
"openai/gpt-4o",
"anthropic/claude-sonnet-4-5-20250929",
]
for model in models:
# Baseline (no optimization)
lm = dspy.LM(model)
dspy.configure(lm=lm)
evaluator = Evaluate(devset=devset, metric=accuracy, num_threads=4)
baseline = evaluator(TicketClassifier())
log_experiment({
"name": f"baseline-{model.split('/')[-1]}",
"optimizer": "none",
"model": model,
"score": baseline,
"baseline_score": baseline,
"improvement": 0,
})
# MIPROv2 optimization
run_experiment(
name=f"mipro-{model.split('/')[-1]}",
program_class=TicketClassifier,
optimizer_class=dspy.MIPROv2,
optimizer_kwargs={"metric": accuracy, "auto": "medium"},
trainset=trainset,
devset=devset,
metric=accuracy,
model=model,
)
compare_experiments()
# Name Optimizer Model Score Improve Cost
# ------------------------------------------------------------------------------------------------------------------------
# mipro-gpt-4o MIPROv2 openai/gpt-4o 94.0% +14.0% $12.00
# mipro-claude-sonnet-4-5-20250929 MIPROv2 anthropic/claude-... 93.0% +18.0% $8.50
# mipro-gpt-4o-mini MIPROv2 openai/gpt-4o-mini 91.0% +23.0% $5.80
# baseline-gpt-4o none openai/gpt-4o 80.0% +0.0% $0.00
# baseline-claude-sonnet-4-5-20250929 none anthropic/claude-... 75.0% +0.0% $0.00
# baseline-gpt-4o-mini none openai/gpt-4o-mini 68.0% +0.0% $0.00Analyze the results
# GPT-4o gets best absolute score (94%) but is most expensive
# Claude Sonnet gets similar score (93%) at lower cost
# GPT-4o-mini + MIPROv2 (91%) is the best value — close to GPT-4o at 1/2 the cost
# Check cost per point of accuracy
runs = load_experiments()
for r in runs:
if r.get("optimizer") != "none" and r.get("cost_usd", 0) > 0:
cost_per_point = r["cost_usd"] / r["improvement"] if r["improvement"] > 0 else float("inf")
print(f"{r['name']}: ${cost_per_point:.2f} per point of improvement")
# mipro-gpt-4o: $0.86 per point
# mipro-claude-sonnet-4-5-20250929: $0.47 per point
# mipro-gpt-4o-mini: $0.25 per point <-- best valueStacked optimization (advanced)
Run BootstrapFewShot first, then MIPROv2 on the result:
# Stage 1: BootstrapFewShot
optimized_stage1, run1 = run_experiment(
name="stacked-stage1-bootstrap",
program_class=TicketClassifier,
optimizer_class=dspy.BootstrapFewShot,
optimizer_kwargs={"metric": accuracy, "max_bootstrapped_demos": 4},
trainset=trainset,
devset=devset,
metric=accuracy,
)
# Stage 2: MIPROv2 on top of BootstrapFewShot result
def make_preoptimized():
prog = TicketClassifier()
prog.load("artifacts/stacked-stage1-bootstrap.json")
return prog
optimized_stage2, run2 = run_experiment(
name="stacked-stage2-mipro",
program_class=make_preoptimized,
optimizer_class=dspy.MIPROv2,
optimizer_kwargs={"metric": accuracy, "auto": "medium"},
trainset=trainset,
devset=devset,
metric=accuracy,
)
print(f"Bootstrap alone: {run1['score']:.1f}%")
print(f"Bootstrap + MIPROv2: {run2['score']:.1f}%")
# Bootstrap alone: 78.0%
# Bootstrap + MIPROv2: 92.5% <-- stacking helps!