Đánh giá mã luôn đóng vai trò quan trọng trong việc duy trì các tiêu chuẩn cao và củng cố các phương pháp hay nhất trong một dự án mã hóa. Đây không phải là bài đăng về cách các nhà phát triển nên đánh giá mã, mà là về việc giao một phần cho AI.
Như Michael Lynch đã đề cập trong bài đăng của mình - "Cách thực hiện Đánh giá mã như con người" - chúng ta nên để máy tính xử lý các phần nhàm chán của việc đánh giá mã. Trong khi Michael nhấn mạnh vào công cụ định dạng, tôi muốn tiến xa hơn một bước và để trí tuệ nhân tạo tìm ra nó. Ý tôi là, tại sao không tận dụng sự bùng nổ của AI trong ngành?
Bây giờ tôi không nói rằng AI nên được sử dụng thay cho các công cụ định dạng và linter. Thay vào đó, nó được sử dụng trên đó, để nắm bắt những thứ tầm thường mà con người có thể bỏ qua. Đó là lý do tại sao tôi quyết định tạo một hành động github để xem xét mã lệnh pull request diff và tạo ra các đề xuất bằng AI. Hãy để tôi hướng dẫn bạn thực hiện.
🚨 LƯU Ý:
- Hành động GitHub này hiện có sẵn trên thị trường GitHub .
- Đây là hành động javascript - tìm hiểu thêm về cách tạo hành động javascript trên github .
Để tương tác với API github, tôi đã sử dụng octokit
, đây là một dạng SDK hoặc thư viện máy khách để tương tác với API github theo cách thông thường.
Để bạn có thể nhận được sự khác biệt của yêu cầu kéo, bạn cần truyền tiêu đề Accept
có giá trị application/vnd.github.diff
cùng với các tham số bắt buộc.
async function getPullRequestDetails(octokit, { mode }) { let AcceptFormat = "application/vnd.github.raw+json"; if (mode === "diff") AcceptFormat = "application/vnd.github.diff"; if (mode === "json") AcceptFormat = "application/vnd.github.raw+json"; return await octokit.rest.pulls.get({ owner: github.context.repo.owner, repo: github.context.repo.repo, pull_number: github.context.payload.pull_request.number, headers: { accept: AcceptFormat, }, }); }
Nếu bạn chưa quen với github action, đây là loạt bài hướng dẫn github action 101 của Victoria Lo , bạn có thể bắt đầu bằng bài này.
Sau khi nhận được sự khác biệt, tôi phân tích cú pháp và loại bỏ những thay đổi không mong muốn, sau đó trả về trong lược đồ hiển thị bên dưới:
/** using zod */ schema = z.object({ path: z.string(), position: z.number(), line: z.number(), change: z.object({ type: z.string(), add: z.boolean(), ln: z.number(), content: z.string(), relativePosition: z.number(), }), previously: z.string().optional(), suggestions: z.string().optional(), })
Bỏ qua các tệp khá đơn giản. Danh sách đầu vào của người dùng yêu cầu một chuỗi các mẫu glob được phân tách bằng dấu chấm phẩy. Sau đó, nó được phân tích cú pháp, nối với danh sách mặc định của các tệp bị bỏ qua và loại bỏ trùng lặp.
**/*.md; **/*.env; **/*.lock; const filesToIgnoreList = [ ...new Set( filesToIgnore .split(";") .map(file => file.trim()) .filter(file => file !== "") .concat(FILES_IGNORED_BY_DEFAULT) ), ];
Danh sách các tệp bị bỏ qua sau đó được sử dụng để xóa các thay đổi diff tham chiếu đến các tệp bị bỏ qua đó. Điều đó cung cấp cho bạn một payload thô chỉ chứa các thay đổi bạn muốn.
Sau khi phân tích diff, tôi nhận được payload thô, rồi chuyển nó đến API nền tảng. Sau đây là triển khai API OpenAI.
async function useOpenAI({ rawComments, openAI, rules, modelName, pullRequestContext }) { const result = await openAI.beta.chat.completions.parse({ model: getModelName(modelName, "openai"), messages: [ { role: "system", content: COMMON_SYSTEM_PROMPT, }, { role: "user", content: getUserPrompt(rules, rawComments, pullRequestContext), }, ], response_format: zodResponseFormat(diffPayloadSchema, "json_diff_response"), }); const { message } = result.choices[0]; if (message.refusal) { throw new Error(`the model refused to generate suggestions - ${message.refusal}`); } return message.parsed; }
Bạn có thể nhận thấy việc sử dụng định dạng phản hồi trong triển khai API. Đây là một tính năng được cung cấp bởi nhiều nền tảng LLM, cho phép bạn yêu cầu mô hình tạo phản hồi theo một lược đồ/định dạng cụ thể. Tính năng này đặc biệt hữu ích trong trường hợp này vì tôi không muốn mô hình tạo ra ảo giác và tạo ra các đề xuất cho các tệp hoặc vị trí không chính xác trong yêu cầu kéo hoặc thêm các thuộc tính mới vào tải trọng phản hồi.
Lời nhắc hệ thống ở đó để cung cấp cho mô hình nhiều ngữ cảnh hơn về cách nó nên thực hiện đánh giá mã và một số điều cần lưu ý. Bạn có thể xem lời nhắc hệ thống tại đây github.com/murtuzaalisurti/better . Lời nhắc người dùng chứa sự khác biệt thực tế, các quy tắc và ngữ cảnh của yêu cầu kéo. Đó là những gì bắt đầu đánh giá mã.
Hành động github này hỗ trợ cả mô hình OpenAI và Anthropic. Sau đây là cách nó triển khai API Anthropic:
async function useAnthropic({ rawComments, anthropic, rules, modelName, pullRequestContext }) { const { definitions } = zodToJsonSchema(diffPayloadSchema, "diffPayloadSchema"); const result = await anthropic.messages.create({ max_tokens: 8192, model: getModelName(modelName, "anthropic"), system: COMMON_SYSTEM_PROMPT, tools: [ { name: "structuredOutput", description: "Structured Output", input_schema: definitions["diffPayloadSchema"], }, ], tool_choice: { type: "tool", name: "structuredOutput", }, messages: [ { role: "user", content: getUserPrompt(rules, rawComments, pullRequestContext), }, ], }); let parsed = null; for (const block of result.content) { if (block.type === "tool_use") { parsed = block.input; break; } } return parsed; }
Cuối cùng, sau khi lấy các đề xuất, tôi sẽ lọc chúng và chuyển đến GitHub API để thêm bình luận như một phần của bài đánh giá.
Tôi chọn cách bên dưới để thêm bình luận vì bằng cách tạo một bài đánh giá mới, bạn có thể thêm tất cả bình luận cùng một lúc thay vì thêm từng bình luận một. Việc thêm từng bình luận một cũng có thể kích hoạt giới hạn tốc độ vì việc thêm bình luận sẽ kích hoạt thông báo và bạn không muốn gửi thư rác cho người dùng bằng thông báo.
function filterPositionsNotPresentInRawPayload(rawComments, comments) { return comments.filter(comment => rawComments.some(rawComment => rawComment.path === comment.path && rawComment.line === comment.line) ); } async function addReviewComments(suggestions, octokit, rawComments, modelName) { const { info } = log({ withTimestamp: true }); // eslint-disable-line no-use-before-define const comments = filterPositionsNotPresentInRawPayload(rawComments, extractComments().comments(suggestions)); try { await octokit.rest.pulls.createReview({ owner: github.context.repo.owner, repo: github.context.repo.repo, pull_number: github.context.payload.pull_request.number, body: `Code Review by ${modelName}`, event: "COMMENT", comments, }); } catch (error) { info(`Failed to add review comments: ${JSON.stringify(comments, null, 2)}`); throw error; } }
Tôi muốn giữ cho hành động GitHub có tính mở và mở cho việc tích hợp và đó là lý do tại sao bạn có thể sử dụng bất kỳ mô hình nào bạn chọn (xem danh sách các mô hình được hỗ trợ ) hoặc bạn có thể tinh chỉnh và xây dựng mô hình tùy chỉnh của riêng mình trên các mô hình cơ sở được hỗ trợ và sử dụng nó với hành động GitHub này.
Nếu bạn gặp bất kỳ sự cố nào về mã thông báo hoặc giới hạn tỷ lệ , bạn có thể muốn nâng cấp giới hạn mô hình của mình bằng cách tham khảo tài liệu của nền tảng tương ứng.
Vậy, bạn còn chờ gì nữa? Nếu bạn có kho lưu trữ trên github, hãy thử hành động này ngay bây giờ - nó có trên github action marketplace .