본문 바로가기
개발/LLM

[AI][Agent] DSPy로 구현하는 자동 최적화 AI 파이프라인

by ▶ Carpe diem ◀ 2025. 11. 22.

AI 시스템 개발의 패러다임을 바꾸는 DSPy 프레임워크를 소개합니다. 깨지기 쉬운 프롬프트 엔지니어링을 넘어 데이터 기반의 자동 최적화(Self-learning)를 통해 고성능 AI Agent를 구축하는 핵심 원리와 실전 코드를 심도 있게 다룹니다.

 

 

 

복잡한 AI Agent, 왜 아직도 Prompt에 의존하고 있는가?

오늘날 RAG(Retrieval-Augmented Generation) 시스템과 복잡한 LLM 응용 프로그램을 구축하는 AI 개발자들은 공통적인 한계에 부딪히고 있습니다. 바로 '프롬프트 엔지니어링'의 본질적인 취약성과 비확장성입니다. 우리는 수많은 시간을 들여 특정 모델과 데이터에 맞춰 프롬프트를 수동으로 튜닝하지만, 이러한 접근 방식은 DSPy 논문에서 지적하듯 "깨지기 쉬운 문자열(brittle strings)"과 "하드코딩된 프롬프트 템플릿(hard-coded prompt templates)"에 의존하게 됩니다. 모델을 바꾸거나, 데이터 분포가 변경되거나, 파이프라인의 작은 로직 하나만 수정해도 공들여 만든 프롬프트는 쉽게 성능이 저하되며, 이는 곧 전체 시스템의 신뢰성 하락으로 이어집니다.

 

이러한 문제를 해결하기 위해, 우리는 프롬프트 튜닝이라는 장인 정신의 영역에서 벗어나 보다 체계적이고 자동화된 '프로그래밍' 패러다임으로 전환해야 합니다. 이 글의 핵심 주제인 DSPy를 이용한 Self-learning 하는 agent 구축 방법은 바로 이 지점에서 출발합니다. DSPy는 AI 시스템의 동작을 문자열이 아닌 코드로 정의하고, 주어진 데이터와 평가 지표에 맞춰 시스템의 모든 구성 요소를 자동으로 최적화하는 새로운 길을 제시합니다. 이는 개발자가 저수준의 프롬프트 조작에서 벗어나 시스템의 전체적인 로직 설계에 집중할 수 있게 만듭니다.

 

 

 

 

 

 

DSPy 핵심 기술: Prompting에서 Programming으로의 전환

DSPy는 단순히 LLM 호출을 편리하게 만드는 라이브러리가 아닙니다. 이는 AI 시스템 개발의 근본적인 패러다임을 전환하려는 시도입니다. DSPy 공식 문서에서 비유하듯, 이는 저수준의 어셈블리 언어에서 고수준의 C언어로, 복잡한 포인터 연산에서 선언적인 SQL로 전환했던 프로그래밍 역사의 발전 과정과 궤를 같이합니다. 개발자가 "어떻게(how)" LLM을 호출할지 고민하는 대신, "무엇을(what)" 원하는지에 집중하게 함으로써 시스템의 복잡성을 관리하고 신뢰성을 높이는 것이 DSPy의 핵심 철학입니다. 이러한 철학은 세 가지 핵심 추상화 개념을 통해 구현됩니다.

DSPy 이미지

 

 

 

Signatures: 선언적 자연어 명세

Signature는 DSPy의 가장 기본적인 구성 요소로, AI 모듈이 수행해야 할 작업을 선언적으로 정의합니다. 이는 "무엇을(what)" 할 것인지를 명시하고, "어떻게(how)" 프롬프트를 구성할 것인지의 구체적인 구현을 분리하는 역할을 합니다. 예를 들어, 복잡한 질문-답변 프롬프트 템플릿을 작성하는 대신, 개발자는 question -> answer와 같은 간결한 문자열로 시그니처를 정의할 수 있습니다. DSPy는 이 선언적 명세를 바탕으로 적절한 프롬프트를 자동으로 구성하고, LLM의 출력을 파싱하며, 최적화 과정에서 이 시그니처에 맞는 최상의 예시와 명령어를 학습합니다.

 

Modules: 파라미터화된 재사용 가능 구성 요소

Module은 PyTorch의 레이어처럼 동작하는 재사용 가능한 AI 구성 요소입니다. dspy.Predict, dspy.ChainOfThought, dspy.ReAct와 같은 내장 모듈들은 각각 다른 프롬프팅 전략을 추상화합니다. 이 모듈들은 특정 Signature와 결합하여 동작하는데, 여기서 DSPy의 강력함이 드러납니다. 예를 들어, dspy.ChainOfThought 모듈은 question -> answer 시그니처뿐만 아니라 context, question -> search_query와 같은 전혀 다른 시그니처에도 적용될 수 있습니다. 이처럼 모듈이 특정 태스크에 종속되지 않고 다양한 시그니처에 맞춰 동작하는 다형성(polymorphism)은 AI 시스템의 재사용성과 모듈성을 극대화합니다.

 

Optimizers (Teleprompters): Self-Learning의 엔진

Optimizer는 DSPy 프로그램의 성능을 주어진 평가 지표(metric)에 맞춰 자동으로 "컴파일"하는 핵심 엔진입니다. 이것이 바로 DSPy가 Self-learning 하는 agent를 가능하게 하는 심장부입니다. 개발자가 소량의 학습 데이터와 평가 지표(예: 정답 일치율, 의미적 유사도)를 제공하면, MIPROv2나 BootstrapFinetune과 같은 옵티마이저는 프로그램의 각 모듈을 실행하며 성공적인 실행 경로(trace)를 수집합니다. 이후, 이 경로들을 바탕으로 각 모듈에 가장 효과적인 소수샷 예시(few-shot demonstrations)를 생성하거나, 명령어(instructions)를 탐색하거나, 심지어 소형 언어 모델(SLM)의 가중치를 직접 미세 조정(finetune)합니다.

 

이러한 구조적 접근법은 AI 시스템의 신뢰성, 유지보수성, 그리고 특정 LLM이나 프롬프트 기법에 종속되지 않는 이식성(portability)을 획기적으로 향상시킵니다. 

 

 

 

 

Self-Learning의 핵심 원리: DSPy 옵티마이저는 어떻게 작동하는가?

DSPy의 Optimizer가 제공하는 '자동 최적화'는 단순한 마케팅 용어가 아닙니다. 이는 AI 시스템이 데이터로부터 스스로 학습하여 성능을 개선하는 구체적이고 체계적인 알고리즘에 기반합니다. 이 프로세스는 개발자를 직관과 반복 작업에 의존하는 프롬프트 튜닝의 늪에서 해방시키는 기술적 핵심입니다. 최적화 과정은 크게 세 단계로 이루어집니다.

  • 1단계: 추적 생성 (Trace Generation): 최적화되지 않은 초기 프로그램을 소량의 학습 데이터셋(trainset)으로 실행하여 각 모듈의 입출력 동작을 '추적(trace)'으로 수집합니다. 예를 들어, RAG 에이전트라면 Retrieve 모듈이 어떤 구절을 검색했는지, ChainOfThought 모듈이 어떤 추론 과정을 거쳐 답변을 생성했는지 등의 모든 중간 과정이 기록됩니다.
  • 2단계: 고품질 추적 필터링 (Trace Filtering): 모든 추적이 유용한 것은 아닙니다. 사용자가 정의한 평가 메트릭(예: dspy.evaluate.answer_exact_match 또는 SemanticF1)을 사용하여 최종 출력을 프로그래밍 방식으로 채점하고, 높은 점수를 받아 성공적인 결과를 낳은 실행 추적만을 선별하여 최적화에 사용합니다. 즉, 시스템이 성공적으로 문제를 해결했을 때의 모범적인 실행 경로만을 학습 데이터로 삼는 과정입니다.
  • 3단계: 프롬프트 및 가중치 최적화 (Prompt & Weight Optimization): 선별된 고품질 추적을 바탕으로 Optimizer가 본격적으로 작동합니다.
    • BootstrapFewShot 또는 MIPROv2와 같은 프롬프트 옵티마이저는 이 추적들을 후보군으로 삼습니다. 이후, 각 모듈의 프롬프트에 포함될 가장 효과적인 소수샷 예시(few-shot demonstrations)와 명령어(instructions)를 체계적으로 탐색하고 조합하여 새로운 최적의 프롬프트를 구성합니다.
    • BootstrapFinetune과 같은 가중치 옵티마이저는 한 걸음 더 나아갑니다. 수집된 고품질 추적들을 학습 데이터로 변환하여, 소형 언어 모델(SLM)의 가중치를 직접 미세 조정(finetune)합니다. 이를 통해 특정 작업에 고도로 특화된, 작지만 강력한 모델을 생성할 수 있습니다.

