LangGraph

LangGraph

반응형·멀티에이전트 시스템을 만들 때 LangGraph를 쓴다면, init() 한 번으로 에이전트를 추적·평가할 수 있어요. confident-trace는 Python·TypeScript용 OpenTelemetry 네이티브 추적 SDK로, LangGraph 에이전트를 자동으로 추적·평가합니다. 그래프 코드는 그대로 두면 돼요.

출처: 문서

본문

개요

LangGraph는 반응형·멀티에이전트 시스템을 만드는 프레임워크입니다. Confident AI는 Python·TypeScript용 OpenTelemetry 네이티브 추적 SDK인 confident-trace로 LangGraph 에이전트를 자동 추적·평가해요 — init()을 한 번 호출하면 그래프 코드는 그대로 유지됩니다.

이 통합은 LangGraph 에이전트에서 다음 스팬을 포착합니다.

  • 그래프·노드 스팬 — 각 invoke / stream 호출(서브그래프 포함)의 루트 스팬, 그리고 노드·중간 runnable마다 스팬 하나
  • LLM 스팬 — 모델 이름, 토큰 사용량, 완료 사유, (모델이 한 도구 호출 포함) 입출력 메시지
  • 도구 스팬 — 도구 이름, 입력 매개변수, 출력이 그것을 실행한 노드 아래에 중첩
  • 리트리버 스팬 — 쿼리 입력과 검색된 문서 텍스트

LangGraph와 LangChain은 confident-trace에서 하나의 콜백 브리지를 공유합니다. 그래프 노드가 LangChain 체인·리트리버·도구를 호출하면 같은 통합으로 추적돼요. 체인별 세부 사항은 LangChain 페이지를 참고하세요.

Runtime Requirements Setup
Python Python 3.10+, LangGraph 1.x Call init() before invoking the graph
TypeScript Node.js 22+, @langchain/langgraph >=1.4.14 <2, @langchain/core >=1.2.9 <2 Call init() and launch your entry point with the preload

자동 계측

의존성 설치

confident-trace를 LangGraph와 함께 설치하는 명령을 실행하세요.

Python

pip install confident-trace 'langgraph>=1,<2' 'langchain-openai>=1,<2'

TypeScript

tsx는 TypeScript 소스를 직접 실행할 때만 필요해요.

npm install confident-trace '@langchain/langgraph@>=1.4.14 <2' '@langchain/core@>=1.2.9 <2' @langchain/openai@1
npm install -D tsx
yarn add confident-trace '@langchain/langgraph@>=1.4.14 <2' '@langchain/core@>=1.2.9 <2' @langchain/openai@1
yarn add -D tsx

API 키 설정

Confident AI Project API key를 받아 모델 프로바이더 키와 함께 환경 변수로 설정하세요.

export CONFIDENT_API_KEY="<your-confident-project-key>"
export OPENAI_API_KEY="<your-openai-key>"

EU 리전이거나 자체 호스팅 배포라면, 트레이스가 우리 US 서버로 가지 않도록 CONFIDENT_OTEL_ENDPOINT도 설정하세요 — init() 구성을 참고하세요.

LangGraph 계측

그래프를 호출하기 전에 init()을 한 번 호출하세요. LangGraph를 자동으로 감지해 콜백 핸들러를 붙여 줘요 — config에 전달할 핸들러도, 설치할 트레이싱 extra도 없습니다.

Python

from confident_trace import init, shutdown
from langchain_openai import ChatOpenAI
from langgraph.graph import END, START, MessagesState, StateGraph

init()
model = ChatOpenAI(model="gpt-4.1-mini")

def assistant(state: MessagesState):
    return {"messages": [model.invoke(state["messages"])]}

graph = (
    StateGraph(MessagesState)
    .add_node("assistant", assistant)
    .add_edge(START, "assistant")
    .add_edge("assistant", END)
    .compile()
)

try:
    result = graph.invoke({"messages": [{"role": "user", "content": "what is the weather in sf"}]})
    print(result["messages"][-1].content)
finally:
    shutdown()

TypeScript

