paint-brush
Better - AI 搭載のコードレビューツール@murtuzaalisurti

Better - AI 搭載のコードレビューツール

Murtuza8m2024/11/20
Read on Terminal Reader
Read this story w/o Javascript

長すぎる; 読むには

コードレビューは、標準を維持し、プロジェクト内のコードのベストプラクティスを重視する上で常に重要です。これは、開発者がコードをどのようにレビューすべきかについての投稿ではなく、その一部を AI に委任することに関するものです。そのため、プルリクエストの差分をコードレビューし、AI を使用して提案を生成する github アクション (github.com/murtuzaalisurti/better) を作成することにしました。

People Mentioned

Mention Thumbnail
Mention Thumbnail

Companies Mentioned

Mention Thumbnail
Mention Thumbnail
featured image - Better - AI 搭載のコードレビューツール
Murtuza HackerNoon profile picture
0-item
1-item

コードレビューは、コーディング プロジェクトで高い基準を維持し、ベスト プラクティスを強化する上で常に重要です。これは、開発者がコードをどのようにレビューすべきかについての投稿ではなく、その一部を AI に委任することに関するものです。


Michael Lynch 氏が投稿「人間のようにコード レビューを行う方法」で述べているように、コード レビューの退屈な部分はコンピューターに任せるべきです。Michael 氏はフォーマット ツールの重要性を強調していますが、私はもう一歩進んで人工知能に任せたいと思っています。つまり、業界の AI ブームを利用すればいいのではないでしょうか。


ここで私が言いたいのは、AI はフォーマット ツールやリンターの代わりに使用すべきだということではありません。むしろ、AI はそれらの上で使用され、人間が見逃す可能性のある些細な点をキャッチするということです。そのため、プル リクエストの差分をコード レビューし、AI を使用して提案を生成するgithub アクションを作成することにしました。その手順を順を追って説明します。


🚨 注意:


差分を取得する

github API と対話するために、私はoctokit使用しました。これは、 github API と慣用的な方法で対話するための SDK またはクライアント ライブラリのようなものです。


発生したプル リクエストの diff を取得するには、必要なパラメーターとともに、値application/vnd.github.diffを持つAcceptヘッダーを渡す必要があります。


 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, }, }); }



github actions にまったく慣れていない場合は、 Victoria Lo による github actions 101 シリーズがよいスタートになります。



差分を取得したら、それを解析して不要な変更を削除し、以下に示すスキーマで返します。


 /** 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(), })

ファイルを無視する

ファイルを無視するのは非常に簡単です。ユーザー入力リストには、セミコロンで区切られた glob パターンの文字列が必要です。次に、それが解析され、無視されるファイルのデフォルト リストと連結され、重複が除去されます。


 **/*.md; **/*.env; **/*.lock; const filesToIgnoreList = [ ...new Set( filesToIgnore .split(";") .map(file => file.trim()) .filter(file => file !== "") .concat(FILES_IGNORED_BY_DEFAULT) ), ];


次に、無視されたファイル リストを使用して、無視されたファイルを参照する diff 変更を削除します。これにより、必要な変更のみを含む生のペイロードが得られます。

提案の生成

差分を解析した後、生のペイロードを取得したら、それをプラットフォーム API に渡します。以下は OpenAI API の実装です。


 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; }


API 実装でレスポンス形式が使用されていることにお気づきかもしれません。これは多くの LLM プラットフォームで提供される機能で、特定のスキーマ/形式でレスポンスを生成するようにモデルに指示できます。モデルが幻覚を起こしてプル リクエスト内の誤ったファイルや位置の提案を生成したり、レスポンス ペイロードに新しいプロパティを追加したりしないようにしたいので、この場合特に役立ちます。


システム プロンプトは、モデルにコード レビューの実行方法や留意すべき点に関する詳細なコンテキストを提供するためにあります。システム プロンプトは、 github.com/murtuzaalisurti /better で確認できます。ユーザー プロンプトには、実際の差分、ルール、プル リクエストのコンテキストが含まれています。これがコード レビューを開始するものです。


この github アクションは、OpenAI モデルと Anthropic モデルの両方をサポートしています。Anthropic API の実装方法は次のとおりです。


 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; }

コメントの追加

最後に、提案を取得した後、それらをサニタイズして GitHub API に渡し、レビューの一部としてコメントを追加します。

コメントの追加に以下の方法を選択したのは、新しいレビューを作成することで、一度に 1 つのコメントを追加するのではなく、すべてのコメントを一度に追加できるためです。コメントを 1 つずつ追加すると、通知がトリガーされ、ユーザーに通知をスパム送信したくないため、レート制限がトリガーされる可能性もあります。


 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; } }

結論

GitHub アクションをオープンエンドかつ統合可能な状態に維持したいと考えました。そのため、任意のモデル(サポートされているモデルのリストを参照)を使用することも、サポートされている基本モデルの上に独自のカスタム モデルを微調整して構築し、この GitHub アクションで使用することもできます。


トークンの問題やレート制限が発生した場合は、それぞれのプラットフォームのドキュメントを参照してモデル制限をアップグレードすることをお勧めします。


では、何を待っていますか? github にリポジトリがある場合は、今すぐアクションをお試しください。githubアクション マーケットプレイスにあります。