概要
弊社SaaSサービスでは、LLMを用いた要約タスクをはじめとする、プロンプトエンジニアリングを前提とした機能提供の機会が年々増えています。
一方で、プロンプトの設計や改善は依然として発展途上であり
- 「良いプロンプトとは何か」
- 「その品質をどのように評価・再現するのか」
といった点について、属人的・恣意的になりやすいという課題を抱えています。
こうした背景のもと調査を進める中で
近年では DSPy や TextGrad によるシステムプロンプトの自動生成・最適化
さらに RAGAS を用いた LLM as a Judge による品質評価 といった技術が登場し、実運用での導入事例も少しずつ増えてきていることが見えてきました。
これらのアプローチは、従来の試行錯誤に依存したプロンプト設計から一歩進み
評価可能で再現性のあるプロンプト品質管理を実現する手段として、
今後の SaaS サービスにおける AI Solution 設計において、技術選定のデファクトとなり得る可能性を秘めています。
本記事では、特に注目している DSPy、TextGrad、RAGAS の3つのライブラリについて、
プロンプト品質管理の観点から紹介していきます。
内容
SaaS サービスから外部の LLM API(例:OpenAI Chat Completion API ...etc)を利用する場合、LLM は次に続く単語や文章を予測する仕組みであるため、適切なシステムプロンプトを設計する必要があります。
このシステムプロンプトの品質によって、ユーザ入力をもとに生成される質問や指示に対する回答精度は大きく左右されます。
一方で、システムプロンプトは必然的に長文化しやすく、デザインパターンとして知られているテクニックだけでは補いきれない創意工夫が求められる場面も少なくありません。その結果、最終的にどのプロンプトを採択すべきか判断しづらいという課題が残ります。
1. プロンプトの自動生成
上記背景の中、近年注目が集まっている技術の一つがDSpyによるプロンプト自動生成です
DSpy
DSPy では、プロンプトは高度にモジュール化されており、人手でテキストを直接記述する必要性を極力排除しています。
プロンプトは各モジュールの内部で管理され、optimizer を用いて compile を実行することで、LLM を利用した候補プロンプトの生成が行われます。
生成された候補に対して、あらかじめ指定した評価関数を用いて最大化問題として最適化を行い、最終的に最適なプロンプトが内部状態として保持されます。
また、DSPy が内部に保持する状態は、load / save によって永続化することが可能です。
DSPy Saving & Loading チュートリアル
評価関数については、人手で定義する方法に加え、LLM as a Judge としてDSPy自身を用いる簡易的な例も公式ドキュメントに掲載されています。
DSPy Evaluation Metrics
(但し、DSPy で LLM as a Judge を採用する場合、LLM の呼び出し回数が多くなりやすく、コスト面については十分な注意が必要である、という指摘も見られます)
簡単な使い方としては、先ずSignature, Moduleを宣言します
class ConversationSummarizer(dspy.Signature):
"""会話を簡潔で分かりやすい要約に変換します"""
conversation: str = dspy.InputField(desc="複数人の会話テキスト")
summary: str = dspy.OutputField(desc="会話の重要なポイントを150文字以内で要約")
class ConversationSummaryModule(dspy.Module):
"""会話要約を行うモジュール"""
def __init__(self):
super().__init__()
self.summarize = dspy.ChainOfThought(ConversationSummarizer)
def forward(self, conversation: str):
result = self.summarize(conversation=conversation)
return dspy.Prediction(summary=result.summary)
課題に合わせて制定したmetric関数を用いて、Moduleをteleprompterに掛けて
最適化します。compileにより最適化されたModuleはそのまま解答生成に用いることができます
def quality_metric(example, prediction) -> float:
"""要約の品質を評価するメトリクス"""
...(省略)
return max(0.0, min(1.0, score))
lm = dspy.LM('openai/gpt-4o-mini', api_key=os.getenv("OPENAI_API_KEY"))
dspy.configure(lm=lm)
from dspy.teleprompt import MIPROv2
teleprompter = MIPROv2(
metric=quality_metric,
auto=None,
num_candidates=3,
init_temperature=1.0,
)
optimized_module = teleprompter.compile(
ConversationSummaryModule(),
trainset=train_examples,
valset=dev_examples,
num_trials=3,
max_bootstrapped_demos=1,
max_labeled_demos=1,
minibatch_size=2,
)
answer = optimized_module(conversation=test_example.conversation)
score = quality_metric(test_example, answer)
プロンプトエンジニアリングを関数的に扱えるよう、綺麗に抽象化されている印象でした
「人がプロンプトを書く」行為を可能な限り排除する、設計思想の強度を感じました
TextGrad
プロンプト最適化の手法として、近年もう一つ注目されつつあるのが TextGrad です。
TextGrad はプロンプト最適化そのものに焦点を当てたライブラリで、PyTorch に類似したインターフェースを採用しています。
プロンプトをVariableとして扱い、最終的な出力から定義される評価関数のlossに対して、バックプロパゲーションを模した テキストベースのフィードバックを生成することで、元となるプロンプトVariableを反復的に更新・最適化します。
プロンプト最適化という目的においてはDSPyと方向性は一致していますが、アプローチは対照的です。
DSPyが、複数のプロンプト候補を生成し、それらに対する評価関数を 最大化する最適化問題として扱うのに対し、TextGradは評価関数から得られるlossを 最小化するよう、LLMに文脈的な誤差(テキストによる勾配)を推定させ、それを逆伝播させることで、元のプロンプト自体を書き換えていく方式を採用しています。
import textgrad as tg
# 最適化対象となる system prompt
system_prompt = tg.Variable(
initial_system_prompt,
role_description="system prompt for the chat",
requires_grad=True,
)
# system prompt を内部で利用する LLM
model = tg.BlackboxLLM(
"gpt-4o-mini",
system_prompt=system_prompt,
)
# ユーザ質問(固定)
question_var = tg.Variable(
question,
role_description="user question",
requires_grad=False,
)
optimizer = tg.TGD(parameters=[system_prompt])
for _ in range(num_steps):
# LLM 推論
pred = model(question_var)
# TextGrad による損失定義(理想回答との差分を言語化)
loss_fn = tg.TextLoss(f"""
質問: {question}
理想の回答:
{ideal_answer}
現在の回答:
{pred.value}
現在の回答を理想の回答に近づけるための改善点を、
簡潔に箇条書きで列挙してください。
""")
# system prompt に対してテキスト勾配を逆伝播
loss = loss_fn(system_prompt)
loss.backward()
optimizer.step()
# 最適化後の system prompt と最終回答
final_answer = model(question_var)
optimized_prompt = system_prompt.value
最適化したい問題の性質にも大きく依存するため、現時点では両者の優劣を一概に判断するのは難しい、という印象です。
個人的には、特定のフレームワークへのロックインは可能な限り避けたいという考えもあり、現状では TextGrad に触れる機会の方が多くなっています。
2. プロンプトの自動評価
Production 環境で利用可能かどうかを判断するうえでは、品質統制の観点から、自動生成されたプロンプトの精度を定量的に評価することが重要です。
その代表的なライブラリの一つが RAGAS です。
RAGAS はもともと RAG(Retrieval-Augmented Generation)の評価ツールとして開発されましたが、現在ではより広く LLM の出力品質や性能評価に用いられるケースが増えている印象があります。
RAGAS では、LLM as a Judge による評価に加え、BLEU や ROUGE などの 非LLM as a Judge による評価指標も充実しており、用途に応じて複数の評価手法を組み合わせることが可能です。外部 LLM API のモデル名と API キーを指定するだけで、比較的容易に LLM as a Judge を用いた評価を実行し、出力品質に対する定量的な指標を得ることができます。
具体的には、あらかじめ用意した ground truth(質問と正解回答)に対して、
{system prompt, question} → answer
という生成結果が、各評価指標にどの程度適合しているかを測定します。これにより、プロンプトの変更や最適化が、実際の出力品質にどのような影響を与えているかを、再現性のある形で比較・検証することが可能になります。
from datasets import Dataset
from ragas.metrics import answer_similarity
from ragas import evaluate
data_samples = {
'question': ['When was the first super bowl?', 'Who won the most super bowls?'],
'answer': ['The first superbowl was held on Jan 15, 1967', 'The most super bowls have been won by The New England Patriots'],
'ground_truth': ['The first superbowl was held on January 15, 1967', 'The New England Patriots have won the Super Bowl a record six times']
}
dataset = Dataset.from_dict(data_samples)
score = evaluate(dataset,metrics=[answer_similarity])
score.to_pandas()
総括
本分野はまだ発展途上であり、実運用における有効性についても研究段階にあるため、ここでは簡潔な整理にとどめました。
個人的には、RAGAS による LLM の自動精度評価と、TextGrad による自動プロンプト最適化の組み合わせに大きな可能性を感じています。
両者を組み合わせることで、顧客価値の高いソリューションを実現するために求められる精度のハードルを継続的に超えていき、結果として、提供可能なサービスの選択肢を拡張していくことに寄与できるのではないかと期待しています。
参考
以上