【ChatGPT】GitHubプルリクエスト作成時に差分を要約、自動コメントする仕組みを作ってみた

diffコマンドの結果をChatGPTで要約
2023.03.22

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

ChatGPT の波に乗るべく、私もOpenAI APIを触り始めました。 ChatGPTの用途は様々あります。個人的には、要約機能が特に汎用的だと感じています。 以下のような活用ブログもありますね。

本ブログでも ChatGPTによる要約スキルを確かめてみます。 ただ、「コンテンツそのものの要約」ではなく 「コンテンツの更新内容の要約」 ができないか、 に焦点を当てます。

今回 ChatGPTには、いわゆる diff コマンドの結果を解析してもらいます。 そして、それを活用した「GitHubプルリクエスト(PR)の更新内容を要約する仕組み」まで作ってみました。

はじめにまとめ

以下 Pythonスクリプト、およびGitHub Actions ワークフローを作りました。

summarize_diff.py の入力は 対象コンテンツのパスそのコンテンツの更新差分(diffコマンド) です。 以下実行例です。

$ cat diff.txt
# 1c1
# < 突然ですがラスベガスの美味しいラメーン屋さんを紹介します
# ---
# > 突然ですがラスベガスの美味しいラーメン屋さんを紹介します

$ python3 summarize_diff.py blog.md diff.txt
# 更新内容は以下のとおりです。
#
# - 「ラメーン屋」を「ラーメン屋」に修正しました。

.github/workflows/comment-pr-diff-summary-by-chatgpt.yml は上記Pythonスクリプトを組み込んだ GitHun Actions ワークフローです。 以下プルリクエスト作成時の動作例です。

img

作ってみる

[事前準備] OpenAIのAPIキーを発行・登録

OpenAI APIを利用するためのキーを発行します。

img

そのキーをGitHub のシークレットに登録します。 リポジトリの [Settings > Security > Secrets and variables > Actins] の画面へ移動します。 [Repository secrets]OPENAI_API_KEY として登録します。

img

これで GitHub Actions から OpenAI API を呼び出す準備ができました。

Pythonスクリプトを作成

以下のような Pythonスクリプトを作成しました。

import os
import sys
import openai
from pathlib import Path

content_path = sys.argv[1]
diff_path = sys.argv[2]

# APIキーの設定
openai.api_key = os.environ.get("OPENAI_API_KEY")

chatgpt_system_content = '''これから以下の入力を与えます。
- 入力(コンテンツのファイル名)
- 入力(「修正前のコンテンツ」と「修正後のコンテンツ」のdiffコマンドの結果)
  - "< " から始まる行は修正前のコンテンツに該当します
  - "> " から始まる行は修正後のコンテンツに該当します
あなたは与えられた入力から「修正前のコンテンツ」と「修正後のコンテンツ」の差分を調べてください。
「修正前のコンテンツ」と「修正後のコンテンツ」の差分から、修正内容を要約して出力してください。
### 制約
- Markdown形式で出力してください。
- 出力の先頭は「更新内容は以下のとおりです。」としてください。
- リスト形式で更新内容を出力してください。
- 1つのリストアイテムには1つの更新内容を出力してください。
- 日本語で出力してください。
- "です・ます調" で出力してください。
'''

chatgpt_user_content = "### 入力(コンテンツのファイル名)\n\n" + content_path \
    + "\n\n### 入力(「修正前のコンテンツ」と「修正後のコンテンツ」のdiffコマンドの結果)\n\n" + Path(diff_path).read_text()

response = openai.ChatCompletion.create(
    model="gpt-3.5-turbo",
    messages=[
        {"role": "system", "content": chatgpt_system_content},
        {"role": "user", "content": chatgpt_user_content},
    ],
)
print(response.choices[0]["message"]["content"].strip())

プロンプトとしては「指示」と「インプット」に分かれます。

「指示」は以下のような内容です。 一言で言うと "これからインプットされるdiffコマンドを調査して要約してね" です。

これから以下の入力を与えます。
- 入力(コンテンツのファイル名)
- 入力(「修正前のコンテンツ」と「修正後のコンテンツ」のdiffコマンドの結果)
  - "\< " から始まる行は修正前のコンテンツに該当します
  - "\> " から始まる行は修正後のコンテンツに該当します
あなたは与えられた入力から「修正前のコンテンツ」と「修正後のコンテンツ」の差分を調べてください。
「修正前のコンテンツ」と「修正後のコンテンツ」の差分から、修正内容を要約して出力してください。
### 制約
- Markdown形式で出力してください。
- 出力の先頭は「更新内容は以下のとおりです。」としてください。
- リスト形式で更新内容を出力してください。
- 1つのリストアイテムには1つの更新内容を出力してください。
- 日本語で出力してください。
- "です・ます調" で出力してください。

