LLM WikiAccess-protected knowledge portal

WIKI

DSPy 3.3.0: GEPA와 dspy.Flex로 프롬프트와 코드 흐름을 동시에 최적화하는 방법

요약 2026년 8월 3일 출시된 DSPy 3.3.0은 LLM 파이프라인 최적화의 범위를 한 단계 확장했다. 기존 DSPy 최적화기는 시그니처의 프롬프트 지시문 만 다시 쓸 수 있었다. 3.3.0의 핵심 추가 기능인 dspy.Flex 는 파이썬 코드 흐름까지 최적화 대상으로 삼는다. 중간 Predictor를 나누거나, 결정론적 단계를 순수 파이썬으로 이동하거나, 도우미 함수를 새로 만드는 방식으로 전체 모듈 소스를 재작성할 수

경로human/study/content/ai-frontier/139-dspy-3-3-gepa-flex-code-prompt-optimizer.md
카테고리Study
태그#ai-review #code #flex #gepa #optimizer #prompt #study

요약

2026년 8월 3일 출시된 DSPy 3.3.0은 LLM 파이프라인 최적화의 범위를 한 단계 확장했다. 기존 DSPy 최적화기는 시그니처의 프롬프트 지시문만 다시 쓸 수 있었다. 3.3.0의 핵심 추가 기능인 dspy.Flex파이썬 코드 흐름까지 최적화 대상으로 삼는다. 중간 Predictor를 나누거나, 결정론적 단계를 순수 파이썬으로 이동하거나, 도우미 함수를 새로 만드는 방식으로 전체 모듈 소스를 재작성할 수 있다.

핵심 요약:


배경: DSPy란 무엇인가

DSPy(Demonstrate-Search-Predict, Stanford NLP)는 LLM 파이프라인을 프로그래밍하는 프레임워크다. 일반적인 프롬프트 엔지니어링과 다른 점은 두 가지다.

  1. 선언적 시그니처: dspy.Signature로 입력·출력 타입을 선언하면 구체적인 프롬프트 문자열은 최적화기가 생성한다.
  2. 컴파일(최적화): dspy.Compile(program, optimizer, metric)를 호출하면 최적화기가 프로그램을 실행하며 평가 메트릭을 최대화하는 프롬프트·가중치·제어 흐름을 탐색한다.

이 접근의 핵심 가치는 프롬프트를 손으로 반복하지 않아도 된다는 것이다. 무엇이 "정답"인지(평가 메트릭)를 정의하면 DSPy가 어떻게 물어볼지(프롬프트)를 찾아낸다.


GEPA: 반영·돌연변이 기반 프롬프트 최적화

기존 접근의 한계

DSPy 3.2 이전에는 GRPO(Gradient-based Prompt Optimization)가 주요 최적화기였다. GRPO는 샘플링을 반복하며 그래디언트 추정치로 프롬프트를 갱신한다. 문제는 롤아웃 비용이다. 고품질 수렴을 위해 수백 번의 전체 파이프라인 실행이 필요하다.

GEPA의 설계

GEPA(Generalized Error-driven Prompt Augmentation)는 ICLR 2026에서 발표된 알고리즘을 DSPy에 통합한 것이다. 핵심 아이디어는 실패 추적을 돌연변이 연산자로 사용한다는 점이다.

최적화 루프:

  1. 평가: 현재 프롬프트로 프로그램을 실행, 실패한 사례(오류 메시지, 파스 실패, 메트릭 하한 미달)를 수집.
  2. 반영: 반영 모델(별도 LM 호출)이 실패 이유를 진단하고, 실패 패턴을 수정하는 새 프롬프트 지시문 후보를 생성.
  3. 선택: 유전적 탐색으로 성능이 개선된 후보만 다음 세대로 전달.
import dspy

# 평가 메트릭 정의
def accuracy_metric(example, prediction, trace=None):
    return example.answer == prediction.answer

