はじめに
Sreake事業部でインターンをしている村山です。そのようなコードレビューの作業に対し、今日ではLLMを使用したレビュー用のツールが開発されています。今回はそのレビューツールの一つであるPR-Agentを中心に、ツールの調査を行いました。
これはあるPythonのコードです。このコードにはいくつか好ましくない点がありますが、それは一体どこでしょうか?
f = open("file.text", "r")
try:
content = f.read()
finally:
f.close()
if something == None:
print("Something is None!")
コードレビューをする際には、先程のコードような悪い箇所を改善し、コードの健全性を保証する必要があります。コードの健全性を保つためには、時間の制約があるチームであったとしても高い品質のレビューが必要であるとGoogle Engineering Practices Documentationでは述べられています。
しかしながら、コードレビューにも労力が必要であり、高い品質のレビューをすることは簡単ではありません。そういったレビューのサポートになりうるPR-Agent等のツールについて説明していきます。
PR-Agentとは
CodiumAI社によって開発された、PRのレビューをより高速、効率的にするためのツールです。PR-AgentはGPT-4もしくはGPT-3.5を使用し、PRの内容に基づいた様々なフィードバックを提供しています。提供しているフィードバックには以下のようなものがあります。
- 説明の生成
- レビュー
- 内容に対しての質問の応答
- コードの改善
PR-Agentの流れは以下の図のようになっています。
Codium-ai/pr-agentのREADMEより引用
- リクエストを行う 公開リポジトリのPRへのコメント、PR-Agent導入後のプライベートリポジトリのPRにコメント、CLIを用いてPR-Agentを実行するなどしてPR-Agentへのリクエストを行います。
- PR-Agentによるリクエストの処理 PR-Agentはリクエストから、PRのステータス、変更部分の検出と優先順位付け、現在は未だ対応していませんがcontribution.mdやguidelines.mdなどドキュメントの検出を行います。
- PR-Agentのプランニング ユーザのリクエストを分析し、トークンを考慮した圧縮や優先順位付けを行います。
- toolの選択と回答の作成 ユーザのリクエストに基づいて使用するPR-Agentのtoolを選択します。また、そのtoolによって生成された結果をPR comment、PR descriptionなどによって返します。
類似プロジェクトとしては以下のようなものがあります。
- coderabiitai/ai-pr-reviewer
- anc95/ChatGPT-CodeReview
- merwanehamadi/AI-Maintainer
- truskovskiyk/pr-reviewer
これらのプロジェクトは全てGPT-4などのLLMを使用しているため、基本的にはPRの説明の生成やレビューなど同様の機能を提供しています。それぞれのプロジェクトの導入方法や特徴について、以下の表に示します。
使用方法 | 特徴 | |
---|---|---|
ai-pr-reviewer | ・GitHub Actions | Professinal版はCodeRabbit Proで利用可能 |
ChatGPT-CodeReview | ・GitHub App ・GitHub Actions ・Self-hosting | 自動的なレビューのみ(対話不可) |
AI-Maintainer | ・Self-hosting | ローカルで動かす場合は、コードの修正とポートフォワーディングが必要 |
pr-reviewer | ・Python パッケージ | ・GPT-3.5のみ ・自動的なレビューのみ(対話不可) |
PR-Agentの使用方法
PR-Agentの使用方法として、公式のリポジトリでは以下の9つの方法を提示されています。
- Docker imageの使用
- ソースから実行
- GitHub Actionsとしての実行
- Polling serverとしての実行
- GitHub Appとしての実行
- AWS Lambda Functionでのデプロイ
- AWS CodeCommit(PR-Agent CLIの使用)
- GitHub webhook serverでの実行
- Bitbucketパイプラインとしての実行
ここでは、GitHub Actionとしての実行とソースからの実行を例として使用方法について述べていきます。どちらも共通してOpenAI API keyが必要で、ソースからの実行の場合はそれに加えてGitHub Tokenが必要になります。
GitHub Actionとしての実行
1.OpenAI API keyをリポジトリのシークレットに追加する
GitHub Actionsでのシークレットに名前をOPENAI_KEY
としてOpenAIのキーを登録します。
2.リポジトリの.github/workflows/pr_agent.ymlに以下を追加する
on:
pull_request:
issue_comment:
jobs:
pr_agent_job:
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 }}
3.メインブランチにワークフローファイルの追加の変更をマージする。
マージ以降は、PRを開くとそのレビューとPR-Agentの使い方の説明がコメントとして表示されます。PRを開いた際の自動レビューや説明の生成を行うかどうかは、pr_agent.yml内に以下のような環境変数を追加することで設定を行えます。
env:
OPENAI_KEY: ${{ secrets.OPENAI_KEY }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
github_action.auto_review: "true" # enable\disable auto review
github_action.auto_describe: "true" # enable\disable auto describe
ソースからの実行
1.リポジトリをクローンする
git clone https://github.com/Codium-ai/pr-agent.git
cd pr-agent
2.仮想環境にrequirementsをインストールする
python3 -m venv venv
. ./venv/bin/activate
pip install -r requirements.txt
3.シークレットのテンプレートファイルをコピーし、OpenAI keyとGitHub tokenを入力する
cp pr_agent/settings/.secrets_template.toml pr_agent/settings/.secrets.toml
chmod 600 pr_agent/settings/.secrets.toml
.secrets.toml
[openai]
key = "<your openai key>"
...
[github]
user_token = "<your github token>"
4.スクリプトを実行する
python3 -m pr_agent.cli --pr_url <pr_url> {review, ask, descrive ...}
# PRレビューをする場合は以下のようになる
python3 -m pr_agent.cli --pr_url <pr_url> review
Pythonモジュールとして設定するにはpr-agent/pr_agentをPYTHONPATH環境変数に設定します。
export PYTHONPATH=[$PYTHONPATH:]<PATH to pr_agent folder>
言語モデルの変更
ソースからの実行の場合、設定ファイルを編集することでデフォルトのGPT-4以外のモデルを使用することもできます。利用可能なモデルとトークン数についてはpr-agent/pr_agent/algo/init.pyを参照してください。ここでは例として、Vertex AIのcodechat-bisonに変更する方法を示します。
Vertex AIに変更するには、pr_agent/settingsのconfiguration.tomlと.secrets.tomlを編集します。
configuration.toml
[config]
model = "vertex_ai/codechat-bison"
.secrets.toml
[vertexai]
vertex_project = "my-google-cloud-project"
vertex_location = "project-location"
試しに以下のようにaskで使用しているLLMを聞くと、画像のように答えてくれます。
python3 -m pr_agent.cli --pr_url <pr_url> ask "Tell me which LLM you're using."
PR-Agentのプロンプトについて
PR-Agentのask
やdescription
などの各コマンドのプロンプトはpr-agent/pr_agent/settingsのpr_{command}_prompts.tomlから確認することができます。一例として、pr_description_prompts.tomlでは、はじめにペルソナの設定とタスクについての指示を行っています。
- You are PR-Reviewer, a language model designed to review a git Pull Request (PR). Your task is to provide a full description for the PR content.
- Make sure to focus on the new PR code (lines starting with ‘+’).
- Keep in mind that the ‘Previous title’, ‘Previous description’ and ‘Commit messages’ sections may be partial, simplistic, non-informative or out of date. Hence, compare them to the PR diff code, and use them only as a reference.
- Prioritize the most significant PR changes first, followed by the minor ones.
- If needed, each YAML output should be in block scalar format (‘|-‘)
日本語訳がこちらです。
- PR-Reviewerとして、gitのプルリクエスト(PR)をレビューするために設計された言語モデルです。 あなたの仕事は、PRの内容について完全な説明を提供することです。
- 新しいPRコード(「+」で始まる行)に焦点を当ててください。
- 「以前のタイトル」、「以前の説明」、「コミットメッセージ」のセクションは部分的であったり、簡素であったり、情報が不足していたり、古かったりする可能性があることに注意してください。したがって、それらをPR diffコードと比較し、参照としてのみ使用してください。
- 最も重要なPRの変更を最初に優先し、その後に小さなものを続けてください。
- 必要に応じて、各YAML出力はブロックスカラ形式(「|-」)であるべきです。
タスクの指示のあとには、出力するYAMLオブジェクトの制約や出力例が指示されています。
The output must be a YAML object equivalent to type $PRDescription, according to the following Pydantic definitions:
‘
class PRType(str, Enum):
bug_fix = “Bug fix”
tests = “Tests”
refactoring = “Refactoring”
enhancement = “Enhancement”
documentation = “Documentation”
other = “Other”
日本語訳がこちらです。
出力は、以下のPydantic定義に従って$PRDescription型に相当するYAMLオブジェクトでなければなりません:
‘
class PRType(str, Enum):
bug_fix = “バグ修正”
tests = “テスト”
refactoring = “リファクタリング”
enhancement = “機能向上”
documentation = “ドキュメンテーション”
other = “その他”
そして再びプロンプトの最後には以下のように出力の制約について再度指示がなされています。
Answer should be a valid YAML, and nothing else. Each YAML output MUST be after a newline, with proper indent, and block scalar indicator (‘|-‘)
日本語訳がこちらです。
回答は有効なYAMLでなければならず、それ以外のものではありません。各YAML出力は改行の後、適切なインデントとブロックスカラ指標(「|-」)を持っていなければなりません。
これらのプロンプトに加えて、ユーザは追加のプロンプトを与えることができます。CLIから実行している場合は、pr_agent/settings/configuration.tomlの各ツールのextra_instructions
を指定するか、実行時に以下のようにextra_instructions
を追加してください。
以下のコマンドは、CHANGELOG.mdの更新時に、重要な部分を文字色と太字によって強調するよう指示しているものです。
/update_changelog --pr_update_changelog.extra_instructions="Please emphasize the important parts by making the text red and bold."
GitHub Actionsを使用する場合は、workflowファイルの環境変数にextra_instructions
を追加することで対応できます。
env:
OPENAI_KEY: ${{ secrets.OPENAI_KEY }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_UPDATE_CHANGELOG.EXTRA_INSTRUCTIONS: 'Please emphasize the important parts by making the text red and bold.'
PythonのBad codeに対する挙動
PR-Agentの機能の一つに「コードの改善」という機能があります。記事の冒頭で提示したようなPythonのBad codeを対象に、コードの改善の機能の挙動を調査しました。このBad codeはこちらのリポジトリを参考に作成しました。このコードでは、bad-python-codeで紹介されているimportのワイルドカードや大量のif-elifなどの好ましくないコードが含まれている他、関数名の重複エラー、未到達のコードなどもあります。
from datetime import *
def func_x(num):
if num == 1:
return a()
elif num == 2:
return b()
elif num == 3:
return c()
elif num == 4:
return d()
elif num == 5:
return e()
def func_x(is_chubby):
if is_chubby is True:
return a()
elif is_chubby is False:
return b()
def func_x(array):
for i in range(len(array)):
print(array[i])
def func_x(num):
if num > 10:
return True
else:
return False
a = 1
b = 2
c = 3
x = [10, 20, 30]
a = x[0]
b = x[1]
c = x[2]
def func_x(breeds, pet):
for breed in breeds:
if breed == pet:
print("I have a dog.")
else:
print("I don't have a dog")
def func_x(param):
if param:
return 200, {"message": f"{param}"}
else:
return 400, "A useful error message"
f = open("file.text", "r")
try:
content = f.read()
finally:
f.close()
if something == None:
print("Something is None!")
condition = True
if condition == True:
print("'Tis true!")
doggos = ["tamago", "charlie"]
if type(doggos) is list:
print("'doggos' is a list")
def get_lego():
return "21103", "Back to the Future Time Machine", "401", "2013"
my_lego = get_lego()
print(my_lego[0], my_lego[1], my_lego[2], my_lego[3])
numbers = [10, 20, 30]
double_numbers = list(map(lambda num: num * 2, numbers))
dictionary = {}
if "key" in dictionary:
item = dictionary["key"]
else:
item = ""
try:
item = dictionary["key"]
except KeyError:
item = ""
try:
return do_something(my_dict[key])
except KeyError:
return ""
if super_long_comparsion1 and super_long_comparison2:
pass
このようなコードがPR-Agentでの/improveコマンドによってどのように改善が行われるのか検証します。先ほどのBad codeをbad_code.pyとしてコミットを行い、Pull Requestを開きます。PR-Agentを導入していればPRを開いたあと、少し待つとPR-Agentから以下の画像のようなコメントが得られます。
このフィードのCode feedbackを確認するとbad codeに対する改善案が提示されていることがわかります。PR Feedbackでは以下のような提案、コードフィードバックが行われています。
- General suggestions
このPRには、同じ名前のfunc_x
という多くの重複した関数定義が含まれています。Pythonは最後の定義を有効なものと見なすため、問題が発生します。
これらの関数をそれぞれの機能を反映するように名前を変更することをお勧めします。また、PRには変更を検証するテストが不足しています。 - Code feedback
- 関連ファイル:bad_code.py
提案:from datetime import *
の使用を避けてください。
名前空間を汚染しないために、必要な関数やクラスのみをインポートする方が良いです。【重要】
関連行:from datetime import *
- 関連ファイル:bad_code.py
提案:is True
やis False
の使用の代わりに、if条件で直接ブール変数を使用できます。【中程度】
関連行:if is_chubby is True:
- 関連ファイル:bad_code.py
提案:== None
の使用の代わりにis None
を使用してください。これはよりPython的で、速度も速いです。【中程度】
関連行:if something == None:
- 関連ファイル:bad_code.py
提案:type(doggos) is list
の使用の代わりにisinstance(doggos, list)
を使用してください。これはよりPython的で、継承を扱う場合(doggosがlistのサブクラスである場合)にも適用されます。【中程度】
関連行:if type(doggos) is list:
- 関連ファイル:bad_code.py
ここから、/improveコマンドによって具体的なコードの改善案を提示してもらいます。以下の画像のように”/improve”とコメントを入力すると👀のリアクションがつけられます。
リアクションがつけられた後、少し待っているとコードの改善案を以下のように提示してくれます。画像は改善案の一部で、実際にはほぼ全ての好ましくないコードに対して改善案を提示してくれています。
元のコードと改善後のコード、改善の理由などが提示されています。参考程度に同じコードをCodeRabbitによるコード改善を行ったところ以下のようなレスポンスが得られました。PR-Agentと同様に画像で指摘している部分以外の部分も改善案を提示しています。
比較演算子の説明で、PR-Agentではshould beとされている箇所がmore Pythonicというような表現になっていたりもしますが、同様の改善案を提示していることがわかります。
所感
今回はコードレビュー用のツールについての調査・検証をしました。これらのツールは、導入することによってコードの質の確保や作業効率の向上が図れる品質であると感じました。特にPR-Agentに関しては、標準機能の多さ、導入方法の多さ、プロンプトの柔軟性などが他のツールと比べ優位であると感じました。また、開発コミュニティも活発であり、Amazon Bedrockへの対応などが素早く行われていました。
Copilot pull requestがベータ版で提供されるなど、LLMの発展と同様にこのようなツールの発展も活発であるため、今後も動向を追っていきたいと思います。