AI Code Review Agentの構築:静的解析とLLMを統合した自動化パイプラインの実践

Code Review Agentの設計思想:シフトレフトの実現

ソフトウェア開発において、不具合の発見が遅れるほど修正コストは指数関数的に増大します。コードレビューは、バグがメインブランチに混入する前の重要な防波堤ですが、人間によるレビューには「品質のばらつき」「カバレッジの不足」「時間的リソースの限界」という課題が常に付きまといます。

私たちのチームでは、LLM(大規模言語モデル)の推論能力と、既存の静的解析ツールの確実性を組み合わせた「Code Review Agent」を構築しました。その核心は、レビューを以下の3つのレイヤーに分離することにあります。

レイヤー 手法 対象範囲 応答速度
L0: 決定論的チェック ESLint / SonarQube / Semgrep 構文、スタイル、既知の脆弱性 数秒
L1: セマンティックチェック LLM + チーム固有ナレッジベース 設計規約、アンチパターン、文脈依存のバグ 数分
L2: ヒューマンレビュー シニアエンジニア ビジネスロジックの妥当性、アーキテクチャ設計 数時間〜

エージェントのアーキテクチャとワークフロー

Code Review Agentは、単にコードをLLMに投げるだけではありません。コンテキスト(文脈)を注入するためのパイプラインとして機能します。主なステップは以下の通りです。

  1. トリガー:GitLabやGitHubのPull Request(PR)作成・更新時にWebhookで起動。
  2. Diff解析:git diffから変更点のみを抽出。トークンコストを抑えるため、変更箇所の周辺行のみをコンテキストとして保持。
  3. 静的解析の並列実行:L0ツールを同時に走らせ、確実な指摘事項を収集。
  4. RAG(検索拡張生成)による規約抽出:変更内容をクエリとして、チーム独自の「レビューガイド知識ベース」から関連するコーディング規約を検索。
  5. LLMによる統合推論:「Diff内容」「静的解析結果」「関連規約」をプロンプトに組み込み、LLMに最終的なレビュー結果を生成させる。
  6. フィードバック:PRのコメントとして結果を投稿。

以下は、LLMに渡すプロンプトの構造を定義した例です。

{
  "role": "system",
  "content": "あなたは熟練のシニアエンジニアです。以下の入力に基づき、コードレビューを行ってください。
  
  [入力データ]
  1. git diff: 修正されたコードの断片
  2. static_analysis: 静的解析ツールが検出した警告
  3. internal_guidelines: チーム固有のコーディング規約(RAGで取得)

  [出力形式]
  - 深刻度 (Critical/Warning/Info)
  - 問題の所在 (ファイル名、行数)
  - 指摘内容 (なぜ問題なのか、どの規約に違反しているか)
  - 修正案 (具体的なコード例)"
}

レビューガイド知識ベース(RAG)の構築

LLMに「一般的な知識」ではなく「私たちのチームのルール」を教えるために、知識ベースの構築は不可欠です。私たちは以下の3つのソースからデータを蓄積しました。

  • 過去のレビュー指摘事項:過去1年間のPRコメントをLLMで分類し、頻出する指摘をルール化。
  • 障害ポストモーテム:過去のインシデントの原因を「二度と繰り返さないためのチェック項目」に変換。
  • 業界標準の拡張:OWASP Top 10などのセキュリティ基準を自社環境に合わせて具体化。

各ルールはベクトルデータベースに保存され、以下のような構造を持ちます。

# レビュー規約エントリー例
rule_id: "RULE-J-004"
category: "Concurrency"
pattern: "Thread.sleep() の使用"
guideline: "プロダクションコードでの Thread.sleep() は禁止。ScheduledExecutorService を検討すること。"
severity: "Critical"

静的解析とLLMの相互補完

静的解析ツールとLLMは、どちらかが優れているのではなく、互いに補完し合う関係にあります。

  • 静的解析:誤検知(False Positive)が少なく、100%確実にルールを適用できるが、コードの意図(ビジネスロジック)は理解できない。
  • LLM:コードの意図を理解し、複雑な条件分岐の矛盾を見抜けるが、非決定論的であり、時にはハルシネーション(もっともらしい嘘)を起こす。

私たちのパイプラインでは、「静的解析が検出したアラートをLLMが精査し、不要なノイズをフィルタリングする」という手法を採用しています。これにより、開発者に届く通知の信頼性を大幅に向上させました。

def filter_alerts(findings, diff_context):
    # 静的解析の結果をLLMに検証させるロジックの模写
    report = []
    for item in findings:
        is_valid = llm.verify(item, diff_context)
        if is_valid:
            report.append(item)
    return report

Pre-commit Hookによる防衛線の前置

PRの段階で問題を指摘するのは、手戻りコストが発生するため「少し遅い」と言えます。そこで、特に重要なルール(Blocker級)については、git commitの直前に実行されるPre-commit Hookに移植しました。

ただし、コミット時のチェックは速度が命です。LLMによるフルレビューは行わず、軽量な静的解析ツールと、特定のキーワードに対する正規表現チェックのみを実行するように構成します。

#!/bin/bash
# .git/hooks/pre-commit の一部

STAGED_FILES=$(git diff --cached --name-only --diff-filter=ACM)

echo "L0 静的解析チェックを実行中..."
# 特定の重要なルールのみを高速スキャン
results=$(grep -rnE "TODO|FIXME|System.out.println" $STAGED_FILES)

if [ ! -z "$results" ]; then
    echo "警告: 禁止されているパターンが見つかりました。"
    echo "$results"
    exit 1
fi

導入後の成果と継続的改善

このAgentを3ヶ月運用した結果、以下のような定量的・定性的な変化が見られました。

  • レビュー時間の短縮:単純なスタイル違反や初歩的なバグの指摘が自動化され、人間によるレビュー時間が約30%削減されました。
  • 指摘の標準化:レビュアーの習熟度に依存せず、常にチーム規約に基づいたフィードバックが行われるようになりました。
  • 新人の早期習熟:Agentが即座にフィードバックを返すため、新入社員がチームのコーディング習慣を学ぶ「教育ツール」としても機能しています。

今後の課題は、動的なルール適用です。例えば、決済処理などの「クリティカルなパス」には厳格なルールを適用し、内部向けの管理ツールには標準的なルールを適用するといった、修正箇所の重要度に応じた重み付けの実装を進めています。

タグ: CodeReview LLM StaticAnalysis RAG GitHooks

9月29日 03:48 投稿