import { init } from "confident-trace";
import { StateGraph, MessagesAnnotation, START, END } from "@langchain/langgraph";
import { ChatOpenAI } from "@langchain/openai";

const runtime = init();
const model = new ChatOpenAI({ model: "gpt-4.1-mini" });
const graph = new StateGraph(MessagesAnnotation)
  .addNode("assistant", async (state) => ({
    messages: [await model.invoke(state.messages)],
  }))
  .addEdge(START, "assistant")
  .addEdge("assistant", END)
  .compile();

try {
  const result = await graph.invoke({
    messages: [{ role: "user", content: "what is the weather in sf" }],
  });
  console.log(result.messages.at(-1)?.content);
} finally {
  await runtime.shutdown();
}

TypeScript에는 한 가지가 더 필요합니다. Node가 @langchain/langgraph를 로드할 때 SDK가 훅할 수 있도록 엔트리포인트를 confident-trace/register preload로 실행하세요. init()은 export를, preload는 계측을 처리합니다 — 둘 다 필요해요.

preload 없이 init()을 호출하면 설정 경고가 나오고 스팬이 없어요. 반대로 preload만 있고 init()이 없으면 스팬은 만들어지지만 아무것도 내보내지지 않습니다.

장기 실행 서버에서는 시작 시 init()을 한 번, 정상 종료 시 진행 중인 그래프 실행이 끝난 뒤 shutdown()을 한 번 호출하세요 — 요청마다 하지 마세요. 한 번 초기화를 참고하세요.

LangGraph 실행

스크립트를 실행해 트레이스를 Confident AI로 보내세요.

Python

python main.py

TypeScript

# Running TypeScript source directly
node --import tsx --import confident-trace/register src/index.ts

# Running compiled JavaScript
node --import confident-trace/register dist/index.js

이걸 평소 시작 명령으로 만들려면 package.json scripts에 추가하세요.

{
  "scripts": {
    "start": "node --import confident-trace/register dist/index.js",
    "dev": "node --import tsx --import confident-trace/register src/index.ts"
  }
}

완료 ✅. Confident AI 프로젝트에서 Observatory를 열어 트레이스와 그래프·노드·모델 스팬을 확인하세요.

트레이스가 안 보이면 99.99% 프로그램이 스팬을 보낼 기회가 생기기 전에 종료됐기 때문이에요. 종료 전에 반드시 shutdown()(장기 실행 프로세스에선 flush())을 호출하세요 — 트러블슈팅 페이지를 참고하세요.

무엇이 포착되나

통합은 LangGraph가 콜백으로 보고하는 계층을 그대로 반영합니다.

  • 그래프 호출 — 각 invoke / stream 호출(서브그래프 포함)의 루트 스팬
  • 노드·runnable — 노드와 그 안의 각 중간 runnable마다 스팬 하나
  • 모델 호출 — 모델 이름, 토큰 사용량, 완료 사유, 정규화된 입출력 메시지를 담은 LLM 스팬
  • 도구 실행 — 보통 그것을 요청한 모델 옆에서, 실행한 노드 아래에 부모로 연결된 도구 스팬
  • 리트리버 — 검색된 문서 텍스트를 담은 리트리버 스팬

그래프 상태, 도구 값, 문서 텍스트는 콘텐츠 정책을 따릅니다. 크기 제한은 기본적으로 꺼져 있지만, export 전에 제한을 구성하거나 콘텐츠를 삭제할 수 있어요.

그래프와 노드는 일반 스팬으로 내보내져요 — 그래프 이름만으로 에이전트로 표시되진 않습니다. 루트를 에이전트 타입으로 두고 싶다면(예: Task Completion 메트릭을 돌리려면) 트레이스 스팬 속성 설정처럼 호출을 span(type="agent")으로 감싸세요.

Python

노드·도구·모델 실행 안에서는 OpenTelemetry 컨텍스트가 활성이므로, 노드 코드에서 만든 커스텀 스팬이 자동으로 그 노드 아래에 중첩됩니다.

TypeScript

