diff --git a/ee/config/feature_flags/gitlab_com_derisk/resolve_vulnerability_claude_3_7_rollout.yml b/ee/config/feature_flags/gitlab_com_derisk/resolve_vulnerability_claude_3_7_rollout.yml new file mode 100644 index 0000000000000000000000000000000000000000..50a1877a6c284b5d4612c1e4834a9d6bc1100b40 --- /dev/null +++ b/ee/config/feature_flags/gitlab_com_derisk/resolve_vulnerability_claude_3_7_rollout.yml @@ -0,0 +1,9 @@ +--- +name: resolve_vulnerability_claude_3_7_rollout +feature_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/523496 +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/183607 +rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/523503 +milestone: '17.11' +group: group::security insights +type: gitlab_com_derisk +default_enabled: false diff --git a/ee/lib/gitlab/llm/ai_gateway/completions/resolve_vulnerability.rb b/ee/lib/gitlab/llm/ai_gateway/completions/resolve_vulnerability.rb index 41800a443e5b177d126158ff9bd6f244910ae4e3..a0f0d8f1f1a903eeb8c82cf5b0bb6817736c6786 100644 --- a/ee/lib/gitlab/llm/ai_gateway/completions/resolve_vulnerability.rb +++ b/ee/lib/gitlab/llm/ai_gateway/completions/resolve_vulnerability.rb @@ -14,6 +14,8 @@ class ResolveVulnerability < Base EmptyResponseError = Class.new(StandardError) + DEV_PROMPT = "0.0.1-dev" + override :execute def execute return unless valid? @@ -52,6 +54,11 @@ def inputs private + override :prompt_version + def prompt_version + DEV_PROMPT if Feature.enabled?(:resolve_vulnerability_claude_3_7_rollout, user) + end + def request! response = perform_ai_gateway_request! diff --git a/ee/spec/lib/gitlab/llm/ai_gateway/completions/resolve_vulnerability_spec.rb b/ee/spec/lib/gitlab/llm/ai_gateway/completions/resolve_vulnerability_spec.rb index 89f8c81600ac0e3071d87b288be749f6821f3724..60122b7e7784fda5d325679e4fdf71022af418c8 100644 --- a/ee/spec/lib/gitlab/llm/ai_gateway/completions/resolve_vulnerability_spec.rb +++ b/ee/spec/lib/gitlab/llm/ai_gateway/completions/resolve_vulnerability_spec.rb @@ -25,6 +25,8 @@ build(:ai_message, :resolve_vulnerability, user: user, resource: vulnerability, request_id: 'uuid') end + let(:prompt_version) { nil } + let(:options) { {} } let(:logger) { instance_double(Gitlab::Llm::Logger) } @@ -61,21 +63,26 @@ def allow_llm_client_to_raise_error(error) def allow_llm_client_to_return_message(response) identifier_names = vulnerability.finding.identifier_names.join("\n* ") + request_body = { + 'prompt_version' => prompt_version, + 'inputs' => { + filename: finding_location_file, + identifiers: "\n * #{identifier_names}\n", + name: vulnerability.title, + source_code: source_code, + vulnerability_description: vulnerability.description, + vulnerable_code: source_code + } + }.compact + expect_next_instance_of(Gitlab::Llm::AiGateway::Client) do |client| expect(client).to receive(:complete_prompt) .with( base_url: Gitlab::AiGateway.url, prompt_name: :resolve_vulnerability, - inputs: { - filename: finding_location_file, - identifiers: "\n * #{identifier_names}\n", - name: vulnerability.title, - source_code: source_code, - vulnerability_description: vulnerability.description, - vulnerable_code: source_code - }, + inputs: request_body['inputs'], model_metadata: nil, - prompt_version: nil + prompt_version: request_body['prompt_version'] ) .and_return(response) end diff --git a/ee/spec/lib/gitlab/llm/completions/resolve_vulnerability/shared_examples.rb b/ee/spec/lib/gitlab/llm/completions/resolve_vulnerability/shared_examples.rb index 9704e33a4d2c71b1391156bbf77e27caeaa2a71e..cb747bbfd2e94703362d9adb9f69a4c2e3866c8e 100644 --- a/ee/spec/lib/gitlab/llm/completions/resolve_vulnerability/shared_examples.rb +++ b/ee/spec/lib/gitlab/llm/completions/resolve_vulnerability/shared_examples.rb @@ -65,6 +65,7 @@ def expect_tracked_internal_event(event_name, status) RSpec.shared_examples "a resolve vulnerability completion" do before do stub_licensed_features(security_dashboard: true) + stub_feature_flags(resolve_vulnerability_claude_3_7_rollout: false) [:admin_all_resources, :resolve_vulnerability_with_ai].each do |permission| allow(user).to receive(:can?).with(permission).and_return(true) @@ -300,6 +301,38 @@ def expect_tracked_internal_event(event_name, status) end end + context "when passing in prompt version for Claude 3.7" do + let(:prompt_version) { "0.0.1-dev" } + + before do + stub_feature_flags(resolve_vulnerability_claude_3_7_rollout: true) + end + + it 'requests that a MR be created with the extracted patch' do + resolve.execute + + expect(merge_request_service).to have_received(:new).with( + project, + vulnerability, + user, + llm_patch: code_patch, + description_options: description_options + ) + end + + it 'publishes the created merge request for the fix' do + resolve.execute + + expect_published_graphql_content(mr_url) + end + + it 'tracks internal event with success' do + expect_tracked_internal_event("track_mr_creation_from_vr", "success") + + resolve.execute + end + end + context 'when the AIGW responds with a typed code block' do let(:content) { "```java\n#{code_patch}\n```" }