Langfuse

이 문서는 Langfuse를 “OpenTelemetry(OTel) 관점”에서 정리한 교재형 자료다. LLM·에이전트 모니터링에 필요한 OTel 연관 부분만 깊게 다룬다. OTel 기본 개념(Trace/Span/Collector/OTLP)은 Learn → Monitoring → OpenTelemetry 문서를 먼저 읽고 오면 좋다.

0. 목차

  1. Langfuse란? 왜 OTel과 엮이나
  2. Langfuse 데이터 모델 ↔ OTel 매핑
  3. Langfuse를 OTLP 백엔드로 쓰기 (엔드포인트·인증)
  4. GenAI Semantic Conventions (gen_ai.*) 매핑
  5. Langfuse 전용 속성 (langfuse.*) 매핑
  6. Span이 “Generation”이 되는 규칙
  7. 계측 방법 4가지 (SDK · OpenLLMetry · OpenInference · OpenLit)
  8. 에이전트(Agent) 모니터링
  9. Python 실전 예제
  10. Collector를 통한 라우팅과 주의점
  11. 운영 베스트 프랙티스 / 함정
  12. 순수 OTel 백엔드 대비 Langfuse의 차이
  13. 용어 사전 & 링크

1. Langfuse란? 왜 OTel과 엮이나

Langfuse는 오픈소스 LLM Engineering / Observability 플랫폼이다. LLM 호출과 에이전트 실행을 추적(trace)하고, 프롬프트·토큰·비용·지연시간을 보고, 품질 점수(eval)를 매기고, 프롬프트를 관리하는 기능을 제공한다.

핵심은 Langfuse가 OpenTelemetry 위에 세워졌다는 점이다.

  • 수집(ingestion) 측면: Langfuse는 OTLP 호환 백엔드다. 즉 OTel로 계측한 트레이스를 표준 OTLP로 그대로 받는다. → 어떤 OTel 계측이든 “백엔드만 Langfuse로” 바꿔 보낼 수 있다.
  • SDK 측면: Langfuse Python/JS SDK(v3 이상)는 내부적으로 OTel SDK를 사용한다. Langfuse의 span은 곧 OTel span이고, Context 전파도 OTel 메커니즘을 그대로 쓴다.

한 줄 요약: Langfuse = “LLM/에이전트에 특화된 OTel 백엔드 + OTel 기반 SDK”. 그래서 순수 OTel 지식이 거의 그대로 통한다. 일반 OTel과의 차이는 “LLM 도메인 시맨틱(프롬프트·토큰·비용·세대)”을 1급 시민으로 다룬다는 것뿐이다.


2. Langfuse 데이터 모델 ↔ OTel 매핑

Langfuse의 객체들은 OTel 개념과 거의 1:1로 대응한다. 이 매핑을 이해하면 나머지가 전부 쉬워진다.

OTel Langfuse 설명
Trace Trace 같은 trace_id 공유. 한 요청/한 에이전트 실행 전체
Span Observation 트레이스 내 작업 단위. 아래 3종으로 타입이 나뉨
└ 일반 Span Span (observation type) 도구 호출·검색·전처리 등 일반 작업
└ LLM 호출 Span Generation (observation type) LLM 호출. 모델·토큰·비용·프롬프트/응답을 가짐 ★
└ 시점 이벤트 Event (observation type) 순간 발생 로그성 이벤트

OTel에 없는 Langfuse 고유 개념(전부 trace에 부가되는 메타로 표현):