# 프로그램 선언
class ExtractAnswer(dspy.Module):
    def __init__(self):
        self.predict = dspy.Predict("context, question -> answer")

    def forward(self, context, question):
        return self.predict(context=context, question=question)

# GEPA 최적화
from dspy.teleprompt import GEPA

optimizer = GEPA(metric=accuracy_metric, max_iters=10)
optimized_program = optimizer.compile(ExtractAnswer(), trainset=train_data)

GRPO 대비 35× 적은 롤아웃으로 같거나 더 높은 성능을 달성한다. 데이터가 작거나 API 호출 비용이 높은 상황에서 실질적인 차이를 만든다.


dspy.Flex: 코드 흐름까지 최적화하는 첫 번째 프리미티브

Flex의 핵심 개념

기존 DSPy 최적화는 모듈 구조(Predictor 수, 파이썬 로직)는 고정하고 프롬프트 지시문만 바꿀 수 있었다. dspy.Flex는 이 제약을 제거한다.

dspy.Flex는 모듈 구현 자체가 최적화 대상이다. GEPA는 Flex가 적용된 모듈에 대해 다음을 시도한다:

from dspy import Flex

class SmartSummarizer(Flex):
    """이 모듈의 구현 코드는 GEPA가 최적화한다."""
    signature = "long_document -> concise_summary, key_points: list[str]"

# GEPA + Flex: 프롬프트와 코드를 함께 탐색
optimizer = GEPA(metric=summarization_metric, max_iters=15)
optimized = optimizer.compile(SmartSummarizer(), trainset=train_data)

최적화 후 optimized의 소스를 보면 원래 선언한 Flex가 아니라, GEPA가 생성한 완전히 다른 구현이 들어 있을 수 있다.

언제 Flex를 사용할까

상황Flex 사용 여부
프롬프트 지시문 조정으로 충분한 경우필요 없음 (dspy.Predict + GEPA)
태스크를 더 작은 단계로 분해하면 나아질 것 같은 경우권장
파이프라인 구조 자체가 불확실한 경우권장
결정론적 로직과 LM 호출의 비율이 적절한지 모를 경우권장

dspy.ReActV2: 도구 호출 에이전트 재설계

기존 ReAct의 문제

기존 dspy.ReAct는 Thought-Action-Observation 사이클을 프롬프트로 유도하고 문자열을 파싱하는 방식으로 구현됐다. 이 방식은 세 가지 문제를 낳았다.

  1. 파싱 오류 — LM이 정확한 형식을 지키지 않으면 툴 호출이 실패.
  2. 병렬 호출 불가 — 순차적 Thought-Action 루프이므로 여러 도구를 동시에 부를 수 없음.
  3. 대화 이력 관리 — 타입 없는 문자열 누적으로 컨텍스트 관리가 어려움.

ReActV2의 설계

dspy.ReActV2는 제공자의 네이티브 함수 호출 API를 사용한다. 추론 결과는 JSON으로 직렬화되고 도구 정의는 OpenAI/Anthropic/Google의 tool schema로 자동 변환된다.

import dspy

def search_database(query: str) -> list[dict]:
    """데이터베이스에서 관련 레코드를 검색합니다."""
    return db.search(query)

def calculate_metric(data: list[dict], metric: str) -> float:
    """주어진 데이터에 대해 지표를 계산합니다."""
    return compute(data, metric)

agent = dspy.ReActV2(
    "question -> answer",
    tools=[search_database, calculate_metric],
    max_iters=5,
)

# 병렬 도구 호출: 두 도구를 동시에 호출할 수 있음
result = agent(question="2025년 4분기 매출과 비용을 분석해줘")

dspy.History 타입은 멀티턴 대화 이력을 구조화된 방식으로 관리한다. 이전 도구 호출 결과, 오류, Observation이 모두 타입 정보와 함께 보존된다.


아키텍처 다이어그램: DSPy 3.3.0 최적화 흐름

