평가(Evaluations) 개요

평가(Evaluations) 개요

LLM 애플리케이션을 몇 가지 예제로 대충 확인하다 보면, "진짜 잘 개선되고 있는 걸까?"라는 불확실함이 남아요. Weave의 *평가 중심 개발(evolution-driven development)*은 일관되고 정제된 예제들로 애플리케이션의 동작을 측정해서, 이 불확실함을 체계적으로 줄여 주는 방식이에요. 이 페이지에서는 Weave에서 평가를 구성하고 실행하는 방법, 반복 가능한 테스트 케이스로 성능을 측정하고 시간에 따른 변화와 회귀(regression)를 잡아내는 흐름을 살펴볼게요.

출처: Evaluations overview — W&B Weave 공식 문서

Evaluation 객체 만들기

평가 구성의 첫걸음은 Evaluation 객체를 만드는 거예요. Evaluation은 예제 데이터, 채점 로직, 선택적인 전처리로 구성돼요. 이후에 이걸로 평가를 한 번 이상 실행하게 됩니다.

Weave는 각 예제를 애플리케이션에 통과시킨 뒤, 여러 개의 커스텀 채점 함수로 출력을 점수화해요. 그 결과로 애플리케이션의 성능을 한눈에 볼 수 있고, 개별 출력·점수를 들여다볼 수 있는 풍부한 UI도 제공됩니다.

커스텀 이름은 선택 사항이에요. 하지만 UI에서 서로 다른 평가 구성과 실행을 구분하는 데 도움이 되므로, 평가가 하나뿐일 때만 넘어가도 돼요.

선택: 커스텀 이름 붙이기

평가 흐름에는 커스텀 이름을 붙일 수 있는 자리가 두 군데 있어요.

  • Evaluation 객체 이름 (evaluation_name): 설정한 Evaluation 객체에 붙는 영구 라벨.
  • 평가 실행 표시 이름 (__weave["display_name"]): 특정 평가 실행에 붙어 UI에 보이는 라벨.

Evaluation 객체 자체에 이름을 붙이려면 Evaluation 클래스에 evaluation_name 파라미터를 넘겨요.

evaluation = Evaluation(
    dataset=examples, scorers=[match_score1], evaluation_name="My Evaluation"
)

특정 평가 실행(evaluate() 호출)에 이름을 붙이려면 __weave 딕셔너리에 display_name을 넣어요.

evaluation = Evaluation(
    dataset=examples, scorers=[match_score1]
)
evaluation.evaluate(model, __weave={"display_name": "My Evaluation Run"})

테스트 예제 데이터셋 정의하기

먼저 Dataset 객체나 예제 목록으로 평가할 예제 모음을 정의해요. 이 예제들은 흔히 테스트하고 싶은 실패 사례예요. 마치 테스트 주도 개발(TDD)의 단위 테스트와 비슷하죠.

딕셔너리 목록으로 정의한 데이터셋 예시를 볼게요.

examples = [
    {"question": "What is the capital of France?", "expected": "Paris"},
    {"question": "Who wrote 'To Kill a Mockingbird'?", "expected": "Harper Lee"},
    {"question": "What is the square root of 64?", "expected": "8"},
]

채점 함수 정의하기

그다음으로 채점 함수를 하나 이상 만들어요. Weave는 이 함수들로 Dataset의 각 예제를 채점합니다.

각 채점 함수는 output 파라미터를 가져야 하고, 점수를 담은 딕셔너리를 반환해야 해요. 선택적으로 예제의 다른 입력도 포함할 수 있습니다.

채점 함수는 output 키워드 인자를 반드시 가져야 하지만, 나머지 인자는 사용자가 정의하고 데이터셋 예제에서 가져와요. Weave는 인자 이름을 기준으로 딕셔너리 키를 만들어 필요한 키만 사용합니다.

스코어러가 output 인자를 기대하는데 받지 못한다면, 레거시 model_output 키를 쓰고 있는 건 아닌지 확인해 보세요. output 키워드 인자를 쓰도록 고치면 해결돼요.