Langfuse 개념 의미 OTel에서의 표현
Session 여러 trace를 하나의 대화/세션으로 묶음 langfuse.session.id 속성
User 최종 사용자 식별 langfuse.user.id 속성
Score 품질 평가 점수(eval/사람 피드백) 별도 API/SDK로 trace·observation에 부착
Tags / Metadata 필터링용 라벨·메타 langfuse.trace.tags, langfuse.*.metadata.*
Trace (한 에이전트 실행)              session.id=chat-42, user.id=u_777
└─ Span: agent.run                   (Observation: Span)
   ├─ Generation: plan (gpt-4o)      (Observation: Generation) ★ model 있음
   ├─ Span: tool.web_search          (Observation: Span)
   ├─ Span: tool.db_query            (Observation: Span)
   └─ Generation: final-answer       (Observation: Generation) ★

3. Langfuse를 OTLP 백엔드로 쓰기 (엔드포인트·인증)

가장 강력한 사용법: 이미 OTel로 계측된 앱의 텔레메트리를 Langfuse OTLP 엔드포인트로 그대로 전송. 코드 변경 없이 환경변수만으로 가능하다.

3.1 OTLP 엔드포인트

리전 / 환경 엔드포인트
EU (기본) https://cloud.langfuse.com/api/public/otel
US https://us.cloud.langfuse.com/api/public/otel
Japan https://jp.cloud.langfuse.com/api/public/otel
HIPAA https://hipaa.cloud.langfuse.com/api/public/otel
Self-hosted (v3.22.0+) http://<host>:3000/api/public/otel

컬렉터가 signal별 엔드포인트를 요구하면 traces 전용 경로를 쓴다: .../api/public/otel/v1/traces

⚠ 가장 흔한 함정: Langfuse OTLP는 HTTP(http/protobuf 또는 http/json)만 지원한다. gRPC(4317)는 지원하지 않는다. Exporter를 반드시 http/protobuf로 설정할 것. (일반 OTel 기본값이 gRPC인 경우가 많아 자주 막힌다.)

3.2 인증 — Basic Auth (public:secret → base64)

Langfuse 프로젝트의 Public Key(pk-lf-…)Secret Key(sk-lf-…)를 콜론으로 이어 base64 인코딩한다.

# AUTH_STRING 생성
echo -n "pk-lf-xxxx:sk-lf-xxxx" | base64 -w 0
# → 결과를 Authorization: Basic <...> 헤더로 전송

3.3 표준 OTel 환경변수 설정

export OTEL_EXPORTER_OTLP_ENDPOINT="https://us.cloud.langfuse.com/api/public/otel"
export OTEL_EXPORTER_OTLP_HEADERS="Authorization=Basic ${AUTH_STRING},x-langfuse-ingestion-version=4"
export OTEL_EXPORTER_OTLP_PROTOCOL="http/protobuf"   # ★ gRPC 금지

# signal별로 줘야 하는 컬렉터라면:
export OTEL_EXPORTER_OTLP_TRACES_ENDPOINT="https://us.cloud.langfuse.com/api/public/otel/v1/traces"
export OTEL_EXPORTER_OTLP_TRACES_HEADERS="Authorization=Basic ${AUTH_STRING},x-langfuse-ingestion-version=4"

이렇게 두면 OTel 자동계측(opentelemetry-instrument)이든 OpenLLMetry든, 데이터가 Langfuse로 흘러 들어간다.


4. GenAI Semantic Conventions (gen_ai.*) 매핑

OTel 진영의 LLM 표준 속성 네임스페이스가 gen_ai.*다. Langfuse는 이 표준 속성을 자동으로 자기 필드로 해석한다. 표준만 잘 따르면 별도 매핑 코드가 필요 없다.

OTel 속성 (gen_ai.*) Langfuse 필드
gen_ai.request.model / gen_ai.response.model 모델명 (model)
gen_ai.system 제공자/시스템 (openai, anthropic …)
gen_ai.usage.input_tokens (등 gen_ai.usage.*) 토큰 사용량 (usage)
gen_ai.prompt Observation 입력 (input)
gen_ai.completion Observation 출력 (output)
gen_ai.request.temperature, ...max_tokens 모델 파라미터 (modelParameters)