콜백은 LangGraph 계층은 보존하지만 노드 실행을 감싸지는 않아요. 노드 안에서 만든 무관한 HTTP·DB 스팬은, 그 작업을 자체 span으로 감싸지 않는 한 그 아래에 부모로 연결되지 않습니다.

LangGraph 서버 배포 추적

그래프를 LangGraph 서버(langgraph dev 또는 LangGraph Platform)로 배포한다면, 서버가 자기 프로세스 안에서 그래프를 실행하므로 추적 초기화가 그 프로세스 안에서 일어나야 해요 — 호출하는 클라이언트가 아니라요. 패턴은 빠른 시작과 같습니다. 그래프를 내보내는 모듈에서 init()을 한 번 호출하세요.

그래프 모듈에서 추적 초기화

그래프를 만들고 내보내는 파일 맨 위에서 init()을 호출하세요. 서버는 시작 시 이 모듈을 한 번 가져오므로, init()이 한 번 실행되고 서버가 실행하는 모든 실행이 추적됩니다.

Python

from confident_trace import init
from langchain.agents import create_agent
from langchain_openai import ChatOpenAI

init()

def get_weather(city: str) -> str:
    """Returns the weather in a city"""
    return f"It's always sunny in {city}!"

graph = create_agent(
    model=ChatOpenAI(model="gpt-4.1-mini"),
    tools=[get_weather],
    system_prompt="You are a helpful assistant",
)

TypeScript

import { init } from "confident-trace";
import { createAgent } from "langchain";
import { ChatOpenAI } from "@langchain/openai";
import { tool } from "@langchain/core/tools";
import { z } from "zod";

init();

const getWeather = tool(
  async ({ city }: { city: string }) => `It's always sunny in ${city}!`,
  {
    name: "get_weather",
    description: "Returns the weather in a city",
    schema: z.object({ city: z.string() }),
  },
);

export const graph = createAgent({
  model: new ChatOpenAI({ model: "gpt-4.1-mini" }),
  tools: [getWeather],
  systemPrompt: "You are a helpful assistant",
});

langgraph.json에 그래프 등록

graphs 항목이 내보낸 그래프 변수를 가리키게 하고, 서버가 로드하는 env 파일에 CONFIDENT_API_KEY가 있는지 확인하세요.

{
  "dependencies": ["."],
  "graphs": { "agent": "./agent.py:graph" },
  "env": ".env"
}
{
  "node_version": "22",
  "dependencies": ["."],
  "graphs": { "agent": "./agent.ts:graph" },
  "env": ".env"
}

LangGraph 서버 시작

서버를 실행하세요. 서버가 그래프에 대해 실행하는 모든 요청이 Confident AI로 추적됩니다.

Python

pip install -U "langgraph-cli[inmem]"
langgraph dev

TypeScript

서버가 node 명령을 소유하므로 --import를 직접 추가할 수 없어요. 대신 Node의 표준 NODE_OPTIONS 변수로 preload를 적용하세요.

NODE_OPTIONS="--import confident-trace/register" npx @langchain/langgraph-cli dev

thread_id, user_id, customer_id 같은 트레이스 속성은 그래프 안에서 update_trace로 설정할 수 있어요. 스팬 안에서 갱신을 참고하세요. 그래프가 로컬에서 돌든 서버 뒤에서 돌든 똑같이 동작합니다. 서버는 장기 실행이므로 그래프 모듈에서 shutdown()을 호출하지 마세요 — 스팬은 완료될 때 백그라운드로 내보내집니다.

대화와 체크포인트

그래프가 체크포인터로 컴파일되어 있다면, 이미 thread_id를 전달하고 있을 거예요. 그런데 여기 두 시스템에 속하는 서로 다른 thread_id가 있다는 점을 분명히 해 둘게요.

  • configurable.thread_id 는 LangGraph의 것입니다. 체크포인트를 선택해서 그래프가 이전 턴을 기억하게 하죠. 추적은 여기에 관여하지 않아요.
  • 트레이스의 스레드 ID 는 Confident AI의 것입니다. 각 턴의 트레이스를 Observatory의 하나의 스레드로 묶어, 전체 대화를 보고 평가할 수 있게 해 줘요.

