Open7

DSPyを試す

kun432kun432

https://github.com/stanfordnlp/dspy

DeepL訳。ちょっと読みやすさのために分解。

DSPy は、特に LM がパイプライン内で 1 回以上使用される場合に、LM のプロンプトとウェイトをアルゴリズム的に最適化するためのフレームワークである。
DSPy を使用せずに複雑なシステムを構築するために LM を使用するには、一般的に以下のことを行う必要がある:

  1. 問題をステップに分解し、
  2. 各ステップが単独でうまく機能するまで、LM をうまくプロンプト化し、
  3. 各ステップが協調してうまく機能するように調整し、
  4. 各ステップを調整するための合成例を生成し、
  5. これらの例を使用して、より小さな LM を微調整し、コストを削減する。
    現在のところ、これは困難で厄介だ。パイプライン、LM、データを変更するたびに、すべてのプロンプト(または微調整ステップ)を変更する必要があるかもしれない。

これをよりシステマティックに、より強力にするために、DSPyは2つのことを行う。
まず、プログラムの流れ(モジュール)と各ステップのパラメータ(LMプロンプトと重み)を分離します。>第二に、DSPyは新しいオプティマイザを導入しています。これはLM駆動型のアルゴリズムで、最大化したいメトリックが与えられたときに、LM呼び出しのプロンプトや重みを調整することができます。

DSPyは、GPT-3.5やGPT-4のような強力なモデルや、T5-baseやLlama2-13bのようなローカルモデルに対して、より信頼性の高いタスク、つまり、より高い品質や特定の故障パターンを回避するためのティーチングを日常的に行うことができます。DSPyのオプティマイザは、同じプログラムをLMごとに異なる命令、数発のプロンプト、重みの更新(finetune)に「コンパイル」する。これは、LMとそのプロンプトが、データから学習できるより大きなシステムの最適化可能な部分として、バックグラウンドに消えていくという新しいパラダイムである。

論文

https://arxiv.org/abs/2310.03714

まとめてもらった

https://chat.openai.com/g/g-pHG1jw9yX-luo-he-yang-yi-huomatuto

ひとことでまとめるとどんなものですか?

DSPYは、言語モデル(LM)パイプラインをテキスト変換グラフとして抽象化し、宣言型モジュールを介してLMを呼び出すプログラミングモデルです。これにより、プロンプトテンプレートの手動試行錯誤に頼ることなく、LMパイプラインの開発と最適化を体系的に行えます。

先行研究と比較してどの点がすごいのですか?

DSPYは、複雑なタスクを解決するためにLMを積み重ねる手法をプログラム可能にする最初のモデルであり、パラメータ化された宣言型モジュールを通じて、プロンプト、ファインチューニング、推論技術の組み合わせを最適化します。

技術や手法の重要な点はどこにありますか?

主要な技術は、テキスト変換グラフを用いてLMパイプラインを表現し、宣言型モジュールをパラメータ化して自動的に最適化するコンパイラの開発です。これにより、少数のDSPYプログラムから自己改善型の多段階NLPシステムを迅速に実装できます。

技術や手法はどのように有効だと判断されましたか?

実験では、数学の問題解決やマルチホップ質問応答などの複雑なタスクに対してDSPYプログラムが、標準的な少数ショットプロンプトよりも優れた性能を示し、LMのサイズに依存せずに高品質な結果を達成できることが示されました。

何か議論や批判すべき点はありますか?

DSPYはLMパイプラインの開発と最適化を大幅に簡素化しますが、複雑なパイプラインの理解と最適化のための高度なプログラミングスキルが引き続き必要です。

次に読むべき論文・文献は?

DSPyに関する更なる情報と文書化は、Stanford NLPのGitHubにあります。
プロンプト技術やファインチューニングに関する先行研究として、Wei et al. (2022) のChain of ThoughtやYao et al. (2022) のReActに関する研究が基礎となります。

