DSPy를 사용한 프롬프트 엔지니어링

Joshua Noble

Data Scientist

DSPy대규모 언어 모델(LLM) 애플리케이션을 구축하고 일회성 기술이 아닌 코드를 통해 성능을 미세 조정하여 프롬프트 최적화를 수행하기 위한 오픈 소스 Python 프레임워크입니다. DSPy 프로그램은 정확한 아웃풋을 얻기 위해 프롬프트를 최적화하여 LLM 애플리케이션을 구성하고 미세 조정하는 모듈식 방법을 제공합니다. DSPy의 큰 장점은 모델 성능을 직접 추적하지 않고도 Python 코드를 통해 프롬프트 엔지니어링과 추적을 수행할 수 있다는 것입니다.

DSPy의 저력은 생성형 AI를 사용하여 자연어를 생성한 다음, 결과를 테스트하여 가장 효과적인 프롬프트를 생성한다는 것입니다. 이를 통해 스스로 개선되는 AI 시스템을 구축할 수 있고 검색 모델 및 언어 모델에 대한 다양한 인터페이스를 지원합니다. ollama 또는 huggingface와 같은 시스템을 통해 로컬에서 모델을 실행하거나 OpenAI의 ChatGPT 또는 GPT-4를 사용하는 경우 API를 사용하여 실행할 수 있습니다. DSPy는 생각의 연결고리(CoT), 검색 증강 생성(RAG), 요약 등 다양한 사용 사례를 지원합니다. 

이 튜토리얼에서는 IBM watsonx에서 DSPy를 사용하여 RAG 질문 답변 애플리케이션을 만드는 워크플로를 살펴보겠습니다. 언어 모델은 Llama 3, 검색 모델은 ColBERT를 사용합니다. DSPy가 프롬프트를 미세 조정하고 질문 답변에 대한 여러 가지 접근 방식을 구조화하여, 매우 복잡한 질문에 대해 더 뛰어난 답변을 생성하는 방법을 확인할 수 있습니다.

환경 설정

여러 툴 중에서 선택할 수 있지만, 이 튜토리얼에서는 Jupyter Notebook을 사용하기 위해 IBM 계정을 설정하는 방법을 안내합니다.

IBM Cloud 계정을 사용하여 watsonx.ai에 로그인합니다.

watsonx.ai 프로젝트를 생성합니다.

프로젝트 내에서 프로젝트 ID를 가져올 수 있습니다.

그런 다음 '관리' 탭을 클릭하고 '일반' 페이지의 '세부 정보' 섹션에서 프로젝트 ID를 복사합니다. 이 튜토리얼에는 이 ID가 필요합니다.

다음으로, 원하는 환경에서 Jupyter Notebook을 생성합니다. 이 튜토리얼에서 코드를 새 노트북에 복사합니다. 또는 이 노트북을 로컬 시스템에 다운로드하여 watsonx.ai 프로젝트에 에셋으로 업로드할 수 있습니다.

Watson Machine Learning(WML) 서비스 인스턴스 및 API 키 설정

watsonx.ai 런타임 서비스 인스턴스를 만듭니다(적절한 지역을 선택하고 무료 인스턴스인 Lite 요금제 선택).

watsonx.ai Runtime에서 API 키를 생성합니다.

watsonx.ai 런타임 서비스를 watsonx.ai에서 생성한 프로젝트에 연결합니다.

DSPy 라이브러리 설치 및 자격 증명 설정

DSPy를 사용하려면 간단한 pip를 설치하고 환경 변수를 관리하기 위해 dotenv를 설치합니다.

!pip install dspy-ai python-dotenv;

다음으로 이 튜토리얼의 나머지 부분에 필요한 라이브러리를 가져옵니다.

import dspy
from dspy import LM
from dspy.datasets import HotPotQA
from dspy.teleprompt import BootstrapFewShot
import json
import os

from dotenv import load_dotenv
load_dotenv(os.getcwd()+’/.env’, override=True)

