메인 콘텐츠로 건너뛰기
이 노트북은 대화형입니다. 로컬에서 실행하거나 아래 링크를 통해 열 수 있습니다:

DSPy와 Weave를 사용한 LLM 워크플로 최적화

BIG-bench (Beyond the Imitation Game Benchmark)는 200개가 넘는 태스크로 구성된 협업 벤치마크로, 대규모 언어 모델을 탐색하고 그 미래 성능을 추정하기 위해 설계되었습니다. BIG-Bench Hard (BBH)는 현재 세대의 언어 모델로는 풀기 상당히 어려운, 가장 도전적인 23개의 BIG-bench 태스크 모음입니다. 이 튜토리얼에서는 BIG-bench Hard 벤치마크의 causal judgement task에 대해 구현한 LLM 워크플로의 성능을 향상시키고, 프롬프트 전략을 평가하는 방법을 보여줍니다. LLM 워크플로를 구현하고 프롬프트 전략을 최적화하기 위해 DSPy를 사용합니다. 또한 Weave를 사용해 LLM 워크플로를 추적하고 프롬프트 전략을 평가합니다.

의존성 설치

이 튜토리얼에는 다음 라이브러리가 필요합니다:
  • LLM 워크플로를 구성하고 최적화하기 위한 DSPy
  • LLM 워크플로를 추적하고 프롬프트 전략을 평가하기 위한 Weave
  • HuggingFace Hub에서 Big-Bench Hard 데이터셋을 사용하기 위한 datasets
!pip install -qU dspy-ai weave datasets
LLM 공급자로 OpenAI API를 사용할 예정이므로 OpenAI API key도 필요합니다. OpenAI 플랫폼에서 가입하면 자신의 API key를 발급받을 수 있습니다.
import os
from getpass import getpass

api_key = getpass("Enter you OpenAI API key: ")
os.environ["OPENAI_API_KEY"] = api_key

Weave로 추적 활성화하기

Weave는 현재 DSPy와 통합되어 있으며, 코드의 시작 부분에 weave.init을 포함하면 DSPy 함수를 자동으로 추적(트레이싱)할 수 있고, 이를 Weave UI에서 확인할 수 있습니다. 자세한 내용은 DSPy용 Weave 통합 문서를 참고하세요.
import weave

weave.init(project_name="dspy-bigbench-hard")
이 튜토리얼에서는 메타데이터를 관리하기 위해 weave.Object을 상속한 메타데이터 클래스를 사용합니다.
class Metadata(weave.Object):
    dataset_address: str = "maveriq/bigbenchhard"
    big_bench_hard_task: str = "causal_judgement"
    num_train_examples: int = 50
    openai_model: str = "gpt-3.5-turbo"
    openai_max_tokens: int = 2048
    max_bootstrapped_demos: int = 8
    max_labeled_demos: int = 8

metadata = Metadata()
객체 버전 관리: Metadata 객체는 이를 사용하는 함수가 추적될 때 자동으로 버전 관리되고 추적됩니다

BIG-Bench Hard 데이터셋 로드하기

이 데이터셋을 HuggingFace Hub에서 불러온 다음, 학습 세트와 검증 세트로 나누고 Weave에 게시합니다. 이렇게 하면 데이터셋을 버전 관리할 수 있고, weave.Evaluation을 사용해 프롬프트 전략을 평가할 수도 있습니다.
import dspy
from datasets import load_dataset

