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.







