Expérimentation / Tests A/B
La fonctionnalité d'expérimentation répond aux trois questions sur lesquelles tout backend de test A/B s'accorde :
- Quelle variante de l'expérience
Xl'utilisateurUdevrait-il consulter dès maintenant ? (affectation déterministe par utilisateur) - Enregistrez que nous avons montré la variante
Và l'utilisateurUau momentT. (suivi de l'exposition - le dénominateur dans les statistiques finales) - Enregistrez que l'utilisateur
Ua effectué la métriqueMavec la valeurVau momentT. (suivi des résultats – le numérateur dans les statistiques finales)
Le trait est indépendant du fournisseur. Les backends de hachage local, GrowthBook, Statsig, PostHog et Optimizely se connectent tous aux trois mêmes méthodes.
Indicateurs de fonctionnalités et expériences#
| Capacité | But |
|---|---|
FeatureFlag |
Choisissez le code qui exécute (déploiement, kill switch, ciblage). |
Experiment |
Mesurez quelle variante est la plus performante (exposition + résultats). |
Certaines plateformes commerciales regroupent les deux, et il est tentant de les fusionner en une seule fonctionnalité. bext les maintient séparés car les surfaces diffèrent : les indicateurs de fonctionnalités renvoient les variantes bool / String et n'émettent jamais d'analyses ; les expériences renvoient un Variant plus une exposition de demande et des écritures de résultats. Un projet utilisant GrowthBook pour les deux peut toujours installer un plugin qui expose les deux caractéristiques : elles restent simplement distinctes sur le site d'appel.
Le trait#
pub trait ExperimentPlugin: Send + Sync {
fn name(&self) -> &str;
fn assign(
&self,
experiment: &str,
ctx: &ExperimentContext,
) -> Result<Variant, ExperimentError>;
fn record_exposure(
&self,
exposure: ExposureRecord,
) -> Result<(), ExperimentError>;
fn record_outcome(
&self,
outcome: OutcomeRecord,
) -> Result<(), ExperimentError>;
}
assign est pur. L'appeler mille fois avec le même contexte renvoie la même variante et n'affecte rien. L'exposition est l'effet secondaire qui fait bouger les statistiques. Les garder séparés permet aux appelants d'attribuer, de répondre à une autre condition, et ensuite seulement d'exposer - le modèle standard rendu par le serveur.
Types de clés#
| Taper | But |
|---|---|
ExperimentContext |
user_id?, session_id?, attributes: HashMap<String, String>. |
Variant |
experiment, variant_id, is_control: bool. |
ExposureRecord |
experiment, variant_id, user_id?, timestamp. |
OutcomeValue |
Bool(bool), Numeric(f64), Count(i64). |
OutcomeRecord |
experiment, metric, value, user_id?, timestamp. |
ExperimentError |
ExperimentNotFound (404), InvalidContext (400), Backend (500). |
user_id est facultatif partout. Les utilisateurs anonymes participent toujours - ils utilisent par défaut la variante de contrôle et la clé d'enregistrement d'exposition/résultat sur session_id à l'intérieur de attributes si le backend le prend en charge.
Formes de valeur de résultat#
| Variante | Utiliser pour |
|---|---|
Bool |
Conversion binaire (inscrit, cliqué sur CTA, extrait). |
Numeric |
Métrique continue (revenus, temps passé sur la page, profondeur de défilement). |
Count |
Compteur entier (articles ajoutés au panier, pages visitées). |
Implémentation de référence : @bext/experiment-hash#
@bext/experiment-hash est un backend** local et déterministe **qui ne nécessite aucun réseau. L'affectation est un hachage FNV-1a de
experiment_id + ":" + user_id:
- Le hachage est réduit à une valeur dans
[0, 1). - Cette valeur sélectionne une variante en fonction de la répartition configurée.
- Les utilisateurs anonymes (pas de
user_id) obtiennent toujours la variante de contrôle.
Déterministe signifie que la même paire (experiment, user_id) atterrit sur la même variante pendant toute la durée de vie de l'expérience, ce qui est essentiel pour une UX cohérente entre les requêtes.
[[plugins]]
name = "experiment-hash"
source = "@bext/experiment-hash"
[[plugins.experiments]]
id = "checkout_button_color"
variants = [
{ id = "control", weight = 50, is_control = true },
{ id = "treatment", weight = 50, is_control = false },
]
Exemple : attribuer, exposer, enregistrer le résultat#
use bext_plugin_api::experiment::*;
use chrono::Utc;
let plugin: &dyn ExperimentPlugin = /* @bext/experiment-hash */;
// 1. Deterministic assignment (pure, no side effects).
let ctx = ExperimentContext::for_user("alice")
.with_attribute("country", "FR")
.with_attribute("plan", "pro");
let variant = plugin.assign("checkout_button_color", &ctx)?;
// 2. Render the page and tell the backend the user saw this variant.
let html = render_with_variant(&variant);
plugin.record_exposure(ExposureRecord {
experiment: "checkout_button_color".into(),
variant_id: variant.variant_id.clone(),
user_id: ctx.user_id.clone(),
timestamp: Utc::now(),
})?;
// 3. Later — the user converts. Record the outcome.
plugin.record_outcome(OutcomeRecord {
experiment: "checkout_button_color".into(),
metric: "signup".into(),
value: OutcomeValue::Bool(true),
user_id: ctx.user_id,
timestamp: Utc::now(),
})?;
Exposition avant le résultat, toujours#
L'invariant attendu par chaque backend d'expérimentation : enregistrer l'exposition avant les résultats de. L'exposition est ce qui fait que l'utilisateur participe à l'expérience ; les résultats attribués à un utilisateur qui n’a jamais été exposé sont rejetés par le moteur d’analyse. bext n'applique pas l'ordre au niveau des traits - cela dépend de l'appelant - mais l'ordre est important pour que les nombres aient un sens.
assign est une fonction pure - son appel fait que et non enregistrent une exposition. Vous devez appeler explicitement record_exposure après le rendu. Ignorer cette étape signifie que les utilisateurs sont attribués à des variantes mais ne sont jamais comptés dans le dénominateur du test, ce qui rend les résultats invalides.
Indicateur de fonctionnalité#
Aucun. Le trait et les types d'expérience se trouvent dans bext-plugin-api et sont toujours disponibles ; aucune fonction de chargement ne les bloque.
Voir aussi#
@bext/experiment-hash— backend déterministe local.- Indicateur de fonctionnalité — sélection du chemin de code (déploiement, kill switch) distincte de la mesure expérimentale.
- Session — d'où vient généralement le
user_idstable pour le regroupement déterministe. - Traceur — corrèle les affectations de variantes d'expérience avec les traces distribuées.
- Aperçu des capacités — la liste complète des fonctionnalités enfichables.