今日は昨今はやりのLLMプログラミングフレームワークである DSPy を紹介します。
DSPy とは
LLM(大規模言語モデル)を使ったアプリケーション開発において、自然言語で構成されるプロンプトの調整は一番曖昧さが残る部分となり、時間を消費してしまう難しい部分です。少し文言を変えただけで出力がガラッと変ったり、モデルを変更するとそれまで動いていたプロンプトが使い物にならなくなる、などの事象に悩まされます。複数のLLM呼び出しを組み合わせたパイプラインになると、その調整は指数関数的に複雑になっていきます。
DSPyは、こうした問題に対する根本的な解決策として生まれたフレームワークです。「プロンプトを書く」のではなく「プログラムを書く」ように操作でき、プロンプトの最適化はフレームワークに任せることができます。
公式サイトにはこのDPSyのコンセプトを表す一文があります。
Think of DSPy as a higher-level language for AI programming, like the shift from assembly to C.
従来のプロンプト調整を行うプロンプトエンジニアリングと言われていた作業を アセンブラ だとすると、DSPyを用いた作業は C言語のような高水準の抽象化を提供することを目的としています。開発者はタスクの構造を宣言的に記述し、実際のプロンプト生成や最適化はフレームワークに委ねることができます。
Signature, Module, Optimizer
DSPyは Signature, Module, Optimizerの3つの抽象化の柱から構成されます。
Signature(シグネチャ) は、「LLMに何を渡して、何を返してもらうか」を一行で書く宣言的な仕組みを提供します。例えば "question -> answer" と書けば「質問を渡すと回答が返ってくる」という意味になります。 "context, question -> answer" なら「参考情報と質問を渡して回答を得る」という指示を与えることになります。ここに自然言語でプロンプトの文面を書く必要はなく、「入力と出力の形だけ決めれば、あとはDSPyがやってくれる」というのが基本的な考え方になっています。
Module(モジュール) は、LLMにどんな考え方をさせるかを決める部分です。例えば dspy.Predict はシンプルに答えを出力させます。 dspy.ChainOfThought は「まず理由を考えてから答えを出す」という段階的な思考をさせることができます。 dspy.ReAct は外部ツールを使いながら推論を実行させることができます。これらの部品はレゴブロックのように組み合わせることができ、シンプルなQ&Aから複雑なRAGパイプラインまで、同じ仕組みで構築することができます。
Optimizer(オプティマイザ) は、DSPyで最も画期的な部分と言えます。やることは単純で、「正解付きのサンプルデータ」と「何をもって良い回答とするかの基準」を渡すと、フレームワークがLLMへの指示やお手本の出し方を自動的に改善してくれます。例えば BootstrapFewShot は、サンプルデータをLLMに解かせてみて、うまくいった回答例を自動的に集め、次回以降のプロンプトに「お手本」として組み込むこまれます。人間がプロンプトを試行錯誤する代わりに、この仕組みが自動で最適なプロンプトを見つけてくれるため、時間を消費したあいまいなプロンプトエンジニアリング作業が最適化されることになります。
さっそくやってみる
では今日は第一回目ということでもっともシンプルなものを作っていきます。
# 仮想環境を作成・有効化
python3 -m venv ~/dspy-env
source ~/dspy-env/bin/activate
# インストール
pip install dspy-ai openai
# APIキー設定
export OPENAI_API_KEY="sk-your-key-here"
# 実行
python3 << 'EOF'
import dspy
lm = dspy.LM("openai/gpt-4o-mini")
dspy.configure(lm=lm)
qa = dspy.ChainOfThought("question -> answer")
result = qa(question="日本で一番高い山は何ですか?")
print("推論:", result.reasoning)
print("回答:", result.answer)
EOFまずはLinux環境で上記を実行すると以下が出力されます。
推論: 日本で一番高い山は富士山で、高さは3,776メートルです。富士山は日本の象徴的な山として広く知られており、国立公園の一部でもあります。多くの観光客が訪れ、登山や美しい景色を楽しむことができます。
回答: 日本で一番高い山は富士山です。dspy.LM("openai/gpt-4o-mini") は、使用するLLMを指定しています。ここではOpenAIのGPT-4o-miniを選んでいますが、`"anthropic/claude-sonnet-4-20250514"` のようにAnthropicのモデルに差し替えることもできます。モデルを変えてもこの一行を書き換えるだけで、残りのコードはそのまま動きます。
dspy.configure(lm=lm) は、DSPy全体の設定を行っています。「以降のDSPyの処理はすべてこのLMを使ってね」という宣言を行っています。
dspy.ChainOfThought("question -> answer") がDSPyの肝です。ここでは2つのことを同時に指定しています。まず "question -> answer" というSignatureで「質問を受け取って回答を返す」という入出力の形を定義しています。そして ChainOfThought というModuleで「いきなり答えを出すのではなく、まず推論してから答える」という思考パターンを指定しています。上記の出力例であるように「ステップバイステップで考えてください」「思考内容を教えてください」のようなプロンプトを一切書かなくても、DSPyがChainOfThoughtに必要なプロンプトを内部で自動生成してくれています。
qa(question="日本で一番高い山は何ですか?")で実際にLLMを呼び出しています。Signatureで question を入力として定義したので、同じ名前のキーワード引数で質問を渡しています。
result.reasoning と result.answer で結果を取り出します。ChainOfThoughtモジュールを使っているので、最終的な回答(`answer`)だけでなく、そこに至る推論過程(`reasoning`)も自動的に返ってくる。
これは以下のプロンプトに相当します。
あなたは知識豊富なアシスタントです。
以下の質問に対して、まずステップバイステップで推論を行い、
その後に最終的な回答を提示してください。
質問: 日本で一番高い山は何ですか?
推論:
回答:例えば推論過程が不要ならシンプルにスクリプトを以下に修正します。
python3 << 'EOF'
import dspy
lm = dspy.LM("openai/gpt-4o-mini")
dspy.configure(lm=lm)
qa = dspy.Predict("question -> answer")
result = qa(question="日本で一番高い山は何ですか?")
print("回答:", result.answer)
EOF
回答: 日本で一番高い山は富士山です。標高は3,776メートルです。一方推論過程における確信度を出力させることも簡単にできます。
python3 << 'EOF'
import dspy
lm = dspy.LM("openai/gpt-4o-mini")
dspy.configure(lm=lm)
qa = dspy.ChainOfThought("question -> answer,confidence")
result = qa(question="日本で一番高い山は何ですか?")
print("推論:", result.reasoning)
print("回答:", result.answer)
print("確信度:", result.confidence)
EOFこのように自然言語の指定によって曖昧さが残り時間のかかるものであったプロンプトエンジニアリングをプログラマブルに指定できるようにするのがDSPyの神髄になります。
次回予告
次回はDSPyの特徴の一つである、 Optimizer 自動的な最適化にみていきます。

