データベース設計の自動レビュー機能を生成AIで独自開発
講演者の廣瀬氏は、KINTOテクノロジーズのプラットフォーム開発部 DBREグループに所属している。
KINTOテクノロジーズは、モビリティ・カンパニー化を目指すトヨタグループのITサービスを支える内製開発部隊で、東京・大阪・名古屋にまたがって350名超のソフトウェアエンジニアを擁している。その中でDBREグループは、データベース領域にSRE(Site Reliability Engineering:サイト信頼性エンジニアリング)の考え方を取り入れて、プラットフォームやツール開発、運用、トラブルシューティングなどに携わっている。

今回紹介した社内アプリでは、データベースのテーブル設計を生成AIで自動レビューする機能を備えている。プルリクエストをオープンすると、GitHub Actionsがトリガされて、AWS上に構築した自動レビューの仕組みをキックする。そして、GitHub上のコメントとして修正案がフィードバックされる流れになっている。

今回開発したアプリケーションはシステム開発を支援する機能であるため、デプロイ可否の判断や運用時の品質を何らかの基準で評価する必要がある。
「生成AIアプリケーション開発ではこの評価が重要と考えています。生成AIからの応答を100%コントロールできないからです。私は特に機械学習のバックグラウンドがあるわけではないので、開発時に最も苦労した箇所の1つになりました」
では、この自動レビュー機能をどのような方針で開発したのだろうか。廣瀬氏は、従来の構文解析と生成AIの2つの方式があると説明した。構文解析であれば、オブジェクト名がオーバースネークケースで定義されているかはチェックできる。一方で、「格納データが推測できるようにオブジェクト名を命名する」というガイドラインに対して、オブジェクト名が「text」「data1」では意味が曖昧なのでNGになるが、こういう例は構文解析では検出が難しく生成AIの方が得意だ。
自動レビュー機能の開発方針に対して廣瀬氏は「理想は、レビュー観点に応じて構文解析と生成AIのどちらかを使い分けることですが、今回は生成AIのみでMVP(Minimum Viable Product)を実装することにしました」と語った。
では、このような機能をなぜ自前で作ったのだろうか。
すでに、GitHub Copilot/PR-Agent/CodeRabbitなどコードレビューを支援するサービスやOSSが登場しているが、多数のガイドラインの高精度なチェックは現状では困難と判断したという。また、フィードバックの方法を柔軟に調整したいという考えもあった。例えば、意味が曖昧な「data1」というカラムがあったとしても、修正案の提示は難しいためコメントのみにしたいといった具合だ。さらに、将来的に構文解析とのハイブリッド構成で精度向上を目指す意向もあった。
それでは、生成AIアプリケーションはどのように評価すればいいのだろか。
ここで廣瀬氏は、マイクロソフトが公開している「生成AIアプリケーションの評価」というドキュメントを紹介した。ここに、“GenAIOps”ライフサイクルにおける3つの評価プロセスが示されている。「この3つのプロセスで、それぞれ適切に評価することが重要とされており、私たちもこれに従ってアプリケーション開発を実施しました」と廣瀬氏は言う。

廣瀬氏は、この3つのフェーズに沿って、テーブル設計の自動レビュー機能について説明を続けた。
基盤モデルを適切に評価し、選定するためのプロセスとは
GenAIOpsの1つめのフェーズは「モデル選定フェーズ」である。生成AIの基盤モデルを評価し、使用するモデルを決定する段階だ。まず廣瀬氏は、モデル選定時に発生するトレードオフについて説明した。
「モデルごとに、性能とコストとパフォーマンスがトレードオフの関係になっていて、一般的には性能が高ければ高コスト、高レイテンシの傾向がありますが、長期的にはコストが下がる傾向にあります。そこで、ユースケースに応じて優先事項を決定します」
今回はユースケースを踏まえて性能を優先した。データベースのレビューの頻度は高くないため、1回あたりのコストに寛容で、高速な応答も求められないためだ。性能は、ベンチマークスコアを参考にして評価した。

