Skip to content

필사 모드: Langfuse 트레이싱 데이터 모델 — trace, observation, score가 한 번의 실행을 담는 방식

한국어
0%
정확도 0%
💡 왼쪽 원문을 읽으면서 오른쪽에 따라 써보세요. Tab 키로 힌트를 받을 수 있습니다.

들어가며 — 대시보드보다 데이터 모델이 먼저다

LLM 애플리케이션에 트레이싱을 붙이는 작업은 보통 이렇게 시작합니다. SDK를 설치하고, 키를 넣고, 화면에 트레이스가 들어오는 것을 확인하고 만족합니다. 그리고 두 달쯤 뒤에 "지난주에 답변 품질이 떨어진 요청들이 어떤 검색 단계를 거쳤는지" 같은 질문이 들어오면 그때 막힙니다.

이 시리즈는 Langfuse를 구현 관점에서 다룹니다. 도구 비교가 아니라 데이터가 실제로 어떻게 수집되고 어디에 저장되며 어떻게 다시 꺼내지는지를 봅니다. 첫 글은 데이터 모델입니다. 이걸 먼저 이해해야 나머지 다섯 편이 읽힙니다.

구성과 설정 이름은 2026-08-15에 공식 문서에서 확인했습니다. Langfuse는 버전에 따라 아키텍처가 달라지므로 사용 중인 버전의 문서를 다시 확인하세요. 이 시리즈가 기준으로 삼은 것은 자체 호스팅 메이저 버전 v4입니다. 문서의 버전 정책 페이지는 v2를 수명 종료, v3를 폐기 예정, v4를 정식 출시로 표시하고 있습니다.

세 층으로 나뉜다 — trace, observation, score

Langfuse의 데이터 모델 문서가 정의하는 핵심 객체는 세 가지입니다.

trace는 한 번의 요청 또는 작업을 나타냅니다. 챗봇에 사용자가 한 번 말을 걸어 답을 받기까지가 하나의 trace입니다. 문서의 표현으로는 같은 trace_id를 공유하는 observation들의 논리적 묶음입니다.

observation은 그 안에서 애플리케이션이 수행한 개별 단계입니다. 모델 호출, 도구 실행, 검색 단계가 각각 하나의 observation이 됩니다. 중요한 것은 이들이 애플리케이션 구조를 반영해 중첩된다는 점입니다. 평평한 목록이 아니라 트리입니다.

score는 평가 결과입니다. 스코어 문서는 이를 평가 결과를 저장하는 범용 객체로 설명합니다. 사람이 손으로 매긴 점수, LLM 심사 결과, 코드 평가 결과가 전부 같은 형태로 여기에 모입니다.

흔한 오해는 trace를 observation을 담는 컨테이너 테이블로 상상하는 것입니다. 실제 저장 형태는 다르며, 이 부분은 아래에서 다시 다룹니다.

observation이 갈리는 세 갈래 — span, generation, event

observation은 하나의 타입이 아닙니다. 문서는 LLM에 특화된 타입으로 generation과 event를 언급하고, 파이썬 SDK는 LangfuseSpan, LangfuseGeneration, LangfuseEvent 세 클래스를 노출합니다.

타입무엇을 담나시간특징
span일반적인 작업 구간시작과 끝이 있음검색, 전처리, 도구 호출 같은 단계
generation모델 호출시작과 끝이 있음모델 이름, 토큰 사용량, 비용을 함께 담음
event한 시점의 사건시점 하나가드레일 발동, 캐시 적중 같은 표시

generation이 따로 있는 이유는 명확합니다. 비용과 토큰이라는 축이 여기에만 있기 때문입니다. 토큰과 비용 추적 문서에 따르면 사용량은 usage_details에, 비용은 cost_details에 담깁니다. 사용량 타입은 제공자마다 다른 임의의 문자열이며, 가장 단순하게는 입력과 출력 두 가지입니다. 그리고 사용량이 들어왔고 가격이 정의된 모델과 매칭되면 Langfuse가 수집 시점에 비용을 계산해 넣습니다.

