2024.09.27

OpenAI APIを使ってgit commitメッセージやコードレビューをAIに任せましょう!

次世代システム研究室の Y.I です。 OpenAI API を活用してちょっと便利なコマンドを作成したのでご紹介します。作成したものは、「自動Git Commitメッセージ生成」と「コードレビュー」機能です。LangChainやVectorDBなどを利用しなくても、発想次第で便利な機能を作れますので1例としてご覧ください。

機能紹介

Pythonで以下の機能を実現しています。

  • Git commitメッセージの自動生成
    Gitの変更履歴に基づき、適切な日本語のcommitメッセージを生成します。
  • コードレビューの自動化
    Gitの変更履歴に基づき、コードに問題がないかやパフォーマンス改善の提案を行います。
  • openai apiのtokenを環境変数から取り込み
    簡易的ですがtokenをScriptに直書きして漏洩しないように外から渡せるようにします。
  • 生成結果をクリップボードへコピー
    コピーする一手間を省略します。この一手間省略が使い勝手向上に役立ちます。

スクリプトの説明

このスクリプトは、主に以下のステップで構成されています。

1. コマンドライン引数の解析

スクリプトはargparseを使い、実行時に--review--commitのオプションを受け取り、どちらの機能を実行するかを判定します。

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
def parse_arguments():
parser = argparse.ArgumentParser(description='openai apiに問い合わせる内容')
parser.add_argument('--review', '-r', action='store_true', help='code review')
parser.add_argument('--commit', '-c', action='store_true', help='git commit message')
return parser.parse_args()
def parse_arguments(): parser = argparse.ArgumentParser(description='openai apiに問い合わせる内容') parser.add_argument('--review', '-r', action='store_true', help='code review') parser.add_argument('--commit', '-c', action='store_true', help='git commit message') return parser.parse_args()
def parse_arguments():
    parser = argparse.ArgumentParser(description='openai apiに問い合わせる内容')
    parser.add_argument('--review', '-r', action='store_true', help='code review')
    parser.add_argument('--commit', '-c', action='store_true', help='git commit message')
    return parser.parse_args()

2. Gitの差分取得

git diffコマンドを実行し、Gitの変更内容を取得します。この結果が、後にOpenAI APIに送信され、commitメッセージやコードレビューに使用されます。

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
def get_git_diff():
result = subprocess.run(['git', 'diff', 'HEAD'], stdout=subprocess.PIPE)
return result.stdout.decode('utf-8')
def get_git_diff(): result = subprocess.run(['git', 'diff', 'HEAD'], stdout=subprocess.PIPE) return result.stdout.decode('utf-8')
def get_git_diff():
    result = subprocess.run(['git', 'diff', 'HEAD'], stdout=subprocess.PIPE)
    return result.stdout.decode('utf-8')

3. OpenAI APIの呼び出し

OpenAI APIを使って、取得したGit差分に基づいて適切なメッセージを生成します。API呼び出しには、あらかじめ用意されたプロンプトを使い、モデルにはgpt-4o-miniを使用しています。git diff レベルの解析ならば、十分な回答を得られるのでコストを考慮して 4o-mini を利用しています。必要に応じて API を変更します。コマンドライン引数で設定するようにしても良いですね。

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
def exec_openai_api(prompt_system, prompt_user, diff_text):
openai.api_key = api_key
response = openai.chat.completions.create(
model="gpt-4o-mini",
messages=[
{"role": "system", "content": prompt_system},
{"role": "user", "content": prompt_user + f"#diff: {diff_text}"}
],
max_tokens=1000
)
return response
def exec_openai_api(prompt_system, prompt_user, diff_text): openai.api_key = api_key response = openai.chat.completions.create( model="gpt-4o-mini", messages=[ {"role": "system", "content": prompt_system}, {"role": "user", "content": prompt_user + f"#diff: {diff_text}"} ], max_tokens=1000 ) return response
def exec_openai_api(prompt_system, prompt_user, diff_text):
    openai.api_key = api_key
    response = openai.chat.completions.create(
        model="gpt-4o-mini",
        messages=[
            {"role": "system", "content": prompt_system},
            {"role": "user", "content": prompt_user + f"#diff: {diff_text}"}
        ],
        max_tokens=1000
    )
    return response

