[Langfuse 8] 평가로 프롬프트 배포를 막고 롤백하기

심대용·4일 전
post-thumbnail

Langfuse 8 — 품질 회귀를 차단하고 프롬프트 라벨 롤백하기

이 글은 Langfuse 실습 시리즈의 8편입니다. 7편의 평가 원칙을 배포 전 회귀 검사와 실습용 프롬프트 라벨의 승격·롤백으로 연결합니다.

이 글에서 다룰 주제

  • 같은 사건으로 기준 버전과 후보 버전을 비교하는 방법
  • 품질·비용·지연 기준을 실행 전에 고정하고 승격을 막는 gate
  • 실습 전용 label의 승격과 롤백을 실제로 확인하는 방법
  • 로컬 검사, CI 연결, 실제 canary 배포의 책임 구분

주요 단어 · Regression · Dataset · Release Gate · Prompt Label · Canary · Rollback


프롬프트를 짧게 바꾸면 답변이 빨라지고 토큰도 줄어들 수 있습니다. 그런데 증거를 읽지 않고 같은 원인만 답하기 시작했다면 그 변화는 개선일까요? 비용과 속도를 따로 보면 좋은 후보처럼 보이지만, 장애 진단이라는 목적은 잃었습니다.

이번에는 일부러 잘못된 후보를 만듭니다. 목적은 좋은 프롬프트를 발견하는 것이 아니라, 좋지 않은 변경을 배포 검사에서 실제로 막을 수 있는지 확인하는 것입니다. 정상 기능뿐 아니라 실패 경로가 작동하는지도 점검하는 실습입니다.

데이터셋 평가와 gate, 실습 label 승격·롤백

그림의 gate는 Langfuse가 자동으로 프롬프트를 골라 주는 기능이 아닙니다. 우리가 정의한 품질·비용·시간 조건을 코드로 검사합니다. Langfuse는 비교할 실행·점수·프롬프트 버전을 저장하고 UI로 확인할 수 있게 합니다.

1. 기준 버전의 범위

5편의 여섯 사건 중에는 잘못 진단한 사례도 있었습니다. missing-logs와 unknown-injection은 필요한 이관 대신 diagnosed를 반환했습니다. 이 결과를 정답으로 바꾸거나 성공 사례로 계산하지 않았습니다.

이번 회귀 실습에는 기존 실행에서 통과했던 세 종류를 골랐습니다.

사건기대 outcome기대 cause핵심 근거
db-saturationdiagnoseddatabase_saturation높은 pool 사용률·연결 획득 대기 로그
upstream-slowdowndiagnosedupstream_slowdownupstream 지연·dependency timeout
outdated-runbookescalatedoutdated_runbookcurrent=false인 runbook

이는 기존에 통과하던 사례가 깨지는지 보는 부분 회귀 집합입니다. 세 사례를 통과했다고 여섯 사례의 알려진 실패가 해결되지는 않습니다. 실제 운영 승격에서는 알려진 실패를 별도 허용하기로 검토하거나 수정한 뒤 전체 필수 집합을 다시 통과해야 합니다.

공식 CI 가이드도 최초 실행이나 최신 실행이라는 이유만으로 실패한 run을 승인된 baseline으로 취급하지 않도록 설명합니다. baseline과 후보의 입력·기대값·평가 기준이 같아야 비교할 수 있고, 누락·중복 사례나 잘못된 점수는 실패로 처리해야 합니다. Experiments in CI/CD

2. 세 프롬프트 버전

원래 학습 프롬프트와 분리한 aiops-lab/release-gate를 사용했습니다.

버전내용역할
v15편의 근거 기반·읽기 전용 진단 프롬프트세 사례 비교 기준
v2어떤 증거든 DB 원인과 가짜 evidence ID를 출력gate가 막아야 하는 의도적 잘못된 후보
v3v1과 prompt·config가 완전히 동일label 승격·복원 동작만 확인하는 훈련용

v2에는 다음처럼 잘못된 응답을 의도적으로 지시했습니다. 실제 모델이 이 JSON을 생성하도록 호출했고, 결과를 코드에서 미리 넣거나 바꿔치기하지 않았습니다.

