PROD · OBSERVABILITY

관찰 가능성

📡 LangSmith 추적 🔭 OpenTelemetry 📊 메트릭 & 알림

Agent의 모든 단계를 추적하고, 지연·오류·비용을 실시간으로 모니터링합니다. LangSmith와 OpenTelemetry로 분산 추적 파이프라인을 구축합니다.

관찰 가능성 3 기둥

기둥수집 대상도구용도
📝 Logs이벤트, 오류, 도구 호출 결과Python logging, CloudWatch디버깅, 감사
📊 Metrics지연, 토큰 수, 성공률, 비용Prometheus, Grafana성능 모니터링
🔭 Traces요청→Agent→도구 전체 경로LangSmith, OpenTelemetry병목 분석, 재현

LangSmith 트레이싱 설정

pythonlangsmith_setup.py — 자동 추적 설정
import os

# 환경변수로 자동 활성화 (코드 변경 불필요)
os.environ["LANGCHAIN_TRACING_V2"]  = "true"
os.environ["LANGCHAIN_API_KEY"]       = os.getenv("LANGSMITH_API_KEY")
os.environ["LANGCHAIN_PROJECT"]        = "production-agent"
os.environ["LANGCHAIN_ENDPOINT"]       = "https://api.smith.langchain.com"

# 특정 실행에만 메타데이터 추가
from langsmith import traceable

@traceable(name="research-pipeline", tags=["production", "v2"])
async def run_research_agent(query: str, user_id: str):
    result = await agent.ainvoke(
        {"messages": [{"role": "user", "content": query}]},
        config={
            "metadata": {
                "user_id":    user_id,
                "session_id": str(uuid.uuid4()),
                "env":        "production"
            }
        }
    )
    return result

# 수동 트레이스 제어
from langsmith import Client
client = Client()

def log_feedback(run_id: str, score: float, comment: str = ""):
    """사용자 피드백을 LangSmith 트레이스에 기록"""
    client.create_feedback(
        run_id=run_id,
        key="user_rating",
        score=score,
        comment=comment
    )

OpenTelemetry 분산 추적

pythonotel_tracing.py — OTel + LangChain 통합
from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor

# OTel 초기화 (Jaeger/Tempo에 전송)
provider = TracerProvider()
exporter = OTLPSpanExporter(endpoint="http://otel-collector:4317")
provider.add_span_processor(BatchSpanProcessor(exporter))
trace.set_tracer_provider(provider)

tracer = trace.get_tracer("agent-service")

# FastAPI 자동 계측
FastAPIInstrumentor.instrument_app(app)

# LangGraph 노드별 수동 스팬
async def instrumented_agent_node(state) -> dict:
    with tracer.start_as_current_span("agent.reasoning") as span:
        query = state["messages"][-1].content
        span.set_attribute("query.length",  len(query))
        span.set_attribute("messages.count", len(state["messages"]))

        import time
        start = time.time()
        response = await llm.ainvoke(state["messages"])
        latency = time.time() - start

        span.set_attribute("llm.latency_ms",      round(latency * 1000))
        span.set_attribute("llm.input_tokens",    response.usage_metadata.get("input_tokens", 0))
        span.set_attribute("llm.output_tokens",   response.usage_metadata.get("output_tokens", 0))
        span.set_attribute("tool_calls.count",    len(response.tool_calls))

        return {"messages": [response]}

핵심 메트릭 & 알림 설정

pythonmetrics.py — Prometheus 메트릭 수집
from prometheus_client import Counter, Histogram, Gauge, start_http_server

# 메트릭 정의
agent_requests_total = Counter(
    "agent_requests_total",
    "Total agent requests",
    ["status", "agent_type"]
)
agent_latency = Histogram(
    "agent_request_duration_seconds",
    "Agent request latency",
    ["agent_type"],
    buckets=[0.5, 1, 2, 5, 10, 30, 60]
)
token_usage = Counter(
    "llm_tokens_total",
    "Total LLM tokens used",
    ["model", "type"]   # type: input | output
)
active_tasks = Gauge(
    "agent_active_tasks",
    "Currently running agent tasks"
)

# 메트릭 서버 시작 (Prometheus 스크랩용)
start_http_server(9090)

# 미들웨어로 자동 수집
from fastapi import Request
import time

@app.middleware("http")
async def metrics_middleware(request: Request, call_next):
    start = time.time()
    active_tasks.inc()
    try:
        response = await call_next(request)
        status = "success" if response.status_code < 400 else "error"
        agent_requests_total.labels(status=status, agent_type="default").inc()
        agent_latency.labels(agent_type="default").observe(time.time() - start)
        return response
    finally:
        active_tasks.dec()

알림 임계값 권장 설정

메트릭경고 임계값위험 임계값조치
P95 응답 지연> 10초> 30초오토스케일 트리거
오류율> 2%> 5%온콜 알림, Circuit Breaker
시간당 토큰 비용> $50> $100Rate Limiting 강화
Guardrail 차단율> 2%> 10%규칙 재검토 또는 공격 감지
LLM API 오류율> 1%> 3%Fallback 모델 전환