LangChain, LangGraph, LangSmith — AI 에이전트 스택의 세 조각
ChatGPT API를 한 번 호출하는 코드는 누구나 짤 수 있지만, 그것이 실제 서비스가 되는 순간 이야기가 달라집니다. LLM 호출이 실패하면 어디서 멈춰야 하는지, 사용자 승인을 기다리는 동안 상태를 어떻게 보관할지, 운영 환경에서 무엇이 잘못됐는지 어떻게 추적할지가 모두 새로운 문제로 등장합니다. LangChain, LangGraph, LangSmith는 이 세 가지 다른 층위의 문제를 해결하기 위한 도구입니다. 이 글은 각 도구의 정체와 책임 경계, 그리고 셋이 어떻게 맞물려 한 스택을 이루는지를 처음 에이전트를 만드는 분을 대상으로 풀어냅니다.
1. 왜 세 개로 나뉘어 있나
한 줄 요약
세 도구는 서로를 대체하지 않습니다. 책임 경계가 다릅니다.
- LangChain — LLM과 외부 시스템을 잇는 컴포넌트와 통합 라이브러리
- LangGraph — 에이전트의 실행 흐름과 상태를 다루는 오케스트레이션 런타임
- LangSmith — 트레이스, 평가, 배포를 책임지는 관측·운영 플랫폼
이 세 층은 각각 다른 시점의 고민에서 탄생했습니다. 처음에는 LLM을 코드에서 부르는 것만으로도 신기했고(LangChain), 점차 멀티스텝 에이전트를 만들기 시작하면서 흐름 제어가 필요해졌고(LangGraph), 운영에 올리고 보니 LLM 호출이 블랙박스라는 문제가 생긴 것입니다(LangSmith). 하나의 거대한 프레임워크로 묶지 않고 분리한 이유는 각 층의 변화 속도와 사용자 층이 다르기 때문입니다.
책임 경계를 그림으로
flowchart TB
subgraph App[Your Agent Application]
direction TB
Logic[Business Logic / Tools]
end
subgraph LG[LangGraph: Orchestration Runtime]
direction TB
StateGraph[StateGraph: nodes + edges]
Checkpointer[Checkpointer: durable state]
HITL[Interrupt / Resume]
end
subgraph LC[LangChain: Components and Integrations]
direction TB
Models[Chat Models]
Tools[Tools]
Retrievers[Retrievers / Vector Stores]
Runnables[Runnables / LCEL]
end
subgraph LS[LangSmith: Observability and Evals]
direction TB
Tracing[Tracing]
Datasets[Datasets and Evaluators]
Studio[Prompt Hub / Studio]
end
App --> LG
LG --> LC
App -.traces.-> LS
LG -.traces.-> LS
LC -.traces.-> LS
호출 흐름은 위에서 아래로 내려가지만, LangSmith로 향하는 화살표는 점선입니다. LangSmith는 호출 경로에 끼어드는 것이 아니라 관찰자(observer) 위치에서 트레이스만 받아갑니다. 즉 LangSmith가 일시적으로 죽어도 애플리케이션은 동작합니다.
작성 시점 정보
- 패키지명:
langchain,langchain-core,langchain-community,langgraph,langsmith - LangGraph는 2025년 말 안정 메이저 릴리스가 나온 상태이며, 현재 LangGraph CLI는 0.4.25 (2026-05-07 기준)
- 공식 문서 통합 도메인:
docs.langchain.com - 통합 카운트: LangChain 기준 600개 초과 (2026년 시점 LangChain 공식 발표)
2. LangChain — 부품 상자
무엇인가
LangChain은 한 줄로 말하면 LLM 애플리케이션의 부품 상자입니다. ChatGPT, Claude, Gemini 같은 모델, Pinecone·Chroma 같은 벡터 DB, Tavily·DuckDuckGo 같은 검색 도구를 같은 인터페이스로 묶어 쓰게 해 줍니다.
핵심은 추상화입니다. 어느 모델 제공자를 쓰든 model.invoke(messages) 한 줄로 호출할 수 있고, 어느 벡터 스토어를 쓰든 retriever.invoke(query) 한 줄로 검색할 수 있습니다. 모델을 OpenAI에서 Anthropic으로 바꾸고 싶다면 import 한 줄과 인스턴스 생성 한 줄만 바꾸면 됩니다.
패키지 구조
LangChain은 2024년부터 세 패키지로 분리되어 있습니다.
| 패키지 | 책임 | 의존성 |
|---|---|---|
langchain-core |
Runnable, BaseMessage, BaseChatModel 등 기반 추상화 | 매우 가벼움 (외부 SDK 없음) |
langchain |
더 높은 수준의 체인, 검색 알고리즘 등 cognitive architecture | langchain-core 위 |
langchain-community |
600개 이상의 서드파티 통합 (모델·DB·도구) | 통합 대상 SDK |
이 분리에는 명확한 이유가 있습니다. 핵심 추상화(langchain-core)는 거의 변하지 않지만, 통합(langchain-community)은 매주 새로 들어오고 빠지기 때문입니다. 한 패키지로 묶여 있던 시절에는 작은 통합 패치 하나가 전체 버전을 끌어올렸고, 그래서 LangChain이 "버전이 너무 자주 바뀐다"는 비판을 받았습니다. 분리된 지금은 핵심 API를 안정화한 채로 통합만 빠르게 늘릴 수 있습니다.
추가로 큰 제공자는 자기 이름의 별도 패키지를 가집니다. langchain-openai, langchain-anthropic, langchain-google-genai 같은 식입니다.
Runnable과 LCEL — 부품을 잇는 규약
LangChain의 모든 컴포넌트는 Runnable 인터페이스를 구현합니다. Runnable은 네 가지 메서드를 가집니다.
invoke(input)— 단일 입력 동기 실행batch(inputs)— 여러 입력을 병렬로 실행stream(input)— 토큰/청크 단위 스트리밍ainvoke / abatch / astream— 위 셋의 비동기 버전
이 규약 위에서 두 Runnable은 파이프 연산자 |로 합쳐집니다. 이 합성 문법을 LCEL(LangChain Expression Language) 이라고 부릅니다.
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_anthropic import ChatAnthropic
prompt = ChatPromptTemplate.from_messages([
("system", "You are a helpful Korean assistant."),
("user", "{question}"),
])
model = ChatAnthropic(model="claude-sonnet-4-5")
parser = StrOutputParser()
chain = prompt | model | parser
print(chain.invoke({"question": "LangChain이 뭔가요?"}))
prompt | model | parser는 단순한 코드처럼 보이지만, 합성된 chain 자체가 다시 Runnable이라는 점이 핵심입니다. 즉 chain.batch(...)로 100개 질문을 병렬 처리할 수도 있고, chain.stream(...)로 토큰 단위 스트리밍도 됩니다. 사용자가 별도로 비동기·스트리밍 코드를 짜지 않아도 LCEL이 공짜로 얹어 줍니다.
flowchart LR
Input[Input Dict] --> P[Prompt Runnable]
P --> M[Model Runnable]
M --> O[OutputParser Runnable]
O --> Output[String Output]
style P fill:#e0f2fe
style M fill:#e0f2fe
style O fill:#e0f2fe
언제 LangChain이 충분한가
다음과 같은 경우 LangChain만으로 충분하고, LangGraph는 과합니다.
- 단일 LLM 호출 + 후처리 — 요약, 분류, 추출 같은 한 번의 호출
- 고정된 RAG 파이프라인 —
retriever | prompt | model | parser같은 직선형 체인 - 간단한 도구 호출 — 한 번 도구를 부르고 끝나는 워크플로우
반대로 다음 신호가 보이면 LangGraph를 검토할 차례입니다.
- 같은 노드를 조건에 따라 반복해야 함 (ReAct 루프 등)
- 사용자 승인이 중간에 들어가야 함
- 실행이 길어 중간 상태를 저장해야 함
- 분기가 동적으로 결정되어야 함
3. LangGraph — 에이전트 런타임
왜 LangGraph가 등장했나
LangChain만으로 에이전트를 만들 수도 있습니다. AgentExecutor 같은 고수준 추상이 한때 존재했습니다. 그런데 실제 운영을 해 보면 이런 추상이 제어가 부족하다는 문제에 부딪힙니다. 도구 호출이 실패했을 때 다음 단계가 무엇인지, 같은 도구를 두 번 부를지 다른 도구로 갈지, 사용자에게 물어볼지 그냥 진행할지를 프레임워크가 미리 정해 둔 규칙대로만 결정하기 때문입니다.
LangGraph는 이 문제를 그래프로 풀었습니다. 에이전트의 흐름을 노드(node) 와 엣지(edge) 로 명시적으로 그리는 것입니다. 한 노드는 LLM을 부를 수도, 도구를 실행할 수도, 사람에게 승인 요청을 보낼 수도 있습니다. 다음에 어디로 갈지는 엣지가 결정합니다. 모든 결정이 사용자 코드에 노출되어 있으므로 디버깅과 변경이 쉽습니다.
설계 영감은 두 군데에서 왔습니다. 분산 그래프 처리 모델인 Google Pregel과 데이터 처리 프레임워크 Apache Beam입니다. 노드는 각자 메시지를 받아 상태를 업데이트하고, 다음 슈퍼스텝으로 넘어가는 구조가 그대로 들어가 있습니다.
핵심 구성요소
LangGraph의 그래프는 다음 다섯 가지로 이루어집니다.
| 구성요소 | 역할 |
|---|---|
| State | 그래프 전체가 공유하는 데이터 스키마 (TypedDict 또는 Pydantic) |
| Node | State를 받아 부분 업데이트(dict)를 돌려주는 함수 |
| Edge | 노드 사이 전이. 고정 엣지와 조건부 엣지가 있음 |
| Reducer | State 필드 업데이트 방식 (덮어쓰기 vs 병합) |
| Checkpointer | 매 슈퍼스텝마다 State를 저장하는 저장소 |
여기에 두 개의 가짜 노드 START와 END가 있습니다. 그래프는 반드시 START에서 시작해 END로 끝납니다.
가장 단순한 그래프
from typing import TypedDict
from langgraph.graph import StateGraph, START, END
class State(TypedDict):
question: str
answer: str
def answer_node(state: State) -> dict:
return {"answer": f"Echo: {state['question']}"}
builder = StateGraph(State)
builder.add_node("answer", answer_node)
builder.add_edge(START, "answer")
builder.add_edge("answer", END)
graph = builder.compile()
result = graph.invoke({"question": "LangGraph가 뭔가요?"})
print(result["answer"])
compile()은 빌더를 실제 실행 가능한 객체로 바꿔 줍니다. 컴파일 시점에 그래프 구조 검사(고립된 노드, 도달 불가능한 엣지 등)가 일어나고, 컴파일된 그래프는 LangChain의 Runnable 인터페이스를 구현합니다. 즉 graph.invoke / batch / stream이 그대로 동작합니다.
조건부 엣지로 만드는 ReAct 루프
진짜 에이전트는 LLM이 도구를 부를지 답변을 낼지를 매 턴 결정합니다. 이를 LangGraph에서 표현하면 다음과 같습니다.
from typing import TypedDict, Annotated
from langgraph.graph import StateGraph, START, END
from langgraph.graph.message import add_messages
from langchain_core.messages import BaseMessage
class State(TypedDict):
messages: Annotated[list[BaseMessage], add_messages]
def llm_node(state: State) -> dict:
response = model_with_tools.invoke(state["messages"])
return {"messages": [response]}
def tool_node(state: State) -> dict:
last = state["messages"][-1]
tool_results = run_tool_calls(last.tool_calls)
return {"messages": tool_results}
def should_continue(state: State) -> str:
last = state["messages"][-1]
return "tools" if last.tool_calls else END
builder = StateGraph(State)
builder.add_node("llm", llm_node)
builder.add_node("tools", tool_node)
builder.add_edge(START, "llm")
builder.add_conditional_edges("llm", should_continue, {"tools": "tools", END: END})
builder.add_edge("tools", "llm")
graph = builder.compile()
Annotated[list[BaseMessage], add_messages]가 reducer 부분입니다. State 필드는 기본적으로 덮어쓰기(replace)이지만, add_messages가 붙으면 새 메시지가 리스트에 누적됩니다. 이런 식의 reducer 덕분에 노드는 부분 업데이트만 반환하면 되고, 합치는 책임은 LangGraph가 가집니다.
flowchart LR
START([START]) --> LLM[llm node]
LLM -->|tool_calls present| Tools[tools node]
LLM -->|no tool_calls| END([END])
Tools --> LLM
Checkpointer — 죽지 않는 에이전트
여기까지가 LangGraph의 형태였다면, 진짜 가치는 Checkpointer에서 나옵니다. Checkpointer는 매 노드 실행 직후 State를 저장하는 저장소입니다. 단순히 "끝나면 결과를 저장"이 아니라 모든 슈퍼스텝마다 저장합니다.
from langgraph.checkpoint.memory import MemorySaver
graph = builder.compile(checkpointer=MemorySaver())
config = {"configurable": {"thread_id": "user-42"}}
graph.invoke({"messages": [...]}, config=config)
thread_id는 영속 커서입니다. 같은 thread_id로 다시 invoke하면 멈춘 곳부터 이어서 실행됩니다. 새 thread_id를 주면 새 대화가 시작됩니다.
이 단순한 메커니즘이 가져오는 효과는 큽니다.
- 장기 실행 작업 — 1시간짜리 리서치 에이전트가 30분에 서버가 죽어도 같은 thread_id로 다시 부르면 이어집니다
- 사용자 세션 — 챗봇 대화 히스토리를 자동으로 보관합니다
- 사람 개입 지점 — 다음 절에서 보겠지만, 이 메커니즘 위에 human-in-the-loop가 올라갑니다
- 시간 여행 — 과거 체크포인트에서 분기를 만들어 다른 결정을 시뮬레이션할 수 있습니다
운영에서는 MemorySaver 대신 PostgresSaver나 SqliteSaver를 씁니다. 메모리 saver는 프로세스가 죽으면 사라집니다.
Human-in-the-Loop — 사람을 그래프 안으로
사용자 승인이 필요한 워크플로우를 그래프 외부 분기로 처리하면 코드가 복잡해집니다. LangGraph는 interrupt() 함수로 그래프 안에서 사람을 기다립니다.
from langgraph.types import interrupt, Command
def approval_node(state: State) -> dict:
user_decision = interrupt({
"action": "approve_payment",
"amount": state["amount"],
})
return {"approved": user_decision == "yes"}
interrupt()가 호출되면 그래프는 그 자리에서 멈추고 State를 체크포인트에 저장합니다. 호출자(예: 백엔드 핸들러)는 사용자 입력을 받은 뒤 Command(resume="yes")를 들고 graph.invoke(...)를 다시 부릅니다. interrupt()의 반환값으로 "yes"가 들어와 노드가 이어 실행됩니다.
이 메커니즘이 우아한 이유는 비동기 메시지 큐나 별도 워크플로우 엔진을 도입하지 않고, 체크포인트 + 함수 일시정지만으로 사람 개입을 표현한다는 점입니다.
LangGraph는 LangChain 없이 쓸 수 있다
LangGraph는 LangChain의 Runnable 인터페이스를 따르지만, LangChain 자체에 의존하지 않습니다. 노드 함수에서 OpenAI SDK를 직접 부르거나, 그냥 requests로 HTTP를 쳐도 됩니다. LangGraph는 흐름 제어만 책임지는 가벼운 런타임입니다.
4. LangSmith — 운영의 눈
왜 필요한가
에이전트가 잘못 답변했을 때 가장 먼저 묻는 질문은 "왜?"입니다. LLM에게 어떤 프롬프트가 갔는지, 도구는 무엇을 반환했는지, 어느 단계에서 의도가 빗나갔는지를 보지 못하면 디버깅은 추측 게임이 됩니다.
LangSmith는 이 블랙박스를 열어 줍니다. 모든 LLM 호출, 도구 호출, 노드 전이를 트레이스(trace) 라는 단위로 기록하고, 그 안에 입력·출력·지연 시간·토큰 수를 모두 담습니다. 운영자는 LangSmith UI에서 트리 형태로 펼쳐 보며 어떤 호출이 어디서 어떤 응답을 받았는지를 단계별로 확인합니다.
핵심 기능
| 기능 | 책임 |
|---|---|
| Tracing | 모든 실행을 트리 구조로 기록 (root run + child runs) |
| Datasets | 입력·기대 출력 쌍의 모음. 회귀 테스트 기반이 됨 |
| Evaluators | 데이터셋 위에서 자동·LLM-as-judge 채점 |
| Annotation Queues | 사람이 직접 라벨링하는 큐 |
| Prompt Hub / Studio | 프롬프트 버전 관리와 시각적 프로토타이핑 |
| Deployments | LangGraph 그래프를 배포·확장하는 LangGraph Platform |
통합은 환경변수 두 줄
LangChain이나 LangGraph로 짠 코드라면 LangSmith를 켜는 것이 거의 무료입니다.
export LANGSMITH_TRACING=true
export LANGSMITH_API_KEY=lsv2_pt_xxx
export LANGSMITH_PROJECT=my-agent # 선택
이 두 환경변수만 있으면 모든 Runnable.invoke 호출이 자동으로 LangSmith로 흘러갑니다. 코드 변경 없이 관측이 켜지는 것입니다. 내부에서는 langsmith SDK가 콜백 핸들러로 등록되어 모든 LangChain·LangGraph 실행을 감지합니다.
LangChain을 안 쓰는 코드(예: OpenAI SDK 직접 호출)도 @traceable 데코레이터로 트레이싱이 가능합니다.
from langsmith import traceable
@traceable
def search_papers(query: str) -> list:
return external_api.search(query)
이 함수를 부르는 모든 호출이 자식 run으로 묶여 LangSmith 트레이스에 들어갑니다. Python·TypeScript·Go·Java SDK가 제공되며, OpenAI SDK·Anthropic SDK·Vercel AI SDK·LlamaIndex 등 다양한 프레임워크와도 통합됩니다.
트레이스의 구조
LangSmith의 트레이스는 트리(tree) 입니다. 한 사용자 요청 = 한 root run이고, 그 아래로 LLM 호출, 도구 호출, 하위 체인이 자식으로 매달립니다.
flowchart TB
Root[Root Run: chat completion] --> R1[LLM Run #1: classify intent]
Root --> R2[Tool Run: search docs]
Root --> R3[LLM Run #2: synthesize answer]
R3 --> R3a[Sub-run: parse output]
각 run에는 입력 JSON, 출력 JSON, 시작·종료 시각, 토큰 수, 비용 추정치, 메타데이터가 붙습니다. 에러가 났다면 스택트레이스도 함께 기록됩니다.
평가(Evaluation) — 회귀 테스트의 LLM 버전
LLM 애플리케이션은 단위 테스트로 다 잡히지 않습니다. 같은 입력에도 출력이 달라질 수 있고, 무엇이 "맞는 답"인지가 정해져 있지 않은 경우도 많기 때문입니다. LangSmith는 이를 위해 Datasets + Evaluators 패턴을 제공합니다.
- Dataset 만들기 — 입력과 기대 출력 쌍을 모음 (실제 트레이스에서 골라 넣어도 됨)
- Evaluator 정의 — 정답 일치, 임베딩 유사도, LLM-as-judge 같은 평가 함수
- Run — 새 버전의 에이전트를 데이터셋에 돌려 채점
- 비교 — 이전 버전과 점수 차이를 추적
이 사이클이 있으면 프롬프트나 모델을 바꿀 때마다 "이전보다 나아졌나"를 정량적으로 답할 수 있습니다.
배포 옵션
LangSmith는 세 가지 배포 옵션을 제공합니다.
- Managed Cloud — LangChain Inc가 호스팅
- BYOC (Bring Your Own Cloud) — 사용자의 AWS/GCP에 LangChain Inc가 관리
- Self-Hosted — 사용자가 모두 운영. 데이터 거주성 요구사항이 있는 조직용
5. 셋이 같이 일하는 모습
전형적인 RAG 에이전트 스택
문서 RAG에 도구 호출까지 붙은 에이전트를 만든다고 합시다. 세 도구가 어떻게 책임을 나누는지 보면 다음과 같습니다.
flowchart TB
subgraph LangGraph["LangGraph (Runtime)"]
START([START]) --> Retrieve[retrieve node]
Retrieve --> Plan[plan node]
Plan -->|need tool| Tool[tool node]
Plan -->|done| END([END])
Tool --> Plan
end
subgraph LangChain["LangChain (Components)"]
VectorStore[VectorStore]
Embeddings[Embeddings]
ChatModel[ChatAnthropic]
ToolDefs[Tool definitions]
end
Retrieve -.uses.-> VectorStore
Retrieve -.uses.-> Embeddings
Plan -.uses.-> ChatModel
Tool -.uses.-> ToolDefs
LangGraph -.traces.-> LS[LangSmith]
LangChain -.traces.-> LS
- LangGraph는 retrieve → plan → tool → plan 루프의 흐름과 상태를 관리합니다
- LangChain은 각 노드 안에서 쓸 부품(벡터 스토어, 임베딩, 모델, 도구)을 제공합니다
- LangSmith는 위 모든 호출을 자동으로 트레이스로 받아 운영자가 볼 수 있게 합니다
각 도구의 책임이 겹치지 않는다는 점이 중요합니다. LangGraph는 무엇을 할지 모르고, LangChain은 언제 할지 모르고, LangSmith는 둘 다 본 적 없는 것처럼 관찰만 합니다.
함께 쓸 때의 장점
- 단일 트레이스 컨텍스트 — LangChain·LangGraph 호출이 자동으로 LangSmith로 흐름. 코드 추가 없음
- 공통 Runnable 인터페이스 — LangGraph로 컴파일한 그래프도
invoke / stream / batch를 그대로 지원 - 점진적 도입 가능 — 단순한 시작은 LangChain만, 흐름이 복잡해지면 LangGraph 추가, 운영 들어가면 LangSmith 추가
따로 쓸 때의 옵션
- LangChain만 — 간단한 LLM 호출이나 RAG 직선 파이프라인
- LangGraph만 (LangChain 없이) — 모델 호출은 OpenAI SDK 직접 사용, 흐름만 LangGraph로
- LangSmith만 — OpenAI SDK·Anthropic SDK 직접 호출 코드에
@traceable만 붙여 관측 추가
6. 관련 기술과 용어 정리
LangChain 생태계 주변에서 자주 만나는 단어를 한 번 정리합니다.
| 용어 | 정의 |
|---|---|
| LCEL | LangChain Expression Language. | 연산자로 Runnable을 합성하는 문법 |
| Runnable | invoke·batch·stream을 가진 LangChain의 공통 실행 인터페이스 |
| Chain | Runnable이 여러 개 합쳐진 실행 단위 |
| Agent | LLM이 다음 행동(도구 호출/응답)을 결정하는 패턴 |
| Tool | 모델이 호출할 수 있도록 노출된 함수 (스키마 + 실행 로직) |
| RAG | Retrieval-Augmented Generation. 검색으로 컨텍스트를 채워 답변 생성 |
| Embeddings | 텍스트를 벡터로 바꾸는 모델 (검색 기반) |
| Vector Store | 임베딩을 저장하고 유사도 검색을 제공하는 DB |
| Prompt Template | 변수 자리를 가진 프롬프트 정의 (ChatPromptTemplate) |
| Output Parser | 모델 출력을 구조화된 데이터로 변환 (예: PydanticOutputParser) |
| Memory | 대화 히스토리·장기 기억 보관 메커니즘 |
| Checkpointer | LangGraph에서 그래프 State를 저장하는 백엔드 |
| Thread | LangGraph에서 한 사용자/세션의 영속 컨텍스트 (thread_id로 식별) |
| Interrupt | LangGraph 노드 안에서 호출하면 그래프가 멈추고 사람 입력을 기다림 |
| Trace / Run | LangSmith의 실행 기록 단위. 트리 구조로 중첩됨 |
| Evaluator | 데이터셋 위에서 모델 출력을 점수화하는 함수 |
| Deep Agents | LangGraph 위에 얹힌 고수준 패키지. 계획·서브에이전트·파일 시스템 활용 |
7. 시작하기 전 체크리스트
처음 에이전트를 만드는 분에게 권하는 순서입니다.
- 모델 호출 자체에 익숙해지기 —
ChatAnthropic또는ChatOpenAI를 LangChain으로 한 번 부르고 응답을 받습니다 - 간단한 LCEL 체인 — 프롬프트 → 모델 → 파서를
|로 잇는 경험 - RAG 한 사이클 — 임베딩으로 인덱싱, 검색해서 컨텍스트 주입, 답변 생성
- LangGraph로 ReAct 루프 — LLM·Tool 두 노드와 조건부 엣지로 도구 호출 에이전트
- Checkpointer 추가 — 같은 thread_id로 두 번 호출해 보고 상태가 이어지는지 확인
- LangSmith 켜기 — 환경변수만 추가하고 트레이스 UI에서 호출 트리 보기
- interrupt()로 사람 개입 — 승인 지점을 그래프 안에 넣어 보기
이 순서를 따라가면 각 도구가 왜 필요한지를 직접 체감할 수 있습니다. 처음부터 LangGraph + Checkpointer + LangSmith를 다 쓰려고 하면 학습 곡선이 가팔라집니다.
8. 마치며
LangChain·LangGraph·LangSmith를 한 덩어리로 보면 거대해 보이지만, 책임 경계로 갈라 놓고 보면 각자의 역할이 뚜렷합니다.
- LangChain은 부품을 제공합니다 — 모델·벡터 스토어·도구를 같은 인터페이스로 묶어 줍니다
- LangGraph는 흐름을 제어합니다 — 그래프 구조와 체크포인트로 stateful·durable한 에이전트를 만듭니다
- LangSmith는 관측합니다 — 모든 호출을 트레이스로 받아 운영의 눈이 됩니다
처음에는 LangChain만으로 충분합니다. 같은 노드를 반복해야 할 때 LangGraph가 들어오고, 운영에 올릴 때 LangSmith가 합류합니다. 셋이 인기 있는 이유는 화려해서가 아니라, AI 에이전트를 실제로 운영해 본 사람들이 부딪히는 문제를 각각 다른 층에서 해결해 주기 때문입니다.
참고자료
- LangChain 공식 문서: https://docs.langchain.com/
- LangGraph 공식 개요: https://docs.langchain.com/oss/python/langgraph/overview
- LangGraph Quickstart: https://docs.langchain.com/oss/python/langgraph/quickstart
- LangGraph Graph API: https://docs.langchain.com/oss/python/langgraph/graph-api
- LangGraph Interrupts (HITL): https://docs.langchain.com/oss/python/langgraph/interrupts
- LangSmith Observability: https://docs.langchain.com/langsmith/observability
- LangSmith Tracing Quickstart: https://docs.langchain.com/langsmith/observability-quickstart
- LangChain GitHub: https://github.com/langchain-ai/langchain
- LangGraph GitHub: https://github.com/langchain-ai/langgraph
- LangSmith SDK: https://github.com/langchain-ai/langsmith-sdk
- LangChain 0.1 아키텍처 분리 발표: https://www.langchain.com/blog/the-new-langchain-architecture-langchain-core-v0-1-langchain-community-and-a-path-to-langchain-v0-1
- Pregel 논문 (LangGraph 설계 영감): https://research.google/pubs/pub37252/
- Apache Beam (LangGraph 설계 영감): https://beam.apache.org/