{
  "outcome": "diagnosed",
  "cause": "database_saturation",
  "evidence_ids": ["fabricated-evidence"],
  "explanation": "근거와 무관하게 DB 원인으로 단정하는 의도적인 회귀 실습 출력입니다.",
  "next_action": "운영 변경 없이 담당자에게 추가 읽기 전용 확인을 요청합니다."
}

v2는 현실적인 프롬프트 개선 후보라기보다 negative control, 즉 검사에서 반드시 잡혀야 하는 의도적 실패 입력입니다. 이 비교로 어느 모델이 더 뛰어난지 결론 내리지 않습니다.

v3를 둔 이유도 분리합니다. gate에서 실패한 v2를 승격해 놓고 “롤백 연습”이라고 부르면 배포 규칙을 스스로 깨는 셈입니다. v2는 끝까지 차단하고, 내용이 같은 정상 버전끼리 실습 label만 옮겼다가 복원합니다.

3. 실행 전에 정한 gate

Release gate는 후보가 승격될 조건을 판정하는 검사입니다. 결과를 본 뒤 후보가 통과하도록 기준을 낮추면 검사 의미가 달라집니다. 이번 실습은 다음 정책을 실행 전에 증거 파일에 저장했습니다.

조건실습 기준단위·분모
작업 계약 통과율1.0 이상outcome·cause·필수 evidence 등을 모두 통과한 사건 / 필수 3건
증거 ID 유효율1.0 이상인용 ID가 모두 존재하는 사건 / 필수 3건
평균 배부 비용0.01 USD 이하세 사건 generation에 배부한 모의 비용의 평균
p95 전체 소요 시간30초 이하순차 실행 3건의 client 측 agent duration

비용은 실제 청구액이 아닙니다. 입력 100만 토큰당 0.10 USD, 출력 100만 토큰당 0.30 USD라는 실습용 가정 단가로 계산한 배부 금액입니다. 로컬 Ollama API 청구는 없지만 장비·전력·운영 비용을 여기서 측정한 것은 아닙니다.

p95는 정렬된 세 값을 선형 보간했습니다. 표본이 세 개뿐이므로 이 값으로 서비스 SLO를 보증할 수 없습니다. 현재 실습은 품질·비용·지연을 같은 판정에 넣는 구조를 확인하는 것이며, 0.01 USD와 30초도 운영에 그대로 복사할 보편적 기준은 아닙니다.

checks = {
    "quality": task_success_rate >= 1.0,
    "evidence": valid_evidence_rate >= 1.0,
    "cost": mean_allocation_cost_usd <= 0.01,
    "latency": p95_latency_seconds <= 30.0,
}
eligible = all(checks.values())

전체 구현은 필수 case ID를 정확히 한 번씩 받았는지도 확인합니다. 점수가 없거나 NaN인 비용·시간이 들어오면 통과로 계산하지 않습니다. 이 검사는 종합 안전성 판정은 아닙니다. 자연어 조치 제안의 적절성과 실제 권한 집행은 별도 검토·런타임 제어가 필요합니다.

4. Dataset과 Experiment 실행

Langfuse Dataset aiops-incident-regression에는 세 사건의 입력과 기대 outcome·cause·required evidence를 저장했습니다. 모델에는 실제 관측 자료만 넘기고 기대 정답은 평가 함수에서만 사용합니다.

SDK experiment의 task는 5편의 run_case()를 재사용합니다. 각 사건은 조회 도구·runbook 검색·Ollama generation·평가 observation을 남깁니다. 변경한 것은 prompt version이며, fixture·평가 기준·모델·순차 실행 조건은 같습니다.

result = dataset.run_experiment(
    name="aiops-release-gate",
    run_name=run_name,
    task=task,
    evaluators=[quality, evidence],
    run_evaluators=[average_quality],
    max_concurrency=1,
    metadata={
        "prompt_name": "aiops-lab/release-gate",
        "prompt_version": prompt.version,
        "policy_hash": digest(GATE_POLICY),
    },
)

