「生成AI×pre-commit」: Gitコミットレビューを自動化するCLIツールを作ってみた

## はじめに

:::note info
この記事は[BrainPad Advent Calendar 2024](https://qiita.com/advent-calendar/2024/brainpad) 25日目の記事です。
:::

![DALL·E 2024-12-22 01.13.22 - A more vivid and sharply detailed festive Christmas scene blending themes of technology and collaboration. A warm, cozy workspace with a Christmas tre.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/665774/d67e7ea7-5924-b522-c34e-939476837f14.png)

**メリクリスマス！**

株式会社ブレインパッドでデータサイエンティストをしている金（a.k.a ジュンヒョン）です。

昨年に引き続き、今年もアドベントカレンダーの25日目の記事を担当させていただきます！ちなみに、昨年は以下の記事を書きました。興味があればぜひご覧ください。

https://qiita.com/sunyeul/items/c1851943888e0b06eedc

今回は、**生成AIとpre-commitを組み合わせて、Gitコミットレビューを効率化するCLIツール**を紹介します。このツールは、個人のサイドプロジェクトとして開発したもので、Pythonで実装し、LangChainを活用してOpenAIやAnthropicのAIモデルを利用したコードレビューを実現しました。

主な特徴は以下の通りです：
- **コード変更の内容やコミットメッセージの品質**を自動で分析
- **改善提案**を提示して、開発者のレビュー負担を軽減

このツールはGitHubで公開していますので、ぜひチェックしてみてください！

https://github.com/sunyeul/commit-review-agent

個人の開発経験を活かして作ったこのツールを通じて、生成AIの可能性を感じていただければ幸いです。アドベントカレンダーの最終日にふさわしい、“生成AI”からのクリスマスプレゼントとして、このツールの仕組みや使い方を解説します。どうぞお楽しみください！


## 1. プロジェクト概要

**プロジェクト名**: `commit_review`
**主な機能**:

- Git でステージした（`git add` 済みの）Pythonファイルのみを抽出
- ファイルの内容や差分、コミットメッセージを言語モデル（OpenAI / Anthropic）に送信し、自動レビューを受け取る
- コード説明や変更点の要約、コミットメッセージのフィードバックを返し、最終的にコミットして問題ないかを判定
- `pre-commit`フックとして統合可能で、Gitコミット時に自動でレビューを実行し、開発者のワークフローを妨げることなくコード品質を向上


|コミット成功例 | コミット失敗例 |
|:---:|:---:|
| ![commit_success.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/665774/9f35c403-24a9-9489-6893-f52f7f25e629.gif) | ![commit_fail.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/665774/d1645144-9ab7-b76b-30e5-df6adb5c3dad.gif) |


## 2. フォルダ/コード構造

プロジェクトのフォルダは以下のようになっています。

```bash
commit_review/
  ├── __init__.py
  ├── __main__.py         # CLIエントリーポイント
  ├── git_utils.py        # Gitリポジトリ/ファイル操作などのユーティリティ
  ├── main.py             # 全体レビューの実行ロジック
  ├── prompt_template.py  # ReviewResultの定義やプロンプトテンプレート
  └── review_api_manager.py  # 言語モデルAPIの選択と呼び出し
```

それぞれのファイルが担う役割は以下の通りです。

- `__init__.py`
    - パッケージを Python モジュールとして認識させる初期化用ファイル。特に実装内容はありません。

- `__main__.py`
    - `click` ライブラリを使った CLI の入り口です。
    - `run()` 関数で、後述する `main.py` の `main()` を呼び出します。

- `git_utils.py`
    - GitPython を使用して、ステージ済みのファイル取得やファイル内容の読み込み、コミットメッセージの取得を行います。
    - 主な関数:
        - `get_staged_python_files(repo: Repo) -> List[str]`
        - `get_staged_file_contents(file_paths: List[str]) -> str`
        - `get_staged_file_changes(file_paths: List[str], repo: Repo) -> str`
        - `get_commit_message(repo: Repo) -> str`

- `main.py`
    - `main(api_choice: str, review_point: Optional[str] = None) -> None`
    - プログラムの中心的なロジックを管理します。
        - ステージされた Python ファイルの取得
        - ファイル内容や変更内容を取得
        - コミットメッセージを読み込み
        - `review_api_manager.py` で選択した言語モデルにレビューリクエストを送信
        - 結果の表示（レビューの要約、コミット可否など）

- `prompt_template.py`
    - LangChain 用の出力パーサーや Pydantic モデル (`ReviewResult`)、プロンプトテンプレート (`prompt_template`) が含まれます。
    - `ReviewResult` は API からのレスポンスを JSON 形式で受け取った際に、どのような項目をパースするか定義しています。
    - `prompt_template` には実際にモデルに送るプロンプト（「これこれをレビューして欲しい」という指示）を記述します。

- `review_api_manager.py`
    - `ReviewAPIManager` クラスがあり、`api_choice` を見て OpenAI / Anthropic のどちらかを初期化 (`get_model()`) します。
    - `request_review(...)` メソッドで LangChain のチェーン（prompt_template → model → parser）を構築し、レビュー結果を取得・返却します。


## 3. 実際のコード例

たとえば `review_api_manager.py` は以下のように実装されています。

```python
# review_api_manager.py
from typing import Dict, List, Optional
from dotenv import load_dotenv
from langchain_anthropic import ChatAnthropic
from langchain_openai import ChatOpenAI

from .prompt_template import parser, prompt_template

load_dotenv()

class ReviewAPIManager:
    def __init__(self, api_choice: str):
        self.api_choice = api_choice

    def get_model(self):
        """APIキーのロードと、選択されたモデルインスタンスの生成."""
        if self.api_choice == "openai":
            return ChatOpenAI(model="gpt-4o", temperature=0.1)
        elif self.api_choice == "anthropic":
            return ChatAnthropic(model="claude-3-5-sonnet-20240620", temperature=0.1)
        else:
            raise ValueError(f"サポートされていないAPI選択: {self.api_choice}")

    def request_review(
        self,
        file_contents: str,
        file_changes: str,
        commit_message: str,
        review_point: Optional[str] = None,
    ) -> Dict[str, str | List[str]]:
        """LLMにレビューリクエストを行い、パースした結果を返す."""
        model = self.get_model()
        chain = prompt_template | model | parser
        return chain.invoke(
            {
                "file_contents": file_contents,
                "file_changes": file_changes,
                "commit_message": commit_message,
                "review_point": review_point,
            }
        )
```

- `api_choice` に `"openai"` を指定すると、ChatOpenAI (gpt-4oモデル) を利用し、`"anthropic"` を選ぶと ``ChatAnthropic (claude-3-5-sonnet-20240620モデル) を利用します。
- コード全体をパイプライン(チェーン)としてまとめることで、**プロンプトへの入力 → LLM呼び出し → JSON形式へのパース**が一度に行われます。


## 4. 使い方

### 4.1 セットアップ

1.必要ライブラリのインストール

```bash
pip install commit-review-agent@git+https://github.com/sunyeul/commit-review-agent.git
```

2.APIキーの設定
`.env` ファイルをルートディレクトリに作成し、OpenAI/Anthropic の APIキーを設定(どちらかが設定されていればOK)

```makefile
OPENAI_API_KEY=sk-xxxxxxx
ANTHROPIC_API_KEY=xxxxxxx
```

### 4.2 Pre-commit フックとして設定

このツールを pre-commit フックとして設定することで、コミット前に自動的にレビューが実行されます。

1.pre-commit のインストール

```bash
pip install pre-commit
```

2.`.pre-commit-config.yaml` ファイルの作成

```yaml
repos:
-   repo: https://github.com/sunyeul/commit-review-agent
    rev: v0.1.1
    hooks:
      - id: commie-review
        name: Commit Review
        entry: commit-review
        args: [--review-point, "可読性", --api-choice, "openai"]
        language: python
        types: [python]
        pass_filenames: false
```

3.`Pre-commit` のインストールを有効化
プロジェクト内で以下のコマンドを実行して、pre-commit を有効化します。

```bash
pre-commit install
```

4.動作確認
変更を加えた Python ファイルをステージして、コミットを試みます。

```bash
git add example.py
git commit -m "Add new feature"
```

コミット時にこのツールが呼び出され、レビュー結果が表示されます。レビューで問題が検出された場合、コミットがブロックされます。


## 5. 動作イメージ (`pre-commit`フック)

- Pythonファイルを編集してステージ (`git add`)
- `git commit -m {コミットメッセージ}` コマンドを入力
- コンソールにレビュー結果が表示され、コード/コミットメッセージの評価や改善提案がリストアップ
- 最後に “Commit OK” or “Not OK” が出力されるので、問題なければコミットを確定するか、指摘事項に合わせて修正・リトライするかを判断

|コミット成功例 | コミット失敗例 |
|:---:|:---:|
| ![commit_success.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/665774/9f35c403-24a9-9489-6893-f52f7f25e629.gif) | ![commit_fail.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/665774/d1645144-9ab7-b76b-30e5-df6adb5c3dad.gif) |


## 6. まとめ・今後の展望

この小さなツールは、以下のような機能を提供する簡易的なコードレビューボットです。

- **コミットメッセージの不備**がないかチェック
- **コードの変更内容**が意図に沿っているかを確認
- 提案レベルでの**改善ポイント**を提示

ただし、AIによる提案が常に正しいわけではありません。あくまで参考程度に活用しつつ、最終的な判断は人間が行うのが望ましいでしょう。

このツールをベースに、以下のような発展形も考えられます。

- **プルリクエスト単位でのレビューボット化**
    - コミット単位ではなく、リポジトリ全体のコード変更に基づく高度なレビュー機能を追加。
- **テスト結果やLinterの統合**
    - Flake8やBlackなどのLinter結果やテストカバレッジ情報をLLMに提供し、包括的なフィードバックを生成。
- **大規模リポジトリへの対応**
    - 多数のファイルや複雑なコード変更にも対応できるよう最適化。

クリスマスという特別な日に、このツールが少しでも「**コードレビューの負荷軽減**」というプレゼントになれば嬉しいです。
生成AIの力を借りて、みなさんの開発がより効率的で快適なものになることを願っています！

---

ここまでお読みいただき、ありがとうございました！
もし疑問点や面白い使い方があれば、ぜひコメントで共有してください。
それでは皆さん、素敵なクリスマス＆エンジニアリングライフをお過ごしください！