쉽게 풀어쓰는 LangGraph란 무엇인가
LangGraph는 여러 번의 LLM 호출을 '그래프'로 묶어 주는 오케스트레이션 라이브러리입니다. 모델도, 프레임워크도 아닙니다. 어디까지 갔고 무엇을 알아냈는지 기억하면서 다음에 무엇을 할지 고르는 진행 관리자에 가깝습니다.
목차
- 개요
- 체인으로는 왜 부족한가
- LangGraph를 이루는 세 가지 — 상태·노드·엣지
- 가장 흔한 모양: 도구를 쓰는 에이전트
- 그래프가 되면 따라오는 것들
- 언제 쓰고 언제 쓰지 않는가
- 맺음말
개요
LangGraph를 한 문장으로
LangGraph는 여러 번의 LLM 호출을 그래프 형태로 묶어 실행해 주는 오케스트레이션 라이브러리입니다. 새 모델도 아니고, LangChain을 대체하는 프레임워크도 아닙니다. LangChain 팀이 2024년에 공개했고, 여러 단계를 거치는 AI 기능을 만들 때 "지금 어디까지 왔고 무엇을 알아냈는지"를 기억하면서 다음에 무엇을 할지 정해 주는 진행 관리자에 가깝습니다.
비유하자면 요리 레시피와 주방장의 차이입니다. 레시피는 1번부터 5번까지 순서대로 따라가면 끝나지만, 주방장은 중간에 간을 보고 싱거우면 소금을 더 넣고 다시 간을 봅니다. 같은 단계를 두 번 밟기도 하고, 재료가 없으면 다른 길로 갑니다. LangGraph는 후자를 코드로 표현하기 위한 도구입니다.
LangGraph는 LLM을 대신하지 않습니다. LLM과 도구 사이에 앉아, 지금까지 쌓인 내용을 들고 다음 차례를 정하는 자리에 있습니다.
이 글은 개념을 잡는 데 목적이 있습니다. 상태 스키마 설계와 멀티에이전트 구성, 운영 시 주의점은 LangGraph 상태 기반 멀티에이전트 워크플로우 구현에서 이어서 다룹니다.
체인으로는 왜 부족한가
한 줄로 늘어선 파이프라인
LLM 애플리케이션을 처음 만들 때 가장 자연스러운 구조는 한 줄로 늘어선 파이프라인입니다. 질문을 받아 문서를 검색하고, 검색 결과를 프롬프트에 넣어 모델을 호출하고, 답변을 정리해 돌려줍니다. LangChain의 체인(LCEL)은 이 모양을 아주 간결하게 표현합니다.
# 한 줄로 흐르는 체인 — 시작과 끝이 정해져 있다
chain = retriever | prompt | llm | parser
answer = chain.invoke("환불 규정이 어떻게 되나요?")
이 구조는 단계가 고정되어 있고 되돌아갈 일이 없을 때 가장 좋은 선택입니다. 읽기 쉽고, 디버깅할 지점도 적습니다.
실무에서 부딪히는 세 가지 요구
문제는 실제 요구가 한 줄로 끝나지 않을 때 드러납니다. 자주 부딪히는 것은 세 가지입니다.
첫째는 분기입니다. 사용자의 질문이 단순 인사면 검색을 건너뛰고 바로 답해야 하고, 사내 규정 질문이면 문서를 찾아야 합니다. 둘째는 반복입니다. 검색 결과가 부실하면 질의를 바꿔 다시 찾아야 하고, 생성한 코드가 실행에 실패하면 오류 메시지를 보고 고쳐야 합니다. 셋째는 중단과 재개입니다. 금액이 큰 처리라면 사람이 한 번 확인하고 나서 이어가야 합니다.
체인 안에서 이런 흐름을 구현하지 못하는 것은 아닙니다. if와 while을 섞어 파이썬 코드로 짜면 됩니다. 다만 그렇게 하면 흐름이 코드 곳곳에 흩어지고, 지금까지 쌓인 대화·검색 결과·시도 횟수를 어디에 담아 다닐지가 매번 즉흥적으로 결정됩니다. 단계가 늘수록 이 오케스트레이션 코드가 정작 중요한 비즈니스 로직보다 두꺼워집니다.
| 필요한 흐름 | 체인으로 하면 | 그래프로 하면 |
|---|---|---|
| 조건에 따라 다른 경로 | 호출부에 if 분기를 쌓음 |
조건부 엣지 하나로 선언 |
| 될 때까지 다시 시도 | while 루프와 카운터를 직접 관리 |
노드로 되돌아가는 엣지 |
| 중간에 사람 확인 | 실행을 쪼개고 상태를 직접 저장 | 체크포인트에서 멈췄다 재개 |
| 진행 상황 보관 | 변수로 들고 다님 | 상태(State) 한 곳에 모임 |
LangGraph는 이 흐름을 코드 여기저기가 아니라 그래프 정의 한 곳에 적게 만드는 것이 핵심입니다.
LangGraph를 이루는 세 가지 — 상태·노드·엣지
상태(State): 함께 보는 작업 노트
상태는 그래프가 도는 동안 계속 들고 다니는 데이터 묶음입니다. 지금까지의 대화, 검색해 온 문서, 몇 번 재시도했는지 같은 값이 여기 들어갑니다. 모든 단계가 이 노트를 읽고, 자기 몫을 적어 넣습니다.
from typing import Annotated, TypedDict
from operator import add
class State(TypedDict):
question: str
messages: Annotated[list, add] # 덮어쓰지 않고 뒤에 이어 붙임
documents: list
retry: int
여기서 Annotated[list, add]가 LangGraph다운 부분입니다. 각 단계는 자기가 바꾼 값만 돌려주고, 그 값을 기존 상태에 어떻게 합칠지는 상태 정의가 정합니다. 대화 기록처럼 쌓여야 하는 값은 이어 붙이고, 재시도 횟수처럼 최신 값만 의미 있는 값은 덮어씁니다. 상태를 어떻게 합칠지 미리 못 박아 두기 때문에, 단계가 늘어도 "이 값을 누가 지웠지" 같은 문제가 줄어듭니다.
노드(Node): 한 단계의 일
노드는 상태를 받아 바뀐 부분을 돌려주는 평범한 함수입니다. LLM을 부르는 노드, 검색하는 노드, 결과를 다듬는 노드처럼 한 단계에 하나씩 둡니다. 특별한 클래스를 상속할 필요 없이, 함수 하나가 곧 노드입니다.
def retrieve(state: State) -> dict:
docs = vectorstore.similarity_search(state["question"], k=4)
return {"documents": docs} # 바뀐 부분만 돌려준다
엣지(Edge): 다음에 어디로 갈지
엣지는 노드와 노드를 잇는 화살표입니다. 항상 같은 곳으로 가는 보통 엣지와, 상태를 보고 갈 곳을 고르는 조건부 엣지가 있습니다. 후자가 체인과 갈라지는 지점입니다.
def should_retry(state: State) -> str:
if not state["documents"] and state["retry"] < 2:
return "rewrite" # 질의를 바꿔 다시 검색
return "generate" # 답변 생성으로 진행
graph.add_conditional_edges("retrieve", should_retry)
세 가지를 모으면 그래프가 됩니다. 아래는 검색이 부실하면 질의를 고쳐 다시 찾는, 가장 흔한 모양입니다.
체인에는 없는 것이 딱 하나 있습니다. 검색으로 되돌아가는 화살표, 즉 사이클입니다. LangGraph의 존재 이유를 하나만 꼽으라면 이 화살표입니다.
가장 흔한 모양: 도구를 쓰는 에이전트
생각하고, 쓰고, 다시 생각한다
실무에서 가장 많이 만나는 그래프는 "모델이 도구를 쓰는" 모양입니다. 모델에게 검색·계산기·사내 API 같은 도구 목록을 알려 주면, 모델은 답을 바로 내놓는 대신 "이 도구를 이렇게 불러 달라"고 요청합니다. 그래프는 그 도구를 실행해 결과를 상태에 붙이고 다시 모델에게 넘깁니다. 모델이 더는 도구를 찾지 않으면 그때 끝냅니다.
모델과 도구 사이를 몇 바퀴 돌 수 있다는 점이 전부입니다. 이 단순한 루프가 "알아서 찾아보고 답하는" 에이전트의 실체입니다.
코드로는 이 정도
그래프를 조립하는 코드는 생각보다 짧습니다. 상태를 정하고, 노드를 등록하고, 엣지를 잇고, compile()을 부르면 실행 가능한 객체가 나옵니다.
from langgraph.graph import StateGraph, START, END
builder = StateGraph(State)
builder.add_node("agent", call_model)
builder.add_node("tools", run_tools)
builder.add_edge(START, "agent")
builder.add_conditional_edges("agent", needs_tool, {"yes": "tools", "no": END})
builder.add_edge("tools", "agent") # 되돌아오는 화살표
app = builder.compile()
app.invoke({"question": "지난달 환불 건수를 알려줘", "messages": [], "retry": 0})
간단한 에이전트라면 이걸 매번 짤 필요도 없습니다. LangGraph는 위 모양을 그대로 만들어 주는 create_react_agent 같은 준비된 함수를 제공하고, 흐름을 손봐야 할 때 직접 조립으로 내려오면 됩니다.
그래프가 되면 따라오는 것들
흐름을 그래프로 선언하고 상태를 한 곳에 모으면, 그 구조 덕분에 따라오는 기능이 몇 가지 있습니다. 개념을 잡는 단계에서는 이 목록만 알아 두어도 충분합니다.
| 기능 | 무엇인가 | 언제 고마운가 |
|---|---|---|
| 체크포인트 | 매 단계 상태를 저장 | 중간에 끊겨도 그 지점부터 재개 |
| 사람 승인(HITL) | 특정 노드 앞에서 멈춤 | 결제·발송처럼 되돌리기 어려운 처리 |
| 스트리밍 | 단계별 중간 결과를 흘려보냄 | "검색 중…" 같은 진행 표시 |
| 시간 되감기 | 과거 상태에서 다시 시작 | 프롬프트만 바꿔 재현·비교 |
| 메모리 | 스레드 단위로 상태 유지 | 여러 턴에 걸친 대화 |
특히 체크포인트는 그래프 구조가 아니면 직접 만들기 번거로운 기능입니다. 단계마다 상태가 한 덩어리로 정의되어 있기 때문에, 그 덩어리를 저장했다가 그대로 되살리는 일이 자연스럽게 가능해집니다.
언제 쓰고 언제 쓰지 않는가
쓰지 않아도 되는 경우
LangGraph는 공짜가 아닙니다. 상태 스키마를 정의하고, 노드를 나누고, 엣지를 잇는 만큼의 코드와 개념이 늘어납니다. 한 번 호출하고 끝나는 요약·분류·번역 기능에 그래프를 씌우면 얻는 것 없이 읽을 것만 늘어납니다. 검색 한 번에 생성 한 번으로 끝나는 기본 RAG도 대개 체인으로 충분합니다.
쓰는 편이 나은 경우
반대로 분기와 반복이 들어오기 시작하면 판단이 뒤집힙니다. 아래 중 두 개 이상에 해당한다면 그래프로 가는 편이 대체로 낫습니다.
자주 나오는 오해 세 가지
"LangChain을 먼저 다 알아야 한다" — 아닙니다. LangGraph는 LangChain 없이도 쓸 수 있고, 노드 안에서 어떤 SDK로 모델을 부르든 상관하지 않습니다. 다만 메시지 타입이나 도구 정의처럼 LangChain의 공통 부품을 쓰면 편해지는 지점이 있습니다.
"에이전트를 만드는 도구다" — 절반만 맞습니다. 사이클이 있는 에이전트에 잘 맞지만, 사이클 없이 여러 갈래로 나뉘었다 합쳐지는 일반 워크플로우를 조립하는 데도 그대로 쓰입니다. 오히려 모델에게 자유를 덜 주고 경로를 고정하는 용도로도 자주 쓰입니다.
"LangSmith나 클라우드가 있어야 한다" — 아닙니다. 라이브러리 자체는 파이썬 패키지 하나이고 로컬에서 그대로 돕니다. 추적·배포 제품은 같은 팀의 별개 상품입니다.
맺음말
핵심 요약
LangGraph는 여러 번의 LLM 호출을 그래프로 묶어 실행해 주는 오케스트레이션 라이브러리입니다. 진행 상황을 담는 상태, 한 단계의 일을 맡는 노드, 다음에 어디로 갈지 정하는 엣지 세 가지가 전부이고, 여기에 체인에는 없는 사이클이 더해집니다. 조건에 따라 경로가 갈리거나 될 때까지 다시 시도해야 하는 흐름을, 코드 곳곳의 if와 while 대신 그래프 정의 한 곳에 모아 두는 것이 값어치의 대부분입니다.
적용 판단 기준
| 상황 | LangGraph가 맞나 | 이유 |
|---|---|---|
| 한 번 호출하고 끝나는 요약·분류 | 과함 | 체인 한 줄이면 충분 |
| 검색 1회 + 생성 1회의 기본 RAG | 대체로 과함 | 분기도 반복도 없음 |
| 검색 결과가 부실하면 다시 찾는 RAG | 맞음 | 사이클이 필요 |
| 도구를 골라 쓰는 에이전트 | 맞음 | 모델↔도구 루프가 기본 모양 |
| 사람 승인이 끼는 업무 자동화 | 맞음 | 체크포인트로 멈췄다 재개 |
개념이 잡혔다면 다음은 상태 스키마를 어떻게 쪼개고 여러 에이전트를 어떤 구조로 붙일지입니다. 그 부분은 LangGraph 상태 기반 멀티에이전트 워크플로우 구현에서 이어집니다.