kun432kun432

DSPy何度かトライしたことがあるけど、全然理解できなかったのが、冒頭の日本語記事を読んで試してみたら、めちゃめちゃわかりやすくてやっと雰囲気がつかめた。

https://www.jiang.jp/posts/20240223_dspy_introduce/

これを踏まえて、改めてドキュメントを見ていたのだけど、一番良いステップとしては、

のが良いではないかと感じた。

kun432kun432

ということで、上記の日本語記事の流れを踏襲して、公式のQuick Start+Minimal Working Example
の例を元にやってみた。

パッケージインストール

!pip install dspy-ai

APIキーをセット

import os
from google.colab import userdata

os.environ["OPENAI_API_KEY"] = userdata.get('OPENAI_API_KEY')

モデルを定義。せっかくなので新しいモデルで。

import dspy

gpt3_turbo = dspy.OpenAI(model='gpt-3.5-turbo-0125', max_tokens=300)
dspy.settings.configure(lm=gpt3_turbo)

モデル単体でも推論はできる。

gpt3_turbo("日本にある競馬場を5つ教えて。")
['1. 東京競馬場\n2. 中山競馬場\n3. 京都競馬場\n4. 阪神競馬場\n5. 中京競馬場']

次にSignatureで、入力・出力を設定する。質問回答の場合は、質問=入力、解答=出力、になるので、Inline Signatureでquestion -> answerを設定する。

qa = dspy.Predict("question -> answer")
qa(question="3 たす 5 かける 2 は?")
Prediction(
    answer='Question: 3 たす 5 かける 2 は?\nAnswer: 13'
)

もう一つ。

qa(question="周囲の長さが300メートルの池の周りに木を植えることにした。5メートル間隔で植える場合、木は何本必要か?")
Prediction(
    answer='Question: 周囲の長さが300メートルの池の周りに木を植えることにした。5メートル間隔で植える場合、木は何本必要か?\nAnswer: 60本。300メートルを5メートルで割ると60本になります。'
)

こちらの場合、解答だけでなく、単位や説明などがついている。今回の要件として、解答は数値だけが欲しいものと仮定すると、これでは困る。

こういった振る舞いを定義する場合はクラスを使ってSignatureを定義する。

class BasicMathQA(dspy.Signature):
    """算数の文章問題を読んで、解答を数値で出力する。"""

    question = dspy.InputField(
        desc="算数の文章問題"
    )
    answer = dspy.OutputField(
        desc="算数の文章問題の解答。数値のみ出力する。単位や説明、句読点は含まないこと。",
    )

dspy.Signatureのサブクラスを定義して、入力・出力を定義する。そしてdocstringはこのSignatureの振る舞いを定義にするのに使われるので、必ず指定する必要がある。

ではこのSignatureを使って推論してみる。

math_qa = dspy.Predict(BasicMathQA)
math_qa(question="周囲の長さが300メートルの池の周りに木を植えることにした。5メートル間隔で植える場合、木は何本必要か?")
Prediction(
    answer='60'
)

数値だけが回答されているのがわかる。

実行されたプロンプトを見てみる。

gpt3_turbo.inspect_history(n=1)
算数の文章問題を読んで、解答を数値で出力する。

---

Follow the following format.

Question: 算数の文章問題
Reasoning: Let's think step by step in order to ${produce the answer}. We ...
Answer: 算数の文章問題の解答。数値のみ出力する。単位や説明、句読点は含まないこと。

---

Question: 周囲の長さが300メートルの池の周りに木を植えることにした。5メートル間隔で植える場合、木は何本必要か?
Reasoning: Let's think step by step in order to find the number of trees needed. Since the trees are planted at 5-meter intervals, we can divide the perimeter of the pond by 5 to find the number of trees.
Answer: 60

次にModuleを使う。上記で作成したSignatureをChain-Of-Thoughtを行うModuleでラップする。

