diff --git a/doc/api/graphql/reference/_index.md b/doc/api/graphql/reference/_index.md
index ff8013243a2a2e3b42add2978b53cbd936c0d467..25680cea828797910194c2af9a6aa287b7d41199 100644
--- a/doc/api/graphql/reference/_index.md
+++ b/doc/api/graphql/reference/_index.md
@@ -2498,6 +2498,7 @@ Input type: `AiCatalogAgentUpdateInput`
| `systemPrompt` | [`String`](#string) | System prompt for the agent. |
| `tools` | [`[AiCatalogBuiltInToolID!]`](#aicatalogbuiltintoolid) | List of GitLab tools enabled for the agent. |
| `userPrompt` | [`String`](#string) | User prompt for the agent. |
+| `versionBump` | [`AiCatalogVersionBump`](#aicatalogversionbump) | Bump version, calculated from the last released version name. |
#### Fields
@@ -46171,6 +46172,16 @@ Possible item types for AI items.
| `AGENT` | Agent. |
| `FLOW` | Flow. |
+### `AiCatalogVersionBump`
+
+Possible version bumps for AI catalog items.
+
+| Value | Description |
+| ----- | ----------- |
+| `MAJOR` | Major version bump. |
+| `MINOR` | Minor version bump. |
+| `PATCH` | Patch version bump. |
+
### `AiConversationsThreadsConversationType`
Conversation type of the thread.
diff --git a/ee/app/graphql/mutations/ai/catalog/agent/update.rb b/ee/app/graphql/mutations/ai/catalog/agent/update.rb
index 5302e98e8f9e9d70a933d459e8fe65202202b6dc..b0232ac45571c43ffdfc8597353b5339f3f8e230 100644
--- a/ee/app/graphql/mutations/ai/catalog/agent/update.rb
+++ b/ee/app/graphql/mutations/ai/catalog/agent/update.rb
@@ -45,6 +45,10 @@ class Update < BaseMutation
required: false,
description: 'User prompt for the agent.'
+ argument :version_bump, Types::Ai::Catalog::VersionBumpEnum,
+ required: false,
+ description: 'Bump version, calculated from the last released version name.'
+
authorize :admin_ai_catalog_item
def resolve(args)
diff --git a/ee/app/graphql/types/ai/catalog/version_bump_enum.rb b/ee/app/graphql/types/ai/catalog/version_bump_enum.rb
new file mode 100644
index 0000000000000000000000000000000000000000..19aaea7f0096c003dba605a9771acc2af54c2906
--- /dev/null
+++ b/ee/app/graphql/types/ai/catalog/version_bump_enum.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+module Types
+ module Ai
+ module Catalog
+ class VersionBumpEnum < BaseEnum
+ graphql_name 'AiCatalogVersionBump'
+ description 'Possible version bumps for AI catalog items.'
+
+ ::Ai::Catalog::ItemVersion::VERSION_BUMP_OPTIONS.each do |version_bump|
+ value version_bump.upcase, description: "#{version_bump.to_s.capitalize} version bump.", value: version_bump
+ end
+ end
+ end
+ end
+end
diff --git a/ee/app/models/ai/catalog/item_version.rb b/ee/app/models/ai/catalog/item_version.rb
index a89026b369db10fb313b7df8ca6b3e012a7205e1..a043be4e70028012c5bae5f099213e013fe55f0c 100644
--- a/ee/app/models/ai/catalog/item_version.rb
+++ b/ee/app/models/ai/catalog/item_version.rb
@@ -12,6 +12,7 @@ class ItemVersion < ApplicationRecord
VERSION_BUMP_MAJOR = :major
VERSION_BUMP_MINOR = :minor
VERSION_BUMP_PATCH = :patch
+ VERSION_BUMP_OPTIONS = [VERSION_BUMP_MAJOR, VERSION_BUMP_MINOR, VERSION_BUMP_PATCH].freeze
self.table_name = "ai_catalog_item_versions"
@@ -55,6 +56,7 @@ def draft?
def version_bump(bump_level)
return if version.nil?
+ raise ArgumentError, "unknown bump_level: #{bump_level}" unless bump_level.in?(VERSION_BUMP_OPTIONS)
old_version = Gitlab::VersionInfo.parse(version)
@@ -65,8 +67,6 @@ def version_bump(bump_level)
[old_version.major, old_version.minor + 1, 0]
when VERSION_BUMP_PATCH
[old_version.major, old_version.minor, old_version.patch + 1]
- else
- raise ArgumentError, "unknown bump_level: #{bump_level}"
end
Gitlab::VersionInfo.new(*new_version).to_s
diff --git a/ee/app/services/ai/catalog/agents/update_service.rb b/ee/app/services/ai/catalog/agents/update_service.rb
index 9364263fbc6f82d1412789d0eda35d82f86ed118..1b57bcff623bec16a34d5277eb8672fdf79a1e80 100644
--- a/ee/app/services/ai/catalog/agents/update_service.rb
+++ b/ee/app/services/ai/catalog/agents/update_service.rb
@@ -51,6 +51,7 @@ def prepare_version_to_update
version_to_update.schema_version = Ai::Catalog::ItemVersion::AGENT_SCHEMA_VERSION
end
+ version_to_update.version = calculate_next_version if should_calculate_version?(version_to_update)
version_to_update.release_date ||= Time.zone.now if params[:release] == true
version_to_update
end
@@ -60,10 +61,9 @@ def determine_version_to_update
version_params = build_version_params(latest_version)
latest_version.assign_attributes(version_params)
- return latest_version unless latest_version.changed?
return latest_version unless should_create_new_version?(latest_version)
- build_new_version(latest_version, version_params)
+ build_new_version(version_params)
end
def build_version_params(latest_version)
@@ -78,21 +78,26 @@ def build_version_params(latest_version)
end
def should_create_new_version?(version)
- version.released? && version.enforce_readonly_versions?
+ version.definition_changed? && version.released? && version.enforce_readonly_versions?
end
- def build_new_version(latest_version, version_params)
- new_version_params = version_params.merge(
- version: calculate_next_version(latest_version)
- )
+ def build_new_version(version_params)
+ agent.build_new_version(version_params)
+ end
- agent.build_new_version(new_version_params)
+ def should_calculate_version?(version)
+ version.new_record? || params[:version_bump].present?
end
- def calculate_next_version(latest_version)
- # TODO: Support params[:version_bump] parameter.
- # For now, always make a major version bump.
- latest_version.version_bump(Ai::Catalog::ItemVersion::VERSION_BUMP_MAJOR)
+ def calculate_next_version
+ # TODO replace with agent.latest_released_version once
+ # https://gitlab.com/gitlab-org/gitlab/-/issues/554673 is completed
+ latest_released_version = agent.versions.where.not(release_date: nil).order(id: :desc).take # rubocop:disable CodeReuse/ActiveRecord -- Will be fixed after https://gitlab.com/gitlab-org/gitlab/-/issues/554673
+
+ return BaseService::DEFAULT_VERSION unless latest_released_version
+
+ bump_level = params[:version_bump] || Ai::Catalog::ItemVersion::VERSION_BUMP_MAJOR
+ latest_released_version.version_bump(bump_level)
end
end
end
diff --git a/ee/spec/factories/ai/catalog/item_versions.rb b/ee/spec/factories/ai/catalog/item_versions.rb
index eb14b04da0217df88c0286be3ed9eca470ac4c5c..6b1bda42c61bfc2b00c5798e3feb8b2a79c8da0a 100644
--- a/ee/spec/factories/ai/catalog/item_versions.rb
+++ b/ee/spec/factories/ai/catalog/item_versions.rb
@@ -2,7 +2,7 @@
FactoryBot.define do
factory :ai_catalog_item_version, class: 'Ai::Catalog::ItemVersion' do
- version { FFaker::Number.unique.number(digits: 6).to_s.scan(/.{2}/).join('.') }
+ sequence(:version) { |n| "#{n}.0.99" }
schema_version { 1 }
for_agent
release_date { nil }
diff --git a/ee/spec/graphql/mutations/ai/catalog/agent/update_spec.rb b/ee/spec/graphql/mutations/ai/catalog/agent/update_spec.rb
index 65303af94c54b4295a2481553e566fc623c6314d..b7bbfff56a52c9323059a925b8063dcdb5d4995d 100644
--- a/ee/spec/graphql/mutations/ai/catalog/agent/update_spec.rb
+++ b/ee/spec/graphql/mutations/ai/catalog/agent/update_spec.rb
@@ -23,6 +23,7 @@
:system_prompt,
:user_prompt,
:tools,
+ :version_bump,
:client_mutation_id
)
end
diff --git a/ee/spec/graphql/types/ai/catalog/version_bump_enum_spec.rb b/ee/spec/graphql/types/ai/catalog/version_bump_enum_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..b3703b5228d9f69e4943980446d5d4ec6482b33f
--- /dev/null
+++ b/ee/spec/graphql/types/ai/catalog/version_bump_enum_spec.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe GitlabSchema.types['AiCatalogVersionBump'], feature_category: :workflow_catalog do
+ it 'exposes all version bump options' do
+ expect(described_class.values.keys).to match_array(
+ Ai::Catalog::ItemVersion::VERSION_BUMP_OPTIONS.map { |o| o.to_s.upcase }
+ )
+ end
+end
diff --git a/ee/spec/requests/api/graphql/mutations/ai/catalog/agent/update_spec.rb b/ee/spec/requests/api/graphql/mutations/ai/catalog/agent/update_spec.rb
index cdddba13446b5c6ae71ee39b85b27cfe03a6ea4e..8d15f8b7c5efd637f7b3d4c0fb387f282799e878 100644
--- a/ee/spec/requests/api/graphql/mutations/ai/catalog/agent/update_spec.rb
+++ b/ee/spec/requests/api/graphql/mutations/ai/catalog/agent/update_spec.rb
@@ -8,6 +8,10 @@
let_it_be(:maintainer) { create(:user) }
let_it_be(:project) { create(:project, maintainers: maintainer) }
let_it_be_with_reload(:agent) { create(:ai_catalog_item, project: project) }
+ let_it_be_with_reload(:latest_released_version) do
+ create(:ai_catalog_item_version, :released, version: '1.0.0', item: agent)
+ end
+
let_it_be_with_reload(:latest_version) { create(:ai_catalog_item_version, version: '1.1.0', item: agent) }
let(:current_user) { maintainer }
@@ -49,7 +53,8 @@
release: true,
system_prompt: 'New system prompt',
tools: tools.map { |tool| global_id_of(tool) },
- user_prompt: 'New user prompt'
+ user_prompt: 'New user prompt',
+ version_bump: 'PATCH'
}
end
@@ -130,7 +135,7 @@
expect(latest_version.reload).to have_attributes(
schema_version: 1,
- version: '1.1.0',
+ version: '1.0.1',
release_date: Time.zone.now,
definition: {
system_prompt: 'New system prompt',
diff --git a/ee/spec/services/ai/catalog/agents/update_service_spec.rb b/ee/spec/services/ai/catalog/agents/update_service_spec.rb
index df6889a21fbbb26eaef8c0fda9c343f336b4bf40..b1d885bdb0a93e5b754624bca35eb5c7ee5d4c0b 100644
--- a/ee/spec/services/ai/catalog/agents/update_service_spec.rb
+++ b/ee/spec/services/ai/catalog/agents/update_service_spec.rb
@@ -6,6 +6,10 @@
let_it_be(:project) { create(:project) }
let_it_be(:user) { create(:user) }
let_it_be_with_reload(:agent) { create(:ai_catalog_item, public: false, project: project) }
+ let_it_be_with_reload(:latest_released_version) do
+ create(:ai_catalog_item_version, :released, version: '1.0.0', item: agent)
+ end
+
let_it_be_with_reload(:latest_version) { create(:ai_catalog_item_version, version: '1.1.0', item: agent) }
let(:tools) { Ai::Catalog::BuiltInTool.where(id: [1, 9]) }
@@ -84,6 +88,46 @@
)
end
+ context 'when version_bump is provided' do
+ let(:params) { super().merge(version_bump: :patch) }
+
+ it 'sets the version correctly' do
+ execute_service
+
+ expect(latest_version.reload).to have_attributes(
+ schema_version: 1,
+ version: '1.0.1',
+ release_date: Time.zone.now,
+ definition: {
+ user_prompt: 'New user prompt',
+ tools: [1, 9],
+ system_prompt: 'New system prompt'
+ }.stringify_keys
+ )
+ end
+
+ context 'when there is no previous released version' do
+ before do
+ latest_released_version.destroy!
+ end
+
+ it 'sets the version to the default version' do
+ execute_service
+
+ expect(latest_version.reload).to have_attributes(
+ schema_version: 1,
+ version: '1.0.0',
+ release_date: Time.zone.now,
+ definition: {
+ user_prompt: 'New user prompt',
+ tools: [1, 9],
+ system_prompt: 'New system prompt'
+ }.stringify_keys
+ )
+ end
+ end
+ end
+
it 'returns success response with item in payload', :aggregate_failures do
result = execute_service
@@ -122,6 +166,25 @@
expect { execute_service }.not_to change { latest_version.reload.attributes }
end
+ context 'when version_bump is provided' do
+ let(:params) { super().merge(version_bump: :patch) }
+
+ it 'sets the version correctly' do
+ execute_service
+
+ expect(agent.reload.latest_version).to have_attributes(
+ schema_version: 1,
+ version: '1.1.1',
+ release_date: Time.zone.now,
+ definition: {
+ user_prompt: 'New user prompt',
+ tools: [1, 9],
+ system_prompt: 'New system prompt'
+ }.stringify_keys
+ )
+ end
+ end
+
context 'when the `ai_catalog_enforce_readonly_versions` flag is disabled' do
before do
stub_feature_flags(ai_catalog_enforce_readonly_versions: false)
diff --git a/ee/spec/services/ai/catalog/flows/update_service_spec.rb b/ee/spec/services/ai/catalog/flows/update_service_spec.rb
index 1ab99319d8f0fa4c36c365806e107e935b99d448..b1bbcaebc26684ec6b26aeafe9b9f6f5ad251a4b 100644
--- a/ee/spec/services/ai/catalog/flows/update_service_spec.rb
+++ b/ee/spec/services/ai/catalog/flows/update_service_spec.rb
@@ -97,9 +97,9 @@
end
context 'when the prefix is not valid' do
- let(:params) { super().merge(steps: [{ agent: agent, pinned_version_prefix: '2' }]) }
+ let(:params) { super().merge(steps: [{ agent: agent, pinned_version_prefix: '2.2' }]) }
- it_behaves_like 'an error response', 'Step 1: Unable to resolve version with prefix 2'
+ it_behaves_like 'an error response', 'Step 1: Unable to resolve version with prefix 2.2'
end
end