🚀

DSPyとは何か?プロンプトを自動最適化する新しいフレームワーク

に公開

はじめに:LLM開発の「よくある悩み」

大規模言語モデル(LLM)を使ったアプリケーション開発は、多くの可能性を秘めている一方で、特有の難しさも伴います。

  • 複雑化するプロンプト: 高度なタスクを実行させようとすると、プロンプトはどんどん長く、複雑になります。コンテキストを詰め込み、Few-shotの例を追加し、出力形式を細かく指示する...。結果として、管理が難しい「魔法の呪文」のようなプロンプトが出来上がってしまいます。
  • 属人化するノウハウ: 優れたプロンプトを書くスキルは「プロンプトエンジニアリング」と呼ばれますが、そのノウハウは個人の経験や感覚に依存しがちです。チームでの開発やメンテナンスが難しい「プロンプト職人」問題に悩まされている方も多いのではないでしょうか。
  • モデルへの過剰適合: ある特定のLLM(例: GPT-5)で完璧に動作するプロンプトが、別のモデル(例: Claude 4Llama 4)ではうまく動かない、という経験はありませんか? モデルを変えるたびに、大規模なプロンプトの書き換えが必要になるのは大きな負担です。
  • 評価と改善の難しさ: プロンプトを少し変更した結果、本当に性能が向上したのかを定量的に評価するのは簡単ではありません。改善のサイクルをうまく回せず、開発が行き詰まってしまうこともあります。

もし、あなたがこれらの課題に一つでも心当たりがあるなら、DSPyはあなたのためのフレームワークかもしれません。DSPyは、これらの課題を解決し、LLMアプリケーション開発をより体系的で効率的なものに変えるための、新しいアプローチを提案します。

DSPyとは?:プロンプトを「コンパイル」する新発想

DSPyは、スタンフォード大学の研究者たちが開発した、LLMアプリケーションを構築するための新しいフレームワークです。その最大の特徴は、プロンプトを手で書くのではなく、プログラムによって自動的に最適化(コンパイル)するという点にあります。

これは、私たちがC++やJavaのような高級言語で書いたコードを、コンピュータが実行可能な機械語に「コンパイル」するのと似ています。DSPyでは、開発者はLLMに「何をしてほしいか」を宣言的に記述します。すると、DSPyの**オプティマイザ(Optimizer)**が、その指示を最も効果的に実行するためのプロンプトを自動で生成・洗練させてくれるのです。

このアプローチの中核をなすのが、以下の3つの主要コンポーネントです。

  1. Signature(シグネチャ):

    • LLMに実行させたいタスクの入力と出力の形式を定義します。
    • 例えば、「質問を入力したら、回答を出力する」といったシンプルな形式を question -> answer のように記述します。
    • DSPyは、このSignatureからタスクを実行するための基本的なプロンプトを自動で生成します。
  2. Module(モジュール):

    • Signatureを組み合わせて、より複雑な処理のパイプラインを構築するためのコンポーネントです。
    • dspy.Predict(単純な予測)や dspy.ChainOfThought(思考の連鎖を促す)など、様々な組み込みモジュールが用意されています。
    • これらのモジュールを組み合わせることで、RAG(Retrieval-Augmented Generation)のような高度なパイプラインも宣言的に記述できます。
  3. Optimizer(オプティマイザ):

    • DSPyの心臓部です。少数の教師データ(入力と正解出力のペア)を使い、モジュールが生成するプロンプトを自動で改善します。
    • 例えば、Few-shotプロンプティングのための最適な具体例を教師データから選んでプロンプトに埋め込んだり、思考の連鎖(Chain of Thought)を促すための指示を追加したりといった最適化を自動で行います。このプロセスをDSPyではTeleprompterと呼んでいます。

この仕組みにより、開発者は面倒なプロンプトエンジニアリングから解放され、アプリケーションのロジック設計と、性能を評価するためのデータ作成に集中できるようになります。

ハンズオン:DSPyで顧客からの質問に答えるFAQシステムを作る

百聞は一見に如かず。架空の写真編集クラウドサービス「Gemini Photo」のFAQシステムを構築する、という実践的なシナリオを通して、DSPyの使い方を体験してみましょう。

Step 1: 準備

まず、必要なライブラリをインストールし、LLMを設定します。

pip install dspy-ai
import dspy

# dspy.LMに必要なライブラリをインストールします
# pip install dspy-ai[openai]

lm = dspy.LM("openai/gpt-4o-mini", api_key="YOUR_OPENAI_API_KEY")
dspy.configure(lm=lm)

注意: 上記のコードでは gpt-4o-mini を使用します。YOUR_OPENAI_API_KEY をご自身のAPIキーに置き換えてください。

Step 2: Signatureの定義

次に、「顧客からの質問に、サービス情報(コンテキスト)を基に回答する」というタスクの入出力形式をSignatureとして定義します。

