본문으로 건너뛰기

Potato에서 Langfuse 트레이스 검토하기

프로젝트를 폴링하거나 내보내기 파일을 변환해 Langfuse 트레이스를 Potato로 가져오고, 트레이스마다 여러 검토자를 배정한 뒤, 답을 점수로 되돌려 보냅니다.

Potato는 두 가지 방법으로 Langfuse 트레이스를 읽습니다. 하나는 Langfuse 프로젝트에서 끝난 트레이스를 폴링하는 방법이고, 다른 하나는 python -m potato.trace_converter --input-format langfuse로 트레이스 파일을 변환하는 방법입니다. 폴링한 트레이스는 검토자들의 병합된 답이 Langfuse에서 트레이스와 판정된 각 observation의 점수로 되돌아오고, 변환한 트레이스는 결과가 Potato의 내보내기에 남습니다.

Langfuse는 LLM 및 에이전트 애플리케이션을 디버깅하고 평가하기 위한 오픈 소스 자체 호스팅 AI 엔지니어링 플랫폼이며, Langfuse 저장소는 ee 폴더 바깥에서 MIT 라이선스입니다. Langfuse 데이터 모델에서 트레이스는 단일 요청이나 작업을 나타냅니다. observation은 트레이스 안의 한 단계이며 LLM 호출, 도구 호출, 검색 단계 등이 해당하고, observation은 중첩되어 애플리케이션의 구조를 따릅니다. 세션은 한 사용자 상호작용의 트레이스를 묶고, 점수는 트레이스, observation, 세션에 붙는 값입니다.

Langfuse 자체의 어노테이션 큐에서는 검토자가 점수 설정에 따라 트레이스, observation, 세션을 채점합니다. Potato는 트레이스마다 블라인드 검토자를 여럿 두고, 그 검토자들 사이의 일치도를 계산하며, 불일치를 판정한 뒤, 결과를 Langfuse의 같은 트레이스에 되돌려 보냅니다. LLM 및 에이전트 트레이스를 위한 사람 검토 큐는 Langfuse, LangSmith, Braintrust, Arize, Potato의 검토 기능을 비교합니다.

폴링과 내보내기 파일의 비교

표는 두 경로를 비교합니다.

폴링(trace_ingestion)내보내기 파일(trace_converter)
트레이스조회 기간 안에 시작된 프로젝트의 모든 트레이스, 그 뒤로는 새 트레이스가 끝날 때마다 한 번씩파일에 있는 트레이스
Langfuse APIObservations API v2, 대체 수단으로 레거시 traces API없음. 파일은 직접 만듭니다
디스플레이가 읽는 항목 필드steps, observation마다 하나conversation, 생각, 행동, 결과마다 한 턴
유지되는 observation 유형모든 유형GENERATION, SPAN, EVENT
세션 idmetadata.session_id 아래에 유지됨유지되지 않음
Langfuse로 보내는 점수예아니요

각 트레이스 옆 Langfuse에서 검토된 점수를 보고 싶은 팀에는 폴링이 맞습니다. 결과를 Potato에 두어도 되는 고정 표본에는 내보내기 파일이 맞습니다. 논문용이나 별도 보관한 평가 세트용으로 고른 트레이스가 고정 표본의 전형적인 예입니다.

Langfuse 프로젝트 폴링하기

아래 설정은 Langfuse Cloud를 1분에 한 번 폴링하고 트레이스마다 검토자 두 명을 배정합니다. 첫 폴링은 지난 7일 안에 시작된 모든 트레이스를 가져옵니다. 두 검토자가 모두 답하면 Potato는 병합된 답을 각각 human.resolved, human.helpfulness, human.step_check라는 이름의 점수로 트레이스에 되돌려 보냅니다. 이 설정은 완전하며 python -m potato.validate_cli --strict를 통과합니다.

yaml
annotation_task_name: Langfuse trace review
task_dir: .
output_annotation_dir: annotation_output/
data_files:
  - data/traces.jsonl
item_properties:
  id_key: id
  text_key: task_description
user_config:
  allow_all_users: true
  users: []
 
# Pull every trace that started in the last 7 days, then poll once a minute.
# Keys for write-back come from LANGFUSE_PUBLIC_KEY and LANGFUSE_SECRET_KEY.
trace_ingestion:
  enabled: true
  api_key: change-me
  sources:
    - type: langfuse
      api_url: https://cloud.langfuse.com
      public_key: pk-lf-...
      secret_key: sk-lf-...
      poll_interval: 60
      lookback_hours: 168
  writeback:
    trigger: fully_annotated
    min_annotators: 2
    score_prefix: human
    langfuse:
      score_configs: true
 