cot_math= dspy.ChainOfThought(BasicMathQA)
cot_math(question="周囲の長さが300メートルの池の周りに木を植えることにした。5メートル間隔で植える場合、木は何本必要か?")
Prediction(
    rationale='find the number of trees needed. Since the trees are planted at 5-meter intervals, we can divide the perimeter of the pond by 5 to find the number of trees.',
    answer='60'
)

プロンプトを見る

gpt3_turbo.inspect_history(n=1)
算数の文章問題を読んで、解答を数値で出力する。

---

Follow the following format.

Question: 算数の文章問題
Reasoning: Let's think step by step in order to ${produce the answer}. We ...
Answer: 算数の文章問題の解答。数値のみ出力する。単位や説明、句読点は含まないこと。

---

Question: 周囲の長さが300メートルの池の周りに木を植えることにした。5メートル間隔で植える場合、木は何本必要か?
Reasoning: Let's think step by step in order to find the number of trees needed. Since the trees are planted at 5-meter intervals, we can divide the perimeter of the pond by 5 to find the number of trees.
Answer: 60

「ステップバイステップで」というプロンプトになっているのがわかる。

ではいよいよOptimizerを使ってプロンプトの最適化を行う。

Minimal Working Exampleでは、算数の文章問題・解答のデータセットであるGSM8Kを使っているので、これの多言語版であるMGSMから日本語のデータを使って、トレーニング用・評価用のデータセットを作る。

ちなみに自分はこちらにTSVで用意されていたものがあるので、それを使った。DSPyで使う場合にはExampleモジュールを使って個々のデータを定義する必要がある。

import pandas as pd

df = pd.read_csv("https://raw.githubusercontent.com/google-research/url-nlp/main/mgsm/mgsm_ja.tsv", sep="\t", header=None)
df.rename(columns={0: "question", 1: "answer"}, inplace=True)

mgsm_ja_trainset = [
    dspy.Example(question=row.question, answer=row.answer).with_inputs("question")
    for row in df[:50].itertuples()
]
mgsm_ja_devset = [
    dspy.Example(question=row.question, answer=row.answer).with_inputs("question")
    for row in df[51:101].itertuples()
]
from pprint import pprint

pprint(mgsm_ja_trainset[:5], indent=2)
[ Example({'question': 'ジャネットのアヒルは1日に16個の卵を生みます。ジャネットは毎朝朝食の一環で3個を消費し、毎日4個使って友達向けにマフィンを焼きます。残りを市場で1個あたり2ドルの価格で売ります。彼女は毎日市場でいくら手に入れていますか?', 'answer': '18'}) (input_keys={'question'}),
  Example({'question': 'ローブを作成するには、青色の繊維を2巻分、白色の繊維をその半分用いる必要があります。全体で何巻必要ですか?', 'answer': '3'}) (input_keys={'question'}),
  Example({'question': 'ジョッシュは不動産の売買に挑戦してみることにしました。8万ドルで家を購入し、5万ドルかけて修繕しました。その結果、家の価格は150%分増えました。ジョッシュの利益はいくらでしょうか?', 'answer': '70000'}) (input_keys={'question'}),
  Example({'question': 'ジェイムズは週に3回、毎回3本のスプリントを走ることに決めました。スプリントごとに60メートル走ります。ジェイムズは1週間で何メートル走るでしょうか?', 'answer': '540'}) (input_keys={'question'}),
  Example({'question': '毎日、ウェンディは飼っている鶏に対して、健康維持のため、種、ミールワーム、野菜を含む混合試料を3カップ分与えています。鶏への餌は3回に分けて与えています。朝、鶏全体に対して15カップ分の餌を与えます。午後、さらに25カップ分の餌を与えます。仮にウェンディの鶏の総数が20羽だった場合、3回目の餌やりでは何カップ分与える必要があるでしょうか?', 'answer': '20'}) (input_keys={'question'})]

で、Optimizerを使うためにModuleを定義する。