자격 증명을 설정하려면 1단계에서 생성한 WATSONX_APIKEY 및 PROJECT_ID 필요합니다. 이 둘은 디렉터리의 .env 파일에 저장하거나 자리 표시자 텍스트를 지우고 입력할 수 있습니다. API 엔드포인트 역할을 하는 URL도 설정하세요.

os.environ[‘WX_URL’] = “https://us-south.ml.cloud.ibm.com”
os.environ[‘WX_APIKEY’] = os.getenv(“WATSONX_APIKEY”, “”)

WATSONX_APIKEY= os.getenv(“WATSONX_APIKEY”, “”)
PROJECT_ID = os.getenv(“PROJECT_ID”,””)

DSPy와 함께 watsonx 사용

이제 DSPy LM 클래스를 사용하여 watsonx 모델과 함께 작동하도록 DSPy를 구성합니다. 이 클래스를 사용하면 watsonx API를 호출하여 새 프롬프트를 생성하고 테스트할 수 있는 프롬프트에 대한 응답을 생성할 수 있습니다. DSPy 아래에는 LiteLLM이라는 다른 라이브러리를 사용하여 watsonx 서비스에 접속합니다. LiteLLM은 Hugging face, Azure 및 watsonx를 포함한 OpenAI 형식을 사용하여 매우 광범위한 LLM API를 호출할 수 있는 간단한 래퍼를 제공합니다.

watsonx 계정에 액세스하려면 먼저 첫 번째 단계에서 생성한 API 키를 사용하여 watsonx 서비스의 토큰을 저장해야 합니다. os 라이브러리를 호출하여 "https://iam.cloud.ibm.com/identity/token"에 액세스하고 토큰을 검색하고 나중에 사용할 수 있도록 저장하세요.