num_annotators_per_item: 2
 
instance_display:
  fields:
    - key: steps
      type: agent_trace
      label: Langfuse trace
      display_options:
        collapse_observations: true
 
annotation_schemes:
  - annotation_type: radio
    name: resolved
    description: Did the agent answer the user's question correctly?
    labels: [correct, partly_correct, wrong]
  - annotation_type: likert
    name: helpfulness
    description: How helpful was the final answer?
    size: 5
    min_label: Not helpful
    max_label: Very helpful
  - annotation_type: process_reward
    name: step_check
    description: Mark each step correct or incorrect.
    steps_key: steps
    mode: per_step

potato start config.yaml -p 8000으로 서버를 시작하세요. Potato는 시작할 때 데이터 파일이 필요하며, data/traces.jsonl에 이미 있는 트레이스는 폴링한 트레이스와 함께 큐에 들어갑니다. 파일의 트레이스는 steps 목록이 있을 때만 위의 디스플레이에 나타납니다.

type: langfuse인 trace_ingestion.sources 아래의 각 항목은 폴러 하나를 시작합니다. 폴러는 다음 키를 읽습니다.

키기본값의미
api_urlhttps://cloud.langfuse.comLangfuse 호스트. 자체 호스팅 서버는 자기 URL을 씁니다
public_key, secret_key없음프로젝트의 API 키
poll_interval30폴링 사이의 초
lookback_hours24첫 폴링이 거슬러 올라가는 범위
settle_seconds30트레이스가 끝난 뒤 그 트레이스를 가져가기 전에 기다리는 초
max_trace_seconds3600끝나지 않은 트레이스를 그래도 가져가는 시점까지의 초

폴러가 가져가는 트레이스

폴러는 시간 구간에 대해 GET /api/public/v2/observations를 읽고 행을 트레이스 id별로 묶습니다. 그 엔드포인트가 없는 서버에서는 레거시 GET /api/public/traces API를 대신 씁니다. Langfuse 마이그레이션 가이드는 Langfuse Cloud가 레거시 읽기 엔드포인트를 2026년 11월 16일까지 제공한다고 밝히며, 폴러는 전환에 변경이 필요하지 않습니다. 루트 observation에 종료 시각이 있고 settle_seconds가 지나면 트레이스가 끝난 것으로 봅니다. 루트가 끝나지 않는 트레이스는 max_trace_seconds 뒤에 metadata.complete를 false로 하여 가져갑니다. Potato는 이미 항목 저장소에 있는 트레이스를 건너뛰므로, 재시작해도 트레이스가 두 번 추가되지 않습니다.

각 트레이스는 id가 langfuse_<trace id>인 항목 하나가 됩니다. 항목의 steps 목록은 자식 observation을 시작 순서로 담고, 각 단계는 observation의 입력, 출력, 유형, 이름, 모델, observation id를 유지합니다. 트레이스 이름은 task_description이 되고, 이름이 없는 트레이스는 트레이스 입력을 씁니다. 세션 id, 사용자 id, 태그, 입력, 출력은 metadata 아래에 들어갑니다. 폴러는 구간 안의 모든 트레이스를 가져오며 이름, 태그, 환경으로 거르지 않습니다. 자동화 규칙과 분류 큐가 폴링한 트레이스 중 어느 것을 검토자가 먼저 볼지 정합니다.

Langfuse 내보내기 변환하기

langfuse 변환기는 JSON이나 JSONL의 트레이스 객체 하나 또는 트레이스 객체 목록을 읽습니다. 각 트레이스에는 전체 observation 객체로 이루어진 observations 목록이 필요합니다. 레거시 GET /api/public/traces/{traceId} 엔드포인트는 그 형태의 트레이스를 반환하고, 목록 엔드포인트 GET /api/public/traces는 객체가 아니라 observation id를 반환합니다. v2 Observations API의 행은 변환 전에 traceId로 묶어 트레이스당 객체 하나로 만들고, observation을 observations 아래에 넣어야 합니다. 파일을 변환하세요.

bash
python -m potato.trace_converter \
  --input langfuse_traces.json \
  --input-format langfuse \
  --output data/traces.jsonl

