mirror of
https://github.com/freeedcom/ai-codereviewer.git
synced 2025-06-29 04:24:13 +00:00
Merge branch 'dev' into update-readme-and-workflow
This commit is contained in:
commit
74d6b7b9e5
5 changed files with 1338 additions and 1313 deletions
7
.github/workflows/code_review.yml
vendored
7
.github/workflows/code_review.yml
vendored
|
@ -9,18 +9,19 @@ permissions: write-all
|
||||||
jobs:
|
jobs:
|
||||||
if: '! github.event.pull_request.draft'
|
if: '! github.event.pull_request.draft'
|
||||||
code_review:
|
code_review:
|
||||||
|
if: '! github.event.pull_request.draft'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
- name: Code Review
|
- name: Code Review
|
||||||
uses: freeedcom/ai-codereviewer@main
|
uses: lukehollenback/ai-codereviewer@main-luke
|
||||||
with:
|
with:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
||||||
OPENAI_API_MODEL: "gpt-4-turbo-preview"
|
OPENAI_API_MODEL: "gpt-3.5-turbo"
|
||||||
exclude: "yarn.lock, dist/**, **/*.json, **/*.md, **/*.yaml, **/*.xml"
|
exclude: "yarn.lock, dist/**, **/*.json, **/*.md, **/*.yaml, **/*.xml"
|
||||||
custom_prompts: |
|
custom_prompts: |
|
||||||
Do not worry about the verbosity of variable names, as long as they are somewhat descriptive.
|
Do not worry about the verbosity of variable names, as long as they are somewhat descriptive.
|
||||||
Be sure to call out potential null pointer exceptions.
|
|
||||||
Be sure to call out concurrency issues and potential race conditions.
|
Be sure to call out concurrency issues and potential race conditions.
|
||||||
|
Do not worry about things a static analyzer would catch in real-time during development.
|
||||||
|
|
|
@ -11,10 +11,18 @@ inputs:
|
||||||
description: "OpenAI API model."
|
description: "OpenAI API model."
|
||||||
required: false
|
required: false
|
||||||
default: "gpt-4"
|
default: "gpt-4"
|
||||||
|
max_tokens:
|
||||||
|
description: "Maximum number of tokens that can be generated per analysis."
|
||||||
|
required: false
|
||||||
|
default: "700"
|
||||||
exclude:
|
exclude:
|
||||||
description: "Glob patterns to exclude files from the diff analysis"
|
description: "Glob patterns to exclude files from the diff analysis"
|
||||||
required: false
|
required: false
|
||||||
default: ""
|
default: ""
|
||||||
|
custom_prompts:
|
||||||
|
description: "Custom commands to augment the agent's prompts with. Each line is an individual command."
|
||||||
|
required: false
|
||||||
|
default: ""
|
||||||
runs:
|
runs:
|
||||||
using: "node16"
|
using: "node16"
|
||||||
main: "dist/index.js"
|
main: "dist/index.js"
|
||||||
|
|
18
dist/index.js
vendored
18
dist/index.js
vendored
|
@ -51,6 +51,7 @@ const minimatch_1 = __importDefault(__nccwpck_require__(2002));
|
||||||
const GITHUB_TOKEN = core.getInput("GITHUB_TOKEN");
|
const GITHUB_TOKEN = core.getInput("GITHUB_TOKEN");
|
||||||
const OPENAI_API_KEY = core.getInput("OPENAI_API_KEY");
|
const OPENAI_API_KEY = core.getInput("OPENAI_API_KEY");
|
||||||
const OPENAI_API_MODEL = core.getInput("OPENAI_API_MODEL");
|
const OPENAI_API_MODEL = core.getInput("OPENAI_API_MODEL");
|
||||||
|
const MAX_TOKENS = Number(core.getInput("max_tokens"));
|
||||||
const octokit = new rest_1.Octokit({ auth: GITHUB_TOKEN });
|
const octokit = new rest_1.Octokit({ auth: GITHUB_TOKEN });
|
||||||
const openai = new openai_1.default({
|
const openai = new openai_1.default({
|
||||||
apiKey: OPENAI_API_KEY,
|
apiKey: OPENAI_API_KEY,
|
||||||
|
@ -85,14 +86,14 @@ function getDiff(owner, repo, pull_number) {
|
||||||
return response.data;
|
return response.data;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
function analyzeCode(parsedDiff, prDetails) {
|
function analyzeCode(parsedDiff, prDetails, customPrompts) {
|
||||||
return __awaiter(this, void 0, void 0, function* () {
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
const comments = [];
|
const comments = [];
|
||||||
for (const file of parsedDiff) {
|
for (const file of parsedDiff) {
|
||||||
if (file.to === "/dev/null")
|
if (file.to === "/dev/null")
|
||||||
continue; // Ignore deleted files
|
continue; // Ignore deleted files
|
||||||
for (const chunk of file.chunks) {
|
for (const chunk of file.chunks) {
|
||||||
const prompt = createPrompt(file, chunk, prDetails);
|
const prompt = createPrompt(file, chunk, prDetails, customPrompts);
|
||||||
const aiResponse = yield getAIResponse(prompt);
|
const aiResponse = yield getAIResponse(prompt);
|
||||||
if (aiResponse) {
|
if (aiResponse) {
|
||||||
const newComments = createComment(file, chunk, aiResponse);
|
const newComments = createComment(file, chunk, aiResponse);
|
||||||
|
@ -105,7 +106,7 @@ function analyzeCode(parsedDiff, prDetails) {
|
||||||
return comments;
|
return comments;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
function createPrompt(file, chunk, prDetails) {
|
function createPrompt(file, chunk, prDetails, customPrompts) {
|
||||||
return `Your task is to review pull requests. Instructions:
|
return `Your task is to review pull requests. Instructions:
|
||||||
- Provide the response in following JSON format: {"reviews": [{"lineNumber": <line_number>, "reviewComment": "<review comment>"}]}
|
- Provide the response in following JSON format: {"reviews": [{"lineNumber": <line_number>, "reviewComment": "<review comment>"}]}
|
||||||
- Do not give positive comments or compliments.
|
- Do not give positive comments or compliments.
|
||||||
|
@ -113,6 +114,7 @@ function createPrompt(file, chunk, prDetails) {
|
||||||
- Write the comment in GitHub Markdown format.
|
- Write the comment in GitHub Markdown format.
|
||||||
- Use the given description only for the overall context and only comment the code.
|
- Use the given description only for the overall context and only comment the code.
|
||||||
- IMPORTANT: NEVER suggest adding comments to the code.
|
- IMPORTANT: NEVER suggest adding comments to the code.
|
||||||
|
${customPrompts}
|
||||||
|
|
||||||
Review the following code diff in the file "${file.to}" and take the pull request title and description into account when writing the response.
|
Review the following code diff in the file "${file.to}" and take the pull request title and description into account when writing the response.
|
||||||
|
|
||||||
|
@ -140,13 +142,13 @@ function getAIResponse(prompt) {
|
||||||
const queryConfig = {
|
const queryConfig = {
|
||||||
model: OPENAI_API_MODEL,
|
model: OPENAI_API_MODEL,
|
||||||
temperature: 0.2,
|
temperature: 0.2,
|
||||||
max_tokens: 700,
|
max_tokens: MAX_TOKENS,
|
||||||
top_p: 1,
|
top_p: 1,
|
||||||
frequency_penalty: 0,
|
frequency_penalty: 0,
|
||||||
presence_penalty: 0,
|
presence_penalty: 0,
|
||||||
};
|
};
|
||||||
try {
|
try {
|
||||||
const response = yield openai.chat.completions.create(Object.assign(Object.assign(Object.assign({}, queryConfig), (OPENAI_API_MODEL === "gpt-4-1106-preview"
|
const response = yield openai.chat.completions.create(Object.assign(Object.assign(Object.assign({}, queryConfig), (OPENAI_API_MODEL === "gpt-4-turbo-preview" || OPENAI_API_MODEL === "gpt-4-turbo" || OPENAI_API_MODEL === "gpt-3.5-turbo" || OPENAI_API_MODEL === "gpt-4-0125-preview" || OPENAI_API_MODEL === "gpt-4-1106-preview" || OPENAI_API_MODEL === "gpt-3.5-turbo-0125" || OPENAI_API_MODEL === "gpt-3.5-turbo-1106"
|
||||||
? { response_format: { type: "json_object" } }
|
? { response_format: { type: "json_object" } }
|
||||||
: {})), { messages: [
|
: {})), { messages: [
|
||||||
{
|
{
|
||||||
|
@ -155,6 +157,7 @@ function getAIResponse(prompt) {
|
||||||
},
|
},
|
||||||
] }));
|
] }));
|
||||||
const res = ((_b = (_a = response.choices[0].message) === null || _a === void 0 ? void 0 : _a.content) === null || _b === void 0 ? void 0 : _b.trim()) || "{}";
|
const res = ((_b = (_a = response.choices[0].message) === null || _a === void 0 ? void 0 : _a.content) === null || _b === void 0 ? void 0 : _b.trim()) || "{}";
|
||||||
|
console.log(`Trimmed Response: ${res}`);
|
||||||
return JSON.parse(res).reviews;
|
return JSON.parse(res).reviews;
|
||||||
}
|
}
|
||||||
catch (error) {
|
catch (error) {
|
||||||
|
@ -225,7 +228,10 @@ function main() {
|
||||||
const filteredDiff = parsedDiff.filter((file) => {
|
const filteredDiff = parsedDiff.filter((file) => {
|
||||||
return !excludePatterns.some((pattern) => { var _a; return (0, minimatch_1.default)((_a = file.to) !== null && _a !== void 0 ? _a : "", pattern); });
|
return !excludePatterns.some((pattern) => { var _a; return (0, minimatch_1.default)((_a = file.to) !== null && _a !== void 0 ? _a : "", pattern); });
|
||||||
});
|
});
|
||||||
const comments = yield analyzeCode(filteredDiff, prDetails);
|
const customPrompts = core.getMultilineInput("custom_prompts")
|
||||||
|
.map(customPrompt => `- ${customPrompt}`)
|
||||||
|
.join("\n");
|
||||||
|
const comments = yield analyzeCode(filteredDiff, prDetails, customPrompts);
|
||||||
if (comments.length > 0) {
|
if (comments.length > 0) {
|
||||||
yield createReviewComment(prDetails.owner, prDetails.repo, prDetails.pull_number, comments);
|
yield createReviewComment(prDetails.owner, prDetails.repo, prDetails.pull_number, comments);
|
||||||
}
|
}
|
||||||
|
|
2
dist/index.js.map
vendored
2
dist/index.js.map
vendored
File diff suppressed because one or more lines are too long
22
src/main.ts
22
src/main.ts
|
@ -8,6 +8,7 @@ import minimatch from "minimatch";
|
||||||
const GITHUB_TOKEN: string = core.getInput("GITHUB_TOKEN");
|
const GITHUB_TOKEN: string = core.getInput("GITHUB_TOKEN");
|
||||||
const OPENAI_API_KEY: string = core.getInput("OPENAI_API_KEY");
|
const OPENAI_API_KEY: string = core.getInput("OPENAI_API_KEY");
|
||||||
const OPENAI_API_MODEL: string = core.getInput("OPENAI_API_MODEL");
|
const OPENAI_API_MODEL: string = core.getInput("OPENAI_API_MODEL");
|
||||||
|
const MAX_TOKENS: number = Number(core.getInput("max_tokens"));
|
||||||
|
|
||||||
const octokit = new Octokit({ auth: GITHUB_TOKEN });
|
const octokit = new Octokit({ auth: GITHUB_TOKEN });
|
||||||
|
|
||||||
|
@ -58,14 +59,15 @@ async function getDiff(
|
||||||
|
|
||||||
async function analyzeCode(
|
async function analyzeCode(
|
||||||
parsedDiff: File[],
|
parsedDiff: File[],
|
||||||
prDetails: PRDetails
|
prDetails: PRDetails,
|
||||||
|
customPrompts: string
|
||||||
): Promise<Array<{ body: string; path: string; line: number }>> {
|
): Promise<Array<{ body: string; path: string; line: number }>> {
|
||||||
const comments: Array<{ body: string; path: string; line: number }> = [];
|
const comments: Array<{ body: string; path: string; line: number }> = [];
|
||||||
|
|
||||||
for (const file of parsedDiff) {
|
for (const file of parsedDiff) {
|
||||||
if (file.to === "/dev/null") continue; // Ignore deleted files
|
if (file.to === "/dev/null") continue; // Ignore deleted files
|
||||||
for (const chunk of file.chunks) {
|
for (const chunk of file.chunks) {
|
||||||
const prompt = createPrompt(file, chunk, prDetails);
|
const prompt = createPrompt(file, chunk, prDetails, customPrompts);
|
||||||
const aiResponse = await getAIResponse(prompt);
|
const aiResponse = await getAIResponse(prompt);
|
||||||
if (aiResponse) {
|
if (aiResponse) {
|
||||||
const newComments = createComment(file, chunk, aiResponse);
|
const newComments = createComment(file, chunk, aiResponse);
|
||||||
|
@ -78,7 +80,7 @@ async function analyzeCode(
|
||||||
return comments;
|
return comments;
|
||||||
}
|
}
|
||||||
|
|
||||||
function createPrompt(file: File, chunk: Chunk, prDetails: PRDetails): string {
|
function createPrompt(file: File, chunk: Chunk, prDetails: PRDetails, customPrompts: string): string {
|
||||||
return `Your task is to review pull requests. Instructions:
|
return `Your task is to review pull requests. Instructions:
|
||||||
- Provide the response in following JSON format: {"reviews": [{"lineNumber": <line_number>, "reviewComment": "<review comment>"}]}
|
- Provide the response in following JSON format: {"reviews": [{"lineNumber": <line_number>, "reviewComment": "<review comment>"}]}
|
||||||
- Do not give positive comments or compliments.
|
- Do not give positive comments or compliments.
|
||||||
|
@ -86,6 +88,7 @@ function createPrompt(file: File, chunk: Chunk, prDetails: PRDetails): string {
|
||||||
- Write the comment in GitHub Markdown format.
|
- Write the comment in GitHub Markdown format.
|
||||||
- Use the given description only for the overall context and only comment the code.
|
- Use the given description only for the overall context and only comment the code.
|
||||||
- IMPORTANT: NEVER suggest adding comments to the code.
|
- IMPORTANT: NEVER suggest adding comments to the code.
|
||||||
|
${customPrompts}
|
||||||
|
|
||||||
Review the following code diff in the file "${
|
Review the following code diff in the file "${
|
||||||
file.to
|
file.to
|
||||||
|
@ -117,7 +120,7 @@ async function getAIResponse(prompt: string): Promise<Array<{
|
||||||
const queryConfig = {
|
const queryConfig = {
|
||||||
model: OPENAI_API_MODEL,
|
model: OPENAI_API_MODEL,
|
||||||
temperature: 0.2,
|
temperature: 0.2,
|
||||||
max_tokens: 700,
|
max_tokens: MAX_TOKENS,
|
||||||
top_p: 1,
|
top_p: 1,
|
||||||
frequency_penalty: 0,
|
frequency_penalty: 0,
|
||||||
presence_penalty: 0,
|
presence_penalty: 0,
|
||||||
|
@ -127,7 +130,7 @@ async function getAIResponse(prompt: string): Promise<Array<{
|
||||||
const response = await openai.chat.completions.create({
|
const response = await openai.chat.completions.create({
|
||||||
...queryConfig,
|
...queryConfig,
|
||||||
// return JSON if the model supports it:
|
// return JSON if the model supports it:
|
||||||
...(OPENAI_API_MODEL === "gpt-4-1106-preview"
|
...(OPENAI_API_MODEL === "gpt-4-turbo-preview" || OPENAI_API_MODEL === "gpt-4-turbo" || OPENAI_API_MODEL === "gpt-3.5-turbo" || OPENAI_API_MODEL === "gpt-4-0125-preview" || OPENAI_API_MODEL === "gpt-4-1106-preview" || OPENAI_API_MODEL === "gpt-3.5-turbo-0125" || OPENAI_API_MODEL === "gpt-3.5-turbo-1106"
|
||||||
? { response_format: { type: "json_object" } }
|
? { response_format: { type: "json_object" } }
|
||||||
: {}),
|
: {}),
|
||||||
messages: [
|
messages: [
|
||||||
|
@ -139,6 +142,9 @@ async function getAIResponse(prompt: string): Promise<Array<{
|
||||||
});
|
});
|
||||||
|
|
||||||
const res = response.choices[0].message?.content?.trim() || "{}";
|
const res = response.choices[0].message?.content?.trim() || "{}";
|
||||||
|
|
||||||
|
console.log(`Trimmed Response: ${res}`);
|
||||||
|
|
||||||
return JSON.parse(res).reviews;
|
return JSON.parse(res).reviews;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error:", error);
|
console.error("Error:", error);
|
||||||
|
@ -232,7 +238,11 @@ async function main() {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
const comments = await analyzeCode(filteredDiff, prDetails);
|
const customPrompts = core.getMultilineInput("custom_prompts")
|
||||||
|
.map(customPrompt => `- ${customPrompt}`)
|
||||||
|
.join("\n")
|
||||||
|
|
||||||
|
const comments = await analyzeCode(filteredDiff, prDetails, customPrompts);
|
||||||
if (comments.length > 0) {
|
if (comments.length > 0) {
|
||||||
await createReviewComment(
|
await createReviewComment(
|
||||||
prDetails.owner,
|
prDetails.owner,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue