DSPy와 Milvus의 통합을 탐구하여 고효율 RAG 파이프라인 구축하기
소개
대규모 언어 모델(LLMs)은 혁신적인 생성 능력을 갖추고 있으며, 지식 베이스와 검색기 같은 도구로 보강되어 챗봇 및 agents와 같은 고급 GenAI 애플리케이션을 구동합니다. LLM과 상호작용하는 데 핵심이 되는 것은 프롬프트로, 이는 이러한 모델이 특정 작업을 수행하도록 안내하는 지시문입니다. 그러나 효과적인 프롬프트를 작성하는 것은 미묘하고 복잡한 과정이며, 종종 Chain-of-Thought 및 ReAct와 같은 정교한 기법이 필요합니다. 여기에 더해 프롬프트는 점점 더 복잡해지고 있습니다. 또한 동일한 프롬프트라도 GPT-4와 Gemini와 같은 서로 다른 LLM에서는 사전 학습 방법과 데이터셋의 차이로 인해 상이한 결과를 낼 수 있습니다. 이러한 과제는 더 나은 맞춤형 결과를 위해 프롬프트를 조정하고 최적화하는 노동 집약적 작업인 Prompt Engineering에 대한 관심 급증을 촉발했습니다.
수동 프롬프트 작성은 단순한 LLM 애플리케이션에는 잘 작동하지만, 여러 구성 요소가 포함된 복잡한 LLM 기반 파이프라인에서는 답답하고 시간이 많이 소요됩니다. DSPy는 모델 프롬프트와 가중치의 알고리즘적 최적화를 가능하게 하는 프로그래밍 가능한 인터페이스를 도입함으로써 개발자가 언어 모델과 상호작용하는 방식의 패러다임 전환을 나타내며, 더 효율적인 언어 모델 개발로 이어집니다. DSPy는 Milvus 벡터 데이터베이스를 원활하게 통합하여, 프로그래밍 방식 접근법을 사용해 Retrieval Augmented Generation(RAG) 애플리케이션의 최적화를 자동화합니다.
다음 섹션에서는 DSPy의 본질과 작동 메커니즘을 살펴보고, DSPy와 Milvus 벡터 데이터베이스를 사용해 RAG 애플리케이션을 구축하고 최적화하는 방법을 보여주는 실용적인 예제를 제공합니다.
DSPy란 무엇인가?
Stanford NLP Group이 소개한 DSPy는 언어 모델의 프롬프트와 가중치를 최적화하도록 설계된 프로그래밍 방식 프레임워크로, LLM이 여러 파이프라인 단계에 걸쳐 통합될 때 특히 유용합니다. Pythonic 문법으로 LLM에 지시하기 위한 다양한 조합 가능하고 선언적인 모듈을 제공합니다.
프롬프트를 수동으로 작성하고 조정하는 데 의존하는 전통적인 프롬프트 엔지니어링 기법과 달리, DSPy는 질의-응답 예제를 학습하고 이 학습을 모방하여 더 맞춤화된 결과를 위한 최적화된 프롬프트를 생성합니다. 이 접근 방식은 전체 파이프라인을 작업의 뉘앙스에 명시적으로 맞춰 동적으로 재구성할 수 있게 하여, 지속적인 수동 프롬프트 조정의 필요성을 없애줍니다.
핵심 개념과 기본 구성 요소
DSPy에서는 세 가지 기본 요소인 Signatures, Modules, Optimizers(이전에는 Teleprompters라고 불림)가 자동화된 프롬프트 최적화와 모델 미세 조정을 위한 핵심 빌딩 블록을 형성합니다.
Signatures
Signatures는 DSPy 모듈의 입력/출력 동작을 정의하는 선언적 명세입니다. 이는 언어 모델에 어떻게 프롬프트를 제공해야 하는지가 아니라 어떤 작업을 실행해야 하는지를 언어 모델에 알려줍니다.
- 시그니처는 세 가지 필수 요소로 구성됩니다:
- 언어 모델이 해결하고자 하는 하위 작업에 대한 간결한 설명.
- 언어 모델에 제공하는 하나 이상의 입력 필드(예: 입력 질문)에 대한 설명.
- 언어 모델에서 기대하는 하나 이상의 출력 필드(예: 질문에 대한 답변)에 대한 설명.
다음은 인기 있는 LLM 작업에 대한 시그니처 예시입니다:
- Question Answering:
"question -> answer" - Sentiment Classification:
"sentence -> sentiment" - Retrieval Augmented Question Answering:
"context, question -> answer" - Multiple-Choice Question Answering with Reasoning:
"question, choices -> reasoning, selection"
이러한 시그니처는 DSPy가 다양한 모듈 내에서 LLM 작업을 효율적으로 조율하도록 안내하여, 간소화되고 정확한 작업 실행을 촉진합니다.
모듈
DSPy 모듈은 LLM 파이프라인 내의 기존 프롬프팅 기법을 추상화합니다. 세 가지 주요 특징이 있습니다:
- 각 내장 모듈은 특정 프롬프팅 기법(예: Chain of Thoughts 또는 ReAct)을 추상화하고 DSPy Signatures를 처리합니다.
- DSPy 모듈에는 프롬프트 구성 요소와 LLM 가중치를 포함한 학습 가능한 매개변수가 있어, 입력을 처리하고 출력을 생성할 수 있습니다.
- DSPy 모듈은 더 크고 복잡한 모듈을 만들기 위해 결합될 수 있습니다.
- DSPy는
dspy.ReAct,dspy.ChainofThought,dspy.Predict,dspy.ProgramOfThought,dspy.ReAct,dspy.MultiChainComparison및dspy.Retrieve를 포함하여 다양한 목적을 위한 일곱 가지 내장 모듈을 제공합니다.
옵티마이저
DSPy 옵티마이저(이전 명칭: Teleprompters)는 정확도와 같은 지정된 지표를 극대화하기 위해 프롬프트 및 LLM 가중치와 같은 DSPy 프로그램의 매개변수를 미세 조정하도록 설계된 알고리즘입니다. 일반적인 DSPy 옵티마이저에는 세 가지 입력이 필요합니다:
DSPy 프로그램: 단일 모듈(예:
dspy.Predict)일 수도 있고 복잡한 다중 모듈 프로그램일 수도 있습니다.선택한 지표: 프로그램의 출력을 평가하고 점수(점수가 높을수록 더 나은 결과를 의미)를 할당하는 함수입니다.
소규모 훈련 입력 세트: 보통 5~10개의 예시만으로 충분합니다.
훈련 데이터, 모듈, 지표를 정의하면 옵티마이저는 프로그램 효율성을 향상하기 위해 LLM 가중치, 프롬프트 지시문, few-shot 데모를 최적화합니다. 예를 들어, BootstrapFewShot 옵티마이저는 지정된 지표와 일치하는 답변을 생성하는 반면, COT(Chain of Thought)와 같은 모듈은 정확한 답변에 도달하기 위한 구조화된 추론을 생성합니다. DSPy는 향후 테스트 쿼리를 처리하기 위한 few-shot 데모로 이러한 성공 사례와 근거를 기록합니다.
위에서 언급한 핵심 구성 요소 외에도 DSPy는 데이터, 지표, 어서션을 보조 구성 요소로 통합하여 기능성과 적응성을 강화합니다. 자세한 내용은 DSPy 문서를 참조하세요.
DSPy 워크플로: 효율적인 LLM 파이프라인 구축
DSPy는 LLM 파이프라인을 구축할 때 정확히 어떻게 작동할까요? 명확성을 위해 이 과정을 몇 가지 주요 단계로 나눌 수 있습니다.
- 먼저, 작업을 정의하고 몇 가지 예제 입력을 준비해야 합니다—대개 레이블 없이(또는 메트릭에 필요한 경우 최종 출력에 대한 레이블만 포함하여) 준비합니다.
- 둘째, 내장 모듈 중에서 선택하고, 각 모듈에 시그니처(입력/출력 명세)를 할당하며, 이러한 모듈을 Python 코드에 매끄럽게 통합하여 파이프라인을 구성합니다.
- 셋째, 파이프라인의 검증 로직을 정의합니다. 여기에는 프롬프트와 최종 결과의 품질을 평가하는 데 사용할 메트릭과 예제 입력이 포함됩니다.
- 넷째, DSPy 옵티마이저를 사용해 코드를 컴파일합니다. 이는 고품질 지침, 자동 few-shot 예제 또는 업데이트된 LLM 가중치를 생성합니다.
- 마지막으로, 파이프라인의 원하는 성능 수준을 달성하기 위해 데이터셋, 프로그램 또는 검증 로직을 개선하는 반복적인 프로세스에 참여합니다. 변화하는 요구사항을 충족하고 결과를 최적화하기 위해 지속적으로 평가하고 개선합니다.
DSPy vs. LlamaIndex/LangChain/AutoGPT
DSPy는 LangChain, LlamaIndex, AutoGPT와 같은 다른 많은 인기 AI 프레임워크와 비교할 때 그 접근 방식에서 두드러집니다. 이들이 어떻게 다르고 어떻게 맞물리는지 자세히 살펴보겠습니다:
LangChain은 맞춤형 애플리케이션을 만들기 위한 툴킷입니다. 다양한 언어 모델과 유틸리티 패키지를 활용하여 개발자가 특정 요구사항에 맞게 애플리케이션을 조정할 수 있도록 합니다.
LlamaIndex는 다양한 비공개 데이터 소스와 언어 모델의 통합을 간소화하도록 설계된 오케스트레이션 프레임워크입니다. 데이터 처리 및 프로세싱 작업을 단순화합니다.
AutoGPT는 GPT-4 및 GPT3.5로 구동되는 고급 AI 에이전트입니다. 사전 정의된 규칙과 목표를 기반으로 의사결정을 내리고 행동하도록 프로그래밍되어 있습니다. 자율성과 의사결정 역량을 강조합니다.
DSPy의 차별화된 특징:
- DSPy는 언어 모델과의 향상된 상호작용을 위해 프롬프트 구성을 최적화하고 자동화합니다.
- 사전 구축된 모듈로 고수준 애플리케이션 개발을 지원하는 LangChain 및 LlamaIndex와 달리, DSPy는 맞춤형 파이프라인 내에서 LLM에 프롬프트하는 방법을 학습하거나 LLM을 미세 조정할 수 있는 강력하고 범용적인 모듈을 제공합니다. DSPy의 강점은 변화하는 데이터, 프로그램 제어 흐름 조정 또는 대상 언어 변화에 따라 프롬프트를 동적으로 조정하고 LLM을 미세 조정할 수 있는 능력에 있습니다. 이러한 자동화된 최적화 프로세스는 최소한의 노력으로 더 우수한 품질의 출력을 이끌어낼 수 있으며, 특히 개발자가 단순한 프로그램이나 프로토타이핑 프로그램을 프로덕션 목적의 더 정교한 프로그램으로 확장하는 데 열려 있을 때 더욱 그렇습니다.
- DSPy는 LangChain 및 LlamaIndex와 같은 라이브러리가 제공하는 사전 정의된 프롬프트와 통합에 의존하기보다, 가볍지만 자동으로 최적화되는 프로그래밍 모델이 필요한 사용 사례에 이상적입니다.
DSPy Integration with Milvus Vector Database
Milvus는 매우 유연하고, 신뢰할 수 있으며, 초고속인 클라우드 네이티브 오픈소스 벡터 데이터베이스입니다. 벡터 유사도 검색을 구동하며, 다양한 GenAI 및 Retrieval Augmented Generation (RAG) 애플리케이션을 구축하는 데 특히 유용합니다.
Milvus는 MilvusRM Client 형태의 검색 모듈로 DSPy 워크플로에 통합되어, 빠르고 효율적인 RAG 파이프라인을 더 쉽게 구현할 수 있게 해줍니다.
Building a RAG Application with DSPy and Milvus
Retrieval Augmented Generation(RAG)은 LLM이 외부 지식 저장소에 접근하고, 사용자 쿼리와 관련된 맥락 정보를 찾기 위해 이러한 저장소를 검색하며, 정제된 응답을 생성할 수 있도록 하는 방법입니다.
이 데모에서는 답변 생성을 위해 GPT-3.5(gpt-3.5-turbo)를 사용하여 간단한 RAG 애플리케이션을 구축합니다. MilvusRM을 통해 Milvus를 벡터 저장소로 사용하고, DSPy를 사용하여 RAG 파이프라인을 구성하고 최적화합니다.
사전 요구 사항
RAG 앱을 구축하기 전에 MilvusRM Client와 Milvus를 설치하세요.
- MilvusRM을 설치하려면 다음 코드를 실행하세요.
pip install dspy-ai[milvus]
- Milvus를 설치하세요. 자세한 지침은 Milvus documentation을 참조하세요.
데이터셋 로드
이 예제에서는 복잡한 질문-답변 쌍 모음인 HotPotQA를 학습 데이터셋으로 사용합니다. HotPotQA 클래스를 통해 이를 로드할 수 있습니다.
from dspy.datasets import HotPotQA
# Load the dataset.
dataset = HotPotQA(train_seed=1, train_size=20, eval_seed=2023, dev_size=50, test_size=0)
# Tell DSPy that the 'question' field is the input. Any other fields are labels and/or metadata.
trainset = [x.with_inputs('question') for x in dataset.train]
devset = [x.with_inputs('question') for x in dataset.dev]
Milvus 벡터 데이터베이스로 데이터 가져오기
벡터 검색을 위해 컨텍스트 정보를 Milvus 컬렉션에 수집합니다. 이 컬렉션에는 embedding 필드와 text 필드가 있어야 합니다. 이 경우 OpenAI의 text-embedding-3-small 모델을 기본 쿼리 임베딩 함수로 사용합니다.
import requests
MILVUS_URI = "http://localhost:19530"
MILVUS_TOKEN = ""
from pymilvus import MilvusClient, DataType, Collection
from dspy.retrieve.milvus_rm import openai_embedding_function
client = MilvusClient(
uri=MILVUS_URI,
token=MILVUS_TOKEN
)
if 'dspy_example' not in client.list_collections():
client.create_collection(
collection_name="dspy_example",
overwrite= True,
dimension=1536,
primary_field_name="id",
vector_field_name="embedding",
id_type="int",
metric_type="IP",
max_length=65535,
enable_dynamic=True
)
text = requests.get('https://raw.githubusercontent.com/wxywb/dspy_dataset_sample/master/sample_data.txt').text
for idx, passage in enumerate(text.split('\n')):
if len(passage) == 0:
continue
client.insert(collection_name="dspy_example", data = [{"id": idx , "embedding": openai_embedding_function(passage)[0], "text": passage}])
MilvusRM 정의.
이제 MilvusRM을 정의해야 합니다.
from dspy.retrieve.milvus_rm import MilvusRM
import os
import dspy
os.environ["OPENAI_API_KEY"] = "<YOUR_OPENAI_API_KEY>"
retriever_model = MilvusRM(
collection_name="dspy_example",
uri=MILVUS_URI,
token=MILVUS_TOKEN, # ignore this if no token is required for Milvus connection
embedding_function = openai_embedding_function
)
turbo = dspy.OpenAI(model='gpt-3.5-turbo')
dspy.settings.configure(lm=turbo)
시그니처 구축
이제 데이터를 로드했으므로 파이프라인의 하위 작업에 대한 시그니처를 정의해 보겠습니다. 간단한 입력 question과 출력 answer를 식별할 수 있지만, RAG 파이프라인을 구축하고 있으므로 Milvus에서 컨텍스트 정보를 검색합니다. 따라서 시그니처를 context, question --> answer로 정의해 보겠습니다.
class GenerateAnswer(dspy.Signature):
"""Answer questions with short factoid answers."""
context = dspy.InputField(desc="may contain relevant facts")
question = dspy.InputField()
answer = dspy.OutputField(desc="often between 1 and 5 words")
모델이 무엇을 받게 되고 무엇을 생성해야 하는지에 대한 더 명확한 지침을 정의하기 위해 context 및 answer 필드에 짧은 설명을 포함합니다.
파이프라인 구축
이제 RAG 파이프라인을 정의해 보겠습니다.
class RAG(dspy.Module):
def __init__(self, rm):
super().__init__()
self.retrieve = rm
# This signature indicates the task imposed on the COT module.
self.generate_answer = dspy.ChainOfThought(GenerateAnswer)
def forward(self, question):
# Use milvus_rm to retrieve context for the question.
context = self.retrieve(question).passages
# COT module takes "context, query" and output "answer".
prediction = self.generate_answer(context=context, question=question)
return dspy.Prediction(context=[item.long_text for item in context], answer=prediction.answer)
파이프라인 실행 및 결과 얻기
이제 이 RAG 파이프라인을 구축했습니다. 직접 시도해 보고 결과를 얻어보겠습니다.
rag = RAG(retriever_model)
print(rag("who write At My Window").answer)
# The result:
# 'Townes Van Zandt'
데이터셋에서 정량적 결과를 평가할 수 있습니다.
from dspy.evaluate.evaluate import Evaluate
from dspy.datasets import HotPotQA
evaluate_on_hotpotqa = Evaluate(devset=devset, num_threads=1, display_progress=False, display_table=5)
metric = dspy.evaluate.answer_exact_match
score = evaluate_on_hotpotqa(rag, metric=metric)
print('rag:', score)
# The result:
# rag: 50.0
파이프라인 최적화
이 프로그램을 정의한 후 다음 단계는 컴파일입니다. 이 프로세스는 성능을 향상하기 위해 각 모듈 내의 매개변수를 업데이트합니다. 컴파일 프로세스는 세 가지 중요한 요소에 따라 달라집니다:
- 학습 세트: 이 데모에서는 학습 데이터셋의 20개 질문-답변 예제를 활용합니다.
- 검증 지표: 간단한
validate_context_and_answer지표를 설정합니다. 이 지표는 예측된 답변의 정확성을 검증하고, 검색된 컨텍스트에 해당 답변이 포함되어 있는지 확인합니다. - 특정 옵티마이저(Teleprompter): DSPy의 컴파일러에는 프로그램을 효과적으로 최적화하도록 설계된 여러 teleprompter가 포함되어 있습니다.
from dspy.teleprompt import BootstrapFewShot
# Validation logic: check that the predicted answer is correct.
# Also check that the retrieved context does contain that answer.
def validate_context_and_answer(example, pred, trace=None):
answer_EM = dspy.evaluate.answer_exact_match(example, pred)
answer_PM = dspy.evaluate.answer_passage_match(example, pred)
return answer_EM and answer_PM
# Set up a basic teleprompter, which will compile our RAG program.
teleprompter = BootstrapFewShot(metric=validate_context_and_answer)
# Compile!
compiled_rag = teleprompter.compile(rag, trainset=trainset)
# Now compiled_rag is optimized and ready to answer your new question!
이제 컴파일된 RAG 프로그램을 평가해 보겠습니다.
score = evaluate_on_hotpotqa(compiled_rag, metric=metric)
print(score)
print('compile_rag:', score)
# The result:
# compile_rag: 52.0
평가 점수가 이전 값인 50.0에서 52.0으로 증가했으며, 이는 답변 품질이 향상되었음을 나타냅니다.
요약
DSPy는 프로그래밍 가능한 인터페이스를 통해 언어 모델 상호작용에서 도약을 보여주며, 모델 프롬프트와 가중치의 알고리즘적·자동화된 최적화를 가능하게 합니다. RAG 구현에 DSPy를 활용하면 다양한 언어 모델이나 데이터셋에 대한 적응이 훨씬 쉬워지고, 번거로운 수동 개입의 필요성이 크게 줄어듭니다.
또한 DSPy는 Milvus 벡터 데이터베이스를 통합하여 워크플로 내에 MilvusRM 검색기 모듈을 도입합니다. 이 새로운 통합을 통해 개발자는 향상된 답변 품질을 위해 RAG 애플리케이션 내에서 프롬프트 최적화와 모델 매개변수 조정을 자동화할 수 있습니다. 더 자세히 알아보고 싶다면 DSPy 문서의 MilvusRM 상세 가이드를 확인해 보세요!
참고 자료
DSPy GitHub 페이지: https://github.com/stanfordnlp/dspy
DSPy 논문: https://arxiv.org/pdf/2310.03714
DSPy 문서: https://dspy-docs.vercel.app/quick-start/installation/
MilvusRM 가이드: https://dspy-docs.vercel.app/docs/deep-dive/retrieval_models_clients/MilvusRM
Milvus 문서: https://milvus.io/docs
계속 읽기

How to Improve Retrieval Quality for Japanese Text with Sudachi, Milvus/Zilliz, and AWS Bedrock
Learn how Sudachi normalization and Milvus/Zilliz hybrid search improve Japanese RAG accuracy with BM25 + vector fusion, AWS Bedrock embeddings, and practical code examples.

Zilliz Cloud Update: Tiered Storage, Business Critical Plan, Cross-Region Backup, and Pricing Changes
This release offers a rebuilt tiered storage with lower costs, a new Business Critical plan for enhanced security, and pricing updates, among other features.

Vector Databases vs. Time Series Databases
Use a vector database for similarity search and semantic relationships; use a time series database for tracking value changes over time.