이 명령은 트레이스마다 JSON 한 줄을 쓰며, --auto-detect는 첫 항목에 type이 있는 observations 목록으로 형식을 인식합니다. 변환기는 각 필드를 다음과 같이 매핑합니다.

Langfuse 필드Potato 필드
트레이스 idid
트레이스 nameagent_name
트레이스 input.query, input.input, input.question 또는 문자열 inputtask_description
GENERATION observation 출력(content, text 또는 completion)conversation의 Agent (Thought) 턴
SPAN observation 이름과 입력name(key=value, ...) 형태로 쓴 Agent (Action) 턴
SPAN observation 출력(result, output 또는 객체 전체)Environment 턴
EVENT observation 출력, 출력이 없으면 입력System (<name>) 턴
observation 수, 트레이스 이름, 모델metadata_table의 행

변환기는 observation을 파일 순서대로 유지하고 그 밖의 모든 observation 유형은 버립니다. Langfuse의 observation 유형에는 agent, tool, chain, retriever, evaluator, embedding, guardrail도 있으며, 그 유형으로 계측된 트레이스는 변환에서 그 단계를 잃습니다. 트레이스 출력, 메타데이터, 세션 id, observation 수준도 옮겨지지 않습니다. 트레이스가 그 유형이나 필드에 의존한다면 프로젝트를 대신 폴링하세요.

변환한 파일에서는 디스플레이와 단계 수준 스킴이 conversation을 가리키게 하세요. 폴링 설정에서 instance_display 블록과 annotation_schemes 아래의 step_check 항목을 다음으로 바꾸세요.

yaml
instance_display:
  fields:
    - key: task_description
      type: text
      label: User request
    - key: conversation
      type: agent_trace
      label: Langfuse trace
 
annotation_schemes:
  - annotation_type: process_reward
    name: step_check
    description: Click the first step that went wrong.
    steps_key: conversation
    mode: first_error

Langfuse 트레이스를 위한 디스플레이와 스킴

agent_trace 디스플레이는 각 단계를 유형 배지가 달린 카드로 보여 주며, 폴러의 steps 목록과 변환기의 conversation 목록을 모두 읽습니다. 3분할 eval_trace 디스플레이는 같은 단계를 추론, 함수 호출, 최종 답변으로 나눕니다. 디스플레이의 key는 트레이스가 들어온 경로에 맞게 steps나 conversation으로 설정하세요.

쓰기 되돌림은 radio, likert, slider, number, select, multiselect 스킴의 트레이스 수준 답을 보냅니다. 단계 수준 질문은 process_reward, trajectory_eval, tool_call_review, failure_attribution, gui_trajectory를 씁니다. 폴링한 트레이스에서는 min_annotators명의 검토자가 준 각 단계 레이블이 그 단계 자신의 observation에 대한 점수가 됩니다. 텍스트 상자는 Potato의 내보내기에 근거를 기록하며 전송되지는 않습니다. Langfuse 세션 전체에 대한 질문에는 enabled: true인 sessions 블록이 폴링한 트레이스를 metadata.session_id로 묶고, session_level: true인 스킴은 /sessions 페이지에서 세션마다 한 번 답합니다(세션 수준 점수화).

MAST 실패 분류 체계와 τ-bench 에이전트 평가 디자인은 에이전트 실패에 대한 바로 쓸 수 있는 트레이스 수준 스킴을 제공합니다.

Langfuse의 점수

쓰기 되돌림은 min_annotators명의 검토자가 답하면 스킴마다 점수 하나를 보냅니다. trigger: adjudicated에서는 Potato가 판정자의 결정을 기다리며, 어느 트리거에서든 판정된 값이 투표를 대체합니다. 각 점수는 <score_prefix>.<scheme name>이라는 이름을 갖고 아래 값을 담습니다.

스킴점수 값Langfuse 데이터 유형
labels가 있는 radio, select, likert검토자 대부분이 고른 레이블Categorical
multiselect검토자의 절반을 넘는 수가 고른 레이블, 쉼표로 구분Categorical
labels가 없는 likert중앙값 점수Numeric
slider, number평균Numeric
단계 수준 스킴검토자 대부분이 그 단계에 준 레이블, 단계의 observation으로 전송Categorical

