코드 평가자(Code evaluators)

코드 평가자(Code evaluators)

코드 평가자는 Langfuse에서 커스텀 Python 또는 TypeScript 로직을 실행해 하나 이상의 점수(scores)를 반환합니다. 이 문서는 코드 평가자의 사용 대상 선택, 설정 방법, 함수 계약, 런타임 제약을 설명해요. 코드가 모델 기반 판단보다 더 신뢰할 수 있는 결정적이고 객관적인 검사에 사용합니다.

출처: 문서

본문

이 기능은 어디에서 쓸 수 있나요?

플랜 사용 가능 여부
Hobby 사용 가능
Core 사용 가능
Pro 사용 가능
Enterprise 사용 가능
Self Hosted 사용 가능

셀프 호스팅: 코드 평가자는 구성된 code evaluator dispatcher 가 필요합니다. dispatcher가 없으면 비활성화됩니다.

코드 평가자는 OpenTelemetry 기반 SDK로 수집된 observations를 요구합니다: Python SDK v3+ 또는 JS/TS SDK v4+. 필요하다면 Python v2 → v3 마이그레이션 가이드 또는 JS/TS v3 → v4 마이그레이션 가이드 를 참고하세요.

코드 평가자는 Langfuse에서 커스텀 Python 또는 TypeScript 로직을 실행하고 하나 이상의 scores 를 반환합니다. 코드가 모델 기반 판단보다 더 신뢰할 수 있는 결정적이고 객관적인 검사에 사용하세요.

일반적인 예로는 정확 일치(exact match) 검사, 정규식 검증, JSON 파싱 가능 여부, 스키마 검증, 키워드 검사, 툴 호출 검사, 커스텀 비즈니스 규칙이 있습니다.

평가에 의미론적 판단, 루브릭 기반 추론, 유용성·어조·답변 품질 같은 주관적 평가가 필요할 때는 LLM-as-a-Judge 를 대신 사용하세요. 평가자와 룰로 실시간 프로덕션 trace에 점수를 매기려면 Evaluate Production Traffic 으로 시작하세요.

코드 평가자는 어떻게 사용하나요?

코드 평가자는 두 유형의 데이터에서 실행할 수 있습니다: Observations(실시간 프로덕션 트래픽의 개별 작업) 또는 Experiments(통제된 테스트 데이터셋). 선택은 개발에서 테스트하는지 프로덕션을 모니터링하는지에 따라 달라집니다.

결정 트리(Decision tree)

어떤 데이터에 결정적 평가가 필요하나요?

  • 실시간 프로덕션 데이터 (실시간 트래픽 모니터링) → Observations (개별 작업: LLM 호출, 검색, 툴 호출)
  • 오프라인 실험 데이터 (통제된 환경 테스트) → Experiments (데이터셋이 있는 통제된 테스트 케이스)

프로덕션 패턴: 팀은 보통 개발 중에는 Experiments로 결정적 검사를 검증한 뒤, 확장 가능한 모니터링을 위해 프로덕션에는 Observation-level 평가자를 배포합니다.

각 평가 대상 이해

trace 내 개별 observations(LLM 호출, 검색 작업, 임베딩 생성, 툴 호출 같은)에 평가자를 실행하세요. Is Root Observation 필터를 사용해 논리적 루트를 대상으로 하세요: 물리적 부모가 없는 observation이나 SDK가 애플리케이션 루트로 명시적으로 표시한 observation. 논리적 루트는 여전히 물리적 부모가 있을 수 있습니다. 특정 작업이 필요하면 이름이나 유형 필터를 사용하세요.

Observations를 대상으로 하는 이유

  • 작업 수준 정밀도: observation 유형으로 필터링해 전체 trace가 아니라 중요한 작업만 평가
  • 결정적 프로덕션 모니터링: 실시간 트래픽에서 JSON 유효성, 스키마 준수, 정확 일치, 비즈니스 규칙 검사
  • 합성 평가: 하나의 trace 안의 다른 작업에 다른 코드 평가자 실행
  • 결합 필터링: observation 필터를 userId, sessionId, tags, version, metadata 같은 trace 필터와 결합

데이터 흐름: 들어오는 observation이 룰의 필터와 일치하면 룰이 연결된 코드 평가자를 트리거합니다. 점수는 특정 observation에 연결됩니다.

예시 사용 사례

  • 최종 LLM 응답이 파싱 가능한 JSON인지 검증
  • 툴 호출이 필수 인자를 포함하는지 확인
  • 선택된 모델 호출에 커스텀 비즈니스 규칙 적용

통제된 테스트 데이터셋에 평가자를 실행해 재현 가능한 환경에서 모델 버전, 프롬프트 변형, 시스템 구성을 비교하세요.

Experiments를 대상으로 하는 이유

  • 개발 워크플로우를 위한 결정적 pass/fail 검사가 필요할 때
  • 여러 프롬프트 버전이나 모델 구성을 비교하고 싶을 때
  • 평가자가 검사해야 하는 expected output이나 metadata가 있는 데이터셋이 있을 때

데이터 흐름: 각 실험 실행은 선택한 평가자가 점수를 매길 수 있는 traces와 observations를 생성합니다. 평가자는 observation 데이터와 expected output, 아이템 metadata 같은 실험 아이템 컨텍스트를 받습니다.

  • 테스트 입력과 (선택적으로) expected output으로 데이터셋을 만듭니다.
  • UI 또는 SDK로 실험을 실행합니다. Experiments via UI 또는 Experiments via SDK 참고.
  • 생성된 observations에 점수를 매길 코드 평가자를 선택합니다.
  • 실험 실행 간 결과를 비교해 데이터 기반 결정을 내립니다.

예시 사용 사례

  • 지원 질문 데이터셋에서 두 프롬프트 버전을 비교하고 각 응답이 필요한 JSON 필드를 포함하는지 확인

단계별 설정

코드 평가자 만들기

Evaluators 페이지로 가서 New evaluator를 클릭하세요. 템플릿 갤러리에서 Code evaluator를 고른 뒤 Python 또는 TypeScript를 선택하세요.

평가자 정의 및 테스트

평가자 정의는 언어와 Langfuse가 실행하는 evaluate 함수를 포함합니다. 은 이 평가자가 실행될 들어오는 observations를 선택합니다.

evaluate 함수를 구현하세요. 코드를 결정적으로 유지하고 런타임 제약 안에 두세요.

오른쪽에서 대표 샘플 observations로 필터링하고 하나를 선택해 평가자를 실행하세요. 테스트 결과로 ctx에 전달된 observation과 experiment 필드가 내 코드가 기대하는 것과 일치하는지 확인하세요.

LLM-as-a-Judge와 달리 코드 평가자에는 별도의 변수 매핑 단계가 없습니다. 내 코드가 필요한 데이터를 ctx에서 직접 읽습니다.

평가자 저장

저장한 뒤 다음을 할 수 있습니다:

  • 테스트 샘플을 선택하는 데 사용한 필터로 을 만들거나, 기존 룰에 평가자를 연결해 들어오는 observation에 실행
  • 룰 없이 계속. 배치 평가프롬프트 실험 에서 평가자 사용 가능

함수 계약(Function contract)

각 평가자는 evaluate 함수를 노출합니다. Langfuse는 EvaluationContext를 전달하고 하나 이상의 점수를 담은 EvaluationResult를 기대합니다.

Python:

from dataclasses import dataclass, field
from typing import Any

@dataclass
class ToolCall:
    id: str = ""
    name: str = ""
    arguments: Any = None
    type: str = ""
    index: int = 0

@dataclass
class ObservationContext:
    input: Any = None
    output: Any = None
    metadata: Any = None
    tool_calls: list[ToolCall] = field(default_factory=list)

@dataclass
class ExperimentContext:
    item_expected_output: Any = None
    item_metadata: Any = None

@dataclass
class EvaluationContext:
    observation: ObservationContext
    experiment: ExperimentContext | None = None

@dataclass
class Score:
    name: str
    value: int | float | str | bool
    data_type: str
    comment: str | None = None
    config_id: str | None = None
    metadata: dict[str, Any] | None = None

@dataclass
class EvaluationResult:
    scores: list[Score]

def evaluate(ctx: EvaluationContext) -> EvaluationResult:
    output_present = ctx.observation.output is not None

    return EvaluationResult(
        scores=[
            Score(
                name="Output present",
                value=output_present,
                data_type="BOOLEAN",
                comment=(
                    "Observation output is present."
                    if output_present
                    else "Observation output is missing."
                ),
                metadata={"rule": "output_present"},
            )
        ]
    )

TypeScript:

type ToolCall = {
  id: string;
  name: string;
  arguments: unknown;
  type: string;
  index: number;
};

type EvaluationContext = {
  observation: {
    input: any;
    output: any;
    metadata: any;
    toolCalls: ToolCall[];
  };
  experiment:
    | {
        itemExpectedOutput: any;
        itemMetadata: any;
      }
    | undefined;
};

type ScoreBase = {
  name: string;
  comment?: string;
  configId?: string | null;
  metadata?: Record<string, unknown>;
};

type NumericScore = ScoreBase & {
  dataType: "NUMERIC";
  value: number;
};