@weave.op()
def get_dataset(metadata: Metadata):
    # Huggingface Hub에서 태스크에 해당하는 BIG-Bench Hard 데이터셋 로드
    dataset = load_dataset(metadata.dataset_address, metadata.big_bench_hard_task)[
        "train"
    ]

    # 학습 및 검증 데이터셋 생성
    rows = [{"question": data["input"], "answer": data["target"]} for data in dataset]
    train_rows = rows[0 : metadata.num_train_examples]
    val_rows = rows[metadata.num_train_examples :]

    # `dspy.Example` 객체로 구성된 학습 및 검증 예제 생성
    dspy_train_examples = [
        dspy.Example(row).with_inputs("question") for row in train_rows
    ]
    dspy_val_examples = [dspy.Example(row).with_inputs("question") for row in val_rows]

    # Weave에 데이터셋 게시 - 데이터 버전 관리 및 평가에 활용 가능
    weave.publish(
        weave.Dataset(
            name=f"bigbenchhard_{metadata.big_bench_hard_task}_train", rows=train_rows
        )
    )
    weave.publish(
        weave.Dataset(
            name=f"bigbenchhard_{metadata.big_bench_hard_task}_val", rows=val_rows
        )
    )

    return dspy_train_examples, dspy_val_examples

dspy_train_examples, dspy_val_examples = get_dataset(metadata)
데이터셋 준비 단계와 데이터 구조를 보여주는 DSPy 데이터셋 로딩 인터페이스

DSPy 프로그램

DSPy는 자유 형식 문자열을 직접 조작하는 방식에서 벗어나, 모듈식 연산자들을 조합해 텍스트 변환 그래프를 구성하는 프로그래밍 방식에 더 가깝게 LM 파이프라인을 구축할 수 있게 해 주는 프레임워크입니다. 이때 컴파일러가 프로그램으로부터 자동으로 최적화된 LM 호출 전략과 프롬프트를 생성합니다. 우리는 dspy.OpenAI 추상화를 사용하여 GPT-3.5 Turbo를 호출하는 LLM 요청을 수행하겠습니다.
system_prompt = """
You are an expert in the field of causal reasoning. You are to analyze the a given question carefully and answer in `Yes` or `No`.
You should also provide a detailed explanation justifying your answer.
"""

llm = dspy.OpenAI(model="gpt-3.5-turbo", system_prompt=system_prompt)
dspy.settings.configure(lm=llm)

인과 추론 시그니처 작성하기

시그니처는 임의의 텍스트 변환을 추상화하는, 신경망 레이어와 유사한 태스크 적응형 컴포넌트인 DSPy 모듈의 입력/출력 동작을 선언적으로 정의한 것입니다.
from pydantic import BaseModel, Field

class Input(BaseModel):
    query: str = Field(description="답변할 질문")

class Output(BaseModel):
    answer: str = Field(description="질문에 대한 답변")
    confidence: float = Field(
        ge=0, le=1, description="답변에 대한 신뢰도 점수"
    )
    explanation: str = Field(description="답변에 대한 설명")

class QuestionAnswerSignature(dspy.Signature):
    input: Input = dspy.InputField()
    output: Output = dspy.OutputField()

class CausalReasoningModule(dspy.Module):
    def __init__(self):
        self.prog = dspy.TypedPredictor(QuestionAnswerSignature)

    @weave.op()
    def forward(self, question) -> dict:
        return self.prog(input=Input(query=question)).output.dict()
이제 우리의 LLM 워크플로인 CausalReasoningModule을 Big-Bench Hard의 인과 추론 서브셋에 속한 예제로 테스트해 보겠습니다.
import rich

baseline_module = CausalReasoningModule()

prediction = baseline_module(dspy_train_examples[0]["question"])
rich.print(prediction)
Baseline DSPy program evaluation results with performance metrics and output examples

DSPy 프로그램 평가하기