입력/출력 해석 우선순위 (Generation 기준):

  1. langfuse.observation.input / ...output (Langfuse 전용, 최우선)
  2. gen_ai.prompt / gen_ai.completion (OTel GenAI 표준)
  3. input.value / output.value (OpenInference 표준)

5. Langfuse 전용 속성 (langfuse.*) 매핑

표준만으로 표현 안 되는 Langfuse 고유 필드(세션·유저·비용 등)는 langfuse.* 속성으로 직접 지정한다. 이 속성들이 최우선으로 적용된다.

5.1 Trace 레벨

속성 매핑
langfuse.user.id userId
langfuse.session.id sessionId (대화 묶기)
langfuse.trace.name trace 이름
langfuse.trace.tags tags 배열
langfuse.trace.metadata.* trace 최상위 metadata 키
langfuse.release 릴리스 버전

5.2 Observation 레벨

속성 매핑
langfuse.observation.type 관측 타입 명시 지정 (generation/span/event)
langfuse.observation.model.name 모델명
langfuse.observation.model.parameters 모델 파라미터 (JSON 문자열)
langfuse.observation.usage_details 토큰 사용량 (JSON 문자열)
langfuse.observation.cost_details 비용 (JSON 문자열)
langfuse.observation.input / .output 입력/출력
langfuse.observation.metadata.* 필터 가능한 metadata

실전 팁: 직접 OTel span을 만들 때 위 속성을 set 하면 Langfuse가 알아서 예쁘게 해석한다. 예: 토큰/비용을 정확히 넣고 싶으면 langfuse.observation.usage_details / cost_details에 JSON 문자열로 set.


6. Span이 “Generation”이 되는 규칙

Langfuse가 어떤 span을 일반 Span으로 볼지 Generation(LLM 호출)으로 볼지 결정하는 규칙은 단순하다.

핵심 규칙: span에 model 속성(또는 gen_ai.request.model / langfuse.observation.model.name)이 있으면 → 자동으로 Generation으로 분류된다. 명시하려면 langfuse.observation.type=generation을 직접 지정.

왜 중요한가? Generation으로 분류돼야 토큰 집계·비용 계산·모델별 통계 같은 LLM 전용 분석이 동작한다. 도구 호출(web_search, db_query)은 model 속성이 없으니 일반 Span으로 남는다 — 이게 정상이다.


7. 계측 방법 4가지

Langfuse로 데이터를 보내는 길은 여러 갈래지만, 전부 결국 OTel span을 만들어 OTLP로 보낸다는 점에서 동일하다.

방법 성격 언제
Langfuse SDK (v3/v4) OTel 기반 네이티브 SDK. @observe·context manager 가장 직접적·풍부한 제어. 권장 기본
OpenLLMetry (Traceloop) 인기 LLM 라이브러리 자동계측 → gen_ai.* 방출 OpenAI/Anthropic SDK 등 자동 추적
OpenInference (Arize) 또 다른 GenAI 자동계측 표준 (input.value 등) LlamaIndex/LangChain 생태계
OpenLit OTel 기반 GenAI/GPU 계측 경량 자동계측 + GPU 메트릭

이들은 섞어 쓸 수 있다. 같은 OTel Context를 공유하므로, 자동계측이 만든 span과 Langfuse SDK로 만든 span이 한 트레이스로 자연스럽게 합쳐진다. 프레임워크 레벨(CrewAI, AutoGen, Semantic Kernel, Pydantic AI, LlamaIndex, Hugging Face smolagents 등)도 OTel을 내보내면 그대로 들어온다.


8. 에이전트(Agent) 모니터링

에이전트는 여러 LLM 호출 + 여러 도구 호출 + 반복 루프로 구성된다. 평면 로그로는 디버깅이 불가능하고, 중첩 트레이스가 필수다. 이것이 Langfuse가 빛나는 지점.

8.1 에이전트 트레이스의 전형적 구조