class CustomerSupportQA(dspy.Signature):
    """指定されたサービス情報に基づいて、顧客の質問に簡潔に回答します。"""
    service_info = dspy.InputField(desc="回答の根拠となるサービス関連の情報")
    customer_question = dspy.InputField()
    answer = dspy.OutputField(desc="顧客への最終的な回答")

Step 3: Moduleの作成と実行

このSignatureを使って、思考の連鎖(Chain of Thought)を行うモジュールを作成し、実行してみましょう。

# モジュールの定義
generate_answer = dspy.ChainOfThought(CustomerSupportQA)

# 実行するコンテキストと質問
context = "Gemini Photoには3つのプランがあります。無料プランでは1GBのストレージと基本的な編集機能が利用できます。Proプラン(月額1,500円)では、RAW現像、AIノイズ除去、無制限のクラウドストレージが利用可能です。Businessプランはチームでの利用を想定しており、料金は問い合わせが必要です。"
question = "無料プランで使える機能は何ですか?"

response = generate_answer(service_info=context, customer_question=question)

print(f"質問: {question}")
print(f"回答: {response.answer}")

この時点でも、DSPyは定義したSignatureに基づいて適切なプロンプトを生成し、LLMは質問に答えることができます。しかし、このプロンプトはまだ汎用的なものです。

Step 4: Optimizerによる"コンパイル"

ここからがDSPyの真骨頂です。実際のFAQの例をいくつか教師データとして用意し、Optimizerを使ってモジュールをコンパイルし、このタスクに特化したプロンプトを生成させます。

# 1. FAQの例から簡単な教師データを作成
trainset = [
    dspy.Example(
        service_info="Gemini PhotoはJPEG, PNG, WEBP形式に対応しています。",
        customer_question="対応しているファイル形式を教えてください。",
        answer="JPEG, PNG, WEBP形式に対応しています。"
    ).with_inputs("service_info", "customer_question"),
    dspy.Example(
        service_info="サブスクリプションの解約は、アカウント設定ページの「プラン管理」からいつでも行えます。",
        customer_question="どうすれば解約できますか?",
        answer="アカウント設定の「プラン管理」からいつでも解約できます。"
    ).with_inputs("service_info", "customer_question"),
    dspy.Example(
        service_info="Proプランでは、RAWファイルの現像、AIによるノイズ除去、無制限のクラウドストレージが利用可能です。",
        customer_question="Proプランのメリットは何ですか?",
        answer="RAW現像、AIノイズ除去、無制限ストレージが利用できる点です。"
    ).with_inputs("service_info", "customer_question"),
]

# 2. Optimizer(Teleprompter)を定義
optimizer = dspy.teleprompt.BootstrapFewShot(metric=dspy.evaluate.answer_exact_match)

# 3. モジュールをコンパイル(最適化)
compiled_qa = optimizer.compile(student=generate_answer, trainset=trainset)

# 4. 最適化されたモジュールで再度実行
response_compiled = compiled_qa(service_info=context, customer_question=question)

print(f"質問: {question}")
print(f"回答(コンパイル後): {response_compiled.answer}")

optimizer.compile()を実行すると、DSPyは裏側でtrainsetを使い、generate_answerモジュール内のプロンプトを改善します。具体的には、BootstrapFewShotオプティマイザが、LLMに対して効果的なFew-shotの具体例(この場合はFAQの例)をプロンプトに組み込んでくれるのです。

これにより、手作業でFew-shotの例を選んだり、プロンプトの言い回しを調整したりすることなく、タスクに特化した高性能なプロンプトを自動で手に入れることができました。

最適化の効果が見えない…?

上記のコードを実行してみると、コンパイル前と後で、回答が全く同じになるケースがあります。

質問: 無料プランで使える機能は何ですか?
回答: 無料プランでは、1GBのストレージと基本的な編集機能が利用できます。
...
質問: 無料プランで使える機能は何ですか?
回答(コンパイル後): 無料プランでは1GBのストレージと基本的な編集機能が利用できます。

これでは、DSPyの最適化の力が発揮されているのか分かりません。この現象は、タスクが簡単すぎるために、最適化前の基本的なプロンプトでもLLMが完璧な答えを出せてしまう場合に起こります。

質問を複雑化し、DSPyの真価を試す

DSPyの真価は、より複雑なタスクでこそ輝きます。そこで、AIに推論を要求する、より複雑な質問に変更してみましょう。

question = "Proプランと無料プランを比較して、どちらが写真好きの学生にとってコストパフォーマンスが高いか、理由と共に説明して。"

この質問で再度スクリプトを実行すると、ついにDSPyの力が姿を現します。

