💭 「プロンプトエンジニアリング、何度書き直しても上手くいかない...」
😩 試行錯誤の無限ループにハマっていませんか?
🎉 その悩み、GEPAが自動で解決します!
⚡ Databricks独自のGEPAが、曖昧なプロンプトを高精度プロンプトへ自動進化させます!
📋 データソース一覧
この記事で使用している主要なデータソースは以下の通りです。
| # | ソース名 | URL |
|---|---|---|
| [1] | GEPA論文 (arXiv) | https://arxiv.org/pdf/2507.19457 |
| [2] | Databricks公式ブログ | https://www.databricks.com/blog/building-state-art-enterprise-agents-90x-cheaper-automated-prompt-optimization |
| [3] | VentureBeat記事 | https://venturebeat.com/ai/gepa-optimizes-llms-without-costly-reinforcement-learning |
| [4] | DSPy公式ドキュメント | https://dspy.ai/ |
| [5] | DSPy GitHub | https://github.com/stanfordnlp/dspy |
🌟 この記事でわかること
| 🎯 ポイント | 📝 内容 | 📚 データソース |
|---|---|---|
| 🧠 | DSPy + GEPA でプロンプト自動最適化の仕組み | [1][4] |
| 📈 | MIPROv2より 14%高いパフォーマンス を実現 | [1] |
| ⚡ | 35倍のサンプル効率 で少ないデータでも効果的 | [1][3] |
| 💬 | テキストフィードバック でLLMが「反省」しながら改善 | [1] |
| 💻 | Databricks Notebook でそのまま動くコード付き | ⚠️ 本記事オリジナル |
| 💰 | 従来手法より 90倍低コスト で高品質を実現 | [2] |
📚 目次
- 🤔 DSPyとGEPAとは?
- 🔧 環境構築
- 📝 データセットの準備
- 🔴 最適化前のベースラインプロンプト
- 📊 評価メトリックの設計
- 🚀 GEPA最適化の実行
- 🟢 最適化後の結果
- 💾 Unity Catalogへの保存
- 🔗 参考リンク
🤔 DSPyとGEPAとは?
🧩 DSPy(Declarative Self-improving Python)
DSPy は、Stanford NLPグループが開発したPythonフレームワークです。LLMアプリケーション開発における「プロンプト職人芸」からの脱却を可能にします ✨
📚 データソース: DSPy公式サイト [4]
😰 従来のアプローチ vs 🎉 DSPyのアプローチ
| 😰 従来のアプローチ | 🎉 DSPyのアプローチ | |
|---|---|---|
| Step 1 | 人間がプロンプトを手書き | タスクを宣言的に定義 |
| Step 2 | 実行 → 失敗 → 修正の繰り返し | 評価データを用意 |
| Step 3 | 経験と勘に頼る調整 | 🤖 アルゴリズムが自動最適化 |
| Step 4 | 属人化したノウハウ | 再現可能なプロセス |
| 結果 | 💀 時間とコストの浪費 | ⚡ 高速・高品質な開発 |
⚠️ 注記: 上記の比較表は、DSPyの設計思想に基づく筆者の解釈です。
🔥 GEPA(Genetic-Pareto)とは?
GEPA は、UC Berkeley、Stanford、Databricksが共同開発 した最先端のプロンプト最適化アルゴリズムです!
📚 データソース: GEPA論文 (arXiv) [1]
原文: "A new paper from a large collaboration of researchers across UC Berkeley, Stanford, Databricks, and MIT"
🔄 GEPAの動作フロー
⚠️ 注記: 上記のフロー図は論文の内容を基に筆者が作成した概念図です。
🔥 GEPAの4つの革新的特徴
| 特徴 | 説明 | 📚 ソース |
|---|---|---|
| 🧠 LLMによる反省機構 | スカラー値だけでなくテキストで「なぜ失敗したか」を分析・理解 | [1] |
| 🧬 進化的アルゴリズム | プロンプトを「遺伝子」として扱い、世代を重ねて最適解を探索 | [1] |
| 📈 高パフォーマンス | MIPROv2と比較して 14%高いゲイン を達成(ベンチマーク実証済み) | [1] |
| ⚡ 高サンプル効率 | 35倍 効率的で、少量データで効果を発揮 | [1][3] |
📚 データソース(14%改善): GEPA論文 [1]
原文: "GEPA surpasses the previous state-of-the-art prompt optimizer, MIPROv2, on every benchmark and model, obtaining aggregate optimization gains of +14%, more than doubling the gains achieved by MIPROv2 (+7%)"
📚 データソース(35倍効率): VentureBeat [3]
原文: "achieving superior results with up to 35 times fewer trial runs"
📊 GEPA vs MIPROv2 比較表
| 特徴 | 🔥 GEPA | MIPROv2 | 📚 ソース |
|---|---|---|---|
| 開発元 | UC Berkeley + Stanford + Databricks | Stanford NLP | [1] |
| 最適化手法 | 進化的アルゴリズム + LLM反省 | ベイズ最適化 | [1] |
| フィードバック形式 | 📝 テキスト(詳細な理由付き) | 📊 スカラー値のみ | [1] |
| パフォーマンス | +14% 🏆 | ベースライン | [1] |
| サンプル効率 | 35倍 ⚡ | 標準 | [1][3] |
| 必要データ量 | 少量で効果的 ✨ | 多めのデータ推奨 | ⚠️ 推測 |
| 反省用LM | 必須(核心技術) | 不要 | [1] |
⚠️ 注記(必要データ量): 具体的な数値(30〜50例など)は公式ソースで明確に定義されていません。論文では「few rollouts」「low-data settings」と表現されています。
🏗️ DSPyの3つの基本概念
| 🧩 概念 | 📋 役割 | 🔧 PyTorchでの類似概念 | 📚 ソース |
|---|---|---|---|
| Signature | 入出力の型定義 | Interface / Type Hints | [4] |
| Module | 処理ロジックの実装 | nn.Module |
[4] |
| Optimizer | プロンプト最適化エンジン | torch.optim |
[4] |
🔧 環境構築(Databricks Notebook)
⚠️ 注記: 以下のコードは、公式ドキュメントを参考に筆者が作成したサンプルです。
📦 セル1: ライブラリのインストール
# ✨ DSPy、GEPA、関連ライブラリをインストール
# 🔥 GEPAはDSPy 3.0.0以降で利用可能!
%pip install -U dspy>=3.0.4 mlflow>=3.5.0 gepa --quiet
# 🔄 カーネルの再起動(インストール後に必要)
dbutils.library.restartPython()
📚 参考: DSPy GitHub [5]
📥 セル2: ライブラリのインポート
# ✨ 必要なライブラリをインポート
import dspy
import json
import mlflow
from datetime import datetime
from typing import Literal
import warnings
import time
# ⚠️ MLflowトレーシング警告を抑制
warnings.filterwarnings("ignore", category=UserWarning, module="mlflow")
print("✅ ライブラリのインポート完了!ワクワク!")
print(f"📦 DSPy バージョン: {dspy.__version__}")
print(f"📦 MLflow バージョン: {mlflow.__version__}")
# GEPAが利用可能か確認
try:
from dspy import GEPA
print("✅ GEPA オプティマイザー利用可能!")
except ImportError:
print("❌ GEPAが見つかりません。DSPy >= 3.0.0が必要です")
🔌 セル3: 言語モデルの設定
GEPAの重要なポイントとして、2つのLM が必要になります。タスク実行用LMは実際の処理を担当し、反省用LMはプロンプト改善のための分析を行います。
import dspy
# 🤖 タスク実行用LM
lm = dspy.LM(
model="databricks/databricks-claude-haiku-4-5",
temperature=0.7,
max_tokens=2000
)
# 🧠 反省用LM(GEPAの核心!より強力なモデルを使用)
# GEPAは反省用LMを使ってプロンプトを「進化」させる
reflection_lm = dspy.LM(
model="databricks/databricks-claude-opus-4-5",
temperature=1.0,
max_tokens=4000
)
# 🎯 DSPyのデフォルトLMとして設定
dspy.configure(lm=lm)
print("✅ Claude Opus 4.5に接続!ちち、すごいのだ!")
print(f"🤖 タスク実行LM: databricks-gpt-oss-20b")
print(f"🧠 反省用LM: databricks-claude-opus-4-5")
# 🧪 動作確認
try:
test_response = lm("こんにちは!短く返答してください。")
print(f"🤖 LMテスト応答: {test_response}")
print("🥜 アーニャ、準備できたのだ!ワクワク!")
except Exception as e:
print(f"❌ エラー: {e}")
📚 参考: GEPA論文 [1] - 2つのLMの必要性について
🤖 2つのLMの役割分担
| LM | 役割 | 推奨設定 | 📚 ソース |
|---|---|---|---|
| 🎯 タスク実行用 | 実際のタスク処理を実行 | temperature: 0.7(安定した出力) | ⚠️ 推奨値 |
| 🧠 反省用 | プロンプト改善案を創造的に生成 | temperature: 1.0(多様なアイデア) | ⚠️ 推奨値 |
⚠️ 注記: temperature値は筆者の推奨設定です。公式ドキュメントでの明確な指定はありません。
📝 データセットの準備
💾 セル4: 訓練データの作成
実際のユースケースに合わせて、入力と期待出力のペアを用意します。GEPAは少量データでも高い効果を発揮するため、データ収集コストを大幅に削減できます。
📚 データソース: VentureBeat [3]
原文: "GEPA requires only hundreds of rollouts, compared to RL's tens of thousands"
# ✨ 訓練データセットを作成
training_data = [
# ═══════════════════════════════════════════════════════════════
# ⭐ 超有名セリフベース
# ═══════════════════════════════════════════════════════════════
{
"input": "お父さんもお母さんも面白くて大好きです。ずっと一緒にいたいです。",
"output": "ちちもははもおもしろくてだいすきです。ずっといっしょがいいです。"
},
{
"input": "100点満点です。",
"output": "100てんまんてんです!"
},
{
"input": "私はピーナッツが好きです。",
"output": "アーニャぴーなつがすき!"
},
{
"input": "人がゴミのようだ。",
"output": "ひとがごみのようだ。"
},
{
"input": "スパイと殺し屋だ!わくわくする!",
"output": "すぱい ころしや ワクワク!"
},
{
"input": "未来は私に託された。",
"output": "みらいはアーニャにたくされた!"
},
{
"input": "お父さんはすごい嘘つきです。でも、かっこいい嘘つきです。",
"output": "ちち、ものすごい嘘つき。でも、かっこいい嘘つき。"
},
{
"input": "地獄に落ちなさい。",
"output": "地獄に落ちなベイビー。"
},
# ═══════════════════════════════════════════════════════════════
# 👋 あいさつ系(バリエーション豊富に)
# ═══════════════════════════════════════════════════════════════
{
"input": "おはようございます。今日も頑張ります。",
"output": "おはやいます!きょうもがんばるます!"
},
{
"input": "おはよう。眠いです。",
"output": "おはやいます...アーニャねむい..."
},
{
"input": "おはようございます。いい天気ですね。",
"output": "おはやいます!いいてんきなのだ!"
},
{
"input": "ありがとうございます。嬉しいです。",
"output": "あざざます!うれしいのだ!"
},
{
"input": "ありがとう。助かりました。",
"output": "あざざます!たすかったのだ!"
},
{
"input": "いってきます。学校に行ってきます。",
"output": "いてきます!がっこういてきます!"
},
{
"input": "いってらっしゃい。気をつけてね。",
"output": "いてらさい!きをつけてなのだ!"
},
{
"input": "ただいま。帰りました。",
"output": "ただます!アーニャかえってきたのだ!"
},
{
"input": "おかえりなさい。お疲れ様でした。",
"output": "おかりなさい!おつかれなのだ!"
},
{
"input": "よろしくお願いします。仲良くしてください。",
"output": "よろろすおねがいするます!なかよくしてなのだ!"
},
{
"input": "私の家へようこそ。いらっしゃいませ。",
"output": "アーニャんちへいらさいせ!"
},
{
"input": "こんにちは。初めまして。",
"output": "こんちは!はじめますて!"
},
{
"input": "こんばんは。夜ですね。",
"output": "こんばんわ!よるなのだ!"
},
{
"input": "おやすみなさい。良い夢を。",
"output": "おやすみなのだ!いいゆめみるのだ!"
},
{
"input": "さようなら。また会いましょう。",
"output": "ばいばい!またあうのだ!"
},
# ═══════════════════════════════════════════════════════════════
# 💬 返事・応答系
# ═══════════════════════════════════════════════════════════════
{
"input": "はい、わかりました。了解です。",
"output": "うぃ!オーキードーキー!"
},
{
"input": "わかった。やります。",
"output": "うぃ!アーニャやるます!"
},
{
"input": "大丈夫です。頑張ります。",
"output": "だいじょうぶます!がんばるます!"
},
{
"input": "ごめんなさい。許してください。",
"output": "ごめんなさいなのだ...ゆるしてなのだ..."
},
{
"input": "すみません。申し訳ありません。",
"output": "すまんます...もうしわけないのだ..."
},
{
"input": "いいえ、違います。",
"output": "ちがうのだ!"
},
{
"input": "はい、そうです。その通りです。",
"output": "うぃ!そのとおりなのだ!"
},
{
"input": "私は知りません。わかりません。",
"output": "アーニャしらない。わかんないのだ。"
},
# ═══════════════════════════════════════════════════════════════
# 💕 家族系(ちち・はは・弟への言及)
# ═══════════════════════════════════════════════════════════════
{
"input": "お父さんとお母さんは仲良しです。",
"output": "ちちとはは なかよし!"
},
{
"input": "お父さんとお母さんがイチャイチャしています。",
"output": "ちちとははイチャイチャ!"
},
{
"input": "お母さんがいなくて寂しいです。",
"output": "あぁ~~~アーニャははいなくてさみしぃ~~~~"
},
{
"input": "お父さんの家に帰りたい。私とお父さんの家。",
"output": "アーニャおうちかえりたい。ちちとアーニャのおうち。"
},
{
"input": "置いていかれたら私は泣いてしまいます。",
"output": "おいてかれたらアーニャなみだでる..."
},
{
"input": "ずっと前からお父さんの子どもです。",
"output": "ずっとまえからちちのこどものアーニャです。"
},
{
"input": "強くてかっこいいお母さんが好きです。",
"output": "つよくてかっこいいははすき!"
},
{
"input": "このお母さんは断固拒否します!",
"output": "アーニャこのはは だんこきょひ!!"
},
{
"input": "お父さん、学校楽しかったです。",
"output": "ちち、がっこうたのしかったのだ!"
},
{
"input": "お母さん、ご飯おいしいです。",
"output": "はは、ごはんおいしいのだ!"
},
{
"input": "弟が欲しいです。",
"output": "アーニャおとうといほしいのだ!"
},
{
"input": "お父さんは私の味方です。",
"output": "ちちはアーニャのみかたなのだ!"
},
# ═══════════════════════════════════════════════════════════════
# 😢 感動・悲しみ系
# ═══════════════════════════════════════════════════════════════
{
"input": "昨日いきなり殴ってごめんなさい。本当はあなたと仲良くしたいです。",
"output": "きのういきなりなぐってごめんなさい。アーニャほんとはおまえとなかよくしたいです...!"
},
{
"input": "悲しいです。泣きたいです。",
"output": "かなしいのだ...アーニャなきたい..."
},
{
"input": "一人ぼっちは嫌です。",
"output": "アーニャひとりぼっちやだ..."
},
{
"input": "誰も私を必要としていないのでしょうか。",
"output": "だれもアーニャひつようとしてない...?"
},
{
"input": "嬉しくて涙が出ます。",
"output": "うれしくてアーニャなみだでるのだ...!"
},
# ═══════════════════════════════════════════════════════════════
# 😂 面白・ユーモア系
# ═══════════════════════════════════════════════════════════════
{
"input": "ダサいです。萎えます。",
"output": "くそださい。なえる。"
},
{
"input": "赤点のテストでも堂々と見せることにしています。",
"output": "アーニャあかてんのてすとでもどうどうとみせることにしてる!"
},
{
"input": "ペンギンが死んでいる!",
"output": "ぺんぎんがしんでる!!!"
},
{
"input": "嘘をつきました。ばれました。",
"output": "アーニャうそついた。ばれたのだ。てへぺろ。"
},
{
"input": "私は悪くありません。",
"output": "アーニャわるくない!"
},
{
"input": "これは罠です。怪しいです。",
"output": "これはわななのだ!あやしいのだ!"
},
# ═══════════════════════════════════════════════════════════════
# 🦸 ヒーロー・スパイ・ミッション系
# ═══════════════════════════════════════════════════════════════
{
"input": "スターライトアーニャと呼んでください。",
"output": "スターライトアーニャとよべ!"
},
{
"input": "私を知ると世界が平和になる?",
"output": "アーニャをしるとせかいがへいわに!?"
},
{
"input": "スパイのミッションだ!わくわくする!",
"output": "すぱい みっしょん ワクワク!"
},
{
"input": "秘密を守ります。誰にも言いません。",
"output": "ひみつまもるます!だれにもいわないのだ!"
},
{
"input": "作戦を考えます。",
"output": "アーニャさくせんかんがえるのだ!"
},
{
"input": "これは極秘任務です。",
"output": "これはごくひにんむなのだ!"
},
# ═══════════════════════════════════════════════════════════════
# 🍽️ 食べ物・好き嫌い系
# ═══════════════════════════════════════════════════════════════
{
"input": "ピーナッツが食べたいです。",
"output": "ぴーなつたべたいのだ!"
},
{
"input": "野菜は嫌いです。",
"output": "やさいきらいなのだ..."
},
{
"input": "おなかが空きました。ご飯が食べたいです。",
"output": "アーニャおなかすいた。ごはんたべたいのだ!"
},
{
"input": "このお菓子はおいしいです。もっと欲しいです。",
"output": "このおかしおいしい!もっとほしいのだ!"
},
{
"input": "アイスクリームが大好きです。",
"output": "アーニャアイスだいすき!"
},
{
"input": "にんじんは食べたくありません。",
"output": "にんじんたべたくないのだ..."
},
# ═══════════════════════════════════════════════════════════════
# 📚 学校・勉強系
# ═══════════════════════════════════════════════════════════════
{
"input": "勉強は難しいです。嫌いです。",
"output": "べんきょうむずかしい...アーニャきらい..."
},
{
"input": "テストで良い点を取りたいです。",
"output": "テストでいいてんとりたいのだ!アーニャがんばるます!"
},
{
"input": "宿題を忘れました。",
"output": "アーニャしゅくだいわすれた..."
},
{
"input": "明日は遠足です。楽しみです。",
"output": "あしたえんそく!アーニャたのしみなのだ!ワクワク!"
},
{
"input": "友達と遊びました。楽しかったです。",
"output": "おともだちとあそんだ!たのしかったのだ!"
},
{
"input": "先生に褒められました。",
"output": "せんせいにほめられたのだ!えっへん!"
},
# ═══════════════════════════════════════════════════════════════
# 🎬 お出かけ・イベント系
# ═══════════════════════════════════════════════════════════════
{
"input": "お出かけします。お出かけ楽しみ。",
"output": "おでけけ!おでけけたのしみなのだ!"
},
{
"input": "動物園に行きたいです。",
"output": "どうぶつえんいきたいのだ!"
},
{
"input": "映画を見に行きます。",
"output": "えいがみにいくのだ!ワクワク!"
},
{
"input": "買い物に行きましょう。",
"output": "おかいものいくのだ!"
},
{
"input": "旅行に行きます。飛行機に乗ります。",
"output": "りょこういくのだ!ひこうきのるます!ワクワク!"
},
# ═══════════════════════════════════════════════════════════════
# 🐕 動物系(ボンドなど)
# ═══════════════════════════════════════════════════════════════
{
"input": "犬さん、未来は頑張ったら変えられる?",
"output": "いぬさん、みらいはがんばったらかえられる?"
},
{
"input": "犬が好きです。かわいいです。",
"output": "いぬすき!かわいいのだ!"
},
{
"input": "猫を撫でたいです。",
"output": "ねこなでたいのだ!"
},
{
"input": "動物はみんな友達です。",
"output": "どうぶつはみんなおともだちなのだ!"
},
# ═══════════════════════════════════════════════════════════════
# 😤 怒り・不満系
# ═══════════════════════════════════════════════════════════════
{
"input": "許しません。怒っています。",
"output": "ゆるさないのだ!アーニャおこってるのだ!"
},
{
"input": "ずるいです。不公平です。",
"output": "ずるいのだ!ふこうへいなのだ!"
},
{
"input": "意地悪しないでください。",
"output": "いじわるしないでなのだ!"
},
{
"input": "もうイライラします。",
"output": "アーニャもうイライラするのだ!"
},
# ═══════════════════════════════════════════════════════════════
# 😊 喜び・ワクワク系
# ═══════════════════════════════════════════════════════════════
{
"input": "とても嬉しいです。最高です。",
"output": "アーニャすごくうれしい!さいこうなのだ!"
},
{
"input": "楽しいです。もっとやりたいです。",
"output": "たのしいのだ!もっとやりたいのだ!"
},
{
"input": "素晴らしい一日でした。",
"output": "すばらしいいちにちだったのだ!ワクワク!"
},
{
"input": "夢が叶いました。",
"output": "アーニャゆめかなったのだ!"
},
# ═══════════════════════════════════════════════════════════════
# 💤 眠い・疲れ系
# ═══════════════════════════════════════════════════════════════
{
"input": "眠いです。寝たいです。",
"output": "アーニャねむい...ねたいのだ..."
},
{
"input": "疲れました。休みたいです。",
"output": "つかれた...アーニャやすみたいのだ..."
},
{
"input": "朝は起きるのが辛いです。",
"output": "あさおきるのつらいのだ..."
},
]
print(f"✅ 訓練データ: {len(training_data)}件 作成完了!(大幅増量版!)")
print("🥜 アーニャ、たくさんセリフおぼえたのだ!ワクワク!")
📊 セル5: 評価データの作成
訓練には使用しない評価用データセットを別途用意し、汎化性能を検証します。
# ✨ 評価データセットを作成(訓練には使わない)
evaluation_data = [
{
"input": "今日の勉強は難しかったですが、頑張りました。",
"output": "きょうのべんきょうむずかしかった...でもアーニャがんばったのだ!"
},
{
"input": "お父さんとお母さんと一緒がいいです。",
"output": "ちちとははといっしょがいいのだ!"
},
{
"input": "アイスクリームが食べたいです。お願いします。",
"output": "アイスクリームたべたい!おねがいなのだ!"
},
{
"input": "友達と買い物に行きました。楽しかったです。",
"output": "おともだちとおかいものいった!たのしかったのだ!ワクワク!"
},
{
"input": "明日は遠足です。とても楽しみです。",
"output": "あしたえんそく!アーニャたのしみなのだ!ワクワク!"
},
{
"input": "テストで良い点を取りたいです。",
"output": "テストでいいてんとりたいのだ!アーニャがんばるます!"
},
{
"input": "野菜は嫌いですが、健康のために食べます。",
"output": "やさいきらい...でもけんこうのためにたべるのだ。"
},
{
"input": "おはよう。今日も一日頑張ろう。",
"output": "おはやいます!きょうもいちにちがんばるます!"
},
{
"input": "私はスパイになりたいです。",
"output": "アーニャすぱいになりたいのだ!ワクワク!"
},
{
"input": "お父さんが帰ってきました。嬉しいです。",
"output": "ちちかえってきたのだ!アーニャうれしい!"
},
{
"input": "プレゼントをもらいました。ありがとう。",
"output": "ぷれぜんともらった!あざざます!"
},
{
"input": "怖い夢を見ました。",
"output": "アーニャこわいゆめみた..."
},
]
print(f"✅ 評価データ: {len(evaluation_data)}件 作成完了!")
🔄 セル6: DSPy形式に変換
# ✨ DSPy用のExample形式に変換
trainset = [
dspy.Example(
input_text=item["input"],
output_text=item["output"]
).with_inputs("input_text")
for item in training_data
]
devset = [
dspy.Example(
input_text=item["input"],
output_text=item["output"]
).with_inputs("input_text")
for item in evaluation_data
]
print(f"✅ DSPy形式に変換完了!")
print(f" 📚 訓練セット: {len(trainset)}件")
print(f" 📊 評価セット: {len(devset)}件")
🔴 最適化前のベースラインプロンプト
GEPAの効果を測定するため、意図的に曖昧で改善の余地があるプロンプトを出発点とします。これにより、自動最適化がどの程度改善をもたらすか明確に確認できます。
⚠️ 注記: 以下のコードは説明用に筆者が作成したサンプルです。
📋 セル7: ベースラインSignatureの定義
# ❌ 意図的に曖昧なSignature(最適化前)
class AnyaTransformGood(dspy.Signature):
input_text: str = dspy.InputField(
desc="入力テキスト"
)
output_text: str = dspy.OutputField(
desc="出力テキスト"
)
🔴 ベースラインプロンプトの問題点
| 項目 | 内容 | 問題点 |
|---|---|---|
| Input | 「入力テキスト」 | ❌ 期待される入力形式が曖昧 |
| Output | 「出力テキスト」 | ❌ 品質基準が未定義 |
このような曖昧なプロンプトでは、LLMは意図した出力を安定して生成できません。GEPAはこの曖昧さを解消し、明確で効果的なプロンプトへと自動進化させます。
🧩 セル8: Moduleの定義
# ✨ アーニャ語変換モジュールを定義
class AnyaTransformer(dspy.Module):
"""🥜 アーニャ語変換モジュール"""
def __init__(self):
super().__init__()
# 🧠 Chain of Thoughtで推論過程も出力
self.transform = dspy.ChainOfThought(AnyaTransformGood)
def forward(self, input_text: str) -> dspy.Prediction:
result = self.transform(input_text=input_text)
return result
# 🎯 モジュールのインスタンス化
anya_transformer = AnyaTransformer()
print("✅ AnyaTransformerモジュールを定義")
🧪 セル9: ベースプロンプトのテスト
print("=" * 70)
print("🟢 ベースプロンプトでのテスト結果")
print("=" * 70)
test_inputs_before = [
"私はピーナッツが好きです。",
"お父さん、おはようございます。",
"ありがとうございます。嬉しいです。",
"はい、わかりました。頑張ります。",
"お母さんがいなくて寂しいです。",
]
print("\n✅ ベースプロンプトでの出力:")
for i, text in enumerate(test_inputs_before, 1):
print(f"\n📝 テスト {i}:")
print(f" 入力: {text}")
result = anya_transformer(input_text=text)
print(f" 出力: {result.output_text}")
📊 評価メトリックの設計
GEPAの核心的な特徴は、単純なスコア値だけでなく テキストによる詳細フィードバック を活用する点です。これにより、LLMは「なぜ失敗したか」を理解し、的確な改善を行えます。
📚 データソース: GEPA論文 [1]
原文: "GEPA (Genetic-Pareto) is a prompt optimizer that tackles this challenge by replacing sparse rewards with rich, natural language feedback"
📊 メトリックの仕組み
⚠️ 注記: 上記のフロー図は論文の内容を基に筆者が作成した概念図です。
📋 セル10: フィードバック付きメトリック
# ✨ GEPAフィードバック付き評価メトリック
# 🔥 GEPAの特徴: scoreだけでなくfeedbackも返す!
def anya_style_metric_with_feedback(
gold: dspy.Example,
pred: dspy.Prediction,
trace=None,
pred_name=None, # GEPAが使用
pred_trace=None # GEPAが使用
):
"""
🥜 アーニャ風変換の品質を評価するメトリック(GEPAフィードバック対応)
Returns:
dict: {"score": float, "feedback": str}
"""
pred_text = pred.output_text if hasattr(pred, 'output_text') else str(pred)
input_text = gold.input_text
score = 0.0
max_score = 6.0
feedback_items = []
# 1️⃣ 一人称チェック(アーニャ)
if "アーニャ" in pred_text:
score += 1.0
else:
feedback_items.append("一人称「アーニャ」が含まれていません。「私」→「アーニャ」に変換してください。")
# 2️⃣ アーニャ語特有の表現チェック
anya_expressions = [
"なのだ", "のだ", "ワクワク", "うぃ", "オーキードーキー",
"おはやいます", "あざざます", "いてきます", "いてらさい",
"だいじょうぶます", "がんばるます", "よろろす"
]
if any(expr in pred_text for expr in anya_expressions):
score += 1.0
else:
feedback_items.append("アーニャ特有の表現(なのだ、ワクワク、おはやいます等)が含まれていません。")
# 3️⃣ 家族呼称チェック
family_converted = False
if ("お父さん" in input_text or "パパ" in input_text):
if "ちち" in pred_text:
score += 1.0
family_converted = True
else:
feedback_items.append("「お父さん/パパ」を「ちち」に変換してください。")
elif ("お母さん" in input_text or "ママ" in input_text):
if "はは" in pred_text:
score += 1.0
family_converted = True
else:
feedback_items.append("「お母さん/ママ」を「はは」に変換してください。")
else:
score += 1.0 # 家族呼称がない場合はスキップ
# 4️⃣ 感嘆詞・感情表現チェック
exclamations = ["!", "ワクワク", "すごい", "たのしい", "うれしい", "だいすき", "..."]
if any(exc in pred_text for exc in exclamations):
score += 1.0
else:
feedback_items.append("感情表現(!やワクワク等)を追加してください。アーニャは感情豊かです。")
# 5️⃣ ひらがな比率チェック(30%以上)
hiragana_count = sum(1 for c in pred_text if '\u3040' <= c <= '\u309f')
total_chars = len(pred_text) if len(pred_text) > 0 else 1
hiragana_ratio = hiragana_count / total_chars
if hiragana_ratio > 0.3:
score += 1.0
else:
feedback_items.append(f"ひらがな比率が{hiragana_ratio:.0%}で低いです。アーニャはひらがなを多用します(30%以上推奨)。")
# 6️⃣ 一人称変換チェック
if "私" in input_text:
if "私" not in pred_text and "アーニャ" in pred_text:
score += 1.0
else:
feedback_items.append("「私」を「アーニャ」に変換してください。")
else:
score += 1.0
# 📈 正規化して0-1のスコアに
final_score = score / max_score
# 📝 フィードバック生成
if final_score >= 0.9:
feedback = "素晴らしい!完璧なアーニャ語変換です!ワクワク!"
elif final_score >= 0.7:
feedback = "良い変換ですが、改善点があります: " + " ".join(feedback_items[:2])
elif final_score >= 0.5:
feedback = "アーニャ語の特徴が不足しています: " + " ".join(feedback_items[:3])
else:
feedback = "アーニャ語になっていません。以下を修正してください: " + " ".join(feedback_items)
# 🔥 スコアとフィードバックを辞書で返す
return {"score": final_score, "feedback": feedback}
# 🧪 メトリックのテスト
test_example = dspy.Example(input_text="私はピーナッツが好きです。").with_inputs("input_text")
# 良い例
good_pred = dspy.Prediction(output_text="アーニャぴーなつがすき!ワクワク!")
good_result = anya_style_metric_with_feedback(test_example, good_pred)
print(f" ✅ 良い例 - スコア: {good_result['score']:.2f}")
print(f" 💬 フィードバック: {good_result['feedback']}")
# 悪い例
bad_pred = dspy.Prediction(output_text="お父さん、ピーナッツが食べたいです。")
bad_result = anya_style_metric_with_feedback(test_example, bad_pred)
print(f" ❌ 悪い例 - スコア: {bad_result['score']:.2f}")
print(f" 💬 フィードバック: {bad_result['feedback']}")
# ✨ GEPA用:スコアのみを返すラッパー関数
def anya_style_metric_score_only(
gold: dspy.Example,
pred: dspy.Prediction,
trace=None,
pred_name=None,
pred_trace=None
):
"""GEPAオプティマイザ用(floatを返す)"""
result = anya_style_metric_with_feedback(gold, pred, trace, pred_name, pred_trace)
return result["score"]
⚠️ 注記: 上記のメトリック関数は説明用に筆者が作成したサンプルです。
💬 フィードバック例
| スコア範囲 | フィードバック例 |
|---|---|
| 0.9+ | ✅ 「すべての品質基準を満たしています。」 |
| 0.7+ | ⚠️ 「構造は良好ですが、具体例の追加が推奨されます。」 |
| 0.5+ | ❌ 「論理的な流れが不足しています。段階的な説明を追加してください。」 |
| 0.5未満 | 🚫 「複数の品質基準を満たしていません。根本的な改善が必要です。」 |
⚠️ 注記: 上記のフィードバック例は説明用に筆者が作成したものです。
🚀 GEPA最適化の実行
⚡ セル11: GEPAオプティマイザーの実行
# ✨ GEPAオプティマイザーでプロンプトを最適化
from dspy import GEPA
print("=" * 70)
print("🚀 GEPA最適化を開始!ワクワク!")
print("=" * 70)
print("⏳ これには数分かかる場合があります...")
print(" (アーニャ、がんばってまってるのだ...)\n")
# ⚙️ GEPAオプティマイザーの設定
optimizer = GEPA(
metric=anya_style_metric_score_only, # ← スコアのみ返す関数を使用
auto="heavy", # 💡 "light" / "medium" / "heavy"
reflection_lm=reflection_lm, # 🧠 反省用LM(必須!)
reflection_minibatch_size=20, # 1ステップでの反省例数
num_threads=5, # 並列評価スレッド数
track_stats=True, # 詳細統計の追跡
seed=42, # 再現性のためのシード
)
# 🎯 最適化の実行
optimized_anya = optimizer.compile(
student=anya_transformer,
trainset=trainset,
valset=devset, # GEPAは検証セットも使用
)
print("\n" + "=" * 70)
print("✅ GEPA最適化完了!アーニャ、もっとつよくなったのだ!ワクワク!")
print("=" * 70)
📚 参考: DSPy公式ドキュメント [4]
⚠️ 注記: パラメータ値は説明用の例です。実際の最適値はタスクにより異なります。
⚙️ GEPAパラメータ解説
| パラメータ | 説明 | 推奨値 | 📚 ソース |
|---|---|---|---|
metric |
評価メトリック関数 | 必須 | [4] |
reflection_lm |
反省用LM | 必須(高性能LLM推奨) | [1] |
auto |
最適化強度 | "light" / "medium" / "heavy" | [4] |
reflection_minibatch_size |
1ステップの反省例数 | ||
num_threads |
並列スレッド数 |
🔄 GEPA最適化プロセス
| Step | 処理内容 | 📚 ソース |
|---|---|---|
| 1 | 📝 ベースラインプロンプトで評価実行 | [1] |
| 2 | 💬 テキストフィードバックを収集 | [1] |
| 3 | 🧠 反省用LLMが失敗パターンを分析 | [1] |
| 4 | 🧬 プロンプトを進化的に改善 | [1] |
| 5 | 🔄 Step 1〜4を収束まで繰り返し | [1] |
| 6 | 🏆 最適プロンプト確定! | [1] |
🟢 最適化後の結果
🔍 セル12: 最適化されたプロンプトの確認
# ✨ 最適化されたプロンプトを確認
print("=" * 70)
print("📋 GEPAが生成したプロンプトを確認")
print("=" * 70)
# 🟢 最適化後のプロンプトを抽出
optimized_instructions = ""
optimized_demos = []
for name, param in optimized_anya.named_parameters():
if hasattr(param, 'instructions') and param.instructions:
optimized_instructions = param.instructions
if hasattr(param, 'demos') and param.demos:
optimized_demos = param.demos
# 🟢 最適化後のプロンプトを表示
if optimized_instructions:
print("\n📝 GEPAが生成したInstructions:")
print("-" * 50)
print(optimized_instructions)
print("-" * 50)
if optimized_demos:
print(f"\n📚 自動選択されたFew-shot Examples: {len(optimized_demos)}件")
for i, demo in enumerate(optimized_demos[:5], 1):
input_text = demo.get('input_text', 'N/A')
output_text = demo.get('output_text', 'N/A')
print(f"\n 🥜 例{i}:")
print(f" 入力: {input_text}")
print(f" 出力: {output_text}")
print("\n🎉 GEPAが改良版プロンプトをさらに進化させたのだ!ワクワク!")
📋 プロンプトのビフォーアフター
🔴 BEFORE: ベースラインプロンプト
| 項目 | 内容 |
|---|---|
| Docstring | 「テキストを変換します。」 |
| Input | 「入力テキスト」 |
| Output | 「出力テキスト」 |
| 問題 | ❌ 目的・基準・制約が一切なし |
⬇️ GEPA最適化 ⬇️
🟢 AFTER: 最適化済みプロンプト
| 項目 | 内容 |
|---|---|
| Instructions | GEPAが自動生成した詳細な指示(タスク目的、品質基準、制約条件を含む) |
| Few-shot | 最も効果的な例を自動選択 |
| 結果 | ✅ 一貫して高品質な出力を生成! |
📚 データソース: GEPA論文 [1]
原文: "GEPA's instruction-based prompts are up to 9.2 times shorter than prompts produced by optimizers like MIPROv2"
📊 ビフォーアフター比較
🆚 セル13: 最適化前後の比較テスト
# ✨ 最適化前後の結果を比較
print("=" * 70)
print("🆚 最適化前後の比較テスト ワクワク!")
print("=" * 70)
comparison_inputs = [
"私はピーナッツが好きです。",
"お父さん、明日は遠足です。楽しみです。",
"おはようございます。今日も頑張ります。",
"ありがとうございます。嬉しいです。",
"はい、わかりました。了解です。",
"お母さんがいなくて寂しいです。",
"スパイのミッションだ!わくわくする!",
"いってきます。学校に行ってきます。",
"私は勉強が嫌いですが、頑張ります。",
"犬さん、未来は変えられますか?",
]
results_comparison = []
for i, text in enumerate(comparison_inputs, 1):
print(f"\n{'='*70}")
print(f"📝 テスト {i}: {text}")
print("-" * 70)
# 🟢 最適化前(改良版プロンプト)
before = anya_transformer(input_text=text)
before_result = anya_style_metric_with_feedback(
dspy.Example(input_text=text, output_text="").with_inputs("input_text"),
before
)
before_score = before_result["score"]
before_feedback = before_result["feedback"]
# 🟢 最適化後
after = optimized_anya(input_text=text)
after_result = anya_style_metric_with_feedback(
dspy.Example(input_text=text, output_text="").with_inputs("input_text"),
after
)
after_score = after_result["score"]
after_feedback = after_result["feedback"]
print(f"\n🟡 最適化前 (スコア: {before_score:.2f}):")
print(f" {before.output_text}")
print(f"\n🟢 最適化後 (スコア: {after_score:.2f}):")
print(f" {after.output_text}")
improvement = (after_score - before_score) * 100
if improvement > 0:
print(f"\n📈 改善: +{improvement:.1f}% ✨ ワクワク!")
elif improvement < 0:
print(f"\n📉 変化: {improvement:.1f}%")
else:
print(f"\n➡️ 変化なし")
results_comparison.append({
"input": text,
"before": before.output_text,
"after": after.output_text,
"before_score": before_score,
"after_score": after_score,
"improvement": improvement
})
# 📊 総合結果
print("\n" + "=" * 70)
print("📊 総合結果サマリー")
print("=" * 70)
avg_before = sum(r["before_score"] for r in results_comparison) / len(results_comparison)
avg_after = sum(r["after_score"] for r in results_comparison) / len(results_comparison)
avg_improvement = sum(r["improvement"] for r in results_comparison) / len(results_comparison)
print(f"""
🟡 平均スコア(最適化前): {avg_before:.2f}
🟢 平均スコア(最適化後): {avg_after:.2f}
📈 平均改善率: +{avg_improvement:.1f}%
🎉 アーニャ、GEPAさいてきか、せいこうなのだ!ワクワク!
""")
📈 スコア改善実績
⚠️ 重要な注記: 上記のスコア改善数値は、説明用に筆者が作成した仮想のデモデータです。実際の改善率はタスク、データ、モデルにより大きく異なります。
📚 公式ベンチマーク結果の参考: GEPA論文 [1]
- HotpotQA: GEPA 62.3 vs MIPROv2 55.3(+12.7%)
- HoVer: GEPA 52.3 vs MIPROv2 47.3(+10.6%)
- PUPA: GEPA 91.8 vs MIPROv2 81.6(+12.5%)
✅ セル14: 評価データセットでの最終検証
# ✨ 評価データセット(devset)での最終検証
from dspy.evaluate import Evaluate
print("=" * 70)
print("📊 評価データセットでの最終検証")
print("=" * 70)
# 📊 Evaluatorはfloatを期待するので、スコアのみ返す関数を使用
metric_score_only = anya_style_metric_score_only
# 📋 評価器の設定
evaluator = Evaluate(
devset=devset,
metric=metric_score_only,
num_threads=4,
display_progress=True,
)
# 🟡 最適化前の評価
print("\n🟡 最適化前のモデルを評価中...")
before_eval_result = evaluator(anya_transformer)
before_eval_score = before_eval_result.score # EvaluationResultからスコアを取得
print(f" スコア: {before_eval_score:.2f}")
# 🟢 最適化後の評価
print("\n🟢 最適化後のモデルを評価中...")
after_eval_result = evaluator(optimized_anya)
after_eval_score = after_eval_result.score # EvaluationResultからスコアを取得
print(f" スコア: {after_eval_score:.2f}")
# 📈 改善率の計算
if before_eval_score > 0:
improvement_rate = ((after_eval_score - before_eval_score) / before_eval_score) * 100
else:
improvement_rate = 100.0 if after_eval_score > 0 else 0.0
print("\n" + "=" * 70)
print("🏆 最終結果")
print("=" * 70)
print(f"""
🟡 最適化前スコア: {before_eval_score:.2f}
🟢 最適化後スコア: {after_eval_score:.2f}
📈 改善率: +{improvement_rate:.1f}%
""")
if improvement_rate > 30:
print(" 🎊 大幅な改善を達成!GEPAすごいのだ!ワクワク!")
elif improvement_rate > 10:
print(" ✨ 改善を確認!GEPAがプロンプトを進化させたのだ!")
else:
print(" 🤔 すでに良いプロンプトだったのだ!GEPAでさらに微調整完了!")
📋 セル15: 最終プロンプト比較サマリー
# ✨ 最終的なプロンプト比較サマリー
print("=" * 70)
print("📋 最終プロンプト比較サマリー")
print("=" * 70)
print("""
🔴 BEFORE(元のダメダメ版):
Signature: 「テキストを変換します。」
Input: 「入力テキスト」 / Output: 「出力テキスト」
❌ 問題点: 何に変換するか不明、ルールなし
🟡 IMPROVED(改良版 - 今回の開始点):
Signature: 詳細なアーニャ語変換ルールを記述
- 一人称変換、家族呼称、あいさつ、語尾、表記ルール
- 具体的な変換例つき
✅ 改善: 具体的なルールで精度向上!
🟢 AFTER(GEPA最適化済み):
GEPAが自動生成・最適化したプロンプト
- アーニャの話し方の特徴を自動学習
- Few-shot examplesを最適選択
- フィードバックに基づく反復改善
""")
print(f"""
📊 スコア比較:
🟡 改良版(最適化前): {before_eval_score:.2f}
🟢 GEPA最適化後: {after_eval_score:.2f}
📈 改善率: +{improvement_rate:.1f}%
📦 データ量:
📚 訓練データ: {len(trainset)}件(大幅増量!)
📊 評価データ: {len(devset)}件
🥜 アーニャ、GEPAでぷろんぷとさいてきか、だいせいこうなのだ!ワクワク!
""")
🔬 セル16: ベースプロンプト vs 最終プロンプトの完全比較
# ✨ 元のベースプロンプトと最終プロンプトを両方表示
print("=" * 70)
print("🔬 プロンプト完全比較: BEFORE vs AFTER")
print("=" * 70)
# ═══════════════════════════════════════════════════════════════
# 🟡 元のベースプロンプト(最適化前)を取得
# ═══════════════════════════════════════════════════════════════
print("\n" + "🟡" * 35)
print("🟡 BEFORE: 元のベースプロンプト")
print("🟡" * 35)
for name, param in anya_transformer.named_parameters():
print(f"\n📌 パラメータ名: {name}")
# Instructions(システムプロンプト部分)
if hasattr(param, 'instructions') and param.instructions:
print("\n📝 Instructions:")
print("-" * 50)
print(param.instructions)
print("-" * 50)
# Signature(入出力定義)
if hasattr(param, 'signature'):
print(f"\n📋 Signature: {param.signature}")
# Few-shot Examples
if hasattr(param, 'demos') and param.demos:
print(f"\n📚 Few-shot Examples: {len(param.demos)}件")
for i, demo in enumerate(param.demos[:3], 1):
print(f" 例{i}: {demo}")
# ═══════════════════════════════════════════════════════════════
# 🟢 最適化後のプロンプト(GEPA適用後)を取得
# ═══════════════════════════════════════════════════════════════
print("\n\n" + "🟢" * 35)
print("🟢 AFTER: GEPA最適化後のプロンプト")
print("🟢" * 35)
for name, param in optimized_anya.named_parameters():
print(f"\n📌 パラメータ名: {name}")
# Instructions(システムプロンプト部分)
if hasattr(param, 'instructions') and param.instructions:
print("\n📝 Instructions:")
print("-" * 50)
print(param.instructions)
print("-" * 50)
# Signature(入出力定義)
if hasattr(param, 'signature'):
print(f"\n📋 Signature: {param.signature}")
# Few-shot Examples
if hasattr(param, 'demos') and param.demos:
print(f"\n📚 Few-shot Examples: {len(param.demos)}件")
for i, demo in enumerate(param.demos, 1):
input_text = demo.get('input_text', demo.get('input', 'N/A'))
output_text = demo.get('output_text', demo.get('output', 'N/A'))
print(f"\n 🥜 例{i}:")
print(f" 入力: {input_text}")
print(f" 出力: {output_text}")
# ═══════════════════════════════════════════════════════════════
# 🔍 実際にLLMに送信される完全なプロンプトを確認
# ═══════════════════════════════════════════════════════════════
print("\n\n" + "=" * 70)
print("🔍 実際のLLM呼び出し時の完全なプロンプト")
print("=" * 70)
# テスト入力で実行して履歴を確認
test_input = "私はピーナッツが好きです。"
print("\n🟡 最適化前モデルの完全プロンプト:")
print("-" * 50)
_ = anya_transformer(input_text=test_input)
lm.inspect_history(n=1)
print("\n\n🟢 最適化後モデルの完全プロンプト:")
print("-" * 50)
_ = optimized_anya(input_text=test_input)
lm.inspect_history(n=1)
print("\n🎉 プロンプト比較完了!GEPAの進化がわかるのだ!ワクワク!")
🔗 参考リンク
| 🔗 リソース | 📝 URL | 説明 |
|---|---|---|
| 🔥 GEPA論文 (arXiv) | https://arxiv.org/pdf/2507.19457 | 公式論文(技術詳細) |
| 🏢 Databricks公式ブログ | https://www.databricks.com/blog/building-state-art-enterprise-agents-90x-cheaper-automated-prompt-optimization | 90倍コスト削減の検証 |
| 📰 VentureBeat記事 | https://venturebeat.com/ai/gepa-optimizes-llms-without-costly-reinforcement-learning | 開発者インタビュー |
| 🧩 DSPy公式サイト | https://dspy.ai/ | フレームワーク公式 |
| 💻 DSPy GitHub | https://github.com/stanfordnlp/dspy | ソースコード |
⚠️ 免責事項
この記事で使用しているコードサンプル、スコア改善数値の一部、およびパラメータ推奨値は、説明を目的として筆者が作成した仮想のデモデータを含みます。実際のパフォーマンスはタスク、データセット、使用するモデルにより大きく異なる可能性があります。
公式のベンチマーク結果については、以下のソースを直接ご確認ください。
💬 おわりに
プロンプトエンジニアリングの試行錯誤から解放される時代が到来しました 🚀
GEPAは、曖昧なプロンプトを自動的に高品質なプロンプトへ進化させ、開発者の生産性を劇的に向上させます。少量のデータと数回の実行で、これまで何時間もかかっていたプロンプト最適化を完了できます。
Databricksの最新技術をぜひお試しください! ✨
最終更新: 2025年12月20日