이것은 전체 실행 파일 중 experiment 호출 부분입니다. SDK가 task를 감싼 observation 안에 agent의 단계들이 들어가므로, agent observation ID와 trace ID를 같은 것으로 취급하지 않습니다. 저장 결과는 experiment item의 trace ID와 내부 agent의 trace ID가 같은지까지 확인합니다. SDK Experiments

5. 후보가 빠르고 싸도 차단되는 이유

2026-10-05 14:03 KST에 v1과 v2를 각 세 사건에 실행했습니다. 총 여섯 번의 로컬 Ollama 호출이며, 실제 결과는 다음과 같습니다.

지표기준 v1잘못된 후보 v2
작업 계약 통과3/30/3
증거 ID 유효3/30/3
합계 토큰1,6221,117
평균 모의 배부 비용0.000075133 USD0.000052033 USD
p95 전체 소요 시간3.110초1.964초
gate 판정이 부분 집합에서 통과품질·증거 조건으로 차단

v2는 비용과 지연 조건만 보면 통과합니다. 그러나 세 사건 모두 실제로 존재하지 않는 fabricated-evidence를 반환했고, upstream 지연과 오래된 runbook도 DB 원인으로 단정했습니다. 그래서 품질과 증거 조건에서 승격이 차단됐습니다.

실험 item·출력·평가 점수·prompt 연결·토큰·비용과 최종 label을 API로 다시 읽어 32개 확인 항목을 통과했습니다. 여기서 “검증 통과”는 의도대로 잘못된 후보가 거절되고 기록이 저장됐다는 뜻이며, v2의 품질 통과를 뜻하지 않습니다.

Langfuse의 Datasets → aiops-incident-regression → Experiments에서 gate-baseline-v1과 gate-negative-control-v2를 비교합니다. 평균 점수만 보지 말고 각 item의 기대값과 출력, 가짜 evidence ID를 함께 읽습니다.

기준 v1과 실패 후보 v2를 나란히 비교한 실제 Experiment 화면

상단 Baseline에 gate-baseline-v1, Compare에 gate-negative-control-v2를 고른다. 세 item의 계약·근거 점수는 기준 1.00에서 후보 0.00으로 떨어졌다. SUMMARY는 화면에 표시된 3개 item 범위이며, 각 행에서 fabricated-evidence를 직접 확인할 수 있다.

모델 호출 자체는 성공하고 유효한 JSON도 돌아왔습니다. 따라서 실행 오류 개수만으로는 이런 회귀를 놓칩니다. 원인·이관·증거 계약을 별도로 평가해야 “응답은 왔지만 업무 결과는 틀림”을 구분할 수 있습니다.

이 실습에서 후보가 더 적은 토큰과 짧은 응답을 사용했다면, 그것은 증거를 검토하는 일을 생략했기 때문일 수 있습니다. 성공 결과당 비용이나 품질 하한을 같이 보지 않으면 이런 후보를 비용 최적화로 오해하기 쉽습니다. 성공이 0건이면 성공당 비용은 0으로 쓰지 않고 계산 불가로 처리해야 합니다.

6. lab label 승격과 롤백

Langfuse label은 특정 prompt version을 가리키는 포인터입니다. 실행 코드는 label로 prompt를 가져올 수 있으며, 같은 label을 이전 버전으로 옮기면 다음 가져오기에서 이전 내용을 받습니다. 실습에서는 production 대신 별도 이름 aiops-lab-production을 사용했습니다. Prompt Version Control

실제로 실행한 순서는 다음과 같습니다.

  1. aiops-lab-production을 v1에 연결하고 직접 조회합니다.
  2. v1·v2 실험을 실행하고 v2의 gate 실패를 확인합니다.
  3. v2는 승격하지 않습니다.
  4. v1과 내용·설정 해시가 같은 v3를 훈련용 label에 연결합니다.
  5. label을 v1으로 복원하고 직접 조회해 확인합니다.
# 실습용 label만 변경. 실제 production label은 사용하지 않음.
langfuse.update_prompt(
    name="aiops-lab/release-gate",
    version=3,
    new_labels=["aiops-lab-production"],
)
assert langfuse.get_prompt(
    "aiops-lab/release-gate",
    label="aiops-lab-production",
    cache_ttl_seconds=0,
).version == 3

