고급 프롬프트 최적화를 위한 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)

더 알아보기 (Learn more)