반응형
LLM 앱 만들면 프롬프트 짜고, 테스트하고, 결과 별로면 수정하고, 또 테스트하고. 이 루프가 끝이 없습니다. DSPy는 이걸 뒤집습니다. 입력과 출력만 정의하면 최적 프롬프트를 AI가 찾아줍니다.
[핵심 요약]
→ DSPy: Declarative Self-improving Python — Stanford 연구팀 개발
→ 핵심 철학: "프롬프트를 짜는 것" → "프로그램을 선언하고 컴파일"
→ 3가지 핵심 구성: Signature(입출력 선언) + Module(추론 방식) + Optimizer(자동 최적화)
→ 주요 옵티마이저: BootstrapFewShot, MIPROv2, SIMBA, GEPA
→ 효과: 팩트 정확도 30~45% 향상, 환각률 25% 감소 (논문 기준)
→ 지원 모델: Claude, GPT, Gemini, Llama, Ollama 등 모든 LLM
→ 가장 잘 맞는 케이스: 반복 실행 태스크 + 측정 가능한 품질 기준 있을 때
프롬프트 엔지니어링 vs DSPy
기존 프롬프트 엔지니어링:
→ 프롬프트 직접 작성 (수백~수천 단어)
→ 결과 별로면 수동 수정
→ 모델 바꾸면 처음부터 다시
→ 어떤 프롬프트가 왜 잘 되는지 불명확
→ "감"에 의존한 반복 시행착오
DSPy:
→ 입력/출력 타입만 선언
→ 예시 데이터 + 평가 기준 정의
→ 옵티마이저가 자동으로 최적 프롬프트 탐색
→ 모델 바꿔도 자동 재최적화
→ 측정 가능하고 재현 가능한 개선
머신러닝 비유:
손으로 피처 엔지니어링 → AutoML이 자동화
손으로 프롬프트 작성 → DSPy가 자동화
실전 1 — 설치 및 기본 구조
pip install dspy
pip install anthropic # 또는 openai
Signature — 입출력 선언
DSPy의 가장 기본 단위입니다. 무엇을 받아서 무엇을 출력하는지 선언합니다.
import dspy
# LM 설정
lm = dspy.LM("anthropic/claude-sonnet-4-5", api_key="your-key")
dspy.configure(lm=lm)
# ── Signature 정의 ──────────────────────────────────────
# 형식: "입력1, 입력2 -> 출력1, 출력2"
# 필드 설명이 곧 프롬프트 힌트가 됨
class SentimentClassifier(dspy.Signature):
"""고객 리뷰의 감정을 분류합니다."""
review: str = dspy.InputField(desc="분석할 고객 리뷰 텍스트")
sentiment: str = dspy.OutputField(desc="positive, negative, neutral 중 하나")
confidence: float = dspy.OutputField(desc="0.0~1.0 사이 신뢰도")
reason: str = dspy.OutputField(desc="분류 이유 한 줄 설명")
# ── Module로 실행 ────────────────────────────────────────
classifier = dspy.Predict(SentimentClassifier)
result = classifier(review="배송이 빠르고 제품도 마음에 들어요. 재구매 의향 있습니다.")
print(result.sentiment) # positive
print(result.confidence) # 0.95
print(result.reason) # 긍정적 경험과 재구매 의향을 명확히 표현
[Signature의 역할]
→ 필드 이름 + desc가 자동으로 프롬프트에 반영됨
→ 타입 어노테이션으로 출력 검증
→ Docstring이 태스크 설명으로 사용됨
→ 프롬프트를 직접 작성하지 않아도 됨
실전 2 — Module: 추론 방식 선택
같은 Signature라도 추론 방식을 다르게 쓸 수 있습니다.
import dspy
class QASignature(dspy.Signature):
"""주어진 컨텍스트를 기반으로 질문에 답합니다."""
context: str = dspy.InputField(desc="답변 근거가 되는 문서 내용")
question: str = dspy.InputField(desc="사용자 질문")
answer: str = dspy.OutputField(desc="간결하고 정확한 답변")
# ── Predict: 단순 예측 ─────────────────────────────────
simple_qa = dspy.Predict(QASignature)
# ── ChainOfThought: 단계별 추론 (복잡한 문제에 유리) ───
cot_qa = dspy.ChainOfThought(QASignature)
# ── ReAct: 도구 사용 포함 에이전트 추론 ────────────────
def search_tool(query: str) -> str:
"""실제로는 검색 API 호출"""
return f"검색 결과: {query}에 관한 정보..."
react_qa = dspy.ReAct(QASignature, tools=[search_tool])
# 사용
context = """
DSPy는 Stanford에서 개발한 LLM 프레임워크입니다.
2023년 처음 공개됐으며 자동 프롬프트 최적화가 핵심 기능입니다.
"""
result_simple = simple_qa(context=context, question="DSPy는 어디서 만들었나요?")
result_cot = cot_qa(context=context, question="DSPy의 가장 중요한 특징은?")
print(result_simple.answer)
print(result_cot.answer)
# ChainOfThought는 result_cot.reasoning으로 추론 과정도 확인 가능
print(result_cot.reasoning)
[Module 선택 가이드]
→ Predict: 단순 분류, 키워드 추출 등 직관적 태스크
→ ChainOfThought: 복잡한 추론, 수학 문제, 다단계 분석
→ ProgramOfThought: 코드 실행이 필요한 수치 계산
→ ReAct: 도구 사용 + 추론 반복이 필요한 에이전트 태스크
→ MultiChainComparison: 여러 추론 경로 비교 후 최선 선택
실전 3 — 핵심: Optimizer로 자동 최적화
여기가 DSPy의 핵심입니다. 예시 데이터 + 평가 기준을 주면 옵티마이저가 최적 프롬프트를 자동으로 찾습니다.
import dspy
from dspy.evaluate import Evaluate
# ── 1. 프로그램 정의 ────────────────────────────────────
class ReviewAnalyzer(dspy.Module):
def __init__(self):
self.classify = dspy.ChainOfThought(SentimentClassifier)
def forward(self, review: str):
return self.classify(review=review)
# ── 2. 학습 데이터 준비 ─────────────────────────────────
# 실제로는 수십~수백 개 권장
trainset = [
dspy.Example(
review="제품이 설명과 달라서 실망했습니다.",
sentiment="negative"
).with_inputs("review"),
dspy.Example(
review="가격 대비 품질이 훌륭합니다!",
sentiment="positive"
).with_inputs("review"),
dspy.Example(
review="그냥 보통이에요. 나쁘지는 않아요.",
sentiment="neutral"
).with_inputs("review"),
# ... 더 많은 예시
]
# ── 3. 평가 기준(Metric) 정의 ───────────────────────────
def sentiment_metric(example, prediction, trace=None) -> float:
"""
예측이 정답과 일치하면 1.0, 아니면 0.0
복잡한 태스크는 LLM-as-Judge 사용 가능
"""
return float(example.sentiment == prediction.sentiment)
# ── 4. 옵티마이저 실행 ──────────────────────────────────
from dspy.teleprompt import BootstrapFewShot
# BootstrapFewShot: 가장 기본적인 옵티마이저
# 학습 데이터에서 자동으로 few-shot 예시 선택
optimizer = BootstrapFewShot(
metric=sentiment_metric,
max_bootstrapped_demos=4, # 최대 few-shot 예시 수
max_labeled_demos=16, # 레이블된 데이터 최대 사용량
)
program = ReviewAnalyzer()
# 컴파일 — 이 과정에서 최적 프롬프트 탐색
optimized_program = optimizer.compile(
program,
trainset=trainset,
)
# ── 5. 결과 비교 ─────────────────────────────────────────
# 최적화 전
result_before = program(review="포장이 엉망이고 배송도 늦었어요.")
print(f"최적화 전: {result_before.sentiment}")
# 최적화 후
result_after = optimized_program(review="포장이 엉망이고 배송도 늦었어요.")
print(f"최적화 후: {result_after.sentiment}")
# 최적화된 프로그램 저장
optimized_program.save("optimized_classifier.json")
실전 4 — MIPROv2로 고급 최적화
BootstrapFewShot이 few-shot 예시만 최적화한다면, MIPROv2는 지시문(instructions)까지 자동으로 개선합니다.
from dspy.teleprompt import MIPROv2
# MIPROv2: 지시문 + few-shot 동시 최적화
# Bayesian Optimization으로 탐색 공간 효율화
optimizer = MIPROv2(
metric=sentiment_metric,
auto="medium", # "light" / "medium" / "heavy" (탐색 깊이)
num_threads=4, # 병렬 실행
prompt_model=dspy.LM("anthropic/claude-sonnet-4-5"), # 지시문 생성용
task_model=dspy.LM("anthropic/claude-haiku-4-5"), # 실제 태스크용
)
# 검증셋도 준비
devset = [
dspy.Example(review="완전 별로에요.", sentiment="negative").with_inputs("review"),
dspy.Example(review="강력 추천합니다!", sentiment="positive").with_inputs("review"),
]
optimized = optimizer.compile(
ReviewAnalyzer(),
trainset=trainset,
valset=devset,
requires_permission_to_run=False, # 자동 실행 허용
)
# 최적화된 프롬프트 확인 (어떻게 바꿨는지 투명하게 확인 가능)
optimized.inspect_history(n=3)
# ── RAG 파이프라인에 DSPy 적용 ────────────────────────
class RAGSignature(dspy.Signature):
"""검색된 문서를 기반으로 질문에 답합니다."""
question: str = dspy.InputField()
context: list[str] = dspy.InputField(desc="검색된 관련 문서들")
answer: str = dspy.OutputField(desc="정확하고 간결한 답변")
citations: list[int] = dspy.OutputField(desc="사용한 문서 인덱스 목록")
class RAGProgram(dspy.Module):
def __init__(self, retriever):
self.retriever = retriever
self.generate = dspy.ChainOfThought(RAGSignature)
def forward(self, question: str):
# 1. 검색
docs = self.retriever(question)
context = [doc.text for doc in docs]
# 2. 생성
return self.generate(question=question, context=context)
# RAG 품질 평가 메트릭
def rag_metric(example, prediction, trace=None) -> float:
# 정답 포함 여부 + 인용 정확도 복합 평가
answer_correct = example.answer.lower() in prediction.answer.lower()
has_citations = len(prediction.citations) > 0
return float(answer_correct) * 0.7 + float(has_citations) * 0.3
[옵티마이저 선택 가이드]
→ BootstrapFewShot: 시작점. 데이터 적을 때 (10~50개)
→ MIPROv2: 지시문까지 최적화. 데이터 충분할 때 (50개+)
→ SIMBA: 어려운 예시 집중 학습. 엣지케이스 많을 때
→ GEPA: 실패 패턴 분석 기반. 반복 개선 필요할 때
→ BootstrapFinetune: 모델 자체를 파인튜닝. 최고 성능 필요 시
마무리
✅ DSPy가 빛나는 경우
→ 같은 태스크를 반복 실행하는 프로덕션 LLM 앱
→ "이 프롬프트가 왜 잘 되는지 모르겠다" 상황
→ 모델을 자주 교체하거나 여러 모델 비교가 필요할 때
→ 팩트 정확도, 감정 분류 등 측정 가능한 품질 기준이 있을 때
→ RAG 파이프라인 품질을 체계적으로 올리고 싶을 때
❌ DSPy가 안 맞는 경우
→ 1회성 또는 비정형 태스크 (매번 다른 요청)
→ 평가 기준을 수치로 표현하기 어려운 창의적 작업
→ 학습 데이터가 아예 없는 완전 새 태스크
→ 빠른 프로토타이핑이 목표 (오버엔지니어링)
→ 단순 챗봇 — 프롬프트 직접 짜는 게 더 빠름
반응형
'AI 개발' 카테고리의 다른 글
| Goose 완전 가이드 — 오픈소스 CLI 에이전트, Claude Code 대안 (0) | 2026.05.18 |
|---|---|
| Augment Code Intent 완전 가이드 — IDE 다음 단계, 에이전트 함대를 지휘하는 법 (0) | 2026.05.18 |
| EU AI Act 개발자 실전 가이드 — 2026년 8월부터 AI 앱 만들때 주의 (0) | 2026.05.18 |
| GEO(Generative Engine Optimization) 완전 가이드 — SEO가 죽고 AI 검색 시대가 왔다 (0) | 2026.05.18 |
| xAI Grok Build — 터미널 안으로 들어온 또 하나의 코딩 에이전트 (0) | 2026.05.18 |