둘 다 같은 문자열을 쓰면, 그래프가 보는 메모리와 내가 검사하는 대화가 일치해요. 각 호출은 여전히 자체 트레이스이며, 스레드는 그것들을 묶을 뿐입니다. 인터럽트 후 체크포인트를 재개하면 이전 트레이스를 이어가는 것이 아니라 새 트레이스가 시작돼요.

이 조각들은 빠른 시작의 try 블록 안 graph.invoke 호출을 대체하며, 그래프가 체크포인터로 컴파일됐다고 가정합니다.

Python

콜백 브리지는 LangGraph의 실행 메타데이터에서 configurable.thread_id를 읽어 그래프 스팬에 대화 ID로 찍어 주므로, 그냥 graph.invoke만으로 충분해요.

thread_id = "conversation-42"
config = {"configurable": {"thread_id": thread_id}}
for prompt in ("Hello", "What did I just say?"):
    result = graph.invoke(
        {"messages": [{"role": "user", "content": prompt}]}, config
    )
    print(result["messages"][-1].content)

호출을 자체 span으로 감싸면(예: 최종 출력을 기록하려고 — 트레이스 스팬 속성 설정 참고), 그 스팬이 트레이스 루트가 되어 값을 상속받지 않아요 — update_trace로 thread_id를 설정하거나, 그 주위에 trace_context를 여세요.

ainvoke, stream / astream, batch / abatch, astream_events v2도 지원됩니다.

TypeScript

TypeScript 통합은 configurable.thread_id를 읽지 않아요 — 그래프 메모리만 구동합니다. 각 턴을 스팬으로 감싸고 updateTrace({ threadId })로 트레이스의 스레드 ID를 직접 설정하고, 같은 변수를 재사용하세요.

import { withSpan, updateTrace } from "confident-trace";

const threadId = "conversation-42";
const config = { configurable: { thread_id: threadId } };
for (const prompt of ["Hello", "What did I just say?"]) {
  await withSpan({ name: "turn", type: "agent" }, async () => {
    updateTrace({ threadId, input: prompt });
    const result = await graph.invoke(
      { messages: [{ role: "user", content: prompt }] }, config,
    );
    const answer = result.messages.at(-1)?.content;
    updateTrace({ output: answer });
    console.log(answer);
  });
}

shutdown() 전에 그래프·모델 스트림을 소비하거나 닫으세요. 버려진 스트림은 스팬을 열어 둔 채 트레이스가 불완전해져요. flush와 shutdown을 참고하세요.

트레이스 스팬 속성 설정

호출이 시작되기 전에 아는 속성을 추가하려면 트레이스 컨텍스트를 사용하세요. 추가 스팬은 만들지 않아요. graph.invoke()가 시작한 트레이스가 태그, 메타데이터, 사용자 ID, 고객 ID를 상속받습니다.

Python

from confident_trace import init, trace_context

init()

with trace_context(
    tags=["support"],
    metadata={"release": "2026-09"},
    user_id="user-42",
    customer_id="customer-7",
):
    result = graph.invoke({"messages": [{"role": "user", "content": "Hello"}]})

TypeScript

import { init, traceContext } from "confident-trace";

init();

const result = await traceContext(
  {
    tags: ["support"],
    metadata: { release: "2026-09" },
    userId: "user-42",
    customerId: "customer-7",
  },
  () => graph.invoke({ messages: [{ role: "user", content: "Hello" }] }),
);

각 ID와 함께 선택적 표시 이름을 설정하려면 사용자와 고객을, 지원되는 모든 트레이스 속성과 갱신 동작은 트레이스 컨텍스트를 참고하세요.

다중 턴 계측

LangGraph 엔트리포인트 호출 하나가 이미 하나의 대화 턴일 때는 turn()이 필요 없어요 — 통합이 그 턴의 트레이스를 자동으로 만들어 주거든요. 경계를 직접 정의하고 싶을 때, 예를 들어 두 번의 연속 LangGraph 호출을 하나의 턴으로 묶고 싶을 때 turn()을 사용하세요. 이후 턴에서 같은 스레드 ID를 재사용해 하나의 대화로 묶으면 돼요.