「インプット」には以下のような形式で貼り付けます。

### 入力(コンテンツのファイル名)

${ファイル名}

### 入力(「修正前のコンテンツ」と「修正後のコンテンツ」のdiffコマンドの結果)

${diffコマンドの結果

以下にローカル環境で試した例を1つ載せます。

$ cat diff.txt
# 2c2
# <   cidr_block           = "10.0.0.0/16"
# ---
# >   cidr_block           = "10.0.0.0/24"

$ python3 summarize_diff.py network.tf diff.txt
# 更新内容は以下のとおりです。
#  
# - cidr_block を "10.0.0.0/16" から "10.0.0.0/24" に変更しました。

GitHub Actions ワークフローを作成

作ったワークフローは以下のとおり。

name: comment-pr-summary-by-chatgpt

on:
  pull_request_target:
    types: [ opened, reopened ]

jobs:
  comment-pr-summary-by-chatgpt:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      pull-requests: write
    steps:
      #####
      # セットアップ
      #####
      - name: Switch to pull request branch
        uses: actions/checkout@v3
        with:
          ref: ${{ github.event.pull_request.head.sha }}

      - name: Fetch base branch
        run: |
          git fetch origin \
            ${{ github.event.pull_request.base.sha }}:BASE
      - name: Setup Python
        uses: actions/setup-python@v3
        with:
          python-version: '3.9'
          architecture: 'x64'

      - name: Install OpenAI
        run: |
          pip install -U openai
      #####
      # 更新されたファイル名一覧を取得
      #####
      - name: Get diff files
        run: |
          # ※特定拡張子にフィルタしています
          git diff --diff-filter=M --name-only HEAD BASE \
            | grep -e "\.md" -e "\.tf$" -e "\.yaml$" -e "\.py$" > _diff_files.txt
          cat _diff_files.txt
      #####
      # Run ChatGPT
      #####
      - name: run ChatGPT
        shell: bash {0}
        env:
          OPENAI_API_KEY: ${{secrets.OPENAI_API_KEY}}
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          URL: ${{ github.event.pull_request.html_url }}
        run: |
          # 各ファイルに対して「差分取得 -> 要約 -> PRコメント」を行う
          cat _diff_files.txt \
          | while read file_path;do
            echo "## summarize updates: ${file_path}"
            # 修正前テキスト, 修正後テキストの取得
            git cat-file -p BASE:${file_path} > _before.txt
            git cat-file -p HEAD:${file_path} > _after.txt
            # 差分の取得
            diff _before.txt _after.txt > _diff.txt | true
            cat _diff.txt
            # ChatGPTによる要約の取得
            chatgpt_response=$(python3 summarize_diff.py "${file_path}" "_diff.txt")
            echo "${chatgpt_response}"
            # GitHub PR へコメント
            echo "### :robot: ${file_path} の更新まとめ by ChatGPT
            ${chatgpt_response}" > _body.txt
            gh pr comment --body-file _body.txt "${URL}"
          done

Get diff files ステップで PRの Headブランチと Baseブランチの差分(変更差分のみ)を取得します。

それぞれのファイルに対して run ChatGPT ステップで要約を取得しています。 以下、実行している bash スクリプトです。

# 各ファイルに対して「差分取得 -> 要約 -> PRコメント」を行う
cat _diff_files.txt \
| while read file_path;do
  echo "## summarize updates: ${file_path}"
  # 修正前テキスト, 修正後テキストの取得
  git cat-file -p BASE:${file_path} > _before.txt
  git cat-file -p HEAD:${file_path} > _after.txt
  # 差分の取得
  diff _before.txt _after.txt > _diff.txt | true
  cat _diff.txt
  # ChatGPTによる要約の取得
  chatgpt_response=$(python3 summarize_diff.py "${file_path}" "_diff.txt")
  echo "${chatgpt_response}"
  # GitHub PR へコメント
  echo "### :robot: ${file_path} の更新まとめ by ChatGPT
  ${chatgpt_response}" > _body.txt
  gh pr comment --body-file _body.txt "${URL}"
done

試してみる

Pythonコードの修正

「不完全なFizzBuzzコード」を完成させるPRを作ります。

img

返ってきたコメントがこちら。

img

ファイル名( test/fizzbuzz.py )や print("FizzBuzz") という情報から 『3の倍数と5の倍数』 と、よしなに予測していそうです。

ただ、再度実行するとこんな感じで、とても機械的な差分出力になりました。少し残念。

img

CloudFormationテンプレートの修正

CloudFormationテンプレートのパラメータを修正してみます。

img

結果がこちら。必要最小限ですが悪くないです。

img

Terraformコードの修正

パラメータ変更とリソース削除のPRを出してみます。

img

返ってきたコメントがこちら。

img

いいですね。削除された行の塊を「aws_flow_logリソース」と把握してくれているのは 地味にGoodポイントではないでしょうか。

Markdownファイル更新

「タイポ修正を3つ」と「加筆」を行っています。

img

返ってきたコメントがこちら。

img

うーん。言っていることは合っているけど、もうちょっと修正したポイントにフォーカスしてほしいですね。

追加で何度か試してみました。3回目の「誤り(=タイポ修正)」という表現はGoodですね。

img

img

考慮点や課題など

ワークフロー・スクリプトの考慮点

今回作った仕組みは差分のあるファイル毎に OpenAI API を叩きます。 大量のファイル更新の差分があるPRを作成する際には、 実行時間およびAPI利用料金には注意してください。

また、簡易的なPythonスクリプトなので「トークン数上限」考慮を入れていません。 以下のように事前にトークン数を見積るツールを適宜活用すると良さそうです。

品質向上の課題

本当は diffコマンドだけではなく、 以下のように「修正前」と「修正後」のコンテンツも参考情報としてインプットさせたかったです。

### 入力(コンテンツのファイル名)

${ファイル名}

### 入力(修正前のコンテンツ)

${修正前のコンテンツ}

### 入力(修正後のコンテンツ)

${修正後のコンテンツ}

### 入力(「修正前のコンテンツ」と「修正後のコンテンツ」のdiffコマンドの結果)

${diffコマンドの結果}

…が先程のトークン数上限の関係上、難しかったです。

「どの括り」の「どの部分」が変わったかが分かるのはメリットなので、 上限が無ければ追加しちゃいたいですね。

以下、「修正前」と「修正後」のコンテンツもインプットさせて、成功したときのキャプチャです。

img

  • VPCの CIDRブロックが…
  • VPCの DNSサポートが…

というように、どのリソースのどのパラメータが変わったかを、ChatGPTが判断してくれています。

おわりに

以上、OpenAI API をプルリクエストに組み込んでみました。

今回の出来としては、「あくまで参考」程度に捉えるのが良いのかなと思いますが、 ざっくりと変更点を日本語で書いてくれるのは良いですね。

プロンプトもまだまだ改善できそうなので、チューニングしていきたいです。

参考

補足

「Pythonコードの修正」テストの diffコマンドの結果

2c2,4
<     if i % 3 == 0:
---
>     if i % 15 == 0:
>         print("FizzBuzz")
>     elif i % 3 == 0:

「CloudFormationテンプレートの修正」テストの diffコマンドの結果

2c2
<   cidr_block           = "10.0.0.0/24"
---
>   cidr_block           = "10.0.0.0/16"
4c4
<   enable_dns_support   = false
---
>   enable_dns_support   = true

「Terraformコードの修正」テストの diffコマンドの結果

2c2
<   cidr_block           = "10.0.0.0/24"
---
>   cidr_block           = "10.0.0.0/16"
4c4
<   enable_dns_support   = false
---
>   enable_dns_support   = true
16,27d15
<   }
< }
< 
< resource "aws_flow_log" "this" {
<   log_destination      = local.flowlogs_bucket_arn
<   log_destination_type = "s3"
<   traffic_type         = "ALL"
<   vpc_id               = aws_vpc.this.id
<   destination_options {
<     file_format                = "parquet"
<     hive_compatible_partitions = true
<     per_hour_partition         = true

「Markdownファイル更新」テストの diffコマンドの結果

1c1
< そもそもマルチアカウンt戦略とは
---
> そもそもマルチアカウント戦略とは
7c7
< そもそもAWS(クラウド)を使うこと辞退が、アジリティとガバナンスを両立できるための手段となります。
---
> そもそもAWS(クラウド)を使うこと自体が、アジリティとガバナンスを両立できるための手段となります。
14c14
< - リソースのセキュリティ評価 ( by AWS SecurityHub, Amazon Inspector ) など...
---
> - リソースのセキュリティ評価 ( by AWS Security Hub, Amazon Inspector ) など...
22a23,28
> 
> 具体的なマルチアカウント戦略のメリットは以下のとおりです。
> 
> - メリット1: セキュリティ向上
> - メリット2: 開発スピードの促進
> - メリット3: コスト最適化