1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Azure OpenAIとGitHub Actionsを組み合わせてプルリクエストのAIレビューを実施する手順

Last updated at Posted at 2025-01-29

1. Azure OpenAIを使用したプルリクエストの自動レビューの導入

今やChatGPTなどの生成AIを使用して開発をすることが当たり前になり、多くのエンジニアが生成AIを活用して効率的に業務を進めています。

一方で、セキュリティの観点から、業務に生成AIを使用することは制約を設けている企業も少なくありません。

生成AIを使用して業務の効率化を図りたいが、使用してはならないルールがある。そういった場合、Azure OpenAIを活用することで解決できる可能性があります。

Azure OpenAIとは、Microsoft社が提供する生成AIモデルを使用してカスタムコパイロットを構築するサービスのことであり、このサービスを使用して高水準のセキュリティでセキュアな環境を構築することができます。

当記事では、そのAzure OpenAIGitHub Actionsを組み合わせてプルリクエストが作成(更新)された時に自動でレビューする仕組みを構築するための手順を紹介します。

なお、こちらの記事ではAzure OpenAI上でのサービス構築については取り扱いません。

2. 自動レビューの概要

ここでのプルリクエストの自動レビューの手順については以下のようなイメージとなります。

自動レビューイメージ.png

このフローでは、以下の手順で自動レビューが行われます。

  1. プルリクエストの作成・更新: 開発者がコードの変更をプッシュし、プルリクエストを作成または更新します。

  2. GitHub Actionsのトリガー: プルリクエストのイベントを検知して、GitHub Actionsが自動的に起動し、変更されたファイルの差分(diff)を取得します。

  3. Azure OpenAIへのリクエスト: 差分情報をAzure OpenAIのAPIに送信し、コードレビューを依頼します。

  4. レビュー結果の取得: Azure OpenAIからのレビュー結果を受け取ります。

  5. プルリクエストへのコメント投稿: 取得したレビュー結果をプルリクエストのコメントとして投稿し、開発者に対して自動レビューの結果を通知します。

これにより、プルリクエストが作成・更新されるたびに、自動でプルリクエスト内に存在する差分に対するコードレビューが行われます。