투표가 동률이면 판정자가 결정하거나 다른 검토자가 동률을 깰 때까지 아무것도 보내지 않습니다. 코멘트는 값이 어떻게 정해졌는지 밝히며, 예를 들어 Potato: 2 of 2 annotator(s) chose correct입니다. 메타데이터에는 검토자 수, 답변 수, 일치한 검토자 쌍의 비율이 들어 있습니다. 사용자 이름은 전송하지 않습니다. langfuse.score_configs: true이면 Potato는 각 점수를 처음 보낼 때 그 점수 이름을 딴 Langfuse 점수 설정을 만듭니다. 그러면 Langfuse가 필터에 resolved 범주와 1~5 유용성 범위를 나열합니다.

쓰기 되돌림은 두 변수가 모두 설정되어 있으면 LANGFUSE_PUBLIC_KEY와 LANGFUSE_SECRET_KEY에서, 아니면 langfuse 소스에서 Langfuse 키를 읽습니다. Potato는 트레이스와 스킴마다 점수에 고정된 id를 부여합니다. Langfuse는 id, 이름, 날짜가 모두 일치할 때만 점수를 대체합니다(중복 점수 방지). 따라서 나중 날짜에 답이 바뀌면 그 날짜의 두 번째 점수가 추가됩니다. 관리자 API 키로 전송을 확인하세요.

bash
curl -H "X-API-Key: $KEY" http://localhost:8000/admin/api/trace_writeback

응답은 대기, 전송, 실패, 보류된 점수의 수를 세고 보류된 각 트레이스가 기다리는 이유를 알려 줍니다.

Potato의 내보내기에 담기는 결과

폴링했든 변환했든 모든 트레이스는 주석을 annotation_output/<user>/user_state.json에 보관합니다. agent_eval 내보내기는 답을 트레이스별로 집계하며, 단일 선택 스킴마다 레이블 분포와 다수 레이블을, 숫자 스킴마다 평균을 담습니다.

bash
python -m potato.export --config config.yaml --format agent_eval --output results/

내보내기 형식에 jsonl, parquet, huggingface 등의 형식이 나와 있습니다. 세션 수준 답은 annotation_output/session_annotations.jsonl로 갑니다. /admin/iaa 대시보드는 스킴마다 검토자 사이의 일치도를 보고하며, 주석자 간 일치도 설명이 계수 선택을 다룹니다. 데이터셋 페이지는 Import ingested traces로 폴링한 트레이스에서 버전이 있는 평가 세트를 만듭니다. 수정된 궤적은 궤적 편집을 통해 SFT와 DPO 레코드가 됩니다.

한계

  • 쓰기 되돌림은 폴링한 트레이스에만 해당합니다. 데이터 파일에서 온 트레이스는 변환했든 아니든 결과가 Potato에 남습니다.
  • 텍스트 답은 Potato에 남습니다. 쓰기 되돌림은 자유 서술 답을 보내지 않으며, 세션 수준 답도 보내지 않습니다.
  • 폴러는 프로젝트 전체를 가져옵니다. 표집과 우선순위는 트레이스가 도착한 뒤 자동화 규칙과 분류 큐가 정합니다.

자주 묻는 질문

Langfuse란 무엇인가요?

Langfuse는 LLM 및 에이전트 애플리케이션을 트레이싱하고, 그 트레이스를 평가하며, 프롬프트를 관리하는 오픈 소스 AI 엔지니어링 플랫폼입니다. 저장소는 ee 폴더 바깥에서 MIT 라이선스이며, Langfuse는 Langfuse Cloud나 직접 운영하는 서버에서 실행됩니다. Potato는 Langfuse에 연결해 트레이스마다 여러 사람 검토자를 배정하고, 그들의 병합된 답을 점수로 Langfuse에 되돌려 보냅니다.

Langfuse는 에이전트 관찰 가능성(observability)을 위해 무엇을 기록하나요?

Langfuse는 에이전트 실행 하나를 트레이스로, 그 실행 안의 각 단계를 트레이스 아래에 중첩된 observation으로 기록합니다. 단계는 실행 안의 LLM 호출, 도구 호출, 검색 단계 또는 다른 작업입니다. observation은 generation, agent, tool, retriever, guardrail을 포함한 열 가지 유형 중 하나이고, 세션은 한 사용자 상호작용의 트레이스를 묶습니다. 트레이스에 span, event, generation 외의 유형인 observation이 있으면 Langfuse는 observation의 타이밍과 중첩에서 에이전트 그래프를 그립니다. Potato의 폴러는 그 observation을 읽어 각각을 검토자가 레이블을 달 수 있는 단계로 바꿉니다.

더 읽을거리