4. commitメッセージ生成のプロンプト

commitメッセージを生成するためのプロンプトでは、AIに「コードレビューの専門家」として振る舞うように指示し、わかりやすい日本語でメッセージを生成します。特に「修正によりどのように変わるのか」を生成してもらうことで、commit messageに作成したコードの効果が明記され、将来にコード解析する際の手がかかりやレビューアーへ有益な情報を伝えることができます。

<

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
def generate_commit_message():
return (
"あなたはプログラムコードレビューの専門家です。",
"git commitメッセージをmarkdownは使わずにplaintext形式の日本語で作ってください。"
"メッセージは、要約、修正内容、修正によりどのように変わるのかを回答して。"
"修正内容は特に簡潔に回答してください。"
"回答がmax_tokensを超えてしまう場合は文字数が収まるように要約してください。"
)
def generate_commit_message(): return ( "あなたはプログラムコードレビューの専門家です。", "git commitメッセージをmarkdownは使わずにplaintext形式の日本語で作ってください。" "メッセージは、要約、修正内容、修正によりどのように変わるのかを回答して。" "修正内容は特に簡潔に回答してください。" "回答がmax_tokensを超えてしまう場合は文字数が収まるように要約してください。" )
def generate_commit_message():
    return (
        "あなたはプログラムコードレビューの専門家です。",
        "git commitメッセージをmarkdownは使わずにplaintext形式の日本語で作ってください。"
        "メッセージは、要約、修正内容、修正によりどのように変わるのかを回答して。"
        "修正内容は特に簡潔に回答してください。"
        "回答がmax_tokensを超えてしまう場合は文字数が収まるように要約してください。"
    )

5. コードレビュー用のプロンプト

コードレビューを行う際には、詳細なチェック項目をAIに提供し、バグやパフォーマンスの改善点を指摘させる仕組みになっています。

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
def generate_codereview():
return (
"あなたはプログラムコードレビューの専門家です。",
"git commitメッセージを元に、以下の観点でコードレビューしてください。"
"また指摘があれば改善するサンプルコードも提示してください"
+ "バグがないかチェック"
+ "よりコード量が少なくなる効率が良いcodeの提案"
+ "無駄な処理を追加していないか"
+ "変数やメソッドパラメーターの値がNULLや0やマイナス値でも問題がないか"
+ "脆弱性につながるコードがないか"
+ "パフォーマンス改善につながる提案があるか"
+ "修正の目的を推測して、目的を満たしているか、より良い実現方法があれば提案してください"
+ "回答がmax_tokensを超えてしまう場合は文字数が収まるように要約してください"
)
def generate_codereview(): return ( "あなたはプログラムコードレビューの専門家です。", "git commitメッセージを元に、以下の観点でコードレビューしてください。" "また指摘があれば改善するサンプルコードも提示してください" + "バグがないかチェック" + "よりコード量が少なくなる効率が良いcodeの提案" + "無駄な処理を追加していないか" + "変数やメソッドパラメーターの値がNULLや0やマイナス値でも問題がないか" + "脆弱性につながるコードがないか" + "パフォーマンス改善につながる提案があるか" + "修正の目的を推測して、目的を満たしているか、より良い実現方法があれば提案してください" + "回答がmax_tokensを超えてしまう場合は文字数が収まるように要約してください" )
def generate_codereview():
    return (
        "あなたはプログラムコードレビューの専門家です。",
        "git commitメッセージを元に、以下の観点でコードレビューしてください。"
        "また指摘があれば改善するサンプルコードも提示してください"
        + "バグがないかチェック"
        + "よりコード量が少なくなる効率が良いcodeの提案"
        + "無駄な処理を追加していないか"
        + "変数やメソッドパラメーターの値がNULLや0やマイナス値でも問題がないか"
        + "脆弱性につながるコードがないか"
        + "パフォーマンス改善につながる提案があるか"
        + "修正の目的を推測して、目的を満たしているか、より良い実現方法があれば提案してください"
        + "回答がmax_tokensを超えてしまう場合は文字数が収まるように要約してください"
    )

6. クリップボードへのコピー

回答結果を一手間少なく扱えるように、クリップボードにコピーします。 api利用token数は表示のみでコピー対象から外しています。

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
# clipboard へ copy
pyperclip.copy(commit_message)
# clipboard へ copy pyperclip.copy(commit_message)
# clipboard へ copy
pyperclip.copy(commit_message)

