背景・目的
プルリクエスト(PR)レビューでは、下記のような課題があります。
- レビューに時間がかかり、開発のボトルネックになる
- レビュー内容が人によってバラつく
- 小さな修正でも人手が必要になる
以前は、下記の通りClaudeで試してみましたが、今回はGitHub ActionsとBedrockと組み合わせて
自動でPRをレビューする仕組みを試してみます。
実践
今回、下記のような構成で試しました。
github-action-bedrock-example % tree -a
├── .github
│ ├── scripts
│ │ └── bedrock_review.py
│ └── workflows
│ ├── bedrock-pr-review.yml
│ ├── bedrock-smoke.yml
│ └── test.yml
├── .gitignore
├── demo
│ └── bad_example.py
├── README.md
└── tests
├── __init__.py
└── test_bedrock_review.py
%
前提
下記の環境で試します
- Mac
- Cursor
準備
環境の作成
-
プロジェクトを作成します
% gh repo create github-action-bedrock-example --private --clone ✓ Created repository XXXX/github-action-bedrock-example on github.com https://github.com/XXXX/github-action-bedrock-example %
-
Cursorのワークスペースに、作成したプロジェクトを追加します
AWSの設定
OIDCの設定
-
AWSにサインインします
-
IAMに移動します
-
ナビゲーションペインのIDプロバイダーをクリックします
-
「プロバイダを追加」をクリックします
-
下記を入力し、「プロバイダを追加」をクリックします
- プロバイダのタイプ:OpenID Connect
- プロバイダのURL:https://token.actions.githubusercontent.com
- 対象者:sts.amazonaws.com
ロールと信頼ポリシーの構成
-
IAMのナビゲーションペインで、「ロール」をクリックします
-
「ロールを作成」をクリックします
-
下記を指定し、「次へ」をクリックします
- ①信頼されたエンティティタイプ:ウェブアイデンティティ
- ②アイデンティティプロバイダー:上記で追加した、IDプロバイダ
- ③Audience:sts.amazonaws.com(IDプロバイダを選択するとデフォルトで設定)
- ④GitHub 組織:Githubアカウント
- ⑤GitHubリポジトリ:リポジトリを指定
- ⑥GitHub branch:指定なし
-
許可ポリシーを選択せずに「次へ」をクリックします
-
ロール名を指定してクリックします
-
作成したロールに対して、インラインポリシーを作ります
-
下記のアクションを追加し、次へをクリックします
- bedrock:InvokeModel
- bedrock:InvokeModelWithResponseStream
-
「ポリシーの作成」をクリックします
GitHubの設定
Secretsの設定
-
GitHubにサインインし、対象リポジトリを選択します
-
①「Settings」>②「Secrets and variables > Actions」>③「Secrets」>④「New repository secrets」をクリックします
-
下記の2つのシークレットを追加します
Variableの設定
-
事前に今回利用するモデルのIDを調べます
hirotoshi@MacBook-Pro workflows % aws bedrock list-foundation-models --region us-west-2 \ --query "modelSummaries[?modelName=='Claude 3.7 Sonnet'].[modelId,modelName]" \ --output table -------------------------------------------------------------------- | ListFoundationModels | +--------------------------------------------+---------------------+ | anthropic.claude-3-7-sonnet-20250219-v1:0 | Claude 3.7 Sonnet | +--------------------------------------------+---------------------+ %
-
①「Settings」>②「Secrets and variables > Actions」>③「Variables」>④「New Repository Variable」をクリックします
-
下記を設定します
- AWS_REGION
- MDOEL_ID
MODEL_IDには、下記のように指定します。
To call the US Anthropic Claude 3.7 Sonnet inference profile, specify the following inference profile ID in one of the source Regions:
us.anthropic.claude-3-7-sonnet-20250219-v1:0
https://docs.aws.amazon.com/bedrock/latest/userguide/inference-profiles-support.html
モデルの有効化
疎通
疎通テスト用のコード(bedrock-smoke.yml)
GitHub Actions → AWS OIDC ロール Assume が正しくできるか確認します。
-
下記のコードを作成します
name: bedrock-smoke on: workflow_dispatch: permissions: id-token: write contents: read jobs: smoke: runs-on: ubuntu-latest env: AWS_REGION: ${{ vars.AWS_REGION }} MODEL_ID: ${{ vars.MODEL_ID }} # 例: anthropic.claude-3-7-sonnet-20250219-v1:0 steps: - uses: actions/checkout@v4 - name: Configure AWS (OIDC) uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: arn:aws:iam::${{ secrets.AWS_ID }}:role/${{ secrets.ROLE_NAME }} aws-region: ${{ env.AWS_REGION }} - name: Show caller identity run: aws sts get-caller-identity - name: Quick Bedrock call (hello) run: | python - <<'PY' import os, json, boto3 client = boto3.client("bedrock-runtime", region_name=os.environ["AWS_REGION"]) body = { "anthropic_version":"bedrock-2023-05-31", "max_tokens":128, "temperature":0, "messages":[{"role":"user","content":[{"type":"text","text":"丁寧な日本語で短く挨拶してください。"}]}] } resp = client.invoke_model(modelId=os.environ["MODEL_ID"], body=json.dumps(body)) out = json.loads(resp["body"].read()) print("\n=== Model Response ===\n" + out["content"][0]["text"]) PY
-
pushします
-
「Run workflow」> 「Run workflow」をクリックします
-
成功して、下記のように表示されました
PRレビューBot
PRが作成/更新されたときに、差分コードを自動でチェックし、AIがレビューコメントを自動投稿する仕組みを作ります。
下記を目的としています。
- 自動化: 人手を介さずにAIがコードレビュー
- 品質向上: 潜在的な問題やベストプラクティスを指摘
- 効率化: レビュー時間の短縮
GitHub Actionsの作成
bedrock-pr-review.yml
-
下記のコードを作成します
# .github/workflows/bedrock-pr-review.yml name: bedrock-pr-review on: pull_request: types: [opened, synchronize, reopened] permissions: id-token: write contents: read pull-requests: write jobs: review: runs-on: ubuntu-latest env: AWS_REGION: ${{ vars.AWS_REGION }} # 例: us-west-2 MODEL_ID: ${{ vars.MODEL_ID }} # 例: us.anthropic.claude-3-7-sonnet-20250219-v1:0(もしくは inference profile ARN) steps: - uses: actions/checkout@v4 with: fetch-depth: 0 # すべての履歴を取得 - name: Configure AWS (OIDC) uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: arn:aws:iam::${{ secrets.AWS_ID }}:role/${{ secrets.ROLE_NAME }} aws-region: ${{ env.AWS_REGION }} - name: Set up Python uses: actions/setup-python@v5 with: python-version: "3.11" - name: Install deps run: pip install boto3 - name: Collect diff id: diff shell: bash run: | set -euo pipefail BASE_SHA='${{ github.event.pull_request.base.sha }}' HEAD_SHA='${{ github.event.pull_request.head.sha }}' # base/head のコミットだけ浅く取得 git fetch --no-tags --prune --depth=2 origin "$BASE_SHA" "$HEAD_SHA" TMP="$(mktemp)" trap 'rm -f "$TMP"' EXIT # パイプのSIGPIPEで失敗しないよう一時的にpipefailを無効化 set +o pipefail if git merge-base --is-ancestor "$BASE_SHA" "$HEAD_SHA"; then git diff --unified=0 "$BASE_SHA...$HEAD_SHA" -- \ . ':(exclude)*.lock' ':(exclude)*.min.*' \ ':(exclude)package-lock.json' ':(exclude)pnpm-lock.yaml' \ | head -c 180000 > "$TMP" else git diff --unified=0 "$BASE_SHA" "$HEAD_SHA" -- \ . ':(exclude)*.lock' ':(exclude)*.min.*' \ ':(exclude)package-lock.json' ':(exclude)pnpm-lock.yaml' \ | head -c 180000 > "$TMP" fi set -o pipefail # Base64エンコードで安全に出力(特殊文字対策) DIFF_B64=$(cat "$TMP" | base64 -w 0) echo "DIFF_B64=$DIFF_B64" >> "$GITHUB_OUTPUT" # 念のため通常の形式でも出力(デバッグ用) { echo "DIFF<<EOF" cat "$TMP" echo "" # 確実に改行を追加 echo "EOF" } >> "$GITHUB_OUTPUT" - name: Run Bedrock review id: bedrock env: DIFF: ${{ steps.diff.outputs.DIFF }} DIFF_B64: ${{ steps.diff.outputs.DIFF_B64 }} run: | # Bedrockレビューを実行して結果を保存 REVIEW_OUTPUT=$(python .github/scripts/bedrock_review.py) # GitHub Actionsの出力として設定 { echo "REVIEW<<EOF" echo "$REVIEW_OUTPUT" echo "EOF" } >> "$GITHUB_OUTPUT" - name: Post review comment uses: actions/github-script@v7 env: REVIEW_OUTPUT: ${{ steps.bedrock.outputs.REVIEW }} with: script: | const reviewOutput = process.env.REVIEW_OUTPUT || ''; const body = `## 🤖 Bedrock Review\n\n${reviewOutput}`; console.log('Review output length:', reviewOutput.length); console.log('Review preview:', reviewOutput.substring(0, 200)); github.rest.issues.createComment({ owner: context.repo.owner, repo: context.repo.repo, issue_number: context.issue.number, body })
下記の処理を行っています。
- PRの変更内容を取得
- レビュー(スクリプトを呼び出す)
- 結果をポスト
-
上記のコードをGitHubにPushします
レビュースクリプト(Python)の作成
bedrock_review.py
-
下記のコードを作成します
#!/usr/bin/env python3 import os, sys, json, boto3 def main(): # Base64エンコードされた差分があるかチェック if "DIFF_B64" in os.environ: import base64 diff = base64.b64decode(os.environ["DIFF_B64"]).decode("utf-8") else: diff = sys.stdin.read().strip() if not diff: print("差分がありません(スキップ)。") return MAX_BYTES = 180_000 b = diff.encode("utf-8") if len(b) > MAX_BYTES: diff = b[:MAX_BYTES].decode("utf-8", errors="ignore") + \ "\n\n[...truncated for cost guardrail...]" prompt = f"""You are a senior software reviewer. Review the following unified diff and list: 1) Critical issues (security, correctness) 2) Suggested improvements (readability, performance) 3) Tests to add Output in GitHub Markdown bullets, concise Japanese. Diff: {diff} """ client = boto3.client("bedrock-runtime", region_name=os.environ["AWS_REGION"]) body = { "anthropic_version": "bedrock-2023-05-31", "max_tokens": 1200, "temperature": 0.2, "messages": [ {"role": "user", "content": [{"type": "text", "text": prompt}]} ], } resp = client.invoke_model(modelId=os.environ["MODEL_ID"], body=json.dumps(body)) out = json.loads(resp["body"].read()) text = out["content"][0]["text"] print(text) if __name__ == "__main__": main()
下記の処理を行っています。
- 入力を受け取り、DIFFを取ります
- コスト抑制のためDIFFを指定したサイズで切り落とします
- シニアソフトウェアエンジニア(レビューアー)として、下記をレビューします(してもらいます)
- セキュリティ、正確性
- 可読性、性能
- テストの提案
- アウトプットは、Markdownの箇条書き(日本語で)
-
上記のコードをGitHubにPushします
実行と確認
- ブランチを作成します
% git checkout -b test origin/main branch 'test' set up to track 'origin/main'. Switched to a new branch 'test' %
テストコードの作成(bad_example.py)
意図的に指摘されるコードを作ります。
-
問題のあるコードを作成します
import os, requests def divide(a, b): # TODO: zero division not handled return a / b def run_user_code(src): # DANGEROUS: arbitrary code execution return eval(src) def fetch(url): # BAD: TLS verification disabled r = requests.get(url, timeout=5, verify=False) return r.text[:100] # Hardcoded secret (for bot to flag) API_TOKEN = "sk_test_1234567890" if __name__ == "__main__": print(divide(10, 0))
-
GitHubにPushします
-
PRを作成します
- bad_example.pyだけではなく、GitHub Actionsのコードも指摘されています
レビュー指摘対応
下記のほか、テストコードも追加しています。
bedrock-pr-review.yml
-
上記で指摘された内容を取り込むためコードを修正します
# .github/workflows/bedrock-pr-review.yml name: bedrock-pr-review on: pull_request: types: [opened, synchronize, reopened] permissions: id-token: write contents: read pull-requests: write jobs: review: runs-on: ubuntu-latest env: AWS_REGION: ${{ vars.AWS_REGION }} # 例: us-west-2 MODEL_ID: ${{ vars.MODEL_ID }} # 例: us.anthropic.claude-3-7-sonnet-20250219-v1:0(もしくは inference profile ARN) steps: - uses: actions/checkout@v4 with: fetch-depth: 0 # すべての履歴を取得 - name: Configure AWS (OIDC) uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: arn:aws:iam::${{ secrets.AWS_ID }}:role/${{ secrets.ROLE_NAME }} aws-region: ${{ env.AWS_REGION }} - name: Set up Python uses: actions/setup-python@v5 with: python-version: "3.11" - name: Install deps run: pip install boto3 - name: Collect diff id: diff shell: bash run: | set -euo pipefail # PRイベントからベース/ヘッドのコミットSHAを取得 # フォールバック: PRイベントがない場合はエラーで終了 BASE_SHA='${{ github.event.pull_request.base.sha }}' HEAD_SHA='${{ github.event.pull_request.head.sha }}' if [[ -z "$BASE_SHA" || -z "$HEAD_SHA" ]]; then echo "⚠️ PR情報が取得できません。" echo "このワークフローはPRでのみ動作します。" exit 1 fi echo "=== Git差分収集開始 ===" echo "Base SHA: $BASE_SHA" echo "Head SHA: $HEAD_SHA" # 必要なコミットを取得 # マージベース判定に十分な深さで取得(浅いクローンの問題を軽減) echo "必要なコミットを取得中..." if ! git fetch --no-tags --prune --depth=10 origin "$BASE_SHA" "$HEAD_SHA"; then echo "⚠️ 浅いフェッチに失敗しました。" echo "完全フェッチを試行します..." git fetch --unshallow origin "$BASE_SHA" "$HEAD_SHA" || { echo "❌ Git フェッチに失敗しました" exit 1 } fi TMP="$(mktemp)" trap 'rm -f "$TMP"' EXIT # 差分生成: マージベースがあるかチェックして適切な方法を選択 echo "差分を生成中..." # 一時的にpipefailを無効化(headコマンドでのSIGPIPE対策) set +o pipefail # マージベースの存在確認と差分生成 if git merge-base --is-ancestor "$BASE_SHA" "$HEAD_SHA" 2>/dev/null; then echo "マージベースが見つかりました。三点差分を使用します。" DIFF_RANGE="$BASE_SHA...$HEAD_SHA" else echo "⚠️ マージベースが見つかりません。二点差分を使用します。" DIFF_RANGE="$BASE_SHA $HEAD_SHA" fi # 差分生成(不要なファイルを除外) # 除外パターン: ロックファイル、最小化ファイル、マップファイル git diff --unified=3 $DIFF_RANGE -- \ . ':(exclude)*{.lock,.min.*,.map}' \ ':(exclude){package-lock.json,pnpm-lock.yaml,yarn.lock}' \ | head -c 180000 > "$TMP" # pipefailを再有効化 set -o pipefail # 差分サイズを確認 DIFF_SIZE=$(wc -c < "$TMP") echo "生成された差分サイズ: ${DIFF_SIZE}バイト" if [[ $DIFF_SIZE -eq 0 ]]; then echo "⚠️ 差分が空です。除外ファイルのみの変更かもしれません。" fi # Base64エンコードで安全に出力(特殊文字対策) echo "Base64エンコード中..." if command -v base64 >/dev/null 2>&1; then # Linux/macOS互換性のため -w オプションをチェック # -w 0: 改行なしのBase64出力 if base64 --help 2>&1 | grep -q "\-w"; then DIFF_B64=$(cat "$TMP" | base64 -w 0) else DIFF_B64=$(cat "$TMP" | base64) fi echo "DIFF_B64=$DIFF_B64" >> "$GITHUB_OUTPUT" echo "Base64エンコード完了(${#DIFF_B64}文字)" else echo "⚠️ base64コマンドが見つかりません。" echo "通常の形式で出力します。" fi # 念のため通常の形式でも出力(デバッグ用) { echo "DIFF<<EOF" cat "$TMP" echo "" # 確実に改行を追加 echo "EOF" } >> "$GITHUB_OUTPUT" - name: Run Bedrock review id: bedrock env: DIFF: ${{ steps.diff.outputs.DIFF }} DIFF_B64: ${{ steps.diff.outputs.DIFF_B64 }} run: | # Bedrockレビューを実行して結果を保存 REVIEW_OUTPUT=$(python .github/scripts/bedrock_review.py) # GitHub Actionsの出力として設定 { echo "REVIEW<<EOF" echo "$REVIEW_OUTPUT" echo "EOF" } >> "$GITHUB_OUTPUT" - name: Post review comment uses: actions/github-script@v7 env: REVIEW_OUTPUT: ${{ steps.bedrock.outputs.REVIEW }} with: script: | const reviewOutput = process.env.REVIEW_OUTPUT || ''; const body = `## 🤖 Bedrock Review\n\n${reviewOutput}`; console.log('Review output length:', reviewOutput.length); console.log('Review preview:', reviewOutput.substring(0, 200)); github.rest.issues.createComment({ owner: context.repo.owner, repo: context.repo.repo, issue_number: context.issue.number, body })
-
Pushし、テストを実行します
考察
今回の取り組みを通して、以下の点を確認できました。
- AIによるレビューは即効性が高い
- セキュリティ上の懸念や、パフォーマンス・可読性の指摘が自動で検出できた
今後も、継続して使っていき改善していきたいと思います。
参考