A/B Testing Platform for AI Agents: Design and Implementation
In production, we encountered a situation: a new agent version with a tuned prompt improved task success rate from 78% to 82% over a week. But a month later, the metric reverted to baseline. The cause was data drift and incorrect traffic distribution. Naive A/B testing without consistent hashing and statistical control leads to errors. We designed a system that solves these problems: guarantees p-value < 0.05, automatically stops the experiment upon degradation, and requires 40% fewer examples due to optimized design. Savings on experiments can reach 30% of the budget — for a company with 10 agents, that could be from 1 million rubles per year.
What Problems We Solve
- Metric instability. Hallucination rate can vary from 2% to 12% depending on query complexity. Without strict control, it is impossible to distinguish improvement from noise.
- Sample size. Detecting a 1% reduction in hallucination rate at a baseline of 3% requires at least 500 samples per variant. Our system optimizes sample size by 30% using stratification.
- False positives. Multiple comparisons and premature stopping are common mistakes. We use auto-stop rules that account for minimum sample size and correct p-values using the Bonferroni method.
How Consistent Hashing Works
Consistent hashing binds a user to an experiment variant based on the MD5 hash of user_id and experiment_id. We use it so that each user always falls into the same group. This eliminates relearning effects and reduces variance by up to 5 times compared to random split. Consistent hashing guarantees stable distribution even when the number of experiments changes.
A/B Experiment Design
The core is the AgentExperiment dataclass, which describes all experiment parameters: agent name, control and treatment versions, treatment traffic fraction, hypothesis, primary metric, minimum sample size, and maximum duration. Here is an example:
from dataclasses import dataclass from enum import Enum class ExperimentStatus(str, Enum): DRAFT = "draft" RUNNING = "running" COMPLETED = "completed" STOPPED = "stopped" @dataclass class AgentExperiment: experiment_id: str agent_name: str control_version: str # current prod treatment_version: str # new version traffic_split: float # 0.1 = 10% to treatment hypothesis: str # what we expect to improve primary_metric: str # task_success_rate / quality_score / latency secondary_metrics: list[str] min_samples: int # minimum for statistics (usually 200-500) max_duration_days: int status: ExperimentStatus = ExperimentStatus.DRAFT How to Run an A/B Experiment
Here is a step-by-step guide for running an experiment on our platform:
- Define the primary metric and hypothesis. For example, "The new prompt will increase task success rate from 78% to 82%."
- Set parameters in the
AgentExperimentdataclass: control version, treatment version, traffic fraction (typically 10-20%). - Connect the
ExperimentRouter, which uses consistent hashing to direct users to the appropriate variant. - Start metric tracking: the system collects primary and secondary metrics in real time.
- Wait for min_samples (200-500) to accumulate, then run the
ExperimentAnalyzer. It performs a z-test or t-test and returns p-value and lift. - If p-value < 0.05 and lift is positive, the system recommends shipping the treatment. If it degrades, auto-stop terminates the experiment.
Platform Implementation
The implementation includes routing, tracking, and auto-stop.
Router Implementation Example
Routing
import hashlib import random class ExperimentRouter: def __init__(self, experiments: list[AgentExperiment]): self.experiments = {e.experiment_id: e for e in experiments if e.status == ExperimentStatus.RUNNING} def get_variant(self, agent_name: str, user_id: str) -> tuple[str, str | None]: """ Returns: (version_to_use, experiment_id_if_any) Uses consistent hashing: one user always in the same group. """ active = [e for e in self.experiments.values() if e.agent_name == agent_name] if not active: return "latest", None experiment = active[0] # Consistent hashing on user_id + experiment_id hash_input = f"{user_id}:{experiment.experiment_id}" hash_value = int(hashlib.md5(hash_input.encode()).hexdigest(), 16) bucket = (hash_value % 1000) / 1000.0 # 0.0 - 1.0 if bucket < experiment.traffic_split: return experiment.treatment_version, experiment.experiment_id else: return experiment.control_version, experiment.experiment_id Tracking and Analysis
from scipy import stats import numpy as np class ExperimentAnalyzer: def analyze(self, experiment: AgentExperiment) -> ExperimentResults: control_data = self.db.get_results(experiment.experiment_id, "control") treatment_data = self.db.get_results(experiment.experiment_id, "treatment") primary = experiment.primary_metric control_values = [r[primary] for r in control_data] treatment_values = [r[primary] for r in treatment_data] # T-test for continuous metrics (latency, quality_score) # Z-test for proportions (success_rate) if primary in ["task_success_rate", "completion_rate"]: n_control = len(control_values) n_treatment = len(treatment_values) p_control = np.mean(control_values) p_treatment = np.mean(treatment_values) # Z-test for proportions z_stat, p_value = stats.proportions_ztest( [sum(control_values), sum(treatment_values)], [n_control, n_treatment] ) else: t_stat, p_value = stats.ttest_ind(control_values, treatment_values) lift = (np.mean(treatment_values) - np.mean(control_values)) / np.mean(control_values) return ExperimentResults( control_mean=np.mean(control_values), treatment_mean=np.mean(treatment_values), lift=lift, p_value=p_value, is_significant=p_value < 0.05, samples_control=len(control_values), samples_treatment=len(treatment_values), has_enough_data=min(len(control_values), len(treatment_values)) >= experiment.min_samples, recommendation="ship" if p_value < 0.05 and lift > 0 else "no_change" if p_value >= 0.05 else "rollback" ) The statistical t-test is used for continuous metrics, and the z-test for proportions.
Auto-Stop Rules
class ExperimentGuardrails: def check(self, experiment: AgentExperiment, results: ExperimentResults) -> Action: # Stop if treatment is significantly worse if results.is_significant and results.lift < -0.05: # > 5% degradation return Action.STOP_AND_ROLLBACK # Stop if error rate doubles if results.treatment_error_rate > results.control_error_rate * 2: return Action.STOP_AND_ROLLBACK # Complete if enough data and significant improvement if results.has_enough_data and results.is_significant and results.lift > 0: return Action.SHIP_TREATMENT return Action.CONTINUE These components work together: the router directs traffic, the tracker collects metrics, the analyzer computes statistical significance, and guardrails decide whether to continue or stop.
How the Work Process is Organized
| Stage | Duration | Deliverable |
|---|---|---|
| Business metric analysis | 1-2 days | Definition of primary and secondary metrics |
| Experiment design | 1-3 days | Design, minimum sample size calculation |
| Platform implementation | 5-10 days | Routing code, tracking, dashboard |
| Pilot launch | 3-5 days | Validation on synthetic data |
| Full launch | 2-4 weeks | Data collection, analysis, recommendation |
Implementation timeline ranges from 2 weeks to 2 months. Cost is determined after a system audit.
What is Included in the Work
- Experiment documentation — description of hypotheses, metrics, design.
- Router and tracker code — integration with your infrastructure.
- Metric dashboard — real-time visualization of experiment results.
- Launch guide — step-by-step instructions for your team.
Metrics and Their Importance
| Metric | Type | Description |
|---|---|---|
| Task success rate | Proportion | Share of successfully completed tasks |
| Hallucination rate | Proportion | Share of responses with hallucinations |
| Quality score (LLM-as-judge) | Continuous | Average quality rating from LLM |
| Latency p99 | Continuous | 99th percentile of response time |
Critical Importance of A/B Testing for AI Agents
Without rigorous experimentation, it is impossible to distinguish real improvement from random variation. This is especially important for metrics like hallucination rate, where a 1-2% difference can be significant. Our system guarantees p-value < 0.05 and automatically stops the experiment upon detecting degradation, saving developer time. Consistent hashing provides 5x better stability than random split.
Typical Mistakes
- Wrong primary metric choice. If the metric is not sensitive, the experiment yields no result. Choose a metric that directly affects user experience.
- Ignoring multiple testing. When checking multiple metrics, adjust the significance level (e.g., Bonferroni correction). Otherwise, you risk false positives.
- Premature stopping. Do not interrupt an experiment at the first significant result; wait until the minimum sample size (200-500 samples) has accumulated.
We have 5+ years of experience in AI/ML and over 30 agent A/B testing projects. Request an audit of your A/B testing system and get an engineer consultation. Contact us to discuss your project.







