LangChain 연동

이 문서는 LangChain · LangGraph · DeepAgents를 쓸 때 Langfuse를 연동하는 방법을 모아둔 실전 BackLog(작업 노트)다. 세 프레임워크는 모두 하나의 메커니즘 — LangChain CallbackHandler — 으로 연동된다. 개념 배경은 Learn → Monitoring → Langfuse, OpenTelemetry 문서를 함께 보라.

0. 목차

  1. 핵심 원리 — “콜백 핸들러 하나로 끝”
  2. 공통 준비 (설치 · 환경변수 · 초기화)
  3. ① LangChain 연동
  4. ② LangGraph 연동
  5. ③ DeepAgents 연동
  6. Trace 속성 — session / user / tags
  7. 트레이스 구조 매핑 (무엇이 어떤 Observation이 되나)
  8. 운영 팁 / 함정 (flush · 비동기 · 비용)
  9. 체크리스트 & 링크

1. 핵심 원리 — “콜백 핸들러 하나로 끝”

왜 세 프레임워크를 한 문서에서 다루는가? 의존 관계가 포개져 있기 때문이다.

DeepAgents   ─빌드 온─►  LangGraph  ─빌드 온─►  LangChain (Runnable + Callbacks)
                                                      │
                                          Langfuse CallbackHandler
                                          (config={"callbacks":[handler]})

한 줄 요약: LangGraph도 DeepAgents도 결국 LangChain Runnable이다. 따라서 LangChain용 Langfuse CallbackHandlerconfig에 한 번 꽂으면 세 경우 모두 동일하게 추적된다. 핸들러는 내부적으로 OpenTelemetry Context 전파를 사용해 중첩 구조(체인·노드·서브에이전트)를 자동으로 트리로 만든다.

프레임워크 연동 방식 차이점
LangChain CallbackHandlerconfig 기본형
LangGraph 동일 그래프 노드/엣지가 span 트리로
DeepAgents 동일 서브에이전트·플래닝·툴까지 자동 트리

2. 공통 준비 (설치 · 환경변수 · 초기화)

2.1 설치

pip install langfuse langchain langchain-openai     # 공통
pip install langgraph                                # LangGraph용
pip install deepagents tavily-python                 # DeepAgents용

2.2 환경변수

export LANGFUSE_PUBLIC_KEY="pk-lf-..."
export LANGFUSE_SECRET_KEY="sk-lf-..."
export LANGFUSE_HOST="https://us.cloud.langfuse.com"   # EU면 cloud.langfuse.com
export OPENAI_API_KEY="sk-..."                          # 사용하는 LLM 키