type BooleanScore = ScoreBase & {
  dataType: "BOOLEAN";
  value: boolean;
};

type CategoricalScore = ScoreBase & {
  dataType: "CATEGORICAL";
  value: string;
};

type TextScore = ScoreBase & {
  dataType: "TEXT";
  value: string;
};

type Score = NumericScore | BooleanScore | CategoricalScore | TextScore;

type EvaluationResult = {
  scores: Score[];
};

function evaluate({
  observation: { input, output, metadata, toolCalls },
  experiment,
}: EvaluationContext): EvaluationResult {
  const itemExpectedOutput = experiment?.itemExpectedOutput;
  const itemMetadata = experiment?.itemMetadata;
  const outputPresent = output != null;

  return {
    scores: [
      {
        name: "Output present",
        value: outputPresent,
        dataType: "BOOLEAN",
        comment: outputPresent
          ? "Observation output is present."
          : "Observation output is missing.",
        metadata: {
          rule: "output_present",
          hasInput: input != null,
          hasObservationMetadata: metadata != null,
          toolCallCount: toolCalls.length,
          hasExpectedOutput: itemExpectedOutput != null,
          hasExperimentMetadata: itemMetadata != null,
        },
      },
    ],
  };
}

컨텍스트 필드(Context fields)

필드 설명
ctx.observation.input 평가자 대상이 선택한 observation에 기록된 입력
ctx.observation.output 평가자 대상이 선택한 observation에 기록된 출력
ctx.observation.metadata observation에 기록된 메타데이터
ctx.observation.tool_calls(Python) / ctx.observation.toolCalls(TypeScript) id, name, arguments, type, index를 가진 정렬된 호출. 유효한 JSON 인자는 파싱됨
ctx.experiment 평가자가 실험에서 실행될 때만 존재
ctx.experiment.item_expected_output(Python) / ctx.experiment.itemExpectedOutput(TypeScript) 실험 아이템의 expected output
ctx.experiment.item_metadata(Python) / ctx.experiment.itemMetadata(TypeScript) 실험 아이템의 메타데이터

점수 필드(Score fields)

필드 설명
name 필수 점수 이름
value 필수 점수 값
data_type / dataType 필수 점수 데이터 타입. 지원 값은 NUMERIC, CATEGORICAL, BOOLEAN, TEXT
comment 점수와 함께 저장되는 선택적 근거 또는 설명
config_id / configId 선택적 점수 구성 ID. 제공되면 점수는 참조된 score config 를 만족해야 함
metadata 점수와 함께 저장되는 선택적 메타데이터

예시: Exact match

이 예시는 observation 출력이 실험 아이템의 expected output과 정확히 일치하면 통과하는 불리언 점수를 반환합니다.

Python:

def evaluate(ctx: EvaluationContext) -> EvaluationResult:
    """Evaluates one observation and returns one or more Langfuse scores."""
    expected_output = (
        ctx.experiment.item_expected_output if ctx.experiment is not None else None
    )
    matches_expected_output = (
        expected_output is not None and ctx.observation.output == expected_output
    )

    return EvaluationResult(
        scores=[
            Score(
                name="Exact match",
                value=matches_expected_output,
                data_type="BOOLEAN",
                comment=(
                    "Output exactly matches the expected output."
                    if matches_expected_output
                    else "Output does not match the expected output."
                ),
            )
        ]
    )

TypeScript:

/**
 * Evaluates one observation and returns one or more Langfuse scores.
 */
function evaluate({
  observation: { input, output, metadata },
  experiment,
}: EvaluationContext): EvaluationResult {
  const itemExpectedOutput = experiment?.itemExpectedOutput;
  const itemMetadata = experiment?.itemMetadata;
  const matchesExpectedOutput =
    itemExpectedOutput != null && output === itemExpectedOutput;

  return {
    scores: [
      {
        name: "Exact match",
        value: matchesExpectedOutput,
        dataType: "BOOLEAN",
        comment: matchesExpectedOutput
          ? "Output exactly matches the expected output."
          : "Output does not match the expected output.",
        metadata: {
          hasInput: input != null,
          hasObservationMetadata: metadata != null,
          hasExperimentMetadata: itemMetadata != null,
        },
      },
    ],
  };
}

코드 평가자 실행 디버깅

모든 코드 평가자 실행은 trace를 만들어 평가 과정에 대한 완전한 가시성을 제공합니다. 선택한 입력/출력, 실험 컨텍스트, 런타임 지연시간, 반환된 점수, 로그, 오류를 검사할 수 있습니다.

tracing 테이블에서 환경 langfuse-code-eval로 필터링해 코드 평가자 실행 trace를 표시할 수 있습니다.

