고급 프롬프트 최적화를 위한 DSPy 최적화기
고급 프롬프트 최적화를 위한 DSPy 최적화기
DSPyOptimizer 는 DSPy의 MIPROv2 알고리즘을 사용해 Ragas 메트릭을 위한 최첨단 프롬프트 최적화를 제공해요. instruction 최적화와 demonstration 최적화를 결합해서 단순한 진화(evolutionary) 방식보다 더 좋은 프롬프트를 찾아내죠.
출처: 문서
본문
개요
DSPyOptimizer 는 MIPROv2(Multi-prompt Instruction Proposal with Ranked Outcomes)를 사용해 메트릭 프롬프트를 다음과 같은 방식으로 최적화해요.
- Instruction 최적화 : 여러 프롬프트 변형을 생성하고 테스트해요
- Demonstration 최적화 : 효과적인 few-shot 예시를 자동으로 선택해요
- 결합 탐색 : instruction과 demonstration 공간을 동시에 탐색해요
이 방식은 보통 단순한 GeneticOptimizer 보다 더 나은 결과를 내요. 특히 고품질의 어노테이션(annotated) 데이터가 있을 때 그렇죠.
설치
DSPy는 선택 의존성(optional dependency)이에요. 다음과 같이 설치할 수 있어요.
# Using uv (recommended)
uv add "ragas[dspy]"
# Using pip
pip install "ragas[dspy]"
기본 사용법
사전 요구사항
다음이 필요해요.
- 어노테이션 데이터셋 : 메트릭의 ground truth 점수
- 프롬프트가 있는 메트릭 :
PydanticPrompt를 사용하는 메트릭(대부분의 Ragas 메트릭) - LLM : 최적화에 쓸 LLM(gpt-4o-mini 권장 - 비용 측면)
빠른 시작
from openai import OpenAI
from ragas.llms import llm_factory
from ragas.metrics.collections import Faithfulness
from ragas.optimizers import DSPyOptimizer
from ragas.config import InstructionConfig
# Setup LLM for optimization
client = OpenAI()
llm = llm_factory("gpt-4o-mini", client=client)
# Initialize metric
metric = Faithfulness(llm=llm)
# Create annotated dataset (see below for format)
dataset = create_annotated_dataset()
# Configure DSPy optimizer
config = InstructionConfig(
llm=llm,
optimizer=DSPyOptimizer(
num_candidates=10, # Try 10 prompt variations
max_bootstrapped_demos=5, # Generate up to 5 examples
max_labeled_demos=5, # Use up to 5 human annotations
)
)
# Optimize the metric's prompts
metric.optimize_prompts(dataset, config)
# Save optimized prompts for reuse
metric.save_prompts("optimized_faithfulness.json")
어노테이션 데이터셋 형식
DSPy 최적화기는 ground truth 어노테이션을 필요로 해요.
from ragas.dataset_schema import (
PromptAnnotation,
SampleAnnotation,
SingleMetricAnnotation
)
# Create prompt annotations
prompt_annotation = PromptAnnotation(
prompt_input={"user_input": "...", "response": "..."},
prompt_output={"score": 0.9}, # Actual metric output
edited_output=None, # Or corrected output if needed
)
# Create sample with annotations
sample = SampleAnnotation(
metric_input={"user_input": "...", "response": "..."},
metric_output=0.9, # Ground truth score
prompts={"faithfulness_prompt": prompt_annotation},
is_accepted=True, # Whether to use in optimization
)
# Create dataset
dataset = SingleMetricAnnotation(
name="faithfulness",
samples=[sample, ...] # Need 20-50+ samples for best results
)
고급 설정
최적화 파라미터
MIPROv2의 동작을 제어할 수 있어요.
optimizer = DSPyOptimizer(
num_candidates=20, # More candidates = better prompts, higher cost
max_bootstrapped_demos=10, # Auto-generated few-shot examples
max_labeled_demos=10, # Human-annotated examples to use
init_temperature=1.0, # Exploration temperature (0.0-2.0)
)
파라미터 가이드:
| Parameter | Default | Description | Cost Impact |
|---|---|---|---|
| num_candidates | 10 | Prompt variations to try | High - linear scaling |
| max_bootstrapped_demos | 5 | Auto-generated examples | Medium - adds LLM calls |
| max_labeled_demos | 5 | Human annotations to use | Low - uses existing data |
| init_temperature | 1.0 | Exploration randomness | None - algorithmic only |
비용 최적화
MIPROv2 최적화는 비용이 많이 들 수 있어요. 다음과 같이 비용을 줄일 수 있어요.
# Budget-conscious configuration
budget_optimizer = DSPyOptimizer(
num_candidates=5, # Fewer candidates
max_bootstrapped_demos=2, # Fewer generated examples
max_labeled_demos=3, # More reliance on annotations
init_temperature=0.5, # Less exploration
)
# Use cheaper LLM for optimization
cheap_llm = llm_factory("gpt-4o-mini", client=client)
config = InstructionConfig(llm=cheap_llm, optimizer=budget_optimizer)
비용 추정:
- 후보(candidate)당 약 10~50회 LLM 호출
- bootstrapped demo당 약 5~10회 호출
- 총계:
num_candidates * 30 + max_bootstrapped_demos * 7회 호출(대략)
GeneticOptimizer와 비교
DSPyOptimizer를 써야 할 때
✅ 다음 경우에는 DSPyOptimizer 를 사용하세요.
- 고품질 어노테이션 예시가 50개 이상 있을 때
- 최고의 메트릭 정확도가 필요할 때
- 최적화에 LLM 호출 100~500회를 감당할 수 있을 때
- 프로덕션용 중요 메트릭을 최적화할 때
GeneticOptimizer를 써야 할 때
✅ 다음 경우에는 GeneticOptimizer 를 사용하세요.
- 어노테이션 데이터가 제한적일 때(<20개 예시)
- 더 빠르고 저렴한 최적화가 필요할 때
- 초기 프로토타이핑을 하고 있을 때
- 단순한 instruction-only 최적화로 충분할 때
나란히 비교
from ragas.optimizers import GeneticOptimizer, DSPyOptimizer
# Genetic optimizer - simpler, faster, cheaper
genetic_config = InstructionConfig(
llm=llm,
optimizer=GeneticOptimizer(
max_steps=50, # Evolution steps
population_size=10, # Population per generation
)
)
# DSPy optimizer - advanced, better results, more expensive
dspy_config = InstructionConfig(
llm=llm,
optimizer=DSPyOptimizer(
num_candidates=10,
max_bootstrapped_demos=5,
max_labeled_demos=5,
)
)
# Compare results
metric_genetic = Faithfulness(llm=llm)
metric_genetic.optimize_prompts(dataset, genetic_config)
metric_dspy = Faithfulness(llm=llm)
metric_dspy.optimize_prompts(dataset, dspy_config)
# Evaluate on holdout set
test_scores_genetic = metric_genetic.batch_score(test_set)
test_scores_dspy = metric_dspy.batch_score(test_set)
일반적인 결과:
| Metric | GeneticOptimizer | DSPyOptimizer | Improvement |
|---|---|---|---|
| Faithfulness | 0.82 | 0.89 | +8.5% |
| Answer Relevancy | 0.75 | 0.84 | +12% |
| Context Precision | 0.78 | 0.86 | +10% |
여러 메트릭 다루기
같은 방식으로 여러 메트릭을 최적화할 수 있어요.
from ragas.metrics.collections import (
Faithfulness,
AnswerRelevancy,
ContextPrecision
)
metrics = {
"faithfulness": Faithfulness(llm=llm),
"answer_relevancy": AnswerRelevancy(llm=llm),
"context_precision": ContextPrecision(llm=llm),
}
# Optimize each metric
for name, metric in metrics.items():
print(f"Optimizing {name}...")
# Load metric-specific dataset
dataset = load_annotated_dataset(name)
# Optimize
metric.optimize_prompts(dataset, dspy_config)
# Save
metric.save_prompts(f"optimized_{name}.json")
문제 해결
Import 오류
ImportError: DSPy optimizer requires dspy-ai 가 나오면:
# Install the DSPy extra
uv add "ragas[dspy]"
# or
pip install "ragas[dspy]"
최적화가 너무 오래 걸려요
LLM 호출 수를 줄이세요:
fast_optimizer = DSPyOptimizer(
num_candidates=3, # Minimum viable
max_bootstrapped_demos=1,
max_labeled_demos=3,
)
결과가 좋지 않아요
흔한 원인은 다음과 같아요.
- 데이터 부족 : 고품질 어노테이션 20개 이상 필요
- 낮은 품질의 어노테이션 : ground truth 점수가 정확한지 확인
- 잘못된 LLM : 최적화에는 gpt-4o 이상 사용
- 잘못된 설정 : 먼저 기본 파라미터를 시도
메모리 문제
MIPROv2는 큰 데이터셋에서 메모리를 많이 사용할 수 있어요.
# Process in smaller batches
from ragas.dataset_schema import SingleMetricAnnotation
def optimize_in_batches(dataset, batch_size=20):
# Split dataset
batches = [
dataset.select(range(i, min(i + batch_size, len(dataset.samples))))
for i in range(0, len(dataset.samples), batch_size)
]
# Optimize on first batch for speed
best_batch = batches[0]
metric.optimize_prompts(best_batch, dspy_config)
모범 사례
데이터 품질
- 다양한 예시 : 엣지 케이스와 일반적인 시나리오를 모두 다뤄요
- 정확한 라벨 : ground truth 점수를 다시 확인해요
- 충분한 양 : 프로덕션 메트릭에는 50개 이상 예시
최적화 전략
- 작게 시작 : 먼저 3~5개 후보로 테스트
- 반복 : 필요에 따라 파라미터를 점진적으로 늘려가기
- 검증 : 항상 holdout 셋으로 테스트
- 캐시 : 최적화된 프롬프트를 저장해서 재실행 방지
프로덕션 배포
# 1. Optimize offline
metric = Faithfulness(llm=optimization_llm)
metric.optimize_prompts(training_dataset, dspy_config)
metric.save_prompts("production_faithfulness.json")
# 2. Load in production
production_metric = Faithfulness(llm=production_llm)
production_metric.load_prompts("production_faithfulness.json")
# 3. Use for evaluation
results = production_metric.batch_score(production_samples)