Auto oss vs cost efficient 50/50 A/B test (#9355)
I want to run an a/b test where 50% of free users get defaulted to auto cost efficient, and other 50% to auto open weights. ## Tests - 50% of the time when i open the app with a different WARP_DATA_DIR i get the expected flip/flopping - my default gets persisted throughout signup --------- Co-authored-by: Oz <oz-agent@warp.dev>
This commit is contained in:
@@ -7,10 +7,15 @@ use warp_core::ui::icons::Icon;
|
|||||||
use warpui::{AppContext, SingletonEntity};
|
use warpui::{AppContext, SingletonEntity};
|
||||||
|
|
||||||
use crate::auth::AuthStateProvider;
|
use crate::auth::AuthStateProvider;
|
||||||
|
use crate::experiments::FreeTierDefaultModel;
|
||||||
use crate::workspaces::user_workspaces::UserWorkspaces;
|
use crate::workspaces::user_workspaces::UserWorkspaces;
|
||||||
|
|
||||||
use super::llms::{DisableReason, LLMInfo, LLMPreferences};
|
use super::llms::{DisableReason, LLMInfo, LLMPreferences};
|
||||||
|
|
||||||
|
/// mirrors server-side model ids
|
||||||
|
const AUTO_OPEN_LLM_ID: &str = "auto-open";
|
||||||
|
const AUTO_COST_EFFICIENT_LLM_ID: &str = "auto-efficient";
|
||||||
|
|
||||||
impl From<&LLMInfo> for OnboardingModelInfo {
|
impl From<&LLMInfo> for OnboardingModelInfo {
|
||||||
fn from(llm: &LLMInfo) -> Self {
|
fn from(llm: &LLMInfo) -> Self {
|
||||||
Self {
|
Self {
|
||||||
@@ -36,6 +41,27 @@ pub fn build_onboarding_models(prefs: &LLMPreferences) -> (Vec<OnboardingModelIn
|
|||||||
(models, default_id)
|
(models, default_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn apply_free_tier_default_model_override(
|
||||||
|
models: &mut [OnboardingModelInfo],
|
||||||
|
server_default_id: LLMId,
|
||||||
|
ctx: &mut AppContext,
|
||||||
|
) -> LLMId {
|
||||||
|
// server only gives back cost-efficient as a default if you're on a free or no plan
|
||||||
|
// if you ARE on some sort of plan... we should respect what the server says
|
||||||
|
if server_default_id != LLMId::from(AUTO_COST_EFFICIENT_LLM_ID) {
|
||||||
|
return server_default_id;
|
||||||
|
}
|
||||||
|
let auto_open_id = LLMId::from(AUTO_OPEN_LLM_ID);
|
||||||
|
let auto_open_available = models.iter().any(|m| m.id == auto_open_id);
|
||||||
|
if !auto_open_available || !FreeTierDefaultModel::should_default_to_auto_open(ctx) {
|
||||||
|
return server_default_id;
|
||||||
|
}
|
||||||
|
for m in models.iter_mut() {
|
||||||
|
m.is_default = m.id == auto_open_id;
|
||||||
|
}
|
||||||
|
auto_open_id
|
||||||
|
}
|
||||||
|
|
||||||
pub fn current_onboarding_auth_state(ctx: &AppContext) -> OnboardingAuthState {
|
pub fn current_onboarding_auth_state(ctx: &AppContext) -> OnboardingAuthState {
|
||||||
let auth_state = AuthStateProvider::as_ref(ctx).get();
|
let auth_state = AuthStateProvider::as_ref(ctx).get();
|
||||||
if auth_state.is_anonymous_or_logged_out() {
|
if auth_state.is_anonymous_or_logged_out() {
|
||||||
|
|||||||
@@ -0,0 +1,71 @@
|
|||||||
|
use super::{BucketRange, Experiment, Layer};
|
||||||
|
use lazy_static::lazy_static;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::str::FromStr;
|
||||||
|
use warpui::AppContext;
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
pub static ref FREE_TIER_DEFAULT_MODEL_LAYER: Layer = Layer {
|
||||||
|
name: "FreeTierDefaultModelLayer",
|
||||||
|
hasher_seeds: (3141, 5926),
|
||||||
|
traffic_allocations: HashMap::from([
|
||||||
|
(FreeTierDefaultModel::AutoEfficient.get_group_id(), 50.0),
|
||||||
|
(FreeTierDefaultModel::AutoOpen.get_group_id(), 50.0),
|
||||||
|
]),
|
||||||
|
bucket_ranges: vec![
|
||||||
|
BucketRange::new(FreeTierDefaultModel::AutoEfficient, 0..500),
|
||||||
|
BucketRange::new(FreeTierDefaultModel::AutoOpen, 500..1000),
|
||||||
|
]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 50/50 A/B test of the default model surfaced to free-tier users in the
|
||||||
|
/// pre-signup onboarding ("configure oz") model picker.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum FreeTierDefaultModel {
|
||||||
|
/// Control: keep the existing free-tier default (auto (cost-efficient)).
|
||||||
|
AutoEfficient,
|
||||||
|
/// Experiment: surface auto (open-weights) as the default for free users.
|
||||||
|
AutoOpen,
|
||||||
|
}
|
||||||
|
|
||||||
|
const FREE_TIER_DEFAULT_MODEL_AUTO_EFFICIENT: &str = "AutoEfficient";
|
||||||
|
const FREE_TIER_DEFAULT_MODEL_AUTO_OPEN: &str = "AutoOpen";
|
||||||
|
|
||||||
|
impl Experiment<FreeTierDefaultModel> for FreeTierDefaultModel {
|
||||||
|
fn name() -> &'static str {
|
||||||
|
"FreeTierDefaultModel"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn variant(&self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
Self::AutoEfficient => FREE_TIER_DEFAULT_MODEL_AUTO_EFFICIENT,
|
||||||
|
Self::AutoOpen => FREE_TIER_DEFAULT_MODEL_AUTO_OPEN,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn allow_user_overrides_in_stable() -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for FreeTierDefaultModel {
|
||||||
|
type Err = anyhow::Error;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
match s {
|
||||||
|
FREE_TIER_DEFAULT_MODEL_AUTO_EFFICIENT => Ok(Self::AutoEfficient),
|
||||||
|
FREE_TIER_DEFAULT_MODEL_AUTO_OPEN => Ok(Self::AutoOpen),
|
||||||
|
_ => Err(anyhow::anyhow!(
|
||||||
|
"Variant {} is not a valid group in FreeTierDefaultModel",
|
||||||
|
s
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FreeTierDefaultModel {
|
||||||
|
pub fn should_default_to_auto_open(ctx: &mut AppContext) -> bool {
|
||||||
|
matches!(Self::get_group(ctx), Some(Self::AutoOpen))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,6 +9,7 @@ mod block_onboarding_layer;
|
|||||||
mod login_layer;
|
mod login_layer;
|
||||||
mod rendering;
|
mod rendering;
|
||||||
pub use block_onboarding_layer::{BlockOnboarding, BLOCK_ONBOARDING_LAYER};
|
pub use block_onboarding_layer::{BlockOnboarding, BLOCK_ONBOARDING_LAYER};
|
||||||
|
pub use free_tier_default_model_layer::{FreeTierDefaultModel, FREE_TIER_DEFAULT_MODEL_LAYER};
|
||||||
pub use improved_palette_search_layer::{ImprovedPaletteSearch, IMPROVED_PALETTE_SEARCH_LAYER};
|
pub use improved_palette_search_layer::{ImprovedPaletteSearch, IMPROVED_PALETTE_SEARCH_LAYER};
|
||||||
pub use login_layer::{AuthFlowInstructions, LOGIN_LAYER};
|
pub use login_layer::{AuthFlowInstructions, LOGIN_LAYER};
|
||||||
use warp_core::user_preferences::GetUserPreferences as _;
|
use warp_core::user_preferences::GetUserPreferences as _;
|
||||||
@@ -69,6 +70,7 @@ lazy_static! {
|
|||||||
&*BLOCK_ONBOARDING_LAYER,
|
&*BLOCK_ONBOARDING_LAYER,
|
||||||
&*rendering::LAYER,
|
&*rendering::LAYER,
|
||||||
&*IMPROVED_PALETTE_SEARCH_LAYER,
|
&*IMPROVED_PALETTE_SEARCH_LAYER,
|
||||||
|
&*FREE_TIER_DEFAULT_MODEL_LAYER,
|
||||||
];
|
];
|
||||||
|
|
||||||
/// Mapping of experiments to their respective layers. The mappings are built up
|
/// Mapping of experiments to their respective layers. The mappings are built up
|
||||||
@@ -403,6 +405,7 @@ pub fn init(ctx: &mut AppContext) {
|
|||||||
#[path = "mod_tests.rs"]
|
#[path = "mod_tests.rs"]
|
||||||
mod tests;
|
mod tests;
|
||||||
|
|
||||||
|
mod free_tier_default_model_layer;
|
||||||
mod improved_palette_search_layer;
|
mod improved_palette_search_layer;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod validation_tests;
|
mod validation_tests;
|
||||||
|
|||||||
+10
-4
@@ -100,7 +100,9 @@ use warpui::keymap::{EditableBinding, FixedBinding};
|
|||||||
use warpui::windowing::WindowManager;
|
use warpui::windowing::WindowManager;
|
||||||
|
|
||||||
use crate::ai::llms::{LLMPreferences, LLMPreferencesEvent};
|
use crate::ai::llms::{LLMPreferences, LLMPreferencesEvent};
|
||||||
use crate::ai::onboarding::{build_onboarding_models, current_onboarding_auth_state};
|
use crate::ai::onboarding::{
|
||||||
|
apply_free_tier_default_model_override, build_onboarding_models, current_onboarding_auth_state,
|
||||||
|
};
|
||||||
use crate::pricing::{PricingInfoModel, PricingInfoModelEvent};
|
use crate::pricing::{PricingInfoModel, PricingInfoModelEvent};
|
||||||
use warp_graphql::billing::StripeSubscriptionPlan;
|
use warp_graphql::billing::StripeSubscriptionPlan;
|
||||||
|
|
||||||
@@ -1995,8 +1997,10 @@ impl RootView {
|
|||||||
|
|
||||||
let themes = onboarding_theme_picker_themes();
|
let themes = onboarding_theme_picker_themes();
|
||||||
let onboarding_view = ctx.add_typed_action_view(move |ctx| {
|
let onboarding_view = ctx.add_typed_action_view(move |ctx| {
|
||||||
let llm_preferences = LLMPreferences::as_ref(ctx);
|
let (mut models, default_model_id) =
|
||||||
let (models, default_model_id) = build_onboarding_models(llm_preferences);
|
build_onboarding_models(LLMPreferences::as_ref(ctx));
|
||||||
|
let default_model_id =
|
||||||
|
apply_free_tier_default_model_override(&mut models, default_model_id, ctx);
|
||||||
|
|
||||||
let workspace_enforces_autonomy = UserWorkspaces::as_ref(ctx)
|
let workspace_enforces_autonomy = UserWorkspaces::as_ref(ctx)
|
||||||
.ai_autonomy_settings()
|
.ai_autonomy_settings()
|
||||||
@@ -2039,8 +2043,10 @@ impl RootView {
|
|||||||
&LLMPreferences::handle(ctx),
|
&LLMPreferences::handle(ctx),
|
||||||
move |_, llm_preferences, event, ctx| match event {
|
move |_, llm_preferences, event, ctx| match event {
|
||||||
LLMPreferencesEvent::UpdatedAvailableLLMs => {
|
LLMPreferencesEvent::UpdatedAvailableLLMs => {
|
||||||
let (models, default_model_id) =
|
let (mut models, default_model_id) =
|
||||||
build_onboarding_models(llm_preferences.as_ref(ctx));
|
build_onboarding_models(llm_preferences.as_ref(ctx));
|
||||||
|
let default_model_id =
|
||||||
|
apply_free_tier_default_model_override(&mut models, default_model_id, ctx);
|
||||||
onboarding_view_clone.update(ctx, |onboarding_view, ctx| {
|
onboarding_view_clone.update(ctx, |onboarding_view, ctx| {
|
||||||
onboarding_view.set_onboarding_models(models, default_model_id, ctx);
|
onboarding_view.set_onboarding_models(models, default_model_id, ctx);
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user