Python

from confident_trace import init, turn

init()

with turn("support-turn", thread_id="chat-42"):
    first = graph.invoke({"messages": [{"role": "user", "content": "Find my account."}]})
    second = graph.invoke({"messages": first["messages"] + [{"role": "user", "content": "Summarize it."}]})

TypeScript

import { init, turn } from "confident-trace";

init();

const second = await turn({ name: "support-turn", threadId: "chat-42" }, async () => {
  const first = await graph.invoke({ messages: [{ role: "user", content: "Find my account." }] });
  return graph.invoke({ messages: [...first.messages, { role: "user", content: "Summarize it." }] });
});

스레드 입출력, 턴 ID, 사용자 ID는 스레드를 참고하세요.

트러블슈팅

Python

  • 트레이스 없음: 그래프 실행 전에 init()이 실행되고, 프로세스가 shutdown()에 도달해 버퍼링된 스팬이 플러시되는지 확인하세요.
  • 중복 스팬: 같은 그래프에 instrumentor가 두 개 붙어 있어요. confident-trace가 자체 핸들러를 관리하게 두세요 — 자동 모드와 함께 수동 ConfidentLangGraphCallbackHandler를 추가하지 말고, 두 번째 프로바이더 instrumentor도 붙이지 마세요.
  • 불완전한 스트림: shutdown() 전에 그래프·모델 스트림을 소비하거나 닫으세요.
  • 턴마다 별도 트레이스: 예상된 동작이에요. 스레드 ID를 공유하는 턴은 스레드로 묶이고, 체크포인트 재개는 새 트레이스예요.
  • 스레드 풀에서 스팬 누락: 활성 컨텍스트가 워커에 도달하도록 copy_context().run으로 작업을 제출하세요.

TypeScript

  • 트레이스 없음: 그래프 실행 전에 init()이 실행되고, 시작 명령에 --import confident-trace/register가 포함되며, shutdown()에 도달해 버퍼링된 스팬이 플러시되는지 확인하세요. runtime.getInstrumentationStatus()가 LangGraph 훅이 붙었는지 알려 줘요.
  • 중복 스팬: 같은 그래프에 instrumentor가 두 개 붙어 있어요. confident-trace가 자체 핸들러를 관리하게 두세요 — 자동 모드와 함께 수동 ConfidentLangGraphCallbackHandler를 추가하지 말고, 두 번째 프로바이더 instrumentor도 붙이지 마세요.
  • 불완전한 스트림: shutdown() 전에 그래프·모델 스트림을 소비하거나 닫으세요.
  • 턴마다 별도 트레이스: 예상된 동작이에요. 스레드 ID를 공유하는 턴은 스레드로 묶이고, 체크포인트 재개는 새 트레이스예요. 각 턴에서 updateTrace({ threadId })를 호출하는 것을 기억하세요.

일반 설정 문제는 트러블슈팅을 참고하세요.

LangGraph 계측 비활성화

특정 통합만 활성화하려면 init()에 통합 식별자 목록을 전달하세요. LangGraph의 식별자는 Python·TypeScript 모두 "langgraph"이고, 이를 빼면 이 통합이 비활성화돼요. 빈 목록은 모든 자동 계측을 비활성화합니다.

Python

from confident_trace import init
init(instrumentations=())
# Use ("langgraph",) to opt in; omit "langgraph" to disable it.

TypeScript

import { init } from "confident-trace";
init({ instrumentations: [] });
// Use ["langgraph"] to opt in; omit "langgraph" to disable it.

이렇게 하면 Confident AI의 자동 계측이 꺼지고, 초기화 이후의 호출은 이 통합으로 계측되지 않아요.

다음 단계

Threads

체크포인트된 대화를 스레드로 묶고, 턴 입출력을 설정하며, 전체 대화를 하나의 단위로 평가하세요.

Online Evals

트레이스와 스팬이 Confident AI로 수집되는 실시간으로 평가를 돌려 프로덕션 AI 품질을 모니터링하세요.

더 알아보기