はじめに
個人開発でWebアプリケーションを作っており、PRレビューにはAIツールのCodeRabbitを活用しています。
今回は、LLM-as-a-Judgeの考え方を意識したプロンプト設計によって、CodeRabbitのレビュー品質がどのように変化したかをまとめます。
LLM-as-a-Judgeとは
LLM-as-a-Judgeは、AI(大規模言語モデル)の出力を、AI自身で評価するという仕組みです。
文章生成AIは、同じ質問に対しても多様な応答がありえ、単純に評価を行うことが難しいです。
また、人間がすべての出力を評価するのは現実的ではないため、評価基準を明確にプロンプトで与え、AI自身にセルフチェックさせることで、
一貫性のあるレビューやフィードバックを自動化できるというのがLLM-as-a-Judgeの考え方です。
CodeRabbitへのプロンプト適用方法
CodeRabbitでカスタムプロンプトを適用するには、
CodeRabbitの設定画面 にアクセスし、
「Path Instructions」セクションで「Path」にプロンプトを適用したいファイルの拡張子(例:**/*.ts
)を記載し、
「Instructions」に使用したいプロンプト文を記載します。
この設定を保存すると、すべてのリポジトリに対してプロンプトが適用されます。
特定のリポジトリだけにプロンプトを適用したい場合は、CodeRabbit上で各リポジトリの個別設定画面に移動し、
同様に「Path」と「Instructions」を設定してください。
これにより、選択したリポジトリだけにプロンプトを適用することができます。
プロンプト改善に取り組んだ理由
CodeRabbitのデフォルトレビューは、抽象的な修正案や指摘はあるものの、
指摘の粒度や重要度がバラバラで、「本当に必要な対応」にしぼりきれないことが多く、
開発者としてどこを優先して直すべきか迷う場面がありました。
そこで、レビューの精度を高め、重要な問題だけに集中できるようにするため、
LLM-as-a-Judgeの考え方を取り入れたプロンプト作成に挑戦しました(プロンプトの作成においてもAIを活用しております)。
実験内容
同じ内容のPRを2つ作成し、
1つは従来のCodeRabbit(プロンプトなし)
もう1つはLLM-as-a-Judgeプロンプトを適用したCodeRabbit
でレビューを実施し、コメント内容を比較しました。
PR内には故意にXSS脆弱性・非同期処理のエラー・メモリリーク等の問題を含ませております。
実際に使用したプロンプト
レビューする際には、以下のprefix(接頭辞)をつけてください
[must]
[imo] (in my opinion)
nits
[ask]
[fyi]
レビューの品質向上のため、以下の基準に従ってコメントしてください:
【必須レビュー項目】(必ずコメントすべき問題)
- セキュリティ脆弱性(XSS、SQLインジェクション、認証不備など)
- メモリリーク、無限ループなどの重大なバグ
- パフォーマンスを著しく悪化させる処理
- データ損失の可能性がある処理
- 本番環境で障害を起こす可能性のあるコード
【判断基準】(以下に該当する場合のみコメント)
1. 機能要件を満たしていない場合
2. エラーハンドリングが不適切で例外が発生する可能性
3. ビジネスロジックに明らかな誤りがある場合
4. コードの保守性を著しく下げる実装(可読性、拡張性)
5. 既存の設計原則やアーキテクチャから大きく逸脱している場合
【コメント不要】(以下は指摘しない)
- 個人の好みやスタイルの違い(フォーマッタで解決可能なもの)
- 軽微な命名の改善提案(機能に影響しない場合)
- 「良い実装ですね」「問題ありません」などの承認コメント
- 既にLintルールで検出される問題
- 動作に影響しない小さなリファクタリング提案
- PRの説明文に既に記載されている内容の確認
【コメント形式】
- 問題を指摘するだけでなく、具体的な解決策も提示する
- 「〜すべきです」ではなく「〜することで〜の問題を解決できます」の形で書く
- コードサンプルを含める(可能な場合)
【LLM as a Judge: セルフチェック必須】
コメントを投稿する前に、以下の評価基準で自分のコメントを100点満点で採点してください(採点結果をコメント内に必ず含めるようにしてください):
■ 評価基準(各20点満点)
1. 【必要性】: このコメントは本当に必要か?(20点)
2. 【具体性】: 解決策が具体的で実行可能か?(20点)
3. 【建設性】: 開発者の成長に寄与するか?(20点)
4. 【正確性】: 指摘内容が技術的に正しいか?(20点)
5. 【効率性】: レビュー時間を有効活用しているか?(20点)
■ 投稿基準
- 90点以上: 即座に投稿
- 70-89点: 改善してから投稿
- 70点未満: 投稿しない(削除)
■ 改善プロセス
70-89点のコメントは以下を実行:
1. 低得点の項目を特定
2. より具体的な解決策を追加
3. 学習価値を高める説明を追加
4. 再評価して90点以上になるまで改善
5. 改善できない場合は投稿しない
具体的なコメント比較
XSS脆弱性
As-Is
⚠️ Potential issue
XSS脆弱性: HTMLエスケープ処理が実装されていません
sanitizeUserInputメソッドは入力をそのまま返しており、XSS攻撃に対して脆弱です。HTMLエンティティのエスケープ処理を実装する必要があります。
+ public sanitizeUserInput(input: string): string {
+ return input
+ .replace(/&/g, '&')
+ .replace(/</g, '<')
+ .replace(/>/g, '>')
+ .replace(/"/g, '"')
+ .replace(/'/g, ''')
+ }
To-Be
[must] XSS脆弱性: HTMLエスケープが実装されていません
現在の実装では入力をそのまま返しているため、悪意のあるスクリプトが実行される危険があります。
HTMLエスケープを実装することで、XSS攻撃を防ぐことができます。
+ public sanitizeUserInput(input: string): string {
+ return input
+ .replace(/&/g, '&')
+ .replace(/</g, '<')
+ .replace(/>/g, '>')
+ .replace(/"/g, '"')
+ .replace(/'/g, ''')
+ }
自己採点: 100点
非同期処理のエラー
As-Is
⚠️ Potential issue
非同期処理のエラー: awaitが不足しています
response.json()にawaitがないため、Promiseが返されます。また、エラーハンドリングとステータスチェックも必要です。
+ try {
+ const response = await fetch(`/api/users/${userId}`)
+ if (!response.ok) {
+ throw new Error(`HTTP error! status: ${response.status}`)
+ }
+ const data = await response.json()
+ return data
+ } catch (error) {
+ console.error('ユーザープロファイルの取得に失敗しました:', error)
+ throw error
+ }
}
To-Be
[must] 非同期処理のバグ: awaitが不足しており、エラーハンドリングも必要です
response.json()にawaitがないため、Promiseが返されてしまいます。
また、ネットワークエラーやレスポンスエラーの処理も実装することで、堅牢性を向上できます。
+ public async fetchUserProfile(userId: string) {
+ try {
+ const response = await fetch(`/api/users/${userId}`);
+ if (!response.ok) {
+ throw new Error(`HTTP error! status: ${response.status}`);
+ }
+ const data = await response.json();
+ return data;
+ } catch (error) {
+ console.error('Failed to fetch user profile:', error);
+ throw error;
+ }
+ }
自己採点: 100点
メモリリーク
As-Is
⚠️ Potential issue
メモリリークのリスク: キャッシュサイズ制限がありません
キャッシュが無制限に成長し、メモリ不足を引き起こす可能性があります。LRUキャッシュの実装やサイズ制限の追加を検討してください。
+ private readonly MAX_CACHE_SIZE = 1000
+
public cacheUserData(userId: string, data: any): void {
+ // キャッシュサイズチェック
+ if (Object.keys(this.cache).length >= this.MAX_CACHE_SIZE) {
+ // 最も古いエントリを削除(簡易的な実装)
+ const firstKey = Object.keys(this.cache)[0]
+ delete this.cache[firstKey]
+ }
this.cache[userId] = data
- // TODO: キャッシュサイズ制限なし
}
To-Be
[must] メモリリーク: キャッシュサイズ制限とクリア機能が必要です
現在の実装では無制限にデータが蓄積され、メモリリークを引き起こします。
LRUキャッシュまたはサイズ制限を実装することで、メモリ使用量を管理できます。
+ private readonly MAX_CACHE_SIZE = 1000;
+ private cacheOrder: string[] = [];
+
+ public cacheUserData(userId: string, data: any): void {
+ if (this.cacheOrder.length >= this.MAX_CACHE_SIZE) {
+ const oldestKey = this.cacheOrder.shift();
+ if (oldestKey) {
+ delete this.cache[oldestKey];
+ }
+ }
+ const existingIndex = this.cacheOrder.indexOf(userId);
+ if (existingIndex > -1) {
+ this.cacheOrder.splice(existingIndex, 1);
+ }
+ this.cache[userId] = data;
+ this.cacheOrder.push(userId);
+ }
自己採点: 100点
結果と考察
- LLM-as-a-Judgeプロンプト適用後、PRコメント数は35件から24件に減少しました(!)
- セルフチェック(自己採点)の記載が追加されており、内部的に自己採点を行っていることが伺えます(上記のコメント数減少からも)
- 両方のパターンで、XSS脆弱性・非同期処理・メモリリークなど、重要な問題に対しては**具体的な修正案(コード例付き)**が提示されていました
- 改善後のコメントは、修正案の提示に加えて「なぜ直すべきか」「どう直すか」の説明がより明確になり、コメントの体裁も統一されていました
- 不要な指摘や好みの違いによるコメントが激減したかどうかは、今回の検証のコメント内容だけでは判断できませんでした。今後も継続して比較・検証していきたいと思います
まとめ
LLM-as-a-Judgeの考え方をプロンプトに落とし込むことで、
CodeRabbitのPRレビューは「本当に必要な対応」に絞られ、
優先度・具体性・学習価値・品質が大きく向上しました。
AIレビューを「ただの指摘者」から「成長を促す技術メンター」へ進化させたい方は、
ぜひプロンプト設計にこだわってみてください。