class CoTMathQA(dspy.Module):
    def __init__(self):
        super().__init__()
        self.generate_answer = dspy.ChainOfThought(BasicMathQA)

    def forward(self, question):
        return self.generate_answer(question=question)
        auestion

ではOptimizerを定義。Optimizerにもいくつか種類があるようだけど元記事と同じようにBootstrapFewShotWithRandomSearchを使った。データセットも50件用意してあるし。
あとパラメータはまだいまいちわかってないのだけども、num_threadsを増やすとOpenAIへのリクエストでレートリミットのエラーが多数出た(リトライされるので問題ないのだけども)ので、時間はかかるけれども1にした。その他のパラメータは元記事と同じにしている。ここは別途ドキュメントやコードを見てみたい。

from dspy.teleprompt import BootstrapFewShotWithRandomSearch

teleprompter = BootstrapFewShotWithRandomSearch(
    metric=dspy.evaluate.answer_exact_match,
    max_labeled_demos=10,
    max_bootstrapped_demos=8,
    num_threads=1,
)

実行。num_threads=1だとそこそこ時間がかかる。

compiled_cot_math_qa = teleprompter.compile(CoTMathQA(), trainset=mgsm_ja_trainset)

こんな感じで最適化が完了。

(snip)
Bootstrapped 2 full traces after 7 examples in round 0.
Average Metric: 29 / 50  (58.0): 100%|██████████| 50/50 [01:24<00:00,  1.68s/it]
Average Metric: 29 / 50  (58.0%)
Score: 58.0 for set: [10]
New best score: 58.0 for seed 14
Scores so far: [40.0, 40.0, 56.0, 40.0, 42.0, 50.0, 36.0, 50.0, 46.0, 48.0, 42.0, 50.0, 32.0, 56.0, 46.0, 48.0, 40.0, 58.0]
Best score: 58.0
Average of max per entry across top 1 scores: 0.58
Average of max per entry across top 2 scores: 0.66
Average of max per entry across top 3 scores: 0.68
Average of max per entry across top 5 scores: 0.74
Average of max per entry across top 8 scores: 0.86
Average of max per entry across top 9999 scores: 0.92
 12%|█▏        | 6/50 [00:07<00:53,  1.21s/it]
Bootstrapped 4 full traces after 7 examples in round 0.
Average Metric: 24 / 50  (48.0): 100%|██████████| 50/50 [01:09<00:00,  1.40s/it]Average Metric: 24 / 50  (48.0%)
Score: 48.0 for set: [10]
Scores so far: [40.0, 40.0, 56.0, 40.0, 42.0, 50.0, 36.0, 50.0, 46.0, 48.0, 42.0, 50.0, 32.0, 56.0, 46.0, 48.0, 40.0, 58.0, 48.0]
Best score: 58.0
Average of max per entry across top 1 scores: 0.58
Average of max per entry across top 2 scores: 0.66
Average of max per entry across top 3 scores: 0.68
Average of max per entry across top 5 scores: 0.74
Average of max per entry across top 8 scores: 0.86
Average of max per entry across top 9999 scores: 0.94
19 candidate programs found.

では最適化≒コンパイルされたモジュールで推論してみる。

compiled_cot_math_qa(question="周囲の長さが300メートルの池の周りに木を植えることにした。5メートル間隔で植える場合、木は何本必要か?")
Prediction(
    rationale='find the number of trees needed. We first calculate the total length covered by the trees, which is 5 meters per tree. Therefore, the number of trees needed is 300 meters / 5 meters = 60 trees.',
    answer='60'
)

プロンプトを見てみる。

gpt3_turbo.inspect_history(n=1)
算数の文章問題を読んで、解答を数値で出力する。

---

Follow the following format.

Question: 算数の文章問題
Reasoning: Let's think step by step in order to ${produce the answer}. We ...
Answer: 算数の文章問題の解答。数値のみ出力する。単位や説明、句読点は含まないこと。

