Home 論文解説: DSPy — 宣言的LMパイプラインを自動コンパイルするフレームワーク
投稿
キャンセル

📄 論文解説: DSPy — 宣言的LMパイプラインを自動コンパイルするフレームワーク

論文概要(Abstract)

DSPy(Declarative Self-improving Python)は、LLMパイプラインを宣言的プログラムとして記述し、プロンプトの詳細をプログラマから分離するフレームワークである。手書きプロンプトへの依存を排除し、入出力の型仕様(Signature)・LLM操作の抽象化(Module)・自動最適化コンパイラ(Teleprompter)の三層構造により、LLM変更やドメイン変更のたびに手動でプロンプトを書き直す必要をなくす。GSM8Kで手書きプロンプト比+8%、HotPotQAで+10%の改善を実証した。

この記事は Zenn記事: LLMプロンプト管理CI/CD:Langfuse×LaunchDarklyでA/Bテストと安全ロールアウト の深掘りです。

情報源

  • arXiv ID: 2310.11511
  • URL: https://arxiv.org/abs/2310.11511
  • 著者: Omar Khattab, Arnav Singhvi, Paridhi Maheshwari, Zhiyuan Zhang et al.(Stanford University)
  • 発表年: 2023
  • 分野: cs.CL, cs.AI, cs.LG

背景と動機(Background & Motivation)

LLMアプリケーションの開発では、プロンプトエンジニアリングが事実上のプログラミングとなっている。しかし、手書きプロンプトには深刻な問題がある。

  1. LLM変更時の脆弱性: GPT-4からClaude 3.5に切り替えるだけで、最適なプロンプトが変わる
  2. パイプライン構造変更時の再作業: RAGの検索ステップを追加するだけで、下流プロンプトの全面書き直しが必要
  3. スケーラビリティの限界: チームで50個以上のプロンプトを管理する場合、誰がいつ何を変更したか追跡困難

Zenn記事で紹介したLangfuse×LaunchDarklyアーキテクチャはプロンプトの配信・監視に焦点を当てていたが、DSPyはその上流にあるプロンプト自体の生成・最適化を自動化する。Zenn記事の3層防御(Eval→Feature Flag→Observability)にDSPyを統合すれば、「プロンプト生成→評価→配信→監視」の完全自動パイプラインが実現できる。

主要な貢献(Key Contributions)

  • 宣言的パイプライン記述: プロンプトのテキストを書かずに、入出力の型・意味だけでLLMパイプラインを記述できるSignature/Module抽象を提案
  • 自動プロンプト最適化(Teleprompters): BootstrapFewShot・COPRO・MIPROなどのアルゴリズムにより、Few-Shot例・命令文・CoTステップを自動生成・最適化
  • コンパイラとしてのLLMパイプライン: ソースコード(DSPyプログラム)をメトリクスとデータから「コンパイル」し、特定タスク・特定LLMに最適化された推論パイプラインを出力する新パラダイム
  • モジュール性と再利用性: Predict・ChainOfThought・ReAct・MultiChainComparisonなど再利用可能なモジュールを提供

技術的詳細(Technical Details)

Signatureアーキテクチャ

graph LR
    A[入力フィールド\nSignature定義] --> B[DSPy Signature\n宣言的仕様]
    B --> C[DSPy Module\nPredict / CoT / ReAct]
    C --> D[Teleprompter\nコンパイラ]
    D --> E{最適化アルゴリズム}
    E --> F[BootstrapFewShot\n成功traceからFew-Shot収集]
    E --> G[COPRO\n命令文の自動生成・最適化]
    E --> H[MIPRO\n命令+Few-Shot同時最適化]
    F --> I[コンパイル済みプログラム\nJSONに保存]
    G --> I
    H --> I
    I --> J[本番推論\n追加LLM呼び出しなし]
    K[訓練データ\ntrainset] --> D
    L[評価メトリクス関数\nmetric] --> D

SignatureはLLMの呼び出しを「何をするか」の宣言として記述する。プロンプトの”How”をフレームワークに委ねる設計思想がコアにある。

1
2
3
4
5
6
7
8
9
10
# インライン記法(短縮形)
"question -> answer"
"context, question -> answer"

# クラス記法(型・説明付き)
class GenerateAnswer(dspy.Signature):
    """Answer questions with short factoid answers."""
    context = dspy.InputField(desc="may contain relevant facts")
    question = dspy.InputField()
    answer = dspy.OutputField(desc="often between 1 and 5 words")

