【21歳/大学生】 AIでコミットメッセージの自動生成とコードのリファクタリングが行えるCLIツールを公開してみた

## 何を作ったか

生成AIでソフトウェア開発のさまざまな作業をサポートするCLIツールを作成しました。
現在はコミットの自動生成とリファクタリングをAIが行う機能をサポートしています。


### パッケージ名：[code-agent](https://pypi.org/project/code-agent/ "PyPIのページ")

Pythonが入っている環境なら`pip3 install code-agent`でインストールできます。
システム全体にインストールする場合、新しいバージョンのpipでは素直にインストールさせてくれないので`--break-system-packages`オプションを付ける必要があります。

### コマンド名：`codex`

サブコマンドでどの機能を利用するか指定できます。
- `codex gen-commit`: ローカルのGitリポジトリからHEADとインデックスの差分をもとに、コミットメッセージを作成する。
- `codex refactor`: 引数に与えられたファイルを読み取り、リファクタリングが行える箇所のコードの表示と新しいコードの提案をする。

※codexコマンドの機能を利用するためには環境変数に`OPENAI_API_KEY`を事前にエクスポートする必要がありますのでご注意ください。


## リファクタリングのエージェントは開発でも学習でもかなり使えそう

### `codex refactor` 
コマンドの引数に任意の個数のファイルパスを指定します。すると以下のようにいくつかの提案を出してくれます

   ⇩  Rustで実装したwcコマンドのプログラムを与えてみた例
![スクリーンショット 2024-03-06 14.12.01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2788034/907dfd92-8985-131a-c101-a5a463b9a52a.png)

生成された提案を見てみると、細かいポイントを得ていてかなり有益な提案が得られていますよね。

 余談ですがプロンプトの指示で "Make suggestions that improve memory and processing efficiency and reduce bugs by taking advantage of the characteristics of the programming language used by the user."（ユーザーが使用するプログラミング言語の特性を活かして、メモリや処理効率の向上、バグの軽減されるような提案をしてください） という文言を入れると提案されるコードの質が向上した印象があります。そこらへんのプロンプトエンジニアリングも楽しいですね！
 
僕はまだRustに慣れていないので、Rustらしい書き方を教えてくれるのは使っていてかなり便利だなと思います。

## コミットメッセージの自動生成によってコミットの作業が楽になる
個人的にはGitの本質はバージョン管理にあると思っています（当たり前）。でもコミットメッセージを作る煩雑さから、気づいたら大量のファイルに変更がされてたということがよくありますよね。憂鬱な気分で愚直にコミットメッセージを作成したり、あまり考えずにまとめてコミットしちゃうくらいなら、コミットメッセージの作成はAIに任せましょう！

### `codex gen-commit`
このエージェントはローカルのGitレポジトリから最新のコミットとインデックス（ステージに上げられた変更）の差分を読み取り、コミットメッセージを作成してくれます。なのでユーザーは意味のあるまとまりで変更ファイルをステージに上げて`codex gen-commit`コマンドを実行するだけです。引数にリポジトリへのパスを受け取りますが、デフォルトでカレントディレクトリにセットされるのでプロジェクトのルートに居れば引数なしでOK！

![スクリーンショット 2024-03-06 18.48.23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2788034/f2237240-696a-b38e-07a2-43d7c9f85a67.png)

実行すると生成されたコミットメッセージをそのままコミットするかを尋ねられるのでyと入力することでコミットまでをエージェントがやってくれます。

## `pip install code-agent`でインストールできる
冒頭にも書きましたが、PyPIに公開したのでpipでインストールできます。
インストールがうまくいけば`codex`コマンドを使い始めることができます。

ちなみになぜコマンド名を`codex`にしたかというと、プログラマが書いたコードをもとに
- 面倒な作業をAIに代わりにやってもらったり
- コードの質を上げるための提案をしてくれたり

さまざまな Code ⛌ ◯◯◯の機能を実現したエージェントを作りたいという思いを込めて`codex`という名前にしました。


これからも継続して機能を追加していく予定なので、インストールして使ってみてもらえると嬉しいです！
またこのツールの開発を通じてソフトウェア開発のスキルを磨いていきたいので、改善案などをコメント欄もしくはGithubのリポジトリ上でフィードバックしてもらえるととても助かります。

https://github.com/Gonsix/Code-Agent


## 自分は誰なのか
自己紹介が遅れていました。はじめまして、@Gonsixと申します。僕は21歳の大学生で情報系の学部に通っています。
プログラミングは大学に入ってから始めたのですが、約３年間かなりコンピュータサイエンンスに没頭してきたので色々な分野に興味があり日常的にコードを書いています。最近はLangChainを利用して生成AIのアプリケーションを作るのが一番楽しいと感じています。

### Pythonが好き
でも最近はRustとかも触っていて難しいけど好きな言語になりつつあります。、文法はともかくCargoが素晴らしいツールだと思います。Pythonではモダンなパッケージ管理にPoetryが使えるけれど、どの環境にもインストールされているってわけではないのでちょっと不便。一方Rustではパッケージ管理からテストのフレームワークまでCargoに一式揃っている。またユーザーが一貫性のあるワークフローで開発ができるように言語が設計されている点が素晴らしいと思います。もちろん、メモリ安全性が担保されていたり実行速度が早いことなど他にも優れた特徴を持ち合わせている言語だと思います。

