ChatGPT の波に乗るべく、私もOpenAI APIを触り始めました。 ChatGPTの用途は様々あります。個人的には、要約機能が特に汎用的だと感じています。 以下のような活用ブログもありますね。
- OpenAIを使ってDevelopersIOの新着記事を3行で要約してSlackに投稿してみた | DevelopersIO
- ChatGPT を使ってちょうど読んでいた論文を3行まとめにしてもらった | DevelopersIO
本ブログでも ChatGPTによる要約スキルを確かめてみます。 ただ、「コンテンツそのものの要約」ではなく 「コンテンツの更新内容の要約」 ができないか、 に焦点を当てます。
今回 ChatGPTには、いわゆる diff
コマンドの結果を解析してもらいます。 そして、それを活用した「GitHubプルリクエスト(PR)の更新内容を要約する仕組み」まで作ってみました。
はじめにまとめ
以下 Pythonスクリプト、およびGitHub Actions ワークフローを作りました。
- summarize_diff.py | Gist : ChatGPTへ「diffコマンドの結果」を渡し、内容を要約するスクリプト
- comment-pr-diff-summary-by-chatgpt.yml | Gist : 上記スクリプトをPR作成時に自動で走らせるためのワークフロー
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 ワークフローです。 以下プルリクエスト作成時の動作例です。
作ってみる
[事前準備] OpenAIのAPIキーを発行・登録
OpenAI APIを利用するためのキーを発行します。
そのキーをGitHub のシークレットに登録します。 リポジトリの [Settings > Security > Secrets and variables > Actins] の画面へ移動します。 [Repository secrets] に OPENAI_API_KEY
として登録します。
これで 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を作ります。
返ってきたコメントがこちら。
ファイル名( test/fizzbuzz.py
)や print("FizzBuzz")
という情報から 『3の倍数と5の倍数』 と、よしなに予測していそうです。
ただ、再度実行するとこんな感じで、とても機械的な差分出力になりました。少し残念。
CloudFormationテンプレートの修正
CloudFormationテンプレートのパラメータを修正してみます。
結果がこちら。必要最小限ですが悪くないです。
Terraformコードの修正
パラメータ変更とリソース削除のPRを出してみます。
返ってきたコメントがこちら。
いいですね。削除された行の塊を「aws_flow_logリソース」と把握してくれているのは 地味にGoodポイントではないでしょうか。
Markdownファイル更新
「タイポ修正を3つ」と「加筆」を行っています。
返ってきたコメントがこちら。
うーん。言っていることは合っているけど、もうちょっと修正したポイントにフォーカスしてほしいですね。
追加で何度か試してみました。3回目の「誤り(=タイポ修正)」という表現はGoodですね。
考慮点や課題など
ワークフロー・スクリプトの考慮点
今回作った仕組みは差分のあるファイル毎に OpenAI API を叩きます。 大量のファイル更新の差分があるPRを作成する際には、 実行時間およびAPI利用料金には注意してください。
また、簡易的なPythonスクリプトなので「トークン数上限」考慮を入れていません。 以下のように事前にトークン数を見積るツールを適宜活用すると良さそうです。
品質向上の課題
本当は diffコマンドだけではなく、 以下のように「修正前」と「修正後」のコンテンツも参考情報としてインプットさせたかったです。
### 入力(コンテンツのファイル名)
${ファイル名}
### 入力(修正前のコンテンツ)
${修正前のコンテンツ}
### 入力(修正後のコンテンツ)
${修正後のコンテンツ}
### 入力(「修正前のコンテンツ」と「修正後のコンテンツ」のdiffコマンドの結果)
${diffコマンドの結果}
…が先程のトークン数上限の関係上、難しかったです。
「どの括り」の「どの部分」が変わったかが分かるのはメリットなので、 上限が無ければ追加しちゃいたいですね。
以下、「修正前」と「修正後」のコンテンツもインプットさせて、成功したときのキャプチャです。
- VPCの CIDRブロックが…
- VPCの DNSサポートが…
というように、どのリソースのどのパラメータが変わったかを、ChatGPTが判断してくれています。
おわりに
以上、OpenAI API をプルリクエストに組み込んでみました。
今回の出来としては、「あくまで参考」程度に捉えるのが良いのかなと思いますが、 ざっくりと変更点を日本語で書いてくれるのは良いですね。
プロンプトもまだまだ改善できそうなので、チューニングしていきたいです。
参考
- <初心者向き> OpenAI APIを使ってPythonでChatGPT遊びするための最初の三歩くらい | DevelopersIO
- OpenAI Platformことはじめ 〜Organizationメンバーに招待されたら | DevelopersIO
- Webhook events and payloads - GitHub Docs
補足
「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: コスト最適化