各フィールドにはdesc(説明)、prefix(プレフィックス)、型情報を付与可能。フレームワークはSignatureから適切なプロンプトを自動生成し、Few-Shot例をインターカレートする。

ModuleシステムとPyTorchとの対比

ModuleはSignatureを受け取り、実際のLLM呼び出しロジックを実装する。PyTorchのnn.Moduleに対応する設計である。

PyTorchDSPy役割
nn.Moduledspy.Module処理ロジックの抽象化
nn.ParameterDemonstrations/Instructions最適化対象のパラメータ
torch.optim.AdamBootstrapFewShot最適化アルゴリズム
loss.backward()teleprompter.compile()パラメータ更新

組み込みモジュール一覧:

Module動作
dspy.PredictSignatureを直接LLMに渡す基本モジュール
dspy.ChainOfThought出力前に”rationale”フィールドを自動追加しCoTを誘導
dspy.ProgramOfThoughtPythonコードを生成・実行して答えを導出
dspy.ReActThought-Action-Observationループを実装するエージェント
dspy.RetrieveRAG用の検索モジュール

Teleprompter(コンパイラ)のアルゴリズム

TeleprompterはDSPyの「コンパイラ」で、トレーニングデータ+メトリクス関数を受け取り、プログラムのパラメータ(Few-Shotデモ・命令文・CoTプレフィックス)を最適化する。

BootstrapFewShot アルゴリズム

\[\text{D}_p = \{ (x_i^{(p)}, y_i^{(p)}, r_i^{(p)}) \mid M(x_i, \hat{y}_i) = \text{True} \}\]

ここで、

  • $x_i^{(p)}$: モジュール$p$の入力
  • $y_i^{(p)}$: モジュール$p$の出力
  • $r_i^{(p)}$: モジュール$p$のrationale(中間推論)
  • $M$: メトリクス関数(正解判定)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
def bootstrap_few_shot(program, trainset, metric, max_demos=4):
    """BootstrapFewShot: 成功traceからFew-Shot例を自動収集

    Args:
        program: DSPyプログラム(Module)
        trainset: 学習データ(dspy.Exampleのリスト)
        metric: 評価メトリクス関数
        max_demos: 各モジュールの最大デモ数

    Returns:
        コンパイル済みプログラム
    """
    demonstrations = defaultdict(list)

    for example in trainset:
        # traceを記録しながらプログラムを実行
        trace = execute_with_trace(program, example)

        # メトリクスを満たした場合のみ、中間ステップもデモとして収集
        if metric(example, trace.output):
            for module_name, module_trace in trace.items():
                demonstrations[module_name].append({
                    "input": module_trace.input,
                    "output": module_trace.output,
                    "rationale": module_trace.rationale
                })

    # 各モジュールにデモを設定
    for module_name, module in program.named_modules():
        module.demonstrations = sample(demonstrations[module_name], max_demos)

    return program

核心的なポイント: 教師プログラムが正解した例の中間ステップ(rationale・検索クエリ等)も含めてデモとして収集する。アノテーターが知らない中間出力についても、Few-Shot例が自動生成される。

COPRO(Collaborative Prompt Optimization)

命令文(instruction)を自動生成・最適化するグラジエントフリー手法。過去の命令文とスコアのペアを参照しながら、LLMが新しい命令候補を反復生成する。

\[\text{inst}^* = \arg\max_{\text{inst} \in \mathcal{I}} \frac{1}{|T|} \sum_{(x,y) \in T} M(y, P_{\text{inst}}(x))\]

ここで、

  • $\mathcal{I}$: 命令文の探索空間
  • $T$: 訓練セット
  • $P_{\text{inst}}$: 命令文instを使ったプログラム

実装のポイント(Implementation)

CI/CD統合パターン

DSPyの最大の実務的価値は、コンパイル済みプログラムをJSONで保存・ロードし、GitでバージョニングしてCI/CDに組み込める点にある。

1
2
3
4
5
6
7
# コンパイル済みプログラムの保存(Gitリポジトリに含める)
compiled_rag = teleprompter.compile(RAG(), trainset=trainset)
compiled_rag.save("prompts/compiled/rag_v2.3.json")

# 本番環境での読み込み
rag = RAG()
rag.load("prompts/compiled/rag_v2.3.json")

Zenn記事のPromptfoo品質ゲートとの統合:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# .github/workflows/dspy-compile.yml
name: "DSPy Prompt Compilation"
on:
  push:
    paths: ["prompts/signatures/**", "dspy_modules/**"]
