Vポイントマーケティング|TECH LABの Tech Blog

TECH LABのエンジニアが技術情報を発信しています

ブログタイトル

Azure OpenAIによるコードレビューを試してみました

こんにちは、AIエンジニアリンググループの矢澤です。

最近テストの採点や添削指導について考える機会がありました。 選択式の問題や数学の定理のような答えが一つに決まっている問題はともかく、自由記述形式の問題などでは正確な採点が難しいこともあるのではないかと感じ、教育現場などではどのように対応しているのかというのが気になりました。 JDLAが実施している「Generative AI Test」では生成AIが採点補佐を行うという話題もありましたが、将来的には入試などでもAIによる採点が一部使われるようになるのかもしれません。

生成AIは近年特に発展し、単純な質問回答やアイデア出しだけでなく、プログラムのコード生成なども行えるようになってきています。 実際に、Visual Studio CodeやDatabricksなどのツールでは、ユーザーの入力コードやコメント、エラー内容などに応じてリアルタイムにコード補完を実行できるようになっていて、開発効率の向上に繋がっていると感じます。

このようなことが可能なのは、LLMの学習データ自体にWeb上のソースコードなどが含まれていて、他の情報と併せてLLMが統計的に学習しているためです。 そのため、簡単な処理や頻繁に登場する関数1であれば、プロンプトを基に一から正しいコードを生成することも可能です。 しかし、複雑なコードを完全に自動生成するのはまだ難しく、コメントで仕様を詳細に説明したり人間による修正を行ったりすることが必要となり、実際の開発で生成AIを活用するにはまだハードルがあると思います。

ただし、これまでのチーム開発ではソースコードを実装した後に他者に都度コードレビューを依頼し、リファクタリングなどを行っていたことを考えると、既に実装したコードのレビューをAIが行ってくれるだけでも便利なのではないかと思いました。 そこで今回は、Azure OpenAI(以下AOAI)を使ってソースコードのレビューを行うスクリプトを作成し、結果を確認してみることにしました。

コードレビュースクリプト(修正前)

今回実装したスクリプトは、以下の通りです。

import os
from pathlib import Path
 
from dotenv import load_dotenv
from langchain_openai import AzureChatOpenAI
 
 
def main(code_path):
    WORK_DIR = Path("/workdir/")
 
    load_dotenv(WORK_DIR / ".env")
    aoai_endpoint = os.environ["AZURE_OPENAI_API_BASE"]
    aoai_deployment = os.environ.get("AZURE_OPENAI_DEPLOYMENT", "gpt-4o")
    aoai_version = os.environ.get("AZURE_OPENAI_API_VERSION", "2024-02-01")
    aoai_max_tokens = int(os.environ.get("AZURE_OPENAI_MAX_TOKENS", 2048))
    llm = AzureChatOpenAI(
        azure_endpoint=aoai_endpoint,
        azure_deployment=aoai_deployment,
        api_version=aoai_version,
        temperature=0,
        max_tokens=aoai_max_tokens,
        timeout=None,
        max_retries=2
    )
 
    with open(code_path, "r") as code_file:
        source_code = code_file.read()
 
    messages = [
        (
            "system", "あなたは優秀なソフトウェア開発者です。ユーザーが入力したソースコードに対してレビューを行い、問題点や改善点などを指摘してください。",
        ),
        (
            "human", f"ソースコード:{source_code}"
        ),
    ]
    ai_msg = llm.invoke(messages)
 
    with open(WORK_DIR / "data/review_result.txt", "w") as result_file:
        result_file.write(ai_msg.content)
 
 
if __name__ == "__main__":
    code_path = input("対象コードのパス:")
 
    main(code_path)

処理の流れは比較的単純で、環境変数からAOAI関連の設定を読み込んでAzureChatOpenAI(GPT-4o)のインスタンスを作成します。 その後ソースコードのファイルを読み込んで、定義済みのシステムプロンプトとともにAOAIにユーザープロンプトとして入力します。 出力された回答は、後で確認しやすいようにdataフォルダ以下のテキストファイルに出力するようにしています。

対象コードの選定

レビューの対象とするソースコードとして、GitHubなどに上がっているオープンソースのプログラムが考えられます。 しかし、そのようなプログラムは一般的に複雑で1つのファイルとして完結していることは少なく、また前提情報などが無いと開発者の意図を完全に理解することは難しいため、一部分をレビューしても的外れな指摘となってしまう可能性があるのではないかと思いました。

次に対象コードの候補として考えたのは、実務で使用しているコードを解析することです。 こちらは今後進められればと思っていますが、ソースコードの中には営業秘密などのドメイン知識が含まれていることもあり、Azure環境に送ったり今回の記事に載せるためには社内確認が必要となる可能性があります。 もちろん、そのようなドメイン知識を含まない汎用的なプログラムも探せば見つかるとは思いますが、より素早く試せるソースコードがあります。

それは、上記で実装した「コードレビュースクリプト」自体を解析対象とすることです。 上記のスクリプトはそれ単体で動作することが保証されているため、生成AIによるコードレビューを試してみるには十分な対象ではないかと思いました。 そこで今回は、「コードレビュースクリプト」を「コードレビュースクリプト」に入力し、AOAIの出力がどのようになるか確認してみました。

コードレビュー結果

レビューの結果は次のようになりました。