이 프로세스의 진정한 가치는 프롬프트 엔지니어링을 장인적이고 비결정적인 기술에서 반복 가능하고, 감사 가능하며, 최적화 가능한 빌드 단계로 전환하는 데 있습니다. 이는 마치 코드 컴파일 시 성능 목표를 달성하기 위해 -O2, -O3와 같은 다양한 최적화 플래그를 사용하는 것과 같습니다. 개발자는 더 이상 "이 질문에는 어떤 예시를 넣어야 할까?"와 같은 모호한 질문에 시간을 허비할 필요 없이, 데이터와 명확한 평가 기준만 제공하면 시스템이 스스로 최적의 전략을 학습합니다.

 

이러한 체계적이고 메트릭 기반의 프로세스는 도입부에서 언급한 '깨지기 쉬운 문자열(brittle strings)' 문제에 대한 직접적인 해결책입니다. 하드코딩된 프롬프트가 모델이나 데이터의 변화에 쉽게 깨지는 반면, DSPy 프로그램은 '재컴파일'됩니다. 옵티마이저는 새로운 컨텍스트에 적응하여 효과적인 새 프롬프트와 가중치를 찾아내어 시스템의 복원력과 유지보수성을 보장합니다.



 

 

 

실전 예제: DSPy로 Self-Learning RAG Agent 구축하기

이론적 개념은 실제 코드를 통해 생명력을 얻습니다. 이번 섹션에서는 간단한 RAG(Retrieval-Augmented Generation) 에이전트를 정의하고, DSPy Optimizer를 통해 이 에이전트가 주어진 데이터를 기반으로 스스로 성능을 개선하는 '학습' 과정을 단계별로 구현해 보겠습니다. 이를 통해 DSPy가 어떻게 추상적인 'Self-learning' 개념을 구체적인 코드로 실현하는지 명확히 확인할 수 있습니다.

👉 참고: DSPy GitHub

 

 

환경 설정 및 기본 RAG Agent 정의

먼저, DSPy를 설치하고 사용할 LLM을 설정합니다. 이 예제에서는 OpenAI의 gpt-4o-mini를 사용하지만, DSPy는 Ollama를 통한 로컬 모델이나 다른 주요 클라우드 LLM도 유연하게 지원하여 모델 종속성을 제거합니다.

# DSPy 설치
# pip install -U dspy

import dspy

# LLM 설정 (OpenAI API 키는 환경 변수 등으로 설정)
lm = dspy.LM('openai/gpt-4o-mini')
dspy.configure(lm=lm)

 

다음으로, 기본적인 RAG 파이프라인을 dspy.Module로 정의합니다. __init__에서 필요한 하위 모듈(dspy.ChainOfThought)을 선언하고, forward 메서드에서는 외부에서 설정된 검색 클라이언트(search)를 호출하고 그 결과를 생성 모듈에 전달하는 RAG 로직을 구현합니다. 이 코드는 이전의 의사 코드와 달리 즉시 실행 가능한 완전한 형태입니다.

# 이 예제에서는 외부에서 'search'라는 검색 클라이언트가
# dspy.configure(rm=...) 등으로 설정되었다고 가정합니다.

class RAG(dspy.Module):
    def __init__(self):
        super().__init__()
        self.respond = dspy.ChainOfThought('context, question -> response')

    def forward(self, question):
        # 1. 질문을 사용하여 관련성 높은 컨텍스트를 검색
        context = search(question).passages
        
        # 2. 검색된 컨텍스트와 질문을 바탕으로 답변 생성
        return self.respond(context=context, question=question)

 

 

 

 

Self-Learning을 위한 평가 기준 정의

시스템이 스스로 '학습'하려면 무엇이 '좋은' 결과인지 판단할 기준이 필요합니다. 즉, 명확한 평가 지표(metric)가 필수적입니다. 이 예제에서는 생성된 답변과 정답 간의 의미적 일치도를 F1 점수로 측정하는 SemanticF1 메트릭을 사용합니다. 이 메트릭은 단순히 단어 일치를 넘어, 답변의 핵심 아이디어가 얼마나 일치하는지를 LLM을 통해 정량적으로 평가합니다.

 

 

옵티마이저를 통한 Self-Learning 실행

이제 Self-learning의 핵심인 Optimizer를 사용하여 RAG 에이전트를 '컴파일'합니다. dspy.MIPROv2 옵티마이저에 평가 메트릭과 학습 데이터셋(trainset)을 전달하고 compile 메서드를 호출합니다.

from dspy.evaluate import SemanticF1
# 튜토리얼의 trainset과 metric을 사용한다고 가정
# trainset = ... (학습 데이터 로드)
metric = SemanticF1(decompositional=True)

