DSPy GEPA: 리플렉션 기반 프롬프트 옵티마이저
게시일: 2025년 10월 26일 | 원문 작성일: 2025년 9월 24일 | 출처: DSPy API Documentation | 논문: GEPA: Reflective Prompt Evolution Can Outperform Reinforcement Learning (Agrawal et al., 2025) | 원문 보기
핵심 요약
GEPA(Genetic-Pareto)는 강화학습을 능가할 수 있는 리플렉션 기반 옵티마이저예요. 단순히 점수만 받는 게 아니라 텍스트 피드백을 활용해서 프롬프트가 왜 그런 점수를 받았는지 이해하고, 그걸 바탕으로 개선 방향을 찾아내죠. 이 덕분에 아주 적은 rollout으로도 고성능 프롬프트를 만들어낼 수 있어요.
주요 특징:
- 텍스트 피드백 활용: 단순 점수가 아니라 왜 실패했는지, 어떻게 개선할지에 대한 자연어 피드백을 최적화에 활용
- Pareto 프론티어 기반 진화: 하나의 최고 후보만 유지하는 게 아니라, 여러 특화된 전략들을 동시에 유지하며 탐색
- 적은 rollout으로 높은 성능: 리플렉션을 통해 실패를 분석하고 targeted하게 개선해서 샘플 효율이 높음
- 추론 시점 검색 지원: 배치 inference에서 각 태스크마다 최적의 아웃풋을 찾는 test-time search로도 사용 가능
GEPA가 뭔가요?
GEPA는 DSPy 프레임워크를 위한 evolutionary optimizer인데요, LLM을 활용해 복잡한 시스템의 텍스트 컴포넌트(프롬프트 같은 것들)를 진화시켜요. 기존의 프롬프트 최적화 방법들과 다른 점은, 실행 trace 전체를 캡처해서 각 predictor가 어떻게 동작했는지 보고, 거기에 reflection을 적용해서 새로운 instruction을 제안한다는 거예요.
예를 들어, 당신의 DSPy 프로그램이 문서 검색 → 답변 생성 파이프라인이라고 해봐요. GEPA는 검색 단계가 잘못된 문서를 가져왔다는 걸 파악하고, “음, 검색 instruction이 너무 일반적이네. 좀 더 구체적인 키워드를 찾도록 바꿔보자” 같은 식으로 reflection을 하면서 개선해나가는 거죠.
왜 GEPA를 써야 할까요?
| 기존 방식의 문제 | GEPA의 해결책 |
|---|---|
| 단순 점수만 받아서 왜 실패했는지 모름 | 텍스트 피드백으로 실패 원인을 파악하고 targeted 개선 |
| 하나의 “최고” 후보만 유지해서 local optima에 빠지기 쉬움 | Pareto 프론티어로 여러 특화된 전략을 동시에 유지 |
| 블랙박스 최적화라 중간 과정을 활용 못함 | 실행 trace를 전부 보고 각 단계의 문제를 진단 |
| 수백~수천 번의 evaluation이 필요 | 리플렉션으로 샘플 효율을 높여 적은 rollout으로 고성능 달성 |
어떻게 작동하나요?
GEPA의 3가지 핵심 아이디어
1. 리플렉션 기반 mutation
단순히 랜덤하게 변형하는 게 아니라, LLM이 실제 실행 trace를 보고 “아, 이 부분이 문제구나”라고 분석한 다음, 그 문제를 해결할 수 있는 새로운 instruction을 제안해요. 사람이 디버깅하듯이 말이죠.
2. 풍부한 텍스트 피드백
GEPA는 단순 점수(0.7점 같은 것) 말고도 evaluation log, 실패한 파싱, constraint 위반, 에러 메시지 같은 모든 텍스트 정보를 최적화 signal로 활용할 수 있어요. 예를 들어 “JSON 파싱 실패했어요, 따옴표가 빠졌네요” 같은 구체적인 피드백을 줄 수 있죠. 이게 진짜 강력해요.
3. Pareto 프론티어 유지
하나의 “최고” 후보만 진화시키면 금방 local optima에 갇혀버려요. GEPA는 Pareto 프론티어, 즉 “적어도 하나의 evaluation 인스턴스에서 최고 점수를 받은 후보들의 집합”을 유지해요. 그래서 다양한 전략들이 공존하면서 서로 보완하고, 나중에 merge까지 할 수 있죠.
실전 사용법
기본 사용
GEPA를 사용하려면 먼저 피드백을 제공하는 metric 함수가 필요해요:
그리고 GEPA를 초기화하고 compile하면 돼요:
예산 설정
| 설정 | 용도 | 특징 |
|---|---|---|
auto=‘light’ | 빠른 실험 | 최소한의 rollout으로 빠르게 프로토타입 |
auto=‘medium’ | 균형잡힌 최적화 | 대부분의 use case에 적합한 기본값 |
auto=‘heavy’ | 철저한 최적화 | 최고 성능이 필요할 때, 시간이 걸려도 괜찮으면 |
max_full_evals=N | 커스텀 예산 | 전체 validation evaluation 횟수 제한 |
max_metric_calls=N | 커스텀 예산 | metric 호출 횟수 제한 (더 세밀한 제어) |
💡 Tip: auto, max_full_evals, max_metric_calls 중 정확히 하나만 지정해야 해요.
추론 시점 검색 (Inference-Time Search)
GEPA를 배치 inference에서 test-time search 메커니즘으로도 쓸 수 있어요. 각 태스크마다 진화 과정에서 발견된 최고 점수 아웃풋을 얻을 수 있죠:
좋은 피드백 metric 만들기
GEPA의 성능은 피드백의 품질에 크게 좌우돼요. 다음 원칙들을 따르면 좋아요:
실용적인 레시피
- 기존 artifact 활용: 이미 있는 로그, 유닛 테스트, evaluation 스크립트, profiler 아웃풋을 그냥 표면화하면 돼요. 새로 만들 필요 없어요.
- 결과 분해: 점수를 목표별로 쪼개세요 (정확도, 레이턴시, 비용, 안전성 등). 에러를 단계별로 어트리뷰트하세요.
- trajectory 노출: 파이프라인의 각 단계에 라벨을 붙이고, pass/fail을 중요한 에러 메시지와 함께 보고하세요.
- 체크에 기반: 자동 validator(유닛 테스트, 스키마, 시뮬레이터)를 써보세요. 검증 불가능한 태스크에는 LLM-as-a-judge도 괜찮아요.
- 명확함 우선: 에러 커버리지와 의사결정 포인트에 집중하세요. 기술적 복잡도는 중요하지 않아요.
피드백 예시들
| 태스크 유형 | 좋은 피드백 예시 |
|---|---|
| 문서 검색 (HotpotQA) | “정답 문서 중 ‘Climate Change Impact’를 검색하지 못했어요. ‘Paris Agreement’는 정확히 검색했고, ‘Ocean Acidification’은 불필요하게 가져왔네요.” |
| Multi-objective (PUPA) | “품질 점수: 0.8 (good), 프라이버시 점수: 0.4 (poor - 민감 정보 노출). 트레이드오프: 상세한 답변이 프라이버시를 희생했어요.” |
| 코드 생성 파이프라인 | ”파싱: 성공, 컴파일: 실패 (line 23에서 타입 에러), 실행: 불가. ‘int’를 ‘str’과 concatenate할 수 없어요.” |
| JSON 생성 | ”JSON 파싱 실패. 문제: line 5의 trailing comma, ‘description’ 필드 누락, ‘price’가 string인데 number여야 함.” |
주요 파라미터
| 파라미터 | 설명 | 기본값 |
|---|---|---|
metric | 피드백과 평가를 위한 metric 함수 (필수) | - |
reflection_lm | Reflection에 사용할 LM. 강력한 모델 권장 (필수) 추천: GPT-5, Claude Sonnet 4.5 | - |
reflection_minibatch_size | 각 GEPA step에서 reflection에 사용할 예제 수 | 3 |
candidate_selection_strategy | 후보 선택 전략: ‘pareto’ (확률적 선택) 또는 ‘current_best' | 'pareto’ |
use_merge | 성공적인 프로그램 variant를 merge할지 여부 | True |
track_stats | 상세한 결과와 모든 제안된 프로그램을 반환할지 여부 | False |
track_best_outputs | Validation set의 최고 아웃풋을 추적할지 (inference-time search용) | False |
num_threads | 평가를 병렬화할 스레드 수 | None |
log_dir | 로그와 체크포인트를 저장할 디렉토리 | None |
seed | 재현성을 위한 랜덤 시드 | 0 |
Advanced: 커스텀 instruction proposer
대부분의 경우는 기본 instruction proposer로 충분해요. 하지만 이런 특수한 케이스에서는 커스터마이징이 필요할 수 있어요:
- 멀티모달 처리: 텍스트와 함께
dspy.Image입력 처리 - 세밀한 constraint 제어: 피드백만으로는 부족한 instruction 길이/포맷 요구사항
- 도메인 특화 지식: 피드백으로 제공할 수 없는 전문 용어나 컨텍스트
- Provider별 최적화: OpenAI, Anthropic 등 특정 LLM provider에 특화된 프롬프팅
- 외부 지식 통합: 런타임에 데이터베이스, API, 지식베이스 접근
이런 경우에는 ProposalFn 프로토콜을 구현하거나, 멀티모달의 경우 MultiModalInstructionProposer()를 쓸 수 있어요. 자세한 내용은 Advanced Features 문서를 참고하세요.
상세 결과 분석하기
track_stats=True로 설정하면 최적화 과정의 모든 디테일을 볼 수 있어요:
실전 팁
💡 Reflection LM은 강력할수록 좋아요
GEPA는 reflection model의 품질에 민감해요. GPT-5, Claude Sonnet 4.5 같은 강력한 모델을 쓰면 훨씬 좋은 결과를 얻을 수 있어요. temperature=1.0으로 설정해서 다양한 제안을 받는 것도 좋고요.
💡 피드백은 구체적일수록 좋아요
”점수가 낮아요” 대신 “검색된 문서 3개 중 2개가 질문과 무관해요. ‘AI safety’ 키워드로 검색했는데 ‘AI ethics’ 문서만 나왔네요”처럼 구체적으로 쓰세요.
💡 로그 디렉토리를 설정하세요
log_dir을 설정하면 모든 후보와 로그가 저장돼서, 중간에 중단되어도 재개할 수 있고, 나중에 분석할 수도 있어요.
⚠️ 주의: Metric은 일관적이어야 해요
GEPA는 metric이 module-level과 predictor-level에서 같은 점수를 반환할 거라고 기대해요. 불일치가 감지되면 경고가 뜨는데, warn_on_score_mismatch=False로 끌 수는 있지만 권장하지 않아요.
누가 GEPA를 써야 할까요?
GEPA는 이런 상황에 특히 좋아요:
- 복잡한 multi-step 파이프라인을 최적화할 때 (예: 검색 → 재순위화 → 생성 → 검증)
- 실패 원인을 명확히 진단할 수 있는 도메인 (코드 생성, structured output, multi-objective tasks)
- 샘플 효율이 중요할 때 (evaluation이 비싸거나 느린 경우)
- 여러 특화된 전략이 필요한 문제 (한 가지 프롬프트로 모든 경우를 커버하기 어려운 경우)
- Test-time search가 필요할 때 (각 인스턴스별로 최적의 접근을 찾고 싶을 때)
반대로 단순한 태스크나 피드백을 구조화하기 어려운 경우에는 더 간단한 옵티마이저(MIPROv2, BootstrapFewShot 등)가 나을 수 있어요.
더 알아보기
- GEPA 논문 (arxiv:2507.19457) - 알고리즘 디테일과 실험 결과
- GEPA GitHub - 코어 evolution 파이프라인 구현
- DSPy GEPA 튜토리얼 - 실전 예제와 use case들
- GEPA Advanced Features - 커스텀 proposer와 고급 기능
참고: 이 글은 DSPy 프로젝트의 공식 API 문서에서 GEPA optimizer 부분을 번역하고 요약한 것입니다.
원문: https://dspy.ai/api/optimizers/GEPA/overview/
생성: Claude (Anthropic)