PR-AgentとBedrockでGitHubのPRをAIにレビューしてもらう
はじめに
PR-AgentはPRレビューのAI補助ツールです。
本記事で登場するコードは以下のリポジトリに記載しています。全体像が掴みにくくなった際にご活用ください。
Qodo Merge(旧PR-Agent)とは
2024年11月現在Qodo Mergeという名前に変わっているため、旧PR-Agentとなります。リネーム直後ということもあり、本記事では旧名で記載します。
PRレビュー系の生成AIツールは、他にCodeRabbitが挙げられます。異なる点はざっくり以下の通りです。
- PR-AgentはOSS版が提供されており、権限や費用が柔軟にコントロール出来る(CodeRabbitのOSS版は、2024年11月現在アーカイブされています。)
- 言語モデルの選択が可能(サポートされている言語モデル一覧)
- 内部APIを呼び出すため、利用料金が従量制になる
PR-AgentはCodiumAIが開発元で、有料版もあり、以下の機能が利用可能です。詳細はこちらが参考になります。値段はCodeRabbitと同じ$19/人です。
試す経緯
CodeRabbitの調査をする過程でGitHubの権限を調査したところ、GitHubのOAuth AppとGitHub Apps及びOWNER権限が必要でした。大きな組織のGitHubアカウントを運用している場合すぐの導入が難しいと感じ、OSSプロダクトも触ってみようと思ったためです。権限の詳細はこちらに記載しています。
すでにPR-Agent周りの記事はいくつかありますが、ゼロから導入する手順はなさそうだったので書くことにしました。
より実践的な弊社の記事では以下が参考になります。
前提
本記事でBedrockの有効化から、PR-Agentのレビューが出来るようになることを目指します。
- Bedrockを利用
- GitHubと連携
PR-AgentとGitHubを連携する方法は、こちらの通りざっくり2つあります。
- GitHub Actionsから実行
- GitHub Appsから実行
1.のGitHub Actionsを使う方法にします。
2.はGitHub AppsのWebHook送信先にAWS Lambdaの関数URLを使った方法が紹介されています。導入の手間が多いので本記事では見送ります。
またChrome拡張も用意されていますが、OAuth Appの連携が必要なため、本記事での検証は見送ります。
導入方法
Bedrockで利用したいモデルを有効化
AWSのマネジメントコンソールにアクセスし、新しい言語モデルがリリースされた際に試せるように北バージニアリージョンで有効化します。言語モデルのアクセス画面に遷移します。
モデルを有効化します。
利用したい言語モデルのチェックボックスを有効化します。
ユースケースを記載します。
問題なければ送信します。
ステータスが進行中になります。
数分待つと、アクセスが付与されたことが画面上から分かります。
有効化したモデルへアクセス出来ることを確認
AWS CLIを使って、モデルIDを取得します。
aws bedrock list-foundation-models --output table \
--query 'modelSummaries[*].[modelId,modelName,providerName]' \
--region us-east-1 | grep "Claude"
| anthropic.claude-instant-v1:2:100k | Claude Instant | Anthropic |
| anthropic.claude-instant-v1 | Claude Instant | Anthropic |
| anthropic.claude-v2:0:18k | Claude | Anthropic |
| anthropic.claude-v2:0:100k | Claude | Anthropic |
| anthropic.claude-v2:1:18k | Claude | Anthropic |
| anthropic.claude-v2:1:200k | Claude | Anthropic |
| anthropic.claude-v2:1 | Claude | Anthropic |
| anthropic.claude-v2 | Claude | Anthropic |
| anthropic.claude-3-sonnet-20240229-v1:0:28k | Claude 3 Sonnet | Anthropic |
| anthropic.claude-3-sonnet-20240229-v1:0:200k | Claude 3 Sonnet | Anthropic |
| anthropic.claude-3-sonnet-20240229-v1:0 | Claude 3 Sonnet | Anthropic |
| anthropic.claude-3-haiku-20240307-v1:0:48k | Claude 3 Haiku | Anthropic |
| anthropic.claude-3-haiku-20240307-v1:0:200k | Claude 3 Haiku | Anthropic |
| anthropic.claude-3-haiku-20240307-v1:0 | Claude 3 Haiku | Anthropic |
| anthropic.claude-3-opus-20240229-v1:0:12k | Claude 3 Opus | Anthropic |
| anthropic.claude-3-opus-20240229-v1:0:28k | Claude 3 Opus | Anthropic |
| anthropic.claude-3-opus-20240229-v1:0:200k | Claude 3 Opus | Anthropic |
| anthropic.claude-3-opus-20240229-v1:0 | Claude 3 Opus | Anthropic |
| anthropic.claude-3-5-sonnet-20240620-v1:0 | Claude 3.5 Sonnet | Anthropic |
| anthropic.claude-3-5-sonnet-20241022-v2:0 | Claude 3.5 Sonnet v2 | Anthropic |
| anthropic.claude-3-5-haiku-20241022-v1:0 | Claude 3.5 Haiku | Anthropic |
今回はclaudeだけですが、言語モデルの複数のプロバイダーのAPIラッパーであるlitellmを利用します。
pip3 install litellm
有効化した言語モデルと有効化していないモデルを指定して、動作確認を実施します。
- anthropic.claude-3-sonnet-20240229-v1:0 (有効化済み)
- anthropic.claude-3-haiku-20240307-v1:0 (有効化済み)
- anthropic.claude-3-opus-20240229-v1:0 (有効化していない)
Bedrockで有効化したモデルの動作確認をするスクリプト
from litellm import completion
from typing import Dict, List
import time
from datetime import datetime
def test_claude_models():
models = [
{"id": "anthropic.claude-3-sonnet-20240229-v1:0", "name": "Claude 3 Sonnet"}, # 成功する前提
{"id": "anthropic.claude-3-haiku-20240307-v1:0", "name": "Claude 3 Haiku"}, # 成功する前提
{"id": "anthropic.claude-3-opus-20240229-v1:0", "name": "Claude 3 Opus"}, # 失敗する前提
]
results: List[Dict] = []
test_message = "Hello, how are you?"
for model in models:
print(f"\nTesting {model['name']} ({model['id']})...")
try:
start_time = time.time()
response = completion(
model=model["id"], messages=[{"content": test_message, "role": "user"}]
)
end_time = time.time()
response_time = end_time - start_time
result = {
"model_name": model["name"],
"model_id": model["id"],
"status": "success",
"response": response.choices[0].message.content
if response.choices
else None,
"response_time": f"{response_time:.2f}s",
"timestamp": datetime.now().isoformat(),
}
except Exception as e:
result = {
"model_name": model["name"],
"model_id": model["id"],
"status": "error",
"error": str(e),
"timestamp": datetime.now().isoformat(),
}
results.append(result)
print(f"Status: {result['status']}")
if result["status"] == "success":
print(f"Response time: {result['response_time']}")
print(f"Response: {result['response'][:100]}...")
else:
print(f"Error: {result['error']}")
return results
if __name__ == "__main__":
results = test_claude_models()
# 結果の集計を表示
print("\n=== Summary ===")
success_count = sum(1 for r in results if r["status"] == "success")
print(f"Total models tested: {len(results)}")
print(f"Successful requests: {success_count}")
print(f"Failed requests: {len(results) - success_count}")
# 成功したモデルの平均応答時間を計算
response_times = [
float(r["response_time"].rstrip("s"))
for r in results
if r["status"] == "success"
]
if response_times:
avg_response_time = sum(response_times) / len(response_times)
print(f"Average response time: {avg_response_time:.2f}s")
期待通りの結果が得られました。
$ python3 check_model.py
Testing Claude 3 Sonnet (anthropic.claude-3-sonnet-20240229-v1:0)...
Status: success
Response time: 6.42s
Response: Hello! As an AI language model, I don't have feelings or emotions, but I'm operating properly and re...
Testing Claude 3 Haiku (anthropic.claude-3-haiku-20240307-v1:0)...
Status: success
Response time: 1.24s
Response: Hello! As an AI assistant, I don't have feelings or a physical form, but I'm functioning properly an...
Testing Claude 3 Opus (anthropic.claude-3-opus-20240229-v1:0)...
Give Feedback / Get Help: https://github.com/BerriAI/litellm/issues/new
LiteLLM.Info: If you need to debug this error, use `litellm.set_verbose=True'.
Status: error
Error: litellm.BadRequestError: BedrockException - {"message":"Invocation of model ID anthropic.claude-3-opus-20240229-v1:0 with on-demand throughput isn’t supported. Retry your request with the ID or ARN of an inference profile that contains this model."}
=== Summary ===
Total models tested: 3
Successful requests: 2
Failed requests: 1
Average response time: 3.83s
GitHub ActionsとBedrock連携用のIAMロールの作成
bedrockの権限を付与して、PRレビュー出来る状態にします。
import { Stack, aws_iam } from 'aws-cdk-lib';
import type { Construct } from 'constructs';
const gitHubOwner = 'shuntaka9576';
const gitHubRepo = 'pr-agent-sample';
export class GitHubActionOIDCStack extends Stack {
constructor(scope: Construct, id: string) {
super(scope, id);
const accountId = Stack.of(this).account;
new aws_iam.OpenIdConnectProvider(this, 'GitHubIdProvider', {
url: 'https://token.actions.githubusercontent.com',
clientIds: ['sts.amazonaws.com'],
});
new aws_iam.Role(this, 'GitHubActionsOidcRole', {
roleName: 'github-action-assume-role', // <-- 後ほどGitHub Actionsの定義で指定するロール名
assumedBy: new aws_iam.FederatedPrincipal(
`arn:aws:iam::${accountId}:oidc-provider/token.actions.githubusercontent.com`,
{
StringEquals: {
'token.actions.githubusercontent.com:aud': 'sts.amazonaws.com',
},
StringLike: {
'token.actions.githubusercontent.com:sub': `repo:${gitHubOwner}/${gitHubRepo}:*`,
},
},
'sts:AssumeRoleWithWebIdentity'
),
managedPolicies: [
aws_iam.ManagedPolicy.fromAwsManagedPolicyName(
'AWSCloudFormationFullAccess'
),
aws_iam.ManagedPolicy.fromAwsManagedPolicyName('IAMFullAccess'),
aws_iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonS3FullAccess'),
],
inlinePolicies: {
ssm: new aws_iam.PolicyDocument({
statements: [
new aws_iam.PolicyStatement({
effect: aws_iam.Effect.ALLOW,
actions: [
'ssm:GetParameters',
'ssm:GetParameter',
'ssm:PutParameter',
],
resources: ['*'],
}),
],
}),
bedrock: new aws_iam.PolicyDocument({
statements: [
new aws_iam.PolicyStatement({
effect: aws_iam.Effect.ALLOW,
actions: [
'bedrock:InvokeModel',
'bedrock:InvokeModelWithResponseStream',
],
resources: ['arn:aws:bedrock:*::foundation-model/anthropic.*'],
}),
],
}),
},
});
}
}
GitHub Actionsを作成
公式を参考にGitHub Actionsの定義を作成します。
on:
pull_request:
types: [opened, reopened, ready_for_review]
issue_comment:
jobs:
pr_agent_job:
if: ${{ github.event.sender.type != 'Bot' }}
runs-on: ubuntu-latest
permissions:
issues: write
pull-requests: write
contents: write
id-token: write
name: Run pr agent on every pull request, respond to user comments
steps:
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::${{ vars.AWS_ACCOUNT_ID }}:role/github-action-assume-role # <- CDKで作成したロール名を指定
aws-region: us-east-1
- name: PR Agent action step
id: pragent
uses: Codium-ai/pr-agent@main
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
AWS_ACCOUNT_IDを登録します。
gh variable set AWS_ACCOUNT_ID -b "value"
PR-Agentのコンフィグ設定を作成
サポートされているモデル定義は、こちらを参考にします。モデルにClaude 3 Sonnet、フォールバックモデルにClaude 3 Haikuを指定します。このファイルをリポジトリルートに配備します。
[config]
model = "bedrock/anthropic.claude-3-sonnet-20240229-v1:0"
model_turbo = "bedrock/anthropic.claude-3-sonnet-20240229-v1:0"
fallback_models = ["bedrock/anthropic.claude-3-haiku-20240307-v1:0"]
[pr_reviewer]
extra_instructions = "answer in Japanese"
[pr_description]
extra_instructions = "answer in Japanese"
[pr_questions]
extra_instructions = "answer in Japanese"
[pr_code_suggestions]
extra_instructions = "answer in Japanese"
[pr_update_changelog]
extra_instructions = "answer in Japanese"
レビューの動作確認
HonoのAPIを追加する簡単なPRを作成します。2分でレビューは完了しました。
実際のPRはこちらにありますので、直接見ていただくのが良いと思います。
PRサマリの作成
レビューでは、Best Practice
とMaintainability
の観点で指摘がありました。サンプルコードなのであまりコンテキストがなくあまり参考にはなりませんが、外した指摘とは言えず個人的には参考になりました。
指摘が日本語化されていないのが気になりますが、別途調査したいと思います、、
さいごに
レビューコメントの付与方法に関しては、GitHubのMulti-line code suggestionsを使っているCodeRabbitの方に軍配があるなと感じました。この方式だと指摘に対する対応が1対1で管理できるのが整理しやすく便利です。
今現在すぐに少しでもレビューの負荷を下げたい場合は、導入ハードル(AWSとGitHubで完結し、従量課金)が低いので良い選択肢ではないかなと思います。シュッと入れて実際のコードで検証し、刺さればPro版やCodeRabbitを検討する形が良いと思います!