실무적 함의는 분명합니다. 모델 호출을 span으로 남기면 비용 집계에서 사라집니다. 자체 호스팅 모델을 쓴다면 프로젝트 설정의 모델 정의에 정규식 패턴과 가격을 등록해야 그때부터 비용이 붙습니다.

중첩이 필요한 이유 — RAG 한 번의 실행이 남기는 트리

왜 평평한 로그가 아니라 트리여야 하는지는 실제 실행 하나를 그려 보면 바로 보입니다. 사용자 질문 하나가 RAG 파이프라인을 통과할 때 남는 구조는 대략 이렇습니다. 아래는 개념을 보여 주는 예시입니다.

trace  chat-request                       [user_id, session_id, tags, metadata]
└─ span  rag-pipeline                     1,840 ms
   ├─ span  retrieve                        420 ms
   │  ├─ generation  embed-query             90 ms   usage_details, cost_details
   │  └─ span  vector-search                310 ms   metadata: top_k, index
   ├─ span  rerank                          260 ms
   │  └─ generation  rerank-call            250 ms
   ├─ generation  answer                  1,090 ms   model, usage_details, cost_details
   └─ event  guardrail-hit                            metadata: rule=pii

이 트리가 답해 주는 질문은 평평한 로그가 답하지 못합니다. 전체 응답이 1.8초 걸렸을 때 그중 검색이 0.42초를 썼고 답변 생성이 1.09초를 썼다는 분해는 부모와 자식 관계가 있어야 계산됩니다. 실패도 마찬가지입니다. 어느 단계에서 예외가 났는지, 그 단계의 입력이 무엇이었는지는 그 단계가 독립된 observation일 때만 남습니다.

에이전트는 한 단계 더 나갑니다. 도구 호출이 몇 번 일어날지 실행 전에 알 수 없어 같은 이름의 형제 노드가 반복해 붙습니다. 루프를 세 바퀴 돈 실행과 열 바퀴 돈 실행이 같은 trace 이름으로 남고, 차이는 자식 observation의 개수와 깊이로만 드러납니다. "도구 호출이 다섯 번을 넘긴 실행"을 찾는 질의는 이 구조 위에서만 성립합니다.

trace 위에 얹히는 두 층 — session과 user

trace 하나로는 대화를 설명하지 못합니다. 여러 턴이 오가는 상호작용은 trace 여러 개로 남기 때문입니다. Langfuse는 이를 session으로 묶습니다. 문서는 관계를 하나의 session이 여러 trace를 가진다고 표기하며, session 식별자는 200자 미만의 US-ASCII 문자열이면 무엇이든 쓸 수 있다고 설명합니다.

사용자 축은 별도 속성으로 붙습니다. 데이터 모델 문서가 명시하는 trace 수준 속성은 네 가지입니다.

  • user_id — 최종 사용자 식별자
  • session_id — 대화 또는 상호작용 묶음
  • tags — 기능이나 워크플로 분류
  • metadata — 임의의 키와 값

여기에 배포 맥락을 나누는 environment와 애플리케이션 버전을 표시하는 release, version이 더해집니다. 이 값들을 언제 정하느냐가 나중에 질의 가능한 축을 결정합니다. 뒤늦게 붙이면 과거 데이터에는 없습니다.

문서가 명시하는 중요한 동작이 하나 더 있습니다. trace 수준 속성은 그 trace에 속한 모든 observation으로 자동 전파됩니다. 이 문장이 다음 절과 3편의 저장 계층 이야기로 곧장 이어집니다.

score — 평가 결과가 붙는 자리

score는 네 가지 필드로 이루어집니다. 이름, 값, 데이터 타입, 그리고 선택적인 코멘트입니다. 데이터 타입은 문서 기준으로 네 종류입니다.

데이터 타입값의 형태쓰임
NUMERIC실수정확도, 관련성 같은 연속 측정
CATEGORICAL미리 정한 문자열알려진 선택지 중 하나로 분류
BOOLEAN0 또는 1통과와 실패
TEXT1자에서 500자 자유 문자열정성적 주석

