こんにちは。2025年9月にSmartHRへ入社したプロダクトエンジニアのicchanです。 現在はAIインテグレーションユニットで、生成AIを活用した社内問い合わせ対応の効率化に取り組んでいます。
本記事では、社内問い合わせBotの改善プロセスで実践した「2段階DSPy」というアプローチについて解説します。
TL;DR
本記事の要点は次の通りです。
- オンライン指標(Good/Bad/Copy)は「正解」に近いが、フィードバックが遅くノイズが多いため、開発のループを回しにくい
- そこで、オンライン指標を教師データにして、DSPyで「オフライン評価器(Judgeプロンプト)」を学習させ、"高速に実験を回せる物差し"を作った
- さらに、その物差しを評価関数に組み込み、生成プロンプト自体をDSPy(GEPA)で最適化することで、ユーザーの実感に即した改善を自動化した
flowchart LR
subgraph Phase1 [第1段階]
direction LR
Input1_1["ユーザーからの質問"]
Input1_2["チャットボットの回答"]
Input1_3["オンライン指標<br>(Good/Bad/Copy)"]
Input1_4["初期<br>Judge プロンプト"]
Process1["DSPy(GEPA)<br>によるプロンプト<br>最適化"]
Output1["改善された<br>Judge プロンプト"]
Input1_1 & Input1_2 & Input1_3 & Input1_4 --> Process1
Process1 --> Output1
end
subgraph Phase2 [第2段階]
direction LR
Input2_1["ユーザーからの質問"]
Input2_2["チャットボットの回答"]
Input2_3["Judge プロンプト<br>によるScore"]
Input2_4["初期<br>生成プロンプト"]
Process2["DSPy(GEPA)<br>によるプロンプト<br>最適化"]
Output2["改善された<br>生成プロンプト"]
Input2_1 & Input2_2 & Input2_3 & Input2_4 --> Process2
Process2 --> Output2
end
Output1 -.->|2段階目で使用| Input2_3
前提知識
本題に入る前に、改善対象・使用技術・指標を順に整理します。
改善対象:社内問い合わせ回答Bot
改善対象は、SmartHRの従業員がSlack上で利用できる、RAG(検索拡張生成)ベースのAIアシスタントです。 社内ドキュメントを参照して質問に自動回答します。問い合わせ負荷軽減を目的として運用されています。
使用技術:DSPy(Declarative Self-improving Python)
DSPyは、スタンフォード大学の研究チームが開発した LLM(Large Language Model、大規模言語モデル)アプリケーション構築のためのフレームワークです。 最大の特徴は、プロンプトエンジニアリングを人間が手作業で行うのではなく、オプティマイザが自動的に最適化する点にあります。開発者はモジュール構成とデータ、そして評価関数を定義するだけで、内部で使用する具体的なプロンプトなどをDSPyが自動調整してくれます。
使用技術:GEPA
GEPAは、DSPyのエコシステム上で動作するプロンプト最適化アルゴリズムの一つです。 実行トレースと評価指標をもとに、LLMによる反省プロセスを通じて、プロンプトの指示文などのテキスト要素を進化・改善させます。 今回は、評価器(Judgeプロンプト)の作成と、回答生成プロンプトの最適化の両方で、このGEPAによる進化的な探索を使用しました。
オンライン指標(Good / Bad / Copy)
オンライン指標は、ユーザー(従業員)が実際にBotを利用した際の行動ログ指標です。SmartHRの社内Botでは以下の3つのアクションを計測しています。
- Good / Bad
- 回答に対する高評価・低評価のリアクション
- Copy
- 回答テキストにある「コピーボタン」の押下
2段階DSPyというアプローチ
ここからは、オンライン指標をオフライン評価に落とし込むために採用した「2段階DSPy」の考え方を説明します。
「Good/Bad/Copyなどのオンライン指標を直接最大化するように、プロンプトを改善すればいいのでは?」
当初はそう考えがちですが、オンライン指標に直接最適化しようとすると、次の壁に当たります。
- 速度の壁
- オンラインでABテストを実施すると統計的に有意な結果が揃うまで時間がかかり、改善サイクルが遅くなる
- 運用負荷の壁
- ABテスト設計・ABテストインフラの実装、運用・関係者調整などの作業が発生しコストが高くなる
一方で、一般的なオフライン評価(正解データとの意味的類似性など)だけに頼ると、以下の問題が起きます。
- 「それっぽく正しい」回答で高スコアが出るが、現場では使われない
- "そのままコピペして使える回答"に代表される実務的な価値がスコアに反映されない
そこで、オンライン指標から “現場で使われる回答らしさ”を判定できるオフライン指標 をまず作り、その指標を使って高速に最適化を回す、という2段構えのアプローチを採用しました。
全体像:2段階DSPyパイプライン
アプローチの全体像は以下の通りです。
第1段階:オンライン指標からオフライン指標を作る
本段階では、次の流れで Judge プロンプトを作成します。
- Slack上で Good/Bad/Copy のリアクションを収集
- 行動ログを 1〜3点に変換して教師データ化
- DSPyを用いて「質問+回答 → スコア」を予測する Judge プロンプトを学習
第2段階:オフライン指標を使い生成プロンプトを最適化する
第2段階では、次の2種類の指標を組み合わせて評価関数を設計します。
- UX 指標(UX: User Experience、ユーザー体験。第1段階で作った Judge プロンプトのスコア)
- その他のシステム指標(安全性や基本性能を測るための指標群)
以下では、その評価関数の設計と、GEPAで生成プロンプトを最適化する流れを順に説明します。
第1段階:オンライン指標からオフライン指標を作る
この段階では、データの準備とJudgeプロンプトの学習を行います。
オンライン指標を教師データにする
Slack Botにログ収集の仕組みを仕込み、蓄積されたデータを教師として点数化します。
- 3点
- Good + Copy(実務でそのまま使われた可能性が高い)
- 2点
- Good または Copy(概ね良いが改善余地あり)
- 1点
- Bad(解決に繋がらない)
この「点数化」が、オンラインの評価をオフラインへ変換する出発点となります。
オンライン評価の点数予測方法の検討
教師データとして点数化されたオンライン指標を使って、新しい質問と回答のペアからスコアを予測する方法として、いくつかの選択肢を検討しました。
- 手動でルールを定義してLLM-as-a-Judge
- 「回答に手順が含まれていれば高評価」などのルールを人間が書く方法で、事前の言語化が難しく網羅性にも限界がある
- 教師あり学習で点数予測器を作成
- モデルの重みを直接調整する方法で、精度は高くなるが学習結果がブラックボックス化し「なぜその評価になったか」が分かりにくくなる
- DSPy(GEPA)で最適化したプロンプトによるLLM-as-a-Judge
- データから自動で学習しつつ、結果がプロンプト(テキスト)として得られるため解釈可能性を保てる
今回は「解釈可能性」を重視し、DSPyを採用しました。最適化されたプロンプトを読むことで、ユーザーが暗黙的に求めていた評価基準を発見できると考えたためです。
やりたいことはシンプルで、質問と回答のペアからスコアと評価理由を予測させることです。これを先ほど作った教師データを用いて学習させます。
GEPAを使用する場合、最適化のための「反省」を行うLLM(reflection_lm)の指定が重要になります。
import dspy from dspy import GEPA # LMの設定 dspy.configure(lm=dspy.LM("openai/gpt-5-mini")) # 教師データを準備 trainset, valset = build_dataset_from_logs() # Judge モジュールを定義 class Judge(dspy.Module): def __init__(self): self.predict = dspy.Predict("query, response -> score, rationale") def forward(self, query, response): return self.predict(query=query, response=response) # GEPA で最適化(reflection_lm には強力なモデルを指定) optimizer = GEPA( metric=lambda ex, pred, **kw: 1.0 - abs(float(pred.score) - ex.label) / 2.0, reflection_lm=dspy.LM("openai/gpt-5"), ) judge = optimizer.compile(Judge(), trainset=trainset, valset=valset)
この学習プロセスを経ることで、運用可能な Judgeプロンプト が出来上がります。 この時点で「オンライン指標の結果を、オフライン環境で再現できる」状態になります。
補足(実運用向け)
本記事では説明を簡単にするため、metric は数値スコアのみ返す例で記載しています。ただし GEPA は、テキストのフィードバックを材料に反省(reflection)して指示文を更新するため、実運用では dspy.Prediction(score=..., feedback="...") のように改善のヒントを返すと、探索の質が向上しやすくなります。
第2段階:オフライン指標を使い生成プロンプトを最適化する
第1段階で “ユーザーの実感に近い物差し” が手に入りました。 次はこれを使って、回答を生成するプロンプトを最適化します。
評価関数を多目的にする
評価関数の設計でも、選択肢がありました。
- UX 指標(Judge プロンプトのスコア)のみで最適化
- シンプルだが、ユーザー満足度だけを追求すると安全性が疎かになるリスクがある
- 多目的な評価関数(UX + 安全性など)で最適化
- 複数の制約を同時に満たす必要があり複雑だが、プロダクトとしてのバランスを保てる
現実のプロダクト改善では、第1段階で作った指標だけを高めようとすると、別の問題が発生することがあります。 例えば、ユーザーへの寄り添いを重視しすぎて安全性が疎かになったり、逆に安全性を重視しすぎて回答が保守的になりすぎたりするケースです。
そこで、第1段階で作った指標に加えて、プロダクトとして守るべきその他の必須指標(安全性や基本性能など)を組み合わせたものを、最終的な評価関数として設定しました。
DSPy(GEPA)で生成プロンプトを最適化する
本段階でも DSPy を使う理由は、複数の指標(UX スコア、安全性など)を同時に満たすプロンプトを、人間の試行錯誤だけで見つけるのが難しいためです。
DSPy を使えば、第1段階で作った Judge プロンプトを評価関数(metric)として組み込み、自動的に最適なプロンプトを探索できます。
回答生成モジュール(Chain of Thought)を定義し、先ほどの多目的な評価関数を最大化するようにGEPAで探索させます。
# 回答生成モジュールを定義 class GenerateAnswer(dspy.Module): def __init__(self): self.generate = dspy.ChainOfThought("query, context -> answer") def forward(self, query, context): return self.generate(query=query, context=context) # 評価関数:学習済みJudge + 安全性指標 def metric(example, pred, **kwargs): ux = float(judge(example.query, pred.answer).score) / 3.0 safety = compute_safety_score(pred.answer) return 0.7 * ux + 0.3 * safety # GEPA で最適化(reflection_lm には強力なモデルを指定) optimized = GEPA(metric=metric, reflection_lm=dspy.LM("openai/gpt-5")).compile( GenerateAnswer(), trainset=trainset, valset=valset )
このとき、第1段階で作ったJudgeプロンプトが評価関数に組み込まれているため、最適化されたプロンプトは、実務で使いやすい回答を生成する方向へと改善されていきます。
2段階DSPyで得た学び
この取り組みを通じて得られた最大の収穫は、単に精度が上がったことだけではありません。大きく2つあります。
評価指標が「解釈可能なプロンプト」として手に入る
評価指標が「解釈可能なプロンプト」として手に入ることが、今回GEPAのようなプロンプト最適化手法を採用して最も良かった点です。ファインチューニングなどのモデルパラメーターを最適化する手法では、学習結果は「重み(数値)」というブラックボックスの中に埋もれます。 しかし、GEPAによる最適化の成果物は「プロンプト(テキスト)」として出力されます。 最適化されたJudgeプロンプトの中身を読むことで、「断定的な言い回し」や「手順の明確さ」といった、ユーザーが潜在的に求めていた要素を人間が解釈可能な形で発見できました。 これにより、最適化プロセス自体が、ドメイン知識を獲得するための分析ツールとしても機能しました。
オンライン指標は“学習してオフライン化”すると、改善サイクルが回る
ユーザーログを直接の正解にするのではなく、それを模倣するJudgeプロンプトを作ることで、高速な実験が可能になります。 本番環境への影響なく安全に試行錯誤でき、新しいアイデアの検証コストが大幅に下がったと感じています。
まとめ
本記事では、社内問い合わせBot改善における「2段階DSPy」アプローチを紹介しました。
- 第1段階ではオンライン指標(Good/Bad/Copy)を教師データに、DSPyでJudgeプロンプトを学習し、オフライン評価器(物差し)を作る
- 第2段階ではその物差しとその他の指標を評価関数として、DSPyで生成プロンプトを最適化する
このように工程を2段階に分けることで、「速く回る実験」と「ユーザー実感とズレない評価」を両立させることができました。 今後も、こうした技術的な試行錯誤を通じて、より使いやすく信頼できるプロダクトを目指していきます。
We Are Hiring!
SmartHRでは、一緒に働く仲間を募集しています! LLMや評価指標設計、プロダクトへのAI統合に興味のある方は、ぜひカジュアルにお話ししましょう。