actions/ai-inferenceを使ったGitHub ActionsによるAIコードレビュー自動化



actions/ai-inference: An action for calling AI models with GitHub Models でGitHub Actionsから簡単にAIモデルを呼び出せるようになったので、何か利用できないかと考え、Pull Requestのレビューを実施するGitHub Actionsを作成しました。
Copilot コード レビュー は使えるのになぜ、GitHub Actions から利用するのか?
過渡期だと思いますが現状(2025年6月)だと以下のような制約があるためです。
-
pull request上で、Copilotへの指示はできるが少し限定的 GitHub Copilot コードレビュー機能でプルリクエストを日本語でレビューしてもらいたいのようにpull requestのテンプレートにコメントで指示はできるが、コーディング規約を読み込んで指示するという利用はできない。
-
.github/copilot-instructions.md
を読み込んでくれる機能がまだ、Public Preview 最近、.github/copilot-instructions.md
を読み込んでくれるようになりましたが、まだPublic Previewの段階です。
これは近いうちにGAされるのでそのタイミングでこのGitHub Actionsは不要になるかなとも考えています。 -
中央集権的にGitHub Copilotを利用したい 開発チームのメンバー全員がGitHub Copilotを利用できないという状況で、コードレビューのみAIにサポートをしてほしいという状況で利用できるかなと考えています。
作成したGitHub Actions
以下作成したGitHub Actionになります。
manual-code-review-ai.yml
name: 'Manual AI Code Review'
on:
workflow_dispatch:
inputs:
pr_id:
description: 'Pull Request ID (URL number part)'
required: true
type: string
coding_standard_file:
description: 'Coding Standard Markdown File (relative path from repository root)'
required: false
type: string
default: '.github/prompts/coding-standards-all.prompt.md'
ai_model:
description: 'AI Model to use for code review'
required: false
type: string
default: 'openai/gpt-4o'
# レビュー対象ファイルパターンの定義
env:
TARGET_FILE_PATTERNS: "\\.(ts|tsx|js|jsx|css|sh)$"
TARGET_FILE_DESCRIPTION: 'TypeScript, JavaScript, CSS, Shell files'
jobs:
# PR情報取得とブランチ情報の特定
fetch-pr-info:
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: read
outputs:
source_branch: ${{ steps.pr-info.outputs.source_branch }}
target_branch: ${{ steps.pr-info.outputs.target_branch }}
pr_exists: ${{ steps.pr-info.outputs.pr_exists }}
pr_title: ${{ steps.pr-info.outputs.pr_title }}
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Get PR Information
id: pr-info
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_ID: ${{ inputs.pr_id }}
run: |
# PR情報を取得
echo "Fetching PR information for PR #$PR_ID"
# PRが存在するかチェック
if ! PR_DATA=$(gh pr view $PR_ID --json headRefName,baseRefName,title,state 2>/dev/null); then
echo "pr_exists=false" >> $GITHUB_OUTPUT
echo "Error: PR #$PR_ID not found"
exit 1
fi
echo "pr_exists=true" >> $GITHUB_OUTPUT
# ブランチ情報を抽出
SOURCE_BRANCH=$(echo "$PR_DATA" | jq -r '.headRefName')
TARGET_BRANCH=$(echo "$PR_DATA" | jq -r '.baseRefName')
PR_TITLE=$(echo "$PR_DATA" | jq -r '.title')
PR_STATE=$(echo "$PR_DATA" | jq -r '.state')
echo "source_branch=$SOURCE_BRANCH" >> $GITHUB_OUTPUT
echo "target_branch=$TARGET_BRANCH" >> $GITHUB_OUTPUT
echo "pr_title=$PR_TITLE" >> $GITHUB_OUTPUT
echo "PR Information:"
echo " Title: $PR_TITLE"
echo " State: $PR_STATE"
echo " Source Branch: $SOURCE_BRANCH"
echo " Target Branch: $TARGET_BRANCH"
# ブランチチェックアウトとdiff生成(簡素化)
generate-diff:
needs: fetch-pr-info
if: needs.fetch-pr-info.outputs.pr_exists == 'true'
runs-on: ubuntu-latest
permissions:
contents: read
outputs:
has-changes: ${{ steps.diff.outputs.has-changes }}
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set UTF-8 encoding
run: |
git config --local core.quotepath false
git config --global core.quotepath false
export LC_ALL=C.UTF-8
export LANG=C.UTF-8
- name: Generate unified diff
id: diff
env:
SOURCE_BRANCH: ${{ needs.fetch-pr-info.outputs.source_branch }}
TARGET_BRANCH: ${{ needs.fetch-pr-info.outputs.target_branch }}
TARGET_PATTERNS: ${{ env.TARGET_FILE_PATTERNS }}
TARGET_DESCRIPTION: ${{ env.TARGET_FILE_DESCRIPTION }}
run: |
echo "Generating unified diff between $TARGET_BRANCH and $SOURCE_BRANCH"
echo "Target file patterns: $TARGET_PATTERNS"
echo "Target file description: $TARGET_DESCRIPTION"
# ブランチの最新情報を取得
git fetch origin $TARGET_BRANCH
git fetch origin $SOURCE_BRANCH
# 変更されたファイルを取得(対象ファイルのみ)
CHANGED_FILES=$(git diff --name-only --diff-filter=AMR origin/$TARGET_BRANCH..origin/$SOURCE_BRANCH)
echo "Changed files: $CHANGED_FILES"
# ファイルパターンでフィルタリング(環境変数から取得)
RELEVANT_FILES=""
for file in $CHANGED_FILES; do
if [[ "$file" =~ $TARGET_PATTERNS ]]; then
if [ -n "$RELEVANT_FILES" ]; then
RELEVANT_FILES="$RELEVANT_FILES $file"
else
RELEVANT_FILES="$file"
fi
fi
done
if [ -z "$RELEVANT_FILES" ]; then
echo "has-changes=false" >> $GITHUB_OUTPUT
echo "No relevant files changed (pattern: $TARGET_PATTERNS)"
echo "# No relevant changes detected for $TARGET_DESCRIPTION" > unified-diff.txt
exit 0
fi
echo "has-changes=true" >> $GITHUB_OUTPUT
echo "Relevant files: $RELEVANT_FILES"
# 統一されたdiffファイルを生成
echo "Generating unified diff for: $RELEVANT_FILES"
# --unified=3 文脈も多少含める
git diff --unified=3 --minimal origin/$TARGET_BRANCH..origin/$SOURCE_BRANCH -- $RELEVANT_FILES > unified-diff.txt
echo "Unified diff generated (first 50 lines):"
head -50 unified-diff.txt
- name: Upload diff file
if: steps.diff.outputs.has-changes == 'true'
uses: actions/upload-artifact@v4
with:
name: diff-file-${{ github.run_id }}
path: unified-diff.txt
retention-days: 1
# AI レビュー実行(簡素化)
ai-review:
needs: [fetch-pr-info, generate-diff]
if: needs.generate-diff.outputs.has-changes == 'true'
runs-on: ubuntu-latest
permissions:
models: read
contents: read
pull-requests: write
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Download diff file
uses: actions/download-artifact@v4
with:
name: diff-file-${{ github.run_id }}
- name: Validate coding standard file
id: validate-file
env:
CODING_STANDARD_FILE: ${{ inputs.coding_standard_file }}
run: |
if [ -f "$CODING_STANDARD_FILE" ]; then
echo "file_exists=true" >> $GITHUB_OUTPUT
echo "file_path=$CODING_STANDARD_FILE" >> $GITHUB_OUTPUT
echo "Using coding standard file: $CODING_STANDARD_FILE"
else
echo "file_exists=false" >> $GITHUB_OUTPUT
echo "file_path=.github/prompts/coding-standards-all.prompt.md" >> $GITHUB_OUTPUT
echo "Warning: Specified file '$CODING_STANDARD_FILE' not found, using default"
fi
- name: AI Code Review
id: review
uses: actions/ai-inference@v1.1.0
continue-on-error: true
env:
AI_MODEL: ${{ inputs.ai_model }}
with:
model: ${{ env.AI_MODEL }}
system-prompt-file: ${{ steps.validate-file.outputs.file_path }}
prompt-file: unified-diff.txt
max-tokens: 4000
- name: Save review to file
if: steps.review.outcome == 'success' && steps.review.outputs.response != ''
env:
RESPONSE: ${{ steps.review.outputs.response }}
PR_ID: ${{ inputs.pr_id }}
CODING_STANDARD_FILE: ${{ inputs.coding_standard_file }}
AI_MODEL: ${{ inputs.ai_model }}
run: |
cat > review-result.txt << 'EOF'
## 🤖 AI Code Review
**PR:** #${{ env.PR_ID }}
**AI Model:** ${{ env.AI_MODEL }}
**Coding Standard:** ${{ env.CODING_STANDARD_FILE }}
${{ env.RESPONSE }}
---
*このレビューは手動実行されたAIによって生成されました*
EOF
- name: Upload review result
if: steps.review.outcome == 'success' && steps.review.outputs.response != ''
uses: actions/upload-artifact@v4
with:
name: review-result-${{ github.run_id }}
path: review-result.txt
retention-days: 7
- name: Handle Review Error
if: steps.review.outcome == 'failure'
env:
PR_ID: ${{ inputs.pr_id }}
CODING_STANDARD_FILE: ${{ inputs.coding_standard_file }}
AI_MODEL: ${{ inputs.ai_model }}
run: |
echo "⚠️ AIレビューでエラーが発生しました"
cat > review-error.txt << 'EOF'
## ⚠️ AI Code Review - エラー
**PR:** #${{ env.PR_ID }}
**AI Model:** ${{ env.AI_MODEL }}
**Coding Standard:** ${{ env.CODING_STANDARD_FILE }}
AIレビューの実行中にエラーが発生しました。手動レビューをお願いします。
---
*このメッセージは手動実行されたAIレビュー中のエラーです*
EOF
- name: Cleanup
if: always()
run: |
rm -f unified-diff.txt review-*.txt
# レビュー結果をPRにコメント(簡素化)
post-review:
needs: [fetch-pr-info, generate-diff, ai-review]
if: always() && needs.generate-diff.outputs.has-changes == 'true'
runs-on: ubuntu-latest
permissions:
pull-requests: write
actions: read
contents: read
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Download review result
uses: actions/download-artifact@v4
with:
name: review-result-${{ github.run_id }}
continue-on-error: true
- name: Post review result to PR
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_ID: ${{ inputs.pr_id }}
CODING_STANDARD_FILE: ${{ inputs.coding_standard_file }}
AI_MODEL: ${{ inputs.ai_model }}
run: |
echo "Posting review result to PR #$PR_ID"
if [ -f "review-result.txt" ]; then
# レビュー結果ファイルが存在する場合
cat > final-review.txt << 'EOF'
# 🤖 Manual AI Code Review Results
**Requested by:** @${{ github.actor }}
**AI Model:** ${{ env.AI_MODEL }}
**Coding Standard File:** ${{ env.CODING_STANDARD_FILE }}
**Execution ID:** ${{ github.run_id }}
EOF
cat review-result.txt >> final-review.txt
gh pr comment $PR_ID --body-file final-review.txt
echo "Review posted successfully to PR #$PR_ID"
else
# レビューファイルが見つからない場合(エラーまたはレビュー失敗)
gh pr comment $PR_ID \
--body "## 🤖 Manual AI Code Review
**Requested by:** @${{ github.actor }}
**AI Model:** ${{ env.AI_MODEL }}
**Coding Standard File:** ${{ env.CODING_STANDARD_FILE }}
⚠️ レビュー結果の生成中にエラーが発生しました。ワークフロー実行ログを確認してください。
**Execution ID:** ${{ github.run_id }}"
echo "Error message posted to PR #$PR_ID"
fi
# アーティファクトのクリーンアップ(簡素化)
cleanup:
needs: [fetch-pr-info, generate-diff, ai-review, post-review]
if: always() && needs.fetch-pr-info.outputs.pr_exists == 'true'
runs-on: ubuntu-latest
permissions:
actions: write
steps:
- name: Delete artifacts
uses: actions/github-script@v7
with:
script: |
const runId = context.runId;
const artifacts = await github.rest.actions.listWorkflowRunArtifacts({
owner: context.repo.owner,
repo: context.repo.repo,
run_id: runId,
});
for (const artifact of artifacts.data.artifacts) {
if (artifact.name.includes(`-${runId}`)) {
await github.rest.actions.deleteArtifact({
owner: context.repo.owner,
repo: context.repo.repo,
artifact_id: artifact.id,
});
console.log(`Deleted artifact: ${artifact.name}`);
}
}
# 変更がない場合の通知
no-changes-notification:
needs: [fetch-pr-info, generate-diff]
if: needs.fetch-pr-info.outputs.pr_exists == 'true' && needs.generate-diff.outputs.has-changes == 'false'
runs-on: ubuntu-latest
permissions:
pull-requests: write
contents: read
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Post no changes notification
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_ID: ${{ inputs.pr_id }}
CODING_STANDARD_FILE: ${{ inputs.coding_standard_file }}
AI_MODEL: ${{ inputs.ai_model }}
TARGET_DESCRIPTION: ${{ env.TARGET_FILE_DESCRIPTION }}
run: |
gh pr comment $PR_ID \
--body "## 🤖 Manual AI Code Review
**Requested by:** @${{ github.actor }}
**AI Model:** ${{ env.AI_MODEL }}
**Coding Standard File:** ${{ env.CODING_STANDARD_FILE }}
ℹ️ 対象ファイル(${{ env.TARGET_DESCRIPTION }})に変更が検出されませんでした。
**Execution ID:** ${{ github.run_id }}"
内容の説明
以下、GitHub Copilotに作成してもらった処理概要になります。
pull requestのID、コーディング規約(プロンプト)、モデル名を引数としてworkflowを手動実行すると、レビュー結果がpull requestのコメントに自動で追記されます。
## 📋 **ワークフロー概要**
**目的**: 指定PRに対するAI手動コードレビューの実行
**実行**: `workflow_dispatch`による手動トリガー
**対象**: TypeScript、JavaScript、CSS、Shellファイル
## 🔄 **主要処理フロー**
### 1. **PR情報取得** (`fetch-pr-info`)
- 指定PR IDの存在確認・基本情報取得
- ソース/ターゲットブランチの特定
### 2. **差分生成** (`generate-diff`)
- 対象ファイルパターンでフィルタリング
- 統一diffファイル生成・アーティファクト保存
### 3. **AIレビュー実行** (`ai-review`)
- `actions/ai-inference@v1.1.0`でレビュー実行
- 指定コーディング規約ファイルをシステムプロンプトとして使用
- 最大4000トークンでレビュー生成
### 4. **結果投稿** (`post-review`)
- AIレビュー結果をPRにコメント投稿
- エラー時は適切なフィードバック提供
### 5. **後処理**
- **クリーンアップ**: アーティファクト自動削除
- **変更なし通知**: 対象ファイル変更なしの場合
## ⚙️ **パラメータ**
| パラメータ | 必須 | デフォルト値 | 説明 |
|------------|------|--------------|------|
| `pr_id` | ✅ | - | レビュー対象PR番号 |
| `coding_standard_file` | - | coding-standards-all.prompt.md | コーディング規約ファイル |
| `ai_model` | - | `openai/gpt-4o` | 使用AIモデル |
コーディング規約 Markdownファイル
以下のようなファイルを作成して、コードレビューをお願いしています。
これも、Copilotに自動生成してもらったものになります。
アウトプット
GitHub Actionsの実行後は以下のようなアウトプットが得られました。 Pull Requestのコメントとして記載されます。
検討事項
以下の点は検討事項であり、実際に利用しながら調整していく必要があると考えています。
-
ソース差分の検知をどのように行うか?
プレミアムリクエストを消費してしまうため、actions/ai-inferenceの実行回数はなるべく少なくしたいと考え、gitのdiff全体をInputとして利用しています。ただし、ソースの変更量が多い場合はInputの上限に抵触したり、レビューの精度が落ちる可能性があります。pull requestの状況によっては、拡張子やプロダクションコード・テストコードなどのカテゴリごとにdiffを分割する必要があるかもしれません。その場合はtj-actions/changed-files
のようなGitHub Actionsを使って、ファイルの差分や更新状態ごとにレビューを行う方法も有効だと考えています。 -
pull request先の変更も検知する
pull request先の変更も検知してレビュー対象にする挙動になっているので、git diffの3点リーダー(...)構文を使う形にするのが良いのかもしれません。
git diff origin/$TARGET_BRANCH...origin/$SOURCE_BRANCH -- $RELEVANT_FILES
-
コーディングルールのサイズ 現在(2025年6月)1つのファイルに全部入りのコーディングルールをプロンプトに設定しています。各観点ごとに分けた、短めのルールを複数ファイル、一括実行したときの挙動も確認してみたいです。
-
Pull Requestのコンテキストを含める
- Pull Requestの本文を含めた方が、レビュー精度が上がりそうな気もしています。
-
実行に失敗することがある
実際に10ファイル程度の比較的大きなレビューを依頼した際に、変更行数の影響なのかエラーが発生することがあり、エラーになる場合とならない場合があるため、pull requestトリガーでの自動実行よりも手動実行の方が安定するのではないかと感じました。
今後、実施してみたいこと
ai-inference
はよい感じに、AIモデルの実行手続きがまとめられていて、curl
でAPIを直叩きするより大分簡易に実行できると感じました。
現時点でのアイデアとしては、pull requestの概要や、リリースノート作成の自動化あたりを試してみようと考えています。