TEXT 타입에는 제약이 있습니다. 문서는 TEXT score를 실험, LLM 심사 평가자, 분석에서 쓸 수 없다고 명시합니다. 자유 서술은 사람이 읽는 용도이지 집계 축이 아니라는 뜻입니다.

붙는 자리도 네 곳입니다. trace, observation, session, 그리고 데이터셋 실행입니다. 가장 흔한 것은 trace에 붙는 형태이고, 이것이 종단 평가에 해당합니다. v4에서 눈여겨볼 변화는 평가자가 trace 수준에서 observation 수준으로 내려왔다는 점입니다. 버전 정책 문서는 v3에서 v4로 넘어오며 일어난 변화 중 하나로 이를 명시합니다. 파이프라인 중간의 검색 단계 하나만 따로 평가하는 일이 가능해졌다는 의미입니다.

저장 형태를 알아야 질의가 어긋나지 않는다

앞에서 미뤄 둔 부분입니다. 데이터 모델 문서는 저장을 이렇게 설명합니다. 개념적으로 하나의 observation 테이블이 있고, 각 행은 observation 수준 데이터에 더해 trace 수준 속성의 사본을 함께 담습니다.

이 한 문장이 실무에서 세 가지를 결정합니다.

  • 비정규화가 기본입니다. user_id로 필터링하는 질의는 조인 없이 처리됩니다. 관계형 데이터베이스를 상상하고 조인을 설계하면 방향이 어긋납니다.
  • trace 속성은 사후 수정이 값싸지 않습니다. 사본이 observation 수마다 존재하기 때문입니다.
  • 관측 데이터에 맞는 저장 엔진이 필요합니다. 넓고 반복이 많은 행을 대량으로 스캔하는 패턴은 컬럼 지향 저장이 잘하는 일입니다. Langfuse가 trace, observation, score를 ClickHouse에 두는 이유가 여기 있고, 3편에서 이 부분을 자세히 봅니다.

전송 방식도 알아 두면 좋습니다. 문서는 Langfuse가 trace를 로컬에서 배치로 모아 백그라운드로 보낸다고 설명합니다. 요청 경로를 동기적으로 막지 않기 위해서입니다. 그리고 SDK 전체가 OpenTelemetry 표준 위에 서 있습니다. 이 두 가지의 실제 동작은 2편에서 다룹니다.

마치며 — 먼저 정할 것과 나중에 고칠 것

데이터 모델에서도 나중에 고치기 쉬운 것과 어려운 것이 갈립니다. score를 추가하고 대시보드를 다시 그리고 태그를 늘리는 일은 언제든 할 수 있습니다. 반면 user_idsession_id를 어떤 값으로 채울지, 어디까지를 하나의 trace로 볼지, 어떤 단계를 별도 observation으로 남길지는 초기에 정해야 합니다. 나중에 바꾸면 과거 데이터와의 연결이 끊깁니다.

지금 할 수 있는 점검은 간단합니다. 지난달에 받았던 질문 세 개를 적고, 지금 남기는 구조로 답할 수 있는지 확인해 보세요. 답할 수 없다면 빠진 것은 대시보드가 아니라 계측입니다.

도구 선택이 아직 끝나지 않았다면 LLM 프로덕션 모니터링 플랫폼 비교를 먼저 읽는 편이 좋습니다. 이 시리즈는 Langfuse를 쓰기로 정한 다음의 이야기입니다.

직접 해보기

  • DuckDB 데이터 분석 놀이터 — 중첩된 트리를 평평한 테이블로 펴서 부모 자식 관계를 질의로 풀어 보세요.
  • PostgreSQL 놀이터 — trace와 observation을 관계형으로 모델링해 보면 왜 비정규화가 선택되는지 체감할 수 있습니다.

시리즈

참고 자료

현재 단락 (1/62)

LLM 애플리케이션에 트레이싱을 붙이는 작업은 보통 이렇게 시작합니다. SDK를 설치하고, 키를 넣고, 화면에 트레이스가 들어오는 것을 확인하고 만족합니다. 그리고 두 달쯤 뒤에 ...

작성 글자: 0원문 글자: 5,363작성 단락: 0/62