Skip to content

Commit d0f045c

Browse files
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>
1 parent f0c8b7f commit d0f045c

4 files changed

Lines changed: 110 additions & 4 deletions

File tree

app/src/ai/onboarding.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,15 @@ use warp_core::ui::icons::Icon;
77
use warpui::{AppContext, SingletonEntity};
88

99
use crate::auth::AuthStateProvider;
10+
use crate::experiments::FreeTierDefaultModel;
1011
use crate::workspaces::user_workspaces::UserWorkspaces;
1112

1213
use super::llms::{DisableReason, LLMInfo, LLMPreferences};
1314

15+
/// mirrors server-side model ids
16+
const AUTO_OPEN_LLM_ID: &str = "auto-open";
17+
const AUTO_COST_EFFICIENT_LLM_ID: &str = "auto-efficient";
18+
1419
impl From<&LLMInfo> for OnboardingModelInfo {
1520
fn from(llm: &LLMInfo) -> Self {
1621
Self {
@@ -36,6 +41,27 @@ pub fn build_onboarding_models(prefs: &LLMPreferences) -> (Vec<OnboardingModelIn
3641
(models, default_id)
3742
}
3843

44+
pub fn apply_free_tier_default_model_override(
45+
models: &mut [OnboardingModelInfo],
46+
server_default_id: LLMId,
47+
ctx: &mut AppContext,
48+
) -> LLMId {
49+
// server only gives back cost-efficient as a default if you're on a free or no plan
50+
// if you ARE on some sort of plan... we should respect what the server says
51+
if server_default_id != LLMId::from(AUTO_COST_EFFICIENT_LLM_ID) {
52+
return server_default_id;
53+
}
54+
let auto_open_id = LLMId::from(AUTO_OPEN_LLM_ID);
55+
let auto_open_available = models.iter().any(|m| m.id == auto_open_id);
56+
if !auto_open_available || !FreeTierDefaultModel::should_default_to_auto_open(ctx) {
57+
return server_default_id;
58+
}
59+
for m in models.iter_mut() {
60+
m.is_default = m.id == auto_open_id;
61+
}
62+
auto_open_id
63+
}
64+
3965
pub fn current_onboarding_auth_state(ctx: &AppContext) -> OnboardingAuthState {
4066
let auth_state = AuthStateProvider::as_ref(ctx).get();
4167
if auth_state.is_anonymous_or_logged_out() {
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
use super::{BucketRange, Experiment, Layer};
2+
use lazy_static::lazy_static;
3+
use std::collections::HashMap;
4+
use std::str::FromStr;
5+
use warpui::AppContext;
6+
7+
lazy_static! {
8+
pub static ref FREE_TIER_DEFAULT_MODEL_LAYER: Layer = Layer {
9+
name: "FreeTierDefaultModelLayer",
10+
hasher_seeds: (3141, 5926),
11+
traffic_allocations: HashMap::from([
12+
(FreeTierDefaultModel::AutoEfficient.get_group_id(), 50.0),
13+
(FreeTierDefaultModel::AutoOpen.get_group_id(), 50.0),
14+
]),
15+
bucket_ranges: vec![
16+
BucketRange::new(FreeTierDefaultModel::AutoEfficient, 0..500),
17+
BucketRange::new(FreeTierDefaultModel::AutoOpen, 500..1000),
18+
]
19+
};
20+
}
21+
22+
/// 50/50 A/B test of the default model surfaced to free-tier users in the
23+
/// pre-signup onboarding ("configure oz") model picker.
24+
#[derive(Debug)]
25+
pub enum FreeTierDefaultModel {
26+
/// Control: keep the existing free-tier default (auto (cost-efficient)).
27+
AutoEfficient,
28+
/// Experiment: surface auto (open-weights) as the default for free users.
29+
AutoOpen,
30+
}
31+
32+
const FREE_TIER_DEFAULT_MODEL_AUTO_EFFICIENT: &str = "AutoEfficient";
33+
const FREE_TIER_DEFAULT_MODEL_AUTO_OPEN: &str = "AutoOpen";
34+
35+
impl Experiment<FreeTierDefaultModel> for FreeTierDefaultModel {
36+
fn name() -> &'static str {
37+
"FreeTierDefaultModel"
38+
}
39+
40+
fn variant(&self) -> &'static str {
41+
match self {
42+
Self::AutoEfficient => FREE_TIER_DEFAULT_MODEL_AUTO_EFFICIENT,
43+
Self::AutoOpen => FREE_TIER_DEFAULT_MODEL_AUTO_OPEN,
44+
}
45+
}
46+
47+
fn allow_user_overrides_in_stable() -> bool {
48+
false
49+
}
50+
}
51+
52+
impl FromStr for FreeTierDefaultModel {
53+
type Err = anyhow::Error;
54+
55+
fn from_str(s: &str) -> Result<Self, Self::Err> {
56+
match s {
57+
FREE_TIER_DEFAULT_MODEL_AUTO_EFFICIENT => Ok(Self::AutoEfficient),
58+
FREE_TIER_DEFAULT_MODEL_AUTO_OPEN => Ok(Self::AutoOpen),
59+
_ => Err(anyhow::anyhow!(
60+
"Variant {} is not a valid group in FreeTierDefaultModel",
61+
s
62+
)),
63+
}
64+
}
65+
}
66+
67+
impl FreeTierDefaultModel {
68+
pub fn should_default_to_auto_open(ctx: &mut AppContext) -> bool {
69+
matches!(Self::get_group(ctx), Some(Self::AutoOpen))
70+
}
71+
}

app/src/experiments/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ mod block_onboarding_layer;
99
mod login_layer;
1010
mod rendering;
1111
pub use block_onboarding_layer::{BlockOnboarding, BLOCK_ONBOARDING_LAYER};
12+
pub use free_tier_default_model_layer::{FreeTierDefaultModel, FREE_TIER_DEFAULT_MODEL_LAYER};
1213
pub use improved_palette_search_layer::{ImprovedPaletteSearch, IMPROVED_PALETTE_SEARCH_LAYER};
1314
pub use login_layer::{AuthFlowInstructions, LOGIN_LAYER};
1415
use warp_core::user_preferences::GetUserPreferences as _;
@@ -69,6 +70,7 @@ lazy_static! {
6970
&*BLOCK_ONBOARDING_LAYER,
7071
&*rendering::LAYER,
7172
&*IMPROVED_PALETTE_SEARCH_LAYER,
73+
&*FREE_TIER_DEFAULT_MODEL_LAYER,
7274
];
7375

7476
/// Mapping of experiments to their respective layers. The mappings are built up
@@ -403,6 +405,7 @@ pub fn init(ctx: &mut AppContext) {
403405
#[path = "mod_tests.rs"]
404406
mod tests;
405407

408+
mod free_tier_default_model_layer;
406409
mod improved_palette_search_layer;
407410
#[cfg(test)]
408411
mod validation_tests;

app/src/root_view.rs

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,9 @@ use warpui::keymap::{EditableBinding, FixedBinding};
100100
use warpui::windowing::WindowManager;
101101

102102
use crate::ai::llms::{LLMPreferences, LLMPreferencesEvent};
103-
use crate::ai::onboarding::{build_onboarding_models, current_onboarding_auth_state};
103+
use crate::ai::onboarding::{
104+
apply_free_tier_default_model_override, build_onboarding_models, current_onboarding_auth_state,
105+
};
104106
use crate::pricing::{PricingInfoModel, PricingInfoModelEvent};
105107
use warp_graphql::billing::StripeSubscriptionPlan;
106108

@@ -1995,8 +1997,10 @@ impl RootView {
19951997

19961998
let themes = onboarding_theme_picker_themes();
19971999
let onboarding_view = ctx.add_typed_action_view(move |ctx| {
1998-
let llm_preferences = LLMPreferences::as_ref(ctx);
1999-
let (models, default_model_id) = build_onboarding_models(llm_preferences);
2000+
let (mut models, default_model_id) =
2001+
build_onboarding_models(LLMPreferences::as_ref(ctx));
2002+
let default_model_id =
2003+
apply_free_tier_default_model_override(&mut models, default_model_id, ctx);
20002004

20012005
let workspace_enforces_autonomy = UserWorkspaces::as_ref(ctx)
20022006
.ai_autonomy_settings()
@@ -2039,8 +2043,10 @@ impl RootView {
20392043
&LLMPreferences::handle(ctx),
20402044
move |_, llm_preferences, event, ctx| match event {
20412045
LLMPreferencesEvent::UpdatedAvailableLLMs => {
2042-
let (models, default_model_id) =
2046+
let (mut models, default_model_id) =
20432047
build_onboarding_models(llm_preferences.as_ref(ctx));
2048+
let default_model_id =
2049+
apply_free_tier_default_model_override(&mut models, default_model_id, ctx);
20442050
onboarding_view_clone.update(ctx, |onboarding_view, ctx| {
20452051
onboarding_view.set_onboarding_models(models, default_model_id, ctx);
20462052
})

0 commit comments

Comments
 (0)