From 83dda621c580e8531ff1a666a1a1a48d2992bad8 Mon Sep 17 00:00:00 2001 From: Aakash Bakhle <48802744+aakashb95@users.noreply.github.com> Date: Tue, 7 Jul 2026 16:21:51 +0530 Subject: [PATCH] feat: default new accounts to captain v2 (#14917) # Pull Request Template ## Description defaults new accounts to captain v2 ## Type of change - [x] New feature (non-breaking change which adds functionality) ## How Has This Been Tested? Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration. locally and specs ## Checklist: - [x] My code follows the style guidelines of this project - [x] I have performed a self-review of my code - [x] I have commented on my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [x] My changes generate no new warnings - [x] I have added tests that prove my fix is effective or that my feature works - [x] New and existing unit tests pass locally with my changes - [x] Any dependent changes have been merged and published in downstream modules --- .../captain/preferences_controller.rb | 7 +++ .../fields/captain_model_overrides_field.rb | 2 + enterprise/app/models/concerns/agentable.rb | 2 +- enterprise/app/models/enterprise/account.rb | 14 ++++++ .../reconcile_plan_features_service.rb | 6 +++ .../app/services/llm/base_ai_service.rb | 6 ++- enterprise/config/premium_features.yml | 1 + lib/llm/feature_router.rb | 10 +++++ .../captain/preferences_controller_spec.rb | 44 +++++++++++++++++++ .../super_admin/accounts_controller_spec.rb | 18 +++++++- spec/enterprise/models/account_spec.rb | 31 +++++++++++++ .../models/concerns/agentable_spec.rb | 8 ++++ .../handle_stripe_event_service_spec.rb | 25 +++++++++++ .../reconcile_plan_config_service_spec.rb | 20 +++++---- .../services/llm/base_ai_service_spec.rb | 8 ++++ spec/lib/llm/feature_router_spec.rb | 26 +++++++++++ spec/models/account_spec.rb | 26 +++++++++++ .../concerns/captain_featurable_spec.rb | 9 ---- 18 files changed, 243 insertions(+), 20 deletions(-) diff --git a/app/controllers/api/v1/accounts/captain/preferences_controller.rb b/app/controllers/api/v1/accounts/captain/preferences_controller.rb index 04eeff92b..482b001d6 100644 --- a/app/controllers/api/v1/accounts/captain/preferences_controller.rb +++ b/app/controllers/api/v1/accounts/captain/preferences_controller.rb @@ -66,6 +66,7 @@ class Api::V1::Accounts::Captain::PreferencesController < Api::V1::Accounts::Bas config = Llm::Models.feature_config(feature_key) route = Llm::FeatureRouter.resolve(feature: feature_key, account: Current.account) config.merge( + default: default_model_for(feature_key), enabled: account_features[feature_key] == true, model: route[:model], selected: route[:model], @@ -74,4 +75,10 @@ class Api::V1::Accounts::Captain::PreferencesController < Api::V1::Accounts::Bas ) end end + + def default_model_for(feature_key) + return Llm::FeatureRouter::CAPTAIN_V2_ASSISTANT_MODEL if feature_key == 'assistant' && Current.account.feature_enabled?('captain_integration_v2') + + Llm::Models.default_model_for(feature_key) + end end diff --git a/enterprise/app/fields/captain_model_overrides_field.rb b/enterprise/app/fields/captain_model_overrides_field.rb index a8f3fe399..c361d511a 100644 --- a/enterprise/app/fields/captain_model_overrides_field.rb +++ b/enterprise/app/fields/captain_model_overrides_field.rb @@ -29,6 +29,8 @@ class CaptainModelOverridesField < Administrate::Field::Base end def default_model_id(feature_key) + return Llm::FeatureRouter::CAPTAIN_V2_ASSISTANT_MODEL if feature_key == 'assistant' && resource.feature_enabled?('captain_integration_v2') + Llm::Models.default_model_for(feature_key) end diff --git a/enterprise/app/models/concerns/agentable.rb b/enterprise/app/models/concerns/agentable.rb index b5a6cd1b9..086deedc1 100644 --- a/enterprise/app/models/concerns/agentable.rb +++ b/enterprise/app/models/concerns/agentable.rb @@ -47,7 +47,7 @@ module Concerns::Agentable def agent_model route = Llm::FeatureRouter.resolve(feature: 'assistant', account: account) - return route[:model] if route[:source] == :account_override + return route[:model] if route[:source] == :account_override || account&.feature_enabled?('captain_integration_v2') installation_model.presence || route[:model] end diff --git a/enterprise/app/models/enterprise/account.rb b/enterprise/app/models/enterprise/account.rb index fcfe90777..62898803a 100644 --- a/enterprise/app/models/enterprise/account.rb +++ b/enterprise/app/models/enterprise/account.rb @@ -1,4 +1,9 @@ module Enterprise::Account + # Transitional marker for the Captain V1 to V2 rollout. New cloud accounts get + # this marker so plan reconciliation can enable V2 for them without upgrading + # existing paid accounts. Remove once every account is migrated to V2. + CAPTAIN_V2_DEFAULT_ELIGIBLE = 'captain_v2_default_eligible'.freeze + class << self def captain_document_sync_intervals parse_captain_document_sync_intervals(InstallationConfig.find_by(name: 'CAPTAIN_DOCUMENT_AUTO_SYNC_INTERVALS')&.value) @@ -94,6 +99,15 @@ module Enterprise::Account private + def enable_default_features + super + if ChatwootApp.self_hosted_enterprise? + enable_features('captain_integration', 'captain_integration_v2') + elsif ChatwootApp.chatwoot_cloud? + internal_attributes[CAPTAIN_V2_DEFAULT_ELIGIBLE] = true + end + end + def sync_assignment_features if feature_enabled?('assignment_v2') # Enable advanced_assignment for Business/Enterprise plans diff --git a/enterprise/app/services/enterprise/billing/reconcile_plan_features_service.rb b/enterprise/app/services/enterprise/billing/reconcile_plan_features_service.rb index fea5b7664..205bc348e 100644 --- a/enterprise/app/services/enterprise/billing/reconcile_plan_features_service.rb +++ b/enterprise/app/services/enterprise/billing/reconcile_plan_features_service.rb @@ -36,7 +36,9 @@ class Enterprise::Billing::ReconcilePlanFeaturesService def perform account.disable_features(*PREMIUM_PLAN_FEATURES) + account.disable_features('captain_integration_v2') if default_plan? account.enable_features(*current_plan_features) + account.enable_features('captain_integration_v2') if captain_v2_default_eligible? account.enable_features(*manually_managed_features) account.save! end @@ -69,4 +71,8 @@ class Enterprise::Billing::ReconcilePlanFeaturesService def manually_managed_features @manually_managed_features ||= Internal::Accounts::InternalAttributesService.new(account).manually_managed_features end + + def captain_v2_default_eligible? + !default_plan? && account.internal_attributes[Enterprise::Account::CAPTAIN_V2_DEFAULT_ELIGIBLE] == true + end end diff --git a/enterprise/app/services/llm/base_ai_service.rb b/enterprise/app/services/llm/base_ai_service.rb index bec3b5cb9..a84060775 100644 --- a/enterprise/app/services/llm/base_ai_service.rb +++ b/enterprise/app/services/llm/base_ai_service.rb @@ -34,7 +34,7 @@ class Llm::BaseAiService def setup_model route = feature_route - return @model = route[:model] if account_override_route?(route) + return @model = route[:model] if account_override_route?(route) || captain_v2_assistant? @model = @fallback_model.presence || installation_model.presence || route&.dig(:model) || DEFAULT_MODEL end @@ -49,6 +49,10 @@ class Llm::BaseAiService route&.dig(:source) == :account_override end + def captain_v2_assistant? + @llm_feature.to_s == 'assistant' && @llm_account&.feature_enabled?('captain_integration_v2') + end + def installation_model InstallationConfig.find_by(name: 'CAPTAIN_OPEN_AI_MODEL')&.value end diff --git a/enterprise/config/premium_features.yml b/enterprise/config/premium_features.yml index 282319fe7..260de1356 100644 --- a/enterprise/config/premium_features.yml +++ b/enterprise/config/premium_features.yml @@ -4,6 +4,7 @@ - sla - custom_roles - captain_integration +- captain_integration_v2 - captain_document_auto_sync - csat_review_notes - conversation_required_attributes diff --git a/lib/llm/feature_router.rb b/lib/llm/feature_router.rb index da0aa56e9..d75b06dca 100644 --- a/lib/llm/feature_router.rb +++ b/lib/llm/feature_router.rb @@ -1,6 +1,8 @@ module Llm::FeatureRouter class UnknownFeatureError < StandardError; end + CAPTAIN_V2_ASSISTANT_MODEL = 'gpt-5.2'.freeze + class << self def resolve(feature:, account: nil) feature_key = feature.to_s @@ -8,6 +10,7 @@ module Llm::FeatureRouter model = account_model_override(account, feature_key) source = model.present? ? :account_override : :default + model ||= captain_v2_assistant_model(account, feature_key) model ||= Llm::Models.default_model_for(feature_key) { @@ -25,5 +28,12 @@ module Llm::FeatureRouter return unless model return model if Llm::Models.valid_model_for?(feature_key, model) end + + def captain_v2_assistant_model(account, feature_key) + return unless feature_key == 'assistant' + return unless account&.feature_enabled?('captain_integration_v2') + + CAPTAIN_V2_ASSISTANT_MODEL + end end end diff --git a/spec/controllers/api/v1/accounts/captain/preferences_controller_spec.rb b/spec/controllers/api/v1/accounts/captain/preferences_controller_spec.rb index dfc2e4ff0..db7ca93a5 100644 --- a/spec/controllers/api/v1/accounts/captain/preferences_controller_spec.rb +++ b/spec/controllers/api/v1/accounts/captain/preferences_controller_spec.rb @@ -67,6 +67,50 @@ RSpec.describe 'Api::V1::Accounts::Captain::Preferences', type: :request do source: 'default' ) end + + it 'returns the assistant YAML default for V1 accounts' do + get "/api/v1/accounts/#{account.id}/captain/preferences", + headers: admin.create_new_auth_token, + as: :json + + expect(response).to have_http_status(:success) + expect(json_response.dig(:features, :assistant)).to include( + default: Llm::Models.default_model_for('assistant'), + selected: Llm::Models.default_model_for('assistant'), + source: 'default' + ) + end + + it 'returns GPT-5.2 as the assistant default for V2 accounts' do + account.enable_features!('captain_integration_v2') + + get "/api/v1/accounts/#{account.id}/captain/preferences", + headers: admin.create_new_auth_token, + as: :json + + expect(response).to have_http_status(:success) + expect(json_response.dig(:features, :assistant)).to include( + default: Llm::FeatureRouter::CAPTAIN_V2_ASSISTANT_MODEL, + selected: Llm::FeatureRouter::CAPTAIN_V2_ASSISTANT_MODEL, + source: 'default' + ) + end + + it 'keeps the V2 assistant default when an account override is selected' do + account.enable_features!('captain_integration_v2') + account.update!(captain_models: { 'assistant' => 'gpt-5.1' }) + + get "/api/v1/accounts/#{account.id}/captain/preferences", + headers: admin.create_new_auth_token, + as: :json + + expect(response).to have_http_status(:success) + expect(json_response.dig(:features, :assistant)).to include( + default: Llm::FeatureRouter::CAPTAIN_V2_ASSISTANT_MODEL, + selected: 'gpt-5.1', + source: 'account_override' + ) + end end end diff --git a/spec/controllers/super_admin/accounts_controller_spec.rb b/spec/controllers/super_admin/accounts_controller_spec.rb index b2f4ff405..366e178cd 100644 --- a/spec/controllers/super_admin/accounts_controller_spec.rb +++ b/spec/controllers/super_admin/accounts_controller_spec.rb @@ -65,6 +65,21 @@ RSpec.describe 'Super Admin accounts API', type: :request do expect(editor_select.at_css('option[value=""]').text.squish).to eq("Use default: #{default_model} (#{default_model_id})") end + + it 'shows the Captain V2 assistant default in the model selector', if: ChatwootApp.enterprise? do + account.enable_features!('captain_integration_v2') + sign_in(super_admin, scope: :super_admin) + + get "/super_admin/accounts/#{account.id}/edit" + + document = Nokogiri::HTML(response.body) + assistant_select = document.at_css('select[name="account[captain_models][assistant]"]') + default_model_id = Llm::FeatureRouter::CAPTAIN_V2_ASSISTANT_MODEL + default_model = Llm::Models.model_config(default_model_id)['display_name'] + + expect(response).to have_http_status(:success) + expect(assistant_select.at_css('option[value=""]').text.squish).to eq("Use default: #{default_model} (#{default_model_id})") + end end end @@ -97,6 +112,7 @@ RSpec.describe 'Super Admin accounts API', type: :request do it 'rejects invalid Captain model overrides' do sign_in(super_admin, scope: :super_admin) + existing_captain_models = account.captain_models patch "/super_admin/accounts/#{account.id}", params: { @@ -112,7 +128,7 @@ RSpec.describe 'Super Admin accounts API', type: :request do expect(response).to have_http_status(:unprocessable_entity) expect(response.body).to include('not a valid model for label_suggestion') - expect(account.reload.captain_models).to be_nil + expect(account.reload.captain_models).to eq(existing_captain_models) end end end diff --git a/spec/enterprise/models/account_spec.rb b/spec/enterprise/models/account_spec.rb index c69a83256..03f3b8d37 100644 --- a/spec/enterprise/models/account_spec.rb +++ b/spec/enterprise/models/account_spec.rb @@ -222,6 +222,37 @@ RSpec.describe Account, type: :model do end end + describe 'default features' do + before do + InstallationConfig.find_or_initialize_by(name: 'ACCOUNT_LEVEL_FEATURE_DEFAULTS').update!( + value: Featurable::FEATURE_LIST, + locked: true + ) + end + + it 'enables Captain V2 for new self-hosted enterprise accounts' do + allow(ChatwootApp).to receive(:self_hosted_enterprise?).and_return(true) + + account = create(:account) + + expect(account).to be_feature_enabled('captain_integration') + expect(account).to be_feature_enabled('captain_integration_v2') + expect(account.captain_preferences[:models]['assistant']).to eq('gpt-5.2') + expect(account.captain_models).to be_nil + end + + it 'marks new cloud accounts as eligible for the Captain V2 paid-plan default' do + allow(ChatwootApp).to receive(:self_hosted_enterprise?).and_return(false) + allow(ChatwootApp).to receive(:chatwoot_cloud?).and_return(true) + + account = create(:account) + + expect(account.internal_attributes[Enterprise::Account::CAPTAIN_V2_DEFAULT_ELIGIBLE]).to be true + expect(account).not_to be_feature_enabled('captain_integration') + expect(account).not_to be_feature_enabled('captain_integration_v2') + end + end + describe 'captain document sync cadence' do let(:account) { create(:account) } diff --git a/spec/enterprise/models/concerns/agentable_spec.rb b/spec/enterprise/models/concerns/agentable_spec.rb index d179393c9..f2145ff85 100644 --- a/spec/enterprise/models/concerns/agentable_spec.rb +++ b/spec/enterprise/models/concerns/agentable_spec.rb @@ -179,6 +179,14 @@ RSpec.describe Concerns::Agentable do expect(dummy_instance.send(:agent_model)).to eq('gpt-4.1-nano') end + it 'returns the Captain V2 default when Captain V2 is enabled' do + create(:installation_config, name: 'CAPTAIN_OPEN_AI_MODEL', value: 'gpt-4.1-nano') + account.enable_features!('captain_integration_v2') + + expect(dummy_instance.send(:agent_model)).to eq('gpt-5.2') + expect(account.reload.captain_models).to be_nil + end + it 'returns the assistant feature default model when account is nil' do agent = dummy_class.new(account: nil) diff --git a/spec/enterprise/services/enterprise/billing/handle_stripe_event_service_spec.rb b/spec/enterprise/services/enterprise/billing/handle_stripe_event_service_spec.rb index 3223efa86..5116e1ec3 100644 --- a/spec/enterprise/services/enterprise/billing/handle_stripe_event_service_spec.rb +++ b/spec/enterprise/services/enterprise/billing/handle_stripe_event_service_spec.rb @@ -175,6 +175,7 @@ describe Enterprise::Billing::HandleStripeEventService do described_class::STARTUP_PLAN_FEATURES.each do |feature| account.enable_features(feature) end + account.enable_features('captain_integration_v2') account.enable_features(*described_class::BUSINESS_PLAN_FEATURES) account.enable_features(*described_class::ENTERPRISE_PLAN_FEATURES) account.save! @@ -193,6 +194,7 @@ describe Enterprise::Billing::HandleStripeEventService do all_features.each do |feature| expect(account).not_to be_feature_enabled(feature) end + expect(account).not_to be_feature_enabled('captain_integration_v2') end end @@ -218,6 +220,29 @@ describe Enterprise::Billing::HandleStripeEventService do expect(account).not_to be_feature_enabled(feature) end end + + it 'does not enable Captain V2 for existing paid accounts during reconciliation' do + allow(subscription).to receive(:[]).with('plan') + .and_return({ 'id' => 'test', 'product' => 'plan_id_startups', 'name' => 'Startups' }) + + stripe_event_service.new.perform(event: event) + + expect(account.reload).not_to be_feature_enabled('captain_integration_v2') + end + + it 'enables Captain V2 for new cloud accounts marked as default eligible' do + account.update!( + internal_attributes: account.internal_attributes.merge( + Enterprise::Account::CAPTAIN_V2_DEFAULT_ELIGIBLE => true + ) + ) + allow(subscription).to receive(:[]).with('plan') + .and_return({ 'id' => 'test', 'product' => 'plan_id_startups', 'name' => 'Startups' }) + + stripe_event_service.new.perform(event: event) + + expect(account.reload).to be_feature_enabled('captain_integration_v2') + end end context 'with Business plan' do diff --git a/spec/enterprise/services/internal/reconcile_plan_config_service_spec.rb b/spec/enterprise/services/internal/reconcile_plan_config_service_spec.rb index 63a318415..76e9e3602 100644 --- a/spec/enterprise/services/internal/reconcile_plan_config_service_spec.rb +++ b/spec/enterprise/services/internal/reconcile_plan_config_service_spec.rb @@ -11,14 +11,16 @@ RSpec.describe Internal::ReconcilePlanConfigService do it 'disables the premium features for accounts' do account = create(:account) - account.enable_features!('disable_branding', 'audit_logs', 'captain_integration') + account.enable_features!('disable_branding', 'audit_logs', 'captain_integration', 'captain_integration_v2') account_with_captain = create(:account) - account_with_captain.enable_features!('captain_integration') + account_with_captain.enable_features!('captain_integration', 'captain_integration_v2') disable_branding_account = create(:account) disable_branding_account.enable_features!('disable_branding') service.perform - expect(account.reload.enabled_features.keys).not_to include('captain_integration', 'disable_branding', 'audit_logs') - expect(account_with_captain.reload.enabled_features.keys).not_to include('captain_integration') + expect(account.reload.enabled_features.keys).not_to include( + 'captain_integration', 'captain_integration_v2', 'disable_branding', 'audit_logs' + ) + expect(account_with_captain.reload.enabled_features.keys).not_to include('captain_integration', 'captain_integration_v2') expect(disable_branding_account.reload.enabled_features.keys).not_to include('disable_branding') end @@ -56,14 +58,16 @@ RSpec.describe Internal::ReconcilePlanConfigService do it 'does not disable the premium features for accounts' do account = create(:account) - account.enable_features!('disable_branding', 'audit_logs', 'captain_integration') + account.enable_features!('disable_branding', 'audit_logs', 'captain_integration', 'captain_integration_v2') account_with_captain = create(:account) - account_with_captain.enable_features!('captain_integration') + account_with_captain.enable_features!('captain_integration', 'captain_integration_v2') disable_branding_account = create(:account) disable_branding_account.enable_features!('disable_branding') service.perform - expect(account.reload.enabled_features.keys).to include('captain_integration', 'disable_branding', 'audit_logs') - expect(account_with_captain.reload.enabled_features.keys).to include('captain_integration') + expect(account.reload.enabled_features.keys).to include( + 'captain_integration', 'captain_integration_v2', 'disable_branding', 'audit_logs' + ) + expect(account_with_captain.reload.enabled_features.keys).to include('captain_integration', 'captain_integration_v2') expect(disable_branding_account.reload.enabled_features.keys).to include('disable_branding') end diff --git a/spec/enterprise/services/llm/base_ai_service_spec.rb b/spec/enterprise/services/llm/base_ai_service_spec.rb index f18752485..d4c07d336 100644 --- a/spec/enterprise/services/llm/base_ai_service_spec.rb +++ b/spec/enterprise/services/llm/base_ai_service_spec.rb @@ -30,6 +30,14 @@ RSpec.describe Llm::BaseAiService do expect(described_class.new(feature: 'assistant', account: account).model).to eq('gpt-4.1-nano') end + it 'uses the Captain V2 assistant default ahead of the installation model' do + create(:installation_config, name: 'CAPTAIN_OPEN_AI_MODEL', value: 'gpt-4.1-nano') + account.enable_features!('captain_integration_v2') + + expect(described_class.new(feature: 'assistant', account: account).model).to eq('gpt-5.2') + expect(account.reload.captain_models).to be_nil + end + it 'uses the feature default when feature context has no account override or installation model' do expect(described_class.new(feature: 'assistant', account: account).model).to eq(Llm::Models.default_model_for('assistant')) end diff --git a/spec/lib/llm/feature_router_spec.rb b/spec/lib/llm/feature_router_spec.rb index e0eb4afa0..ea2b9f91f 100644 --- a/spec/lib/llm/feature_router_spec.rb +++ b/spec/lib/llm/feature_router_spec.rb @@ -30,6 +30,32 @@ RSpec.describe Llm::FeatureRouter do ) end + it 'resolves GPT-5.2 as the assistant default when Captain V2 is enabled without storing an account override' do + account.enable_features!('captain_integration_v2') + + resolved = described_class.resolve(feature: 'assistant', account: account) + + expect(resolved).to include( + feature: 'assistant', + provider: 'openai', + model: 'gpt-5.2', + source: :default + ) + expect(account.reload.captain_models).to be_nil + end + + it 'keeps account model overrides ahead of the Captain V2 default' do + account.enable_features!('captain_integration_v2') + account.update!(captain_models: { 'assistant' => 'gpt-5.1' }) + + resolved = described_class.resolve(feature: 'assistant', account: account) + + expect(resolved).to include( + model: 'gpt-5.1', + source: :account_override + ) + end + it 'falls back to the feature default when the account override is invalid' do account.captain_models = { 'editor' => 'invalid-model' } diff --git a/spec/models/account_spec.rb b/spec/models/account_spec.rb index 56bd41f7c..e3bc636d1 100644 --- a/spec/models/account_spec.rb +++ b/spec/models/account_spec.rb @@ -50,6 +50,21 @@ RSpec.describe Account do end end + describe 'captain defaults for new accounts' do + it 'does not store Captain model overrides or enable premium Captain features' do + InstallationConfig.find_or_initialize_by(name: 'ACCOUNT_LEVEL_FEATURE_DEFAULTS').update!( + value: Featurable::FEATURE_LIST, + locked: true + ) + + account = create(:account) + + expect(account).not_to be_feature_enabled('captain_integration') + expect(account).not_to be_feature_enabled('captain_integration_v2') + expect(account.captain_models).to be_nil + end + end + describe 'conversation unread counts feature flag' do let(:account) { create(:account) } let(:inbox) { create(:inbox, account: account) } @@ -337,6 +352,10 @@ RSpec.describe Account do let(:account) { create(:account) } describe 'with no saved preferences' do + before do + account.update!(captain_models: nil) + end + it 'returns defaults from llm.yml' do prefs = account.captain_preferences @@ -346,6 +365,13 @@ RSpec.describe Account do expect(prefs[:models][feature]).to eq(Llm::Models.default_model_for(feature)) end end + + it 'returns GPT-5.2 for assistant when Captain V2 is enabled' do + account.enable_features!('captain_integration_v2') + + expect(account.captain_preferences[:models]['assistant']).to eq('gpt-5.2') + expect(account.reload.captain_models).to be_nil + end end describe 'with saved model preferences' do diff --git a/spec/models/concerns/captain_featurable_spec.rb b/spec/models/concerns/captain_featurable_spec.rb index 7221ad055..0fb145142 100644 --- a/spec/models/concerns/captain_featurable_spec.rb +++ b/spec/models/concerns/captain_featurable_spec.rb @@ -58,15 +58,6 @@ RSpec.describe CaptainFeaturable do end describe 'model accessor methods' do - context 'when no models are explicitly configured' do - it 'returns default models for all features' do - Llm::Models.feature_keys.each do |feature_key| - expected_default = Llm::Models.default_model_for(feature_key) - expect(account.send("captain_#{feature_key}_model")).to eq(expected_default) - end - end - end - context 'when models are explicitly configured' do before do account.update!(captain_models: {