ただし、モデル選定時にはジレンマも感じたそうだ。
まずは、チューニングの前後で性能が異なる点である。生成 AIアプリの性能は、基盤モデルの性能とプロンプトチューニングで決まってくるが、このプロンプトチューニングは基盤モデルごとに方針が異なる。例えば、ChatGPTはMarkdown形式、ClaudeはXML形式といった具合だ。そのため、同一プロンプトで各モデルの性能を評価できず、チューニング次第では順位が逆転する可能性もある。また、各モデル用にチューニングしたプロンプトで評価すると、チューニング工数が必要以上に肥大してしまうだろう。
「私たちは、ベンチマークスコアのいいモデルに、粗いプロンプトを組み合わせて確認して、いけそうと思えたら次のフェーズへ移ることにしました。そして、AWS上での開発のしやすさから、Bedrockで利用可能なモデルのうち、性能を重視してClaude 3.5 Sonnetを採用しました」
生成AIの応答を適切に評価し、チューニングするには
GenAIOpsの2つめのフェーズは「アプリケーション開発フェーズ」。アプリケーションの出力となる生成AIの応答を評価し、チューニングしていく段階だ。
ここで廣瀬氏は、Anthropic社の「Create strong empirical evaluations」というドキュメントを紹介し、磨かれたプロンプトをデプロイするためには、テスト用のデータセットを作成して評価とプロンプトチューニングを繰り返すことが重要だと説明した。その際に「推論結果が期待にどれだけ近いか」などの評価観点を「何らかの方法で算出したスコア」として定義して「最良のスコアを得たプロンプト」を採用するという。「ポイントは定量化すること。チューニング前後の比較の曖昧さを排除できるからです」と廣瀬氏は言う。
では、どのように定量化すればいいのだろうか。生成AIの評価観点をブレイクダウンする方法としては諸説あるが、調べた中で1番わかりやすかったのは、クオリティとコンプライアンスからなるものだという。これら観点は、さらに真実性・安全性・公平性・堅牢性に分類できる。

ただし、同じ観点であってもアプリケーションの特性に応じて正解率の算出方法を選ぶ必要があるという。例えば、Amazon Bedrockでも、テキスト生成ではRWKスコア、要約ではBERTスコアという具合に、タスクごとに正解率の算出方法に異なる指標を採用している。
評価観点のスコア化には3つの方法があり、コードベース・人間によるもの・モデルベースに大別できる。それぞれの方法にメリットとデメリットがあるので、評価の観点に応じて適材適所で選択していく必要がある。
まずクオリティの観点では、スコア算出にコードベースのアプローチを選択した。正解のテーブル定義(DDL)と完全一致をベストスコアにしつつ、類似度を定量化できるからだ。算出ロジックには、テキスト間の距離を計測する手法の1つである「レーベンシュタイン距離」を採用した。この手法では、完全一致で距離「0」、値が大きいほど類似度が低いとみなす。ただし、「DDLにおける類似度」を完全に表す指標ではないため、基本的には全データセットで0スコアを目指し、非0スコアのデータセットに対してプロンプトチューニングを行う方針とした。
続いて、コンプライアンスの観点は、社内向けアプリケーションであることや、プロンプトに埋め込むユーザー入力をテーブル定義に限定する実装になっていることから、今回は対応不要と判断した。
ただし、ユーザー向けチャットアプリのようにユーザーに公開するアプリケーションなどではコンプライアンスの観点での評価も必要になる。その場合は「クラウドベンダー提供のガードレール機能が便利だろう」と加えた。
評価の実装は、Python向けWebフレームワークであるStreamlitでおこなった。ここでは、jsonl(JSON Lines)形式のデータセットを与えて、ガイドラインとテーブル定義(DDL)をプロンプトに埋め込み、Amazon Bedrock で処理し、スコア値が最小になるまでプロンプトのチューニングを繰り返している。

