이 문서는 LangChain · LangGraph · DeepAgents를 쓸 때 Langfuse를 연동하는 방법을 모아둔 실전 BackLog(작업 노트)다. 세 프레임워크는 모두 하나의 메커니즘 — LangChain
CallbackHandler— 으로 연동된다. 개념 배경은 Learn → Monitoring → Langfuse, OpenTelemetry 문서를 함께 보라.
0. 목차
- 핵심 원리 — “콜백 핸들러 하나로 끝”
- 공통 준비 (설치 · 환경변수 · 초기화)
- ① LangChain 연동
- ② LangGraph 연동
- ③ DeepAgents 연동
- Trace 속성 — session / user / tags
- 트레이스 구조 매핑 (무엇이 어떤 Observation이 되나)
- 운영 팁 / 함정 (flush · 비동기 · 비용)
- 체크리스트 & 링크
1. 핵심 원리 — “콜백 핸들러 하나로 끝”
왜 세 프레임워크를 한 문서에서 다루는가? 의존 관계가 포개져 있기 때문이다.
DeepAgents ─빌드 온─► LangGraph ─빌드 온─► LangChain (Runnable + Callbacks)
│
Langfuse CallbackHandler
(config={"callbacks":[handler]})
한 줄 요약: LangGraph도 DeepAgents도 결국 LangChain Runnable이다. 따라서 LangChain용 Langfuse
CallbackHandler를config에 한 번 꽂으면 세 경우 모두 동일하게 추적된다. 핸들러는 내부적으로 OpenTelemetry Context 전파를 사용해 중첩 구조(체인·노드·서브에이전트)를 자동으로 트리로 만든다.
| 프레임워크 | 연동 방식 | 차이점 |
|---|---|---|
| LangChain | CallbackHandler → config |
기본형 |
| 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 위에 플래닝·서브에이전트·파일시스템 도구를 얹은 고수준 에이전트다. 역시 같은 핸들러를 invoke의 config에 넘기면 된다.
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)·유저 식별·태그는 config의 metadata에 langfuse_* 키로 넣는다. 세 프레임워크 공통이다.
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. 운영 팁 / 함정
- 짧게 끝나는 스크립트는 flush 필수. 프로세스가 바로 종료되면 전송 전에 죽는다 → 끝에
langfuse.flush()호출. - 핸들러는 재사용. 매 호출마다
CallbackHandler()새로 만들지 말 것. - 비동기/스트리밍도 동일.
ainvoke/astream/stream전부config에 핸들러를 넘기면 된다. - 토큰/비용은 대체로 자동. LangChain이 usage를 주는 모델이면 Generation에 토큰·비용이 자동 집계. 누락되면 모델 응답의 usage 메타를 확인.
- 중첩이 안 끊기게. 직접 만든 바깥 span과 합치려면 같은 실행 컨텍스트에서 호출 — OTel Context가 전파돼 한 트레이스로 묶인다.
- 프롬프트에 PII 주의. 입력/출력이 그대로 저장된다. 마스킹 정책 검토.
- OTLP 직결 대안. 콜백 대신 OpenLLMetry/OpenInference 자동계측으로 OTLP를 Langfuse에 직접 보낼 수도 있다(=Langfuse 문서의 OTLP 백엔드 방식). 콜백 방식이 LangChain 생태계에선 가장 직접적.
# 스크립트 종료 전
langfuse.flush()
9. 체크리스트 & 링크
| ✔ | 항목 |
|---|---|
| ☐ | LANGFUSE_PUBLIC_KEY/SECRET_KEY/HOST 환경변수 설정 |
| ☐ | from langfuse.langchain import CallbackHandler 후 1개 생성·재사용 |
| ☐ | 모든 invoke/stream에 config={"callbacks":[handler]} |
| ☐ | metadata에 langfuse_session_id/user_id/tags |
| ☐ | 짧은 스크립트면 종료 전 langfuse.flush() |
| ☐ | Langfuse UI에서 Generation에 토큰/비용 잡히는지 확인 |
참고 링크
- LangChain 연동: langfuse.com/integrations/frameworks/langchain
- LangGraph 연동: langfuse.com/guides/cookbook/integration_langgraph
- DeepAgents 연동: langfuse.com/integrations/frameworks/langchain-deepagents
- 선행 학습: Langfuse · OpenTelemetry