채점(Scorers) 개요

채점(Scorers) 개요

AI 시스템의 출력이 "좋은지"를 판단하는 기준은 애플리케이션마다 달라요. Weave의 Scorer는 AI 출력을 받아 분석하고, 평가 지표(metric)를 담은 딕셔너리를 반환해서 이 판단을 자동화해 주는 도구예요. 필요하면 입력 데이터를 참조로 쓰고, 평가의 설명이나 추론 같은 부가 정보를 함께 내보낼 수도 있죠. 이 페이지에서는 Scorer가 Weave 평가에서 어떻게 쓰이는지, 직접 만드는 방법, 그리고 개별 호출에 적용하고 결과를 분석하는 법까지 다룰게요.

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

나만의 Scorer 만들기

커스텀 Scorer를 쓰면 내장 Scorer가 다루지 못하는, 자신의 사용 사례에 맞는 평가 기준을 코드로 담을 수 있어요. Scorer를 정의하는 방법은 함수 방식과, 더 복잡한 로직을 위한 클래스 방식 두 가지가 있습니다.

Weave에는 필터를 쓰지 않고 바로 사용할 수 있는 미리 정의된 Scorer로컬 SLM Scorer도 준비돼 있어요. 할루시네이션 탐지, 요약 품질, 임베딩 유사도, (로컬) 독성 탐지, (로컬) 컨텍스트 관련성 채점 등이 포함됩니다.

함수 기반 Scorer

함수 기반 Scorer는 @weave.op 데코레이터가 붙고 딕셔너리를 반환하는 함수예요. 간단한 평가에 잘 맞습니다.

import weave

@weave.op
def evaluate_uppercase(text: str) -> dict:
    return {"text_is_uppercase": text.isupper()}

my_eval = weave.Evaluation(
    dataset=[{"text": "HELLO WORLD"}],
    scorers=[evaluate_uppercase]
)

평가를 실행하면 evaluate_uppercase가 텍스트가 전부 대문자인지 확인해요.

클래스 기반 Scorer

더 고급 평가, 특히 스코어러의 추가 메타데이터를 추적하거나, LLM 평가자에 다른 프롬프트를 시도하거나, 여러 함수 호출이 필요할 때는 Scorer 클래스를 써요. 요구사항은 세 가지예요.

  1. weave.Scorer를 상속받고,
  2. @weave.op 데코레이터가 붙은 score 메서드를 정의하고,
  3. score 메서드는 딕셔너리를 반환해야 한다.
import weave
from openai import OpenAI
from weave import Scorer

llm_client = OpenAI()

class SummarizationScorer(Scorer):
    model_id: str = "gpt-4o"
    system_prompt: str = "Evaluate whether the summary is good."

    @weave.op
    def some_complicated_preprocessing(self, text: str) -> str:
        processed_text = "Original text: \n" + text + "\n"
        return processed_text

    @weave.op
    def call_llm(self, summary: str, processed_text: str) -> dict:
        res = llm_client.chat.completions.create(
            messages=[
                {"role": "system", "content": self.system_prompt},
                {"role": "user", "content": (
                    f"Analyze how good the summary is compared to the original text."
                    f"Summary: {summary}\n{processed_text}"
                )}])
        return {"summary_quality": res}

    @weave.op
    def score(self, output: str, text: str) -> dict:
        """Score the summary quality.

        Args:
            output: The summary generated by an AI system
            text: The original text to summarize
        """
        processed_text = self.some_complicated_preprocessing(text)
        eval_result = self.call_llm(summary=output, processed_text=processed_text)
        return {"summary_quality": eval_result}

evaluation = weave.Evaluation(
    dataset=[{"text": "The quick brown fox jumps over the lazy dog."}],
    scorers=[summarization_scorer])

이 클래스는 요약을 원문과 비교해 요약 품질을 평가해요.

Scorer 동작 방식

Scorer 키워드 인자

Scorer는 AI 시스템의 출력과 데이터셋 행의 입력 데이터 양쪽에 접근할 수 있어요.

  • 입력: 데이터셋 행의 데이터(예: label·target 컬럼)를 스코어러에 쓰려면, 스코어러 정의에 label이나 target 키워드 인자를 추가해서 사용할 수 있게 하면 돼요. 예를 들어 label 컬럼을 쓰려면 스코어러 함수(또는 score 클래스 메서드)의 파라미터 목록이 이렇게 돼요.