# 원래 정상 내용으로 복원
langfuse.update_prompt(
    name="aiops-lab/release-gate",
    version=1,
    new_labels=["aiops-lab-production"],
)

실습 label이 v1으로 복원된 Prompt Versions 화면

왼쪽 v1에 aiops-lab-production이 붙고, 최신 v3에는 latest가 남아 있다. 최신 버전과 현재 앱이 가져올 label의 버전은 다를 수 있다.

확인 시 cache_ttl_seconds=0으로 캐시를 우회했습니다. 일반 애플리케이션이 프롬프트를 캐시하고 있다면 label 변경과 모든 요청의 실제 전환 사이에 차이가 생길 수 있습니다. label이 바뀐 화면뿐 아니라 generation에 연결된 prompt version도 확인해야 합니다. Prompt Caching

7. 로컬 gate와 CI 연결

실습 파일은 저장된 후보 결과만 읽고 승격 가능 여부를 종료 코드로 반환하는 경로를 제공합니다. 네트워크 연결이나 새 LLM 호출, label 변경은 하지 않습니다.

docs/langfuse-study-2026-10-04/lab/.venv/bin/python \
  docs/langfuse-aiops-2026-10-05/lab/evaluation.py regression --check-candidate

실제로 실행한 결과는 다음과 같았습니다. 종료 코드 2는 이번 스크립트에서 gate 거절을 뜻하도록 정한 값입니다.

decision: blocked
failed_checks: quality, evidence
cost: pass
latency: pass
exit_code: 2

이 종료 코드를 CI의 다음 배포 단계 조건으로 사용할 수 있습니다. 이번에는 로컬 gate를 실행했으며 GitHub Actions workflow를 실제로 구동하거나 배포한 것은 아닙니다. Langfuse 공식 experiment-action을 쓰는 경우에는 실험 결과를 포함한 RegressionError로 회귀를 전달하는 경로가 있습니다. CI/CD 실험 가이드

실제 CI에서는 prompt version뿐 아니라 코드 commit, dataset version, 평가기 version, 모델 설정을 함께 고정해야 합니다. 어느 값이 바뀌었는지 알 수 없다면 점수 변화가 프롬프트 때문인지 데이터 변경 때문인지 분리하기 어렵습니다.

8. 실제 canary로 확장

현재 실습에서는 운영 트래픽을 분배하지 않았습니다. 실제 canary는 앱 또는 gateway에서 일정 비율의 요청이 후보 label을 사용하도록 구현하고, Langfuse에 실제 선택한 prompt version을 기록하는 방식으로 확장할 수 있습니다. Langfuse가 자체적으로 트래픽을 나누거나 지표를 읽어 승자를 자동 승격시키는 것으로 이해하면 안 됩니다. Prompt CI/CD와 rollout

확장할 때는 사용자·사건 유형별 편차와 샘플 수를 같이 봅니다. 전체 평균이 좋아져도 특정 장애 유형에서 근거 없는 단정이 늘 수 있습니다. 첫 단계는 읽기 전용 제안이며, 사람이 실제 조치를 검토하는 흐름을 유지하는 편이 평가 결과를 해석하기 쉽습니다.

다음 글에서는 배포 후 들어오는 실행을 평가하고, 알림과 검토 결과를 다시 Dataset으로 가져오는 흐름을 이어 갑니다. Offline gate가 배포 전에 알려진 실패를 막는다면, 온라인 평가는 배포 뒤 달라진 입력과 새 실패를 발견하는 역할을 합니다.


실습·공식 문서 확인: 2026-10-05. Langfuse OSS v4.50.0, Python SDK 4.16.0, Ollama 0.35.1, qwen3:8b. 실습 파일 lab/evaluation.py, 결과 evidence/eval-regression.json. 실제 LLM 응답·토큰·시간·저장 결과를 사용했으며 사건 자료와 배부 단가는 합성·가정입니다.

이전: 7편 — 답변과 평가기를 함께 검증하기

다음: 9편 — 저장된 실행 평가와 알림

profile
어제보다 더 성장하는 나

0개의 댓글