Open20

OpenAI PR Viewer の使い方忘れたので整理する

Yuji TsuritaniYuji Tsuritani

まずは、.github/workflows/ai-pr-reviewer.yml ファイルを作成

ai-pr-reviewer.yml
name: Code Review

permissions:
  contents: read
  pull-requests: write

on:
  pull_request:
  pull_request_review_comment:
    types: [created]

concurrency:
  group: ${{ github.repository }}-${{ github.event.number || github.head_ref ||
    github.sha }}-${{ github.workflow }}-${{ github.event_name ==
    'pull_request_review_comment' && 'pr_comment' || 'pr' }}
  cancel-in-progress: ${{ github.event_name != 'pull_request_review_comment' }}

jobs:
  review:
    runs-on: ubuntu-latest
    steps:
      - uses: coderabbitai/ai-pr-reviewer@latest
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
        with:
          debug: false
          review_simple_changes: false
          review_comment_lgtm: false
Yuji TsuritaniYuji Tsuritani

データエンジニア用にカスタマイズしてみる。

            あなたの目的は、非常に経験豊富なデータエンジニアとして機能し、コードの一部を徹底的にレビューし、
            以下のようなキーエリアを改善するためのコードスニペットを提案することです:
              - ロジック
              - セキュリティ
              - パフォーマンス
              - データ競合
              - 一貫性
              - エラー処理
              - 保守性
              - モジュール性
              - 複雑性
              - 最適化
              - ベストプラクティス: DRY, SOLID, KISS

            Pythonコードの場合、PEP8, PEP257, Effective Python のスタイルガイドラインに従ってください。
            Terraformコードの場合、Terraform公式スタイルガイド(https://developer.hashicorp.com/terraform/language/style)に従ってください。
Yuji TsuritaniYuji Tsuritani

一旦できてプロンプト

ai-pr-reviewer.yml
name: Code Review

permissions:
  contents: read
  pull-requests: write

on:
  pull_request:
    # staging, developmentブランチに対してPRしたときに動作させる意図
    # しかし、main->staging, developmentへのPRは動作してしまう。
    branches-ignore:
      - "release*" # ignore release branches
      - main # ignore main branch
  pull_request_review_comment:
    types: [created]

concurrency:
  group: ${{ github.repository }}-${{ github.event.number || github.head_ref ||
    github.sha }}-${{ github.workflow }}-${{ github.event_name ==
    'pull_request_review_comment' && 'pr_comment' || 'pr' }}
  cancel-in-progress: ${{ github.event_name != 'pull_request_review_comment' }}

jobs:
  review:
    runs-on: ubuntu-latest
    steps:
      - uses: coderabbitai/ai-pr-reviewer@latest
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
        with:
          debug: false
          review_simple_changes: true # 細部までレビューを行う
          review_comment_lgtm: true # 修正が問題ない場合はコメントにLGTMを記載する
          openai_heavy_model: o1-mini
          path_filters: |
            !dist/**
            !**/*.lock
          system_message: |
            あなたは @coderabbitai(別名 github-actions[bot])で、OpenAIによって訓練された言語モデルです。
            あなたの目的は、非常に経験豊富なデータエンジニアとして機能し、コードの一部を徹底的にレビューし、
            以下のようなキーエリアを改善するためのコードスニペットを提案することです:
              - ロジック
              - セキュリティ
              - パフォーマンス
              - データ競合
              - 一貫性
              - エラー処理
              - 保守性
              - モジュール性
              - 複雑性
              - 最適化
              - ベストプラクティス: DRY, SOLID, KISS

            Pythonコードの場合、PEP8, PEP257, Effective Python のスタイルガイドラインに従ってください。
            Terraformコードの場合、Terraform公式スタイルガイド(https://developer.hashicorp.com/terraform/language/style)に従ってください。
            些細なコードスタイルの問題や、コメント・ドキュメントの欠落についてはコメントしないでください。
            重要な問題を特定し、解決して全体的なコード品質を向上させることを目指してくださいが、細かい問題は意図的に無視してください。
          summarize: |
            次の内容でmarkdownフォーマットを使用して、最終的な回答を提供してください。

              - *ウォークスルー*: 特定のファイルではなく、全体の変更に関する高レベルの要約を80語以内で。
              - *変更点*: ファイルとその要約のテーブル。スペースを節約するために、同様の変更を持つファイルを1行にまとめることができます。

            GitHubのプルリクエストにコメントとして追加されるこの要約には、追加のコメントを避けてください。
          summarize_release_notes: |
            このプルリクエストのために、その目的とユーザーストーリーに焦点を当てて、markdownフォーマットで簡潔なリリースノートを作成してください。
            変更は次のように分類し箇条書きにすること:
              "New Feature", "Bug fix", "Documentation", "Refactor", "Style",
              "Test", "Chore", "Revert"
            例えば:
            ```
            - New Feature: UIに統合ページが追加されました
            ```
            回答は50-100語以内にしてください。この回答はそのままリリースノートに使用されるので、追加のコメントは避けてください。

            リリースノートの下に、このPRの変更についての短いお祝いのポエムを作成し、このポエムを引用(>記号を使用)として追加してください。
            ポエムの中で関連する場所に絵文字を使用することができます。
Yuji TsuritaniYuji Tsuritani

01-mini を使ってみたが、rate limit に引っかかってしまった。beta モデルのため rate limit が低めに設定されているか?
公式ドキュメントには、o1-mini の rate limit に関する記述はなかった。

Yuji TsuritaniYuji Tsuritani

言語設定などを修正してできたプロンプト

ai-pr-reviewer.yml
name: Code Review

permissions:
  contents: read
  pull-requests: write

on:
  pull_request:
    # staging, developmentブランチに対してPRしたときに動作させる意図
    # しかし、main->staging, developmentへのPRは動作してしまう。
    branches-ignore:
      - "release*" # ignore release branches
      - main # ignore main branch
  pull_request_review_comment:
    types: [created]

concurrency:
  group: ${{ github.repository }}-${{ github.event.number || github.head_ref ||
    github.sha }}-${{ github.workflow }}-${{ github.event_name ==
    'pull_request_review_comment' && 'pr_comment' || 'pr' }}
  cancel-in-progress: ${{ github.event_name != 'pull_request_review_comment' }}

jobs:
  review:
    runs-on: ubuntu-latest
    steps:
      - uses: coderabbitai/ai-pr-reviewer@latest
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
        with:
          debug: false
          review_simple_changes: true # 細部までレビューを行う
          review_comment_lgtm: true # 修正が問題ない場合はコメントにLGTMを記載する
          openai_light_model: gpt-4o-mini
          openai_heavy_model: gpt-4o-mini
          openai_timeout_ms: 900000 # 15分.
          language: ja-JP
          path_filters: |
            !dist/**
            !**/*.lock
            !db/**
            !**/*.csv
            !**/*.tsv
            !**/*.gz
            !**/*.zip
            !**/*.docx
            !**/*.xlsx
            !**/*.pyc
            !**/*.toml
            !**/*.json
            !**/*.mmd
            !**/*.svg
            !**/*.jpeg
            !**/*.jpg
            !**/*.png
            !**/*.gif
            !**/*.bmp
            !**/*.tiff
            !**/*.tfstate
            !**/*.tfstate.backup
          system_message: |
            あなたは @coderabbitai(別名 github-actions[bot])で、OpenAIによって訓練された言語モデルです。
            あなたの目的は、非常に経験豊富なデータエンジニアとして機能し、コードの一部を徹底的にレビューし、
            以下のようなキーエリアを改善するためのコードスニペットを提案することです:
              - ロジック
              - セキュリティ
              - パフォーマンス
              - データ競合
              - 一貫性
              - エラー処理
              - 保守性
              - モジュール性
              - 複雑性
              - 最適化
              - ベストプラクティス: DRY, SOLID, KISS

            Pythonコードの場合、PEP8, PEP257, Effective Python のスタイルガイドラインに従ってください。
            Terraformコードの場合、Terraform公式スタイルガイド(https://developer.hashicorp.com/terraform/language/style)に従ってください。
            些細なコードスタイルの問題や、コメント・ドキュメントの欠落についてはコメントしないでください。
            重要な問題を特定し、解決して全体的なコード品質を向上させることを目指してくださいが、細かい問題は意図的に無視してください。
          summarize: |
            次の内容でmarkdownフォーマットを使用して、最終的な回答を提供してください。

              - *ウォークスルー*: 特定のファイルではなく、全体の変更に関する高レベルの要約を80語以内で。
              - *変更点*: ファイルとその要約のテーブル。スペースを節約するために、同様の変更を持つファイルを1行にまとめることができます。

            GitHubのプルリクエストにコメントとして追加されるこの要約には、追加のコメントを避けてください。
          summarize_release_notes: |
            このプルリクエストのために、その目的とユーザーストーリーに焦点を当てて、markdownフォーマットで簡潔なリリースノートを作成してください。
            変更は次のように分類し箇条書きにすること:
              "New Feature", "Bug fix", "Documentation", "Refactor", "Style",
              "Test", "Chore", "Revert"
            例えば:
            ```
            - New Feature: UIに統合ページが追加されました
            ```
            回答は50-100語以内にしてください。この回答はそのままリリースノートに使用されるので、追加のコメントは避けてください。

            リリースノートの下に、このPRの変更についての短いお祝いのポエムを作成し、このポエムを引用(>記号を使用)として追加してください。
            ポエムの中で関連する場所に絵文字を使用することができます。
Yuji TsuritaniYuji Tsuritani

大した処理ではなかったのもあるが、大したレビューをもらえなかった。

Yuji TsuritaniYuji Tsuritani

今度はテストコードのレビューをしてもらう。

test_main.py
import pytest
from google.cloud.exceptions import Forbidden, NotFound
from src.main import execute_query, load_query_from_gcs

# =============================================================================
# load_query_from_gcs 関数のテスト
# =============================================================================


def test_load_query_from_gcs_success(mocker):  # noqa: ANN001, ANN201
    """正常系: バケットとファイルが存在する場合のテスト."""
    # モックオブジェクトを作成
    mock_storage_client = mocker.Mock()
    mock_bucket = mocker.Mock()
    mock_blob = mocker.Mock()

    # バケットとファイルのモックの返り値を設定
    mock_storage_client.bucket.return_value = mock_bucket
    mock_bucket.blob.return_value = mock_blob
    mock_blob.download_as_string.return_value = b"SELECT * FROM test_table;"

    # 関数を実行して結果を確認
    result = load_query_from_gcs(mock_storage_client, "test_bucket", "test_file.sql")
    assert result == "SELECT * FROM test_table;"

    # モックが正しく呼び出されたことを確認
    mock_storage_client.bucket.assert_called_once_with("test_bucket")
    mock_bucket.blob.assert_called_once_with("test_file.sql")
    mock_blob.download_as_string.assert_called_once()


def test_load_query_from_gcs_not_found(mocker):  # noqa: ANN001, ANN201
    """404 Not Found エラーが発生する場合のテスト."""
    # モックオブジェクトを作成
    mock_storage_client = mocker.Mock()
    mock_bucket = mocker.Mock()

    # NotFound エラーを発生させる
    mock_storage_client.bucket.return_value = mock_bucket
    mock_bucket.blob.side_effect = NotFound("Bucket or file not found")

    # 例外が正しく発生するかを確認
    with pytest.raises(NotFound):
        load_query_from_gcs(mock_storage_client, "test_bucket", "non_existent_file.sql")

    # モックが正しく呼び出されたことを確認
    mock_storage_client.bucket.assert_called_once_with("test_bucket")
    mock_bucket.blob.assert_called_once_with("non_existent_file.sql")


def test_load_query_from_gcs_forbidden(mocker):  # noqa: ANN001, ANN201
    """403 Forbidden エラーのテスト."""
    # モックオブジェクトを作成
    mock_storage_client = mocker.Mock()
    mock_bucket = mocker.Mock()

    # Forbidden エラーを発生させる
    mock_storage_client.bucket.return_value = mock_bucket
    mock_bucket.blob.side_effect = Forbidden("Access denied")

    # 例外が正しく発生するかを確認
    with pytest.raises(Forbidden):
        load_query_from_gcs(mock_storage_client, "test_bucket", "restricted_file.sql")

    # モックが正しく呼び出されたことを確認
    mock_storage_client.bucket.assert_called_once_with("test_bucket")
    mock_bucket.blob.assert_called_once_with("restricted_file.sql")


def test_load_query_from_gcs_unexpected_error(mocker):  # noqa: ANN001, ANN201
    """予期しないエラーが発生する場合のテスト."""
    # モックオブジェクトを作成
    mock_storage_client = mocker.Mock()
    mock_bucket = mocker.Mock()

    # 予期しないエラーを発生させる
    mock_storage_client.bucket.return_value = mock_bucket
    mock_bucket.blob.side_effect = RuntimeError("Unexpected error")

    # 例外が正しく発生するかを確認
    with pytest.raises(RuntimeError, match="An unexpected error occurred"):
        load_query_from_gcs(mock_storage_client, "test_bucket", "some_file.sql")

    # モックが正しく呼び出されたことを確認
    mock_storage_client.bucket.assert_called_once_with("test_bucket")
    mock_bucket.blob.assert_called_once_with("some_file.sql")


# =============================================================================
# execute_query 関数のテスト
# =============================================================================


def test_execute_query_success(mocker):  # noqa: ANN001, ANN201
    """正常系: クエリの実行が成功する場合のテスト."""
    # モックオブジェクトを作成
    mock_bq_client = mocker.Mock()
    mock_query_job = mocker.Mock()
    mock_query_job.result.return_value = None

    # クエリの実行のモックの返り値を設定
    mock_bq_client.query.return_value = mock_query_job
    test_query = "SELECT * FROM `test_dataset.test_table`;"
    mock_table_ref = "test_project.test_dataset.test_table"
    # 関数を実行して結果を確認
    execute_query(mock_bq_client, test_query, mock_table_ref)

    # モックが正しく呼び出されたことを確認
    mock_bq_client.query.assert_called_once_with(test_query, job_config=mocker.ANY)
    mock_query_job.result.assert_called_once()


def test_execute_query_not_found(mocker):  # noqa: ANN001, ANN201
    """404 Not Found エラーが発生する場合のテスト."""
    # モックオブジェクトを作成
    mock_bq_client = mocker.Mock()

    test_query = "SELECT * FROM `test_dataset.test_table`;"
    mock_table_ref = "test_project.test_dataset.test_table"

    # NotFound エラーを発生させる
    mock_bq_client.query.side_effect = NotFound("Table not found")

    # 例外が正しく発生するかを確認
    with pytest.raises(NotFound):
        execute_query(mock_bq_client, test_query, mock_table_ref)

    # モックが正しく呼び出されたことを確認
    mock_bq_client.query.assert_called_once_with(test_query, job_config=mocker.ANY)


def test_execute_query_forbidden(mocker):  # noqa: ANN001, ANN201
    """403 Forbidden エラーのテスト."""
    # モックオブジェクトを作成
    mock_bq_client = mocker.Mock()

    test_query = "SELECT * FROM `test_dataset.test_table`;"
    mock_table_ref = "test_project.test_dataset.test_table"

    # Forbidden エラーを発生させる
    mock_bq_client.query.side_effect = Forbidden("Access denied")

    # 例外が正しく発生するかを確認
    with pytest.raises(Forbidden):
        execute_query(mock_bq_client, test_query, mock_table_ref)

    # モックが正しく呼び出されたことを確認
    mock_bq_client.query.assert_called_once_with(test_query, job_config=mocker.ANY)


def test_execute_query_unexpected_error(mocker):  # noqa: ANN001, ANN201
    """予期しないエラーが発生する場合のテスト."""
    # モックオブジェクトを作成
    mock_bq_client = mocker.Mock()

    test_query = "SELECT * FROM `test_dataset.test_table`;"
    mock_table_ref = "test_project.test_dataset.test_table"

    # 予期しないエラーを発生させる
    mock_bq_client.query.side_effect = RuntimeError("Unexpected error")

    # 例外が正しく発生するかを確認
    with pytest.raises(RuntimeError, match="An unexpected error occurred"):
        execute_query(mock_bq_client, test_query, mock_table_ref)

    # モックが正しく呼び出されたことを確認
    mock_bq_client.query.assert_called_once_with(test_query, job_config=mocker.ANY)
Yuji TsuritaniYuji Tsuritani

テストコードに対するレビューはくれなかった。
@coderabbitai review で手動レビュー依頼をするも反応なし。

Yuji TsuritaniYuji Tsuritani

なんか CodeRabbit bot と Github Actions が競合していた。
CodeRabbit を一旦切った。

Yuji TsuritaniYuji Tsuritani

変更が大きいとダメらしい。

Files skipped from review due to trivial changes (1)
app/execute_query/tests/test_main.py (diff too large)
Yuji TsuritaniYuji Tsuritani

コミットを正常系の単体テスト1つに絞ってみた。レビューはされたが、それか・・・

Yuji TsuritaniYuji Tsuritani

やはりあまり実用には耐えないのだろうか。
コンテキスト長的には問題ない気がするのだが・・・