diff --git a/.gitlab/dangerjs/aiGenerateGitMessage.js b/.gitlab/dangerjs/aiGenerateGitMessage.js new file mode 100644 index 0000000000..b2ded5b860 --- /dev/null +++ b/.gitlab/dangerjs/aiGenerateGitMessage.js @@ -0,0 +1,120 @@ +const { OpenAI } = require("langchain/llms/openai"); +const { + ChatPromptTemplate, + SystemMessagePromptTemplate, +} = require("langchain/prompts"); +const { LLMChain } = require("langchain/chains"); +const { ChatOpenAI } = require("langchain/chat_models/openai"); +const openAiTokenCount = require("openai-gpt-token-counter"); + +const mrModifiedFiles = danger.git.modified_files; +const mrCommits = danger.gitlab.commits; + +module.exports = async function () { + let mrDiff = await getMrGitDiff(mrModifiedFiles); + const mrCommitMessages = getCommitMessages(mrCommits); + + // Init output message + let outputDangerMessage = `\n\nPerhaps you could use an AI-generated suggestion for your commit message. Here is one `; + + // Setup LLM prompt + const inputPrompt = `You are a helpful assistant that creates suggestions for single git commit message, that user can use to describe all the changes in their merge request. + Use git diff: {mrDiff} and users current commit messages: {mrCommitMessages} to get the changes made in the commit. + + Output should be git commit message following the conventional commit format. + + Output only git commit message in desired format, without comments and other text. + + Do not include lines with JIRA tickets mentions (e.g. "Closes JIRA-123") to the output. + + Avoid including temporary commit messages (e.g. "Cleanup", "Merged" or "wip: Test") to the output. + + Avoid using vague terms (e.g. "some checks", "add new ones", "few changes" ) in the output. + + + [EXAMPLE OUTPUT] + feat(scope): add support for component XXX + + - adds support for wifi6 + - adds validations for logging script + + + [EXAMPLE OUTPUT] + feat(scope): add support for component XXX + + - adds support for wifi6 + - adds validations for logging script + - Closes https://github.com/espressif/esp-idf/issues/1234 + + `; + + // Count input tokens for LLM + const mrCommitMessagesTokens = openAiTokenCount(mrCommitMessages.join(" ")); + const gitDiffTokens = openAiTokenCount(mrDiff); + const promptTokens = openAiTokenCount(inputPrompt); + + const inputLlmTokens = + mrCommitMessagesTokens + gitDiffTokens + promptTokens; + + console.log(`Input tokens for LLM: ${inputLlmTokens}`); + + if (inputLlmTokens < 4096) { + outputDangerMessage += `(based on your MR git-diff and your current commit messages):\n\n`; + } else { + outputDangerMessage += `(based only on your current commit messages, git-diff of this MR is too big (${inputLlmTokens} tokens) for the AI model):\n\n`; + mrDiff = ""; + } + + // Call LLM + const generatedCommitMessage = await createAiGitMessage( + inputPrompt, + mrDiff, + mrCommitMessages + ); + + outputDangerMessage += "```\n" + generatedCommitMessage + "\n```\n"; // Add the generated git message, format to the markdown code block + outputDangerMessage += "\n**NOTE: AI-generated suggestions may not always be correct, please review the suggestion before using it.**" // Add disclaimer + return outputDangerMessage; +}; + +async function getMrGitDiff(mrModifiedFiles) { + let mrDiff = ""; + for (const file of mrModifiedFiles) { + const fileDiff = await danger.git.diffForFile(file); + mrDiff += fileDiff.diff.trim(); + } + + return mrDiff; +} + +function getCommitMessages(mrCommits) { + let mrCommitMessages = []; + for (const commit of mrCommits) { + mrCommitMessages.push(commit.message); + } + + return mrCommitMessages; +} + +async function createAiGitMessage(inputPrompt, mrDiff, mrCommitMessages) { + const chat = new ChatOpenAI({ + engine: "gpt-3.5-turbo", + temperature: 0, + }); + + const chatPrompt = ChatPromptTemplate.fromPromptMessages([ + SystemMessagePromptTemplate.fromTemplate(inputPrompt), + ]); + + const chain = new LLMChain({ + prompt: chatPrompt, + llm: chat, + }); + + const response = await chain.call({ + mrDiff: mrDiff, + mrCommitMessages: mrCommitMessages, + }); + + return response.text; +} diff --git a/.gitlab/dangerjs/dangerfile.js b/.gitlab/dangerjs/dangerfile.js index 502fb04c66..570bf882a5 100644 --- a/.gitlab/dangerjs/dangerfile.js +++ b/.gitlab/dangerjs/dangerfile.js @@ -16,7 +16,7 @@ async function runChecks() { // Checks for MR commits require("./mrCommitsTooManyCommits.js")(); - require("./mrCommitsCommitMessage.js")(); + await require("./mrCommitsCommitMessage.js")(); require("./mrCommitsEmail.js")(); // Checks for MR code diff --git a/.gitlab/dangerjs/mrCommitsCommitMessage.js b/.gitlab/dangerjs/mrCommitsCommitMessage.js index 183970f9d5..2ada3d28ac 100644 --- a/.gitlab/dangerjs/mrCommitsCommitMessage.js +++ b/.gitlab/dangerjs/mrCommitsCommitMessage.js @@ -6,7 +6,7 @@ * @dangerjs WARN */ -module.exports = function () { +module.exports = async function () { const mrCommits = danger.gitlab.commits; const detectRegexes = [ @@ -60,11 +60,19 @@ module.exports = function () { } } - // Create report - if (partMessages.length) { - partMessages.sort(); - let dangerMessage = `\nSome issues found for the commit messages in this MR:\n${partMessages.join('\n')} + // Create report + if (partMessages.length) { + partMessages.sort(); + let dangerMessage = `\nSome issues found for the commit messages in this MR:\n${partMessages.join( + "\n" + )} \nPlease consider updating these commit messages. It is recommended to follow this [commit messages guide](https://gitlab.espressif.cn:6688/espressif/esp-idf/-/wikis/dev-proc/Commit-messages)`; - warn(dangerMessage); - } + + // Create AI generated suggestion for git commit message based of gitDiff and current commit messages + const AImessageSuggestion = + await require("./aiGenerateGitMessage.js")(); + dangerMessage += AImessageSuggestion; + + warn(dangerMessage); + } };