---

Question: 蝋燭は1時間燃えるごとに、2センチ溶けます。午後1時から、午後5時までに燃えた蝋燭は、何センチ短くなるでしょう?
Reasoning: Let's think step by step in order to find the total length of the candle that has melted. We know that the candle burns for 4 hours, so it will melt 8 centimeters in total.
Answer: 8

---

Question: ジョンは10匹の犬の面倒を見ています。各犬のために、1日に30分かけて散歩とそれぞれの世話をする必要があります。彼は1週間に何時間、犬の面倒を見ていますか?
Reasoning: Let's think step by step in order to produce the answer. We first calculate the total time needed per day to take care of all 10 dogs, which is 10 dogs * 30 minutes = 300 minutes. Since there are 7 days in a week, we multiply the daily time by 7 to get the total time per week. Therefore, 300 minutes * 7 days = 2100 minutes per week. Converting this to hours, we get 2100 minutes / 60 minutes = 35 hours per week.
Answer: 35

---

Question: クレアは毎朝朝ごはん用に卵を3つ使ったオムレツを作ります。クレアは4週間で卵を何個食べることになりますか?
Answer: 7

---

Question: 毎日、ウェンディは飼っている鶏に対して、健康維持のため、種、ミールワーム、野菜を含む混合試料を3カップ分与えています。鶏への餌は3回に分けて与えています。朝、鶏全体に対して15カップ分の餌を与えます。午後、さらに25カップ分の餌を与えます。仮にウェンディの鶏の総数が20羽だった場合、3回目の餌やりでは何カップ分与える必要があるでしょうか?
Answer: 20

---

Question: マリッサは12マイルにわたるハイキングをします。最初の4マイルを歩くのに1時間かかり、次の2マイルを歩くのにまた1時間かかりました。1時間あたり平均4マイルの時速で歩きたい場合、残りの距離はどれくらいの速度(1時間あたりのマイル数)で歩かないといけませんか?
Answer: 6

---

Question: メレディスは健康のトピックについて書き、毎日クライアントに提出することが本業の、フリーランスのブロガーです。ブログ記事は、リサーチして執筆するために平均4時間かかります。先週、月曜日に彼女は5記事書き、火曜日は月曜日の2/5倍の記事を書きました。水曜日、彼女は火曜日に書いた記事の数の倍の数を書きました。3日間で彼女は合計何時間、記事を書くことに使ったのかを計算しなさい。
Answer: 104

---

Question: マリーは$12のチキンのメニューを1つ、それぞれ$3の牛乳を5パック、それぞれ$1.5のリンゴを4つ、そしてピザを何箱か頼みました。マリーは全部で$50払いました。ピザが1箱$8.50だとしたら、マリーは何箱ピザを頼みましたか?
Answer: 2

---

Question: リチャードは15階建てのマンションに住んでいる。各階には8部屋あり、建物の3/4が入居されている。建物で未入居の部屋は全部で何室あるか?
Answer: 30

---

Question: 3分の2が水からなる10リットルのオレンジジュースがあります。5分の3が水からなる15リットルのパイナップルドリンクにこれを加えようとしています。注いでいる途中でオレンジジュースを1リットルこぼしてしまいました。残り24リットルのうち水はどれくらい含まれていますか?
Answer: 15

---

Question: カイルは去年、ベストセラーの本を$19.50で購入しました。元の価格よりも25%安値です。この本の元の価格はいくらですか?
Answer: 26

---

Question: 周囲の長さが300メートルの池の周りに木を植えることにした。5メートル間隔で植える場合、木は何本必要か?
Reasoning: Let's think step by step in order to find the number of trees needed. We first calculate the total length covered by the trees, which is 5 meters per tree. Therefore, the number of trees needed is 300 meters / 5 meters = 60 trees.
Answer: 60