7. script全体

python scriptの全体です。

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
import subprocess
import pyperclip
import os
import openai
import argparse
api_key = os.getenv('OPENAI_API_KEY')
if not api_key:
print("環境変数 OPENAI_API_KEYを登録してください。 OPENAI_API_KEY='xxx...'")
exit(1)
def parse_arguments():
"""Parse command-line args"""
parser = argparse.ArgumentParser(description='openai apiに問い合わせる内容')
parser.add_argument('--review', '-r', action='store_true', help='code review')
parser.add_argument('--commit', '-c', action='store_true', help='git commit message')
return parser.parse_args()
def get_git_diff():
"""git diff HEADを出力"""
result = subprocess.run(['git', 'diff', 'HEAD~3'], stdout=subprocess.PIPE)
return result.stdout.decode('utf-8')
def exec_openai_api(prompt_system, prompt_user, diff_text):
"""openai apiでgit commitメッセージを作成する"""
openai.api_key = api_key
response = openai.chat.completions.create(
# model="gpt-4o",
# model="gpt-4o-2024-08-06",
model="gpt-4o-mini",
# model="o1-preview",
messages=[
{"role": "system", "content": prompt_system},
{"role": "user", "content":
prompt_user + f"#diff: {diff_text}"}
],
max_tokens=1000
)
return response
def generate_commit_message():
"""openai apiでgit commitメッセージを作成する"""
return (
"あなたはプログラムコードレビューの専門家です。",
"git commitメッセージをmarkdownは使わずにplaintext形式の日本語で作ってください。メッセージは、要約、修正内容、修正によりどのように変わるのかを回答して。修正内容は特に簡潔に回答してください。回答がmax_tokensを超えてしまう場合は文字数が収まるように要約してください。"
)
def generate_codereview():
"""openai apiで codereview する"""
return (
"あなたはプログラムコードレビューの専門家です。",
"git commitメッセージを元に、以下の観点でコードレビューしてください。また指摘があれば改善するサンプルコードも提示してください"
+ "バグがないかチェック"
+ "よりコード量が少なくなる効率が良いcodeの提案"
+ "無駄な処理を追加していないか"
+ "変数やメソッドパラメーターの値がNULLや0やマイナス値でも問題がないか"
+ "脆弱性につながるコードがないか"
+ "パフォーマンス改善につながる提案があるか"
+ "修正の目的を推測して、目的を満たしているか、より良い実現方法があれば提案してください"
+ "回答がmax_tokensを超えてしまう場合は文字数が収まるように要約してください。"
)
def main():
"""メイン関数"""
diff_text = get_git_diff()
if not diff_text.strip():
print("No changes to commit.")
return
args = parse_arguments()
if args.review:
prompt_system, prompt_user = generate_codereview()
elif args.commit:
prompt_system, prompt_user = generate_commit_message()
else:
prompt_system, prompt_user = generate_commit_message()
response = exec_openai_api(prompt_system, prompt_user, diff_text)
commit_message = response.choices[0].message.content.strip()
print(f"commit message: {commit_message}")
print(f"token: {response.usage}")
# clipboard へ copy
pyperclip.copy(commit_message)
if __name__ == "__main__":
main()
import subprocess import pyperclip import os import openai import argparse api_key = os.getenv('OPENAI_API_KEY') if not api_key: print("環境変数 OPENAI_API_KEYを登録してください。 OPENAI_API_KEY='xxx...'") exit(1) def parse_arguments(): """Parse command-line args""" parser = argparse.ArgumentParser(description='openai apiに問い合わせる内容') parser.add_argument('--review', '-r', action='store_true', help='code review') parser.add_argument('--commit', '-c', action='store_true', help='git commit message') return parser.parse_args() def get_git_diff(): """git diff HEADを出力""" result = subprocess.run(['git', 'diff', 'HEAD~3'], stdout=subprocess.PIPE) return result.stdout.decode('utf-8') def exec_openai_api(prompt_system, prompt_user, diff_text): """openai apiでgit commitメッセージを作成する""" openai.api_key = api_key response = openai.chat.completions.create( # model="gpt-4o", # model="gpt-4o-2024-08-06", model="gpt-4o-mini", # model="o1-preview", messages=[ {"role": "system", "content": prompt_system}, {"role": "user", "content": prompt_user + f"#diff: {diff_text}"} ], max_tokens=1000 ) return response def generate_commit_message(): """openai apiでgit commitメッセージを作成する""" return ( "あなたはプログラムコードレビューの専門家です。", "git commitメッセージをmarkdownは使わずにplaintext形式の日本語で作ってください。メッセージは、要約、修正内容、修正によりどのように変わるのかを回答して。修正内容は特に簡潔に回答してください。回答がmax_tokensを超えてしまう場合は文字数が収まるように要約してください。" ) def generate_codereview(): """openai apiで codereview する""" return ( "あなたはプログラムコードレビューの専門家です。", "git commitメッセージを元に、以下の観点でコードレビューしてください。また指摘があれば改善するサンプルコードも提示してください" + "バグがないかチェック" + "よりコード量が少なくなる効率が良いcodeの提案" + "無駄な処理を追加していないか" + "変数やメソッドパラメーターの値がNULLや0やマイナス値でも問題がないか" + "脆弱性につながるコードがないか" + "パフォーマンス改善につながる提案があるか" + "修正の目的を推測して、目的を満たしているか、より良い実現方法があれば提案してください" + "回答がmax_tokensを超えてしまう場合は文字数が収まるように要約してください。" ) def main(): """メイン関数""" diff_text = get_git_diff() if not diff_text.strip(): print("No changes to commit.") return args = parse_arguments() if args.review: prompt_system, prompt_user = generate_codereview() elif args.commit: prompt_system, prompt_user = generate_commit_message() else: prompt_system, prompt_user = generate_commit_message() response = exec_openai_api(prompt_system, prompt_user, diff_text) commit_message = response.choices[0].message.content.strip() print(f"commit message: {commit_message}") print(f"token: {response.usage}") # clipboard へ copy pyperclip.copy(commit_message) if __name__ == "__main__": main()
import subprocess
import pyperclip
import os
import openai
import argparse

api_key = os.getenv('OPENAI_API_KEY')
if not api_key:
    print("環境変数 OPENAI_API_KEYを登録してください。 OPENAI_API_KEY='xxx...'")
    exit(1)

def parse_arguments():
    """Parse command-line args"""
    parser = argparse.ArgumentParser(description='openai apiに問い合わせる内容')
    parser.add_argument('--review', '-r', action='store_true', help='code review')
    parser.add_argument('--commit', '-c', action='store_true', help='git commit message')
    return parser.parse_args()

def get_git_diff():
    """git diff HEADを出力"""
    result = subprocess.run(['git', 'diff', 'HEAD~3'], stdout=subprocess.PIPE)
    return result.stdout.decode('utf-8')

def exec_openai_api(prompt_system, prompt_user, diff_text):
    """openai apiでgit commitメッセージを作成する"""
    openai.api_key = api_key
    response = openai.chat.completions.create(
        # model="gpt-4o",
        # model="gpt-4o-2024-08-06",
        model="gpt-4o-mini",
        # model="o1-preview",
        messages=[
            {"role": "system", "content": prompt_system},
            {"role": "user", "content": 
            prompt_user + f"#diff: {diff_text}"}
        ],
        max_tokens=1000
    )
    return response

def generate_commit_message():
    """openai apiでgit commitメッセージを作成する"""
    return (
        "あなたはプログラムコードレビューの専門家です。",
        "git commitメッセージをmarkdownは使わずにplaintext形式の日本語で作ってください。メッセージは、要約、修正内容、修正によりどのように変わるのかを回答して。修正内容は特に簡潔に回答してください。回答がmax_tokensを超えてしまう場合は文字数が収まるように要約してください。"
        )

def generate_codereview():
    """openai apiで codereview する"""
    return (
        "あなたはプログラムコードレビューの専門家です。",
        "git commitメッセージを元に、以下の観点でコードレビューしてください。また指摘があれば改善するサンプルコードも提示してください"
        + "バグがないかチェック"
        + "よりコード量が少なくなる効率が良いcodeの提案"
        + "無駄な処理を追加していないか"
        + "変数やメソッドパラメーターの値がNULLや0やマイナス値でも問題がないか"
        + "脆弱性につながるコードがないか"
        + "パフォーマンス改善につながる提案があるか"
        + "修正の目的を推測して、目的を満たしているか、より良い実現方法があれば提案してください"
        + "回答がmax_tokensを超えてしまう場合は文字数が収まるように要約してください。"
        )

def main():
    """メイン関数"""
    diff_text = get_git_diff()
    if not diff_text.strip():
        print("No changes to commit.")
        return

    args = parse_arguments()
    if args.review:
        prompt_system, prompt_user = generate_codereview()
    elif args.commit:
        prompt_system, prompt_user = generate_commit_message()
    else:
        prompt_system, prompt_user = generate_commit_message()
        
    response = exec_openai_api(prompt_system, prompt_user, diff_text)
    commit_message = response.choices[0].message.content.strip()
    print(f"commit message: {commit_message}")
    print(f"token: {response.usage}")

    # clipboard へ copy
    pyperclip.copy(commit_message)

if __name__ == "__main__":
    main()

実行結果

docker compose.yaml の volume に関する設定を変更した内容に対して、git commit message を生成してみた結果になります。

・git diff 内容

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
diff --git a/compose.yaml b/compose.yaml
index 7a99870..79e881e 100644
--- a/compose.yaml
+++ b/compose.yaml
@@ -39,7 +39,7 @@ networks:
volumes:
db1vol:
- external: false
+ external: true
db2vol:
- external: false
+ external: true
diff --git a/compose.yaml b/compose.yaml index 7a99870..79e881e 100644 --- a/compose.yaml +++ b/compose.yaml @@ -39,7 +39,7 @@ networks: volumes: db1vol: - external: false + external: true db2vol: - external: false + external: true
diff --git a/compose.yaml b/compose.yaml
index 7a99870..79e881e 100644
--- a/compose.yaml
+++ b/compose.yaml
@@ -39,7 +39,7 @@ networks:

 volumes:
   db1vol:
-    external: false
+    external: true
   db2vol:
-    external: false
+    external: true

・生成結果

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
commit message: コミットメッセージ:
要約: Docker Composeのボリューム設定を修正
修正内容: db1volおよびdb2volのexternal属性をfalseからtrueに変更
修正により: ボリュームが外部ボリュームとして扱われ、他のプロジェクトやサービスからもアクセス可能になる。
token: CompletionUsage(completion_tokens=78, prompt_tokens=202, total_tokens=280, completion_tokens_details={'reasoning_tokens': 0})
commit message: コミットメッセージ: 要約: Docker Composeのボリューム設定を修正 修正内容: db1volおよびdb2volのexternal属性をfalseからtrueに変更 修正により: ボリュームが外部ボリュームとして扱われ、他のプロジェクトやサービスからもアクセス可能になる。 token: CompletionUsage(completion_tokens=78, prompt_tokens=202, total_tokens=280, completion_tokens_details={'reasoning_tokens': 0})
commit message: コミットメッセージ:

要約: Docker Composeのボリューム設定を修正

修正内容: db1volおよびdb2volのexternal属性をfalseからtrueに変更

修正により: ボリュームが外部ボリュームとして扱われ、他のプロジェクトやサービスからもアクセス可能になる。
token: CompletionUsage(completion_tokens=78, prompt_tokens=202, total_tokens=280, completion_tokens_details={'reasoning_tokens': 0})

終わりに

このスクリプトを使えば、Gitのcommitメッセージやコードレビューの作業をAIに任せることができ、開発者の作業効率が向上します。OpenAI APIを活用したこのアプローチは、日々の開発作業をよりスマートに進めるための強力なツールです。ちょっとした script ですが、あまりの便利さにgit commit message を自分で記述する気になれません。。AI を利用してちょっとしたことから改善していきましょう!

グループ研究開発本部では、グループ全体のインテグレーションを支援してくれるアーキテクトを募集しています。アプリケーション開発の方、次世代システム研究室にご興味を持って頂ける方がいらっしゃいましたら、ぜひ募集職種一覧からご応募をお願いします。

  • Twitter
  • Facebook
  • はてなブックマークに追加

グループ研究開発本部の最新情報をTwitterで配信中です。ぜひフォローください。

 
  • AI研究開発室
  • 大阪研究開発グループ

関連記事