코드 평가자 실행 상태:

  • Completed: 평가가 성공적으로 완료되고 유효한 점수를 반환함
  • Error: 평가 실패 (입력, 출력, 지연시간, 로그, 오류 세부 사항은 실행 trace ID 클릭)
  • Pending: 평가가 큐에 대기 중

새 평가자를 활성화하기 전에 평가자 테스트 실행을 사용하세요. 선택한 observation 데이터, 실험 컨텍스트, 점수 이름, 점수 값, 점수 데이터 타입을 검증하는 가장 빠른 방법입니다.

런타임 제약(Runtime constraints)

코드 평가자는 많은 observation에 대해 빠르고 안전하게 실행될 수 있는 컴팩트하고 결정적인 검사를 위한 것입니다.

특정 서드파티 라이브러리나 네트워크 접근이 필요하다면 GitHub Discussions 에 사용 사례를 공유해 주세요. 더 넓은 런타임 지원이 유용한 곳을 이해하는 데 도움이 됩니다.

제약 한도/지침
언어 Python 또는 TypeScript로 평가자 작성. 셀프 호스팅에서 Python은 aws-lambda dispatcher 필요. insecure-local은 TypeScript/JavaScript만 지원
TypeScript 문법 erasable TypeScript 문법 사용. 타입 주석과 인터페이스는 OK. enum, namespace, decorator, parameter properties는 피할 것
의존성 언어 표준 라이브러리(Python & TS/JS)만 사용. 서드파티 패키지는 평가자 런타임에서 사용 불가
네트워크 접근 평가자는 네트워크 egress 없이 실행. 필요한 모든 데이터를 observation 또는 experiment 컨텍스트에 보관
런타임 제한 평가자는 2초 안에 완료되어야 함
결과 형태 evaluate에서 최소 하나의 점수 반환
소스 크기 평가자 소스 코드를 256 KB 미만으로 유지
입력 크기 소스 코드와 선택된 변수를 포함한 dispatch 페이로드를 5.5 MB 미만으로 유지
결과 크기 평가자 결과를 256 KB 미만으로 유지

FAQ

타임아웃 오류는 어떻게 디버깅하나요?

타임아웃은 보통 평가자가 2초 런타임 제한에 비해 너무 많은 작업을 하거나 네트워크에 접근하려 할 때 발생합니다. 네트워크 요청은 런타임이 차단하며 타임아웃 오류로 표면화될 수 있습니다. 이를 디버깅하려면 작은 샘플 observation에서 평가자를 실행하고, 네트워크 호출을 제거하고, 큰 루프나 값비싼 파싱을 피하며, 평가자에 선택된 input/output/metadata/experiment 컨텍스트의 양을 줄이세요.

서드파티 패키지를 사용할 수 있나요?

아니요. 코드 평가자는 현재 표준 라이브러리만 지원합니다. 평가에 서드파티 패키지가 필요하면 그 로직을 내 인프라에서 실행하고 Scores via API/SDK 로 결과를 수집하세요.

실험 컨텍스트가 때때로 존재하지 않는 이유는 뭔가요?

ctx.experiment는 평가자가 실험에서 실행될 때만 존재합니다. 실시간 observation 평가자의 경우 Python에서는 None, TypeScript에서는 undefinedctx.experiment를 처리하도록 코드를 작성하세요.

코드 평가자를 API나 SDK로 만들 수 있나요?

네. Langfuse UI 외에도 안정적인 공개 평가자 엔드포인트가 type: "code"를 받아 코드 평가자를 만들고 평가 룰에서 참조할 수 있습니다. Evaluators API reference 를 참고하세요. 내 애플리케이션이나 CI 파이프라인에서 결정적 평가 로직을 실행하려면 Scores via API/SDK 를 사용해 결과 점수를 Langfuse에 수집하세요.

코드 평가자 실행 trace를 찾을 수 없나요?

코드 평가자 실행은 내부 환경 langfuse-code-eval을 사용합니다. 내부 환경은 기본 tracing 뷰에서 숨겨지므로, tracing 테이블을 environment = langfuse-code-eval로 필터링하거나 관련 점수에서 실행 trace를 여세요.

셀프 호스팅 Langfuse에서 코드 평가자는 어떻게 구성하나요?

셀프 호스팅 배포의 경우 Code evaluators 에서 코드 평가자 dispatcher와 실행 워커를 구성하세요. 유일한 SDK 요구사항은 OpenTelemetry 기반 수집입니다:

GitHub Discussions

런타임 제약 중 하나에 문제가 있거나 제약이 중요한 평가 사용 사례를 막는다면 GitHub Discussions에 세부 내용을 기여해 주세요.

더 알아보기 (Learn more)