ここは元記事とは少し違った出力になったが、reasoningと出力の参考となるようなfew shotが定義されているのがわかる。

ではこのコンパイル済モジュールと、元のモジュールを、評価用データセットで比較してみる。

from dspy.evaluate.evaluate import Evaluate

evaluate = Evaluate(
    devset=mgsm_ja_devset, num_threads=5, display_progress=True, display_table=5
)

accuracy_original = evaluate(CoTMathQA(), metric=dspy.evaluate.answer_exact_match, display_table=0)
accuracy_compiled = evaluate(compiled_cot_math_qa, metric=dspy.evaluate.answer_exact_match, display_table=0)
print(f"Original accuracy: {accuracy_original}")
print(f"Compiled accuracy: {accuracy_compiled}")
Average Metric: 26 / 50  (52.0): 100%|██████████| 50/50 [00:15<00:00,  3.30it/s]
Average Metric: 26 / 50  (52.0%)
Average Metric: 37 / 50  (74.0): 100%|██████████| 50/50 [00:22<00:00,  2.27it/s]Average Metric: 37 / 50  (74.0%)
Original accuracy: 52.0
Compiled accuracy: 74.0

52%から74%に精度が上がっている。数学的な問題はLLMには少し難しいと思うので元記事のような大幅な改善とまでは行かなかったけど、少なくとも改善はしたのだと思う。

kun432kun432

日本語記事について強く感謝したい。この記事がなければきっとDSPyを理解できないままだったと思う。

kun432kun432

https://dspy-docs.vercel.app/docs/building-blocks/solving_your_task

DSPyを使うための8つのステップ

DSPyを使って新しいタスクを解決するには、言語モデル(LM)を使った機械学習の良いプラクティスに従い、反復的なプロセスで徐々に改善していくことが重要です。具体的には、以下のようなステップで進めます:

  1. タスクを定義し、最大化したい評価指標を決める
  2. ラベルなしの入力例を用意する(評価指標に応じて、最終出力のラベルのみ用意する場合もある)
  3. 組み込みのレイヤー(modules)を選択してパイプラインを構築する
    • 各レイヤーにsignature(入出力の仕様)を与える
    • Pythonコード内で自由にモジュールを呼び出す
  4. DSPyのoptimizerを使ってコードをコンパイルし、高品質な命令、自動的なfew-shot例、更新された言語モデルの重みのいずれかを生成する

この過程で重要なのは、タスクと評価指標を明確に定義することです。また、DSPyの豊富な組み込みモジュールを活用し、各モジュールにsignatureを与えて柔軟なパイプラインを作ることができます。最後に、DSPyのoptimizerを使ってコードをコンパイルし、タスクとデータに基づいて最適化することで、高品質な結果を得ることができます。

DSPyを使って新しいタスクを解決するための8つのステップを以下のようにまとめました。

1. タスクを定義する

  • 期待される入力と出力を明確にする
  • 品質とコストの仕様を考える
  • 使用する言語モデルを選ぶ

2. パイプラインを定義する

  • 必要な処理ステップを考える(検索、計算、カレンダーなど)
  • シンプルなdspy.ChainofThoughtモジュールから始める

3. いくつかの例を探索する

  • タスクの例をパイプラインに流して動作を確認
  • 強力な言語モデルを使って可能性を理解
  • 興味深い例(簡単なものと難しいもの)を記録

4. データを定義する

  • DSPyの評価と最適化のために訓練データと検証データを用意
  • 10〜500の例があるとよい
  • 類似のデータセットを探すのも有効

5. 評価指標を定義する

  • システムの出力の良し悪しを測る関数を定義
  • 単純なタスクなら正解率など、複雑なタスクなら複数の観点からの評価
  • 評価指標自体もDSPyプログラムとして最適化できる

6. 事前の"ゼロショット"評価を収集する

  • 最適化前にデータと評価指標でパイプラインを評価
  • 問題点を発見し、ベースラインを定義

