diff --git a/ee/config/feature_flags/beta/code_creation_slash_commands_claude_3_7.yml b/ee/config/feature_flags/beta/code_creation_slash_commands_claude_3_7.yml new file mode 100644 index 0000000000000000000000000000000000000000..990cf6b3b9220378f29212ac38ef0cb61b36fd8a --- /dev/null +++ b/ee/config/feature_flags/beta/code_creation_slash_commands_claude_3_7.yml @@ -0,0 +1,9 @@ +--- +name: code_creation_slash_commands_claude_3_7 +feature_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/521410 +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/182789 +rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/521423 +milestone: '17.10' +group: group::code creation +type: beta +default_enabled: false diff --git a/ee/lib/gitlab/llm/chain/concerns/ai_dependent.rb b/ee/lib/gitlab/llm/chain/concerns/ai_dependent.rb index fa473b9a375b9d3eb2279501483c65f115acf39c..2701cdabfe144870672d8bff7c59033c13af178f 100644 --- a/ee/lib/gitlab/llm/chain/concerns/ai_dependent.rb +++ b/ee/lib/gitlab/llm/chain/concerns/ai_dependent.rb @@ -25,7 +25,8 @@ def request(&block) prompt_str[:options] ||= {} prompt_str[:options].merge!({ use_ai_gateway_agent_prompt: true, - inputs: prompt_options + inputs: prompt_options, + prompt_version: prompt_version }) end diff --git a/ee/lib/gitlab/llm/chain/requests/ai_gateway.rb b/ee/lib/gitlab/llm/chain/requests/ai_gateway.rb index 0db3a2f08df694526a5dd36f957c9f4ada5c0e68..61082a38270578f005b9f7e20cf708755efff8d7 100644 --- a/ee/lib/gitlab/llm/chain/requests/ai_gateway.rb +++ b/ee/lib/gitlab/llm/chain/requests/ai_gateway.rb @@ -111,7 +111,8 @@ def endpoint(unit_primitive, use_ai_gateway_agent_prompt) def body(prompt, options, unit_primitive: nil) if options[:use_ai_gateway_agent_prompt] - request_body_agent(inputs: options[:inputs], unit_primitive: unit_primitive) + request_body_agent(inputs: options[:inputs], unit_primitive: unit_primitive, + prompt_version: options[:prompt_version]) else request_body(prompt: prompt[:prompt], options: options) end @@ -133,12 +134,14 @@ def request_body(prompt:, options: {}) } end - def request_body_agent(inputs:, unit_primitive: nil) + def request_body_agent(inputs:, unit_primitive: nil, prompt_version: nil) params = { stream: true, inputs: inputs } + params[:prompt_version] = prompt_version if prompt_version.present? + feature_setting = chat_feature_setting(unit_primitive: unit_primitive) if feature_setting&.self_hosted? diff --git a/ee/lib/gitlab/llm/chain/tools/explain_code/executor.rb b/ee/lib/gitlab/llm/chain/tools/explain_code/executor.rb index 455fa97a60396d4004579750919a236c35e44e31..54810ff52f505b1f918dd1b32a40d1f5ef4fcb95 100644 --- a/ee/lib/gitlab/llm/chain/tools/explain_code/executor.rb +++ b/ee/lib/gitlab/llm/chain/tools/explain_code/executor.rb @@ -45,6 +45,8 @@ class Executor < SlashCommandTool ) ].freeze + PROMPT_VERSION = '^1.0.0' + SLASH_COMMANDS = { '/explain' => { description: 'Explain the code', @@ -74,6 +76,13 @@ def allow_blank_message? false end + override :prompt_version + def prompt_version + return '0.0.1-dev' if Feature.enabled?(:code_creation_slash_commands_claude_3_7, context.current_user) + + PROMPT_VERSION + end + def authorize Utils::ChatAuthorizer.context(context: context).allowed? end diff --git a/ee/lib/gitlab/llm/chain/tools/fix_code/executor.rb b/ee/lib/gitlab/llm/chain/tools/fix_code/executor.rb index b34e5c0c2819ed645d969b00113457cc5d8d53d9..42b4684cfcf2463899340280e6fd5dd2c7253b38 100644 --- a/ee/lib/gitlab/llm/chain/tools/fix_code/executor.rb +++ b/ee/lib/gitlab/llm/chain/tools/fix_code/executor.rb @@ -52,6 +52,8 @@ def hello_world ) ].freeze + PROMPT_VERSION = '^1.0.0' + SLASH_COMMANDS = { '/fix' => { description: 'Fix any errors in the code', @@ -86,6 +88,13 @@ def context_options { libraries: context.libraries } end + override :prompt_version + def prompt_version + return '0.0.1-dev' if Feature.enabled?(:code_creation_slash_commands_claude_3_7, context.current_user) + + PROMPT_VERSION + end + def selected_text_options super.tap do |opts| opts[:file_content_reuse] = diff --git a/ee/lib/gitlab/llm/chain/tools/refactor_code/executor.rb b/ee/lib/gitlab/llm/chain/tools/refactor_code/executor.rb index 4406ce8794f38e38081072ed209792a20ea8f2d1..471fe0573729e8752a4934f2d16eab4fdae62728 100644 --- a/ee/lib/gitlab/llm/chain/tools/refactor_code/executor.rb +++ b/ee/lib/gitlab/llm/chain/tools/refactor_code/executor.rb @@ -51,6 +51,8 @@ def hello_world ) ].freeze + PROMPT_VERSION = '^1.0.0' + SLASH_COMMANDS = { '/refactor' => { description: 'Refactor the code', @@ -85,6 +87,13 @@ def context_options { libraries: context.libraries } end + override :prompt_version + def prompt_version + return '0.0.1-dev' if Feature.enabled?(:code_creation_slash_commands_claude_3_7, context.current_user) + + PROMPT_VERSION + end + def selected_text_options super.tap do |opts| opts[:file_content_reuse] = diff --git a/ee/lib/gitlab/llm/chain/tools/tool.rb b/ee/lib/gitlab/llm/chain/tools/tool.rb index f1e2ee64ef1ab5aa827e7d82306b7b4c0b34859f..72c82723e3d003f208bf59298c68484d8a9d01c9 100644 --- a/ee/lib/gitlab/llm/chain/tools/tool.rb +++ b/ee/lib/gitlab/llm/chain/tools/tool.rb @@ -13,6 +13,8 @@ class Tool DESCRIPTION = 'Base Tool description' EXAMPLE = 'Example description' + DEFAULT_PROMPT_VERSION = '^1.0.0' + attr_reader :context, :options delegate :resource, :resource=, to: :context @@ -151,6 +153,10 @@ def prompt_options options end + def prompt_version + DEFAULT_PROMPT_VERSION + end + def access_forbidden(_error) content = <<~MESSAGE I'm sorry, this question is not supported in your Duo Pro subscription. You might consider upgrading to Duo Enterprise. Selected tool: #{self.class.name} diff --git a/ee/lib/gitlab/llm/chain/tools/write_tests/executor.rb b/ee/lib/gitlab/llm/chain/tools/write_tests/executor.rb index 36a1e574a587dd04e8a2d15677ca639aa3675808..1f6bbcbb5e1fb927a9fdfd547d70deeeb32b454c 100644 --- a/ee/lib/gitlab/llm/chain/tools/write_tests/executor.rb +++ b/ee/lib/gitlab/llm/chain/tools/write_tests/executor.rb @@ -50,6 +50,8 @@ def hello_world ) ].freeze + PROMPT_VERSION = '^1.0.0' + SLASH_COMMANDS = { '/tests' => { description: 'Write tests for the code', @@ -84,6 +86,13 @@ def context_options { libraries: context.libraries } end + override :prompt_version + def prompt_version + return '0.0.1-dev' if Feature.enabled?(:code_creation_slash_commands_claude_3_7, context.current_user) + + PROMPT_VERSION + end + def authorize Utils::ChatAuthorizer.context(context: context).allowed? end diff --git a/ee/spec/lib/gitlab/llm/chain/concerns/ai_dependent_spec.rb b/ee/spec/lib/gitlab/llm/chain/concerns/ai_dependent_spec.rb index b70cbdced507da2e1f9082b4133acc8cb84d8d36..99385dfc585f42e4e9a430a2cd034e4db446d40e 100644 --- a/ee/spec/lib/gitlab/llm/chain/concerns/ai_dependent_spec.rb +++ b/ee/spec/lib/gitlab/llm/chain/concerns/ai_dependent_spec.rb @@ -78,7 +78,10 @@ def provider_prompt_class describe '#request' do let(:tool) { ::Gitlab::Llm::Chain::Tools::IssueReader::Executor.new(context: context, options: options) } - let(:prompt_options) { tool.prompt.deep_merge({ options: { inputs: options, use_ai_gateway_agent_prompt: true } }) } + let(:prompt_options) do + tool.prompt.deep_merge({ options: { inputs: options, use_ai_gateway_agent_prompt: true, + prompt_version: '^1.0.0' } }) + end before do allow(Gitlab::Llm::Logger).to receive(:build).and_return(logger) @@ -126,7 +129,8 @@ def prompt_options inputs: { field: :test_field }, - use_ai_gateway_agent_prompt: true + use_ai_gateway_agent_prompt: true, + prompt_version: '^1.0.0' } ), unit_primitive: :test diff --git a/ee/spec/lib/gitlab/llm/chain/tools/explain_code/executor_spec.rb b/ee/spec/lib/gitlab/llm/chain/tools/explain_code/executor_spec.rb index 44b5a7e0520ef93643b2a0cf64eb065570870211..ee3926af52ee4c1a23371f7fc4f95366ae4d2d51 100644 --- a/ee/spec/lib/gitlab/llm/chain/tools/explain_code/executor_spec.rb +++ b/ee/spec/lib/gitlab/llm/chain/tools/explain_code/executor_spec.rb @@ -43,6 +43,16 @@ ) end + context 'when using claude 3.7 FF' do + include_context 'with stubbed LLM authorizer', allowed: true + + it_behaves_like 'uses code_creation_slash_commands_claude_3_7 FF correctly' do + let(:prompt_class) { Gitlab::Llm::Chain::Tools::ExplainCode::Prompts::Anthropic } + let(:unit_primitive) { 'explain_code' } + let(:default_unit_primitive) { unit_primitive } + end + end + describe '#name' do it 'returns tool name' do expect(described_class::NAME).to eq('ExplainCode') diff --git a/ee/spec/lib/gitlab/llm/chain/tools/fix_code/executor_spec.rb b/ee/spec/lib/gitlab/llm/chain/tools/fix_code/executor_spec.rb index 559572bb2ac140f97b95efc34c470a9eda5ded39..416da2d621f0bd737e0c5ced2fb9cfb77df8f8be 100644 --- a/ee/spec/lib/gitlab/llm/chain/tools/fix_code/executor_spec.rb +++ b/ee/spec/lib/gitlab/llm/chain/tools/fix_code/executor_spec.rb @@ -47,6 +47,16 @@ ) end + context 'when using claude 3.7 FF' do + include_context 'with stubbed LLM authorizer', allowed: true + + it_behaves_like 'uses code_creation_slash_commands_claude_3_7 FF correctly' do + let(:prompt_class) { Gitlab::Llm::Chain::Tools::FixCode::Prompts::Anthropic } + let(:unit_primitive) { 'fix_code' } + let(:default_unit_primitive) { unit_primitive } + end + end + describe '#name' do it 'returns tool name' do expect(described_class::NAME).to eq('FixCode') diff --git a/ee/spec/lib/gitlab/llm/chain/tools/refactor_code/executor_spec.rb b/ee/spec/lib/gitlab/llm/chain/tools/refactor_code/executor_spec.rb index 50d9a0e83ef9db5562a2c4cc574f3269c05ec7c8..56bcfb390dfafe92ce7fffe36cdd5a0643aa6a6d 100644 --- a/ee/spec/lib/gitlab/llm/chain/tools/refactor_code/executor_spec.rb +++ b/ee/spec/lib/gitlab/llm/chain/tools/refactor_code/executor_spec.rb @@ -44,6 +44,16 @@ ) end + context 'when using claude 3.7 FF' do + include_context 'with stubbed LLM authorizer', allowed: true + + it_behaves_like 'uses code_creation_slash_commands_claude_3_7 FF correctly' do + let(:prompt_class) { Gitlab::Llm::Chain::Tools::RefactorCode::Prompts::Anthropic } + let(:unit_primitive) { 'refactor_code' } + let(:default_unit_primitive) { unit_primitive } + end + end + describe '#name' do it 'returns tool name' do expect(described_class::NAME).to eq('RefactorCode') diff --git a/ee/spec/lib/gitlab/llm/chain/tools/write_tests/executor_spec.rb b/ee/spec/lib/gitlab/llm/chain/tools/write_tests/executor_spec.rb index a211942bcbdb953ad6765fe5babb8ee044948c82..b2fa3b8c053323c82461cad1946ad86d39096c04 100644 --- a/ee/spec/lib/gitlab/llm/chain/tools/write_tests/executor_spec.rb +++ b/ee/spec/lib/gitlab/llm/chain/tools/write_tests/executor_spec.rb @@ -39,6 +39,16 @@ ) end + context 'when using claude 3.7 FF' do + include_context 'with stubbed LLM authorizer', allowed: true + + it_behaves_like 'uses code_creation_slash_commands_claude_3_7 FF correctly' do + let(:prompt_class) { Gitlab::Llm::Chain::Tools::WriteTests::Prompts::Anthropic } + let(:unit_primitive) { 'write_tests' } + let(:default_unit_primitive) { unit_primitive } + end + end + describe '#name' do it 'returns tool name' do expect(described_class::NAME).to eq('WriteTests') diff --git a/ee/spec/support/shared_examples/lib/gitlab/llm/chain/uses_ai_gateway_agent_prompt_shared_examples.rb b/ee/spec/support/shared_examples/lib/gitlab/llm/chain/uses_ai_gateway_agent_prompt_shared_examples.rb index bbb7516b95a99752b5685076637a273ba5e05a76..26ea9af0469fc27ddf3f0d2ca8edc8335dd190f2 100644 --- a/ee/spec/support/shared_examples/lib/gitlab/llm/chain/uses_ai_gateway_agent_prompt_shared_examples.rb +++ b/ee/spec/support/shared_examples/lib/gitlab/llm/chain/uses_ai_gateway_agent_prompt_shared_examples.rb @@ -11,6 +11,7 @@ # - unit_primitive RSpec.shared_examples 'uses ai gateway agent prompt' do let(:inputs) { tool.send(:prompt_options) } + let(:prompt_version) { '^1.0.0' } let(:default_unit_primitive) { nil } before do @@ -27,7 +28,8 @@ prompt[:options] ||= {} prompt[:options].merge!({ use_ai_gateway_agent_prompt: true, - inputs: inputs + inputs: inputs, + prompt_version: prompt_version }) expect(ai_request_double).to receive(:request).with(prompt, unit_primitive: unit_primitive) diff --git a/ee/spec/support/shared_examples/lib/gitlab/llm/chain/uses_claude_3_7_feature_flag_shared_examples.rb b/ee/spec/support/shared_examples/lib/gitlab/llm/chain/uses_claude_3_7_feature_flag_shared_examples.rb new file mode 100644 index 0000000000000000000000000000000000000000..98fabfb346228258840dbe05adaeaba4435a57c5 --- /dev/null +++ b/ee/spec/support/shared_examples/lib/gitlab/llm/chain/uses_claude_3_7_feature_flag_shared_examples.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'uses code_creation_slash_commands_claude_3_7 FF correctly' do + before do + allow(tool).to receive(:provider_prompt_class).and_return(prompt_class) + + allow(Gitlab::Llm::Chain::Requests::AiGateway).to receive(:new).with(user, { + service_name: unit_primitive.to_sym, + tracking_context: { request_id: nil, action: unit_primitive } + }).and_return(ai_request_double) + end + + describe 'without # frozen_string_literal: true FF' do + before do + stub_feature_flags(code_creation_slash_commands_claude_3_7: false) + end + + it 'receives the default prompt verison' do + expect(ai_request_double).to receive(:request).with( + hash_including(options: hash_including(prompt_version: '^1.0.0')), unit_primitive: unit_primitive + ) + + tool.execute + end + end + + describe 'with code_creation_slash_commands_claude_3_7 FF' do + before do + stub_feature_flags(code_creation_slash_commands_claude_3_7: true) + end + + it 'receives the dev prompt verison' do + expect(ai_request_double).to receive(:request).with( + hash_including(options: hash_including(prompt_version: '0.0.1-dev')), unit_primitive: unit_primitive + ) + + tool.execute + end + end +end