싱글턴 컴포넌트 레벨 평가

싱글턴 컴포넌트 레벨 평가 (Single-Turn, Component-Level Evals)

싱글턴 유스케이스에 대해 컴포넌트 레벨 테스트를 실행하는 방법을 배워볼게요. 컴포넌트 레벨 테스트는 최종 아웃풋만이 아니라 LLM 애플리케이션의 개별 부분 — 리트리버, 생성기, 툴, 플래너 — 을 평가하게 해줘요. 어떤 컴포넌트가 실패하는지 정확히 짚어야 하는 복잡한 파이프라인 디버깅에 필수적이에요.

출처: 문서

본문

개요

컴포넌트 레벨 테스트는 최종 아웃풋만이 아니라 LLM 애플리케이션의 개별 부분 — 리트리버, 생성기, 툴, 플래너 — 을 평가하게 해줘요. 이는 어떤 컴포넌트가 정확히 실패하는지 짚어야 하는 복잡한 파이프라인을 디버깅하는 데 필수적이에요.

요구 사항 (Requirements):

컴포넌트 레벨 테스트는 현재 deepeval Python을 사용하는 사용자만 지원하며, 로컬에서 반드시 실행해야 해요. 하지만 프로덕션 컴포넌트에 대한 임시(ad-hoc) 온라인 평가는 여전히 여기서 가능해요.

이 페이지의 @observe 데코레이터는 DeepEval의 것이며, 로컬이나 CI에서 실행하는 코드 기반 평가를 위해 설계됐어요. 프로덕션에서 앱을 트레이싱하려면 confident-trace를 대신 사용하세요 — LLM 트레이싱 퀵스타트를 참고하세요. 둘 다 같은 프로젝트로 데이터를 보내므로, 개발 시 트레이스와 프로덕션 트레이스가 나란히 존재해요.

동작 방식

  1. @observe 데코레이터로 LLM 트레이싱을 설정하고 각 컴포넌트에 대한 metrics를 정의
  2. Confident AI에서 데이터셋 가져오기
  3. evals_iterator()로 골든스를 순회하며 LLM 앱 호출
sequenceDiagram
    participant Your Code
    participant Confident AI
    participant LLM App
    participant Components

    Your Code->>Confident AI: Pull dataset
    Confident AI-->>Your Code: List of goldens (count = N)

    loop N times via evals_iterator()
        Your Code->>LLM App: Invoke app
        LLM App->>Components: @observe captures spans
        Components->>Components: update_current_span() sets test case fields
        Components-->>LLM App: Component metric(s) executes
    end

    LLM App-->>Confident AI: Upload traces & run component metrics
    Note over Confident AI: Generate testing report

종단 간 테스트와 달리, LLM 앱을 호출하는 데 golden.input을 쓸 필요가 없어요. evals_iterator()는 단순히 앱이 몇 번 실행되는지(골든스당 한 번) 제어해요. 테스트 케이스 필드는 각 컴포넌트 안의 update_current_span()으로 설정돼요 — 골든스는 반복 횟수만 결정해요.

컴포넌트 레벨 테스트 로컬에서 실행하기 (Run Component-Level Tests Locally)

이 섹션은 LLM 트레이싱으로 종단 간 평가를 실행한 이전 섹션의 해당 부분과 거의 동일해요. LLM 트레이싱이 워낙 편해서 뭐든 평가할 수 있기 때문이에요.

이 예시에서는 기본적으로 update_current_trace를 update_current_span으로만 바꾸는 거예요.

LLM 트레이싱을 보여주기 위해 이전 섹션의 같은 mock LLM 앱을 사용할게요:

Mock LLM 앱 보기

from openai import OpenAI

def llm_app(query: str) -> str:
    # Retriever for your vector db
    def retriever(query: str) -> list[str]:
        return ["List", "of", "text", "chunks"]
    # Generator that combines retrieved context with user query
    def generator(query: str, text_chunks: list[str]) -> str:
        return OpenAI().chat.completions.create(
            model="gpt-4o",
            messages=[
                {"role": "user", "content": query}
            ]
        ).choices[0].message.content
    # Calls retriever then generator
    return generator(query, retriever(query))

