From c3fe8d0cc4d4818290bb0e50d4e5464748965fd6 Mon Sep 17 00:00:00 2001 From: David O'Regan Date: Thu, 25 Jan 2024 14:18:53 +0100 Subject: [PATCH 01/21] Make chat available to Premium users --- doc/development/ai_features/duo_chat.md | 2 +- ee/app/models/gitlab_subscriptions/features.rb | 2 +- ee/spec/features/duo_chat_spec.rb | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/development/ai_features/duo_chat.md b/doc/development/ai_features/duo_chat.md index b52ac24b7184bc..def0c010ed564b 100644 --- a/doc/development/ai_features/duo_chat.md +++ b/doc/development/ai_features/duo_chat.md @@ -216,7 +216,7 @@ GitLab Duo Chat is enabled in the [Staging](https://staging.gitlab.com) and [Staging Ref](https://staging-ref.gitlab.com/) GitLab environments. Because GitLab Duo Chat is currently only available to members of groups in the -Ultimate tier, Staging Ref may be an easier place to test changes as a GitLab +Premium tier, Staging Ref may be an easier place to test changes as a GitLab team member because [you can make yourself an instance Admin in Staging Ref](https://handbook.gitlab.com/handbook/engineering/infrastructure/environments/staging-ref/#admin-access) and, as an Admin, easily create licensed groups for testing. diff --git a/ee/app/models/gitlab_subscriptions/features.rb b/ee/app/models/gitlab_subscriptions/features.rb index 583ee36c96dce4..b7822a95de4eee 100644 --- a/ee/app/models/gitlab_subscriptions/features.rb +++ b/ee/app/models/gitlab_subscriptions/features.rb @@ -73,6 +73,7 @@ class Features ].freeze PREMIUM_FEATURES = %i[ + ai_chat adjourned_deletion_for_projects_and_groups admin_audit_log auditor_user @@ -169,7 +170,6 @@ class Features ].freeze ULTIMATE_FEATURES = %i[ - ai_chat ai_config_chat ai_features ai_git_command diff --git a/ee/spec/features/duo_chat_spec.rb b/ee/spec/features/duo_chat_spec.rb index 1e59abcd356e2b..95c702f206199c 100644 --- a/ee/spec/features/duo_chat_spec.rb +++ b/ee/spec/features/duo_chat_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' RSpec.describe 'Duo Chat', :js, :saas, :clean_gitlab_redis_cache, feature_category: :duo_chat do - let_it_be_with_reload(:group) { create(:group_with_plan, plan: :ultimate_plan) } + let_it_be_with_reload(:group) { create(:group_with_plan, plan: :premium_plan) } let_it_be(:user) { create(:user) } before_all do -- GitLab From 1560c83d48f0892beaf45c0e9d5d46b871e01142 Mon Sep 17 00:00:00 2001 From: Tetiana Chupryna Date: Mon, 29 Jan 2024 11:33:06 +0100 Subject: [PATCH 02/21] Move ai_tanuki_bot check to premium --- ee/app/models/gitlab_subscriptions/features.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ee/app/models/gitlab_subscriptions/features.rb b/ee/app/models/gitlab_subscriptions/features.rb index b7822a95de4eee..e119b7d1022d43 100644 --- a/ee/app/models/gitlab_subscriptions/features.rb +++ b/ee/app/models/gitlab_subscriptions/features.rb @@ -74,6 +74,7 @@ class Features PREMIUM_FEATURES = %i[ ai_chat + ai_tanuki_bot adjourned_deletion_for_projects_and_groups admin_audit_log auditor_user @@ -173,7 +174,6 @@ class Features ai_config_chat ai_features ai_git_command - ai_tanuki_bot ai_analyze_ci_job_failure ai_agents api_discovery -- GitLab From 8d342a9b864c6ecb11936eca1ccb9be98a60d6db Mon Sep 17 00:00:00 2001 From: Tetiana Chupryna Date: Mon, 29 Jan 2024 16:54:28 +0100 Subject: [PATCH 03/21] Replace `ai_tanuki_bot` because it's outdated --- ee/app/models/ee/namespace.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ee/app/models/ee/namespace.rb b/ee/app/models/ee/namespace.rb index 5c4680755e8db2..7c0feaf1c772e9 100644 --- a/ee/app/models/ee/namespace.rb +++ b/ee/app/models/ee/namespace.rb @@ -105,7 +105,7 @@ module Namespace end scope :with_ai_supported_plan, -> do - plan_names = GitlabSubscriptions::Features.saas_plans_with_feature(:ai_tanuki_bot) + plan_names = GitlabSubscriptions::Features.saas_plans_with_feature(:ai_features) joins("LEFT OUTER JOIN \"gitlab_subscriptions\" ON \"gitlab_subscriptions\".\"namespace_id\" = \"namespaces\".traversal_ids[1]") .joins("LEFT OUTER JOIN \"plans\" ON \"plans\".\"id\" = \"gitlab_subscriptions\".\"hosted_plan_id\"") -- GitLab From 6962213a5e46d56a0f5aa22b2ce6bc4c912db1bd Mon Sep 17 00:00:00 2001 From: Tetiana Chupryna Date: Mon, 29 Jan 2024 17:51:53 +0100 Subject: [PATCH 04/21] Decouple chat permissions --- ee/app/models/ee/user.rb | 9 +++++++++ ee/app/policies/ee/global_policy.rb | 2 +- ee/spec/policies/global_policy_spec.rb | 2 +- ee/spec/services/llm/chat_service_spec.rb | 2 +- 4 files changed, 12 insertions(+), 3 deletions(-) diff --git a/ee/app/models/ee/user.rb b/ee/app/models/ee/user.rb index 7dadf830de06a6..bc0e3801ebd0b9 100644 --- a/ee/app/models/ee/user.rb +++ b/ee/app/models/ee/user.rb @@ -20,6 +20,9 @@ module User GROUP_WITH_AI_ENABLED_CACHE_PERIOD = 1.hour GROUP_WITH_AI_ENABLED_CACHE_KEY = 'group_with_ai_enabled' + GROUP_WITH_AI_CHAT_ENABLED_CACHE_PERIOD = 1.hour + GROUP_WITH_AI_CHAT_ENABLED_CACHE_KEY = 'group_with_ai_chat_enabled' + CODE_SUGGESTIONS_ADD_ON_CACHE_KEY = 'user-%{user_id}-code-suggestions-add-on-cache' CODE_SUGGESTIONS_ENABLED_NAMESPACES_IDS_CACHE_KEY = 'user-%{user_id}-code-suggestions-enabled-namespaces-ids-cache' @@ -648,6 +651,12 @@ def any_group_with_ai_available? end end + def any_group_with_ai_chat_available? + Rails.cache.fetch(['users', id, GROUP_WITH_AI_CHAT_ENABLED_CACHE_KEY], expires_in: GROUP_WITH_AI_CHAT_ENABLED_CACHE_PERIOD) do + member_namespaces.namespace_settings_with_ai_features_enabled.with_feature_available_in_plan(:ai_chat).any? + end + end + def registration_audit_details { id: id, diff --git a/ee/app/policies/ee/global_policy.rb b/ee/app/policies/ee/global_policy.rb index 03ab6309b50467..2d41740d9af011 100644 --- a/ee/app/policies/ee/global_policy.rb +++ b/ee/app/policies/ee/global_policy.rb @@ -93,7 +93,7 @@ module GlobalPolicy next false unless @user next true unless ::Gitlab::Saas.feature_available?(:duo_chat_on_saas) - user.any_group_with_ai_available? + user.any_group_with_ai_chat_available? end condition(:user_belongs_to_paid_namespace) do diff --git a/ee/spec/policies/global_policy_spec.rb b/ee/spec/policies/global_policy_spec.rb index eed96636cc0df3..1f2a9e772c8d2e 100644 --- a/ee/spec/policies/global_policy_spec.rb +++ b/ee/spec/policies/global_policy_spec.rb @@ -663,7 +663,7 @@ let_it_be_with_reload(:current_user) { create(:user) } context 'when on .org or .com', :saas do - let_it_be_with_reload(:group) { create(:group_with_plan, plan: :ultimate_plan) } + let_it_be_with_reload(:group) { create(:group_with_plan, plan: :premium_plan) } context 'when user is member of a group' do before do diff --git a/ee/spec/services/llm/chat_service_spec.rb b/ee/spec/services/llm/chat_service_spec.rb index 741f0c49cb5367..ff6be4ff82444a 100644 --- a/ee/spec/services/llm/chat_service_spec.rb +++ b/ee/spec/services/llm/chat_service_spec.rb @@ -69,7 +69,7 @@ end context 'for saas', :saas do - let_it_be_with_reload(:group) { create(:group_with_plan, plan: :ultimate_plan) } + let_it_be_with_reload(:group) { create(:group_with_plan, plan: :premium_plan) } let_it_be(:project) { create(:project, group: group) } let_it_be(:issue) { create(:issue, project: project) } -- GitLab From 58180b2d8d5fb40fb53a64b84b06a839608d4771 Mon Sep 17 00:00:00 2001 From: Tetiana Chupryna Date: Wed, 31 Jan 2024 12:18:32 +0100 Subject: [PATCH 05/21] Allow experimental features for chat The result of this commit is ability to check experimental settings in premium --- ee/app/models/ee/namespace_setting.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ee/app/models/ee/namespace_setting.rb b/ee/app/models/ee/namespace_setting.rb index 1b439605244e65..8572a083fe35dd 100644 --- a/ee/app/models/ee/namespace_setting.rb +++ b/ee/app/models/ee/namespace_setting.rb @@ -79,7 +79,7 @@ def unique_project_download_limit_alertlist def experiment_settings_allowed? namespace.root? && ::Gitlab::CurrentSettings.should_check_namespace_plan? && - namespace.feature_available?(:experimental_features) + (namespace.feature_available?(:experimental_features) || namespace.feature_available?(:ai_chat)) end def product_analytics_settings_allowed? -- GitLab From 3964a54ac270171c5f56aa93a52ba1f92c5da25b Mon Sep 17 00:00:00 2001 From: Tetiana Chupryna Date: Wed, 31 Jan 2024 12:20:27 +0100 Subject: [PATCH 06/21] Update tests for chat in premium --- ee/spec/features/duo_chat_spec.rb | 2 +- ee/spec/lib/gitlab/llm/completions/chat_real_requests_spec.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ee/spec/features/duo_chat_spec.rb b/ee/spec/features/duo_chat_spec.rb index 95c702f206199c..075ee534873578 100644 --- a/ee/spec/features/duo_chat_spec.rb +++ b/ee/spec/features/duo_chat_spec.rb @@ -11,7 +11,7 @@ end before do - stub_licensed_features(ai_tanuki_bot: true) + stub_licensed_features(ai_chat: true) stub_application_setting(anthropic_api_key: 'somekey') end diff --git a/ee/spec/lib/gitlab/llm/completions/chat_real_requests_spec.rb b/ee/spec/lib/gitlab/llm/completions/chat_real_requests_spec.rb index f2c3bbad1cd33a..7035574ca7159b 100644 --- a/ee/spec/lib/gitlab/llm/completions/chat_real_requests_spec.rb +++ b/ee/spec/lib/gitlab/llm/completions/chat_real_requests_spec.rb @@ -10,7 +10,7 @@ describe 'real requests', :real_ai_request, :zeroshot_executor, :saas do using RSpec::Parameterized::TableSyntax - let_it_be_with_reload(:group) { create(:group_with_plan, :public, plan: :ultimate_plan) } + let_it_be_with_reload(:group) { create(:group_with_plan, :public, plan: :premium_plan) } let_it_be(:project) { create(:project, :repository, group: group) } let(:response_service_double) { instance_double(::Gitlab::Llm::ResponseService) } -- GitLab From b819fca15490888ae46a80ab4efe8ae07e946b89 Mon Sep 17 00:00:00 2001 From: Tetiana Chupryna Date: Wed, 31 Jan 2024 17:34:27 +0100 Subject: [PATCH 07/21] Chat should depend only on :ai_chat feature --- ee/spec/lib/gitlab/llm/completions/chat_real_requests_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ee/spec/lib/gitlab/llm/completions/chat_real_requests_spec.rb b/ee/spec/lib/gitlab/llm/completions/chat_real_requests_spec.rb index 7035574ca7159b..f01de424aa6938 100644 --- a/ee/spec/lib/gitlab/llm/completions/chat_real_requests_spec.rb +++ b/ee/spec/lib/gitlab/llm/completions/chat_real_requests_spec.rb @@ -40,7 +40,7 @@ # TODO: We can't run this QA spec with AI Gateway because the service is not available in test jobs. # See https://gitlab.com/gitlab-org/gitlab/-/issues/434445 for more information. stub_feature_flags(gitlab_duo_chat_requests_to_ai_gateway: false, ai_claude_2_1: true) - stub_licensed_features(ai_features: true, ai_tanuki_bot: true, experimental_features: true) + stub_licensed_features(ai_chat: true) stub_ee_application_setting(should_check_namespace_plan: true) group.namespace_settings.update!(experiment_features_enabled: true) allow(response_service_double).to receive(:execute).at_least(:once) @@ -335,7 +335,7 @@ end before do - stub_licensed_features(ai_features: true, ai_tanuki_bot: true, epics: true, experimental_features: true) + stub_licensed_features(ai_chat: true) end context 'with predefined tools' do -- GitLab From d8e94c5aa4e1fd9c3d68355fbba17d3d19fda2c6 Mon Sep 17 00:00:00 2001 From: Tetiana Chupryna Date: Wed, 31 Jan 2024 17:59:11 +0100 Subject: [PATCH 08/21] Decouple license check for the chat --- ee/lib/gitlab/llm/stage_check.rb | 3 ++- ee/spec/lib/gitlab/llm/tanuki_bot_spec.rb | 2 +- ee/spec/policies/project_policy_spec.rb | 4 ++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/ee/lib/gitlab/llm/stage_check.rb b/ee/lib/gitlab/llm/stage_check.rb index a31860df3877d3..424d1c2d2cd92a 100644 --- a/ee/lib/gitlab/llm/stage_check.rb +++ b/ee/lib/gitlab/llm/stage_check.rb @@ -43,7 +43,8 @@ def available_on_beta_stage?(container, feature) root_ancestor = container&.root_ancestor return false unless root_ancestor&.experiment_features_enabled - root_ancestor.licensed_feature_available?(:ai_features) + licensed_feature = feature == :chat ? :ai_chat : :ai_features + root_ancestor.licensed_feature_available?(licensed_feature) end end end diff --git a/ee/spec/lib/gitlab/llm/tanuki_bot_spec.rb b/ee/spec/lib/gitlab/llm/tanuki_bot_spec.rb index b662723c413d4e..da81380b041348 100644 --- a/ee/spec/lib/gitlab/llm/tanuki_bot_spec.rb +++ b/ee/spec/lib/gitlab/llm/tanuki_bot_spec.rb @@ -36,7 +36,7 @@ describe '.enabled_for?', :use_clean_rails_redis_caching do context 'for saas', :saas do - let_it_be_with_reload(:group) { create(:group_with_plan, plan: :ultimate_plan) } + let_it_be_with_reload(:group) { create(:group_with_plan, plan: :premium_plan) } context 'when user present and container is not present' do where(:ai_duo_chat_switch_enabled, :ai_features_available_to_user, :result) do diff --git a/ee/spec/policies/project_policy_spec.rb b/ee/spec/policies/project_policy_spec.rb index 4ad932606e9f43..ce637a5a321071 100644 --- a/ee/spec/policies/project_policy_spec.rb +++ b/ee/spec/policies/project_policy_spec.rb @@ -3395,7 +3395,7 @@ def create_member_role(member, abilities = member_role_abilities) subject { described_class.new(current_user, project) } context 'when on SaaS instance', :saas do - let_it_be_with_reload(:group) { create(:group_with_plan, plan: :ultimate_plan) } + let_it_be_with_reload(:group) { create(:group_with_plan, plan: :premium_plan) } context 'when container is a group with AI enabled' do include_context 'with ai features enabled for group' @@ -3407,7 +3407,7 @@ def create_member_role(member, abilities = member_role_abilities) it { is_expected.to be_allowed(:access_duo_chat) } - context 'when the group does not have an Ultimate SaaS license' do + context 'when the group does not have an Premium SaaS license' do let_it_be(:group) { create(:group) } it { is_expected.to be_disallowed(:access_duo_chat) } -- GitLab From 1e2dd01e3a9de83d0f0ace01170a89281a167071 Mon Sep 17 00:00:00 2001 From: Tetiana Chupryna Date: Thu, 1 Feb 2024 20:08:54 +0100 Subject: [PATCH 09/21] Update tests with chat in premium --- ee/spec/lib/gitlab/llm/stage_check_spec.rb | 42 +++++++++++++++++++ ee/spec/policies/project_policy_spec.rb | 10 ++--- ...atures_enabled_for_group_shared_context.rb | 10 +++++ 3 files changed, 57 insertions(+), 5 deletions(-) diff --git a/ee/spec/lib/gitlab/llm/stage_check_spec.rb b/ee/spec/lib/gitlab/llm/stage_check_spec.rb index 22de33d8a62ca3..461780244283f0 100644 --- a/ee/spec/lib/gitlab/llm/stage_check_spec.rb +++ b/ee/spec/lib/gitlab/llm/stage_check_spec.rb @@ -67,5 +67,47 @@ end end end + + context 'with premium plan' do + let_it_be(:root_group) { create(:group_with_plan, :private, plan: :premium_plan) } + + before do + stub_ee_application_setting(should_check_namespace_plan: true) + stub_licensed_features(ai_chat: true) + root_group.namespace_settings.update!(experiment_features_enabled: true) + stub_const("#{described_class}::BETA_FEATURES", [feature_name]) + end + + it 'returns false' do + expect(described_class.available?(project, feature_name)).to eq(false) + end + end + end + + context 'with chat feature' do + let(:feature_name) { :chat } + let_it_be(:root_group) { create(:group_with_plan, :private, plan: :premium_plan) } + let_it_be(:group) { create(:group, :private, parent: root_group) } + let_it_be_with_reload(:project) { create(:project, group: group) } + + subject { described_class.available?(container, feature_name) } + + before do + stub_ee_application_setting(should_check_namespace_plan: true) + stub_licensed_features(ai_chat: true) + root_group.namespace_settings.update!(experiment_features_enabled: true) + end + + context 'with project' do + let(:container) { project } + + it { is_expected.to eq(true) } + end + + context 'with group' do + let(:container) { group } + + it { is_expected.to eq(true) } + end end end diff --git a/ee/spec/policies/project_policy_spec.rb b/ee/spec/policies/project_policy_spec.rb index ce637a5a321071..05507381b362fe 100644 --- a/ee/spec/policies/project_policy_spec.rb +++ b/ee/spec/policies/project_policy_spec.rb @@ -3398,7 +3398,7 @@ def create_member_role(member, abilities = member_role_abilities) let_it_be_with_reload(:group) { create(:group_with_plan, plan: :premium_plan) } context 'when container is a group with AI enabled' do - include_context 'with ai features enabled for group' + include_context 'with ai chat enabled for group on SaaS' context 'when user is a member of the group' do before do @@ -3417,7 +3417,7 @@ def create_member_role(member, abilities = member_role_abilities) context 'when user is not a member of the parent group' do context 'when the user has AI enabled via another group' do it 'is disallowed' do - allow(current_user).to receive(:any_group_with_ai_available?).and_return(true) + allow(current_user).to receive(:any_group_with_ai_chat_available?).and_return(true) is_expected.to be_disallowed(:access_duo_chat) end @@ -3431,7 +3431,7 @@ def create_member_role(member, abilities = member_role_abilities) context 'when the user has AI enabled through parent group' do it 'is allowed' do - allow(current_user).to receive(:any_group_with_ai_available?).and_return(true) + allow(current_user).to receive(:any_group_with_ai_chat_available?).and_return(true) is_expected.to be_allowed(:access_duo_chat) end @@ -3442,14 +3442,14 @@ def create_member_role(member, abilities = member_role_abilities) context 'when group has not AI enabled' do context 'when user has AI enabled' do before do - allow(current_user).to receive(:any_group_with_ai_available?).and_return(true) + allow(current_user).to receive(:any_group_with_ai_chat_available?).and_return(true) end context 'when container is a group' do include_context 'with experiment features disabled for group' it 'returns false' do - allow(current_user).to receive(:any_group_with_ai_available?).and_return(true) + allow(current_user).to receive(:any_group_with_ai_chat_available?).and_return(true) is_expected.to be_disallowed(:access_duo_chat) end diff --git a/ee/spec/support/shared_contexts/services/llm/ai_features_enabled_for_group_shared_context.rb b/ee/spec/support/shared_contexts/services/llm/ai_features_enabled_for_group_shared_context.rb index 1ddc3402db1338..bee2c2d9524b02 100644 --- a/ee/spec/support/shared_contexts/services/llm/ai_features_enabled_for_group_shared_context.rb +++ b/ee/spec/support/shared_contexts/services/llm/ai_features_enabled_for_group_shared_context.rb @@ -43,3 +43,13 @@ stub_licensed_features(ai_chat: true) end end + +RSpec.shared_context 'with ai chat enabled for group on SaaS' do + before do + allow(Gitlab).to receive(:org_or_com?).and_return(true) + stub_ee_application_setting(should_check_namespace_plan: true) + stub_licensed_features(ai_chat: true) + allow(group.namespace_settings).to receive(:experiment_settings_allowed?).and_return(true) + group.namespace_settings.reload.update!(experiment_features_enabled: true) + end +end -- GitLab From acadda1744bd81da1aa12a2dd85f20e62a344dcd Mon Sep 17 00:00:00 2001 From: Tetiana Chupryna Date: Fri, 2 Feb 2024 12:35:42 +0100 Subject: [PATCH 10/21] Fix group policy tests for duo chat in premium --- ee/spec/policies/group_policy_spec.rb | 8 ++++---- .../ai_features_enabled_for_group_shared_context.rb | 10 ++++++++++ 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/ee/spec/policies/group_policy_spec.rb b/ee/spec/policies/group_policy_spec.rb index 422006045edf30..e5cc77069f39f9 100644 --- a/ee/spec/policies/group_policy_spec.rb +++ b/ee/spec/policies/group_policy_spec.rb @@ -2957,10 +2957,10 @@ def expect_private_group_permissions_as_if_non_member subject { described_class.new(current_user, group) } context 'when on SaaS instance', :saas do - let_it_be_with_reload(:group) { create(:group_with_plan, plan: :ultimate_plan) } + let_it_be_with_reload(:group) { create(:group_with_plan, plan: :premium_plan) } context 'when container is a group with AI enabled' do - include_context 'with ai features enabled for group' + include_context 'with ai chat enabled for group on SaaS' context 'when user is a member of the group' do before do @@ -2969,7 +2969,7 @@ def expect_private_group_permissions_as_if_non_member it { is_expected.to be_allowed(:access_duo_chat) } - context 'when the group does not have an Ultimate SaaS license' do + context 'when the group does not have an Premium SaaS license' do let_it_be(:group) { create(:group) } it { is_expected.to be_disallowed(:access_duo_chat) } @@ -2992,7 +2992,7 @@ def expect_private_group_permissions_as_if_non_member end context 'when container is a group' do - include_context 'with experiment features disabled for group' + include_context 'with ai features disabled and licensed chat for group on SaaS' it 'returns false' do allow(current_user).to receive(:any_group_with_ai_available?).and_return(true) diff --git a/ee/spec/support/shared_contexts/services/llm/ai_features_enabled_for_group_shared_context.rb b/ee/spec/support/shared_contexts/services/llm/ai_features_enabled_for_group_shared_context.rb index bee2c2d9524b02..8cd52ec2aa482d 100644 --- a/ee/spec/support/shared_contexts/services/llm/ai_features_enabled_for_group_shared_context.rb +++ b/ee/spec/support/shared_contexts/services/llm/ai_features_enabled_for_group_shared_context.rb @@ -53,3 +53,13 @@ group.namespace_settings.reload.update!(experiment_features_enabled: true) end end + +RSpec.shared_context 'with ai features disabled and licensed chat for group on SaaS' do + before do + allow(Gitlab).to receive(:org_or_com?).and_return(true) + stub_ee_application_setting(should_check_namespace_plan: true) + stub_licensed_features(ai_chat: true) + allow(group.namespace_settings).to receive(:experiment_settings_allowed?).and_return(true) + group.namespace_settings.reload.update!(experiment_features_enabled: false) + end +end -- GitLab From cdf2ad3c26771d5fb8b9eb9bc3d0aad7f265cb19 Mon Sep 17 00:00:00 2001 From: Tetiana Chupryna Date: Fri, 2 Feb 2024 12:43:41 +0100 Subject: [PATCH 11/21] Add case for disabled chat --- ee/spec/models/namespace_setting_spec.rb | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/ee/spec/models/namespace_setting_spec.rb b/ee/spec/models/namespace_setting_spec.rb index 445b1ed4e4fd00..4d0b81d387a31d 100644 --- a/ee/spec/models/namespace_setting_spec.rb +++ b/ee/spec/models/namespace_setting_spec.rb @@ -458,11 +458,12 @@ describe '.experiment_settings_allowed?' do using RSpec::Parameterized::TableSyntax - where(:check_namespace_plan, :licensed_feature, :is_root, :result) do - true | true | true | true - false | true | true | false - true | false | true | false - true | true | false | false + where(:check_namespace_plan, :licensed_feature, :is_root, :ai_chat, :result) do + true | true | true | true | true + false | true | true | true | false + true | false | true | false | false + true | true | false | true | false + true | false | true | true | true end with_them do @@ -472,6 +473,7 @@ before do allow(Gitlab::CurrentSettings).to receive(:should_check_namespace_plan?).and_return(check_namespace_plan) allow(group).to receive(:licensed_feature_available?).with(:experimental_features).and_return(licensed_feature) + allow(group).to receive(:licensed_feature_available?).with(:ai_chat).and_return(ai_chat) allow(group).to receive(:root?).and_return(is_root) end -- GitLab From 22e70113e9ed5c3574ec7052556aa98816869cd5 Mon Sep 17 00:00:00 2001 From: Tetiana Chupryna Date: Fri, 2 Feb 2024 13:29:50 +0100 Subject: [PATCH 12/21] Cleanup tanuki_bot spec and remove ai_tanuki_bot feature --- .../models/gitlab_subscriptions/features.rb | 1 - .../agents/zero_shot/qa_evaluation_spec.rb | 2 +- ee/spec/lib/gitlab/llm/tanuki_bot_spec.rb | 198 ++++++++---------- 3 files changed, 94 insertions(+), 107 deletions(-) diff --git a/ee/app/models/gitlab_subscriptions/features.rb b/ee/app/models/gitlab_subscriptions/features.rb index e119b7d1022d43..1f70c43e93d46e 100644 --- a/ee/app/models/gitlab_subscriptions/features.rb +++ b/ee/app/models/gitlab_subscriptions/features.rb @@ -74,7 +74,6 @@ class Features PREMIUM_FEATURES = %i[ ai_chat - ai_tanuki_bot adjourned_deletion_for_projects_and_groups admin_audit_log auditor_user diff --git a/ee/spec/lib/gitlab/llm/chain/agents/zero_shot/qa_evaluation_spec.rb b/ee/spec/lib/gitlab/llm/chain/agents/zero_shot/qa_evaluation_spec.rb index 4454ff02ac9d6c..f7985715d899b7 100644 --- a/ee/spec/lib/gitlab/llm/chain/agents/zero_shot/qa_evaluation_spec.rb +++ b/ee/spec/lib/gitlab/llm/chain/agents/zero_shot/qa_evaluation_spec.rb @@ -40,7 +40,7 @@ # and breaks GitLab ReferenceExtractor stub_default_url_options(host: "gitlab.com", protocol: "https") stub_ee_application_setting(should_check_namespace_plan: true) - stub_licensed_features(ai_features: true, ai_tanuki_bot: true, epics: true) + stub_licensed_features(ai_chat, epics: true) # TODO: We can't run this QA spec with AI Gateway because the service is not available in test jobs. # See https://gitlab.com/gitlab-org/gitlab/-/issues/434445 for more information. stub_feature_flags(gitlab_duo_chat_requests_to_ai_gateway: false) diff --git a/ee/spec/lib/gitlab/llm/tanuki_bot_spec.rb b/ee/spec/lib/gitlab/llm/tanuki_bot_spec.rb index da81380b041348..43f10db8c800c0 100644 --- a/ee/spec/lib/gitlab/llm/tanuki_bot_spec.rb +++ b/ee/spec/lib/gitlab/llm/tanuki_bot_spec.rb @@ -36,10 +36,10 @@ describe '.enabled_for?', :use_clean_rails_redis_caching do context 'for saas', :saas do - let_it_be_with_reload(:group) { create(:group_with_plan, plan: :premium_plan) } + let_it_be_with_reload(:group) { create(:group_with_plan, plan: :ultimate_plan) } context 'when user present and container is not present' do - where(:ai_duo_chat_switch_enabled, :ai_features_available_to_user, :result) do + where(:ai_duo_chat_switch_enabled, :ai_chat_available_to_user, :result) do [ [true, true, true], [true, false, false], @@ -51,7 +51,7 @@ with_them do before do stub_feature_flags(ai_duo_chat_switch: ai_duo_chat_switch_enabled) - allow(user).to receive(:any_group_with_ai_available?).and_return(ai_features_available_to_user) + allow(user).to receive(:any_group_with_ai_chat_available?).and_return(ai_chat_available_to_user) end it 'returns correct result' do @@ -62,7 +62,7 @@ context 'when user and container are both present' do context 'when container is a group with AI enabled' do - include_context 'with ai features enabled for group' + include_context 'with ai chat enabled for group on SaaS' context 'when user is a member of the group' do before_all do @@ -91,7 +91,7 @@ let_it_be(:group) { create(:group) } it 'returns false' do - allow(user).to receive(:any_group_with_ai_available?).and_return(true) + allow(user).to receive(:any_group_with_ai_chat_available?).and_return(true) # add user as a member of the non-licensed group to ensure the # test isn't failing at the membership check group.add_guest(user) @@ -106,7 +106,7 @@ context 'when user is not a member of the group' do context 'when the user has AI enabled via another group' do it 'returns false' do - allow(user).to receive(:any_group_with_ai_available?).and_return(true) + allow(user).to receive(:any_group_with_ai_chat_available?).and_return(true) expect( described_class.enabled_for?(user: user, container: group) @@ -119,14 +119,14 @@ context 'when container is not a group with AI enabled' do context 'when user has AI enabled' do before do - allow(user).to receive(:any_group_with_ai_available?).and_return(true) + allow(user).to receive(:any_group_with_ai_chat_available?).and_return(true) end context 'when container is a group' do - include_context 'with experiment features disabled for group' + include_context 'with ai features disabled and licensed chat for group on SaaS' it 'returns false' do - allow(user).to receive(:any_group_with_ai_available?).and_return(true) + allow(user).to receive(:any_group_with_ai_chat_available?).and_return(true) expect( described_class.enabled_for?(user: user, container: group) @@ -148,7 +148,7 @@ end context 'when user not present, container is present' do - include_context 'with ai features enabled for group' + include_context 'with ai chat enabled for group on SaaS' it 'returns false' do expect( @@ -246,145 +246,133 @@ allow(logger).to receive(:info_or_debug) end - context 'with the ai_tanuki_bot license not available' do + context 'when on Gitlab.com' do before do - allow(License).to receive(:feature_available?).with(:ai_tanuki_bot).and_return(false) + allow(::Gitlab).to receive(:org_or_com?).and_return(true) end - it 'returns an empty response message' do - expect(execute.response_body).to eq(empty_response_message) + context 'when no user is provided' do + let(:user) { nil } + + it 'returns an empty response message' do + expect(execute.response_body).to eq(empty_response_message) + end end - end - context 'with the tanuki_bot license available' do - context 'when on Gitlab.com' do + context 'when user has AI features disabled' do before do - allow(::Gitlab).to receive(:org_or_com?).and_return(true) + allow(described_class).to receive(:enabled_for?).with(user: user).and_return(false) end - context 'when no user is provided' do - let(:user) { nil } - - it 'returns an empty response message' do - expect(execute.response_body).to eq(empty_response_message) - end + it 'returns an empty response message' do + expect(execute.response_body).to eq(empty_response_message) end + end - context 'when user has AI features disabled' do - before do - allow(described_class).to receive(:enabled_for?).with(user: user).and_return(false) - end + context 'when user has AI features enabled' do + before do + allow(::Gitlab::Llm::VertexAi::Client).to receive(:new).and_return(vertex_client) + allow(::Gitlab::Llm::Anthropic::Client).to receive(:new).and_return(anthropic_client) + allow(described_class).to receive(:enabled_for?).and_return(true) + end + context 'when embeddings table is empty (no embeddings are stored in the table)' do it 'returns an empty response message' do + vertex_model.connection.execute("truncate #{vertex_model.table_name}") + expect(execute.response_body).to eq(empty_response_message) end end - context 'when user has AI features enabled' do - before do - allow(::Gitlab::Llm::VertexAi::Client).to receive(:new).and_return(vertex_client) - allow(::Gitlab::Llm::Anthropic::Client).to receive(:new).and_return(anthropic_client) - allow(described_class).to receive(:enabled_for?).and_return(true) - end - - context 'when embeddings table is empty (no embeddings are stored in the table)' do - it 'returns an empty response message' do - vertex_model.connection.execute("truncate #{vertex_model.table_name}") + it 'executes calls through to anthropic' do + embeddings - expect(execute.response_body).to eq(empty_response_message) - end - end + expect(anthropic_client).to receive(:stream).once.and_return(completion_response) + expect(vertex_client).to receive(:text_embeddings).with(**vertex_args).and_return(vertex_response) - it 'executes calls through to anthropic' do - embeddings - - expect(anthropic_client).to receive(:stream).once.and_return(completion_response) - expect(vertex_client).to receive(:text_embeddings).with(**vertex_args).and_return(vertex_response) + execute + end - execute - end + it 'calls the duo_chat_documentation pipeline for the emedded content' do + allow(vertex_client).to receive(:text_embeddings).with(**vertex_args).and_return(vertex_response) + allow(Banzai).to receive(:render).and_return('absolute_links_content') - it 'calls the duo_chat_documentation pipeline for the emedded content' do - allow(vertex_client).to receive(:text_embeddings).with(**vertex_args).and_return(vertex_response) - allow(Banzai).to receive(:render).and_return('absolute_links_content') + expect(anthropic_client).to receive(:stream) + .with( + prompt: a_string_including('absolute_links_content'), + options: { model: "claude-instant-1.1" } + ).once.and_return(completion_response) - expect(anthropic_client).to receive(:stream) - .with( - prompt: a_string_including('absolute_links_content'), - options: { model: "claude-instant-1.1" } - ).once.and_return(completion_response) + execute + end - execute - end + it 'yields the streamed response to the given block' do + embeddings - it 'yields the streamed response to the given block' do - embeddings + allow(anthropic_client).to receive(:stream).once + .and_yield({ "completion" => answer }) + .and_return(completion_response) - allow(anthropic_client).to receive(:stream).once - .and_yield({ "completion" => answer }) - .and_return(completion_response) + expect(vertex_client).to receive(:text_embeddings).with(**vertex_args).and_return(vertex_response) - expect(vertex_client).to receive(:text_embeddings).with(**vertex_args).and_return(vertex_response) + expect { |b| instance.execute(&b) }.to yield_with_args(answer) + end - expect { |b| instance.execute(&b) }.to yield_with_args(answer) - end + it 'raises an error when request failed' do + embeddings - it 'raises an error when request failed' do - embeddings + expect(logger).to receive(:info).with(message: "Streaming error", error: { "message" => "some error" }) + expect(vertex_client).to receive(:text_embeddings).with(**vertex_args).and_return(vertex_response) + allow(anthropic_client).to receive(:stream).once.and_yield({ "error" => { "message" => "some error" } }) - expect(logger).to receive(:info).with(message: "Streaming error", error: { "message" => "some error" }) - expect(vertex_client).to receive(:text_embeddings).with(**vertex_args).and_return(vertex_response) - allow(anthropic_client).to receive(:stream).once.and_yield({ "error" => { "message" => "some error" } }) + execute + end - execute + context 'when embedding database does not exist' do + before do + allow(Embedding::Vertex::GitlabDocumentation).to receive(:table_exists?).and_return(false) end - context 'when embedding database does not exist' do - before do - allow(Embedding::Vertex::GitlabDocumentation).to receive(:table_exists?).and_return(false) - end - - it 'returns an unsupported_response response message' do - expect(execute.response_body).to eq(unsupported_response_message) - end + it 'returns an unsupported_response response message' do + expect(execute.response_body).to eq(unsupported_response_message) end end end + end - context 'when ai_global_switch FF is disabled' do - before do - stub_feature_flags(ai_global_switch: false) - end + context 'when ai_global_switch FF is disabled' do + before do + stub_feature_flags(ai_global_switch: false) + end + + it 'returns an empty response message' do + expect(execute.response_body).to eq(empty_response_message) + end + end + + context 'when the feature flags are enabled' do + before do + allow(::Gitlab::Llm::VertexAi::Client).to receive(:new).and_return(vertex_client) + allow(::Gitlab::Llm::Anthropic::Client).to receive(:new).and_return(anthropic_client) + allow(user).to receive(:any_group_with_ai_available?).and_return(true) + end + + context 'when the question is not provided' do + let(:question) { nil } it 'returns an empty response message' do expect(execute.response_body).to eq(empty_response_message) end end - context 'when the feature flags are enabled' do + context 'when no neighbors are found' do before do - allow(::Gitlab::Llm::VertexAi::Client).to receive(:new).and_return(vertex_client) - allow(::Gitlab::Llm::Anthropic::Client).to receive(:new).and_return(anthropic_client) - allow(user).to receive(:any_group_with_ai_available?).and_return(true) - end - - context 'when the question is not provided' do - let(:question) { nil } - - it 'returns an empty response message' do - expect(execute.response_body).to eq(empty_response_message) - end + allow(vertex_model).to receive(:neighbor_for).and_return(vertex_model.none) + allow(vertex_client).to receive(:text_embeddings).with(**vertex_args).and_return(vertex_response) end - context 'when no neighbors are found' do - before do - allow(vertex_model).to receive(:neighbor_for).and_return(vertex_model.none) - allow(vertex_client).to receive(:text_embeddings).with(**vertex_args).and_return(vertex_response) - end - - it 'returns an i do not know' do - expect(execute.response_body).to eq(empty_response_message) - end + it 'returns an i do not know' do + expect(execute.response_body).to eq(empty_response_message) end end end -- GitLab From 4c10237d5e7bdd6acd998b2d504249728836c990 Mon Sep 17 00:00:00 2001 From: Tetiana Chupryna Date: Fri, 2 Feb 2024 16:35:50 +0100 Subject: [PATCH 13/21] Update GitlabContext spec for chat in premium --- .../gitlab/llm/chain/gitlab_context_spec.rb | 4 +-- .../tools/summarize_comments/executor_spec.rb | 2 +- .../gitlab/llm/chain/utils/authorizer_spec.rb | 28 +++++++++---------- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/ee/spec/lib/gitlab/llm/chain/gitlab_context_spec.rb b/ee/spec/lib/gitlab/llm/chain/gitlab_context_spec.rb index ded06ca736471f..157ce805b9cbb9 100644 --- a/ee/spec/lib/gitlab/llm/chain/gitlab_context_spec.rb +++ b/ee/spec/lib/gitlab/llm/chain/gitlab_context_spec.rb @@ -15,7 +15,7 @@ let(:content_limit) { 500 } context 'with a serializable resource' do - let_it_be(:group) { create(:group_with_plan, plan: :ultimate_plan) } + let_it_be(:group) { create(:group_with_plan, plan: :premium_plan) } let_it_be(:project) { create(:project, group: group) } let(:resource) { create(:issue, project: project) } let(:resource_xml) do @@ -29,7 +29,7 @@ before do stub_ee_application_setting(should_check_namespace_plan: true) - stub_licensed_features(experimental_features: true, ai_features: true) + stub_licensed_features(ai_chat: true) group.namespace_settings.update!(experiment_features_enabled: true) end diff --git a/ee/spec/lib/gitlab/llm/chain/tools/summarize_comments/executor_spec.rb b/ee/spec/lib/gitlab/llm/chain/tools/summarize_comments/executor_spec.rb index 73b69574c678bd..6d168513075fb0 100644 --- a/ee/spec/lib/gitlab/llm/chain/tools/summarize_comments/executor_spec.rb +++ b/ee/spec/lib/gitlab/llm/chain/tools/summarize_comments/executor_spec.rb @@ -29,7 +29,7 @@ before do stub_application_setting(check_namespace_plan: true) - stub_licensed_features(summarize_notes: true, ai_features: true, experimental_features: true) + stub_licensed_features(summarize_notes: true, ai_features: true, experimental_features: true, ai_chat: true) group.add_developer(user) group.update!(experiment_features_enabled: true) diff --git a/ee/spec/lib/gitlab/llm/chain/utils/authorizer_spec.rb b/ee/spec/lib/gitlab/llm/chain/utils/authorizer_spec.rb index 2600fb8a73870a..01ea028dba62fa 100644 --- a/ee/spec/lib/gitlab/llm/chain/utils/authorizer_spec.rb +++ b/ee/spec/lib/gitlab/llm/chain/utils/authorizer_spec.rb @@ -30,14 +30,14 @@ shared_examples 'user authorization' do context 'when user has groups with ai available' do - include_context 'with ai features enabled for group' + include_context 'with ai chat enabled for group on SaaS' it 'returns true' do expect(authorizer.user(user: user).allowed?).to be(true) end end context 'when user has no groups with ai available' do - include_context 'with experiment features disabled for group' + include_context 'with ai features disabled and licensed chat for group on SaaS' it 'returns true when user has no groups with ai available' do expect(authorizer.user(user: user).allowed?).to be(false) @@ -49,7 +49,7 @@ describe '.context.allowed?' do context 'when both resource and container are present' do context 'when container is authorized' do - include_context 'with ai features enabled for group' + include_context 'with ai chat enabled for group on SaaS' it 'returns true if both resource and container are authorized' do expect(authorizer.context(context: context).allowed?).to be(true) @@ -65,7 +65,7 @@ end context 'when container is not authorized' do - include_context 'with experiment features disabled for group' + include_context 'with ai features disabled and licensed chat for group on SaaS' it 'returns false if container is not authorized' do expect(authorizer.context(context: context).allowed?).to be(false) @@ -86,7 +86,7 @@ end context 'when resource container is authorized' do - include_context 'with ai features enabled for group' + include_context 'with ai chat enabled for group on SaaS' it 'returns true' do expect(authorizer.context(context: context).allowed?).to be(true) @@ -94,7 +94,7 @@ end context 'when container is not authorized' do - include_context 'with experiment features disabled for group' + include_context 'with ai features disabled and licensed chat for group on SaaS' it 'returns false' do expect(authorizer.context(context: context).allowed?).to be(false) @@ -113,7 +113,7 @@ end context 'when container is authorized' do - include_context 'with ai features enabled for group' + include_context 'with ai chat enabled for group on SaaS' it 'returns true' do expect(authorizer.context(context: context).allowed?).to be(false) @@ -121,7 +121,7 @@ end context 'when container is not authorized' do - include_context 'with experiment features disabled for group' + include_context 'with ai features disabled and licensed chat for group on SaaS' it 'returns false' do expect(authorizer.context(context: context).allowed?).to be(false) @@ -140,7 +140,7 @@ end context 'when user is authorized' do - include_context 'with ai features enabled for group' + include_context 'with ai chat enabled for group on SaaS' it 'returns true' do expect(authorizer.context(context: context).allowed?).to be(true) @@ -148,7 +148,7 @@ end context 'when user is not authorized' do - include_context 'with experiment features disabled for group' + include_context 'with ai features disabled and licensed chat for group on SaaS' it 'returns false' do expect(authorizer.context(context: context).allowed?).to be(false) @@ -185,7 +185,7 @@ end context 'when resource parent is not authorized' do - include_context 'with experiment features disabled for group' + include_context 'with ai features disabled and licensed chat for group on SaaS' it 'returns false' do expect(authorizer.resource(resource: context.resource, user: context.current_user).allowed?) @@ -194,7 +194,7 @@ end context 'when resource container is authorized' do - include_context 'with ai features enabled for group' + include_context 'with ai chat enabled for group on SaaS' it 'calls user.can? with the appropriate arguments' do expect(user).to receive(:can?).with('read_issue', resource) @@ -214,7 +214,7 @@ context 'when resource is current user' do context 'when user is not in any group with ai' do - include_context 'with experiment features disabled for group' + include_context 'with ai features disabled and licensed chat for group on SaaS' it 'returns false' do expect(authorizer.resource(resource: context.current_user, user: context.current_user).allowed?) @@ -223,7 +223,7 @@ end context 'when user is in any group with ai' do - include_context 'with ai features enabled for group' + include_context 'with ai chat enabled for group on SaaS' it 'returns true' do expect(authorizer.resource(resource: context.current_user, user: context.current_user).allowed?) -- GitLab From c2cf3f77a9b750757da648ecdee5da6921590479 Mon Sep 17 00:00:00 2001 From: Tetiana Chupryna Date: Fri, 2 Feb 2024 18:25:30 +0100 Subject: [PATCH 14/21] Fix test setup for chat dependency --- .../gitlab/llm/completions/summarize_all_open_notes_spec.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ee/spec/lib/gitlab/llm/completions/summarize_all_open_notes_spec.rb b/ee/spec/lib/gitlab/llm/completions/summarize_all_open_notes_spec.rb index 3041b46c98c68e..3f176d23ae7f30 100644 --- a/ee/spec/lib/gitlab/llm/completions/summarize_all_open_notes_spec.rb +++ b/ee/spec/lib/gitlab/llm/completions/summarize_all_open_notes_spec.rb @@ -127,7 +127,8 @@ summarize_notes: true, ai_features: true, epics: true, - experimental_features: true + experimental_features: true, + ai_chat: true ) group.namespace_settings.update!(experiment_features_enabled: true) project.reload -- GitLab From 831de5345c74cd04b3152be354fa03164af9a0c9 Mon Sep 17 00:00:00 2001 From: Tetiana Chupryna Date: Mon, 5 Feb 2024 21:09:25 +0100 Subject: [PATCH 15/21] Add source package to ingestion trait --- .../gitlab/llm/chain/tools/issue_identifier/executor_spec.rb | 2 +- .../lib/gitlab/llm/chain/tools/json_reader/executor_spec.rb | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/ee/spec/lib/gitlab/llm/chain/tools/issue_identifier/executor_spec.rb b/ee/spec/lib/gitlab/llm/chain/tools/issue_identifier/executor_spec.rb index d8473756823827..11f28bbc957f43 100644 --- a/ee/spec/lib/gitlab/llm/chain/tools/issue_identifier/executor_spec.rb +++ b/ee/spec/lib/gitlab/llm/chain/tools/issue_identifier/executor_spec.rb @@ -97,7 +97,7 @@ context 'when user has permission to read resource' do before do stub_application_setting(check_namespace_plan: true) - stub_licensed_features(summarize_notes: true, ai_features: true) + stub_licensed_features(summarize_notes: true, ai_features: true, ai_chat: true) project.add_guest(user) allow(project.root_ancestor.namespace_settings).to receive(:experiment_settings_allowed?).and_return(true) diff --git a/ee/spec/lib/gitlab/llm/chain/tools/json_reader/executor_spec.rb b/ee/spec/lib/gitlab/llm/chain/tools/json_reader/executor_spec.rb index 5f6bdf70fb011e..c5c051a5659349 100644 --- a/ee/spec/lib/gitlab/llm/chain/tools/json_reader/executor_spec.rb +++ b/ee/spec/lib/gitlab/llm/chain/tools/json_reader/executor_spec.rb @@ -63,7 +63,8 @@ stub_licensed_features( ai_features: true, epics: true, - experimental_features: true + experimental_features: true, + ai_chat: true ) group.namespace_settings.update!(experiment_features_enabled: true) end -- GitLab From 476d65742023ff0796b7229327bc114df16d96b9 Mon Sep 17 00:00:00 2001 From: Tetiana Chupryna Date: Tue, 6 Feb 2024 13:09:30 +0100 Subject: [PATCH 16/21] Update list of dependency licensed features --- .../lib/gitlab/llm/chain/tools/epic_identifier/executor_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ee/spec/lib/gitlab/llm/chain/tools/epic_identifier/executor_spec.rb b/ee/spec/lib/gitlab/llm/chain/tools/epic_identifier/executor_spec.rb index 8c8e16b80c624a..19efdf9c482a50 100644 --- a/ee/spec/lib/gitlab/llm/chain/tools/epic_identifier/executor_spec.rb +++ b/ee/spec/lib/gitlab/llm/chain/tools/epic_identifier/executor_spec.rb @@ -94,7 +94,7 @@ context 'when user has permission to read resource' do before do stub_application_setting(check_namespace_plan: true) - stub_licensed_features(summarize_notes: true, ai_features: true, epics: true, experimental_features: true) + stub_licensed_features(summarize_notes: true, epics: true, ai_chat: true) # rubocop: disable RSpec/BeforeAllRoleAssignment group.add_guest(user) group.update!(experiment_features_enabled: true) -- GitLab From 748172484ec9d5d98afdfa034f979f5373107741 Mon Sep 17 00:00:00 2001 From: Tetiana Chupryna Date: Tue, 6 Feb 2024 14:38:49 +0100 Subject: [PATCH 17/21] Update test setup for chat in premium --- ee/spec/finders/llm/extra_resource_finder_spec.rb | 2 +- .../lib/gitlab/llm/chain/tools/epic_identifier/executor_spec.rb | 2 +- .../gitlab/llm/chain/tools/issue_identifier/executor_spec.rb | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ee/spec/finders/llm/extra_resource_finder_spec.rb b/ee/spec/finders/llm/extra_resource_finder_spec.rb index e069019d884e88..3d1381b8df47ce 100644 --- a/ee/spec/finders/llm/extra_resource_finder_spec.rb +++ b/ee/spec/finders/llm/extra_resource_finder_spec.rb @@ -13,7 +13,7 @@ let_it_be(:guest) { create(:user).tap { |u| project.add_guest(u) } } let_it_be(:issue) { create(:issue, project: project) } - include_context 'with ai features enabled for group' + include_context 'with ai chat enabled for group on SaaS' describe '.execute' do subject(:execute) { described_class.new(current_user, referer_url).execute } diff --git a/ee/spec/lib/gitlab/llm/chain/tools/epic_identifier/executor_spec.rb b/ee/spec/lib/gitlab/llm/chain/tools/epic_identifier/executor_spec.rb index 19efdf9c482a50..4cd9ea2ccd0d9f 100644 --- a/ee/spec/lib/gitlab/llm/chain/tools/epic_identifier/executor_spec.rb +++ b/ee/spec/lib/gitlab/llm/chain/tools/epic_identifier/executor_spec.rb @@ -94,7 +94,7 @@ context 'when user has permission to read resource' do before do stub_application_setting(check_namespace_plan: true) - stub_licensed_features(summarize_notes: true, epics: true, ai_chat: true) + stub_licensed_features(epics: true, ai_chat: true) # rubocop: disable RSpec/BeforeAllRoleAssignment group.add_guest(user) group.update!(experiment_features_enabled: true) diff --git a/ee/spec/lib/gitlab/llm/chain/tools/issue_identifier/executor_spec.rb b/ee/spec/lib/gitlab/llm/chain/tools/issue_identifier/executor_spec.rb index 11f28bbc957f43..96f1173272c39a 100644 --- a/ee/spec/lib/gitlab/llm/chain/tools/issue_identifier/executor_spec.rb +++ b/ee/spec/lib/gitlab/llm/chain/tools/issue_identifier/executor_spec.rb @@ -97,7 +97,7 @@ context 'when user has permission to read resource' do before do stub_application_setting(check_namespace_plan: true) - stub_licensed_features(summarize_notes: true, ai_features: true, ai_chat: true) + stub_licensed_features(ai_chat: true) project.add_guest(user) allow(project.root_ancestor.namespace_settings).to receive(:experiment_settings_allowed?).and_return(true) -- GitLab From 8ad2301079d0155341bef86fc5884a77363d4e4b Mon Sep 17 00:00:00 2001 From: Tetiana Chupryna Date: Tue, 6 Feb 2024 14:48:46 +0100 Subject: [PATCH 18/21] Fix user with ai groups check --- ee/app/models/ee/namespace.rb | 4 ++-- ee/app/models/ee/user.rb | 2 +- ee/spec/models/ee/namespace_spec.rb | 6 ++++++ 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/ee/app/models/ee/namespace.rb b/ee/app/models/ee/namespace.rb index 7c0feaf1c772e9..e2c77ff7fab453 100644 --- a/ee/app/models/ee/namespace.rb +++ b/ee/app/models/ee/namespace.rb @@ -104,8 +104,8 @@ module Namespace .where(namespace_settings: { experiment_features_enabled: true }) end - scope :with_ai_supported_plan, -> do - plan_names = GitlabSubscriptions::Features.saas_plans_with_feature(:ai_features) + scope :with_ai_supported_plan, -> (feature = :ai_features) do + plan_names = GitlabSubscriptions::Features.saas_plans_with_feature(feature) joins("LEFT OUTER JOIN \"gitlab_subscriptions\" ON \"gitlab_subscriptions\".\"namespace_id\" = \"namespaces\".traversal_ids[1]") .joins("LEFT OUTER JOIN \"plans\" ON \"plans\".\"id\" = \"gitlab_subscriptions\".\"hosted_plan_id\"") diff --git a/ee/app/models/ee/user.rb b/ee/app/models/ee/user.rb index bc0e3801ebd0b9..b553503a87f42e 100644 --- a/ee/app/models/ee/user.rb +++ b/ee/app/models/ee/user.rb @@ -653,7 +653,7 @@ def any_group_with_ai_available? def any_group_with_ai_chat_available? Rails.cache.fetch(['users', id, GROUP_WITH_AI_CHAT_ENABLED_CACHE_KEY], expires_in: GROUP_WITH_AI_CHAT_ENABLED_CACHE_PERIOD) do - member_namespaces.namespace_settings_with_ai_features_enabled.with_feature_available_in_plan(:ai_chat).any? + member_namespaces.namespace_settings_with_ai_features_enabled.with_ai_supported_plan(:ai_chat).any? end end diff --git a/ee/spec/models/ee/namespace_spec.rb b/ee/spec/models/ee/namespace_spec.rb index 32e29ce911a82c..601f6414bbdac2 100644 --- a/ee/spec/models/ee/namespace_spec.rb +++ b/ee/spec/models/ee/namespace_spec.rb @@ -457,6 +457,12 @@ let_it_be(:opensource_namespace) { create(:namespace_with_plan, plan: :opensource_plan) } it { is_expected.to contain_exactly(ultimate_namespace, ultimate_trial_namespace, opensource_namespace) } + + context 'with ai_chat feature' do + subject { described_class.with_ai_supported_plan(:ai_chat) } + + it { is_expected.to contain_exactly(ultimate_namespace, ultimate_trial_namespace, premium_namespace, opensource_namespace) } + end end describe '.with_code_suggestions_enabled' do -- GitLab From 87507401d37ac06c2fa557f111ddc8069dd7c1a2 Mon Sep 17 00:00:00 2001 From: Tetiana Chupryna Date: Tue, 6 Feb 2024 16:32:26 +0100 Subject: [PATCH 19/21] Fix typo in chat feature flag --- .../lib/gitlab/llm/chain/agents/zero_shot/qa_evaluation_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ee/spec/lib/gitlab/llm/chain/agents/zero_shot/qa_evaluation_spec.rb b/ee/spec/lib/gitlab/llm/chain/agents/zero_shot/qa_evaluation_spec.rb index f7985715d899b7..d6de4407fec3f2 100644 --- a/ee/spec/lib/gitlab/llm/chain/agents/zero_shot/qa_evaluation_spec.rb +++ b/ee/spec/lib/gitlab/llm/chain/agents/zero_shot/qa_evaluation_spec.rb @@ -40,7 +40,7 @@ # and breaks GitLab ReferenceExtractor stub_default_url_options(host: "gitlab.com", protocol: "https") stub_ee_application_setting(should_check_namespace_plan: true) - stub_licensed_features(ai_chat, epics: true) + stub_licensed_features(ai_chat: true, epics: true) # TODO: We can't run this QA spec with AI Gateway because the service is not available in test jobs. # See https://gitlab.com/gitlab-org/gitlab/-/issues/434445 for more information. stub_feature_flags(gitlab_duo_chat_requests_to_ai_gateway: false) -- GitLab From 7995d84c91306afde923364b32212ea5680d8365 Mon Sep 17 00:00:00 2001 From: Tetiana Chupryna Date: Tue, 6 Feb 2024 20:25:30 +0100 Subject: [PATCH 20/21] Clean up ai chat cache --- ee/app/models/ee/user.rb | 4 +++- ee/spec/models/ee/user_spec.rb | 10 +++++++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/ee/app/models/ee/user.rb b/ee/app/models/ee/user.rb index b553503a87f42e..2b90d8e8c3a08c 100644 --- a/ee/app/models/ee/user.rb +++ b/ee/app/models/ee/user.rb @@ -247,8 +247,10 @@ def use_separate_indices? end def clear_group_with_ai_available_cache(ids) - cache_keys = Array.wrap(ids).map { |id| ["users", id, GROUP_WITH_AI_ENABLED_CACHE_KEY] } + cache_keys_ai_features = Array.wrap(ids).map { |id| ["users", id, GROUP_WITH_AI_ENABLED_CACHE_KEY] } + cache_keys_ai_chat = Array.wrap(ids).map { |id| ["users", id, GROUP_WITH_AI_CHAT_ENABLED_CACHE_KEY] } + cache_keys = cache_keys_ai_features + cache_keys_ai_chat ::Gitlab::Instrumentation::RedisClusterValidator.allow_cross_slot_commands do Rails.cache.delete_multi(cache_keys) end diff --git a/ee/spec/models/ee/user_spec.rb b/ee/spec/models/ee/user_spec.rb index 948609daeb9f68..44825796d71a91 100644 --- a/ee/spec/models/ee/user_spec.rb +++ b/ee/spec/models/ee/user_spec.rb @@ -3763,23 +3763,27 @@ describe '.clear_group_with_ai_available_cache', :use_clean_rails_redis_caching do let_it_be(:user) { create(:user) } let_it_be(:other_user) { create(:user) } + let_it_be(:yet_another_user) { create(:user) } before do user.any_group_with_ai_available? other_user.any_group_with_ai_available? + yet_another_user.any_group_with_ai_chat_available? end - it 'clears cache from users with the given ids' do + it 'clears cache from users with the given ids', :aggregate_failures do expect(Rails.cache.fetch(['users', user.id, 'group_with_ai_enabled'])).to eq(false) expect(Rails.cache.fetch(['users', other_user.id, 'group_with_ai_enabled'])).to eq(false) + expect(Rails.cache.fetch(['users', yet_another_user.id, 'group_with_ai_chat_enabled'])).to eq(false) - described_class.clear_group_with_ai_available_cache([user.id]) + described_class.clear_group_with_ai_available_cache([user.id, yet_another_user.id]) expect(Rails.cache.fetch(['users', user.id, 'group_with_ai_enabled'])).to be_nil expect(Rails.cache.fetch(['users', other_user.id, 'group_with_ai_enabled'])).to eq(false) + expect(Rails.cache.fetch(['users', yet_another_user.id, 'group_with_ai_chat_enabled'])).to be_nil end - it 'clears cache when given a single id' do + it 'clears cache when given a single id', :aggregate_failures do expect(Rails.cache.fetch(['users', user.id, 'group_with_ai_enabled'])).to eq(false) described_class.clear_group_with_ai_available_cache(user.id) -- GitLab From 5f936cd25f52c0d84fb2be49a2fb3a7201356a67 Mon Sep 17 00:00:00 2001 From: Tetiana Chupryna Date: Tue, 6 Feb 2024 20:37:29 +0100 Subject: [PATCH 21/21] Add note to docs about experimental in premium --- doc/user/group/manage.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/doc/user/group/manage.md b/doc/user/group/manage.md index 7dc2d5e931097c..528bb6f2b22358 100644 --- a/doc/user/group/manage.md +++ b/doc/user/group/manage.md @@ -458,7 +458,7 @@ for the ability to set merge request approval rules for groups is tracked in ## Enable Experiment and Beta features DETAILS: -**Tier:** Ultimate +**Tier:** Ultimate, Premium **Offering:** SaaS > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/118222) in GitLab 16.0. @@ -468,6 +468,9 @@ WARNING: (for example, the results might be low-quality, incomplete, incoherent, offensive, or insensitive, and might include insecure code or failed pipelines). +NOTE: +[GitLab Duo Chat](../../user/gitlab_duo_chat.md) is an only feature available in Premium tier. + You can give all users in a top-level group access to Experiment and Beta features. This setting [cascades to all projects](../project/merge_requests/approvals/settings.md#settings-cascading) that belong to the group. -- GitLab