3. GitHub Actionsの設定

  1. リポジトリのページで、「Actions」タブをクリックします。

    Actionsタブを開く

  2. 左側の「New workflow」ボタンをクリックします。

    New workflowをクリック

  3. set up a workflow yourself」をクリックします。

    set up a workflow yourselfをクリック

  4. エディタに以下のスクリプトを入力します。
    ここではプロジェクト内のSwiftファイルをレビューする仕様とします。

    スクリプトを入力

    name: ChatGPT Review and Comment
    
    on:
      pull_request:
        types: [opened, synchronize]
    
    jobs:
      review_and_comment:
        runs-on: ubuntu-latest
        timeout-minutes: 20
    
        steps:
        - name: Checkout Repository with Full History
          uses: actions/checkout@v2
          with:
            fetch-depth: 0  # フル履歴を取得
    
        - name: Find Branch Point Commit
          id: find-base
          run: |
            # 現在のブランチ(HEAD)とベースブランチの分岐点を特定
            BASE_COMMIT=$(git merge-base HEAD origin/${{ github.base_ref }})
            echo "BASE_COMMIT=$BASE_COMMIT" >> $GITHUB_ENV
    
        - name: Checkout Base Branch at Branch Point
          run: |
            mkdir base
            git --work-tree=base checkout $BASE_COMMIT -- .
    
        - name: Checkout Head Branch (After Changes)
          run: |
            mkdir head
            git --work-tree=head checkout HEAD -- .
    
        - name: Install dependencies
          run: |
            sudo apt-get update
            sudo apt-get install -y curl jq diffutils parallel
    
        - name: Generate Diffs for Each Swift File
          run: |
            mkdir -p diffs
            find head/ -type f -name "*.swift" | while read file; do
              base_file="base/${file#head/}"
              head_file="$file"
              if [ -f "$base_file" ]; then
                diff -u "$base_file" "$head_file" > "diffs/$(basename "$file").diff" || true
              else
                diff -u /dev/null "$head_file" > "diffs/$(basename "$file").diff" || true
              fi
    
              # 空のdiffファイルを削除
              if [ ! -s "diffs/$(basename "$file").diff" ]; then
                rm "diffs/$(basename "$file").diff"
              fi
            done
    
        - name: Review Each Diff in Parallel
          env:
            GITHUB_TOKEN: ${{ secrets.PERSONAL_ACCESSTOKEN }}
            OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
            OPENAI_ENDPOINT_URL: ${{ secrets.OPENAI_ENDPOINT_URL }}
          run: |
            PR_NUMBER=$(jq -r .pull_request.number $GITHUB_EVENT_PATH)
            PR_TITLE=$(jq -r .pull_request.title $GITHUB_EVENT_PATH)
    
            process_diff() {
              local PART=$1
              local FILE_NAME=$(basename "$PART" .diff)
    
              PR_BODY=$(cat "$PART" | sed 's/"/\\"/g' | jq -Rs .)
    
              JSON_STRING=$(jq -n \
                --arg title "$PR_TITLE" \
                --arg body "$PR_BODY" \
                '{messages: [
                    {role: "system", content: "あなたは優秀なテックリードです。最新のSwift,SwiftUIの基本的なコードの書き方は熟知しており、それらのコンポーネントやiOSアプリのアンチパターンなどに関しても世界でも有数の知見を持っています。
                    以下のルールに従ってコードレビューを行ってください。
                    レビューはプルリクエストが反映されたコードに対して行います。プルリクエストが反映される前のコードを見ることは一切禁じます。
                    以下で記載する修正前はプルリクエストが反映されたコードを指し、修正後はあなたがレビューを行い修正を反映したものです。
                    レビューの結果問題がなければそのファイルについては一切の記載を禁じます。
                    レビューは問題点に対してのみ行い、問題点のないファイルについては記載しないでください。
                    問題点がある場合には修正前と修正後のコードを提示し、どういった観点から修正すべきかを指摘しているかも含めて提示してください。
                    問題文の指摘がある場合、Markdown形式で折り畳みが行えるように指摘全体を<details></details>タグで囲み、<summary></summary>タグで指摘を行うファイル名を囲むようにしてください。
                    "},
                    {role: "user", content: ($title + "\n\n" + $body)}
                  ], max_tokens: 1500}')
    
              # JSONデータをファイルに保存
              echo "$JSON_STRING" > request.json
    
              RESPONSE=$(curl -X POST "$OPENAI_ENDPOINT_URL" \
              -H "Content-Type: application/json" \
              -H "api-key: $OPENAI_API_KEY" \
              --data @request.json)
    
              REVIEW=$(echo "$RESPONSE" | jq -r .choices[0].message.content | sed 's/\n/<br>/g')
    
              if [ "$REVIEW" != "null" ] && [ -n "$REVIEW" ]; then
                echo "Review for $FILE_NAME" > "reviews/review_$(basename "$PART")"
                echo "$REVIEW" >> "reviews/review_$(basename "$PART")"
              fi
            }
    
            export -f process_diff
            mkdir -p reviews
            parallel process_diff ::: diffs/*.diff
    
        - name: Combine Reviews
          run: |
            echo "### Combined Review Comments<br>" > combined_reviews.md
            if [ -z "$(ls -A reviews)" ]; then
              echo "No reviews to combine<br>" >> combined_reviews.md
            else
              for review in reviews/*; do
                cat "$review" >> combined_reviews.md
                echo -e "<br>---<br>" >> combined_reviews.md
              done
            fi
    
        - name: Post Combined Review Comment
          env:
            GITHUB_TOKEN: ${{ secrets.PERSONAL_ACCESSTOKEN }}
          run: |
            PR_NUMBER=$(jq -r .pull_request.number $GITHUB_EVENT_PATH)
            COMBINED_REVIEW=$(cat combined_reviews.md)
    
            POST_DATA=$(jq -n --arg body "$COMBINED_REVIEW" '{"body": $body}')
    
            curl -X POST "https://api.github.com/repos/${{ github.repository }}/issues/${PR_NUMBER}/comments" \
              -H "Authorization: Bearer $GITHUB_TOKEN" \
              -H "Content-Type: application/json" \
              --data "$POST_DATA"
    

以下では、YAMLファイルで行われている主な処理を順番にまとめています。
(※ 実際のGitHub Actionsのステップ数とは必ずしも一致しませんが、全体の流れを4つに大きく分けて解説しています)

1. リポジトリのクローンとベースコミットの特定

フル履歴を取得(checkout)

- name: Checkout Repository with Full History
  uses: actions/checkout@v2
  with:
    fetch-depth: 0

リポジトリの全履歴を取得して、後の差分比較が行えるようにします。

ブランチの分岐点(BASE_COMMIT)を特定

- name: Find Branch Point Commit
  id: find-base
  run: |
    BASE_COMMIT=$(git merge-base HEAD origin/${{ github.base_ref }})
    echo "BASE_COMMIT=$BASE_COMMIT" >> $GITHUB_ENV

git merge-base で、Pull Requestのheadブランチbaseブランチが分岐した共通のコミットを探し、そのコミットハッシュを環境変数として保存します。

2. ベースブランチとHEADブランチの状態をワークツリーに展開

ベースブランチの状態をフォルダに展開

- name: Checkout Base Branch at Branch Point
  run: |
    mkdir base
    git --work-tree=base checkout $BASE_COMMIT -- .

分岐点(baseコミット)時点のファイルをbaseフォルダに展開して、差分取得時の「比較元」として利用します。

HEADブランチ(変更後)の状態をフォルダに展開

- name: Checkout Head Branch (After Changes)
  run: |
    mkdir head
    git --work-tree=head checkout HEAD -- .

最新(HEAD)のファイルをheadフォルダに展開して、差分取得時の「比較先」として利用します。

3. Swiftファイルの差分作成とAzure OpenAIへの送信

差分の生成

- name: Generate Diffs for Each Swift File
  run: |
    mkdir -p diffs
    find head/ -type f -name "*.swift" | while read file; do
      base_file="base/${file#head/}"
      head_file="$file"
      if [ -f "$base_file" ]; then
        diff -u "$base_file" "$head_file" > "diffs/$(basename "$file").diff" || true
      else
        diff -u /dev/null "$head_file" > "diffs/$(basename "$file").diff" || true
      fi

      # 空のdiffファイルを削除
      if [ ! -s "diffs/$(basename "$file").diff" ]; then
        rm "diffs/$(basename "$file").diff"
      fi
    done

headディレクトリ内のすべての .swift ファイルを対象に、diffコマンドでベースとの差分を生成し、diffsフォルダに保存します。ファイルが新規追加された場合は /dev/null との差分を取得します。
仮にSwiftファイル以外をレビューしたい場合にはそのファイルの拡張子を指定することで対象に含めることが可能です。

OpenAI APIを呼び出してレビューを並列実行

- name: Review Each Diff in Parallel
    env:
        GITHUB_TOKEN: ${{ secrets.PERSONAL_ACCESSTOKEN }}
        OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
        OPENAI_ENDPOINT_URL: ${{ secrets.OPENAI_ENDPOINT_URL }}
    run: |
        PR_NUMBER=$(jq -r .pull_request.number $GITHUB_EVENT_PATH)
        PR_TITLE=$(jq -r .pull_request.title $GITHUB_EVENT_PATH)
        
        process_diff() {
            local PART=$1
            local FILE_NAME=$(basename "$PART" .diff)
            
            PR_BODY=$(cat "$PART" | sed 's/"/\\"/g' | jq -Rs .)

            JSON_STRING=$(jq -n \
                --arg title "$PR_TITLE" \
                --arg body "$PR_BODY" \
                '{messages: [
                    {role: "system", content: "あなたは優秀なテックリードです。最新のSwift,SwiftUIの基本的なコードの書き方は熟知しており、それらのコンポーネントやiOSアプリのアンチパターンなどに関しても世界でも有数の知見を持っています。
                   以下のルールに従ってコードレビューを行ってください。
                   レビューはプルリクエストが反映されたコードに対して行います。プルリクエストが反映される前のコードを見ることは一切禁じます。
                   以下で記載する修正前はプルリクエストが反映されたコードを指し、修正後はあなたがレビューを行い修正を反映したものです。
                   レビューの結果問題がなければそのファイルについては一切の記載を禁じます。
                   レビューは問題点に対してのみ行い、問題点のないファイルについては記載しないでください。
                   問題点がある場合には修正前と修正後のコードを提示し、どういった観点から修正すべきかを指摘しているかも含めて提示してください。
                   問題文の指摘がある場合、Markdown形式で折り畳みが行えるように指摘全体を<details></details>タグで囲み、<summary></summary>タグで指摘を行うファイル名を囲むようにしてください。
                   "},
                   {role: "user", content: ($title + "\n\n" + $body)}
                 ], max_tokens: 1500}')

             # JSONデータをファイルに保存
             echo "$JSON_STRING" > request.json

             RESPONSE=$(curl -X POST "$OPENAI_ENDPOINT_URL" \
             -H "Content-Type: application/json" \
             -H "api-key: $OPENAI_API_KEY" \
             --data @request.json)

             REVIEW=$(echo "$RESPONSE" | jq -r .choices[0].message.content | sed 's/\n/<br>/g')

             if [ "$REVIEW" != "null" ] && [ -n "$REVIEW" ]; then
               echo "Review for $FILE_NAME" > "reviews/review_$(basename "$PART")"
               echo "$REVIEW" >> "reviews/review_$(basename "$PART")"
             fi
           }

           export -f process_diff
           mkdir -p reviews
           parallel process_diff ::: diffs/*.diff

ここでは、parallelコマンドを使って .diffファイルを並列処理し、Azure OpenAIに差分内容を送信してレビューを行っています。
同時に、レビュー観点をcontent以降に記述することで独自のレビュー観点からの指摘が可能です。

そして、各差分ファイルごとにJSONを作成した後、curlでAPIを呼び出し、返ってきたレビュー結果をファイルに保存しています。

4. レビューコメントをまとめてPull Requestに投稿

レビュー結果を結合

- name: Combine Reviews
  run: |
    echo "### Combined Review Comments<br>" > combined_reviews.md
    if [ -z "$(ls -A reviews)" ]; then
      echo "No reviews to combine<br>" >> combined_reviews.md
    else
      for review in reviews/*; do
        cat "$review" >> combined_reviews.md
        echo -e "<br>---<br>" >> combined_reviews.md
      done
    fi

reviews ディレクトリに保存された各ファイルごとのレビュー結果を一つにまとめて、combined_reviews.mdを作成します。

Pull Requestのコメントとして投稿

- name: Post Combined Review Comment
  run: |
    PR_NUMBER=$(jq -r .pull_request.number $GITHUB_EVENT_PATH)
    COMBINED_REVIEW=$(cat combined_reviews.md)
    POST_DATA=$(jq -n --arg body "$COMBINED_REVIEW" '{"body": $body}')
    curl -X POST "https://api.github.com/repos/${{ github.repository }}/issues/${PR_NUMBER}/comments" \
      -H "Authorization: Bearer $GITHUB_TOKEN" \
      -H "Content-Type: application/json" \
      --data "$POST_DATA"

最後に、生成したレビューコメントをJSONとしてGitHub APIに送り、Pull Requestのコメント欄に投稿する、という流れになっています。

4. GitHub ActionsのSecret設定

yamlファイル内に記述されている

  • OPENAI_ENDPOINT_URL
  • OPENAI_API_KEY
  • PERSONAL_ACCESSTOKEN

に関しては、ファイル内にハードコードするのはセキュリティ的な観点からよろしくないため、上記の三点をGitHub Actions Secretとして登録します。

Secretの設定手順は以下の通りです。

1.リポジトリのトップページで、「Settings」タブをクリックします。

Group 10694.png

2.左側のメニューから「Secrets and variables」 > 「Actions」を選択します。

Group 10693.png

3.「New repository secret」ボタンをクリックし、以下のキーと値をそれぞれ追加します。

Group 10695.png

  • OPENAI_ENDPOINT_URL: Azure OpenAIのAPIエンドポイントURL
  • OPENAI_API_KEY: Azure OpenAIのAPIキー
  • PERSONAL_ACCESSTOKEN: GitHubの個人用アクセス トークン

※GitHubの個人用アクセス トークンは、プルリクエストにコメントを投稿するために必要です。トークン作成時に、repo 権限を付与してください。
作成方法が分からない場合、こちらを参照してください。

5. 動作テスト

自動レビューの設定が完了したら、実際にプルリクエストを作成して動作を確認します。

  1. 新しいブランチを作成: リポジトリで新しいブランチを作成し、Swiftファイルを追加または変更します。

  2. プルリクエストを作成: 変更をプッシュし、ベースブランチに対してプルリクエストを作成します。

  3. ワークフローの実行確認: プルリクエストを作成すると、GitHub Actionsが自動的にトリガーされます。リポジトリの「Actions」タブからワークフローの実行状況を確認できます。

  4. レビュー結果の確認: ワークフローが正常に完了すると、プルリクエストに自動レビューのコメントが投稿されます。
    成功した場合、以下のようなコメントが自動的にプルリクエストのコメント欄に作成されます。

Group 10696.png

6. おわりに

以上で、Azure OpenAIGitHub Actionsを使用したプルリクエストの自動レビューの設定手順について解説しました。

この仕組みを導入することで、コードレビューの効率化や品質向上が期待できます。また、Azure OpenAIを使用することで、企業内のセキュリティポリシーに準拠した形で生成AIを活用できます。

ぜひ、自社の開発フローに取り入れてみてください!

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?