Langfuse vs Phoenix vs Opik: 세 가지 AI 에이전트 관측성 (Observability) 도구 비교
요약
AI 에이전트의 운영 및 디버깅을 위한 관측성(Observability) 도구인 Langfuse, Phoenix, Opik을 비교 분석합니다. 동일한 에이전트 시나리오에 실패를 주입하여 각 플랫폼의 원인 분석 능력과 트레이드오프를 검증하는 실습 가이드를 제공합니다.
핵심 포인트
- Langfuse, Phoenix, Opik의 기능적 차이와 선택 기준 제시
- 에이전트 실패 원인 파악을 위한 실무적인 비교 방법론
- 복잡한 실행 그래프를 가진 고객 지원 에이전트 사례 활용
- 도구 호출, 검색, 재시도 등 에이전트 특화 모니터링 검증
Langfuse vs Phoenix vs Opik: AI 에이전트 관측성 (Observability) 도구 비교 | Agent Lab Journal
Agent Lab Journal
Guides
...
에이전트 운영 · 중급
Langfuse vs Phoenix vs Opik: 세 가지 AI 에이전트 관측성 (Observability) 도구 비교
중급
120분 실습 가이드
반복 가능한 로컬 비교
기능 체크리스트만으로는 AI 에이전트 관측성 (Observability) 플랫폼이 실패한 단계를 밝혀낼 수 있는지, 지연 시간 급증 (latency spike)을 설명할 수 있는지, 또는 재시도 (retry) 비용을 계산할 수 있는지 알려주지 않습니다. 유용한 비교 방법은 동일하게 계측된 (instrumented) 에이전트를 Langfuse, Phoenix, 그리고 Opik에 통과시키고, 동일한 실패를 주입하여 각 시스템이 잘못된 출력에서 그 원인으로 얼마나 빠르게 안내하는지를 판단하는 것입니다.
목차
-
빠른 결정
-
에이전트 시나리오
-
공정한 비교 방법
-
공통 계측 (instrumentation) 규약
-
베이스라인 에이전트 구축
-
Langfuse 설정
-
Phoenix 설정
-
Opik 설정
-
실패 주입 훈련
-
비교 평가
-
검증 체크리스트
-
한계 및 마이그레이션 위험
-
선택 방법
빠른 결정
AgentOps는 프로토타입이 실제 호출을 시작한 이후 에이전트를 테스트, 모니터링, 디버깅 및 제어하기 위한 운영 규율입니다. 이러한 맥락에서 세 제품은 서로 겹치지만, 그 무게 중심은 다릅니다.
선택 기준
주요 요구 사항이
확인해야 할 주요 트레이드오프 (trade-off)
...
이것은 벤치마크 결과가 아닙니다. 워크로드 기반의 결정 규칙입니다. 가이드의 나머지 부분에서는 제품 매트릭스의 라벨을 신뢰하는 대신, 여러분의 스택을 위한 증거를 어떻게 생성하는지 보여줍니다.
구체적인 사례: 고객 지원 조사 에이전트
테스트 대상은 내부 제품에 대한 질문을 받고, 관련 노트를 검색(Retrieve)하며, 선택적으로 상태 도구(Status tool)를 호출한 뒤, LLM에게 최종 답변 작성을 요청하는 작은 지원 에이전트(Support agent)입니다. 이 에이전트는 단일 채팅 완성(Chat completion)보다는 의도적으로 더 복잡하게 설계되었지만, 오후 한나절 만에 재현할 수 있을 정도로 규모가 작습니다.
실행 그래프 (Execution graph)
-
분류 (Classify): 질문이 문서(Documentation)를 필요로 하는지, 실시간 상태(Live status)를 필요로 하는지, 혹은 둘 다 필요한지 결정합니다.
-
검색 (Retrieve): 최대 3개의 로컬 텍스트 구절을 찾습니다.
-
도구 호출 (Call a tool): 요청 시 결정론적인(Deterministic) JSON 상태 피스처(Fixture)를 읽습니다.
-
작성 (Compose): 검색된 컨텍스트(Context)로부터 간결한 응답을 생성합니다.
-
검증 (Validate): 빈 답변이나 필수 소스 식별자(Source identifier)가 누락된 답변을 거부합니다.
-
1회 재시도 (Retry once): 검증에 실패할 경우 최종 답변을 수정합니다.
이 에이전트는 중첩된 단계(Nested steps), 도구 호출(Tool call), 검색(Retrieval), 모델 사용(Model usage), 검증(Validation), 그리고 재시도(Retry) 동작을 제공합니다. 이는 특정 플랫폼이 단순히 최상위 요청(Top-level requests)을 기록하는 것을 넘어, 유용한 관측성 (Observability)을 제공하는지 테스트하기에 충분한 구성입니다.
모든 실행(Run)에서 반드시 기록해야 할 사항
필드 (Field)
예시 형태 (Example shape)
중요한 이유 (Why it matters)
...
트레이스(Trace)에 비밀 정보가 포함되지 않도록 하십시오. 이번 비교를 위해 합성 질문(Synthetic questions)과 로컬 피스처(Local fixtures)를 사용합니다. 액세스 토큰(Access tokens), 고객 메시지, 개인 문서, 또는 완전한 도구 자격 증명(Tool credentials)을 속성(Attributes)으로 전송하지 마십시오.
공정한 비교 방법
트레이스(Trace)는 하나의 완전한 에이전트 실행(Agent run)을 나타내야 합니다. 스팬(Span)은 해당 실행 내의 하나의 의미 있는 작업(Operation)을 나타내야 합니다. 세 가지 제품 모두에서 동일한 경계(Boundaries)를 사용하십시오:
agent.run
├── classify.intent
├── retrieve.context
...
한 플랫폼은 모든 내부 라이브러리 호출을 기록하는 반면, 다른 플랫폼은 루트 이벤트(Root event)만 수신하도록 두어서는 안 됩니다. 그렇게 되면 제품 자체가 아니라 계측(Instrumentation)의 깊이를 비교하게 될 것입니다.
평가 척도
이 기사 후반부에 나오는 점수들은 5점 척도의 루브릭(Rubric)을 사용합니다. 이는 워크플로 평가(Workflow assessments)이며, 측정된 성능 주장(Measured performance claims)이 아닙니다.
-
5 — direct (직접적): 추가 작업이 거의 없이 태스크를 확인하고 조치할 수 있음.
-
4 — good (양호): 태스크가 지원되지만 약간의 설정이나 탐색이 필요함.
-
3 — workable (작동 가능): 수동 관례나 추가 구성 요소를 통해 태스크 수행이 가능함.
-
2 — awkward (어색함): 정보는 존재하지만 인시던트(Incident)와 연결하기 어려움.
-
1 — unsuitable (부적합): 워크플로가 없거나 별도의 시스템이 필요함.
변수 제어하기 (Control the variables)
-
동일한 Python 환경, 에이전트 코드, 모델, 프롬프트(Prompts), 그리고 테스트 픽스처(Test fixtures)를 사용하세요.
-
프로바이더 클라이언트(Provider clients)의 자동 재시도(Automatic retries)를 비활성화하고, 에이전트의 단일 명시적 재시도만 유지하세요.
-
로컬 CPU 또는 메모리가 제한적인 경우, 한 번에 하나의 관측성(Observability) 백엔드만 실행하세요.
-
관측치를 기록하기 전에 에이전트를 한 번 예열(Warm)하세요.
-
서로 다른 네트워크 조건에서 실행된 런(Runs)을 마치 플랫폼 때문에 차이가 발생한 것처럼 비교하지 마세요.
-
플랫폼이 비용을 계산하도록 허용하기 전에 프로바이더의 원시 사용량(Raw provider usage)을 기록하세요.
-
모든 시나리오를 여러 번 반복하되, 집계 데이터(Aggregates)뿐만 아니라 개별 런(Individual runs)도 함께 보고하세요.
중요한 타이밍 측정 지표는 각 에이전트 단계에서의 지연 시간(Latency)입니다. 중요한 사용량 측정 지표는 프로바이더가 보고한 토큰 수(Token count)입니다. 통화 비용(Currency cost)은 파생 데이터이며, 가격표, 캐시된 입력(Cached-input) 규칙 또는 프로바이더의 과금 정책이 변경될 때 잘못될 수 있습니다.
먼저 공유된 인스트루멘테이션 계약(Instrumentation contract)을 정의하세요
에이전트 전반에 벤더 호출(Vendor calls)을 흩뿌리는 대신 작은 어댑터(Adapter)를 만드세요. 애플리케이션은 "실행 시작(Start run)", "단계 시작(Start step)", "사용량 기록(Record usage)", "에러 기록(Record error)"과 같은 작업을 표현해야 합니다. 각 백엔드 어댑터는 이러한 작업들을 해당 SDK로 매핑합니다.
from contextlib import contextmanager
from typing import Any, Iterator, Protocol
...
이 인터페이스는 의도적으로 작게 설계되었습니다. 이는 비교 작업이 세 개의 별개 에이전트 구현으로 변질되는 것을 방지하며, 나중에 특정 벤더의 SDK를 제거하는 것을 가능하게 합니다.
표준 속성 (Canonical attributes)
COMMON = {
"app.name": "support-research-agent",
"agent.version": "comparison-v1",
...
만약 OpenTelemetry를 통해서도 데이터를 내보낸다면, 위의 비즈니스 속성(business attributes)을 안정적으로 유지하고, 모델별 데이터는 설치된 인스트루멘테이션(instrumentation) 패키지에서 지원하는 시맨틱 컨벤션(semantic conventions)에 매핑하세요. 백엔드 간에 필드 이름을 임의로 변경하지 마십시오.
베이스라인 에이전트 구축하기
1. 격리된 환경 준비
python -m venv .venv
source .venv/bin/activate
python -m pip install --upgrade pip
...
결과를 공유하기 전에 해결된 패키지 버전을 고정(Pin)하세요:
python -m pip freeze > comparison-requirements.lock
SDK 인터페이스는 변경될 수 있습니다. 아래의 코드 스니펫이 설치된 패키지와 다르다면, 패키지 도움말이나 현재 SDK API를 확인하여 동일한 트레이스 계층 구조(trace hierarchy)와 속성을 유지하십시오.
2. 결정론적 로컬 픽스처(deterministic local fixtures) 사용
DOCUMENTS = [
{
"id": "doc-refunds",
...
비교를 위해 라이브 벡터 데이터베이스(vector database)를 사용하지 마세요. 단순한 키워드 검색기(keyword retriever)를 사용하면 인덱싱(indexing), 임베딩(embedding), 네트워크 변동성을 제거할 수 있습니다:
def retrieve(query: str, limit: int = 3) -> list[dict]:
terms = {word.strip(".,?!").lower() for word in query.split()}
ranked = []
...
3. 에이전트의 비즈니스 단계에 인스트루멘테이션 적용
def run_agent(question, scenario, telemetry, model_client):
run_id = new_run_id()
...
실제 모델 작업에도 인스트루멘테이션(instrumentation)을 적용하세요. 실제 구현에서는 compose_answer와 repair_answer가 각각 모델 식별 정보, 타이밍, 사용량, 그리고 안전하게 비식별화된(redacted) 입출력 요약을 포함하는 모델 스팬(model span)을 생성해야 합니다. 여기서는 비교 작업이 특정 모델 제공자에 종속되지 않도록 이를 축약하여 표현했습니다.
4. 로컬 실행 원장(run ledger) 유지
매 실행이 끝날 때마다 JSON 한 줄을 작성합니다. 이는 각 UI가 무엇을 보여주는지 검증하는 데 사용되는 독립적인 소스입니다:
{
"run_id": "generated-uuid",
"backend": "phoenix",
...
위의 0 및 null 값은 스키마 플레이스홀더(placeholder)이며, 관측값(observations)이 아닙니다. 실제 실행(run) 데이터로부터 해당 값을 채우십시오. 지속 시간(durations)에는 단조 시계(monotonic clock)를 사용하고, 타임스탬프(timestamps)에만 벽 시계(wall clock)를 사용하십시오.
Langfuse: 애플리케이션 중심의 트레이싱 (Tracing) 및 운영 (Operations)
Langfuse는 트레이싱(tracing)이 원하는 워크플로우의 일부일 뿐이며, 팀이 공유된 애플리케이션 내에서 프롬프트(prompts), 데이터셋(datasets), 평가(evaluations), 세션(sessions), 사용량(usage) 및 리뷰(review)를 함께 관리하고자 할 때 강력한 후보가 됩니다.
설정 (Configuration)
관리형 배포(managed deployment)의 경우, 환경 변수를 통해 공개 키(public key), 비밀 키(secret key), 호스트(host)를 제공하십시오. 셀프 호스팅(self-hosting)의 경우, 해당 배포에서 생성된 자격 증명을 사용하십시오. 파일은 버전 관리 시스템(version control) 외부에 유지하십시오:
LANGFUSE_PUBLIC_KEY=replace-with-local-or-project-key
LANGFUSE_SECRET_KEY=replace-with-local-or-project-secret
LANGFUSE_HOST=http://localhost:3000
일반적인 비밀 관리(secret-management) 방법을 사용하여 해당 값들을 로드하십시오. 소스 파일이나 트레이스 속성(trace attributes)에 직접 붙여넣지 마십시오.
어댑터 형태 (Adapter shape)
최근 SDK 세대들은 관측값(observations)과 컨텍스트 관리형 스팬(context-managed spans)을 지원합니다. 정확한 생성자(constructor) 및 업데이트 메서드(update method) 이름은 고정한 버전과 일치해야 하지만, 매핑은 다음과 같이 유지되어야 합니다:
from contextlib import contextmanager
from langfuse import get_client
...
수명이 짧은 테스트 프로세스가 종료되기 전에 flush()를 호출하십시오. 서버의 경우, 매 요청마다 동기적으로 flush를 수행하기보다 SDK의 라이프사이클(lifecycle) 가이드를 따르십시오.
점검 사항 (What to inspect)
- 루트
agent.run관측값(observation)을 열고 모든 하위 작업(child operations)이 해당 트레이스(trace)를 공유하는지 확인합니다. - 가장 긴 하위 단계(child step)를 찾아 그 지속 시간(duration)을 로컬 원장(local ledger)과 비교합니다.
- 모델 생성(model generation)을 열고 제공업체(provider)가 보고한 입력 및 출력 사용량(usage)이 존재하는지 확인합니다.
- 시나리오(scenario), 상태(status), 에이전트 버전(agent.version)별로 필터링합니다.
- 재시도(retry)가 첫 번째 시도에 병합되지 않고 별개의 모델 작업(model operation)으로 보이는지 확인합니다.
- 계산된 비용(cost)을 점검하고 이를 제공업체의 과금 기록(billing record)에 기반한 계산값과 비교합니다.
Langfuse가 특히 유용한 경우
이 제품 모델은 실패한 트레이스(trace)를 프롬프트 버전(prompt versions), 평가(evaluations), 그리고 데이터셋(datasets)과 연결하는 데 매우 적합합니다. 덕분에 디버깅 질문이 단순히 "어떤 호출이 느렸는가?"를 넘어 "프롬프트 변경이 검증 실패를 증가시켰는가?"인 경우에도 유용하게 사용할 수 있습니다.
테스트해야 할 Langfuse 실패 사례
-
SDK가 버퍼링된 이벤트(buffered events)를 전송하기 전에 애플리케이션 예외(application exception)가 발생하는 경우.
-
도구(tool)가 다른 스레드(thread)나 태스크(task)에서 실행될 때 부모 컨텍스트(parent context)를 잃어버리는 경우.
-
모델 통합(model integration)이 비용 계산에서 인식하지 못하는 이름으로 사용량(usage)을 보고하는 경우.
-
레드액션(redaction) 규칙이 디버깅에 필요한 데이터를 삭제하거나 개인 데이터를 노출된 상태로 남겨두는 경우.
-
답변 검증(answer validation)은 실패했음에도 불구하고 HTTP 요청이 완료되었다는 이유로 트레이스(trace)가 성공으로 표시되는 경우.
Phoenix: 개방형 계측(instrumentation)을 통한 로컬 우선 점검
Phoenix는 엔지니어가 로컬 관측성(observability) 및 평가 환경을 원하고, 이미 OpenTelemetry 및 OpenInference 호환 계측(instrumentation)을 사용 중이거나 사용할 계획인 경우 특히 매력적입니다.
로컬에서 시작하기
arize-phoenix를 설치한 후, 서비스를 시작하기 전에 사용 가능한 명령어를 확인하세요:
python -m phoenix.server.main --help
설치된 패키지 생성 방식에 따라 배포판에서 더 짧은 CLI 명령어를 제공할 수도 있습니다. 설치된 버전에서 문서화된 명령어를 사용하고 이를 비교 노트에 기록하세요. Python 프로세스는 로컬 실험 중에 Phoenix를 실행할 수도 있습니다.
트레이싱(tracing) 등록
from phoenix.otel import register
tracer_provider = register(
...
만약 에이전트가 OpenAI 호환 클라이언트와 그에 상응하는 계측(instrumentation) 패키지를 사용한다면, 동일한 프로바이더(provider)에 자동 모델 계측(automatic model instrumentation)을 연결하세요:
from openinference.instrumentation.openai import OpenAIInstrumentor
OpenAIInstrumentor().instrument(
...
자동 계측 (Automatic instrumentation)은 편리하지만, 사용자의 비즈니스 그래프 (business graph)를 이해하지는 못합니다. 분류 (classification), 검색 (retrieval), 검증 (validation), 그리고 재시도 결정 (retry decisions)을 위해 수동 스팬 (manual spans)을 추가하세요.
수동 어댑터 형태 (Manual adapter shape)
from contextlib import contextmanager
from opentelemetry import trace
from opentelemetry.trace import Status, StatusCode
class PhoenixTelemetry:
def __init__(self):
self.tracer = trace.get_tracer("support-research-agent")
@contextmanager
def operation(self, name, *, kind, attributes=None):
with self.tracer.start_as_current_span(name) as span:
safe_attributes = flatten_attributes(attributes or {})
span.set_attributes(safe_attributes)
wrapper = PhoenixObservation(span)
try:
yield wrapper
except Exception as exc:
wrapper.set_error(exc)
raise
class PhoenixObservation:
def __init__(self, span):
self.span = span
def set_attributes(self, values):
self.span.set_attributes(flatten_attributes(values))
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기