自己紹介
NTTドコモ データプラットフォーム部(以下DP部)矢野です。 DP部では生成AIをビジネス活用すべく様々な取り組みを行っています。 今回執筆いただいた協働者の森さんとは、DP部が展開する社内の分析サービスに生成AIを用いて効率化や生産性の向上ができないかを詳細検討・実装・運用を一緒に取り組んでいます。 本記事では我々の生成AI取り組みの一端が見渡せるような記事に出来たらと考え、GitHub ActionsとLLMを利用した自動コードレビューについて紹介していただきます。
モチベーション
ドコモでは、Google Cloud上にStreamlitを利用したサーバレスプラットフォームを提供し、社内の開発者がアプリを開発できる環境を整えています。しかし、開発者の多くがデータ分析者であり、アプリ開発の経験が少ないため、商用利用に適したコードを書くのが難しい状況です。さらに、レビュアーの稼働が不足しており、十分なコードレビューが行われていません。 このような状況を克服するために、私たちはLLMを活用した自動コードレビューを導入することにしました。この仕組みにより、レビュアーの負担を軽減し、開発者が自信を持って高品質なコードを作成できる環境を整えることを目指しています。結果的に、開発効率の向上とコードの品質改善を実現し、チーム全体の成長を期待しています。
LLMコードレビューとは?
アプリの開発レポジトリにてプルリクエスト(以下、PR)が作成された際に、コミットされたコードのレビューをLLMで自動で行う機能です。
コードレビューが出力されるまでの流れ
- アプリの開発レポジトリでアプリ開発者がプルリクエストを作成します。
- 開発レポジトリ内に配置されたワークフロー(yamlファイルで定義)が呼び出されます。
- ワークフローにてレビュー項目を取得し、プロンプトに埋め込んでLLMに入力します。
- LLMからレビューがアウトプットされ、そのレビューが開発レポジトリのPR内に返ります。
ワークフローの作成、実行には、Githubのツールで、ビルド、テスト、デプロイのパイプラインを自動化できるCI/CDのプラットフォームである GitHub Actionsを用います。 また、LLMモデルには、Googleが開発したモデルである「Gemini-1.5-pro」を用いています。
実現したいこと
- 開発者側のレビュー機能の導入、維持の負担を軽減できるような体制にしたい
- 出力するレビューのレビューレベルを開発者側で選択できるようにしたい
- レビューの出力形式を制御したい
実施したこと、工夫点
1. GitHub Actionsを使用したワークフローの中央管理により、管理者のレビュー機能のCI/CDの負担を軽減
Github Actionsからワークフローを呼び出すためには、開発リポジトリ内に「.github/workflows」ディレクトリを作成し、そのディレクトリ内にワークフローを記載したyamlファイルを格納する方法が一般的に用いられます。 しかし、LLMコードレビューのワークフローは開発者のフィードバックを元に内容を改善していくことを想定しています。このため、通常通り開発レポジトリ上のyamlファイルからワークフローを呼び出す方法では、ワークフローに変更を加えた際のレビューの仕組みの管理者の負担が増加します。具体的には、変更の度に管理者が各開発者レポジトリ内でyamlファイルの変更を反映させる必要があります。この課題解決のため、本ケースではワークフローの再利用機能を活用し、ワークフローの中央管理を行いました。具体的には、yamlファイルを
- LLMコードレビューのワークフロー (以下、レビューワークフロー。中央管理レポジトリに格納)
- 1を呼び出し再利用するワークフロー (以下、呼び出しワークフロー。開発レポジトリに格納)
の2種に分け、2で1を再利用する形にします。開発レポジトリ内に呼び出しワークフローを格納しさえすれば、レビューワークフローに変更が加わった場合も、何もせず変更後のワークフローを呼び出してすぐに使用できます。 この方式により、管理者がワークフローの変更を反映する際の手間を減らすことに成功しました。
技術的ポイント、工夫点 : ワークフローの分割と呼び出しの設定
上述のyamlファイル分割において、呼び出しワークフローを下記のように記載しました。 ワークフローの呼び出しには、uses キーワードを使用します。コード11行目で中央管理レポジトリのreview.yamlで定義されたレビューワークフローを呼び出しています。(ワークフローの参照構文は、{owner}/{repo}/.github/workflows/{filename}@{ref}です。)
name: auto review on: workflow_dispatch: pull_request: branches: - main types: [opened, synchronize, reopened] jobs: review_action: uses: {owner}/{repo}/.github/workflows/{filename}@{ref} #再利用するLLMレビューのワークフロー
また、レビューワークフローは以下のように記載しました。ワークフローを再利用可能にするには、 on の値に workflow_call を含める必要があるため、2, 3行目で、ワークフローから呼び出された際にこのワークフローが走る、という設定を行っています。jobs: 以下で具体的なコードレビューのワークフローを記載します。
name: review on: workflow_call: ...... env: ...... jobs: ......
2. 様々なレビューレベルを想定し、開発者が選択できるレビューの詳細設定可能に
コードレビューの大きな目的の1つにコードの品質や保守性の向上がありますが、どの程度の向上を求めるかは開発者によって異なり、したがって開発者が求めるレビューのレベルは多種多様です。具体的に求めるレベル感は、メインブランチへのマージ前の厳格なものから、軽いリファクタリングレベルのものまであります。以上を踏まえ、コードレビューを開発者に便利なものにするために、開発者がレビューのレベルを設定できることが有効と考えました。 開発者へのヒアリングの結果、レビューを依頼する際は、何を、どうレビューさせるかといった観点で違いがあることが分かりました。 そこで本ケースでは、 ファイルの何をレビューさせるのか (変更したファイルの変更前との差分のみか、未変更箇所も含めたコード全体か) レビューの際に何を考慮させるのか (他にも変更があったファイルの内容を考慮するのか否か) の2つを選べるような機能を実装しました。また、開発者側で他に指示がある場合は記入いただき、プロンプトに反映できるようにしました。
技術的ポイント、工夫点
1. ワークフロー構文と環境変数$GITHUB_ENVを活用した入力変数の保存
上述のレビュー観点をユーザーが選択できる機能を以下のように追加しました。 まず、呼び出しワークフローにて、job_idwith構文を用いて入力値が渡されます。 with : 以下でユーザーの他の指示と、レビュー項目(コードの差分なのか全体なのか)、レビューの際の複数ファイルの考慮の有無を入力値として設定します。
jobs: action: uses: {owner}/{repo}/.github/workflows/{filename}@{ref} #再利用するLLMレビューのワークフロー with: user_prompt: '' # ユーザーから何か他の指示はあるか review_scope: "diff" # コードの差分のみをレビューするのか、全体をレビューするのか review_multifiles: "false" # 複数ファイルを考慮させるか
次に、入力の名前と種類がレビューワークフローの workflow-reuse-eventsで定義されます。 呼び出しワークフローで設定した入力値は、レビューワークフローのworkflow_call: の下のinputsコンテキストを使って以下のように参照されます。
name: review on: workflow_call: inputs: user_prompt: required: false type: string description: 'For User Prompt' review_scope: required: false type: string default: "diff" description: 'For Review Content' review_multi_files: required: false type: boolean default: false description: 'Flag to determine if this execution is for multiple files review'
参照された入力値は、${{input.○○}}で呼び出すことができます。ワークフロー全体で入力値を使用するために、入力値を環境変数$GITHUB_ENV 変数に保存しました。
echo "REVIEW_SCOPE=${{ inputs.review_scope }}" >> $GITHUB_ENV echo "REVIEW_MULTI_FILES=${{ inputs.review_multi_files }}" >> $GITHUB_ENV
$GITHUB_ENVで保存された環境変数は、pythonコード部分では以下のように呼び出せます。 review_scope = os.getenv('REVIEW_SCOPE') review_multi_files = os.getenv('REVIEW_MULTI_FILES')
2. コードの内容の読み込み、保存方法の工夫
下はコードの差分を読み取り保存する処理です。コード全体を読み取る場合は7行目がcontent=$(cat "$decoded_file")になります。 このコードの工夫点は、以下の2点です。
- マージ先との差分を変数に保存する際、区切り文字とともに変更ファイルごとに追記した点
- 変更ファイルの差分やコード全体を.txtに書き込み、.txtのパスを環境変数に保存することで大きなサイズの変数を環境変数に保存できるようにした点
1 はコードの6~8行目が該当します。これにより、レビューで変更があった全ファイルの内容を考慮させる場合は、diffs_of_changed_filesの内容をそのままプロンプトに組み込めば良いことになります。他のファイルの影響を考慮させずファイル単体でレビューされる場合も、環境変数に保存した区切り文字でdiffs_of_changed_filesの内容を分割することで各ファイルごとのコードの差分あるいはコード全体を取得することができます。 2 はコードの13~15行目が該当します。通常は echo -e "$diffs_of_changed_files" >> $GITHUB_ENV コマンドで環境変数に差分を保存することができます。しかしファイルの差分やコード全体の量が大きいと、変数のサイズが環境変数の固定長を超えてエラーになる場合がありました。そこで、13~15行目のように、差分やコード全体の内容を.txtに書き込み、.txtファイルのパスを環境変数に保存することで、大きなサイズの変数を環境変数に保存しつつ、内容を.txt経由で読み取れるようにしました。
while IFS= read -r file; do decoded_file=$(echo "$file" | iconv -f UTF-8 -t UTF-8 -c) # ファイル名をデコード git fetch origin $BRANCH_NAME # マージ先のリモートブランチの内容を取得 diff=$(git diff origin/$BRANCH_NAME...HEAD -- $decoded_file) # マージ先との差分獲得 split_str_diff="--- Diff of" echo "SPLIT_STR_DIFF=${split_str_diff}" >> $GITHUB_ENV # マージ先との差分を区切り文字とともにファイルごとに追記 diffs_of_changed_files="${diffs_of_changed_files}\n${split_str_diff} ${decoded_file} ---\n{$diff}" done < python_files.txt # 変更が加えられたファイルの差分を.txtファイルに保存 diffs_file_path="diffs_of_changed_files.txt" # 差分を保存する.txtファイルを定義 echo -e "$diffs_of_changed_files" > $diffs_file_path # 差分を.txtファイルに保存 echo "DIFFS_FILE_PATH=${diffs_file_path}" >> $GITHUB_ENV # .txtファイルのパスを環境変数に保存
3. プロンプトで出力内容や形式の制御を行い、出力を安定化
コードレビューの内容は開発者が作成するコードによって様々であったことから、元々のレビューの出力形式も様々で、同じコードの編集をインプットした際にも出力が安定せず開発者のレビュー内容の理解が困難という課題がありました。 開発者にとって理解しやすいレビューを返すには、開発者が求めるレビュー項目を含むよう全体の形式を決めて、その形式に乗っ取ってレビューを出力させる出力制御を行う必要があります。そのためにプロンプトに工夫を施しました。具体的には、デフォルトのプロンプトを以下の4つの部分で構成しました。以下では、特に大きなポイントであった、出力形式の例を用いた指定について述べます。
LLMに役割を与える(+ユーザーのプロンプトがあれば埋め込む) 出力形式を例を示しながら指定する(+出力形式に関する注意を書く) レビューの各項目でしてほしいことを具体的に伝え、文脈や背景事情を説明する(+その他注意点を与える) コードの変更内容[差分or全体、単一ファイルor複数ファイル]をプロンプトに埋め込む
技術的ポイント、工夫点 : 出力形式の指定
出力制御の代表的な手法として出力例(shot)をプロンプト内で与える方法があります。本ケースでは、出力フォーマットとして可読性が高いmarkdown形式を例として取り入れました。markdownの中身は、
- コードの修正箇所
- コードの修正の優先度
- コードの修正箇所の説明と修正が必要な理由
としました。以下にプロンプトの出力制御を行った部分を示します。
日本語を使用してください。あなたはStreamlitとPythonに精通した経験豊富なプログラマーです。 以下にコードを示すので、修正すべき箇所を明確にレビューしてください。 そして、レビュー内容を次の出力フォーマットに準じたmarkdown形式で出力してください。 {user_prompt} 出力フォーマット: # 修正ファイル名 (例 : test.py) ## 1. 修正後のコード例(修正部のみ) ## ~~~python (pythonコードの修正後の修正箇所のみを記述) ~~~ ## 2. 各修正部分の詳細 ## ### 各修正の優先度 : (高 or 中 or 低) ### ### 各修正箇所の説明と修正が必要な理由 ### (各修正箇所の説明と修正が必要な理由の説明を記述) ## 3. 具体的なシナリオへの適用 ## (具体的なシナリオへの適用を記述) 出力全体の注意点: - レビューはMarkdown形式で記述してください。 - レビューは上記の"出力フォーマット"と同じフォーマットで記述してください。 - 各番号のセクションは、見出しレベル2(##)を使用して明確に区切ってください。 - 各セクション内の項目は見出しレベル3(###)を使用してください。 - コード例や修正箇所は、適切なシンタックスハイライトを使用したコードブロック(```python```)で囲んでください。 - 説明を記載する際は、箇条書きや番号付きリストを使用して、情報を整理し見やすくしてください。 - 重要なポイントや強調すべきキーワードは、太字(**太字**)を使用してください。 以下、出力するコードレビューの各項目に関する指示 : .......................................
上記のようなプロンプトによる出力制御を行うことで、以下のような出力を得られるようになりました。(レビュー対象のプログラムは例として、フィボナッチ数列の合計を出力するpythonコードです。)
コードレビューを使用された開発者からのフィードバック
コードレビューを開発者の方に提供して使用いただきながら、現状以下のようなフィードバックを頂いています。 実際にコードレビューの魅力を実感頂けていることが分かり、導入の効果を実感できる結果となりました。
- 改修の優先度の分類やその理由・根拠が明確なためわかりやすく、急いでいる場合での取捨選択ができることも魅力。
- 開発者に代わって修正・実装の意図を記載してもらえるのは非常に良い。
- レビュアーがコードを理解するための助けになりそう。
- 実際に反映前に修正部分を見つけ、エラーの早期発見・改修につながったのでとても助かった。
今後の方向性
これまでの開発やエラーハンドリングで、ワークフローが安定して指定した形式でレビューを出力してくれるようになりました。 現在はLLMコードレビューは、チーム内のメンバーにのみ試験的に提供しご利用いただいている状態ですが、今後はさらに活用いただく範囲を広げていきたいと考えております。 LLMコードレビューを広げていくためには、コードレビューが開発プロセスでの生産性の向上に役立つものになるように、引き続き改善をしていく必要があります。 そのために今後は、コードレビューを活用した開発プロセスの生産性向上に向け、レビュー機能改善サイクルを以下の手順で回していく予定です。
- 改善するメトリクスの定義、取得
- Github Actionsで取得できるメトリクス(PRをオープンしてから、再コミットやクローズまでの時間 etc...)
- 開発者へのレビューに対する満足度のアンケート
- レビューワークフローの改善
- レビューワークフローの改善部分とメトリクスの関係の可視化