# ツールの仕組みについて詳しく見ていこう

これ以降はツールの仕組みが知りたい人向け

ここまではツールの概要について紹介しました。ここからはツールがどんな仕組みになっているのかを紹介しようと思います。興味ある方は引き続きお楽しみください。



## プロンプト（指示分）はこんな感じ
ここら辺はまぁ、こんな感じになっているんだ〜くらいに思っていただくだけで大丈夫です🙆
```:SYSTEM_TEMPLATE
"""You are Git Commit-Maker, a language model designed to generate a clear Commit message.
Your task is to create a commit message from given commit diff(changes), have not actually been committed but already indexed.

Follow these guidelines
- Focus on the new code (lines starting with '+')
- The generated title and description should prioritize the most significant changes.
- You are going to answer in well-designed Markdown 
- Generate all message in {language}


Example Commit Diff:
======
## file: 'src/file1.py'

@@ -12,5 +12,5 @@ def func1():
code line 1 that remained unchanged in the commit
code line 2 that remained unchanged in the commit
-code line that was removed in the commit
+code line added in the commit
code line 3 that remained unchanged in the commit

@@ ... @@ def func2():
...


## file: 'src/file2.py'
...
======

About output format:
{format_instruction}

"""
```

```:USER_TEMPLATE
"""The Commit Diff: 
=====
{patch}
=====
"""
```

##  利用したモデルは GPT-4 
エージェントの内部ではLangChainを使ってChatGPTのAPIを呼び出しています。使用しているモデルは`gpt-4-1106-preview`です。このモデルはJSON形式の出力が安定するようトレーニングされているため、LangChainの`PydanticOutputParser`を使って出力フォーマットを指定する場合に有効です。

### コードの一部
```python
llm = ChatOpenAI(model="gpt-4-1106-preview", temperature=0.0)
# 設定ファイルからシステムテンプレートとユーザーテンプレートを読み出してプロンプトを定義
prompt = ChatPromptTemplate.from_messages(messages=[
    ("system", settings.SYSTEM_TEMPLATE),
    ("user", settings.USER_TEMPLATE)
])
# CommitMessageオブジェクトを渡してパーサーを作成
parser = PydanticOutputParser(pydantic_object=CommitMessage) # 

# チェインの定義
chain = prompt | llm | parser

# 実際にここでOpenAI API が呼ばれる
result: CommitMessage = chain.invoke({
    "format_instruction": parser.get_format_instructions(),
    "patch": patch,
    "language": settings.language
})
```
### LangChainのPydanticOutputParser



LangChainのPydanticOutputParserを使うと、出力の構造をPydanticのモデルで定義できます.

```python:CommitMessage.py
class FeatureChange(BaseModel):
    title: str = Field(description="A feature added, removed, or changed")
    description: str = Field(description="more detailed and informative description about the feature change.")
    relevant_files: List[str] = Field(description="a list of relevant files related to what you are describing.")
class CommitMessage(BaseModel):
    title: str = Field(description="What is the main purpose of this commit?(72 characters)")
    changes: List[FeatureChange] = Field(max_items=6, description="a list of feature changes in this commit")
```
自分はこのモデルを定義する際にネットで "How to write a good commit message"みたいに検索して出てきた、たくさんの記事を参考にしました。

定義したモデルをパーサーに渡してパーサーをインスタンス化すると、モデルにパースできるようなJSON形式で出力してくださいという風な指示分が作成されます。
`parser.get_format_instructions()` 
これをプロンプトの最後の方に挿入することで出力形式を指定することができます。

`chain.invoke(..)`
LangChain内部でOpenAIのAPIが呼ばれるのはココです。 LLMからの回答が返ってきた時、パーサーが機能して文字列を定義したモデルに変換してくれるので返り値の方はパーサー作成時に指定した`CommitMessage`型のオブジェクトのインスタンスになります。
以下はコミットメッセージの自動生成エージェントを例にした図になります。

![スクリーンショット 2024-03-06 18.05.08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2788034/a9d6ec7c-3004-c660-4926-3fbbb2da0607.png)
変数`result` はCommitMessage型のインスタンスなので`result.title`や`result.changes[0].description`で結果のフィールドにアクセスできます。

該当するコードはこちら↓

https://github.com/Gonsix/Code-Agent/blob/6201493e77e23aa9df44a259d77c69ae134ce920/code_agent/agents/commit_maker.py



## コメント追加したり、バグ監査の機能も実装していきたい

code-agentは現在、コミットメッセージの生成とコードのリファクタリング提案という2つの機能を提供していますが、まだまだこれから機能を追加していく予定です。今後は、コード品質の向上する新たな機能、例えばバグの検出やコードのコメント生成などの機能を追加していく予定です。
皆さんのいいねだけでも励みになるのでぜひいいねをお願いします🙏