GEPA を用いた反射的プロンプト最適化:多要素プロンプト、構造化フィードバック、検証手法の構築
本文の状態
日本語全文を表示中
詳細モードで約9分の本文を読めます。
同じ出来事の情報源
この情報源を基点に整理
MarkTechPost
MarkTechPost は、言語モデルが算数文章題を解く能力を向上させるため、GEPA という反射的プロンプト進化フレームワークの使用法を紹介している。同記事では、弱いシードプロンプトから始め、決定論的なベンチマークと構造化評価器を定義し、候補プロンプトの失敗理由を理解できる実行可能なフィードバックを提供する手法を解説している。また、指示フィールドと出力フォーマット規則が同時に進化するように設計された多要素プロンプト構成も採用されている。
Source Article
元記事を日本語で読む
本文に関係しない購読案内、埋め込み通知、サイト内プロモーションは除いています。
本チュートリアルでは、GEPA を反射型プロンプト進化フレームワークとして使用し、言語モデルが算術文章問題を解決する方法を改善します。まず、弱いシードプロンプトから始め、小さな決定論的ベンチマークを作成し、構造化された評価者を定義して、候補となるプロンプトがなぜ失敗するのかを理解できるよう GEPA に実行可能なフィードバックを提供します。また、指示フィールドと出力フォーマット規則の両方が同時に進化するように、マルチコンポーネントプロンプト設定も使用します。最後に、保持された検証セット上でベースラインプロンプトと最適化済みプロンプトを比較し、進化プロセスがどのようにパフォーマンスを向上させるかを調査します。
GEPA と LiteLLM のインストールおよびタスク・反射モデルの設定
コードをコピーしました(別のブラウザを使用)
!pip install -q gepa litellm
import os, re, json, random, getpass, textwrap
import litellm
import gepa.optimize_anything as oa
from gepa.optimize_anything import (
optimize_anything, GEPAConfig, EngineConfig, ReflectionConfig,
)
litellm.suppress_debug_info = True
if not os.environ.get("OPENAI_API_KEY"):
os.environ["OPENAI_API_KEY"] = getpass.getpass("Enter your OpenAI API key: ")
TASK_LM = "openai/gpt-4o-mini"
REFLECTION_LM = "openai/gpt-4.1"
MAX_METRIC_CALLS = 100
GEPA と LiteLLM をインストールし、プロンプト最適化およびモデル呼び出しに必要なライブラリをインポートします。OpenAI API キーを安全に設定し、2 つのモデルを定義します。1 つは問題を解決するタスク用モデル、もう 1 つはプロンプトを改善するための反射用モデルです。また、最適化プロセスが制御範囲内となるよう、最大メトリック呼び出し予算を設定します。
決定論的算数ベンチマークデータセットの構築
コードをコピーしました(別のブラウザを使用してください)
翻訳全文
def make_problems(n, seed=0):
rng = random.Random(seed)
out = []
for _ in range(n):
t = rng.choice(["discount", "travel", "wallet", "chain"])
if t == "discount":
unit = rng.choice([40, 60, 80, 120])
qty = rng.choice([5, 6, 8, 10])
disc = rng.choice([10, 20, 25, 50])
total = unit * qty
gold = total - total * disc // 100
q = (f"A shop sells notebooks at {unit} rupees each. You buy {qty} "
f"notebooks and get a {disc}% discount on the total bill. "
f"How many rupees do you pay in total?")
elif t == "travel":
s1, h1 = rng.choice([40, 50, 60]), rng.choice([2, 3])
s2, h2 = rng.choice([30, 45, 70]), rng.choice([1, 2, 3])
gold = s1 * h1 + s2 * h2
q = (f"A car drives at {s1} km/h for {h1} hours, then at {s2} km/h "
f"for {h2} hours. What is the total distance travelled, in km?")
elif t == "wallet":
tens = rng.choice([3, 5, 7, 9])
fifties= rng.choice([2, 4, 6])
spent = rng.choice([50, 80, 110, 150])
gold = tens * 10 + fifties * 50 - spent
q = (f"You have {tens} ten-rupee notes and {fifties} fifty-rupee "
f"notes. You spend {spent} rupees. How many rupees are left?")
else:
x = rng.choice([6, 9, 12, 15]); y = rng.choice([4, 7, 10]); z = rng.choice([3, 8, 11])
gold = x * 2 - y + z
q = (f"Start with the number {x}. Double it, then subtract {y}, "
f"then add {z}. What number do you end with?")
out.append({"question": q, "answer": gold})
return out
all_problems = make_problems(18, seed=42)
random.Random(1).shuffle(all_problems)
trainset = all_problems[:12]
valset = all_problems[12:]
print(f"Dataset: {len(trainset)} train / {len(valset)} val problems\n")
割引、移動距離、財布の計算、連鎖演算を扱う算数文章問題からなる小規模な決定論的データセットを作成します。各問題に対する正解はプログラムによって生成されるため、ベンチマークの信頼性が保たれ、評価が容易になります。その後、例をシャッフルし、最適化用のトレーニングセットと一般化能力を検証するための検証セットに分割します。
GEPA における評価者の定義と構造化フィードバック
コピーコード コピー済み ブラウザを変更してください
翻訳全文
def build_system_prompt(candidate: dict) -> str:
return (f"{candidate['instructions']}\n\n"
f"OUTPUT FORMAT RULES:\n{candidate['format_rules']}")
def call_task_lm(system_prompt: str, question: str) -> str:
for attempt in range(3):
try:
r = litellm.completion(
model=TASK_LM,
messages=[{"role": "system", "content": system_prompt},
{"role": "user", "content": question}],
temperature=0, max_tokens=600, timeout=60,
)
return r["choices"][0]["message"]["content"] or ""
except Exception as e:
if attempt == 2:
return f"[LM_ERROR] {e}"
return ""
def parse_answers(text: str):
formatted = re.search(r"####\s*(-?\d+)", text)
all_nums = re.findall(r"-?\d+", text)
fmt_val = int(formatted.group(1)) if formatted else None
last_val = int(all_nums[-1]) if all_nums else None
return fmt_val, last_val
def evaluate(candidate: dict, example: dict):
system = build_system_prompt(candidate)
raw = call_task_lm(system, example["question"])
gold = example["answer"]
fmt_val, last_val = parse_answers(raw)
if fmt_val is not None and fmt_val == gold:
score, fb = 1.0, "Correct and correctly formatted."
elif fmt_val is not None and fmt_val != gold:
score, fb = 0.0, (f"WRONG ANSWER. You output '#### {fmt_val}' but the "
f"correct answer is {gold}. Re-check the arithmetic and "
f"the order of the steps.")
elif last_val == gold:
score, fb = 0.5, (f"Right number ({gold}) but FORMAT VIOLATION: the final "
f"line was not exactly '#### {gold}'. Always end with a "
f"line of the form '#### ' and nothing else.")
else:
score, fb = 0.0, (f"WRONG. Correct answer is {gold}. The model's final "
f"number was {last_val}. Likely a multi-step reasoning "
f"slip; show each step and verify before answering.")
oa.log(f"score={score} gold={gold} parsed_fmt={fmt_val} parsed_last={last_val}")
side_info = {
"feedback": fb,
"problem": example["question"],
"gold_answer": gold,
"model_output": raw[:500],
}
return score, side_info
def eval_set(candidate, dataset, label=""):
scores, exact, formatted = [], 0, 0
for ex in dataset:
s, info = evaluate(candidate, ex)
scores.append(s)
if s == 1.0: exact += 1; formatted += 1
elif s == 0.5: formatted += 0
acc = exact / len(dataset)
avg = sum(scores) / len(dataset)
print(f" [{label}] avg_score={avg:.3f} exact_correct+formatted={exact}/{len(dataset)}")
return avg, acc
候補プロンプトがシステムプロンプトに変換される方法と、タスクモデルが各質問を受け取る仕組みを定義します。また、モデルの出力を解析し、最終回答が必須の #### フォーマットに従っているかを確認してスコアを付与する評価者も作成します。GEPA が問題が推論の誤りなのか、フォーマットの不良なのか、あるいはその両方であるかを判断できるように、構造化されたフィードバックを実行可能なサイド情報として返します。
GEPA の設定とプロンプト最適化の実行
コードをコピーしました
別のブラウザを使用してください
seed_candidate = {
"instructions": "Solve the math problem.",
"format_rules": "Give the answer.",
}
print("=== BASELINE (seed prompt) ===")
print("Train:"); base_train = eval_set(seed_candidate, trainset, "train")
print("Val: "); base_val = eval_set(seed_candidate, valset, "val")
print()
objective = (
"Evolve a system prompt (the 'instructions' and 'format_rules' fields) so a "
"small LLM reliably solves multi-step arithmetic word problems AND always "
"ends with a line of exactly the form '#### '. Maximize the score."
)
background = (
"Scoring: 1.0 = correct number in the exact '#### ' format; 0.5 = correct "
"number but wrong/missing format; 0.0 = wrong number. Common failures are (a) not "
"emitting the '####' line, and (b) order-of-operations or multi-step slips. The "
"winning prompt should force explicit step-by-step work, a verification step, and "
"a strict final-answer line."
)
config = GEPAConfig(
engine=EngineConfig(
max_metric_calls=MAX_METRIC_CALLS,
max_workers=4,
parallel=True,
display_progress_bar=True,
seed=0,
),
reflection=ReflectionConfig(
reflection_lm=REFLECTION_LM,
),
)
print("=== RUNNING GEPA (this calls the LLMs; ~1-4 min) ===")
result = optimize_anything(
seed_candidate=seed_candidate,
evaluator=evaluate,
dataset=trainset,
valset=valset,
objective=objective,
background=background,
config=config,
)
まず、弱いシードプロンプトから始め、トレーニングセットとバリデーションセットの両方におけるそのベースライン性能を評価します。次に、最適化目標、背景スコアリングルール、および並列評価や反射モデルを含む GEPA 設定を定義します。最後に optimize_anything を実行して、GEPA が評価フィードバックを用いて指示とフォーマルールフィールドを進化させます。
バリデーションセットにおけるベースラインと GEPA 最適化プロンプトの比較
コピーコード コピー済み
異なるブラウザを使用してください
best = result.best_candidate
print("\n" + "=" * 78)
print("OPTIMIZED CANDIDATE")
print("=" * 78)
print("\n--- instructions ---\n" + textwrap.fill(best["instructions"], 96))
print("\n--- format_rules ---\n" + textwrap.fill(best["format_rules"], 96))
print("\n" + "=" * 78)
print("BEFORE vs AFTER (held-out validation set)")
print("=" * 78)
print("Seed prompt:"); _ = eval_set(seed_candidate, valset, "val-seed")
print("GEPA prompt:"); _ = eval_set(best, valset, "val-gepa")
print(f"\nBaseline val avg_score : {base_val[0]:.3f}")
print("\n" + "=" * 78)
print("EVOLUTION HISTORY (candidate index -> val score, parents)")
print("=" * 78)
cands = getattr(result, "candidates", [])
vscores = getattr(result, "val_aggregate_scores", [])
parents = getattr(result, "parents", [None] * len(cands))
for i, sc in enumerate(vscores):
par = parents[i] if i < len(parents) else None
tag = " <-- BEST" if cands and cands[i] == best else ""
print(f" cand {i:2d}: val_score={sc:.3f} parents={par}{tag}")
print(f"\nTotal metric calls used : {getattr(result, 'total_metric_calls', 'n/a')}")
print(f"Full validation evals : {getattr(result, 'num_full_val_evals', 'n/a')}")
print("\nDone. Try raising MAX_METRIC_CALLS or swapping REFLECTION_LM for a stronger model.")
GEPA によって見出された最良のプロンプトを抽出し、その最適化された指示とフォーマットルールコンポーネントを表示します。シードプロンプトと GEPA 最適化プロンプトを保持された検証セットで比較し、改善が未見の例にも転移するかを確認します。また、プロンプトが最適化の過程でどのように改善したかを理解するために、進化履歴、検証スコア、親関係、および総メトリック呼び出し回数を調査します。
結論として、GEPA を用いてプロンプト最適化が手動の試行錯誤を超えて進化する様子を示しました。タスクモデルが例を解き、評価者が出力にスコアをつけ、反射モデルが詳細なフィードバックを用いてより良いプロンプトを提案するという完全なワークフローを作成しました。また、最適化されたプロンプトを未見の検証問題でテストし、改善が単なる訓練セットへの過学習ではなく一般化しているかを評価できる仕組みを整えました。さらに、構造化されたフィードバック、厳格な評価、反復的な改良が連携してより強力で信頼性の高いプロンプトを生み出す、反射的プロンプト進化の実用的な例も構築しました。
GEPA を用いた反射的プロンプト最適化の構築:多コンポーネント・プロンプト、構造化フィードバック、およびホールドアウト検証に関する記事は、MarkTechPost で最初に公開されました。
原文を表示
In this tutorial, we use GEPA as a reflective prompt-evolution framework to improve the way a language model solves arithmetic word problems. We begin with a weak seed prompt, create a small deterministic benchmark, define a structured evaluator, and pass actionable feedback to GEPA so it can understand why a candidate prompt fails. We also use a multi-component prompt setup in which both the instruction field and the output-format rules evolve together. By the end, we compare the baseline prompt with the optimized prompt on a held-out validation set and inspect how the evolutionary process improves performance.
Installing GEPA and LiteLLM and Configuring the Task and Reflection Models
Copy CodeCopiedUse a different Browser
!pip install -q gepa litellm
import os, re, json, random, getpass, textwrap
import litellm
import gepa.optimize_anything as oa
from gepa.optimize_anything import (
optimize_anything, GEPAConfig, EngineConfig, ReflectionConfig,
)
litellm.suppress_debug_info = True
if not os.environ.get("OPENAI_API_KEY"):
os.environ["OPENAI_API_KEY"] = getpass.getpass("Enter your OpenAI API key: ")
TASK_LM = "openai/gpt-4o-mini"
REFLECTION_LM = "openai/gpt-4.1"
MAX_METRIC_CALLS = 100
We install GEPA and LiteLLM, then import the required libraries for prompt optimization and model calls. We securely set up the OpenAI API key and define two models: a task model that solves the problem and a reflection model that improves the prompt. We also set the maximum metric-call budget to keep the optimization process under control.
Building a Deterministic Arithmetic Benchmark Dataset
Copy CodeCopiedUse a different Browser
def make_problems(n, seed=0):
rng = random.Random(seed)
out = []
for _ in range(n):
t = rng.choice(["discount", "travel", "wallet", "chain"])
if t == "discount":
unit = rng.choice([40, 60, 80, 120])
qty = rng.choice([5, 6, 8, 10])
disc = rng.choice([10, 20, 25, 50])
total = unit * qty
gold = total - total * disc // 100
q = (f"A shop sells notebooks at {unit} rupees each. You buy {qty} "
f"notebooks and get a {disc}% discount on the total bill. "
f"How many rupees do you pay in total?")
elif t == "travel":
s1, h1 = rng.choice([40, 50, 60]), rng.choice([2, 3])
s2, h2 = rng.choice([30, 45, 70]), rng.choice([1, 2, 3])
gold = s1 * h1 + s2 * h2
q = (f"A car drives at {s1} km/h for {h1} hours, then at {s2} km/h "
f"for {h2} hours. What is the total distance travelled, in km?")
elif t == "wallet":
tens = rng.choice([3, 5, 7, 9])
fifties= rng.choice([2, 4, 6])
spent = rng.choice([50, 80, 110, 150])
gold = tens * 10 + fifties * 50 - spent
q = (f"You have {tens} ten-rupee notes and {fifties} fifty-rupee "
f"notes. You spend {spent} rupees. How many rupees are left?")
else:
x = rng.choice([6, 9, 12, 15]); y = rng.choice([4, 7, 10]); z = rng.choice([3, 8, 11])
gold = x * 2 - y + z
q = (f"Start with the number {x}. Double it, then subtract {y}, "
f"then add {z}. What number do you end with?")
out.append({"question": q, "answer": gold})
return out
all_problems = make_problems(18, seed=42)
random.Random(1).shuffle(all_problems)
trainset = all_problems[:12]
valset = all_problems[12:]
print(f"Dataset: {len(trainset)} train / {len(valset)} val problems\n")
We create a small deterministic dataset of arithmetic word problems covering discounts, travel distance, wallet calculations, and chained operations. We generate the correct answer for each problem programmatically, which keeps the benchmark reliable and easy to evaluate. We then shuffle the examples and split them into a training set for optimization and a validation set for testing generalization.
Defining the Evaluator and Structured Feedback for GEPA
Copy CodeCopiedUse a different Browser
def build_system_prompt(candidate: dict) -> str:
return (f"{candidate['instructions']}\n\n"
f"OUTPUT FORMAT RULES:\n{candidate['format_rules']}")
def call_task_lm(system_prompt: str, question: str) -> str:
for attempt in range(3):
try:
r = litellm.completion(
model=TASK_LM,
messages=[{"role": "system", "content": system_prompt},
{"role": "user", "content": question}],
temperature=0, max_tokens=600, timeout=60,
)
return r["choices"][0]["message"]["content"] or ""
except Exception as e:
if attempt == 2:
return f"[LM_ERROR] {e}"
return ""
def parse_answers(text: str):
formatted = re.search(r"####\s*(-?\d+)", text)
all_nums = re.findall(r"-?\d+", text)
fmt_val = int(formatted.group(1)) if formatted else None
last_val = int(all_nums[-1]) if all_nums else None
return fmt_val, last_val
def evaluate(candidate: dict, example: dict):
system = build_system_prompt(candidate)
raw = call_task_lm(system, example["question"])
gold = example["answer"]
fmt_val, last_val = parse_answers(raw)
if fmt_val is not None and fmt_val == gold:
score, fb = 1.0, "Correct and correctly formatted."
elif fmt_val is not None and fmt_val != gold:
score, fb = 0.0, (f"WRONG ANSWER. You output '#### {fmt_val}' but the "
f"correct answer is {gold}. Re-check the arithmetic and "
f"the order of the steps.")
elif last_val == gold:
score, fb = 0.5, (f"Right number ({gold}) but FORMAT VIOLATION: the final "
f"line was not exactly '#### {gold}'. Always end with a "
f"line of the form '#### <integer>' and nothing else.")
else:
score, fb = 0.0, (f"WRONG. Correct answer is {gold}. The model's final "
f"number was {last_val}. Likely a multi-step reasoning "
f"slip; show each step and verify before answering.")
oa.log(f"score={score} gold={gold} parsed_fmt={fmt_val} parsed_last={last_val}")
side_info = {
"feedback": fb,
"problem": example["question"],
"gold_answer": gold,
"model_output": raw[:500],
}
return score, side_info
def eval_set(candidate, dataset, label=""):
scores, exact, formatted = [], 0, 0
for ex in dataset:
s, info = evaluate(candidate, ex)
scores.append(s)
if s == 1.0: exact += 1; formatted += 1
elif s == 0.5: formatted += 0
acc = exact / len(dataset)
avg = sum(scores) / len(dataset)
print(f" [{label}] avg_score={avg:.3f} exact_correct+formatted={exact}/{len(dataset)}")
return avg, acc
We define how the candidate prompt is converted into a system prompt and how the task model receives each question. We also create the evaluator that parses the model output, checks whether the final answer follows the required #### <integer> format, and assigns a score. We return structured feedback as actionable side information so that GEPA can determine whether the issue is incorrect reasoning, poor formatting, or both.
Configuring GEPA and Running the Prompt Optimization
Copy CodeCopiedUse a different Browser
seed_candidate = {
"instructions": "Solve the math problem.",
"format_rules": "Give the answer.",
}
print("=== BASELINE (seed prompt) ===")
print("Train:"); base_train = eval_set(seed_candidate, trainset, "train")
print("Val: "); base_val = eval_set(seed_candidate, valset, "val")
print()
objective = (
"Evolve a system prompt (the 'instructions' and 'format_rules' fields) so a "
"small LLM reliably solves multi-step arithmetic word problems AND always "
"ends with a line of exactly the form '#### <integer>'. Maximize the score."
)
background = (
"Scoring: 1.0 = correct number in the exact '#### <int>' format; 0.5 = correct "
"number but wrong/missing format; 0.0 = wrong number. Common failures are (a) not "
"emitting the '####' line, and (b) order-of-operations or multi-step slips. The "
"winning prompt should force explicit step-by-step work, a verification step, and "
"a strict final-answer line."
)
config = GEPAConfig(
engine=EngineConfig(
max_metric_calls=MAX_METRIC_CALLS,
max_workers=4,
parallel=True,
display_progress_bar=True,
seed=0,
),
reflection=ReflectionConfig(
reflection_lm=REFLECTION_LM,
),
)
print("=== RUNNING GEPA (this calls the LLMs; ~1-4 min) ===")
result = optimize_anything(
seed_candidate=seed_candidate,
evaluator=evaluate,
dataset=trainset,
valset=valset,
objective=objective,
background=background,
config=config,
)
We start with a weak seed prompt and evaluate its baseline performance on both the training and validation sets. We then define the optimization objective, background scoring rules, and GEPA configuration, including parallel evaluation and the reflection model. Finally, we run optimize_anything so GEPA can evolve the instruction and format-rule fields using the evaluator feedback.
Comparing the Baseline and GEPA-Optimized Prompts on the Validation Set
Copy CodeCopiedUse a different Browser
best = result.best_candidate
print("\n" + "=" * 78)
print("OPTIMIZED CANDIDATE")
print("=" * 78)
print("\n--- instructions ---\n" + textwrap.fill(best["instructions"], 96))
print("\n--- format_rules ---\n" + textwrap.fill(best["format_rules"], 96))
print("\n" + "=" * 78)
print("BEFORE vs AFTER (held-out validation set)")
print("=" * 78)
print("Seed prompt:"); _ = eval_set(seed_candidate, valset, "val-seed")
print("GEPA prompt:"); _ = eval_set(best, valset, "val-gepa")
print(f"\nBaseline val avg_score : {base_val[0]:.3f}")
print("\n" + "=" * 78)
print("EVOLUTION HISTORY (candidate index -> val score, parents)")
print("=" * 78)
cands = getattr(result, "candidates", [])
vscores = getattr(result, "val_aggregate_scores", [])
parents = getattr(result, "parents", [None] * len(cands))
for i, sc in enumerate(vscores):
par = parents[i] if i < len(parents) else None
tag = " <-- BEST" if cands and cands[i] == best else ""
print(f" cand {i:2d}: val_score={sc:.3f} parents={par}{tag}")
print(f"\nTotal metric calls used : {getattr(result, 'total_metric_calls', 'n/a')}")
print(f"Full validation evals : {getattr(result, 'num_full_val_evals', 'n/a')}")
print("\nDone. Try raising MAX_METRIC_CALLS or swapping REFLECTION_LM for a stronger model.")
We extract the best prompt found by GEPA and print its optimized instruction and format-rule components. We compare the seed prompt and the GEPA-optimized prompt on the held-out validation set to check whether the improvement transfers to unseen examples. We also inspect the evolution history, validation scores, parent relationships, and total metric calls to understand how the prompt improved over the course of optimization.
In conclusion, we used GEPA to show how prompt optimization can move beyond manual trial and error. We created a complete workflow where a task model solves examples, an evaluator scores the outputs, and a reflection model uses detailed feedback to propose better prompts. We also tested the optimized prompt on unseen validation problems, which helps us assess whether the improvement generalizes rather than merely fitting the training set. Also, we built a practical example of reflective prompt evolution in which structured feedback, strict evaluation, and iterative refinement work together to produce a stronger, more reliable prompt.
Check out the Full Codes with Notebook. Also, feel free to follow us on Twitter and don’t forget to join our 150k+ ML SubReddit and Subscribe to our Newsletter. Wait! are you on telegram? now you can join us on telegram as well.
Need to partner with us for promoting your GitHub Repo OR Hugging Face Page OR Product Release OR Webinar etc.? Connect with us
The post Building Reflective Prompt Optimization with GEPA: Multi-Component Prompts, Structured Feedback, and Held-Out Validation appeared first on MarkTechPost.
関連記事
News to Guide
ニュースの次に確認する
発表内容を、現在の料金や仕様と照らし合わせられる関連ガイドです。
今日のまとめ
AIデイリーブリーフで今日の重要ニュースをまとめ読み