DSPy 3.2: 프롬프트 문자열을 버리고 타입 선언으로 LLM을 다루는 방법
왜 지금 봐야 하나
LLM 기반 시스템의 가장 큰 운영 문제 중 하나는 프롬프트 불안정성이다. 모델 버전이 바뀌면 정성껏 다듬은 few-shot 예시가 역효과를 내고, 프로바이더를 교체하면 표현 방식을 처음부터 다시 맞춰야 하며, 파이프라인 앞단 컴포넌트가 바뀌면 뒷단 프롬프트도 연쇄적으로 깨진다. 이 문제를 "엔지니어가 더 조심하면 된다"는 방식으로 접근하는 한 규모 있는 시스템에서는 관리 비용이 선형 이상으로 늘어난다.
DSPy는 이 문제를 근본적으로 다른 각도로 접근한다. 프롬프트 문자열을 직접 작성하는 대신, 입출력 필드를 선언하면 옵티마이저가 자동으로 최적 프롬프트를 탐색한다. 소프트웨어 컴파일러에 비유하자면, 개발자는 고수준 인터페이스(Signature + Module)만 정의하고 컴파일러(Optimizer)가 특정 LLM에서 실제로 잘 작동하는 저수준 지시문(prompt + few-shot)을 생성한다.
DSPy 3.0이 2025년 8월에 공개되면서 프레임워크가 프로덕션 수준으로 재설계됐고, 2026년 들어 3.1(1월), 3.2(4-5월)까지 빠르게 진화했다. 3.2.1이 2026년 5월 5일에 릴리스됐고, 3.3.0b1이 5월 28일 프리릴리스 상태다. 이 글은 DSPy의 핵심 개념과 3.x 계열에서 달라진 운영 표면을 다룬다.
핵심 변화 한눈에 보기
| 버전 | 릴리스 | 핵심 변화 |
|---|---|---|
| 3.0.0 | 2025-08-12 | 프로덕션 재설계: async, 멀티모달, GEPA·SIMBA·GRPO 옵티마이저, thread-safe 설정 |
| 3.1.0 | 2026-01-06 | 추론 모델 네이티브 지원(o1·Claude extended thinking style), File 타입, Python 3.14 호환 |
| 3.2.0 | 2026-04-21 | BetterTogether 옵티마이저 체이닝, LiteLLM 의존성 분리, 입력 필드 타입 검증 |
| 3.2.1 | 2026-05-05 | async 스트리밍·임베딩 캐시 버그 수정, LiteLLM 상위 바운드 제거 |
| 3.3.0b1 | 2026-05-28 | ReActV2 모듈(네이티브 tool-calling), 타입화된 LM 경계, NumPy 선택적 의존성, BaseLM 상태 직렬화 |
DSPy 컴파일 파이프라인 전체 구조
DSPy가 푸는 문제: 프롬프팅은 왜 취약한가
LLM 파이프라인을 수동으로 프롬프팅하면 세 가지 구조적 문제가 생긴다.
1. 모델 결합(Model coupling). "You are a helpful assistant. Think step by step. Format your output as JSON with fields..."처럼 작성한 프롬프트는 GPT-4o에 최적화된 것이다. Gemini나 Claude로 옮기면 다시 조정해야 한다. 프롬프트가 프로바이더-특정 아티팩트가 되는 순간 시스템은 교체 비용이 높아진다.
2. 조합 취약성(Composition fragility). RAG → 요약 → 분류 파이프라인에서 RAG 모듈이 반환 형식을 바꾸면 뒷단 프롬프트도 연쇄적으로 깨진다. 각 컴포넌트가 암묵적인 포맷 계약으로 연결되어 있기 때문이다.
3. 최적화 불가능성. 수동 프롬프트는 사람이 직관으로 작성한 것이다. 실제 최적 프롬프트를 찾으려면 수백 개 변형을 체계적으로 탐색해야 하는데, 수작업으로는 이 공간을 커버하기 어렵다.
DSPy는 이 세 가지를 모두 구조적으로 해결하려는 시도다.
세 가지 추상화: Signature, Module, Optimizer
Signature: 프롬프트 대신 타입 선언
Signature는 LLM 호출의 입출력 명세다. 프롬프트 문자열이 아니라, Python 클래스 또는 인라인 표현으로 정의한다.
# 인라인 선언 (짧은 작업)
predict = dspy.Predict("question -> answer")
# 클래스 선언 (복잡한 작업, 설명 포함)
class SummarizeDocument(dspy.Signature):
"""긴 문서를 핵심 포인트 중심으로 한국어로 요약한다."""
document: str = dspy.InputField()
key_points: list[str] = dspy.OutputField(desc="가장 중요한 3~5개 포인트")
summary: str = dspy.OutputField(desc="200자 이내 요약문")Signature의 핵심 특징은 세 가지다.
- 프로바이더 독립성: 같은 Signature가 GPT-5, Claude, Gemini에서 동일하게 동작한다.
- 컴파일 가능성: Optimizer가 이 선언을 받아서 실제 모델에 맞는 지시문을 생성한다.
- 조합 가능성: 여러 Signature를 Module로 연결할 때 타입 체계가 계약 역할을 한다.
Module: 로직 구성 단위
Module은 하나 이상의 Signature를 조합해 복잡한 LLM 로직을 표현한다. 내장 모듈들은 검증된 프롬프팅 기법을 캡슐화한다.
| 모듈 | 역할 | 특징 |
|---|---|---|
Predict | 기본 Signature 실행 | 단순 입출력, 가장 빠름 |
ChainOfThought | 내부 reasoning 필드 추가 | rationale → answer 패턴 |
ProgramOfThought | Python 코드를 생성·실행 | 코드 인터프리터 활용 |
ReAct | Reasoning + Acting 반복 | 도구 호출과 관찰 루프 |
ReActV2 (3.3b1) | 네이티브 tool-calling | submit 도구 포함, 예외 처리 강화 |
MultiChainComparison | 여러 체인 결과 비교 | 앙상블 추론 |
class RAGPipeline(dspy.Module):
def __init__(self, num_passages=3):
self.retrieve = dspy.Retrieve(k=num_passages)
self.generate = dspy.ChainOfThought("context, question -> answer")
def forward(self, question):
ctx = self.retrieve(question).passages
return self.generate(context=ctx, question=question)Optimizer: 자동 프롬프트 탐색
Optimizer(구 Teleprompter)는 DSPy 프로그램을 받아서 최적 프롬프트를 탐색한다. 입력은 프로그램 + 트레이닝 예시 20~100개 + metric 함수다.
# 1. 탐색할 프로그램 정의
pipeline = RAGPipeline()
# 2. Metric 정의 (0~1 범위)
def answer_metric(gold, pred, trace=None):
return gold.answer.lower() in pred.answer.lower()
# 3. Optimizer 실행
optimizer = dspy.MIPROv2(metric=answer_metric, auto="medium")
compiled = optimizer.compile(pipeline, trainset=train_examples)
# 4. 저장 및 재사용
compiled.save("rag_compiled.json")
loaded = RAGPipeline().load("rag_compiled.json")컴파일 흐름의 내부 동작
컴파일은 크게 세 단계로 작동한다.
1단계: 부트스트래핑(Bootstrapping). 트레이닝 예시로 파이프라인을 실행하고, metric을 통과한 예시의 중간 reasoning trace를 수집한다. 이 trace가 few-shot 후보 풀이 된다.
2단계: 지시문 탐색(Instruction search). MIPROv2나 GEPA 같은 옵티마이저가 다양한 지시문 변형을 생성하고, 각 변형을 dev set에서 평가한다. 베이지안 서로게이트 모델(MIPROv2) 또는 진화 알고리즘(GEPA)으로 탐색 공간을 효율적으로 커버한다.
3단계: 검증 및 선택(Validation + selection). 지시문·few-shot 조합 중 metric 최고점을 받은 것을 최종 컴파일 결과로 확정한다. 이 결과물이 JSON으로 직렬화되어 프로덕션에 배포된다.
GEPA의 비용 효율: 3.0에서 추가된 GEPA는 유전자 알고리즘과 파레토 프론트 탐색을 결합해 MIPROv2 대비 1/35 비용으로 20% 더 높은 성능을 보인다. 탐색 예산이 제한적인 팀에 실용적 선택지다.
DSPy 3.x 계열의 변화 궤적
3.0.0 (2025-08-12): 프로덕션 재설계
2.x 대비 가장 큰 구조 변화는 다음 세 가지다.
- Async 네이티브: 고동시성 서빙을 위해 비동기 처리를 코어에 통합했다.
dspy.asyncify()없이도 비동기 컨텍스트에서 모듈이 동작한다. - 멀티모달 타입: Image, Audio 입출력 필드를 Signature에서 직접 선언할 수 있다.
- Thread-safe 설정: 전역 상태 오염 없이 여러 스레드가 독립된 LM 설정을 사용할 수 있다.
3.1.0 (2026-01-06): 추론 모델 지원
o1, o3, Claude extended thinking 스타일 모델에서 reasoning 토큰이 별도로 생성된다는 점을 DSPy가 명시적으로 다루기 시작했다. File 타입이 Signature에 추가되어 문서 기반 작업에서 직접 파일 참조가 가능해졌다.
3.2.0 (2026-04-21): BetterTogether와 LiteLLM 분리
BetterTogether는 프롬프트 탐색과 파인튜닝을 전략 문자열로 체이닝한다.
optimizer = dspy.BetterTogether(
metric=answer_metric,
p=dspy.GEPA(metric=answer_metric), # 프롬프트 탐색기
w=dspy.BootstrapFinetune(metric=answer_metric) # 파인튜닝기
)
compiled = optimizer.compile(
pipeline,
trainset=train_examples,
strategy="p -> w -> p" # 프롬프트 → 파인튜닝 → 재프롬프트
)strategy="p -> w -> p" 패턴의 의미: 먼저 프롬프트를 최적화하고, 그 프로그램으로 학습 데이터를 생성해 모델을 파인튜닝하고, 파인튜닝된 모델에서 다시 프롬프트를 최적화한다. 각 단계는 dev set에서 독립적으로 평가되고 최고 결과가 다음 단계로 전달된다.
LiteLLM 의존성 분리: 이전까지 LiteLLM이 직접 의존성이었지만 3.2.0부터 선택적으로 바뀌었다. Optuna도 마찬가지다. pip install dspy는 이제 핵심 기능만 설치하고, 고급 옵티마이저가 필요한 경우 별도 설치한다.
3.3.0b1: ReActV2와 타입화된 LM 경계
3.3.0b1(2026-05-28 프리릴리스)에서 가장 중요한 변화는 두 가지다.
ReActV2: 도구 호출 신뢰성 강화
ReAct 모듈이 ReActV2로 대체됐다. 주요 개선점은 다음과 같다.
- callable → dspy.Tool 자동 변환: Python 함수를 직접 넘기면 도구로 등록된다.
- submit 도구 내장: 최종 답변 제출을 위한 내부 submit 도구가 항상 존재한다. 모델이 submit 없이 멈추면 강제 제출을 유도한다.
- 알 수 없는 도구와 예외 처리: 도구 이름이 잘못되거나 실행 중 에러가 나도 루프가 깨지지 않는다.
- 직렬화된 history 입력: 이전 대화 이력을 직렬화 형태로 받아 상태 유지 에이전트 구현이 쉬워졌다.
타입화된 LM 경계
BaseLM이 이제 타입화된 런타임 상태를 소유한다. dump_state() / load_state()로 LM 설정을 직렬화·복원할 때 API 키는 제외되고, 이전 버전으로 저장된 상태와 하위 호환성이 유지된다. NumPy가 선택적 의존성이 되면서 기본 설치 용량이 줄었다.
운영 관점의 판단 기준
어떤 팀에 적합한가
DSPy는 다음 조건이 갖춰진 팀에서 투자 대비 효과가 높다.
- 평가 데이터가 있거나 만들 수 있다. 20개 이상의 입출력 예시와 명확한 metric이 없으면 옵티마이저를 돌릴 수 없다.
- 모델 교체가 예상된다. 특정 프로바이더에 종속되지 않고 여러 LLM을 실험해야 할 때 Signature 기반 코드는 교체 비용이 낮다.
- 파이프라인이 복잡하다. 단일 LLM 호출은 직접 프롬프팅이 더 빠를 수 있다. 여러 모듈이 연결된 파이프라인에서 DSPy의 가치가 커진다.
한계와 주의점
- 컴파일 비용: MIPROv2로 20개 예시를 탐색하면 수백~수천 LLM 호출이 발생한다. GEPA는 비용을 줄이지만 여전히 유의미한 비용이다.
- 디버깅 불투명성: 최적화된 프롬프트는 사람이 직접 작성하지 않아 "왜 이 지시문인가"를 추적하기 어렵다.
- few-shot 품질 의존성: 트레이닝 예시 품질이 낮으면 부트스트래핑 결과도 낮아진다. 쓰레기를 넣으면 쓰레기가 나온다.
- ReActV2는 아직 프리릴리스(3.3.0b1): 프로덕션 도입은 3.3.0 정식 릴리스를 기다리는 것이 안전하다.
운영 체크리스트
- [ ] 평가 데이터셋과 metric 함수가 준비됐는가? (최소 20개 입출력 예시)
- [ ] 예산과 속도 요구사항에 맞는 옵티마이저를 선택했는가? (빠름: BootstrapFewShot, 효율: GEPA, 강력: MIPROv2)
- [ ] 컴파일된 프로그램을 JSON으로 저장하고 버전 관리하는가?
- [ ] 모델 교체 시 컴파일 결과를 재사용할 수 없다는 점을 인지했는가? (모델 바뀌면 재컴파일 필요)
- [ ] dev set을 트레이닝 셋과 분리해서 평가 오염을 방지했는가?
- [ ] BetterTogether 사용 시 파인튜닝 스텝에 별도 컴퓨팅 예산을 배정했는가?
- [ ] ReActV2는 3.3.0b1 프리릴리스임을 확인하고 프로덕션 사용을 신중하게 검토했는가?
- [ ] 컴파일 비용(LLM API 호출 수)을 사전에 추정했는가?
References
- Stanford NLP Group, "DSPy: The framework for programming—not prompting—language models", GitHub repository. https://github.com/stanfordnlp/dspy
- DSPy Documentation, "Signatures", dspy.ai. https://dspy.ai/learn/programming/signatures/
- DSPy Documentation, "Roadmap", dspy.ai. https://dspy.ai/roadmap/
- PyPI, "dspy", release history 3.2.1 (2026-05-05) and 3.3.0b1 (2026-05-28). https://pypi.org/project/dspy/#history
- Mohanty, A., "DSPy 3.0: The Evolution from Prompt Engineering to Programming", Medium, 2025. https://devoscientist.medium.com/dspy-3-0-the-evolution-from-prompt-engineering-to-programming-b8df5ead5c60
- DSPy Releases, "3.2.0: BetterTogether optimizer chaining, LiteLLM decoupling", GitHub Releases. https://github.com/stanfordnlp/dspy/releases
- DeepWiki, "DSPy Introduction and Core Concepts". https://deepwiki.com/stanfordnlp/dspy/1.1-introduction-and-core-concepts
- FutureAGI, "What is DSPy? Stanford's Compiled Prompt Framework in 2026". https://futureagi.com/blog/what-is-dspy-2026/