diff --git a/ee/app/services/llm/execute_method_service.rb b/ee/app/services/llm/execute_method_service.rb index ad64302a88eb41361d81af188f99b780f6cebdb9..52e114604c9351c6a6bb73e8cef599bcdc1232df 100644 --- a/ee/app/services/llm/execute_method_service.rb +++ b/ee/app/services/llm/execute_method_service.rb @@ -26,6 +26,8 @@ def execute def track_snowplow_event(result) request_id = result[:ai_message]&.request_id + client = Gitlab::Llm::Tracking.client_for_user_agent(options[:user_agent]) + Gitlab::Tracking.event( self.class.to_s, "execute_llm_method", @@ -34,7 +36,8 @@ def track_snowplow_event(result) user: user, namespace: namespace, project: project, - requestId: request_id + requestId: request_id, + client: client ) end end diff --git a/ee/config/events/execute_llm_method.yml b/ee/config/events/execute_llm_method.yml new file mode 100644 index 0000000000000000000000000000000000000000..4fd51ec53335af5a47d111c03966e76a55faba8e --- /dev/null +++ b/ee/config/events/execute_llm_method.yml @@ -0,0 +1,26 @@ +--- +description: Tracks when a call was made to an LLM +internal_events: true +action: execute_llm_method +identifiers: +- project +- namespace +- user +additional_properties: + label: + description: The type of LLM request (such as 'chat' or 'categorize_question') + property: + description: Success of request + client: + description: Client such as 'web' or 'vscode' + requestId: + description: Request id +product_group: duo_chat +milestone: '16.0' +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/117877 +distributions: +- ee +tiers: +- free +- premium +- ultimate diff --git a/ee/lib/gitlab/llm/tracking.rb b/ee/lib/gitlab/llm/tracking.rb index 1236f302dd4b7ce59551a0b0df60705854711cf0..ded4021bf0153e8c346824821f1a076beb10dbdc 100644 --- a/ee/lib/gitlab/llm/tracking.rb +++ b/ee/lib/gitlab/llm/tracking.rb @@ -3,6 +3,21 @@ module Gitlab module Llm class Tracking + USER_AGENT_CLIENTS = { + UsageDataCounters::VSCodeExtensionActivityUniqueCounter::VS_CODE_USER_AGENT_REGEX => + 'vscode', + UsageDataCounters::JetBrainsPluginActivityUniqueCounter::JETBRAINS_USER_AGENT_REGEX => + 'jetbrains', + UsageDataCounters::JetBrainsBundledPluginActivityUniqueCounter::JETBRAINS_BUNDLED_USER_AGENT_REGEX => + 'jetbrains_bundled', + UsageDataCounters::VisualStudioExtensionActivityUniqueCounter::VISUAL_STUDIO_EXTENSION_USER_AGENT_REGEX => + 'visual_studio', + UsageDataCounters::NeovimPluginActivityUniqueCounter::NEOVIM_PLUGIN_USER_AGENT_REGEX => + 'neovim', + UsageDataCounters::GitLabCliActivityUniqueCounter::GITLAB_CLI_USER_AGENT_REGEX => + 'gitlab_cli' + }.freeze + def self.event_for_ai_message(category, action, ai_message:) ::Gitlab::Tracking.event( category, @@ -17,9 +32,8 @@ def self.event_for_ai_message(category, action, ai_message:) def self.client_for_user_agent(user_agent) return unless user_agent.present? - user_agent.match?(Gitlab::Regex.vs_code_user_agent_regex) ? 'vscode' : 'web' + USER_AGENT_CLIENTS.find { |regex, _client| user_agent.match?(regex) }&.last || 'web' end - private_class_method :client_for_user_agent end end end diff --git a/ee/spec/lib/gitlab/llm/tracking_spec.rb b/ee/spec/lib/gitlab/llm/tracking_spec.rb index 5d9e0ba27ef2ddfb58165f34d498af54addf52b9..13a7db0d8654a897da117623d4459eb09bc4f32a 100644 --- a/ee/spec/lib/gitlab/llm/tracking_spec.rb +++ b/ee/spec/lib/gitlab/llm/tracking_spec.rb @@ -6,8 +6,8 @@ let(:user) { build(:user) } let(:resource) { build(:project) } let(:ai_action_name) { 'chat' } - let(:user_agent) { nil } let(:request_id) { 'uuid' } + let(:user_agent) { nil } let(:ai_message) do build(:ai_message, @@ -32,39 +32,67 @@ client: nil ) end + end + + describe '.client_for_user_agent' do + subject { described_class.client_for_user_agent(user_agent) } - context 'with browser user agent' do + context 'when user agent is from a web browser' do let(:user_agent) { 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)' } - it 'tracks event with correct params' do - event_for_ai_message - - expect_snowplow_event( - category: 'Category', - action: 'my_action', - label: ai_action_name, - property: request_id, - user: user, - client: 'web' - ) - end + it { is_expected.to eq('web') } end - context 'with vscode user agent' do + context 'when user agent is from VS Code' do let(:user_agent) { 'vs-code-gitlab-workflow/3.11.1 VSCode/1.52.1 Node.js/12.14.1 (darwin; x64)' } - it 'tracks event with correct params' do - event_for_ai_message - - expect_snowplow_event( - category: 'Category', - action: 'my_action', - label: ai_action_name, - property: request_id, - user: user, - client: 'vscode' - ) + it { is_expected.to eq('vscode') } + end + + context 'when user agent is from JetBrains plugin' do + let(:user_agent) { 'gitlab-jetbrains-plugin/0.0.1 intellij-idea/2021.2.4 java/11.0.13 mac-os-x/aarch64/12.1' } + + it { is_expected.to eq('jetbrains') } + end + + context 'when user agent is from JetBrains bundled plugin' do + let(:user_agent) do + 'IntelliJ-GitLab-Plugin PhpStorm/PS-232.6734.11 (JRE 17.0.7+7-b966.2; Linux 6.2.0-20-generic; amd64)' end + + it { is_expected.to eq('jetbrains_bundled') } + end + + context 'when user agent is from Visual Studio extension' do + let(:user_agent) { 'code-completions-language-server-experiment (gl-visual-studio-extension:1.0.0.0; arch:X64;)' } + + it { is_expected.to eq('visual_studio') } + end + + context 'when user agent is from Neovim plugin' do + let(:user_agent) do + 'code-completions-language-server-experiment (Neovim:0.9.0; gitlab.vim (v0.1.0); arch:amd64; os:darwin)' + end + + it { is_expected.to eq('neovim') } + end + + context 'when user agent is from GitLab CLI (old format)' do + let(:user_agent) { 'GLab - GitLab CLI' } + + it { is_expected.to eq('gitlab_cli') } + end + + context 'when user agent is from GitLab CLI (current format)' do + let(:user_agent) { 'glab/v1.25.3-27-g7ec258fb (built 2023-02-16), darwin' } + + it { is_expected.to eq('gitlab_cli') } + end + + context 'when user agent is nil' do + let(:user_agent) { nil } + + it { is_expected.to be_nil } end end end diff --git a/ee/spec/services/llm/execute_method_service_spec.rb b/ee/spec/services/llm/execute_method_service_spec.rb index 636146cf5a54fa9087a3076f2ec9293f207bb77b..a680440cf50b3cc4c3185f629a841b5c497ac21a 100644 --- a/ee/spec/services/llm/execute_method_service_spec.rb +++ b/ee/spec/services/llm/execute_method_service_spec.rb @@ -13,6 +13,7 @@ let(:success) { true } let(:request_id) { 'uuid' } let(:chat_message) { instance_double(Gitlab::Llm::ChatMessage, request_id: request_id) } + let(:client) { nil } let(:service_response) do instance_double( ServiceResponse, @@ -94,7 +95,8 @@ user: user, namespace: group, project: project, - requestId: 'uuid' + requestId: 'uuid', + client: nil } end @@ -102,6 +104,9 @@ allow_next_instance_of(service_class, user, resource, options) do |instance| allow(instance).to receive(:execute).and_return(service_response) end + + allow(Gitlab::Llm::Tracking).to receive(:client_for_user_agent) + .and_return(client) end shared_examples 'successful tracking' do @@ -166,6 +171,13 @@ it_behaves_like 'successful tracking' end + + context 'when client is detected' do + let(:client) { 'web' } + let(:expected_params) { default_params.merge(client: 'web') } + + it_behaves_like 'successful tracking' + end end end end diff --git a/lib/gitlab/regex.rb b/lib/gitlab/regex.rb index 5d3d1795df7bc80faf0967640411d51a0a03a080..cd23ad92eecc267827c8f4028c9a69ebc019c082 100644 --- a/lib/gitlab/regex.rb +++ b/lib/gitlab/regex.rb @@ -282,10 +282,6 @@ def ml_model_version_name_regex def ml_model_file_name_regex @ml_model_file_name_regex ||= %r{\A[A-Za-z0-9\.\_\-\+ ]+\z} end - - def vs_code_user_agent_regex - /\Avs-code-gitlab-workflow/ - end end end diff --git a/lib/gitlab/usage_data_counters/vscode_extension_activity_unique_counter.rb b/lib/gitlab/usage_data_counters/vscode_extension_activity_unique_counter.rb index ea82a45f870b2b21381592d887356116a1f076de..7b3df134ba31562754ecb8f104f34d29aae60d1d 100644 --- a/lib/gitlab/usage_data_counters/vscode_extension_activity_unique_counter.rb +++ b/lib/gitlab/usage_data_counters/vscode_extension_activity_unique_counter.rb @@ -4,11 +4,11 @@ module Gitlab module UsageDataCounters module VSCodeExtensionActivityUniqueCounter VS_CODE_API_REQUEST_ACTION = 'i_code_review_user_vs_code_api_request' + VS_CODE_USER_AGENT_REGEX = /\Avs-code-gitlab-workflow/ class << self def track_api_request_when_trackable(user_agent:, user:) - user_agent&.match?(Gitlab::Regex.vs_code_user_agent_regex) && track_unique_action_by_user( - VS_CODE_API_REQUEST_ACTION, user) + user_agent&.match?(VS_CODE_USER_AGENT_REGEX) && track_unique_action_by_user(VS_CODE_API_REQUEST_ACTION, user) end private