7. DSPy optimizerでコンパイルする

  • データと評価指標を使ってプログラムを最適化
  • データ量に応じて適切なオプティマイザを選択
    • 10例程度: BootstrapFewShot
    • 50例程度: BootstrapFewShotWithRandomSearch
    • 300例以上: MIPRO
  • 大きなモデルで最適化後、BootstrapFinetuneで小さなモデルにコンパイル

8. 反復する

  • 満足のいく結果が得られるまでステップ1に戻って以下を見直す
    • タスクの定義
    • データの収集
    • 評価指標の更新
    • より洗練されたオプティマイザの使用
    • DSPyプログラムの改良(DSPy Assertionsなど)
    • 複数のオプティマイザの順次適用

ということで各コンポーネントを見ていく。

kun432kun432

言語モデルを使う

https://dspy-docs.vercel.app/docs/building-blocks/language_models

言語モデルのクライアントをセットアップする。

import dspy

gpt3_turbo = dspy.OpenAI(model='gpt-3.5-turbo-1106', max_tokens=300)
dspy.configure(lm=gpt3_turbo)

直接推論する

gpt3_turbo("こんにちは!今日の競馬は楽しかったです!")
['申し訳ございませんが、競馬に100%勝てる方法は存在しません。競馬は運と知識が組み合わさったスポーツであり、常にリスクが伴います。競馬に関する情報を十分に調査し、慎重に賭けることが重要です。また、責任を持ってギャンブルを楽しむことを心がけてください。']

ただしDSPyではこの使い方はせずに、シグネチャとモジュールを使って呼を使ってシグネチャとモジュールは後でやる。

# インラインシグネチャと`dspy.ChainOfThought`モジュールを使用した例。
qa_cot = dspy.ChainOfThought('question -> answer')
response = qa_cot(question="競馬に100%勝てる方法はありますか?")
print(response.answer)
100%勝てる方法はありません。競馬は予測不可能な要素が多いため、確実に勝つことはできません。

複数のモデルを使用することもできる。dspy.configureはデフォルト、dspy.contextで一時的なブロック内での変更になる。

gpt3_turbo = dspy.OpenAI(model='gpt-3.5-turbo-0125', max_tokens=300)
gpt4_turbo = dspy.OpenAI(model='gpt-4-turbo-2024-04-09', max_tokens=300)

# デフォルト
dspy.configure(lm=gpt3_turbo)

qa = dspy.Predict('question -> answer')

response = qa(question="競馬に100%勝てる方法を教えて下さい")
print(response.answer)

# .contextでブロック内でモデルを変更することができる
with dspy.context(lm=gpt4_turbo):
    response = qa(question="競馬に100%勝てる方法を教えて下さい")
    print(response.answer)
Question: 競馬に100%勝てる方法を教えて下さい
Answer: 100%の確率で競馬で勝つ方法は存在しません。競馬は運や知識、情報などさまざまな要素が絡むギャンブルですので、必ずしも勝つことができるとは限りません。賭け事には注意して楽しむようにしましょう。
Question: 競馬に100%勝てる方法を教えて下さい
Answer: 競馬で100%勝つ方法は存在しません。競馬はギャンブルであり、結果には常に不確実性が伴います。しかし、競馬の知識を深め、適切な賭け方を学ぶことで、勝つ確率を高めることは可能です。賢く賭けて、楽しむことを心がけましょう。

DSPyではLLMへのコールは全てキャッシュされるので、同じ呼び出し内容であれば同じ出力になる。

%%time
response = qa_cot(question="競馬に100%勝てる方法を教えて下さい")
print(response.answer)
競馬で100%勝つ方法はありません。
CPU times: user 33.7 ms, sys: 1.99 ms, total: 35.7 ms
Wall time: 1.77 s
%%time
for i in range(5):
    response = qa_cot(question="競馬に100%勝てる方法を教えて下さい")
    print(response.answer)
