はじめに
本記事はUzabase Advent Calendar 2025の2日目の記事です。
私のチームではLLMを使ったアプリケーション開発に携わっており、生成品質の向上は常に重要な課題となっています。
本記事では、評価観点の整理・自動化、データセット作成、そしてDSPyの導入まで、実際に取り組んだ品質改善のプロセス全体と、その中で得られた知見を共有します。
DSPyの導入を検討している方や、既に導入しているもののうまく活用できていない方、LLMアプリケーションの品質改善を体系的に進めたい方の参考になれば幸いです。
抱えていた課題
私のチームでは、企業の中期経営計画書などのPDF資料から、特定の企業課題に関連する情報を抽出する機能を開発しています。
この機能はすでにリリース済みで、社内外からポジティブなフィードバックも多くいただいています。一方で、品質面では以下のような課題がありました。
- 対象の企業課題とは関連性の低い情報が出てしまう
- 資料のページ番号が間違っている
- 抽出した数値が間違っている
- 人物の発言ではない情報を、特定の人物の発言として抽出してしまう
アプリケーションの信頼性を担保するため、これらの品質課題の改善は最優先事項となっていました。以下では、実際に取り組んだ改善内容をご紹介します。
評価観点の整理・自動化
品質改善の第一歩として、評価観点を明確に整理することから始めました。この機能のドメイン固有の評価観点を改めて見直し、以下の6つの観点を定義しました。
- 具体性: 抽象的な表現ではなく、具体的な事業領域、戦略、施策を含んでいるか
- 固有性: 他の企業にも共通する一般的な情報ではなく、その企業特有の情報が含まれているか
- 関連性: 対象の企業課題と直接的かつ具体的な関連性を持っているか
- 網羅性: 重要な情報を漏らさず抽出できているか
- ページ番号の正確性: 正しいページ番号が参照されているか
- 数値の正確性: 数値が正しく資料内に存在するか
これらの評価観点ごとにLLM-as-a-judgeで自動評価を行える仕組みを構築しました。全ての観点を総合的に評価し、0から1のスコアと、改善点や良かった点をまとめたフィードバックの文章を返すようにしています。評価パイプラインの構築で意識した点については、後述の「評価を作る上で意識したこと」で詳しく紹介します。
データセットの作成
次に、プロンプト改善で使用する高品質なデータセットを作成しました。
具体的には、既存のプロンプトで生成された出力の中から、前述の自動評価の仕組みを使ってスコアが高い言及を収集し、それらを人によるチェックで選定して正解データセットとして利用しました。
人による評価は社内のドメインエキスパートに依頼しました。最初のうちは、自動評価でスコアが高かったものの中にも、人の目で見ると不適切なものがありました。そのフィードバックを基に評価の改善にも継続的に取り組み、自動評価と人の評価のギャップを小さくしていきました。
DSPyの導入
評価観点の整理とデータセットの準備が整ったところで、DSPyを導入しました。これが今回の改善の中核となる取り組みです。
DSPy(Declarative Self-improving Python)とは?
DSPyは、LLMアプリケーションをプロンプティングではなくプログラミングするためのフレームワークです。
従来のアプローチでは、プロンプトを手動で作成し、試行錯誤しながら調整していく必要がありました。一方、DSPyではタスクのシグネチャを宣言的に定義するだけで、その後のプロンプト最適化が自動的に行われます。
このアプローチの最大のメリットは、開発者がデータセットと評価関数の作成に集中でき、「どのようにプロンプトを書くか」という実装の詳細がDSPyによって抽象化される点にあります。
実際のコード
実際にどのようにDSPyを使ったのか、コード例を交えて説明します。以下のコードは説明のために簡略化しており、実際に動作するコードではありません。
タスクの入出力を宣言的に指定する
まず、タスクの入出力を宣言的に定義します。以下のコードでは、MentionGeneratorというシグネチャを定義しています。
class MentionGenerator(dspy.Signature):
"""課題が書かれているPDFの該当箇所を抽出する"""
issue_title: str = dspy.InputField(description="課題のタイトル")
issue_background: str = dspy.InputField(description="課題の説明")
pdf: dspy.Image = dspy.InputField(description="企業に関する中期経営計画書のPDFファイル")
mentions: list[Mention] = dspy.OutputField(description="課題が書かれているPDFの該当箇所")
このシグネチャでは、入力として課題のタイトル、説明、PDFファイルを受け取り、出力として言及のリストを返すことを宣言しています。注目すべきは、プロンプトの具体的な文言を一切書いていない点です。
プロンプトの最適化
次に、作成したシグネチャを基にプロンプトの最適化を行います。
optimizer = dspy.GEPA(
metric=metric, # 0-1のスコアを返す評価関数
reflection_lm=dspy.LM(
model="vertex_ai/gemini-2.5-pro", # プロンプトを提案するLLM
)
)
mention_generator = dspy.Predict(MentionGenerator) # シグネチャをもとにモジュールを生成
optimizer.compile(mention_generator, trainset=trainset, valset=valset) # 正解データセットを使って最適化
ここでは、GEPAというオプティマイザーを使用しています。評価関数(metric)とプロンプトを提案するLLM(reflection_lm)を指定し、正解データセット(trainsetとvalset)を使ってモジュールをコンパイル(最適化)します。
この最適化プロセスでは、DSPyが自動的に複数のプロンプトを生成・評価し、最も高いスコアを出すプロンプトを見つけ出します。
使ってみた感想
DSPyを実際に使ってみて、いくつかの気づきがありました。
良かった点
✅ プロンプトエンジニアリングの知識が不要になる
私自身、Few-shotやCoTなどのプロンプトエンジニアリングの深い知識はありませんが、手動で作成していた既存のプロンプトと比べて、大幅に品質を向上させることができました。プロンプトの細かい書き方に頭を悩ませる必要がなくなり、より本質的な部分に集中できるようになったと感じています。
✅ 複雑なタスクの最適化に威力を発揮
評価観点が複数あり、それらがトレードオフなど複雑な関係にある場合に威力を発揮します。例えば、網羅性と関連性はトレードオフの関係にあることが多いですが、DSPyは人間が手動で行うよりも高速に生成と評価のサイクルを繰り返すことができるため、このような複雑な最適化問題にも対応しやすいです。
✅ モデルの変更が簡単にできる
開発の中で実際にGeminiを使っていた部分をローカルLLMに変更したことがありましたが、数行のコードを変更して実行するだけで、簡単にローカルLLMに最適化されたプロンプトを生成することができました。今後もLLMのモデルは継続的に進化していくことが予想されるため、モデルの変更が簡単にできることは大きなメリットだと感じました。
✅ 保守性の向上
プロンプトの中には通常、タスクの概要、評価観点、出力形式など、複数の要素が含まれています。DSPyを使うと、これらの要素を責務ごとにプログラムとして構造化できるため、コードの保守性が向上します。
課題と感じた点
❌ 評価の仕組みとデータセットの重要性は変わらない
結局のところ、プロンプトの品質を向上させるためには、評価の仕組みとデータセットが最も重要です。またここの難しさは、DSPyを使っても変わりません。
❌ 最適化には時間がかかる
最適化プロセスにはそれなりに時間がかかります。特に、評価にLLM-as-a-judgeを使っていると、12時間以上かかることもありました。ちょっとしたLLMタスクや、それほど高い精度が求められない場合は、オーバースペックかもしれません。
❌ コストがかかる
最適化プロセスでは多くのLLM呼び出しが発生するため、コストもかかります。私たちの場合は一度の最適化で1万円程度かかりました。どのOptimizerを使うかや、どこまで最適化を続けるかによってコストは変わりますが、コストについては必ずあらかじめ考慮する必要があります。
評価を作る上で意識したこと
評価を作る上で重要だと感じたことをいくつかまとめます。個人的な経験に基づく内容ですが、参考になれば幸いです。
評価を小さく始める
完璧な評価システムを最初から作ろうとするのではなく、まず評価プロセス自体を回し始めることが重要です。少数のデータセットやシンプルな評価関数から始めて、フィードバックをもとに評価とデータセットをインクリメンタルに改善していくアプローチが有効だと感じました。
最初は、DSPyのドキュメント(参考リンク)にあるような感じで、簡単なプロンプトでLLM-as-a-judgeをするところから始めてみるのがおすすめです。
評価観点は細かく分ける
様々なことを一つの評価関数でやらせようとすると、評価精度を上げるのが難しくなります。
直交性を意識しながら評価観点を分け、各観点について具体的な評価基準を作り、なるべく定量的に評価できるようにすることで、人がやる場合でもLLMがやる場合でも、精度の高い評価に近づけやすくなります。
評価観点ごとの重要度を考える
全ての評価観点の精度を完璧にしようとすると時間がかかりすぎるため、評価観点ごとの重要度を考えて、重要度の高い観点から優先して精度を高めていくことが重要です。
また、スコアが低い場合にユーザーにとってクリティカルな問題になる観点については、生成時にガードレールとして設けることも検討する価値があります。
評価手法の使い分け
求める評価精度や、タスクの性質によって、評価手法を使い分けることも重要です。
- 👤 人でやる: 最も精度が高いが、コストも最も高い
- 🤖 LLMでやる: 自動化できるが、それなりにコストがかかる
- 💻 コードベースでやる: 最もコストが低く、高速
上に行くほどかかるコストは高くなりますが、コードベースでの評価が使える場面は限られます。まずはコードベースでの評価ができないかを検討し、無理そうであればLLM-as-a-judgeを使い、極めて重要な観点については人で評価することも検討すると良いでしょう。
LLM-as-a-judgeの改善にDSPyを使う
DSPyのドキュメント(参考リンク)にも記載があるように、LLM-as-a-judge自体の改善にもDSPyを使うことができます。
例えば、抽出した数値がPDFに正しく存在するかをチェックする部分で試してみたところ、かなり高精度で評価できるようになりました。評価観点が定量的になっていれば、データセットや評価関数の作成は比較的簡単に実装できます。特に、高い精度が求められる評価観点の最適化には良いアプローチです。
まとめ
本記事では、評価観点の整理・自動化、データセット作成、そしてDSPyの導入までの品質改善プロセスを紹介しました。
重要なポイントは、評価の仕組みとデータセットの準備が品質改善の基盤となることです。DSPyはプロンプト最適化を自動化してくれますが、良い評価関数と良いデータセットがなければ、その能力を十分に引き出すことはできません。
本記事が、皆さんのLLMアプリケーション開発の参考になれば幸いです。