From d54db2dadaa864c07fef29a7709ee9b88950a6cd Mon Sep 17 00:00:00 2001
From: Eduardo Bonet <ebonet@gitlab.com>
Date: Wed, 2 Oct 2024 11:52:28 +0200
Subject: [PATCH] Adds Claude 3 to model families

Self-hosted customers can configure Duo to a Claude 3 model that is not
hosted by GitLab
---
 doc/api/graphql/reference/index.md            |  1 +
 .../accepted_models_enum.rb                   |  2 ++
 .../admin/ai/self_hosted_models_helper.rb     | 13 ++++++++++++
 ee/app/models/ai/self_hosted_model.rb         |  3 ++-
 .../ai/self_hosted_models/edit.html.haml      |  4 +---
 .../admin/ai/self_hosted_models/new.html.haml |  4 +---
 .../ai/feature_settings/feature_metadata.yml  |  2 ++
 .../accepted_models_enum_spec.rb              |  1 +
 .../ai/self_hosted_models_helper_spec.rb      | 21 +++++++++++++++++++
 9 files changed, 44 insertions(+), 7 deletions(-)
 create mode 100644 ee/app/helpers/admin/ai/self_hosted_models_helper.rb
 create mode 100644 ee/spec/helpers/admin/ai/self_hosted_models_helper_spec.rb

diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index 39f3b40c5f66b..c1adb1131c454 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -35925,6 +35925,7 @@ LLMs supported by the self-hosted model features.
 
 | Value | Description |
 | ----- | ----------- |
+| <a id="aiacceptedselfhostedmodelsclaude_3"></a>`CLAUDE_3` | Claude 3 model family, suitable for code generation and duo chat. |
 | <a id="aiacceptedselfhostedmodelscodegemma"></a>`CODEGEMMA` | CodeGemma Code: Suitable for code suggestions. |
 | <a id="aiacceptedselfhostedmodelscodellama"></a>`CODELLAMA` | Code-Llama Instruct: Suitable for code suggestions. |
 | <a id="aiacceptedselfhostedmodelscodestral"></a>`CODESTRAL` | Codestral: Suitable for code suggestions. |
diff --git a/ee/app/graphql/types/ai/self_hosted_models/accepted_models_enum.rb b/ee/app/graphql/types/ai/self_hosted_models/accepted_models_enum.rb
index 074df3ef09145..6c314d963d791 100644
--- a/ee/app/graphql/types/ai/self_hosted_models/accepted_models_enum.rb
+++ b/ee/app/graphql/types/ai/self_hosted_models/accepted_models_enum.rb
@@ -13,6 +13,8 @@ class AcceptedModelsEnum < BaseEnum
         value 'MISTRAL', 'Mistral: Suitable for code suggestions and duo chat.', value: 'mistral'
         value 'DEEPSEEKCODER', description: 'Deepseek Coder base or instruct.', value: 'deepseekcoder'
         value 'LLAMA3', description: 'LLaMA 3: Suitable for code suggestions and duo chat.', value: 'llama3'
+        value 'CLAUDE_3', description: 'Claude 3 model family, suitable for code generation and duo chat.',
+          value: 'claude_3'
       end
     end
   end
diff --git a/ee/app/helpers/admin/ai/self_hosted_models_helper.rb b/ee/app/helpers/admin/ai/self_hosted_models_helper.rb
new file mode 100644
index 0000000000000..08610a4f87577
--- /dev/null
+++ b/ee/app/helpers/admin/ai/self_hosted_models_helper.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+module Admin
+  module Ai
+    module SelfHostedModelsHelper
+      def model_choices_as_options
+        ::Ai::SelfHostedModel
+          .models
+          .map { |name, _| { modelValue: name.upcase, modelName: name.capitalize.tr("_", " ") } }
+      end
+    end
+  end
+end
diff --git a/ee/app/models/ai/self_hosted_model.rb b/ee/app/models/ai/self_hosted_model.rb
index 4047a99f2e5e4..6a39899429d9a 100644
--- a/ee/app/models/ai/self_hosted_model.rb
+++ b/ee/app/models/ai/self_hosted_model.rb
@@ -23,7 +23,8 @@ class SelfHostedModel < ApplicationRecord
       codegemma: 2,
       codestral: 3,
       codellama: 4,
-      deepseekcoder: 5
+      deepseekcoder: 5,
+      claude_3: 6
     }
 
     # For now, only OpenAI API format is supported, this method will be potentially
diff --git a/ee/app/views/admin/ai/self_hosted_models/edit.html.haml b/ee/app/views/admin/ai/self_hosted_models/edit.html.haml
index ca0a791f6963f..de28841927945 100644
--- a/ee/app/views/admin/ai/self_hosted_models/edit.html.haml
+++ b/ee/app/views/admin/ai/self_hosted_models/edit.html.haml
@@ -1,9 +1,7 @@
 - add_to_breadcrumbs s_('AdminSelfHostedModels|Self-hosted models'), admin_labels_path
 - breadcrumb_title s_('AdminSelfHostedModels|Edit self-hosted model')
 - if Feature.enabled?(:custom_models_vue_app, current_user)
