이 문서는 Langfuse를 “OpenTelemetry(OTel) 관점”에서 정리한 교재형 자료다. LLM·에이전트 모니터링에 필요한 OTel 연관 부분만 깊게 다룬다. OTel 기본 개념(Trace/Span/Collector/OTLP)은 Learn → Monitoring → OpenTelemetry 문서를 먼저 읽고 오면 좋다.
0. 목차
- Langfuse란? 왜 OTel과 엮이나
- Langfuse 데이터 모델 ↔ OTel 매핑
- Langfuse를 OTLP 백엔드로 쓰기 (엔드포인트·인증)
- GenAI Semantic Conventions (
gen_ai.*) 매핑 - Langfuse 전용 속성 (
langfuse.*) 매핑 - Span이 “Generation”이 되는 규칙
- 계측 방법 4가지 (SDK · OpenLLMetry · OpenInference · OpenLit)
- 에이전트(Agent) 모니터링
- Python 실전 예제
- Collector를 통한 라우팅과 주의점
- 운영 베스트 프랙티스 / 함정
- 순수 OTel 백엔드 대비 Langfuse의 차이
- 용어 사전 & 링크
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 기준):
langfuse.observation.input/...output(Langfuse 전용, 최우선)gen_ai.prompt/gen_ai.completion(OTel GenAI 표준)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. 운영 베스트 프랙티스 / 함정
- gRPC 금지 →
http/protobuf. Langfuse OTLP는 HTTP만. 가장 흔한 연동 실패 원인. - Generation 분류는
model속성에 달렸다. 토큰/비용 통계가 안 잡히면 model 속성 누락을 의심. session.id/user.id를 꼭 넣어라. 대화 단위 분석·유저별 비용 추적의 전제.- 토큰·비용은
usage_details/cost_details로 정확히. 자동 추정에 의존하지 말고 SDK 응답의 실제 usage를 기록. - 프롬프트에 PII가 담긴다. input/output이 그대로 저장되므로 마스킹 정책(Collector
attributesprocessor 또는 SDK 마스킹) 검토. - 표준 시맨틱을 우선하라.
gen_ai.*표준을 따르면 벤더·도구를 바꿔도 호환. Langfuse 전용 속성은 보강용으로. - 샘플링은 신중히. 비용 모니터링이 목적이면 LLM 트레이스는 100% 수집이 보통(개별 호출이 곧 돈). 대신 보존기간/배치로 관리.
- 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에 명시하는 속성/필드 |
참고 링크
- Langfuse OTel 시작: langfuse.com/docs/opentelemetry/get-started
- Python SDK: langfuse.com/docs/sdk/python
- OTel GenAI 시맨틱 규약: opentelemetry.io/docs/specs/semconv/gen-ai
- 선행 학습: Learn → Monitoring → OpenTelemetry