「結果として、プロンプトの品質を、デプロイ可能と判断できるまで上げることができました。60個のデータセットほぼ全てにおいて、0スコアという最良の結果を達成しました。これは、専用アプリの開発により、チューニングと自動評価の高速ループが可能になったことが大きかったと思います」
廣瀬氏は、プロンプトのチューニングについても簡単に説明した。今回は、Claudeのベストプラクティスに則ったプロンプトチューニングを実施したという。中でも、プロンプトをChainさせる手法が有効だったと廣瀬氏は言う。例えば、チェックすべきガイドラインの項目が多数あった場合に、1回で全ガイドラインをチェックすると、数が増えるほどプロンプトが複雑化し、LLMによるチェック漏れや精度低下の懸念が高まるという。そこで、1回のプロンプト実行でチェックさせる項目を1つに限定し、推論により得た「修正後のDDL」を次のプロンプトへの入力として渡し(Chain)、それを繰り返し処理して最終的なDDLを得る仕組みを構築した。
「この手法は、プロンプトが短くタスクが1つに絞られるので精度が向上する、ガイドライン追加時は新規プロンプトを作成すればいいので既存プロンプトの精度に影響がないといったメリットがあります。一方で、LLMのInvoke(呼び出し)回数が増えるため、応答時間と金銭的コストは増加するというデメリットもあります」
LLMの応答をLLMに評価させる「LLM-as-a-Judge」の活用
GenAIOpsの3つめの段階「デプロイ後の運用フェーズ」での評価について説明した。このフェーズでは、プロダクション環境へデプロイ後も、品質・安全性などを継続的に評価し改善を続けることになる。
「アプリケーション開発フェーズでは、手動で作成した正解データを用いてクオリティを評価していました。しかし運用時にはリアルタイムの正解データが存在しないため、別の評価手法が必要になります。そこでモデルベースの評価アプローチとして『LLMの応答をLLMに評価させる』手法であるLLM-as-a-Judgeを採用しました」
LLM-as-a-Judgeは、LLMの評価プラットフォームであるConfident AIのドキュメント「Leveraging LLM-as-a-Judge for Automated and Scalable Evaluation」を参考に、「LLM の出力」と「評価基準(Criteria)」を与えて、基準に基づいてLLMにスコアを付けさせるSingle Output Scoring(正解データなし)を採用したと説明した。

評価基準(Criteria)としては、次の2つを独自に定義したという。
1つ目は適切さ(Appropriateness)。LLMの出力がガイドラインに沿って適切に修正されているかというもの。2つ目がフォーマットの一貫性(Formatting Consistency)。不要な改行や空白などが付与されておらず、フォーマットの一貫性が保たれているかを評価するものだ。
今回開発したアプリケーションのアーキテクチャは、次図のようになっている。このうち赤の点線部分がLLM-as-a-Judgeに当たる。

ただし、LLM-as-a-Judgeは完全に信頼できるとは言えず、人間による評価結果と比較して信頼性を測ることが重要だと語った。今回はユーザーの声を集めやすい社内システムであるため、定量的なスコアを継続的にモニタリングしつつ、ユーザーフィードバックを収集していく予定だ。
さらに今後は、本番環境に入力されたDDLと、LLMが生成したDDLが蓄積されていくため、 出力されたDDLでアノテーションして事後評価を実施したり、本番環境のデータセットでファインチューニングしたりしていきたいと述べた。
最後に、全体のまとめとして、得られた学びを3つのポイントで解説した。
まずは、LLMアプリケーションは評価が重要かつ難しいということ。Amazon BedrockやOpenAIでブラックボックスなAPIを扱えば作るだけなら簡単にできる。しかし、そのクオリティを他社に説明したり担保したりするのは難しい。今回は、最初に評価を設計し、チューニングと評価サイクルを高速に回すことができたが、3つの評価プロセスでユースケースごとに都度判断が必要になり、評価設計の妥当性判断が困難になりやすいと感じたという。
2番目のポイントは、テストデータの作成が大変だったということ。今回は手動で作ったため、多くの時間を要しており、精神的な負荷も高かった。テストデータを自動でLLMに生成させる手法も存在するが、別途プロンプト作成とチューニングが必要になる。結局、どこかで人間による正確性チェックは入れた方がいいと感じたという。
3番目のポイントは、性能が低いモデルほどプロンプトチューニングの工数・難易度が増加したこと。最初は、Claudeの3つのモデル(Haiku/Sonnet/Opus)のそれぞれでチューニングを進めていたが、Haikuは今回のタスクに対して性能が低すぎてチューニングを断念したそうだ。また、後から出た3.5 Sonnetで一気にチューニングが楽になったと、その実感を語ってくれた。