LLM 관측가능성(observability)은 프롬프트, 모델 호출, 도구 실행, 토큰 비용, 응답 품질을 하나의 트레이스로 묶어 프로덕션에서 벌어지는 일을 재현 가능하게 만드는 실천입니다. 2026년 기준으로 Langfuse, LangSmith, Arize Phoenix, Helicone 네 도구가 시장을 나눠 갖고 있고, 그 위에 OpenTelemetry GenAI Semantic Conventions가 벤더 중립 표준으로 자리 잡았습니다. 저는 지난 18개월 동안 세 개의 에이전트 시스템을 프로덕션에 올리면서 이 네 도구를 전부 실전에 써봤어요. 아래는 도구 선택, 코드 통합, 평가 파이프라인, 비용 추적까지 실제로 필요한 것만 정리한 가이드입니다.
LLM 관측가능성은 지연·에러율만 보는 전통 APM으로는 잡히지 않는 환각·리트리벌 실패·프롬프트 회귀·툴콜 재시도 루프를 잡아냅니다.
2026년 도구 선택 기준은 프레임워크 종속성, 셀프호스팅 가능 여부, 평가 기능의 깊이, 그리고 프록시 방식 대 SDK 방식입니다.
OpenTelemetry GenAI Semantic Conventions(v1.41 기준)는 여전히 experimental이지만 벤더 락인을 피하려면 지금부터 이 규약으로 계측해야 합니다.
LLM-as-a-Judge 평가는 매 요청마다 돌리지 말고 샘플링·비동기 큐·회귀 감지에 집중해야 비용과 지연이 통제됩니다.
프로덕션에서는 세션 ID 전파, PII 마스킹, 프롬프트 버전 태깅, 알림 임계값 정의가 도구 선택보다 먼저 결정되어야 합니다.
LLM 관측가능성이란 무엇인가?
LLM 관측가능성은 프롬프트 입력부터 도구 호출, 임베딩 검색, 최종 응답까지의 실행 경로를 하나의 트레이스 트리로 기록하고, 각 스팬에 지연·토큰·비용·평가 점수를 붙여 사후 재현 및 회귀 감지가 가능하게 만드는 실천입니다. 마이크로서비스의 분산 추적 개념을 그대로 이식했지만, 다루는 축이 좀 다릅니다. 전통 추적은 "왜 느린가"에 답합니다. LLM 추적은 "왜 이런 답을 골랐고, 다음 번엔 어떻게 하면 이걸 안 만들 수 있는가"에 답해야 합니다.
평가: 오프라인 데이터셋 회귀와 온라인 LLM-as-a-Judge, 사용자 피드백(👍/👎)을 결합합니다.
프롬프트 관리: 프롬프트에 버전을 붙여 A/B 테스트와 릴리스별 성능 회귀를 추적합니다.
솔직히 저는 이걸 프롬프트 극장(prompt theatre)의 대척점으로 봅니다. 대시보드 없이 프롬프트를 만지는 건, 프로파일러 없이 성능을 튜닝하는 것과 같아요. 뭐가 좋아졌는지 자기 자신을 속이기 딱 좋습니다. 컨텍스트 설계 자체가 잘 되어 있어야 한다는 전제는 AI 에이전트를 위한 컨텍스트 엔지니어링 실전 가이드에서 정리했으니, 여기서는 그 위에 올라갈 관측 레이어에 집중합니다.
왜 기존 APM으로는 부족한가?
기존 APM(Datadog, New Relic, Sentry)은 HTTP 200에 지연 1.2초를 정상으로 봅니다. 문제는 LLM에서 그 "정상 응답"이 사실을 완전히 지어냈을 수 있다는 겁니다. 응답 품질은 인프라 지표와 상관관계가 낮습니다. 다음은 제가 최근 6개월 동안 관측 도구가 없었으면 놓쳤을 실제 장애 유형들입니다.
1. 툴콜 재시도 루프
에이전트가 잘못된 JSON을 반환한 도구를 파싱 실패로 재호출하고, 그 결과를 다시 넣어 요약하고, 다시 도구를 부르는 무한 반복. 최종 응답은 정상이지만 하나의 세션이 380초에 47달러를 태웠습니다. APM은 "200 OK, 6분"으로 봅니다. 저는 이 버그를 프로덕션 배포 이틀 뒤 청구 알림으로 처음 알았어요.
2. 리트리벌 무응답
벡터 검색이 임계값 미만의 유사도 문서만 반환해 컨텍스트가 비어 있는데도, LLM은 태연하게 답을 지어냈습니다. 응답률 100%, 사용자 신뢰도 0%. 리트리벌 스팬을 트레이스에 넣지 않으면 이건 사후 재현이 불가능합니다.
3. 프롬프트 회귀
시스템 프롬프트에 "간결하게 답하세요" 한 줄을 추가했더니 다음 릴리스에서 특정 카테고리 질문의 정확도가 12% 떨어졌습니다. 프롬프트 버전을 태깅해 두지 않으면 원인 커밋을 찾는 데 며칠이 갑니다.
4. 프로바이더 드리프트
같은 gpt-4o도 특정 주에 응답 스타일이 달라졌습니다. 프로바이더는 사일런트 업데이트를 자주 합니다. 응답 분포와 평가 점수를 시계열로 보고 있어야 이걸 잡습니다.
5. 비용 폭주
새 사용자가 30MB PDF를 붙여 넣는 순간 컨텍스트가 폭발합니다. 세션별 토큰 상한과 실시간 알림이 없으면 청구서로 알게 됩니다.
APM은 이 다섯 가지 중 어느 것도 잡지 못합니다. 그렇다고 APM을 대체하라는 얘기는 아닙니다. LLM 관측 도구는 APM 옆에 하나 더 서는 거예요. 인프라 지표는 여전히 APM으로, LLM 실행 트리는 전용 도구로 봐야 합니다.
2026년 LLM 관측가능성 도구 비교: Langfuse vs LangSmith vs Phoenix vs Helicone
2026년 상반기 기준으로 네 도구가 안정적인 선택지입니다. 아래 표는 실제로 도입을 결정할 때 봐야 하는 축들만 뽑은 것입니다.
비교 항목
Langfuse
LangSmith
Arize Phoenix
Helicone
라이선스
MIT (오픈소스)
상용
Elastic 2.0
Apache 2.0
셀프호스팅
가능 (무제한 무료)
엔터프라이즈만
가능 (in-process 지원)
가능
통합 방식
SDK + OpenTelemetry
SDK (LangChain 네이티브)
OTel 우선
프록시 (base_url 교체)
설치 난이도
중 (약 30분)
낮음 (LangChain 사용 시 자동)
매우 낮음 (노트북에서 실행)
가장 낮음 (5분)
프롬프트 버전 관리
있음
있음 (LangChain Hub)
제한적
없음
LLM-as-a-Judge
내장
내장
내장 (평가 특화)
제한적
RAG 디버깅
중
중
강 (임베딩 드리프트)
약
클라우드 가격 (2026-07)
Hobby 무료 / Pro 월 $59
시트 $39 + 1k 트레이스당 $0.50
OSS 무료 / AX 커스텀
월 10K 요청 무료 / $79부터
Langfuse: 데이터 소유권과 기능 완결성
2026년 1월 Clickhouse에 인수됐지만, 오픈소스 코드는 여전히 활발히 유지되고 MIT 라이선스도 그대로입니다. v4에서 OpenTelemetry 기반 트레이싱이 들어오면서 로컬 LLM(vLLM, llama.cpp)도 몽키패치 없이 계측할 수 있게 됐어요. 트레이스·프롬프트 버전·데이터셋·인간 평가·LLM-as-a-Judge까지 한 도구에 다 있고, 데이터가 자기 인프라에 남는다는 게 규제 산업(금융·헬스케어)에서 결정적입니다.
LangSmith: LangGraph 트레이스 시각화
LangChain / LangGraph 팀이라면 사실상 기본값입니다. LangGraph 상태 머신 다이어그램이 트레이스 뷰에 그대로 렌더링되는 건 어디에도 없는 기능이에요. 단점은 벤더 락인입니다. LangChain 밖으로 나가면 매력이 급감하고, 자체 호스팅은 엔터프라이즈 계약이 필요합니다. 오케스트레이션 도구 선택 자체가 아직 열려 있다면 2026년 멀티에이전트 오케스트레이션 완벽 가이드를 먼저 보고 결정하는 걸 권합니다.
Arize Phoenix: RAG와 평가 특화
Phoenix는 in-process로 실행됩니다. Jupyter 셀에서 px.launch_app() 한 줄로 로컬 UI가 뜨고, 계정도 클라우드도 필요 없어요. 임베딩 드리프트 감지, 문서 귀속(document attribution), 리트리벌 관련성 스코어링에서 다른 도구보다 통계적으로 엄격합니다. RAG가 주력이고 평가 지표를 실험 로그처럼 다루고 싶다면 Phoenix가 정답입니다. 관련 문서와 예제는 Arize Phoenix 공식 문서에서 확인할 수 있어요.
Helicone: 5분 설치, 게이트웨이 방식
Base URL만 https://oai.helicone.ai/v1로 바꾸고 헤더를 하나 추가하면 끝. 코드에 SDK를 심을 필요가 없어서 이미 완성된 시스템에 관측을 얹기 가장 빠릅니다. 단점은 스팬 깊이가 API 호출 레벨에 머물러 에이전트 실행 트리를 재구성하기 어렵다는 것. 저는 초기 프로토타입 단계나 비용 대시보드 용도로 Helicone을 쓰고, 프로덕션에서는 Langfuse나 Phoenix로 이전하는 패턴을 씁니다.
어떤 도구를 골라야 하나?
LangChain / LangGraph 기반이라면 LangSmith. 대안 없이 가장 편합니다.
규제 산업, 데이터 소유권이 필수라면 Langfuse 셀프호스팅.
RAG가 핵심, 임베딩 드리프트를 잡고 싶다면 Phoenix.
이미 배포된 시스템에 오늘 안에 붙이고 싶다면 Helicone.
멀티 프레임워크 / 벤더 중립이면 OpenTelemetry + Phoenix 또는 Langfuse.
OpenTelemetry GenAI Semantic Conventions로 벤더 중립 계측하기
2026년의 큰 변화는 OpenTelemetry GenAI Semantic Conventions입니다. 2024년 4월 GenAI SIG가 시작한 이 규약은 LLM 호출·에이전트 스팬·툴 호출·벡터 DB 쿼리·토큰 사용·비용에 대한 속성 이름을 표준화합니다. 그 전에는 도구마다 필드 이름이 달랐어요. llm.prompt, ai.input, gen_ai.request.messages가 다 같은 뜻이었죠. 이제 하나로 통일됩니다.
2026년 5월 CNCF는 OpenTelemetry의 graduation을 발표했고, 지난 12개월 동안 JS 패키지가 13.6억, Python 패키지가 13억 다운로드를 넘겼습니다. Datadog은 v1.37 GenAI SemConv를 네이티브로 지원한다고 공식화했습니다. 즉, 벤더 락인을 피하고 싶다면 지금부터 이 규약으로 계측해야 합니다.
주요 span 속성 (v1.41)
gen_ai.system: openai, anthropic, google_genai 등
gen_ai.request.model: 요청 시 모델명 (예: gpt-4o-2024-11-20)
프롬프트·응답 본문은 별도 이벤트로 발행합니다. 페이로드 크기와 프라이버시 문제 때문에 기본은 opt-out이고, OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT=true로 켜야 기록됩니다.
OpenLLMetry 예시 (Python)
from openai import OpenAI
from traceloop.sdk import Traceloop
from traceloop.sdk.decorators import workflow, task
# 1) OTLP exporter 로 초기화. Langfuse / Phoenix / Datadog 어디로든 보낼 수 있음
Traceloop.init(
app_name="research-agent",
api_endpoint="https://cloud.langfuse.com/api/public/otel", # or Phoenix, Datadog
disable_batch=False,
)
client = OpenAI()
@task(name="summarize_document")
def summarize(doc: str) -> str:
resp = client.chat.completions.create(
model="gpt-4o-2024-11-20",
messages=[
{"role": "system", "content": "You are a concise summarizer."},
{"role": "user", "content": doc},
],
temperature=0.2,
)
return resp.choices[0].message.content
@workflow(name="research_pipeline")
def run(query: str, docs: list[str]) -> str:
summaries = [summarize(d) for d in docs]
# 이후 벡터 검색, 재랭킹, 최종 응답 생성...
return "\n".join(summaries)
이 코드는 어느 관측 도구에도 종속되지 않습니다. api_endpoint만 바꾸면 Langfuse에서 Phoenix로, 혹은 Datadog으로 라이브에서 옮길 수 있어요. 이게 벤더 중립 계측의 실질적 이득입니다.
실전: Langfuse SDK로 프로덕션 에이전트 트레이스 남기기
OpenTelemetry가 표준이 됐지만, 세션 단위 집계·프롬프트 버전 관리·평가 실행을 하나의 UI에서 하고 싶다면 여전히 SDK 방식이 편합니다. 아래는 Langfuse v3 Python SDK로 도구 호출을 하는 에이전트를 계측하는 예시예요.
import os
from anthropic import Anthropic
from langfuse import Langfuse
from langfuse.decorators import observe, langfuse_context
# 1) 초기화. SELF_HOSTED_URL 또는 cloud.langfuse.com
langfuse = Langfuse(
public_key=os.environ["LANGFUSE_PUBLIC_KEY"],
secret_key=os.environ["LANGFUSE_SECRET_KEY"],
host=os.environ.get("LANGFUSE_HOST", "https://cloud.langfuse.com"),
)
anthropic = Anthropic()
def search_docs(query: str) -> list[dict]:
"""벡터 DB 검색. 실제 구현은 pgvector, Qdrant, Pinecone 등."""
return [{"id": "doc-42", "text": "…", "score": 0.83}]
@observe(as_type="generation", name="claude_call")
def claude(messages, tools=None):
resp = anthropic.messages.create(
model="claude-opus-4-8",
max_tokens=1024,
messages=messages,
tools=tools or [],
)
# 토큰 / 모델 메타데이터를 스팬에 붙임
langfuse_context.update_current_observation(
model=resp.model,
usage={
"input": resp.usage.input_tokens,
"output": resp.usage.output_tokens,
},
metadata={"stop_reason": resp.stop_reason},
)
return resp
@observe(name="agent_turn")
def agent_turn(user_query: str, session_id: str, user_id: str):
# 세션 / 사용자 태깅 → 대시보드에서 그룹핑에 필수
langfuse_context.update_current_trace(
session_id=session_id,
user_id=user_id,
tags=["prod", "research-agent", "v2026.07"],
)
tools = [{
"name": "search_docs",
"description": "회사 위키 벡터 검색",
"input_schema": {"type": "object", "properties": {"query": {"type": "string"}}},
}]
messages = [{"role": "user", "content": user_query}]
for step in range(5): # 5턴 상한, 재시도 루프 방지
resp = claude(messages, tools=tools)
if resp.stop_reason == "end_turn":
return resp.content[0].text
# 툴 사용 처리
for block in resp.content:
if block.type == "tool_use" and block.name == "search_docs":
results = search_docs(block.input["query"])
messages.append({"role": "assistant", "content": resp.content})
messages.append({
"role": "user",
"content": [{
"type": "tool_result",
"tool_use_id": block.id,
"content": str(results),
}],
})
raise RuntimeError("Agent hit max iterations")
if __name__ == "__main__":
answer = agent_turn(
"우리 API의 인증 방식 요약해줘",
session_id="sess_abc123",
user_id="user_42",
)
print(answer)
langfuse.flush() # 프로세스 종료 전 배치 전송
여기서 실전에 중요한 두 가지가 있습니다.
session_id와 user_id는 반드시 첫 스팬에 붙입니다. 나중에 붙이면 UI에서 그룹핑이 안 되고, 문제 재현할 때 사용자별로 필터하는 게 안 됩니다.
루프 상한은 코드에 있어야지 대시보드에서 잡으려 하면 늦습니다. 저는 위에서처럼 range(5)로 강제하고, 그 이상은 예외로 던져 관측 도구에 error 스팬을 남깁니다.
스트리밍 응답을 다루거나 프롬프트에 캐시 브레이크포인트를 얹는 방법은 LLM 프롬프트 캐싱 완벽 가이드에 정리돼 있습니다. 캐시 히트가 트레이스에 명시적으로 찍혀야 비용 대시보드가 의미를 갖습니다.
LLM-as-a-Judge 평가 파이프라인 설계
LLM-as-a-Judge는 대상 응답을 다른 LLM이 채점하게 하는 기법입니다. 사람 라벨링 대비 100~1000배 싸지만 세 가지 함정이 있어요.
매 요청마다 돌리면 비용이 두 배가 됩니다. 저는 프로덕션 트래픽의 3~5%만 샘플링하고, 오류 스팬·낮은 사용자 피드백·프롬프트 버전 변경 시점만 100% 커버합니다.
편향이 존재합니다. 같은 프로바이더 모델이 자기 계열 응답을 후하게 채점합니다. 심사 모델은 대상 모델과 다른 프로바이더로 쓰세요 (예: 대상은 GPT-4o, 심사는 Claude).
일관성 없는 rubric은 판사도 헷갈립니다. 0~5점 척도보다 이진(pass/fail) + 근거 문장이 훨씬 안정적입니다.
Langfuse에서 온라인 평가 훅 걸기
from langfuse import Langfuse
from anthropic import Anthropic
import random, json
langfuse = Langfuse()
judge = Anthropic()
JUDGE_PROMPT = """You are a strict quality auditor.
Given the user question and the assistant's answer, decide:
1) grounded: does every claim in the answer appear in the retrieved context?
2) refusal_appropriate: if the answer refuses, is refusal warranted?
Return JSON with keys: grounded (bool), refusal_appropriate (bool), reason (short).
Question: {question}
Retrieved context: {context}
Answer: {answer}
"""
def score_trace(trace_id: str, question: str, context: str, answer: str):
# 트래픽의 5% 만 판단
if random.random() > 0.05:
return
resp = judge.messages.create(
model="claude-opus-4-8",
max_tokens=256,
messages=[{"role": "user", "content": JUDGE_PROMPT.format(
question=question, context=context, answer=answer)}],
)
verdict = json.loads(resp.content[0].text)
langfuse.score(
trace_id=trace_id,
name="groundedness",
value=1 if verdict["grounded"] else 0,
comment=verdict["reason"],
)
이걸 응답 반환 후 비동기 큐(Redis, SQS)에 던져 별도 워커에서 실행하세요. 사용자 지연에 영향을 주면 안 됩니다. Confident-AI의 DeepEval 문서와 Arize의 Phoenix 평가 라이브러리도 사실상 같은 패턴을 씁니다. 세부 rubric만 다릅니다.
토큰 비용과 캐시 히트율 추적
비용 관측의 최소 요구사항은 세 축의 집계입니다. 사용자별, 세션별, 기능별. 여기서 "기능별"이 놓치기 쉬운데, 요약·질문응답·문서 생성 같은 논리적 워크로드를 태그로 붙여야 나중에 "요약 기능이 비용의 68%를 차지하네"라는 통찰이 나옵니다.
여기서 cache_read_input_tokens는 Anthropic 프롬프트 캐시의 히트 토큰 수입니다. 이걸 명시적으로 메타데이터에 실어두면 대시보드에서 캐시 히트율 = read_input / (read_input + regular_input)로 그릴 수 있어요. 저는 이 지표가 90% 이상 유지되지 않으면 캐시 브레이크포인트를 잘못 잡은 걸로 봅니다.
롤백 경로: 프롬프트 버전만 이전 태그로 되돌리면 원복되는가? 모델 프로바이더 페일오버는 되는가?
데이터 보존: GDPR / 개인정보보호법을 고려해 트레이스 보존 기간과 user_id 삭제 API가 있는가?
이 열 개 중 절반이라도 통과 못 하면 도구를 바꾸는 것보다 프로세스를 먼저 고쳐야 합니다. 저는 항상 이 체크리스트를 붙여놓고 배포 전 리뷰합니다.
자주 묻는 질문
Langfuse와 LangSmith 중 어느 것이 나은가요?
LangChain / LangGraph 스택에 이미 있다면 LangSmith가 설치 마찰이 가장 낮고 트레이스 시각화도 최고 수준입니다. 셀프호스팅이 필요하거나, 프레임워크에 종속되지 않고 데이터 소유권이 중요하다면 Langfuse가 맞습니다. 두 도구 모두 LLM-as-a-Judge와 프롬프트 버전 관리를 지원하므로 결정 기준은 프레임워크 종속성과 배포 모델입니다.
OpenTelemetry로 LLM 애플리케이션을 모니터링할 수 있나요?
가능합니다. 2026년 5월 기준 OpenTelemetry GenAI Semantic Conventions는 v1.41까지 진행됐고, 에이전트·워크플로우·툴·모델 스팬과 지연·토큰 사용 메트릭을 커버합니다. Langfuse, Phoenix, Datadog은 모두 OTLP 엔드포인트로 GenAI SemConv 스팬을 받을 수 있어 api_endpoint만 바꿔 관측 백엔드를 옮길 수 있습니다. 단, 규약은 여전히 experimental 상태이므로 OTEL_SEMCONV_STABILITY_OPT_IN을 명시하세요.
LLM-as-a-Judge 평가는 매 요청마다 돌려야 하나요?
아니요. 매 요청 평가는 비용을 두 배로 만듭니다. 프로덕션 트래픽의 3~5%를 확률 샘플링하고, 사용자가 부정 피드백을 준 요청·프롬프트 버전 배포 직후·오류 스팬은 100% 커버하는 하이브리드가 표준입니다. 심사 모델은 반드시 대상 모델과 다른 프로바이더로 골라 자기 편향을 줄이세요.
Helicone은 프로덕션에서 충분한가요?
단순 챗봇이나 프롬프트 단일 호출이 주라면 충분합니다. 비용 대시보드와 API 호출 로그만으로 대부분의 문제가 잡힙니다. 하지만 에이전트가 여러 툴을 부르고 그래프를 그리는 구조라면 API 호출 레벨 관측은 얕습니다. 이 경우 Helicone(비용·라우팅) + Langfuse / Phoenix(트리 관측·평가) 이중 스택이 실무에서 자주 보입니다.
프롬프트와 응답 본문을 관측 도구에 저장해도 되나요?
규제와 사용자 계약을 먼저 확인해야 합니다. 저장한다면 SDK 앞단에 정규식 기반 PII 마스킹 계층을 두고, 관측 도구의 데이터 보존 기간과 삭제 API를 파악해 두세요. OpenTelemetry GenAI 규약도 프롬프트 / 응답 캡처를 기본 opt-out으로 정의합니다. 헬스케어 / 금융에서는 셀프호스팅 옵션(Langfuse OSS, Phoenix)을 강력 권장합니다.
Arize Phoenix는 언제 쓰는 게 좋나요?
RAG가 시스템의 심장이라 리트리벌 품질·임베딩 드리프트·문서 귀속을 정량적으로 봐야 하는 팀에 맞습니다. Phoenix는 in-process로 노트북에서 바로 뜨고 계정도 필요 없어 로컬 디버깅 도구로도 좋습니다. 반면 프롬프트 버전 관리·팀 협업 UI는 Langfuse나 LangSmith가 더 성숙하므로, RAG 파이프라인만 Phoenix로 쓰고 에이전트 트레이스는 다른 도구로 이중화하는 조합도 흔합니다.