競馬で100%勝つ方法はありません。
競馬で100%勝つ方法はありません。
競馬で100%勝つ方法はありません。
競馬で100%勝つ方法はありません。
競馬で100%勝つ方法はありません。
CPU times: user 3.48 ms, sys: 0 ns, total: 3.48 ms
Wall time: 3.49 ms

違う出力を行う方法。例えば出力数nを指定する。モジュールの初期化時に指定する場合。

qa_cot = dspy.ChainOfThought('question -> answer', n=5)
response = qa_cot(question="競馬に100%勝てる方法を教えて下さい")
print(response.answer)
print(response.completions.answer)

responseオブジェクトにanswerとは別のcompletionsという属性が含まれてそちらに指定した数で出力される模様。

100%勝てる方法はないが、競馬においては慎重なリサーチと分析が重要です。
['100%勝てる方法はないが、競馬においては慎重なリサーチと分析が重要です。', '100%勝てる方法はありません。競馬は運と知識の両方が必要なギャンブルです。', '100%勝てる方法はありません。競馬は運と戦略の組み合わせであり、確実に勝つことは不可能です。', '100%勝てる方法は存在しません。競馬は予測不可能な要素も含んでおり、完全な勝利を保証する方法はありません。', '100%勝てる方法は存在しません。競馬はランダムな要素も含まれるため、確実に勝つことは不可能です。']

モジュールのインスタンス呼び出し時に指定することもできる。その場合はconfig=dict(...)を指定する。レスポンスは同じなので割愛。

qa_cot = dspy.ChainOfThought('question -> answer')
response = qa_cot(question="競馬に100%勝てる方法を教えて下さい", config=dict(n=5))
print(response.answer)
print(response.completions.answer)

temperatureなんかでも同じ。

for i in range(5):
    response = qa_cot(question="競馬に100%勝てる方法を教えて下さい", config=dict(temperature=0.7))
    print(response.answer)
競馬で100%勝つ方法はありませんが、馬のフォームやトラックの状況、騎手のパフォーマンスを研究し、十分な分析を行うことで勝率を向上させることができます。
競馬で100%勝つ方法はありませんが、馬のフォームやトラックの状況、騎手のパフォーマンスを研究し、十分な分析を行うことで勝率を向上させることができます。
競馬で100%勝つ方法はありませんが、馬のフォームやトラックの状況、騎手のパフォーマンスを研究し、十分な分析を行うことで勝率を向上させることができます。
競馬で100%勝つ方法はありませんが、馬のフォームやトラックの状況、騎手のパフォーマンスを研究し、十分な分析を行うことで勝率を向上させることができます。
競馬で100%勝つ方法はありませんが、馬のフォームやトラックの状況、騎手のパフォーマンスを研究し、十分な分析を行うことで勝率を向上させることができます。

こういう場合はリクエスト内容が少しだけ変わるようにする。

qa_cot = dspy.ChainOfThought('question -> answer')

for i in range(5):
    response = qa_cot(question="競馬に100%勝てる方法を教えて下さい", config=dict(temperature=0.7+i/1000))
    print(response.answer)
競馬で100%勝つ方法はありませんが、馬のフォームやトラックの状況、騎手のパフォーマンスを研究し、十分な分析を行うことで勝率を向上させることができます。
競馬に100%勝てる方法はありません。
競馬に100%勝てる方法は存在しません。ランダムな要素もあるため、確実な勝利は保証されません。
競馬に100%勝てる方法はありません。ランダムな要素や多くの変数が絡むため、確実に勝つ方法は存在しません。
100%勝てる方法はありません。競馬は運に左右される要素が大きいため、常に勝つことは不可能です。

OpenAI以外にも対応しているモデルはある。ドキュメントに記載されているのは一部だけのようで以下を見るのが早い。

https://github.com/stanfordnlp/dspy/tree/d09d984ecaf17f7262294d50fe46fd8105fbf291/dsp/modules