Why Prompt Changes Feel Better Than They Are
You update a prompt. The next few outputs look better. You ship it.
Three weeks later, a user complains about a regression in output quality that you cannot reproduce. You roll back. The metrics are unclear. You do not know if the original or new prompt was actually better.
This is the default prompt management cycle. It is unreliable because it relies on:
- Anecdotal sampling (a few outputs is not a representative sample)
- Recency bias (new things feel better)
- No control group (you do not know what the old prompt would have produced on the same inputs)
---
The Prompt A/B Test Framework
Step 1: Define Your Success Metric
Before running any test, define what "better" means:
Step 2: Implement Traffic Splitting
```javascript class PromptABTest { constructor(experimentId, promptA, promptB, splitRatio = 0.5) { this.experimentId = experimentId; this.promptA = promptA; this.promptB = promptB; this.splitRatio = splitRatio; } // Deterministic assignment based on user ID assignVariant(userId) { const hash = this.hashString(userId + this.experimentId); return hash < this.splitRatio ? 'A' : 'B'; }
Get advanced blueprint resources
Enter your email to receive technical blueprints, checklists, and visual configurations accompanying this chapter.
No spam. Unsubscribe any time.
hashString(str) { let hash = 0; for (let i = 0; i < str.length; i++) { hash = ((hash << 5) - hash) + str.charCodeAt(i); hash = hash & hash; } return Math.abs(hash) / 2147483647; } getPrompt(userId) { return this.assignVariant(userId) === 'A' ? this.promptA : this.promptB; } }
// In your API handler: const experiment = new PromptABTest('prompt_v2_test', PROMPT_V1, PROMPT_V2, 0.5); const prompt = experiment.getPrompt(userId); const variant = experiment.assignVariant(userId); const response = await callModel(prompt, userInput);
await logExperimentResult({ experimentId: 'prompt_v2_test', variant, userId, input: userInput, output: response, timestamp: new Date().toISOString(), });
javascript async function analyseExperiment(experimentId, minSampleSize = 100) { const { data: results } = await supabase .from('prompt_experiment_results') .select('variant, quality_score') .eq('experiment_id', experimentId); const groupA = results.filter(r => r.variant === 'A'); const groupB = results.filter(r => r.variant === 'B'); if (groupA.length < minSampleSize || groupB.length < minSampleSize) { return { status: 'insufficient_data', samplesA: groupA.length, samplesB: groupB.length }; } const meanA = groupA.reduce((s, r) => s + r.quality_score, 0) / groupA.length; const meanB = groupB.reduce((s, r) => s + r.quality_score, 0) / groupB.length; const pValue = tTest(groupA.map(r => r.quality_score), groupB.map(r => r.quality_score)); return { status: 'ready', variant_A: { n: groupA.length, mean_score: meanA }, variant_B: { n: groupB.length, mean_score: meanB }, winner: meanB > meanA ? 'B' : 'A', improvement_pct: ((meanB - meanA) / meanA * 100).toFixed(1) + '%', statistically_significant: pValue < 0.05, p_value: pValue, recommendation: pValue < 0.05 ? 'Ship Variant ' + (meanB > meanA ? 'B' : 'A') + ' -- statistically significant improvement' : 'Continue collecting data -- results not yet conclusive', }; } ```---
Sample Size Planning
| Expected improvement | Minimum samples per variant |
|---|---|
| 30%+ improvement | 50 per variant |
| 20% improvement | 100 per variant |
| 10% improvement | 400 per variant |
| 5% improvement | 1,600 per variant |
| 2% improvement | 10,000 per variant |
---
Common A/B Testing Mistakes
Mistake 1: Peeking. Checking results before reaching minimum sample size and deciding early. The early results will almost always be noise.
Mistake 2: Multiple simultaneous experiments. Running multiple prompt changes at once makes it impossible to attribute results to any specific change. Test one variable at a time.
Mistake 3: Ignoring novelty effects. Users sometimes behave differently when first encountering a new interface. Run experiments long enough (at least a week) to get past novelty.
Mistake 4: Not controlling for input distribution. If your traffic mix changes during the experiment (e.g., more mobile users, different use cases), the experiment results are confounded.