(LANGFUSE_HOST / LANGFUSE_BASE_URL 둘 다 통용. 셀프호스팅이면 http://<host>:3000)

2.3 초기화 — 세 경우 공통

from langfuse import get_client
from langfuse.langchain import CallbackHandler

langfuse = get_client()                 # 환경변수에서 자격증명 로드
langfuse_handler = CallbackHandler()     # ★ 이 핸들러를 어디에나 재사용

핸들러는 한 번 만들어 재사용하면 된다. 매 호출마다 새로 만들 필요 없다. (SDK v3/v4 기준 import 경로는 langfuse.langchain.)


3. ① LangChain 연동

핸들러를 invoke()config={"callbacks":[...]}에 넘기는 게 전부다.

from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate

prompt = ChatPromptTemplate.from_template("Explain {topic} in one sentence.")
chain = prompt | ChatOpenAI(model="gpt-4o-mini")

response = chain.invoke(
    {"topic": "robotics"},
    config={"callbacks": [langfuse_handler]},   # ★ 핵심 한 줄
)

이제 Langfuse에 트레이스 1건이 생기고, 프롬프트→LLM 호출이 Generation으로 잡힌다. .batch(), .stream(), .ainvoke() 모두 동일하게 config에 핸들러를 넘기면 된다.


4. ② LangGraph 연동

LangGraph도 컴파일된 그래프가 Runnable이라 완전히 동일하다. invoke/stream 호출 시 config에 핸들러를 넘기면, 그래프의 각 노드가 중첩 span으로 그려진다.

from langgraph.graph import StateGraph, START, END
from typing import TypedDict

class State(TypedDict):
    messages: list

graph = StateGraph(State)
graph.add_node("planner", planner_node)
graph.add_node("executor", executor_node)
graph.add_edge(START, "planner")
graph.add_edge("planner", "executor")
graph.add_edge("executor", END)
app = graph.compile()

result = app.invoke(
    {"messages": [{"role": "user", "content": "Plan a trip"}]},
    config={"callbacks": [langfuse_handler]},   # ★ 동일
)

프리빌트 에이전트(create_react_agent 등)도 동일하게 config에 핸들러를 넣는다. 반복 루프·조건 분기가 트레이스 트리에서 그대로 보인다.

팁: 스트리밍(app.stream(...))이나 비동기(app.ainvoke(...))에서도 config 전달 방식은 같다. 노드가 또 LLM·툴을 호출하면 그 안에서 자동으로 더 깊은 span이 생긴다.


5. ③ DeepAgents 연동

DeepAgents(LangChain의 deep agents)는 LangGraph 위에 플래닝·서브에이전트·파일시스템 도구를 얹은 고수준 에이전트다. 역시 같은 핸들러invokeconfig에 넘기면 된다.

from deepagents import create_deep_agent
from tavily import TavilyClient

tavily_client = TavilyClient()

def internet_search(query: str, max_results: int = 5):
    """Run a web search"""
    return tavily_client.search(query, max_results=max_results)

agent = create_deep_agent(
    tools=[internet_search],
    system_prompt="You are an expert researcher.",
)

result = agent.invoke(
    {"messages": [{"role": "user", "content": "AI 로보틱스 2026 동향 조사해줘"}]},
    config={"callbacks": [langfuse_handler]},   # ★ 동일
)

핸들러 하나로 다음이 모두 한 트레이스 트리에 잡힌다:

  • 에이전트의 플래닝/추론 스텝 (각 LLM 호출 = Generation)
  • 도구 호출 (web search 등 = Span)
  • 서브에이전트(sub-agent) 호출 — 병렬 실행·비평(critique) 워크플로 포함
  • 토큰 사용량·지연시간·전체 대화 이력

주의: 서브에이전트가 여러 개로 갈라져도 같은 핸들러를 그대로 쓰면 된다. OTel Context 전파 덕에 병렬 서브에이전트도 올바른 부모 아래로 묶인다. 핸들러를 서브에이전트마다 새로 만들 필요 없다.


6. Trace 속성 — session / user / tags

대화 묶기(session)·유저 식별·태그는 configmetadatalangfuse_* 키로 넣는다. 세 프레임워크 공통이다.

result = agent.invoke(
    {"messages": [{"role": "user", "content": question}]},
    config={
        "callbacks": [langfuse_handler],
        "metadata": {
            "langfuse_user_id":    "u_42",
            "langfuse_session_id": "conv-2026-06-15",
            "langfuse_tags":       ["prod", "research-agent"],
        },
    },
)
metadata 키 Langfuse 매핑
langfuse_user_id userId
langfuse_session_id sessionId (대화 묶기)
langfuse_tags tags 배열

더 세밀한 제어가 필요하면 SDK의 langfuse.start_as_current_span(...)으로 바깥 span을 만들고 그 안에서 체인/에이전트를 호출하면, 그 트레이스에 update_current_trace(...)로 속성을 줄 수 있다. (propagate_attributes() 컨텍스트 매니저도 활용 가능)


7. 트레이스 구조 매핑 (무엇이 어떤 Observation이 되나)

핸들러는 LangChain 콜백 이벤트를 Langfuse Observation으로 변환한다.

LangChain 이벤트 Langfuse Observation
LLM / ChatModel 호출 Generation (model·토큰·비용 포함)
Chain / Runnable 실행 Span
Tool 호출 Span
Retriever 조회 Span (검색 입력/결과)
Agent / LangGraph 노드 Span (하위 중첩)
Trace: deep-agent-run            session=conv-2026-06-15, user=u_42
└─ Span: agent (graph)
   ├─ Generation: planner         gpt-4o   ★
   ├─ Span: tool.internet_search
   ├─ Span: sub-agent: researcher
   │   ├─ Generation: reason       ★
   │   └─ Span: tool.internet_search
   └─ Generation: final-answer     ★

LLM 호출이 Generation으로 분류되는 기준은 model 속성 유무다. LangChain 핸들러는 이를 자동으로 채워준다.


8. 운영 팁 / 함정

  1. 짧게 끝나는 스크립트는 flush 필수. 프로세스가 바로 종료되면 전송 전에 죽는다 → 끝에 langfuse.flush() 호출.
  2. 핸들러는 재사용. 매 호출마다 CallbackHandler() 새로 만들지 말 것.
  3. 비동기/스트리밍도 동일. ainvoke/astream/stream 전부 config에 핸들러를 넘기면 된다.
  4. 토큰/비용은 대체로 자동. LangChain이 usage를 주는 모델이면 Generation에 토큰·비용이 자동 집계. 누락되면 모델 응답의 usage 메타를 확인.
  5. 중첩이 안 끊기게. 직접 만든 바깥 span과 합치려면 같은 실행 컨텍스트에서 호출 — OTel Context가 전파돼 한 트레이스로 묶인다.
  6. 프롬프트에 PII 주의. 입력/출력이 그대로 저장된다. 마스킹 정책 검토.
  7. OTLP 직결 대안. 콜백 대신 OpenLLMetry/OpenInference 자동계측으로 OTLP를 Langfuse에 직접 보낼 수도 있다(=Langfuse 문서의 OTLP 백엔드 방식). 콜백 방식이 LangChain 생태계에선 가장 직접적.
# 스크립트 종료 전
langfuse.flush()

9. 체크리스트 & 링크

항목
LANGFUSE_PUBLIC_KEY/SECRET_KEY/HOST 환경변수 설정
from langfuse.langchain import CallbackHandler 후 1개 생성·재사용
모든 invoke/streamconfig={"callbacks":[handler]}
metadatalangfuse_session_id/user_id/tags
짧은 스크립트면 종료 전 langfuse.flush()
Langfuse UI에서 Generation에 토큰/비용 잡히는지 확인

참고 링크

Scroll to Top