examples 딕셔너리의 expected 값을 사용해 채점하는 match_score1 함수 예시예요.

import weave

# Collect your examples
examples = [
    {"question": "What is the capital of France?", "expected": "Paris"},
    {"question": "Who wrote 'To Kill a Mockingbird'?", "expected": "Harper Lee"},
    {"question": "What is the square root of 64?", "expected": "8"},
]

# Define any custom scoring function
@weave.op()
def match_score1(expected: str, output: dict) -> dict:
    # Here is where you'd define the logic to score the model output
    return {'match': expected == output['generated_text']}

선택: 커스텀 Scorer 클래스 정의

어떤 애플리케이션은 커스텀 Scorer 클래스를 만들고 싶어 하기도 해요. 예를 들어 특정 파라미터(채팅 모델, 프롬프트), 행별 채점 방식, 집계(aggregate) 점수 계산 방식을 가진 표준화된 LLMJudge 클래스를 만들 수 있어요. Scorer 클래스 정의는 RAG 애플리케이션 모델 기반 평가 튜토리얼에서 더 자세히 다룹니다.

평가할 모델(또는 함수) 정의하기

Model 객체를 평가하려면 Evaluation으로 .evaluate()를 호출해요. Weave에서 실험하고 기록하고 싶은 파라미터가 있을 때는 Model 객체를 사용합니다.

from weave import Model, Evaluation
import asyncio

class MyModel(Model):
    prompt: str

    @weave.op()
    def predict(self, question: str):
        # here's where you would add your LLM call and return the output
        return {'generated_text': 'Hello, ' + self.prompt}

model = MyModel(prompt='World')

evaluation = Evaluation(
    dataset=examples, scorers=[match_score1]
)
weave.init('intro-example') # begin tracking results with weave
asyncio.run(evaluation.evaluate(model))

이 코드는 각 예제마다 predict를 실행하고, 각 채점 함수로 출력을 점수화합니다.

선택: 함수로 평가하기

@weave.op()로 추적되는 커스텀 함수를 평가할 수도 있어요.

@weave.op
def function_to_evaluate(question: str):
    # here's where you would add your LLM call and return the output
    return  {'generated_text': 'some response'}

asyncio.run(evaluation.evaluate(function_to_evaluate))

평가 실행하기

Evaluation 객체, 데이터셋, 채점 함수, 모델이 준비됐다면 이제 평가를 실행하고 결과를 Weave에 담을 차례예요. 평가를 실행하려면 Evaluation 객체에서 .evaluate()를 호출하면 됩니다. evaluation이라는 Evaluation 객체와 model이라는 Model 객체가 있다고 가정하면:

asyncio.run(evaluation.evaluate(model))

선택: 여러 번 시행하기 (trials)

Evaluation 객체의 trials 파라미터를 설정하면 각 예제를 여러 번 실행할 수 있어요.

evaluation = Evaluation(
    dataset=examples,
    scorers=[match_score],
    trials=3
)

trials=3이면 각 예제를 모델에 세 번 통과시키고, Weave는 각 시행을 독립적으로 채점하고 표시합니다.

전체 평가 코드 예시

앞선 단계를 한 번에 실행 가능한 하나의 스크립트로 모은 예시예요. 자신의 애플리케이션에 평가 흐름을 적용할 때 참고 자료로 쓰면 됩니다.

from weave import Evaluation, Model
import weave
import asyncio
weave.init('intro-example')
examples = [
    {"question": "What is the capital of France?", "expected": "Paris"},
    {"question": "Who wrote 'To Kill a Mockingbird'?", "expected": "Harper Lee"},
    {"question": "What is the square root of 64?", "expected": "8"},
]

@weave.op()
def match_score1(expected: str, output: dict) -> dict:
    return {'match': expected == output['generated_text']}

@weave.op()
def match_score2(expected: dict, output: dict) -> dict:
    return {'match': expected == output['generated_text']}