@weave.op
def my_custom_scorer(output: str, label: int) -> dict:
    ...

Weave Evaluation이 실행되면 AI 시스템의 출력을 output 파라미터에 넘겨요. Evaluation은 또한 추가 스코어러 인자 이름과 데이터셋 컬럼을 자동으로 매칭하려 시도합니다. 인자나 컬럼 이름을 바꾸기 어렵다면 column mapping을 쓸 수 있어요(아래 참조).

  • 출력: 스코어러 함수 시그니처에 output 파라미터를 넣어 AI 시스템 출력에 접근해요.

column_map로 컬럼 이름 매핑하기

가끔은 score 메서드의 인자 이름이 데이터셋 컬럼 이름과 맞지 않을 때가 있어요. 이럴 때 column_map으로 해결할 수 있습니다. 클래스 기반 스코어러라면 초기화할 때 Scorercolumn_map 속성에 딕셔너리를 넘겨요. 딕셔너리는 {scorer_keyword_argument: dataset_column_name} 순서로 score 메서드 인자 이름을 데이터셋 컬럼 이름에 매핑해요.

import weave
from weave import Scorer

# A dataset of news articles to summarize
dataset = [
    {"news_article": "The news today was great...", "date": "2030-04-20", "source": "Bright Sky Network"},
    ...
]

# Scorer class
class SummarizationScorer(Scorer):

    @weave.op
    def score(self, output, text) -> dict:
        """
            output: output summary from an LLM summarization system
            text: the text to summarize
        """
        ...  # evaluate the quality of the summary

# create a scorer with a column mapping the `text` argument to the `news_article` data column
scorer = SummarizationScorer(column_map={"text" : "news_article"})

이제 score 메서드의 text 인자가 news_article 데이터셋 컬럼의 데이터를 받아요. 컬럼을 매핑하는 또 다른 동등한 방법으로는 Scorer를 서브클래싱해 score 메서드를 오버로드하고 컬럼을 명시적으로 매핑하는 것도 있어요.

import weave
from weave import Scorer

class MySummarizationScorer(SummarizationScorer):

    @weave.op
    def score(self, output: str, news_article: str) -> dict:  # Added type hints
        # overload the score method and map columns manually
        return super().score(output=output, text=news_article)

채점 프롬프트에서 op 변수 참조하기

LLM-as-a-judge 스코어러의 채점 프롬프트에서는 op의 변수를 참조할 수 있어요. 스코어러가 실행될 때 Weave가 이 값들을 자동으로 추출합니다. 예를 들어 이런 함수가 있다면:

@weave.op
def summarize_article(article: str, max_length: int) -> str:
    # Your summarization logic here
    return summary

다음 변수들을 사용할 수 있어요.

Variable Description
{article} The value of the input argument article
{max_length} The value of the input argument max_length
{inputs} A JSON dictionary of all input arguments
{output} The result returned by your op

채점 프롬프트 예시:

Evaluate the quality of this summary.

Original article: {article}
Summary: {output}
Maximum length requested: {max_length}

Rate the summary on a scale of 1-10 based on:
- Accuracy: Does it accurately represent the article?
- Completeness: Does it cover the key points?
- Conciseness: Is it appropriately brief?

Return a JSON object with your rating and reasoning.

Scorer의 최종 요약 (summarize)

평가 중에 Weave는 데이터셋의 각 행에 대해 스코어러를 계산해요. 평가의 최종 점수를 내기 위해 Weave는 출력의 반환 타입에 따라 auto_summarize를 실행합니다. Scorer 클래스에서 summarize 메서드를 오버라이드해서 최종 점수를 직접 계산할 수도 있어요. summarize 함수는 score_rows(데이터셋 각 행에 대해 score 메서드가 반환한 점수를 담은 딕셔너리 목록) 하나를 받고, 요약된 점수를 담은 딕셔너리를 반환합니다.

전체 행을 채점한 뒤에 데이터셋의 최종 점수를 결정해야 할 때 유용해요.

class MyBinaryScorer(Scorer):
    """
    Returns True if the full output matches the target, False if not
    """

    @weave.op
    def score(self, output, target):
        return {"match": output == target}

    def summarize(self, score_rows: list) -> dict:
        full_match = all(row["match"] for row in score_rows)
        return {"full_match": full_match}

