Ken
Langfuse의 프롬프트 관리, 데이터셋, 실험, 코드 평가기, LLM-as-a-Judge, Annotation Queue를 직접 실행하며 확인한 모델 평가 루프와 운영 시 주의점
Langfuse 모델 평가 기능 검증
LLM 애플리케이션은 트레이스를 남기는 것만으로 좋아지지 않습니다. 제품팀이 정말 답해야 하는 질문은 “이번 프롬프트나 모델 변경이 품질을 높였는가?”, “그 판단을 누가 어떤 기준으로 내렸는가?”, “사용자 반응·자동 평가·사람의 검토가 충돌할 때 어디를 봐야 하는가?”입니다.
이 글에서는 셀프호스팅 Langfuse v3.197.1, Python SDK 3.7.0, ClickHouse 25.11.2.24 환경에서 모델 평가 흐름을 직접 실행한 결과를 정리합니다. 외부 LLM API 키 없이도 재현되는 코드 평가기를 기준선으로 삼고, 프롬프트 관리부터 데이터셋 실험, LLM-as-a-Judge, Annotation Queue, ClickHouse 분석까지 이어지는 전체 루프를 검증했습니다.
이 글의 실행 결과는 2026-07-26 실습 환경의 관찰값입니다. SDK 및 서버 버전에 따라 API 형태와 저장소의 내부 표현은 달라질 수 있으므로, 자동화하기 전에는 현재 실행 중인 버전에서 다시 검증하셔야 합니다.
평가의 핵심은 점수가 아니라 피드백 루프입니다
좋은 평가 체계는 모델 하나에 숫자 하나를 붙이는 과정이 아닙니다. 동일한 기준 데이터로 프롬프트와 모델을 비교하고, 결정적 규칙과 모델 기반 판단을 함께 사용하며, 사람이 검토한 결과로 자동 평가를 교정할 수 있어야 합니다.
Langfuse의 장점은 이 루프의 각 결과를 분리된 보고서가 아니라 연결 가능한 관측성 데이터로 남긴다는 데 있습니다. 특히 프롬프트를 Generation에 연결하면 프롬프트 버전별 지연 시간, 토큰, 비용, 점수를 비교할 수 있습니다. 프롬프트와 트레이스 연결하기
1. 프롬프트를 코드 문자열이 아닌 배포 가능한 버전으로 다룹니다
실습에서는 고객 지원용 시스템 프롬프트를 Langfuse에 저장했습니다. v1은 짧은 답을 지시했고, v2는 검증된 제품 사실만 사용하며 모르면 사람에게 에스컬레이션하도록 명시했습니다. 두 버전 모두 production 라벨을 사용했지만, v2를 만들자 라벨은 v1에서 v2로 이동했습니다.
프롬프트 | 의도 | production 결과 |
v1 | 짧고 간결한 답변 | v2 생성 후 라벨이 이동했습니다. |
v2 | 근거 없는 답변 금지, 불확실하면 에스컬레이션 | 현재 운영 배포 대상으로 해석됩니다. |
prompt = lf.get_prompt("support-system")
with lf.start_as_current_observation(
as_type="generation",
name="answer-generation",
model="gpt-4o-mini",
prompt=prompt,
) as generation:
generation.update(output="응답 결과")여기서 prompt=prompt가 중요합니다. 생성 결과에 어떤 프롬프트 버전이 사용됐는지 명시적으로 남겨야, 나중에 “v2가 더 좋다”는 결론을 단순한 추측이 아닌 버전별 지표로 뒷받침할 수 있습니다. Langfuse도 특정 Generation에 프롬프트를 직접 전달하는 방식을 권장합니다. 공식 연결 방식
프롬프트와 데이터셋 같은 운영 메타데이터는 Postgres에, 대량 Trace·Observation·Score는 ClickHouse에 저장됩니다. 이 분리를 이해해야 “프롬프트 정의를 ClickHouse에서 찾을 수 없다”는 상황을 정상 동작으로 받아들일 수 있습니다.
2. 데이터셋은 평가의 공통 기준면입니다
평가 대상이 바뀌면 점수 비교는 의미를 잃습니다. 그래서 실습에서는 고객 지원 Golden Q&A 10개를 support-golden-qa 데이터셋으로 만들고, 각 항목에 안정적인 golden-00부터 golden-09까지의 ID를 부여했습니다.
lf.create_dataset(name="support-golden-qa", description="고객 지원 기준 질의응답")
lf.create_dataset_item(
dataset_name="support-golden-qa",
id="golden-00", # 재실행해도 중복 대신 upsert
input={"question": "..."},
expected_output="...",
)안정적인 ID는 스크립트를 여러 번 실행해도 항목이 늘어나지 않게 하므로, CI나 정기 평가에서 특히 중요합니다. 데이터셋은 “시험 문제”일 뿐 아니라 프롬프트·모델·검색 파이프라인 변경을 비교하는 고정 기준입니다.
3. 같은 데이터셋으로 A/B 실험을 실행합니다
dataset.run_experiment()는 데이터셋의 모든 항목에 태스크를 실행하고, 실행별 트레이스를 남기며, 평가기를 적용합니다. Langfuse 데이터셋을 사용하면 Dataset Run도 자동 생성돼 UI에서 비교할 수 있습니다. SDK 기반 실험 문서
이번 검증에서는 다음 세 가지 코드 평가기를 사용했습니다.
평가기 | 판정 기준 | 적합한 상황 |
keyword-recall | 기대 답변의 핵심 키워드를 얼마나 포함하는지 | 기준 답이 명확한 FAQ |
length-ok | 답변 길이가 정책 범위인지 | 형식·UX 제약 |
answered | 문서 참조나 지원 요청으로 회피하지 않고 답했는지 | 명확한 실패 패턴 |
result_v1 = dataset.run_experiment(
name="prompt-v1",
task=make_task(prompt_v1, "v1"),
evaluators=[keyword_recall, length_ok, answered],
)
result_v2 = dataset.run_experiment(
name="prompt-v2",
task=make_task(prompt_v2, "v2"),
evaluators=[keyword_recall, length_ok, answered],
)결과는 분명했습니다.
실험 | answered | keyword-recall | length-ok |
prompt-v1 | 0.000 | 0.000 | 1.000 |
prompt-v2 | 1.000 | 1.000 | 1.000 |
v1은 짧게 답하라는 지시를 만족했지만 실제 질문에 답하지 않고 회피했습니다. 반대로 v2는 “검증된 사실만 답하고, 모르면 에스컬레이션”이라는 안전장치를 통해 기준 답변을 충족했습니다. 이 사례가 보여 주는 핵심은 길이 같은 형식 지표 하나만 보면 품질 악화를 놓칠 수 있다는 점입니다. 최소한 과업 성공·정확성·형식처럼 상호 보완적인 지표를 함께 두셔야 합니다.
코드 평가기는 결정적이고 비용이 들지 않으며 CI에 적합합니다. 반면 어조, 유용성, 근거성처럼 의미 해석이 필요한 기준에는 LLM-as-a-Judge나 사람 검토가 더 적합합니다. Code Evaluators 문서
4. LLM-as-a-Judge는 자동화된 의미 판단이지만, 교정 대상이기도 합니다
실습에서는 llm-judge-correctness를 구현했습니다. API 키가 없을 때는 기대 답변과의 토큰 중첩 및 회피 문구를 이용한 결정적 루브릭을 사용했고, 키가 있으면 모델에게 0.0~1.0 점수만 반환하도록 요청하는 구조입니다. 따라서 오프라인에서도 전체 흐름을 검증할 수 있고, 운영에서는 실제 Judge 모델로 전환할 수 있습니다.
Judge 대상 | 결과 |
judge-prompt-v1 | 0.000 |
judge-prompt-v2 | 1.000 |
Langfuse의 관리형 Evaluator를 쓰면 Hallucination, Context Relevance, Toxicity, Helpfulness, Ragas 등의 템플릿을 구성해 라이브 데이터나 실험 실행에 적용할 수 있습니다. 구조화된 출력을 지원하는 LLM Connection이 필요하며, 평가 실행 자체도 추적으로 남아 오류·토큰·응답을 디버깅할 수 있습니다. LLM-as-a-Judge 설정과 운영
다만 Judge를 “진실 판별기”로 두면 안 됩니다. 좋은 운영 순서는 다음과 같습니다.
- 명확한 실패 조건은 코드 평가기로 먼저 차단합니다.
- 의미 판단이 필요한 차원에만 Judge를 사용합니다.
- 일부 표본을 사람이 검토해 Judge와의 불일치를 측정합니다.
- 불일치 사례를 Golden Dataset과 루브릭 개선에 반영합니다.
이렇게 하면 LLM-as-a-Judge는 사람을 없애는 기능이 아니라, 사람이 만든 기준을 더 넓은 트래픽으로 확장하는 도구가 됩니다.
5. Annotation Queue로 도메인 전문가를 평가 루프에 넣습니다
자동 평가만으로는 정책 해석, 브랜드 어조, 규제 문구처럼 도메인 지식이 필요한 항목을 충분히 판단할 수 없습니다. Annotation Queue는 Trace·Observation·Session을 대기열에 넣고, 정해진 Score Config에 따라 전문가가 점수와 의견을 남기도록 하는 수동 평가 기능입니다. Annotation Queue 문서
실습에서는 다음 구성을 만들었습니다.
구성 요소 | 설정 |
Score Config 1 | answer-quality: good=1, ok=0.5, bad=0 |
Score Config 2 | factually-correct: BOOLEAN |
Queue | human-review |
검토 대상 | 사용자 thumbs 점수를 이미 가진 Trace 8개 |
사용자 피드백을 이미 가진 트레이스를 선별한 이유가 중요합니다. 같은 Trace에서 사용자 반응, 자동 평가, 사람 평가가 함께 존재해야 신호 간 불일치를 분석할 수 있기 때문입니다. Queue는 UI에서 키보드 중심으로 빠르게 처리할 수 있고, API로 생성·대상 추가를 자동화할 수도 있습니다.
6. 모든 품질 신호는 scores에서 만납니다
이 실습에서 가장 실용적인 발견은 사용자 피드백, 코드 평가기, Judge, 사람 평가가 결국 ClickHouse의 scores 테이블에 모인다는 점입니다. 테이블의 핵심 컬럼은 trace_id, name, value, source, data_type, dataset_run_id, queue_id, timestamp입니다.
Langfuse의 ClickHouse 테이블은 ReplacingMergeTree이므로, 직접 분석할 때는 FINAL과 is_deleted = 0을 기준으로 두었습니다.
SELECT
source,
name,
any(data_type) AS data_type,
count() AS n,
round(avg(value), 3) AS avg_value
FROM scores FINAL
WHERE is_deleted = 0
GROUP BY source, name
ORDER BY source, name;실행 환경에서는 answered, keyword-recall, llm-judge-correctness, user-thumbs, human-answer-quality 등이 한 테이블에서 조회됐습니다. 오프라인 SDK가 기록한 평가 점수는 source = API였고, 관리형 Evaluator의 결과는 EVAL, UI Annotation의 결과는 ANNOTATION으로 구분됩니다. 따라서 평균 점수만 보지 말고, 그 점수가 어떤 생산 경로에서 왔는지를 반드시 함께 보셔야 합니다.
A/B를 ClickHouse에서 다시 구성하기
실습 버전에서는 평가 점수의 dataset_run_id가 ClickHouse에서 비어 있었습니다. Dataset Run 연결은 Postgres에 있으므로, ClickHouse만으로 A/B를 분석하려면 Trace에 실험 변형을 태그로 남겨야 합니다.
lf.update_current_trace(tags=["variant:v2", "eval-experiment"])그 뒤 scores와 traces를 조인하면 실험별 평균을 복원할 수 있습니다.
변형 | answered | keyword-recall | llm-judge-correctness |
prompt-v1 | 0 | 0 | 0 |
prompt-v2 | 1 | 1 | 1 |
이 결과는 UI의 비교 화면을 대체하려는 것이 아닙니다. 운영 데이터와 제품 지표, 비용, 사용자 세그먼트를 결합해 분석할 수 있다는 점이 핵심입니다. 다만 Langfuse는 ClickHouse 내부 스키마를 안정적인 공개 API로 보지 않으므로, 직접 SQL은 읽기 전용 분석에 한정하고 Langfuse 업그레이드마다 검증해야 합니다. ClickHouse 직접 조회 시 유의사항
신호의 일치보다 불일치가 더 가치 있을 때가 있습니다
동일 Trace의 user-thumbs, hallucination-check, human-answer-quality를 한 행으로 피벗했습니다.
실측 표본에는 사용자 thumbs-down이 0, hallucination 점수도 0, 사람 품질 평가는 0.5였던 Trace가 있었습니다. 세 신호가 동시에 낮아진 이 행은 우선적으로 디버깅할 후보입니다. 반대로 사용자 만족은 낮지만 Judge는 높다면, 답변의 정확성보다 톤·지연·정책 준수 같은 다른 축이 문제일 수 있습니다. 평가 체계의 가치는 평균을 하나 더 만드는 데 있지 않고, 이러한 불일치를 조사 가능한 큐로 바꾸는 데 있습니다.
운영에서 확인한 여섯 가지 주의점
- SDK 실험의 Score 출처를 혼동하지 마셔야 합니다. 이 환경에서
run_experiment의 코드 평가기와 코드 기반 Judge는source=API로 저장됐습니다.EVAL은 관리형 Evaluator,ANNOTATION은 UI 기반 사람 검토를 나타냅니다. - ClickHouse에 Dataset Run 연결이 항상 채워진다고 가정하면 안 됩니다. 실습에서는
dataset_run_id가 비어 있었습니다. 데이터 웨어하우스에서 A/B를 이어 보려면 버전·변형·릴리스 태그를 Trace에 의도적으로 기록하는 것이 안전합니다. - Score Config의 타입을 정확히 맞춰야 합니다. CATEGORICAL Config에 NUMERIC Score를 바인딩한 항목은 ingestion 과정에서 조용히 사라졌습니다. Config를 쓸 때는 값 타입을 맞추고, 그렇지 않으면 Config 바인딩 없이 기록해야 합니다.
- 점수 ingestion은 비동기입니다. SDK → Worker → ClickHouse 경로를 지나므로 실습에서는 수초에서 최대 약 2분의 지연을 봤습니다. 테스트와 대시보드는 즉시 조회를 가정하지 말고, 준비 상태를 폴링하거나 허용 지연을 명시해야 합니다.
- Trace 이름은 전역 식별자가 아닙니다. 공유 프로젝트에서
support-request라는 이름만으로 최근 트레이스를 고르면, 다른 실습의 PII 트레이스까지 섞일 수 있습니다. 이름 대신 특정 Score, 태그, 프로젝트, 시간 범위로 대상을 선택하셔야 합니다. - 두 저장소의 백업과 복구 범위를 함께 설계해야 합니다. Postgres가 초기화되면 프롬프트·데이터셋·Annotation Queue가 사라질 수 있고, ClickHouse에는 과거 Trace·Score가 남을 수 있습니다. 평가 메타데이터와 평가 결과를 별개로 백업하면 재현성이 깨집니다.
결론: 모델 평가는 배포 전 테스트와 운영 관측성을 연결하는 시스템입니다
이번 검증에서 v2는 v1보다 세 개의 자동 지표에서 모두 앞섰습니다. 하지만 더 중요한 결과는 그 승패가 아닙니다. 프롬프트 버전은 Generation에 연결됐고, 동일 데이터셋에서 실험됐으며, 코드 평가·Judge·사람 평가·사용자 피드백이 같은 Trace ID 축에서 만났습니다. 그래서 팀은 “좋아 보인다”가 아니라 “어떤 데이터에서, 어떤 기준으로, 누가 판단했는가”를 답할 수 있습니다.
Langfuse를 모델 평가에 사용할 때 가장 먼저 만들 것은 거대한 대시보드가 아닙니다. 작지만 안정적인 Golden Dataset, 실패를 명확히 정의한 코드 평가기, 사람이 검토할 표본과 점수 기준, 그리고 버전·변형을 남기는 Trace 규약입니다. 이 네 가지가 갖춰지면 평가 결과는 보고용 숫자가 아니라 다음 배포를 더 안전하게 만드는 피드백 루프가 됩니다.
재현을 위한 최소 순서
cd usecase/langfuse-ee && ./01-up.sh
cd ../langfuse-eval
python -m venv .venv
source .venv/bin/activate
pip install "langfuse>=3" openai
python 01-seed-traces.py 20
# 프롬프트 → 데이터셋 → 실험 → Judge → Annotation → scores 분석 순으로 실행외부 LLM 키 없이도 코드 평가기와 결정적 Judge 대체 로직으로 전체 파이프라인을 검증할 수 있습니다. 실제 LLM-as-a-Judge를 운영에 연결할 때는 LLM Connection, 비용 한도, 샘플링, 사람 평가와의 정합성 검증을 함께 구성하시는 것을 권장드립니다.