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

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

BIG-bench (Beyond the Imitation Game Benchmark)는 200개가 넘는 작업으로 구성된 협업형 벤치마크로, 대규모 언어 모델을 심층적으로 평가하고 그 미래 역량을 가늠하기 위해 만들어졌습니다. BIG-Bench Hard (BBH)는 BIG-Bench 과제 중 가장 어려운 23개를 모아 놓은 벤치마크로, 현재 세대의 언어 모델로는 해결하기 매우 까다로울 수 있습니다. 이 튜토리얼에서는 BIG-bench Hard 벤치마크의 causal judgement task를 바탕으로 구현한 LLM 워크플로의 성능을 개선하고, 프롬프팅 전략을 평가하는 방법을 살펴봅니다. LLM 워크플로를 구현하고 프롬프팅 전략을 최적화하는 데 DSPy를 사용합니다. 또한 LLM 워크플로를 추적하고 프롬프팅 전략을 평가하기 위해 Weave도 사용합니다.

의존성 설치

이 튜토리얼에서는 다음 라이브러리가 필요합니다:
  • DSPy: LLM 워크플로를 구축하고 최적화하는 데 사용합니다.
  • Weave: LLM 워크플로를 추적하고 프롬프팅 전략을 평가하는 데 사용합니다.
  • datasets: HuggingFace Hub에서 Big-Bench Hard 데이터셋에 액세스하는 데 사용합니다.
!pip install -qU dspy weave "datasets<4"
LLM 공급업체로 OpenAI API를 사용할 예정이므로 OpenAI API 키도 필요합니다. OpenAI 플랫폼에서 회원가입하여 직접 API 키를 발급받을 수 있습니다.
import os
from getpass import getpass

api_key = getpass("OpenAI API 키를 입력하세요: ")
os.environ["OPENAI_API_KEY"] = api_key

Weave를 사용해 추적 활성화

현재 Weave는 DSPy와 통합되어 있으며, 코드 시작 부분에서 weave.init을 호출하면 Weave UI에서 탐색할 수 있도록 DSPy 함수를 자동으로 트레이스합니다. 자세한 내용은 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-4o-mini"
    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)
데이터셋 준비 step과 데이터 구조를 보여주는 DSPy 데이터셋 로딩 인터페이스

DSPy 프로그램

DSPy는 자유 형식 문자열을 직접 조작하는 방식에서 벗어나, 프로그래밍(모듈식 Operator를 조합해 텍스트 변환 그래프를 구성하는 방식)에 더 가깝게 새로운 LM 파이프라인을 구축하도록 돕는 프레임워크입니다. 여기서 컴파일러는 프로그램으로부터 최적화된 LM 호출 전략과 프롬프트를 자동으로 생성합니다. 언어 모델을 설정하기 위해 dspy.LM을 사용하고, 이를 기본값으로 지정하기 위해 dspy.configure를 사용합니다.
llm = dspy.LM("openai/gpt-4o-mini")
dspy.configure(lm=llm)

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

시그니처DSPy 모듈의 입력/출력 동작을 선언적으로 정의하는 명세입니다. DSPy 모듈은 작업에 맞게 적응하는 컴포넌트로, 신경망의 계층과 유사하며 특정 텍스트 변환을 추상화합니다.
class CausalReasoning(dspy.Signature):
    """You are an expert in causal reasoning. Analyze the given question carefully
    and answer Yes or No. Provide a detailed explanation justifying your answer."""

    question: str = dspy.InputField(desc="답변할 질문")
    answer: str = dspy.OutputField(desc="Yes or No")
    confidence: float = dspy.OutputField(desc="0과 1 사이의 신뢰도 점수")
    explanation: str = dspy.OutputField(desc="답변에 대한 상세 설명")

class CausalReasoningModule(dspy.Module):
    def __init__(self):
        self.prog = dspy.Predict(CausalReasoning)

    @weave.op()
    def forward(self, question: str) -> dict:
        result = self.prog(question=question)
        return {
            "answer": result.answer,
            "confidence": result.confidence,
            "explanation": result.explanation,
        }
예제로 Big-Bench Hard의 인과 추론 하위 집합에 있는 항목을 사용해 LLM 워크플로, 즉 CausalReasoningModule을 테스트해 보겠습니다.
import rich

baseline_module = CausalReasoningModule()

prediction = baseline_module(dspy_train_examples[0]["question"])
rich.print(prediction)
성능 메트릭과 출력 예시가 포함된 베이스라인 DSPy 프로그램 평가 결과

DSPy 프로그램 평가하기

이제 베이스라인 프롬프팅 전략이 있으므로, 예측된 답변이 정답과 일치하는지 확인하는 단순한 메트릭으로 weave.Evaluation을 사용해 검증 세트에서 평가해 보겠습니다. Weave는 각 예시를 가져와 애플리케이션에 전달한 다음, 여러 맞춤형 채점 함수로 출력을 평가합니다. 이렇게 하면 애플리케이션의 성능을 보여주는 뷰를 얻을 수 있고, 개별 출력과 점수를 자세히 살펴볼 수 있는 풍부한 UI도 활용할 수 있습니다. 먼저 예측된 답변이 정답과 일치하는지 판별하는 채점 함수를 만들어야 합니다. Weave 채점 함수는 모델의 반환 값을 output으로 받고, 데이터셋 예시의 일치하는 키를 추가 인수로 받습니다. 여기서 answer는 데이터셋에서 오고, outputCausalReasoningModule.forward가 반환한 dict입니다.
@weave.op()
def weave_evaluation_scorer(answer: str, output: dict) -> dict:
    return {"match": int(answer.lower() == output["answer"].lower())}
다음으로, weave.Evaluation이 호출할 수 있는 트레이스 함수로 모듈을 감쌉니다. 래퍼의 인수 이름은 모델이 사용하는 데이터셋 열 이름과 일치해야 합니다.
@weave.op()
def predict(question: str) -> dict:
    return baseline_module(question=question)
이제 Evaluation을 정의하고 실행할 수 있습니다.
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(predict)
DSPy 프로그램의 성능 메트릭, 트레이스, 비교 결과를 보여주는 Weave 평가 대시보드
Python 스크립트에서 실행하는 경우, 다음 코드를 사용해 평가를 실행할 수 있습니다.
import asyncio
asyncio.run(evaluation.evaluate(predict))
인과 추론 데이터셋으로 평가를 실행하면 OpenAI 크레딧이 약 $0.24 소요됩니다.

DSPy 프로그램 최적화하기

이제 베이스라인 DSPy 프로그램이 있으니, 지정된 메트릭을 최대화하도록 DSPy 프로그램의 매개변수를 조정할 수 있는 BootstrapFewShot optimizer를 사용해 인과 추론 성능을 개선해 보겠습니다.
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 프로그램과 비교해 보겠습니다.
@weave.op()
def predict_optimized(question: str) -> dict:
    return optimized_module(question=question)

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

await evaluation.evaluate(predict_optimized)
향상된 성능 메트릭과 출력 품질을 보여주는 최적화된 DSPy 프로그램 평가 결과
베이스라인 프로그램과 최적화된 프로그램의 평가를 비교해 보면, 최적화된 프로그램이 인과 추론 질문에 훨씬 더 정확하게 답한다는 것을 알 수 있습니다.

결론

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