-  - model_options = Ai::SelfHostedModel.models.map { |name, _| { modelValue: name.upcase, modelName: name.capitalize  } }
-
-  #js-edit-self-hosted-model{ data: { view_model: { model: @self_hosted_model, modelOptions: model_options, basePath: admin_ai_self_hosted_models_path }.to_json(methods: [:api_token]) } }
+  #js-edit-self-hosted-model{ data: { view_model: { model: @self_hosted_model, modelOptions: model_choices_as_options, basePath: admin_ai_self_hosted_models_path }.to_json(methods: [:api_token]) } }
 - else
   - page_title _('Edit'), @self_hosted_model.model, s_('AdminSelfHostedModels|Self-hosted models')
   %h1.page-title.gl-text-size-h-display
diff --git a/ee/app/views/admin/ai/self_hosted_models/new.html.haml b/ee/app/views/admin/ai/self_hosted_models/new.html.haml
index 949a5422b0c40..a90359873c929 100644
--- a/ee/app/views/admin/ai/self_hosted_models/new.html.haml
+++ b/ee/app/views/admin/ai/self_hosted_models/new.html.haml
@@ -1,8 +1,6 @@
 - page_title s_('AdminSelfHostedModels|Add self-hosted models')
 - if Feature.enabled?(:custom_models_vue_app, current_user)
-  - model_options = Ai::SelfHostedModel.models.map { |name, _| { modelValue: name.upcase, modelName: name.capitalize } }
-
-  #js-new-self-hosted-model{ data: { view_model: { basePath: admin_ai_self_hosted_models_path, modelOptions: model_options }.to_json } }
+  #js-new-self-hosted-model{ data: { view_model: { basePath: admin_ai_self_hosted_models_path, modelOptions: model_choices_as_options }.to_json } }
 - else
   %h1.page-title.gl-text-size-h-display
     = s_('AdminSelfHostedModels|Add self-hosted models')
diff --git a/ee/lib/gitlab/ai/feature_settings/feature_metadata.yml b/ee/lib/gitlab/ai/feature_settings/feature_metadata.yml
index 1fa8ceda2b377..af0acf10bfd3b 100644
--- a/ee/lib/gitlab/ai/feature_settings/feature_metadata.yml
+++ b/ee/lib/gitlab/ai/feature_settings/feature_metadata.yml
@@ -9,6 +9,7 @@ code_generations:
     - deepseekcoder
     - mistral
     - llama3
+    - claude_3
 code_completions:
   title: Code Completion
   release_state: GA
@@ -26,3 +27,4 @@ duo_chat:
   main_feature: Duo Chat
   compatible_llms:
     - mistral
+    - claude_3
diff --git a/ee/spec/graphql/types/ai/self_hosted_models/accepted_models_enum_spec.rb b/ee/spec/graphql/types/ai/self_hosted_models/accepted_models_enum_spec.rb
index 9aa5f48e07451..25189123cdc59 100644
--- a/ee/spec/graphql/types/ai/self_hosted_models/accepted_models_enum_spec.rb
+++ b/ee/spec/graphql/types/ai/self_hosted_models/accepted_models_enum_spec.rb
@@ -13,6 +13,7 @@
       MISTRAL
       DEEPSEEKCODER
       LLAMA3
+      CLAUDE_3
     ])
   end
 end
diff --git a/ee/spec/helpers/admin/ai/self_hosted_models_helper_spec.rb b/ee/spec/helpers/admin/ai/self_hosted_models_helper_spec.rb
new file mode 100644
index 0000000000000..df5fc482499be
--- /dev/null
+++ b/ee/spec/helpers/admin/ai/self_hosted_models_helper_spec.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+require "spec_helper"
+
+RSpec.describe Admin::Ai::SelfHostedModelsHelper, feature_category: :"self-hosted_models" do
+  describe '#model_choices_as_options' do
+    it 'returns an array of hashes with model options' do
+      expected_result = [
+        { modelValue: "MISTRAL", modelName: "Mistral" },
+        { modelValue: "LLAMA3", modelName: "Llama3" },
+        { modelValue: "CODEGEMMA", modelName: "Codegemma" },
+        { modelValue: "CODESTRAL", modelName: "Codestral" },
+        { modelValue: "CODELLAMA", modelName: "Codellama" },
+        { modelValue: "DEEPSEEKCODER", modelName: "Deepseekcoder" },
+        { modelValue: "CLAUDE_3", modelName: "Claude 3" }
+      ]
+
+      expect(helper.model_choices_as_options).to match_array(expected_result)
+    end
+  end
+end
-- 
GitLab