이 예시에서 기본 auto_summarize는 True의 개수와 비율을 반환할 거예요.

자세한 내용은 CorrectnessLLMJudge 구현을 참고하세요.

호출에 Scorer 적용하기

Scorer를 weave.Evaluation의 일부로 실행하는 것 외에도, 개별 호출에 직접 적용할 수 있어요. 프로덕션 트래픽을 채점하거나 특정 op 호출에 평가 지표를 붙이고 싶을 때 유용합니다. Scorer를 Weave op에 적용하려면 .call() 메서드를 써요. 이 메서드는 연산 결과와 추적 정보 양쪽에 접근하게 해 줍니다. .call() 메서드에 대한 더 자세한 내용은 Calling Ops 가이드를 참고하세요.

기본 예시:

# Get both result and Call object
result, call = generate_text.call("Say hello")

# Apply a scorer
score = await call.apply_scorer(MyScorer())

같은 호출에 여러 스코어러를 적용할 수도 있어요.

# Apply multiple scorers in parallel
await asyncio.gather(
    call.apply_scorer(quality_scorer),
    call.apply_scorer(toxicity_scorer)
)

참고할 점이 몇 가지 있어요. Weave는 스코어러 결과를 자동으로 DB에 저장하고, 스코어러는 메인 연산이 완료된 뒤 비동기로 실행되며, 결과는 UI에서 보거나 API로 조회할 수 있어요. 스코어러를 가드레일(guardrails)이나 모니터로 쓰는 방법, 프로덕션 모범 사례와 완전한 예시는 Guardrails and Monitors 가이드를 참고하세요.

preprocess_model_input 사용하기

preprocess_model_input 파라미터로 데이터셋 예제를 평가 중에 모델에 도달하기 전에 수정할 수 있어요.

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

사용법과 예시는 Evaluations에서 preprocess_model_input로 데이터셋 행 포맷하기를 참고하세요.

점수 분석

Scorer가 실행된 뒤에는 산출된 점수를 살펴보고 싶어질 거예요. 모델 동작을 이해하거나 버전을 비교하기 위해서죠. 여기서는 API와 Weave UI로 단일·다중 호출, 그리고 특정 Scorer가 채점한 모든 호출의 점수를 분석하는 방법을 다룹니다.

단일 호출의 점수 분석

단일 호출을 가져오려면 get_call 메서드를 써요.

client = weave.init("my-project")

# Get a single call
call = client.get_call("call-uuid-here")

# Get the feedback for the call which contains the scores
feedback = list(call.feedback)

UI에서는 Call 세부 정보 패널의 Scores 탭에서 개별 호출의 점수가 표시됩니다.

여러 호출의 점수 분석

여러 호출을 가져오려면 get_calls 메서드를 사용해요.

client = weave.init("my-project")

# Get multiple calls - use whatever filters you want and include feedback
calls = client.get_calls(..., include_feedback=True)

# Iterate over the calls and access the feedback which contains the scores
for call in calls:
    feedback = list(call.feedback)

UI에서는 트레이스 테이블의 Scores 컬럼에서 여러 호출의 점수를 확인할 수 있어요.

특정 Scorer가 채점한 모든 호출 분석

특정 스코어러가 채점한 모든 호출을 가져오려면 get_calls 메서드를 써요.

client = weave.init("my-project")

# To get all the calls scored by any version of a scorer, use the scorer name (typically the class name)
calls = client.get_calls(scored_by=["MyScorer"], include_feedback=True)

# To get all the calls scored by a specific version of a scorer, use the entire ref
# Refs can be obtained from the scorer object or via the UI.
calls = client.get_calls(scored_by=[myScorer.ref.uri()], include_feedback=True)

# Iterate over the calls and access the feedback which contains the scores
for call in calls:
    feedback = list(call.feedback)

UI에서는 Scorers 탭의 Programmatic Scorer 탭으로 이동해 스코어러를 클릭하면 세부 페이지가 열려요. Scores 아래의 View Traces 버튼을 클릭하면 해당 Scorer가 채점한 모든 호출을 볼 수 있습니다. 기본값은 선택한 Scorer 버전 기준이지만, 버전 필터를 제거하면 어떤 버전이든 그 Scorer가 채점한 모든 호출을 볼 수 있어요.

더 알아보기