이제 기본 프롬프트 전략을 마련했으니, 검증 세트에서 예측된 답을 정답과 비교하는 간단한 평가 지표를 사용해 weave.Evaluation으로 성능을 평가해 보겠습니다. Weave는 각 예제를 애플리케이션에 통과시킨 뒤, 여러 개의 사용자 정의 스코어링 함수로 출력값에 점수를 매깁니다. 이를 통해 애플리케이션의 성능을 한눈에 파악하고, 개별 출력과 점수를 깊이 탐색할 수 있는 풍부한 UI를 활용할 수 있습니다. 먼저, 베이스라인 모듈의 출력에서 나온 답이 정답과 같은지 여부를 판별하는 간단한 weave 평가 스코어링 함수를 만들어야 합니다. 스코어링 함수는 model_output 키워드 인자를 반드시 가져야 하지만, 그 외 인자들은 사용자 정의이며 데이터셋 예제에서 전달됩니다. 이 함수는 인자 이름에 해당하는 딕셔너리 키를 사용해 필요한 키만 선택합니다.
@weave.op()
def weave_evaluation_scorer(answer: str, output: Output) -> dict:
    return {"match": int(answer.lower() == output["answer"].lower())}
다음으로 평가를 정의하고 실행하면 됩니다.
validation_dataset = weave.ref(
    f"bigbenchhard_{metadata.big_bench_hard_task}_val:v0"
).get()

evaluation = weave.Evaluation(
    name="baseline_causal_reasoning_module",
    dataset=validation_dataset,
    scorers=[weave_evaluation_scorer],
)

await evaluation.evaluate(baseline_module.forward)
DSPy 프로그램 성능 메트릭, 트레이스, 비교 결과가 포함된 Weave 평가 대시보드
Python 스크립트에서 실행 중이라면, 다음 코드를 사용해 평가를 수행할 수 있습니다:
import asyncio
asyncio.run(evaluation.evaluate(baseline_module.forward))
인과 추론(causal reasoning) 데이터셋에 대한 평가를 실행하면 OpenAI 크레딧 약 $0.24가 사용됩니다.

DSPy 프로그램 최적화

이제 기준 DSPy 프로그램이 준비되었으니, BootstrapFewShot 텔레프롬프터를 사용해 인과 추론 성능을 향상시켜 보겠습니다. 이 텔레프롬프터는 지정한 메트릭을 최대화하도록 DSPy 프로그램의 파라미터를 조정할 수 있습니다.
from dspy.teleprompt import BootstrapFewShot

@weave.op()
def get_optimized_program(model: dspy.Module, metadata: Metadata) -> dspy.Module:
    @weave.op()
    def dspy_evaluation_metric(true, prediction, trace=None):
        return prediction["answer"].lower() == true.answer.lower()

    teleprompter = BootstrapFewShot(
        metric=dspy_evaluation_metric,
        max_bootstrapped_demos=metadata.max_bootstrapped_demos,
        max_labeled_demos=metadata.max_labeled_demos,
    )
    return teleprompter.compile(model, trainset=dspy_train_examples)

optimized_module = get_optimized_program(baseline_module, metadata)
텔리프롬프터 설정과 최적화 진행 상황이 포함된 DSPy 프로그램 최적화 프로세스 인터페이스
평가용 인과 추론 데이터셋을 실행하면 OpenAI 크레딧 기준으로 약 $0.04의 비용이 발생합니다.
이제 최적화된 프로그램(최적화된 프롬프트 전략)이 준비되었으므로, 이를 검증 세트에서 다시 한 번 평가하고 기존 베이스라인 DSPy 프로그램과 비교해 보겠습니다.
evaluation = weave.Evaluation(
    name="optimized_causal_reasoning_module",
    dataset=validation_dataset,
    scorers=[weave_evaluation_scorer],
)

await evaluation.evaluate(optimized_module.forward)
최적화된 DSPy 프로그램의 평가 결과: 향상된 성능 지표와 출력 품질
베이스라인 프로그램과 최적화된 프로그램의 평가 결과를 비교해 보면, 최적화된 프로그램이 인과 추론 질문에 훨씬 더 높은 정확도로 답변한다는 것을 알 수 있습니다.

결론

이 튜토리얼에서는 DSPy를 사용해 프롬프트를 최적화하고, Weave를 사용해 원본 프로그램과 최적화된 프로그램을 추적 및 평가하여 비교하는 방법을 살펴보았습니다.