Trace: agent-run                       session.id=conv-1, user.id=u_42
└─ Span: agent.executor                (전체 에이전트 루프)
   ├─ Generation: reasoning-step-1      gpt-4o, 320 tok   ★
   │   └─ (LLM이 tool 호출 결정)
   ├─ Span: tool.retriever              vector search 80ms
   ├─ Generation: reasoning-step-2      gpt-4o, 510 tok   ★
   ├─ Span: tool.calculator
   └─ Generation: final-answer          gpt-4o, 240 tok   ★

8.2 에이전트에서 OTel이 해결해주는 것

문제 OTel/Langfuse가 주는 답
“몇 번째 스텝에서 폭주했나?” 중첩 span 트리로 루프 반복·깊이 가시화
“비용이 왜 폭발했나?” Generation별 토큰·비용 합산 → 트레이스 총비용
“어느 도구가 느린가?” SpanKind/도구 span의 duration 비교
“멀티 에이전트 간 호출이 끊긴다” W3C Trace Context 전파로 서비스·에이전트 경계 연결
“대화 전체 흐름을 보고 싶다” 여러 trace를 session.id로 묶어 세션 뷰

핵심: 에이전트 모니터링의 본질은 “LLM 호출 = Generation, 도구/단계 = Span, 한 실행 = Trace, 대화 = Session”으로 OTel 트리를 구성하는 것이다. 프레임워크 자동계측이 이걸 대부분 자동으로 해준다.


9. Python 실전 예제

9.1 방법 A — 순수 OTel 앱을 Langfuse로 (코드 변경 0)

export OTEL_EXPORTER_OTLP_ENDPOINT="https://us.cloud.langfuse.com/api/public/otel"
export OTEL_EXPORTER_OTLP_HEADERS="Authorization=Basic ${AUTH_STRING}"
export OTEL_EXPORTER_OTLP_PROTOCOL="http/protobuf"     # gRPC 아님
export OTEL_SERVICE_NAME="my-agent"

opentelemetry-instrument python agent.py
# 기존 gen_ai.* 계측이 그대로 Langfuse에 트레이스로 표시됨

9.2 방법 B — Langfuse SDK (OTel 기반, v4)

pip install langfuse

# 환경변수
#   LANGFUSE_PUBLIC_KEY=pk-lf-...
#   LANGFUSE_SECRET_KEY=sk-lf-...
#   LANGFUSE_BASE_URL=https://us.cloud.langfuse.com

from langfuse import get_client

langfuse = get_client()      # OTel TracerProvider를 내부 구성

# (1) 데코레이터 — 함수 전체를 하나의 observation으로
from langfuse import observe

@observe()
def handle(question: str):
    ...

# (2) context manager — Generation(LLM 호출) 명시
with langfuse.start_as_current_observation(
        as_type="generation",
        name="final-answer",
        model="claude-opus-4-8",
        input={"question": question},
        model_parameters={"temperature": 0.2},
) as gen:
    answer = call_llm(question)
    gen.update(
        output=answer,
        usage_details={"input": 320, "output": 240},  # 토큰
    )

# (3) trace 레벨 속성 (세션/유저/태그)
langfuse.update_current_trace(
    user_id="u_42",
    session_id="conv-1",
    tags=["prod", "agent"],
)

9.3 방법 C — 자동계측 + 수동 혼합

# OpenLLMetry(Traceloop)가 OpenAI/Anthropic 호출을 자동으로 Generation으로 추적.
# 그 위에 Langfuse SDK로 비즈니스 span을 덧씌우면 한 트레이스로 합쳐진다.
from traceloop.sdk import Traceloop
Traceloop.init()      # OTLP_ENDPOINT/HEADERS를 Langfuse로 향하게 설정해두면 됨

with langfuse.start_as_current_observation(as_type="span", name="agent.executor"):
    run_agent()       # 내부 LLM 호출들은 자동계측이 Generation으로 잡음