jobs:
  compile:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: pip install dspy-ai
      - run: python scripts/compile_and_evaluate.py
      - run: |
          # コンパイル結果をコミット
          git add prompts/compiled/
          git commit -m "chore: recompile DSPy prompts"

LLM切り替え時の再コンパイル

1
2
3
4
# GPT-4oで最適化済みのプログラムをClaude 3.5用に再コンパイル
dspy.settings.configure(lm=dspy.Anthropic(model="claude-3-5-sonnet"))
recompiled = teleprompter.compile(rag, trainset=trainset)
recompiled.save("prompts/compiled/rag_claude35.json")

ハイパーパラメータのコスト管理

パラメータ推奨値コストへの影響
max_bootstrapped_demos4-8デモ数が多いほどコンテキスト長増加
num_threads4-8並列評価数。APIレート制限に注意
max_labeled_demos4ラベル付きデータからの直接サンプリング数

実験結果(Results)

データセット手法LM精度
GSM8KZero-shot CoT(ベースライン)GPT-487.1%
GSM8KDSPy BootstrapFewShotGPT-489.5%
GSM8KDSPy MIPROGPT-490.2%
GSM8K手動プロンプトLlama-2 7B11.8%
GSM8KDSPy BootstrapFewShotLlama-2 7B49.3%
HotPotQA手動プロンプト+ColBERTv2 RAGGPT-3.5~35%
HotPotQADSPy MultiHop+BootstrapFewShotGPT-3.5~45%

注目すべき結果: 小型LM(Llama-2 7B)での改善幅が特に顕著で、手動プロンプト比で+37.5ポイント(11.8%→49.3%)の改善を達成。BootstrapFineTuningと組み合わせることで72.5%まで到達し、GPT-3.5-turboの手動プロンプトを上回るケースを実証した。

LangChain等との比較: LangChainはLLMを変更するとプロンプトの書き直しが必要だが、DSPyではdspy.settings.configure(lm=new_lm)で切り替え後に再コンパイルするだけで自動最適化される。

実運用への応用(Practical Applications)

Langfuse×DSPyの統合

Zenn記事で紹介したLangfuseとの統合は以下の形で実現できる:

  1. Langfuseトレーシング→DSPy評価データ: Langfuseに蓄積されたトレースとユーザー評価をDSPyのtrainsetとして活用
  2. DSPyコンパイル→Langfuseラベル管理: コンパイル済みプロンプトをLangfuseのprod-a/prod-bラベルとしてデプロイしA/Bテスト
  3. LaunchDarkly段階ロールアウト: A/Bテストで有意差が確認されたプロンプトをLaunchDarkly Feature Flagで100%ロールアウト

プロダクション視点

  • コンパイルコスト: trainset 100例×パイプライン深さ3で約300回のLLM呼び出し。GPT-4oで約$5-10/回のコンパイル
  • 推論時コスト: コンパイル済みJSONを読み込むだけでLLM追加呼び出しなし
  • スケーリング: コンパイルをCI/CDの一部として週1実行する運用が実用的

関連研究(Related Work)

  • OPRO (Yang et al., 2023): LLMをメタプロンプトベースの最適化器として使用。DSPyはパイプライン全体をコンパイルするためより広範
  • TextGrad (Yuksekgonul et al., 2024): テキスト勾配による自動微分。DSPyがラベルデータを使うのに対し、TextGradはゼロショット
  • APE (Zhou et al., 2022): 自動プロンプトエンジニアリング。DSPyは命令文だけでなくFew-Shot例・CoTステップも同時最適化

まとめと今後の展望

DSPyは「プロンプトエンジニアリング」を「プロンプトコンパイル」へと転換する重要な研究である。Zenn記事で紹介したLangfuse×LaunchDarklyの配信・監視基盤とDSPyの自動最適化を組み合わせることで、「生成→評価→配信→監視→再最適化」の完全なフィードバックループが実現できる。

今後の課題は、コンパイルコストの削減(MIPROv2のベイズ最適化でAPIコスト70%削減を達成)と、マルチモーダルパイプラインへの対応である。

参考文献

この投稿は CC BY 4.0 でライセンスされています。

技術ブログ解説: NVIDIA Dynamo — MoE推論のための分散サービングフレームワーク

論文解説: Judging LLM-as-a-Judge with MT-Bench and Chatbot Arena