class MyModel(Model):
    prompt: str

    @weave.op()
    def predict(self, question: str):
        # here's where you would add your LLM call and return the output
        return {'generated_text': 'Hello, ' + question + self.prompt}

model = MyModel(prompt='World')
evaluation = Evaluation(dataset=examples, scorers=[match_score1, match_score2])

asyncio.run(evaluation.evaluate(model))

@weave.op()
def function_to_evaluate(question: str):
    # here's where you would add your LLM call and return the output
    return  {'generated_text': 'some response' + question}

asyncio.run(evaluation.evaluate(function_to_evaluate("What is the capitol of France?")))

고급 평가 기능

평가 전 데이터셋 행 포맷하기

preprocess_model_input 파라미터로 데이터셋 예제를 평가 함수에 넘기기 전에 변형할 수 있어요. 이런 경우에 유용합니다.

  • 모델이 기대하는 입력에 맞게 필드 이름을 바꿀 때
  • 데이터를 올바른 형식으로 변환할 때
  • 필드를 추가하거나 제거할 때
  • 각 예제에 추가 데이터를 불러올 때

Weave는 preprocess_model_input 함수를 모델의 예측 함수에 입력을 넘기기 전에만 적용해요. 채점 함수는 항상 전처리되지 않은 원래 데이터셋 예제를 받습니다.

필드 이름을 바꾸는 데 preprocess_model_input을 쓰는 예시를 볼게요.

import weave
from weave import Evaluation
import asyncio

# Our dataset has "input_text" but our model expects "question"
examples = [
    {"input_text": "What is the capital of France?", "expected": "Paris"},
    {"input_text": "Who wrote 'To Kill a Mockingbird'?", "expected": "Harper Lee"},
    {"input_text": "What is the square root of 64?", "expected": "8"},
]

@weave.op()
def preprocess_example(example):
    # Rename input_text to question
    return {
        "question": example["input_text"]
    }

@weave.op()
def match_score(expected: str, output: dict) -> dict:
    return {'match': expected == output['generated_text']}

@weave.op()
def function_to_evaluate(question: str):
    return {'generated_text': f'Answer to: {question}'}

# Create evaluation with preprocessing
evaluation = Evaluation(
    dataset=examples,
    scorers=[match_score],
    preprocess_model_input=preprocess_example
)

# Run the evaluation
weave.init('preprocessing-example')
asyncio.run(evaluation.evaluate(function_to_evaluate))

이 예시에서 데이터셋은 input_text 필드를 갖지만, 평가 함수는 question 파라미터를 기대해요. preprocess_example 함수가 필드 이름을 바꿔서 평가가 올바르게 동작하게 해줍니다. 전처리 함수는 1) 데이터셋의 원본 예제를 받아 2) 모델이 기대하는 필드를 담은 딕셔너리를 반환하고 3) Weave가 평가 함수에 넘기기 전에 각 예제마다 실행돼요.

HuggingFace 데이터셋과 함께 쓰기

preprocess_model_input을 활용하면 Weave 평가에서 HuggingFace Datasets를 쓸 수 있어요. 자세한 내용은 Using HuggingFace datasets in evaluations cookbook을 참고하세요.

저장된 보기 (Saved views)

Evals 테이블의 설정·필터·정렬을 *저장된 보기(saved views)*로 저장해 두면 선호하는 구성을 빠르게 다시 불러올 수 있어요. 저장된 보기는 UI와 Python SDK 모두에서 구성하고 접근할 수 있습니다. 자세한 내용은 Saved Views를 확인해 주세요.

명령형 평가 (EvaluationLogger)

더 유연한 평가 프레임워크를 원한다면 Weave의 EvaluationLogger를 살펴보세요. EvaluationLogger는 Python과 TypeScript 양쪽에서 사용할 수 있고 복잡한 워크플로에 더 많은 유연성을 주는 반면, 표준 평가 프레임워크는 더 많은 구조와 안내를 제공해요.

더 알아보기