token = os.popen(‘curl -k -X POST \
    --header “Content-Type: application/x-www-form-urlencoded” \
    --header “Accept: application/json” \
    --data-urlencode “grant_type=urn:ibm:params:oauth:grant-type:apikey” \
    --data-urlencode “apikey=’ + WATSONX_APIKEY + ‘” \
    “https://iam.cloud.ibm.com/identity/token”’).read()

이제 watsonx를 사용하는 LanguageModel 인스턴스를 만들 수 있습니다. 이전에 검색한 토큰을 API 키로 사용하면 Meta의 'llama-3-8b-instruct' 모델을 언어 모델로 사용합니다. 언어 모델에서 사용할 온도와 함께, 언어 모델로 사용할 모델의 경로를 DSPy에 전달합니다. watsonx를 사용하도록 Litelm을 구성하는 방법에 대한 자세한 내용은 GitHub 문서에서 확인할 수 있습니다. 이 경우 0.7에서는 과도한 할루시네이션 없이 창의성을 발휘할 수 있습니다.

lm = dspy.LM(‘watsonx/meta-llama/llama-3-8b-instruct’, api_key=WATSONX_APIKEY, api_base=”https://us-south.ml.cloud.ibm.com”)

dspy.configure(lm=lm, trace=[], temperature=0.7, experimental=True)

검색 모델 추가

이제 RAG의 R에 대한 검색 모델을 불러옵니다. ColBERTv2를 사용하여 Wikipedia 2017 데이터 세트에서 추출을 불러옵니다. ColBERT는 빠르고 정확한 검색 모델로, 수십 밀리초 만에 대규모 텍스트 컬렉션을 확장 가능한 BERT 기반으로 검색할 수 있게 합니다. ColBERT는 벡터 데이터베이스에서 정보를 검색하는 데 사용할 수 있는 많은 옵션 중 하나일 뿐입니다. 비슷한 벡터 데이터베이스로는 Qdrant, Milvus, Pinecone, Chroma 또는 Weaviate 등이 있습니다.

벡터 데이터베이스에는 언어 모델이 빠르게 액세스할 수 있는 특정 정보 세트가 있습니다. 이 경우 Wikipedia 2017의 초록 세트를 사용하여 언어 모델이 생성에 사용할 수 있는 광범위한 사실을 제공합니다. 이 ColBERT와 Wiki 17 데이터 세트의 조합은 누구나 사용할 수 있도록 DSPy 팀에서 무료로 호스팅하는 버전이기 때문에 특히 유용합니다. 데이터를 수집하거나 자체 벡터 데이터베이스 시스템을 설정할 필요 없이 광범위한 정보에 액세스할 수 있습니다. 이 데이터 세트의 한 가지 단점은 2017년 이후의 사건에 대한 내용이 들어있지 않다는 것이지만 데모 용도로는 매우 유용합니다.

자체 데이터로, 또는 업데이트된 데이터 세트로 자체적인 ColBERT를 실행하고 싶다면 이 튜토리얼을 참고하세요.

그런 다음 HotPotQA 데이터 세트를 로드하고 검색 체인을 테스트하는 데 사용할 수 있는 훈련 세트와 테스트 세트로 분할합니다. HotpotQA는 자연스러운 멀티홉 질문들로 구성된 질문 답변 데이터 세트입니다. 보다 설명하기 쉬운 질문 답변 시스템을 구현하기 위해 근거를 강도 높게 감독합니다.

colbertv2_wiki17_abstracts = dspy.ColBERTv2(url=’http://20.102.90.50:2017/wiki17_abstracts’)
dspy.configure(rm=colbertv2_wiki17_abstracts)

기본 QA 테스트

이제 초반 예제에 사용할 서명을 만듭니다. 서명은 모듈의 인풋과 아웃풋 유형을 정의하는 클래스로, DSPy 프로그램에서 서로 다른 모듈 간의 호환성을 보장합니다. 서명은 질문 수집, 아웃풋 및 모델 추론과 같은 여러 작업을 결합합니다. 여기에서 사용할 서명은 질문만 받고 응답을 제공합니다.

class BasicQA(dspy.Signature):
    “””Answer questions with short factoid answers.”””

    question = dspy.InputField()
    answer = dspy.OutputField(desc=”often between 1 and 5 words”)

이제 DSPy의 Predict 메서드를 호출하여 테스트할 수 있는 예측 변수가 있습니다. 이 방법론은 이전에 정의한 newBasicQA 클래스를 가져와 DSPy에 질문을 전달할 때 해당 클래스를 사용합니다.

# Define the predictor.
generate_answer = dspy.Predict(BasicQA)

이제 여러 정보가 있어야 정답을 제시할 수 있는 질문을 만들고 언어 모델만 사용하는 아키텍처로 테스트합니다. 질문에 답하기 위해 우리가 방금 만든 thegenerate_answer 함수를 사용합니다.

# Call the predictor on a particular input.
test_question = “What country was the winner of the Nobel Prize in Literature in 2006 from and what was their name?”

pred = generate_answer(question=test_question)

if pred == None:
    print(“ no answer “)
else:
    # Print the input and the prediction.
    print(f”Answer: Turkey, Orhan Pamuk”)
    print(f”Predicted Answer: {pred.answer}”)

코드는 다음을 반환합니다(답변은 다를 수 있음).

Answer: Turkey, Orhan Pamuk
Predicted Answer: The winner was France and the author was Orhan Pamuk.

오르한 파묵은 2006년 노벨 문학상 수상자였지만 프랑스 출신이 아니며 답의 틀이 옳지 않습니다. 이제 검색 증강 생성을 사용하여 검색으로 모델을 보강하고, DSPy 엔지니어가 성능 개선을 위해 더 나은 프롬프트를 표시하도록 할 수 있습니다.

검색 증강 생성(RAG)

검색 증강 생성(RAG)은 신뢰할 수 있는 지식 기반의 참조를 사용하여 대규모 언어 모델의 아웃풋을 최적화하는 아키텍처입니다. 이렇게 하면 언어 모델이 응답을 생성하기 전에 검증된 소스로 학습 데이터가 보강됩니다. LLM은 대규모 말뭉치를 학습하고 수십억 개의 매개변수를 사용하여 아웃풋을 생성하지만, 말뭉치 훈련에서 최신 정보나 정확한 정보에 접근하지 못할 수도 있습니다. RAG는 모델을 재학습할 필요 없이 LLM의 이미 강력한 능력을 특정 도메인으로 확장합니다. LLM의 아웃풋을 개선하여 다양한 상황에서 관련성, 정확성 및 유용성을 유지할 수 있는 강력하고 잠재적으로 비용 효율적인 방법입니다.

DSPy에서는 서명에 맥락 단계를 추가하여 RAG 아키텍처를 사용합니다. 이 단계에서는 검색 모델에서 맥락을 수집하고 이를 언어 모델의 프롬프트에 추가하여 더 나은 응답을 유도할 수 있습니다.

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”)

이 newGenerateAnswer 시그니처는 RAG 모델과 함께 사용할 수 있습니다. theGenerateAnswer를 'ChainOfThought' 모듈에 전달하여, 검색된 컨텍스트와 질문 및 답변이 생각의 연결고리 접근 방식을 사용하도록 합니다.

또한 RAG에서 맥락 구절을 생성하고 이를 사용하여 답변을 생성하기 위해 forward 메서드를 업데이트합니다. DSPy는 질문에 대한 응답으로 새 답변을 생성할 때마다 이 'forward' 메서드를 호출하여 ColBERT Wiki 17 추상 데이터 세트에서 두 컨텍스트를 모두 수집한 다음 해당 맥락을 언어 모델(이 경우 Llama)에 전달합니다. 각 답변을 생성할 때 DSPy는 아웃풋을 원하는 아웃풋과 비교하여 프롬프트가 모델이 올바른 응답을 생성하는 데 도움이 되는지 확인합니다.

class RAG(dspy.Module):
    def __init__(self, num_passages=3):
        super().__init__()

        self.retrieve = dspy.Retrieve(k=num_passages)
        self.generate_answer = dspy.ChainOfThought(GenerateAnswer)
   
    def forward(self, question):
        context = self.retrieve(question).passages
        prediction = self.generate_answer(context=context, question=question)
        return dspy.Prediction(context=context, answer=prediction.answer)

DSPy가 최상의 프롬프트를 엔지니어링할 수 있도록 하려면 프롬프트를 테스트하고 평가할 수 있는 데이터 세트가 필요합니다.

DSPy 테스트 질문을 제공하기 위해 HotPotQA 데이터 세트를 불러옵니다. HotpotQA는 정답에 도달하기 위해 여러 번의 검색과 추론이 필요한 자연스러운 다중 홉 질문을 특징으로 하는 질문 답변 데이터 세트입니다. 모델이 더 설명 가능한 질문 답변 시스템을 학습하고 테스트하기 위해 지원 사실을 얼마나 잘 생성하는지 테스트하는 데 유용한 도구입니다. 

예를 들어 데이터 세트에는 "프랭클린 루즈벨트 대통령은 선거인단의 표를 의회에 전달할 책임자를 누구로 임명했습니까?"라는 질문이 있습니다. 이 질문에 올바르게 답하려면 몇 가지 정보가 필요하다는 것을 알 수 있습니다.

The answer is: “Robert Digges Wimberly Connor”.

이를 뒷받침하는 맥락은 로버트 디그스 윔벌리 코너와 국립 기록 보관소에 대한 위키백과 페이지에서 나옵니다.

HotPotQA는 카네기 멜론 대학교, 스탠포드 대학교 및 몬트리올 대학교의 NLP 연구원 팀에 의해 수집 및 게시됩니다. HotPotQA에 대한 자세한 내용은 GitHub 사이트에서 확인할 수 있습니다.

데이터 세트를 불러와서 학습 세트와 테스트 세트로 나눕니다. 이는 검색 체인을 테스트하고 DSPy가 언어 모델에 가장 적합한 프롬프트를 찾는 데 도움이 될 수 있습니다.

# 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]

다음으로, DSPy가 프롬프트를 생성하고 평가할 수 있는 더 많은 기회를 제공하기 위해 더 많은 예제를 부트스트랩합니다. Callingcompile는 사용자가 구성한 모든 아키텍처와 HotPotQA 데이터 세트를 사용하여 프롬프트를 생성하고 테스트하며, 언어 모델에서 최고의 성과를 끌어냅니다.

  from dspy.teleprompt import BootstrapFewShot

# Validation logic: check that the predicted answer is correct.
# Also check that the retrieved context does actually 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 DSPy optimizer, which will compile your RAG program.
bfs_optimizer = BootstrapFewShot(metric=validate_context_and_answer)

# Compile!
compiled_rag = bfs_optimizer.compile(RAG(), trainset=trainset)

이제 DSPy가 프롬프트 엔지니어링을 수행했으니 이전에 사용했던 2006노벨상에 대한 사용자 지정 질문으로 이를 테스트해 보겠습니다. 검색 모델은 2017년도 Wikipedia 추출본을 사용하기 때문에 해당 말뭉치에 있을 수 있는 지식으로 최상의 성능을 발휘합니다.

# Get the prediction. This contains `pred.context` and `pred.answer`.
pred = compiled_rag(test_question)

# Print the contexts and the answer.
print(f”Question: {test_question}”)
print(f”Predicted Answer: {pred.answer}”)

이제 정답을 찾을 수 있습니다. 

    질문: 2006년 노벨 문학상 수상자는 어느 국가 출신이며 이름은 무엇이었나요?
    예상 답변: 튀르키예, 오르한 파묵

오르한 파묵은 터키 출신이므로 이 답이 맞습니다. DSPy의 컴파일된 버전은 정답을 맞혔을 뿐만 아니라 짧고 명확한 응답으로 답장을 올바르게 구성했습니다. 이 예측된 응답의 맥락을 보면서, 모델이 어떻게 정답에 도달했는지 살펴봅시다.

pred.context

이 경우 반환:

    ["오르한 파묵 | 페리트 오르한 파묵(Ferit Orhan Pamuk, 오르한 파묵으로 알려짐, 1952년 6월 7일 출생)은 튀르키예의 소설가, 시나리오 작가, 학자이며 2006년에 노벨 문학상을 수상했습니다. 튀르키예에서 가장 저명한 소설가 중 한 명인 그는 63개 언어로 1,300만 권 이상의 책이 판매된 튀르키예의 베스트셀러 작가입니다.", 
     '2006 팔랑카 어워드 | 2006년 카를로스 팔랑카 기념 문학상 수상자(순위, 수상작 제목, 저자 이름).',
     "미구엘 도노소 파레하 | 미구엘 도노소 파레하(Miguel Donoso Pareja, 1931년 7월 13일 – 2015년 3월 16일)는 에콰도르 작가이자 2006년 프레미오 에우제니오 에스페호 상(에콰도르 대통령이 수여하는 에콰도르 국가 문학상)을 수상했습니다."]

답은 반환된 첫 번째 맥락 청크에 있습니다. 언어 모델의 theinspect_history() 메서드를 사용하여 언어 모델의 기록을 살펴보면 DSPy가 최적의 프롬프트를 어떻게 설계했는지 확인할 수 있습니다.

lm.inspect_history()

이 기록은 DSPy가 생성된 프롬프트를 테스트한 컴파일 프로세스의 모든 예제를 포함하기 때문에 매우 깁니다. 기록의 마지막 부분은 모델이 올바른 답과 올바른 형식에 도달한 방법을 보여줍니다.

    [[ ## context ## ]]
    [1] «오르한 파묵 | 페리트 오르한 파묵(Ferit Orhan Pamuk, 보통 오르한 파묵으로 알려짐, 1952년 6월 7일생)은 터키의 소설가, 시나리오 작가, 학자이며 2006년 노벨 문학상 수상자입니다. 튀르키예에서 가장 저명한 소설가 중 한 명입니다. 그의 작품은 63개 언어로 1,300만 권 이상 판매되었으며 그는 튀르키예의 베스트 셀러 작가입니다.»
    [2] «2006 팔랑카 어워드 | 2006년 카를로스 팔랑카 기념 문학상 수상자(순위, 수상작 제목, 저자 이름).»
    [3] «미구엘 도노소 파레하 | 미구엘 도노소 파레하(Miguel Donoso Pareja, 1931년 7월 13일 – 2015년 3월 16일)는 에콰도르 작가이자 2006년 프레미오 에우제니오 에스페호 상(에콰도르 대통령이 수여하는 에콰도르 국가 문학상)을 수상했습니다.»
    
    [[ ## question ## ]]
    2006년 노벨 문학상 수상자는 어느 국가 출신이며 이름은 무엇이었습니까?
    
    '[[ ## reasoning ## ]]' 필드로 시작하여 '[[ ## answer ## ]]', '[[ ## completed ## ]]'에 대한 마커로 끝나는 아웃풋 필드로 응답합니다.
    
    
    [31mResponse:[0m
    
    [32m[[ ## reasoning ## ]]
    본문에는 2006년 노벨 문학상이 언급되어 있으며 튀르키예 소설가 오르한 파묵이 수상자라고 명시되어 있습니다.
    
    [[ ## answer ## ]]
    튀르키예, 오르한 파묵
    
    [[ ## completed ## ]][0m
    

DSPy가 모델을 사용하여 프롬프트를 생성한 것을 볼 수 있습니다.

관련 아웃풋 필드로 응답합니다. [[ ## reasoning ## ]] 필드에서 시작해 [[ ## answer ## ]] 로 넘어간 다음 [[ ## completed ## ]] .

이는 정답과 프레이밍으로 이어집니다.

요약

이 튜토리얼에서 여러분은 DSPy를 사용하여 watsonx 플랫폼에서 RAG 에이전트를 미세 조정하는 데 도움을 주었습니다. 이 RAG 에이전트는 언어 모델인 Llama 3과 검색 모델인 ColBERT로 구성되었습니다. 그리고 DSPy를 사용하여 모델을 컴파일하고 최적화된 프롬프트를 생성함으로써 질문 답변 작업을 위한 프롬프트 엔지니어링을 수행했습니다.

DSPy에 대한 자세한 내용은 튜토리얼, 데모 및 문서를 호스팅하는 GitHub 저장소에서 확인할 수 있습니다.

관련 솔루션
IBM® watsonx.ai

AI 빌더를 위한 차세대 엔터프라이즈 스튜디오인 IBM watsonx.ai로 생성형 AI, 파운데이션 모델 및 머신 러닝 기능을 학습, 검증, 조정 및 배포하세요. 적은 데이터로 짧은 시간 내에 AI 애플리케이션을 구축하세요.

watsonx.ai 살펴보기
인공 지능 솔루션

업계 최고의 AI 전문성과 솔루션 포트폴리오를 보유한 IBM과 함께 AI를 비즈니스에 활용하세요.

AI 솔루션 살펴보기
인공 지능(AI) 컨설팅 및 서비스

IBM Consulting AI 서비스는 기업이 AI 활용 방식을 재구상하여 혁신을 달성하도록 지원합니다.

AI 서비스 살펴보기
다음 단계 안내

IBM Concert는 AI를 사용하여 운영에 관한 중요한 인사이트를 발견하고 개선을 위한 애플리케이션별 권장 사항을 제공합니다. Concert를 통해 비즈니스를 발전시키는 방법을 알아보세요.

Concert 살펴보기 비즈니스 프로세스 자동화 솔루션 살펴보기