このコードは、指定されたソースコードファイルを読み込み、Azure OpenAIサービスを使用してそのコードのレビューを行い、結果をファイルに書き出すものです。以下にいくつかの問題点や改善点を指摘します。

  1. エラーハンドリングの欠如:

    • 環境変数の読み込みやファイル操作に失敗した場合のエラーハンドリングがありません。これにより、予期しないエラーが発生した場合にプログラムがクラッシュする可能性があります。
  2. ハードコーディングされたパス:

    • WORK_DIRがハードコーディングされています。これを環境変数や設定ファイルから読み込むようにすると、柔軟性が向上します。
  3. 環境変数の存在チェック:

    • os.environ["AZURE_OPENAI_API_BASE"]のように、存在しない環境変数にアクセスするとKeyErrorが発生します。os.environ.getを使用してデフォルト値を設定するか、存在チェックを行うべきです。
  4. ファイルパスの検証:

    • code_pathが存在するかどうか、または読み取り可能かどうかの検証がありません。これにより、無効なパスが指定された場合にエラーが発生します。
  5. ファイルのクローズ:

    • withステートメントを使用しているため、ファイルは自動的にクローズされますが、明示的にクローズすることを確認するのは良い習慣です。
  6. 関数の分割:

    • main関数が大きすぎます。各処理を小さな関数に分割することで、コードの可読性と再利用性が向上します。
  7. ログの追加:

    • ログを追加することで、デバッグや運用時のトラブルシューティングが容易になります。

以下は、上記の改善点を反映した修正版のコードです:

import os
from pathlib import Path
from dotenv import load_dotenv
from langchain_openai import AzureChatOpenAI
import logging

def load_environment_variables(work_dir):
    load_dotenv(work_dir / ".env")
    aoai_endpoint = os.environ.get("AZURE_OPENAI_API_BASE")
    if aoai_endpoint is None:
        raise ValueError("AZURE_OPENAI_API_BASE environment variable is not set.")
    azure_deployment = os.environ.get("AZURE_OPENAI_DEPLOYMENT", "gpt-4o")
    aoai_max_tokens = int(os.environ.get("AZURE_OPENAI_MAX_TOKENS", 2048))
    aoai_version = os.environ.get("AZURE_OPENAI_API_VERSION", "2024-02-01")
    return aoai_endpoint, azure_deployment, aoai_max_tokens, aoai_version

def initialize_llm(aoai_endpoint, azure_deployment, aoai_version, aoai_max_tokens):
    return AzureChatOpenAI(
        azure_endpoint=aoai_endpoint,
        azure_deployment=azure_deployment,
        api_version=aoai_version,
        temperature=0,
        max_tokens=aoai_max_tokens,
        timeout=None,
        max_retries=2
    )

def read_source_code(code_path):
    if not Path(code_path).is_file():
        raise FileNotFoundError(f"The file {code_path} does not exist.")
    with open(code_path, "r") as code_file:
        return code_file.read()

def write_review_result(work_dir, content):
    result_path = work_dir / "data/review_result.txt"
    result_path.parent.mkdir(parents=True, exist_ok=True)
    with open(result_path, "w") as result_file:
        result_file.write(content)

def main(code_path):
    logging.basicConfig(level=logging.INFO)
    WORK_DIR = Path("/workdir/")
    try:
        aoai_endpoint, azure_deployment, aoai_max_tokens, aoai_version = load_environment_variables(WORK_DIR)
        llm = initialize_llm(aoai_endpoint, azure_deployment, aoai_version, aoai_max_tokens)
        source_code = read_source_code(code_path)
        messages = [
            ("system", "あなたは優秀なソフトウェア開発者です。ユーザーが入力したソースコードに対してレビューを行い、問題点や改善点などを指摘してください。"),
            ("human", f"ソースコード:{source_code}"),
        ]
        ai_msg = llm.invoke(messages)
        write_review_result(WORK_DIR, ai_msg.content)
        logging.info("Review result has been written successfully.")
    except Exception as e:
        logging.error(f"An error occurred: {e}")

if __name__ == "__main__":
    code_path = input("対象コードのパス:")
    main(code_path)

この修正版では、エラーハンドリング、環境変数の存在チェック、ファイルパスの検証、関数の分割、ログの追加などを行っています。これにより、コードの信頼性と可読性が向上します。

処理の概要や改善点を説明してくれるだけでなく、改善版のコード生成まで行ってくれました。 これであれば、リファクタリングの工数を大幅に削減できそうです。

今回のコードではそこまで複雑な処理を行っていないからかもしれませんが、処理スピードを改善するような性能面での指摘はなく、エラーハンドリングや変数管理など全体的にプログラムの安定性を高めるような指摘が多いと感じました。 そのため、使い捨ての分析スクリプトなどよりは、ある程度の期間実務で活用するような商用ソフトウェアで真価を発揮するのではないかと思います。 また、関数の分割や命名なども自動で提案してくれて、ソースコードの可読性も高まると感じました。

まとめ

今回は、AOAIを使ってソースコードをレビューするスクリプトを作成し、そのスクリプトに対してコードレビューを実施してみました。 AOAIによるレビュー結果は納得感の高いものであり、プログラムの安定性や可読性の向上に繋がるのではないかと思いました。 コードレビューに使える汎用的な生成AIはAOAI以外にも複数あり、さらに最近ではコード生成に特化した生成AIも多数登場しています。 今後は、そのような他の生成AIでもコードレビューやコード生成を行い、各モデルの特徴や差異などを確認してみたいです。


  1. 例えば「整数配列の中の奇数の要素を数える」、「2種類の特徴量間の関係を散布図としてMatplotlibで描画する」など