DSPy 3.3.0 — GEPA + dspy.Flex 최적화 루프 개발자 Signature + Metric 정의 dspy.Compile optimizer=GEPA trainset + metric 평가 실행 전체 파이프라인 실행 실패 사례 수집 GEPA 반영 엔진 ① 실패 패턴 진단 (오류·파스 실패·메트릭 하한) ② 반영 LM으로 수정 후보 생성 ③ 유전적 탐색으로 성능 기준 선별 다음 세대 프롬프트·코드로 반복 dspy.Flex — 코드 흐름 최적화 기존 DSPy: Predictor 구조 고정 → 프롬프트만 수정 dspy.Flex: 모듈 소스 코드 전체가 최적화 대상 • Predictor 분리 / 합치기 • LM 호출 → 순수 파이썬 이동 • 도우미 함수·서브모듈 자동 생성 dspy.ReActV2 — 네이티브 도구 호출 기존 ReAct: 문자열 파싱 기반 Thought-Action-Obs ReActV2: 제공자 네이티브 함수 호출 API 사용 • 병렬 도구 호출 지원 • dspy.History 타입으로 대화 이력 관리 • OpenAI/Anthropic/Google tool schema 자동 변환 최적화 완료 프로그램 최적 프롬프트 + 코드 흐름 내장 GEPA vs GRPO 35× 적은 롤아웃 · 최대 20% 성능 향상
DSPy 3.3.0 — GEPA와 dspy.Flex 최적화 루프

타입 지정 LM 인터페이스

DSPy 3.3.0은 LM 추상화 레이어를 정비했다. 기존에는 모델별 원시 문자열 응답을 파싱해야 했지만, 새 인터페이스는 타입 어댑터를 통해 제공자 간 동작을 표준화한다.

import dspy

# 제공자 전환: 코드 변경 없이 모델만 교체
lm = dspy.LM("openai/gpt-5.6-terra")
# lm = dspy.LM("anthropic/claude-sonnet-5")
# lm = dspy.LM("google/gemini-3.7-flash")

dspy.configure(lm=lm)

# 이하 시그니처·모듈 코드는 동일
class Classify(dspy.Module):
    def __init__(self):
        self.classify = dspy.Predict("text -> category: Literal['spam', 'ham']")

    def forward(self, text: str) -> str:
        return self.classify(text=text).category

제공자 간 추상화의 실질적 이점은 프롬프트·코드를 모델에 종속되지 않게 설계할 수 있다는 점이다. GEPA가 최적화한 프로그램도 LM만 교체하면 다른 제공자에서 그대로 실행된다.


운영 고려사항

GEPA 최적화 비용

GEPA는 GRPO보다 롤아웃이 적지만, 반영 LM 호출이 추가된다. 실질적인 비용 구성:

항목GRPOGEPA
프로그램 롤아웃 수높음낮음 (35× 절감)
반영 LM 호출없음이터레이션당 1회
총 API 비용높음중간
수렴 속도느림빠름

데이터셋이 수십~수백 샘플 수준이라면 GEPA가 더 경제적이다.

dspy.Flex 적용 범위

dspy.Flex는 GEPA가 임의 코드를 생성하므로 최적화 결과를 배포 전에 반드시 검토해야 한다. 특히:

데이터 엔지니어 활용 시나리오

태스크DSPy 적용 방식
비정형 로그 → 구조화 데이터 추출dspy.Predict + GEPA로 추출 정확도 자동 최적화
자연어 쿼리 → SQL 변환dspy.ChainOfThought + Flex로 쿼리 분해 전략 탐색
데이터 품질 이상 설명 생성dspy.ReActV2 + 검색 도구로 이상 원인 추적
멀티소스 데이터 조합 분석dspy.ReActV2 + 병렬 DB 조회 도구

제한사항과 열린 질문

Open question: dspy.Flex가 생성한 코드의 복잡도가 어느 수준 이상이 되면 유지보수 부담이 인간 작성 코드보다 커지는지 실증 사례가 아직 충분하지 않다.


References