Skip to content
AI Product Engineer Wiki

    ↑ ↓ to move between results, Enter to open, Esc to close

    Menu

    A/B testing AI features

    Run A/B tests on AI features, from choosing metrics and sample sizes to novelty effects, cost as a metric, and reading results when output quality varies.

    Level: Advanced 10 min read Updated
    On this page

    Why this matters

    Offline evals tell you which version is better on your test cases. They can’t tell you whether users actually do better with it: whether they accept more drafts, finish tasks faster, come back more often. A version that scores higher offline can still lose in production, because it’s slower, longer-winded, or solves a problem users didn’t have.

    A/B tests answer that question with real users. They’re also how you settle the trade-offs AI features constantly raise: a better model that costs twice as much, a prompt that’s more accurate but more verbose, an AI feature versus no AI feature at all. This article covers what to test, which metrics to use, how many users you need, and how to read results when cost and quality pull in different directions.

    What to test

    Good candidates for A/B tests on AI features:

    • The feature versus no feature. The most important test, and the one most often skipped. Does the AI feature beat the existing workflow?
    • Prompt versions, once offline evals show the new one is at least as good.
    • Models and effort settings: does the higher-quality, higher-cost option change behavior enough to pay for itself?
    • UX changes: streaming versus not, inline versus panel, showing sources or not.

    Only test variants that have already passed your offline evals. A/B tests are slow and expose real users; don’t use them to discover that a prompt is broken.

    Metrics and guardrails

    Define these before the test starts, in writing:

    • One primary metric: the behavior the change should improve, such as draft acceptance rate or task completion. One metric, so the result is unambiguous.
    • Guardrail metrics: things that must not get worse, such as complaint rate, overall task time, or satisfaction.
    • Cost per success: total model cost divided by successful outcomes, per variant. A variant that wins on the primary metric but doubles cost per success may not be worth shipping.
    • The decision rule: what result leads to shipping, iterating or dropping the variant.

    Use the analytics events your feature already records, tagged with the variant from your feature flag.

    Sample size and duration

    The most common A/B testing mistake is stopping too early. Small samples produce big, random swings, and if you check results every day and stop as soon as one looks good, you’ll often ship a change that does nothing.

    Decide the sample size before starting. It depends on your baseline rate and the smallest improvement worth detecting: small improvements need many more users. The code below includes a calculator. For example, with a 40% baseline acceptance rate, detecting a 4-point lift (to 44%) with the usual settings (5% significance, 80% power) needs about 2,400 users’ worth of outcomes per group.

    Duration matters too:

    • Run for full weeks, so weekday and weekend behavior are both included.
    • Watch for novelty effects. New AI features often get a burst of curious usage that fades. If the effect shrinks over the test, wait for it to stabilize before deciding.
    • Watch for learning effects in the other direction: some features get more useful as users learn to use them.

    The statistics, in code

    This module computes the sample size needed per group, and compares two variants’ success rates with a two-proportion test, returning the difference, a 95% confidence interval and a p-value. It has no dependencies.

    analysis/ab-test.ts
    /** Standard normal cumulative distribution, using the Abramowitz and Stegun 7.1.26 approximation (error < 1.5e-7). */
    function normalCdf(z: number): number {
      const t = 1 / (1 + (0.3275911 * Math.abs(z)) / Math.SQRT2);
      const poly = t * (0.254829592 + t * (-0.284496736 + t * (1.421413741 + t * (-1.453152027 + t * 1.061405429))));
      const erf = 1 - poly * Math.exp(-(z * z) / 2);
      return z >= 0 ? (1 + erf) / 2 : (1 - erf) / 2;
    }
    
    // Two-sided 5% significance and 80% power: the conventional defaults.
    const Z_ALPHA = 1.959964;
    const Z_POWER = 0.841621;
    
    /** Outcomes needed in each group to detect an absolute lift from `baseline` with the defaults above. */
    export function sampleSizePerGroup(baseline: number, minLift: number): number {
      const p1 = baseline;
      const p2 = baseline + minLift;
      const pBar = (p1 + p2) / 2;
      const n =
        (Z_ALPHA * Math.sqrt(2 * pBar * (1 - pBar)) + Z_POWER * Math.sqrt(p1 * (1 - p1) + p2 * (1 - p2))) ** 2 / (p2 - p1) ** 2;
      return Math.ceil(n);
    }
    
    /** Two-proportion z-test: is variant B's success rate different from A's? */
    export function compareRates(successA: number, totalA: number, successB: number, totalB: number) {
      const pA = successA / totalA;
      const pB = successB / totalB;
      const pooled = (successA + successB) / (totalA + totalB);
      const z = (pB - pA) / Math.sqrt(pooled * (1 - pooled) * (1 / totalA + 1 / totalB));
      const se = Math.sqrt((pA * (1 - pA)) / totalA + (pB * (1 - pB)) / totalB);
      const diff = pB - pA;
      return { pA, pB, diff, ci95: [diff - 1.96 * se, diff + 1.96 * se] as const, pValue: 2 * (1 - normalCdf(Math.abs(z))) };
    }
    
    // Before the test: how many outcomes per group to detect 40% → 44%?
    console.log('Per group:', sampleSizePerGroup(0.4, 0.04)); // 2389
    
    // After the test: A (current prompt) vs B (new prompt), 2,450 drafts each.
    const result = compareRates(980, 2450, 1078, 2450);
    const pct = (x: number) => `${(x * 100).toFixed(1)}%`;
    console.log(`A ${pct(result.pA)}, B ${pct(result.pB)}, lift ${pct(result.diff)}`);
    console.log(`95% CI ${pct(result.ci95[0])} to ${pct(result.ci95[1])}, p = ${result.pValue.toFixed(4)}`);
    // A 40.0%, B 44.0%, lift 4.0%; 95% CI 1.2% to 6.8%, p = 0.0046
    
    // Cost per success, from the model cost each variant logged.
    const costA = 2450 * 0.01; // $0.010 per draft
    const costB = 2450 * 0.014; // $0.014 per draft: the new prompt is longer
    console.log(`Cost per accepted draft: A $${(costA / 980).toFixed(4)}, B $${(costB / 1078).toFixed(4)}`);

    For real decisions, an experimentation platform or a statistics library is a better home for this; the point of the code is to make the reasoning visible. The output shows a clear win on acceptance (the confidence interval doesn’t include zero), and it also shows that each accepted draft costs more with the new prompt. Both facts belong in the decision.

    Reading the results

    AI tests often end with a trade-off rather than a clean winner. A simple way to read them:

    Reading an A/B test on an AI feature. Quality and cost often move in different directions; decide with both.
    Show text descriptionHide text description

    A two-by-two grid. The vertical axis is change in success rate, increasing upwards. The horizontal axis is cost savings, increasing to the right.

    • top left (high change in success rate, low cost savings): Better but pricier. Ship if the value of the extra successes exceeds the extra cost per success; check guardrails.
    • top right (high change in success rate, high cost savings): Better and cheaper. Ship, once guardrails hold and the result is stable over full weeks. This is the recommended quadrant.
    • bottom left (low change in success rate, low cost savings): Worse and pricier. Drop it, and record why so nobody tries it again blindly.
    • bottom right (low change in success rate, high cost savings): Cheaper but worse. Only if quality stays above the bar and savings matter more than the lost successes.

    Other situations you’ll meet:

    • A flat result. No significant difference. That’s information: the change didn’t matter to users. Prefer the simpler or cheaper variant, and look for a bigger lever.
    • A guardrail regression. The primary metric improved but complaints or task time got worse. Don’t ship until you understand why.
    • Different results by segment. The new version helps one category and hurts another. Consider routing, but be careful: slicing results many ways will find “significant” differences by chance. Decide segments in advance.

    Common mistakes

    • Stopping as soon as results look good. Decide sample size and duration in advance, and run full weeks.
    • Testing variants that haven’t passed offline evals. Use A/B tests to measure user impact, not to find broken prompts.
    • Ignoring cost. Compare cost per success alongside the primary metric.
    • Slicing results until something is significant. Choose segments before the test; treat surprising post-hoc segments as hypotheses.
    • Mistaking novelty for improvement. Check whether the effect holds after the first week.

    Try it

    Exercise

    About 40 min

    Design an A/B test for a prompt change

    1. Pick a prompt change that has passed your offline evals.
    2. Write the primary metric, two guardrails, how you’ll measure cost per success, and the decision rule.
    3. Use sampleSizePerGroup to find how many outcomes you need, and estimate how long that takes at your traffic.
    4. If it would take more than a few weeks, decide whether to test a bigger change instead, or accept detecting only larger effects.
    Show a hint

    If the sample size is impractical, increase the minimum lift you care about. Detecting small effects reliably takes far more traffic than most features have.

    Show a worked solution

    Example:

    • Change: new reply prompt that cites help articles more consistently.
    • Primary metric: draft acceptance rate (baseline 40%). Guardrails: reopened tickets, customer satisfaction.
    • Cost per success: model cost per variant ÷ accepted drafts.
    • Decision rule: ship if acceptance rises at least 2 points, guardrails don’t regress, and cost per accepted draft rises less than 30%.
    • Sample size: about 2,400 drafts per group for a 4-point lift. At 600 drafts a day split evenly, about 8 days; run for 2 full weeks.

    Check your understanding

    Question 1 of 4: Why decide the sample size before starting the test?

    Show answer

    B. Checking repeatedly and stopping when results look good often produces false wins

    Early stopping on a lucky swing is the most common way A/B tests ship changes that do nothing.

    Question 2 of 4: Variant B raises acceptance by 4 points but costs 40% more per accepted draft. What should decide whether to ship it?

    Show answer

    C. The value of the extra successes compared with the extra cost, plus guardrails

    AI tests often trade quality against cost; decide with both, using a value per success agreed in advance.

    Question 3 of 4: The 95% confidence interval for the lift is 1.2% to 6.8%. What does that tell you?

    Show answer

    B. The improvement is very likely real, since the interval does not include zero, though its size is uncertain

    An interval that excludes zero indicates a real difference; its width shows how precisely you know the size.

    Question 4 of 4: Usage of a new AI feature spikes in week one and falls in week two. What is the likely cause?

    Show answer

    B. A novelty effect

    Curiosity drives early usage. Wait for the effect to stabilize before deciding.

    Key takeaways

    • A/B tests measure real user impact; run them only on variants that passed offline evals.
    • Define one primary metric, guardrails, cost per success and a decision rule before starting.
    • Decide sample size and duration in advance, run full weeks, and watch for novelty effects.
    • Read results on quality and cost together; treat flat results and post-hoc segments with care.

    Progress is saved in this browser and lights up the roadmap.