質問: Proプランと無料プランを比較して、どちらが写真好きの学生にとってコストパフォーマンスが高いか、理由と共に説明して。
回答: 写真好きの学生にとって、Proプランは月額1,500円で多機能なため、特にRAW現像やAIノイズ除去を利用したい場合はコストパフォーマンスが高いです。ただし、基本的な編集で十分であれば無料プランも選択肢となります。学生のニーズに応じて選ぶと良いでしょう。
...
質問: Proプランと無料プランを比較して、どちらが写真好きの学生にとってコストパフォーマンスが高いか、理由と共に説明して。
回答(コンパイル後): Proプランは月額1,500円で多機能な編集が可能で、特にRAW現像やAIノイズ除去が利用できるため、写真好きの学生にはコストパフォーマンスが高いです。無料プランは機能が限られているため、真剣に写真を楽しむならProプランをおすすめします。

コンパイル後の回答は、単なる中立的な比較に留まらず、「真剣に写真を楽しむなら」という具体的な条件を付け加え、Proプランを明確に推奨しています。よりユーザーの意図を汲んだ、質の高い回答が生成されました。

lm.inspect_history()でプロンプトを可視化

入力する質問は同じなのに、なぜ出力の質が変わるのでしょうか?その謎を解明するため、DSPyがAIに送っているプロンプト自体を可視化してみましょう。lm.inspect_history(n=1)をコードに追加することで、直前のプロンプトを確認できます。

コンパイル前のプロンプト

lm.inspect_history(n=1)で確認した、最適化前のプロンプトは以下のようになっています。

System message:

Your input fields are:
1. `service_info` (str): 回答の根拠となるサービス関連の情報
2. `customer_question` (str):
Your output fields are:
1. `reasoning` (str):
2. `answer` (str): 顧客への最終的な回答
...
User message:

[[ ## service_info ## ]]
Gemini Photoには3つのプランがあります...
[[ ## customer_question ## ]]
Proプランと無料プランを比較して...

これは非常にシンプルなゼロショットプロンプトです。AIに対して、お手本なしで回答を求めています。

コンパイル後のプロンプト

一方、コンパイル後のプロンプトには、驚くべき変化が現れます。

System message:
...
User message:

This is an example of the task, though some input or output fields are not supplied.

[[ ## service_info ## ]]
Proプランでは、RAWファイルの現像、AIによるノイズ除去、無制限のクラウドストレージが利用可能です。
[[ ## customer_question ## ]]
Proプランのメリットは何ですか?

Assistant message:

[[ ## answer ## ]]
RAW現像、AIノイズ除去、無制限ストレージが利用できる点です。

... (他の例が続く) ...

User message:

[[ ## service_info ## ]]
Gemini Photoには3つのプランがあります...
[[ ## customer_question ## ]]
Proプランと無料プランを比較して...

コンパイル後のプロンプトには、私たちが学習用に与えた3つのサンプルが「お手本」として自動的に挿入されています。これはフューショットプロンプトと呼ばれます。

AIはこれらのお手本を参考にすることで、「なるほど、今回はこのように振る舞えばいいんだな」と学習し、より質の高い回答を生成できたのです。

DSPy vs LangChain:何が違うのか?

LLMフレームワークとして有名なLangChainとDSPyは、何が違うのでしょうか?

  • アプローチの違い:

    • LangChain: 様々なツールやコンポーネントを手続き的に「繋げる」ためのライブラリです。開発者はプロンプトテンプレートやコンポーネントの呼び出し順序を明示的にコード化します。
    • DSPy: システム全体の振る舞いを宣言的に記述し、最適なプロンプトや重みを自動で生成させるフレームワークです。「どうやるか」ではなく「何をしたいか」に集中できます。
  • プロンプト管理:

    • LangChain: PromptTemplateを使い、プロンプトを手で書くのが基本です。
    • DSPy: Signatureから高品質なプロンプトが自動生成され、Optimizerによってさらに改善されます。
  • 移植性:

    • LangChain: LLMをGPT-5からClaude 4に変更する場合、プロンプトの性能が落ちることがあり、手動での調整が必要になるケースが多いです。
    • DSPy: Optimizerが新しいモデルに合わせてプロンプトを再コンパイルしてくれるため、モデルの変更に強いです。同じSignatureModuleのまま、dspy.settings.configure(lm=new_lm)でモデルを切り替え、再度compile()を実行するだけで、新しいモデルに最適化されたプロンプトを得られます。

おわりに

DSPyは、LLMアプリケーション開発におけるプロンプトエンジニアリングのあり方を根本から変える可能性を秘めた、非常に強力なフレームワークです。

宣言的なプログラミングと自動最適化(コンパイル)というアプローチにより、開発者は煩雑なプロンプト調整から解放され、アプリケーションのロジックと品質評価という、より本質的な作業に集中できるようになります。

まだ発展途上のフレームワークではありますが、RAGパイプラインの最適化やAgent開発など、その応用範囲は広がり続けています。

あなたもDSPyを使って、未来のLLM開発を体験してみませんか?

GitHubで編集を提案

Discussion