# MIPROv2 옵티마이저 설정
tp = dspy.MIPROv2(metric=metric, auto="medium", num_threads=24)

# RAG 모듈 컴파일 (이 단계가 바로 Self-learning)
optimized_rag = tp.compile(RAG(), trainset=trainset)

여기서 tp.compile() 함수가 호출되는 순간이 바로 AI 에이전트가 주어진 데이터와 평가 기준을 통해 스스로 프롬프트를 개선하고 최적의 전략을 학습하는 '학습(learning)' 단계입니다.

 

 

 

 

장단점 분석: DSPy, 실무 도입 전 고려사항

DSPy는 AI 시스템 개발에 혁신적인 접근법을 제시하지만, 모든 기술이 그렇듯 현실적인 장단점과 트레이드오프가 존재합니다. 실무 도입을 전략적으로 결정하기 위해, 공식 문서와 커뮤니티 토론을 바탕으로 DSPy의 강점과 약점을 객관적으로 분석할 필요가 있습니다.

구분 장점 (Pros) 단점 (Cons)
개발
패러다임
선언적 프로그래밍을 통한 높은 추상화 수준. Prompt가 아닌 Code로 시스템을 설계하여 유지보수성 및 이식성 향상. 아직 성숙 단계에 있는 프레임워크로, Reddit 사용자 의견에 따르면 일부 LLM 출력 파싱 실패율이 존재할 수 있음.
성능
최적화
데이터와 메트릭 기반의 체계적이고 자동화된 최적화 가능. 전문가가 만든 프롬프트를 능가하는 성능 달성 가능 (GSM8K 수학 문제 사례). Optimizer 실행 시 다수의 LLM 호출이 발생하여 상당한 비용과 시간이 소요될 수 있음. (Reddit "100 calls" 언급)
생태계 및
유연성
특정 LLM에 종속되지 않음 (OpenAI, Ollama, 로컬 모델 등 지원). LangChain, LangGraph 등 다른 프레임워크와 조합하여 사용 가능. LangChain 대비 프로덕션 사용 사례나 관련 커뮤니티 논의가 상대적으로 적어, 실제 운영 시 발생할 수 있는 문제에 대한 정보가 부족할 수 있음.

 

시스템 아키텍트의 관점에서 DSPy는 더 큰 시스템 내의 성능이 중요한 모듈에 대한 전략적 투자로 보아야 합니다. DSPy의 강점은 수동 튜닝으로 달성할 수 있는 수준을 넘어 모듈의 성능을 체계적으로 끌어올려, 초기 컴파일 비용을 정당화하는 데 있습니다. 반면, 성능이 중요하지 않은 구성 요소나 비용과 속도가 최우선인 신속한 프로토타이핑에는 더 간단한 제로샷 접근 방식이 더 실용적일 수 있습니다. 핵심은 성능이 비즈니스 가치로 직접 연결되는 시스템의 특정 부분에 DSPy의 최적화 기능을 외과적으로 적용하는 것입니다.

 

 

 

 

 


 

 

이 글은 현대 AI 에이전트 개발이 직면한 '프롬프트 엔지니어링'의 근본적인 한계에서 시작하여, DSPy가 제시하는 새로운 해결책을 심도 있게 탐색했습니다. 핵심은 명확합니다. 수동적이고 깨지기 쉬운 프롬프트 튜닝에서 벗어나, 시스템의 목표를 코드로 선언하고 데이터 기반으로 스스로 최적화하는 '프로그래밍'과 '자동 컴파일' 패러다임으로의 전환입니다. DSPy는 Signature, Module, 그리고 Optimizer라는 세 가지 핵심 추상화를 통해 이 비전을 현실로 만듭니다.

실무 적용을 위해 DSPy 도입을 고려해야 할 시점은 다음과 같습니다.

  1. 에이전트의 성능이 비즈니스에 결정적일 때: 약간의 성능 향상이 큰 가치를 창출하는 경우, Optimizer를 통한 체계적인 성능 극대화는 필수적입니다.
  2. 프롬프트 구조가 복잡해져 수동 관리가 어려울 때: 다단계 추론, 복합 도구 사용 등으로 프롬프트가 복잡해지면, 코드 기반의 모듈식 관리가 유지보수 비용을 획기적으로 줄여줍니다.
  3. 명확한 평가 지표와 소량의 평가 데이터셋을 확보할 수 있을 때: Optimizer는 '정답'을 향해 나아가는 나침반, 즉 평가 지표와 데이터를 필요로 합니다. 이것이 준비되었다면 DSPy의 잠재력을 최대한 활용할 수 있습니다.