-
LangGraph가 등장한 이유 — 선형 체인을 넘어 그래프로IT 2026. 6. 25. 21:00
LangChain이 해결한 문제는 명확했다. LLM을 쓸 수 있는 형태로 만들기 위한 반복 작업을 없애는 것이다. 그런데 에이전트를 실제로 만들다 보면 또 다른 벽에 부딪힌다.
"결과가 부족하면 다시 검색한다." 이 한 문장을 코드로 표현하는 순간, 선형 체인(Chain)은 한계를 드러낸다. 루프가 없기 때문이다. 체인은 A → B → C로 흘러갈 수는 있어도, "C가 충분하지 않으면 B로 돌아가라"는 구조를 표현할 방법이 없다.
LangGraph는 그 한계를 해결하기 위해 2024년에 등장했다. LangChain의 연장선이지만, 패러다임이 다르다. 선형(linear)에서 그래프(graph)로 달라졌다.
1. 배경 — LangChain의 체인(Chain)이 충분하지 않았던 이유
LangChain의 초기 핵심 개념인 "Chain"은 파이프라인이다. 프롬프트를 조합하고, LLM을 호출하고, 결과를 파싱해서 다음 단계로 넘긴다. 이 구조는 A → B → C처럼 항상 앞으로만 흐른다.
문제는 실제 에이전트 워크플로우가 선형이 아니라는 데 있다. 에이전트는 생각하고, 행동하고, 결과를 보고, 다시 판단한다. 이 과정에서 반복(루프), 조건(분기), 공유 문맥(상태)이 필수다. 선형 체인으로는 다음 세 가지를 표현할 수 없다.
아래 두 다이어그램을 비교해보자. 첫 번째는 선형 체인의 구조다.
이 다이어그램이 보여주는 것은 체인의 단순함이자 한계다. 흐름은 항상 아래로만 향한다. "결과 파싱이 충분하지 않으면 LLM 호출로 돌아가라"는 화살표가 없다. 루프 자체를 표현할 수단이 구조에 없기 때문이다. 체인은 파이프라인이지, 상태 기계(state machine)가 아니다.
실제 에이전트가 필요한 구조는 이렇다.
이 다이어그램에는 뒤로 향하는 화살표가 있다. Check에서 Judge로 돌아가는 화살표가 루프를 만든다. 또한 Judge에서 두 방향으로 갈라지는 분기도 있다. 선형 체인은 이 두 가지 — 루프와 분기 — 를 기본 구조로 지원하지 않는다. 코드로 억지로 구현할 수는 있지만, 그것은 프레임워크가 아니라 직접 루프를 짜는 것이다.
2. 문제 정의 — 체인으로 구현할 수 없는 3가지 패턴
에이전트를 선형 체인으로 구현하려 할 때 막히는 지점은 정확히 세 가지로 압축된다.
첫 번째, 루프(Loop). "결과가 충분하지 않으면 다시 시도"는 조건부 반복이다. 최대 3회 재시도, 혹은 검증 통과 시 종료 같은 로직을 선형 파이프라인에 끼워 넣으면 코드는 금세 뒤엉킨다.
이 패턴이 루프와 자기 수정의 핵심이다. LLM이 초안을 생성하면 검증 LLM이 품질을 평가하고, 기준 미달이면 다시 생성을 요청하는 루프다. 선형 체인은 검증에서 생성으로 향하는 화살표를 표현할 방법이 없다. 루프가 끝나는 조건(최대 횟수 초과, 또는 승인)도 체인 구조 밖에 있다. 이 전체 구조를 담으려면 체인이 아닌 다른 무언가가 필요하다.
두 번째, 분기(Branching). "질문 유형에 따라 다른 처리 노드로 라우팅"은 조건부 전환이다.
조건부 분기의 구조다. LLM이 입력을 코딩 / 리뷰 / 일반 세 카테고리로 분류하고, 각 카테고리에 특화된 노드로 라우팅한다. 선형 체인에서는 "A 다음에 B"는 표현되지만, "A 다음에 B 또는 C 또는 D, 상태에 따라"는 표현되지 않는다. 분기를 구현하려면 체인 바깥에서 if-else를 직접 써야 하고, 그러면 프레임워크가 구조를 보장할 수 없다.
세 번째, 영속 상태(Persistent State). 여러 노드에 걸쳐 누적되는 공유 컨텍스트다. 대화 이력, 중간 결과, 재시도 횟수 같은 값이 노드를 넘나들며 유지되어야 한다. 선형 체인은 앞에서 뒤로만 값을 전달하기 때문에, 루프가 돌아도 이전 결과를 기억하거나 누적 횟수를 추적하는 구조가 없다.
이 다이어그램이 보여주는 것은 상태가 노드들의 공유 칠판(shared blackboard)이라는 개념이다. 각 노드는 상태를 읽고, 변경할 부분만 반환한다. LangGraph 프레임워크가 그 반환값을 현재 상태에 병합(merge)한다. 체인은 값을 앞으로만 전달하지만, 그래프에서는 상태가 중앙에 존재하고 어느 노드든 접근할 수 있다. 루프가 돌아도 이전 노드가 반영한 값이 남아 있는 이유다.
3. LangGraph의 해결 방법 — 그래프(Graph)로 워크플로우를 정의
LangGraph는 이 세 가지 문제를 한 번에 해결하는 구조를 제시한다. 핵심 개념은 세 가지다.
Node(노드)는 하나의 처리 단위다. LLM 호출, 도구 실행, 조건 판단 모두 노드다. Python 함수 하나가 노드 하나가 된다. 노드는 현재 상태(State)를 입력받아 업데이트된 상태를 반환한다.
Edge(엣지)는 노드 간의 전환 경로다. 두 종류가 있다. 일반 엣지는 항상 특정 노드로 이동한다. 조건부 엣지는 상태의 값에 따라 어느 노드로 갈지를 함수가 결정한다. 분기와 루프는 모두 조건부 엣지로 표현된다.
State(상태)는 모든 노드가 공유하는 딕셔너리 형태의 컨텍스트다. TypedDict로 타입을 명시한다. 각 노드는 상태를 읽고, 변경이 필요한 키만 담아 반환한다. LangGraph 프레임워크가 그 반환값을 상태에 병합한다. 루프가 돌아도 상태는 유지되고 누적된다.
이 세 요소의 관계가 LangGraph의 전부다. Node는 State를 다루고, Edge는 Node 사이를 연결한다. 복잡해 보이는 에이전트 워크플로우도 결국 이 세 요소의 조합이다. 아래 코드 패턴으로 보자.
# State — 모든 노드가 공유하는 딕셔너리 컨텍스트 class AgentState(TypedDict): messages: Annotated[list[BaseMessage], add_messages] # Node — 상태를 받아 업데이트된 상태를 반환하는 함수 def agent_node(state: AgentState): response = llm_with_tools.invoke(state["messages"]) return {"messages": [response]} # Graph — 노드 이름과 함수를 매핑한 뒤 엣지로 연결 graph_builder = StateGraph(AgentState) graph_builder.add_node("agent", agent_node) # "agent" → agent_node 함수 graph_builder.add_node("tools", ToolNode(tools)) # "tools" → ToolNode가 도구 실행 처리 # Edge — 조건부 엣지가 분기와 루프를 만든다 graph_builder.add_conditional_edges("agent", should_use_tool) graph_builder.add_edge("tools", "agent") # 루프: 도구 실행 후 agent로 복귀마지막 두 줄이 핵심이다.
add_conditional_edges에 넘긴 함수(should_use_tool)가 분기를 만들고,add_edge("tools", "agent")가 루프를 만든다. 선형 체인에서는 이 두 줄을 쓸 수 없다. 노드는 "무엇을 할지"에만 집중하고, "다음에 어디로 갈지"는 엣지가 담당한다.4. 핵심 패턴 — ReAct 루프를 그래프로 표현
LangChain의
create_react_agent()가 내부적으로 LangGraph를 사용한다. 그러니까 LangChain 함수를 쓸 때 이미 LangGraph의 그래프 위에서 동작하고 있다. 그 내부 구조가 바로 ReAct 루프다.ReAct(Reasoning + Acting)는 "추론(Reason)하고 행동(Act)하는 사이클을 반복"하는 패턴이다. LLM이 생각하고, 도구를 쓰고, 결과를 보고, 다시 생각한다. 이 루프가 에이전트의 "이터레이션"이 발생하는 구조다.
이 다이어그램이
create_react_agent()의 실제 내부 그래프 구조와 동일하다. agent 노드가 LLM을 호출하고, 응답에tool_calls가 있으면 tools 노드로 이동해 도구를 실행한 뒤 다시 agent로 돌아온다.tool_calls가 없으면 루프를 탈출해 END로 향한다. 중요한 점은 이 루프가 몇 번을 돌지 미리 정해져 있지 않다는 것이다. LLM이 충분하다고 판단하는 순간, 즉 응답에tool_calls가 없을 때 자연스럽게 종료된다. 함정은 LLM이 판단을 못 내리거나 도구가 계속 실패하면 루프가 끝나지 않는다는 점이다.recursion_limit설정으로 최대 반복 횟수를 제한해야 프로덕션에서 안전하다.5. LangChain vs LangGraph — 언제 무엇을 쓰는가
create_react_agent()(LangChain 추상화)와 LangGraph 직접 사용 사이의 선택은 복잡도의 문제다. LangChain 추상화는 내부적으로 LangGraph를 쓰지만, 표준적인 ReAct 패턴을 빠르게 구현할 때 편리한 래퍼다. LangGraph를 직접 쓰는 것은 그 내부 구조를 손으로 조립하는 것이다 — 더 많은 코드가 필요하지만, 표현할 수 있는 패턴에 제한이 없다.이 결정 트리를 따르면 선택이 단순해진다. 단순한 도구 사용 에이전트라면
create_react_agent()로 충분하다. 그러나 세 가지 중 하나라도 필요하면 LangGraph를 직접 써야 한다. 첫째, 커스텀 루프 로직 — 검증 후 재생성, 최대 N회 재시도 같은 패턴. 둘째, Human-in-the-loop — 특정 노드에서 인간의 승인을 기다리는 패턴. 셋째, 멀티 에이전트 오케스트레이션 — 여러 에이전트가 상위 그래프에서 노드로 조율되는 구조. 노트북의 두 예제가 각각 LangGraph 직접 사용이 필요한 이유를 보여준다. 예제 1(조건부 분기)과 예제 2(루프와 자기 수정) 모두create_react_agent()로는 표현할 수 없다.6. 효과 — 무엇이 달라졌는가
LangGraph가 가져온 가장 큰 변화는 워크플로우를 코드 로직이 아닌 그래프 구조로 표현할 수 있게 됐다는 점이다. 선형 체인 시절에는 루프와 분기를 Python if-else와 while로 직접 구현해야 했다. 그러면 로직이 코드 안에 숨어들고, 전체 흐름을 한눈에 파악하기 어려워진다.
LangGraph에서는 노드와 엣지로 선언하면 그래프가 흐름도가 된다. 어느 조건에서 어느 노드로 가는지, 루프가 어디서 시작해서 어디서 끝나는지가 구조에 드러난다. 특히 중요한 추가 효과가 세 가지 있다.
Checkpointer와 중간 상태 저장. LangGraph는 각 노드 실행 후 상태를 체크포인트로 저장하는 기능을 제공한다. 덕분에 실행 중에 중단되더라도 마지막 체크포인트에서 재개할 수 있다. 장시간 실행되는 복잡한 에이전트에서 필수 기능이다.
Human-in-the-loop. 특정 노드에서 그래프 실행을 일시 정지하고 인간의 입력을 기다리는 패턴을 구현할 수 있다. 에이전트가 민감한 행동(외부 API 호출, 데이터베이스 쓰기 등)을 하기 전에 인간의 승인을 받는 구조를 그래프 레벨에서 정의한다.
멀티 에이전트 오케스트레이션. 여러 에이전트를 상위 그래프의 노드로 편입시킬 수 있다.
이 다이어그램에서 Math Expert 노드와 Research 노드는 각각 독립적인 에이전트 — 즉 각자의 내부 루프와 도구를 가진 LangGraph 그래프 — 다. 상위 그래프는 Supervisor가 사용자 의도를 파악해 적절한 에이전트 노드로 라우팅한다. 중요한 점은 하위 에이전트가 상위 그래프에게는 그냥 하나의 노드처럼 보인다는 것이다. 이 구조로 복잡한 멀티 에이전트 시스템을 계층적으로 구성할 수 있다. 함정은 에이전트 수가 늘수록 디버깅이 어려워진다는 점이다. 어느 에이전트의 어느 노드에서 문제가 생겼는지 추적하려면
stream_mode="updates"를 활용해 각 노드의 상태 변화를 로깅해야 한다.7. 마무리
LangGraph는 LangChain의 "다음 레이어"다. LangChain이 LLM을 쓸 수 있는 형태로 만들었다면, LangGraph는 그 LLM으로 구성한 에이전트를 복잡한 워크플로우로 엮는 방법을 제공한다.
지금 당장
create_react_agent()로 충분한 수준이라면 LangGraph를 직접 쓸 이유가 없다. 하지만 에이전트가 복잡해지는 순간 — "실패하면 다시 시도", "입력에 따라 다른 에이전트로", "인간이 최종 확인" — 자연스럽게 LangGraph가 필요해진다. 선형 체인으로 구현하던 것이 if-else와 while로 뒤엉키기 시작하는 순간이 바로 그 신호다.선형에서 그래프로의 전환은 단순히 API 차이가 아니다. "워크플로우를 코드 안에 숨기느냐, 구조로 드러내느냐"의 차이다. 그 차이가 시스템을 유지보수할 수 있는 형태로 만드느냐를 결정한다.
이 글은 생성형 AI의 도움을 받아 작성되었습니다. 원본 자료를 기반으로 AI가 초안을 생성하고, 작성자가 검토·편집하였습니다.
'IT' 카테고리의 다른 글
LangGraph 상태에 메시지 외 필드 추가하기 — RouterState 설계 (0) 2026.06.26 graph.invoke vs graph.stream — 에이전트 실행의 두 가지 방식 (1) 2026.06.26 bind_tools — LLM이 도구를 인식하는 방법 (0) 2026.06.26 LangGraph StateGraph 완전 분해 — 노드·엣지·조건부 라우팅 (1) 2026.06.25 LangGraph의 상태(State) 설계 — Annotated와 add_messages가 하는 일 (0) 2026.06.25 Retriever를 에이전트 도구로 — RAG 패턴 구현 (0) 2026.06.24 LLM에 코드 실행 능력 붙이기 — PythonAstREPLTool (0) 2026.06.24 Pydantic Field로 LLM 출력 스키마를 제약하는 방법 (0) 2026.06.23 ProviderStrategy vs ToolStrategy — 구조화 출력 전략 선택 (0) 2026.06.23 LangChain ToolRuntime으로 런타임 컨텍스트 주입하기 (0) 2026.06.23