10. Collector를 통한 라우팅과 주의점

앱 → OTel Collector → Langfuse 구조도 가능하다. 한 파이프라인에서 Langfuse(LLM 트레이스)와 Tempo/Prometheus(인프라)로 동시에 분기할 수 있다.

exporters:
  otlphttp/langfuse:
    endpoint: https://us.cloud.langfuse.com/api/public/otel   # HTTP!
    headers:
      Authorization: "Basic ${AUTH_STRING}"

service:
  pipelines:
    traces:
      receivers:  [otlp]
      processors: [memory_limiter, batch]
      exporters:  [otlphttp/langfuse]      # gRPC otlp exporter 아님 주의

주의: Collector의 filterprocessor로 LLM span만 골라 Langfuse로 보내는 식의 선별 라우팅이 가능하지만, 규칙이 복잡해지면 트레이스가 잘려(incomplete trace) 부모-자식 관계가 깨질 수 있다. 한 트레이스는 통째로 보내는 걸 권장.


11. 운영 베스트 프랙티스 / 함정

  1. gRPC 금지 → http/protobuf. Langfuse OTLP는 HTTP만. 가장 흔한 연동 실패 원인.
  2. Generation 분류는 model 속성에 달렸다. 토큰/비용 통계가 안 잡히면 model 속성 누락을 의심.
  3. session.id / user.id를 꼭 넣어라. 대화 단위 분석·유저별 비용 추적의 전제.
  4. 토큰·비용은 usage_details / cost_details로 정확히. 자동 추정에 의존하지 말고 SDK 응답의 실제 usage를 기록.
  5. 프롬프트에 PII가 담긴다. input/output이 그대로 저장되므로 마스킹 정책(Collector attributes processor 또는 SDK 마스킹) 검토.
  6. 표준 시맨틱을 우선하라. gen_ai.* 표준을 따르면 벤더·도구를 바꿔도 호환. Langfuse 전용 속성은 보강용으로.
  7. 샘플링은 신중히. 비용 모니터링이 목적이면 LLM 트레이스는 100% 수집이 보통(개별 호출이 곧 돈). 대신 보존기간/배치로 관리.
  8. Score(평가)는 별도. trace/observation 생성 후 eval 결과를 Score로 부착해 품질을 시계열로 추적.

12. 순수 OTel 백엔드 대비 Langfuse의 차이

항목 일반 OTel 백엔드 (Tempo/Jaeger) Langfuse
1급 개념 Trace/Span (도메인 무관) Generation·토큰·비용·프롬프트·세션·Score
토큰/비용 그냥 속성. 집계 안 됨 자동 집계·모델 단가 기반 비용 계산
프롬프트 뷰 없음 입력/출력 렌더링, 프롬프트 관리·버전
품질 평가 없음 Score / eval / 사람 피드백
수집 방식 OTLP (gRPC/HTTP) OTLP HTTP만
적합 대상 인프라·마이크로서비스 LLM 앱·에이전트

결론: 인프라 관측은 Tempo/Prometheus, LLM·에이전트 관측은 Langfuse — 둘 다 OTel을 공통 기반으로 쓰므로 Collector에서 분기해 동시에 운용하는 것이 정석이다.


13. 용어 사전 & 링크

용어
Observation Langfuse의 작업 단위 = OTel Span. Span/Generation/Event 3종
Generation LLM 호출 observation. model 속성이 있으면 자동 분류
Session 여러 trace를 한 대화로 묶음 (langfuse.session.id)
Score 품질 평가 점수 (eval/사람 피드백)
gen_ai.* OTel GenAI 시맨틱 규약 속성 네임스페이스
OpenLLMetry Traceloop의 LLM 자동계측 (OTel 기반)
OpenInference Arize의 GenAI 계측 표준 (input.value 등)
usage_details / cost_details 토큰·비용을 Langfuse에 명시하는 속성/필드

참고 링크

Scroll to Top