LLM 트레이싱 설정 및 메트릭 정의

애플리케이션을 @observe 데코레이터로 감싸고, 평가하고 싶은 컴포넌트에 metrics를 제공해요:

from openai import OpenAI
from deepeval.metrics import AnswerRelevancyMetric, ContextualRelevancyMetric
from deepeval.tracing import observe, update_current_span

@observe()
def llm_app(query: str) -> str:

    @observe(metrics=[ContextualRelevancyMetric()], embedder="your-embedding-model-name")
    def retriever(query: str) -> list[str]:
        chunks = ["List", "of", "text", "chunks"]
        update_current_span(input=query, retrieval_context=chunks)
        return chunks

    @observe(metrics=[AnswerRelevancyMetric()])
    def generator(query: str, text_chunks: list[str]) -> str:
        res = OpenAI().chat.completions.create(model="gpt-4o", messages=[{"role": "user", "content": query}]
        ).choices[0].message.content
        update_current_span(input=query, output=res)
        return res

    return generator(query, retriever(query))

위 예시는 몇 개의 @observe 데코레이터만 추가해 LLM 앱을 트레이싱하는 방법을 보여줘요:

  • 각 @observe 데코레이터는 **스팬(span)**을 만들며, 이는 컴포넌트를 나타내요
  • 반면 **트레이스(trace)**는 최상위 @observe 데코레이터가 만들며, 많은 스팬/컴포넌트로 구성돼요
  • 평가하려는 컴포넌트에는 @observe()에 metrics 목록을 포함하고, 해당 컴포넌트 안에서 update_current_span 함수를 호출해 평가용 테스트 케이스를 만들어요

update_current_span()을 호출해 inputs, outputs, retrieval_contexts 등을 설정하면, deepeval이 이를 자동으로 매핑해 LLMTestCases를 만들어요.

데이터셋 가져오기 및 골든스 순회

데이터셋을 가져오고 .evals_iterator()로 반복해요. 이 반복자는 LLM 앱이 몇 번 실행되는지 — 데이터셋의 골든스마다 한 번씩 — 제어해요.

from deepeval.dataset import EvaluationDataset

dataset = EvaluationDataset()
dataset.pull(alias="YOUR-DATASET-ALIAS")

for _ in dataset.evals_iterator():
    llm_app("any input") # golden.input is optional for component-level testing

테스트 케이스 필드는 컴포넌트 안의 update_current_span()으로 채워지므로, LLM 앱에 어떤 인풋이든 전달할 수 있어요 — 테스트 시나리오가 특정 인풋을 요구한다면 golden.input을 써도 되고요.

완료 ✅. 새로 만들어진 공유 가능한 테스팅 리포트 링크가 보일 거예요.

Video

컴포넌트 레벨 테스팅 리포트

for 루프를 비동기로 실행할 수도 있어요:

import asyncio
from deepeval.dataset import EvaluationDataset

dataset = EvaluationDataset()
dataset.pull(alias="YOUR-DATASET-ALIAS")

for golden in dataset.evals_iterator():
    task = asyncio.create_task(a_llm_app(golden.input))
    dataset.evaluate(task)

데이터셋의 evals_iterator() 안에서 LLM 앱을 호출하면, deepeval이 LLM 앱의 호출을 자동으로 캡처하고 @observe된 컴포넌트의 계층 구조에 기반해 테스트 케이스를 동적으로 만들어요. 컴포넌트 레벨 평가에 대한 추가 정보는:

  • @observe됐지만 metrics가 붙지 않은 컴포넌트는 Confident AI가 테스트하지 않고 일반 스팬으로 표시해요
  • 컴포넌트 레벨 테스트에서는 일반적으로 참조 기반(reference-based) 메트릭을 쓰지 않아요. 골든스가 테스트 케이스에 1대1로 매핑되도록 설계되어 expected_output 같은 인자가 중복되기 때문이에요.

더 알아보기