Post

[시니어의 시선] 챗봇의 시대를 넘어: LangGraph로 설계하는 '스스로 일하는' Agentic Workflow 딥다이브

[시니어의 시선] 챗봇의 시대를 넘어: LangGraph로 설계하는 '스스로 일하는' Agentic Workflow 딥다이브

10년 넘게 백엔드와 인프라를 굴리면서 수많은 프레임워크의 흥망성쇠를 지켜봤습니다. MVC 패턴의 유행부터 MSA의 광기, 그리고 최근 몇 년간 온 세상을 집어삼킨 LLM의 등장까지 말이죠. 그런데 최근 30일 동안, 제 머릿속을 가장 강렬하게 때린 화두는 단연코 ‘Agentic Workflow(에이전트 기반 워크플로우)’와 이를 구현하는 LangGraph입니다.

솔직히 고백하자면, 저는 작년까지만 해도 AI 에이전트에 대해 꽤 회의적이었습니다. AutoGPT 같은 오픈소스들이 화려하게 등장했지만, 막상 실무에 도입하려고 하면 ‘통제 불가능한 블랙박스’에 불과했거든요. 프롬프트를 조금만 길게 줘도 무한 루프에 빠지거나, 엉뚱한 API를 호출하고는 해맑게 “작업을 완료했습니다!”라고 거짓말(Hallucination)을 하는 꼴을 보며 뒷목을 잡은 게 한두 번이 아닙니다. 기존 LangChain의 AgentExecutor나 체인(Chain) 구조는 너무 경직되어 있어서, 예외 상황이 발생했을 때 스스로 실수를 바로잡는 유연한 흐름을 만들기가 거의 불가능에 가까웠죠.

“우리가 원한 건 정해진 궤도만 달리는 기차가 아니라, 장애물을 만나면 우회할 줄 아는 자율주행 자동차였습니다.”

이런 갈증을 느끼던 찰나, LangGraph가 등장했습니다. 그리고 이 녀석의 내부 아키텍처를 뜯어보는 순간, 오랜만에 등골이 짜릿해지는 기분을 느꼈습니다.

TL;DR

Agentic Workflow는 단순히 프롬프트를 잘 쓰는 기술이 아니라 LLM에게 ‘상태(State)’와 ‘흐름(Control Flow)’을 부여하는 시스템 엔지니어링입니다. LangGraph는 이 복잡한 비선형적 흐름을 순환 그래프(Cyclic Graph) 구조와 체크포인터(Checkpointer)를 통해 실무 수준에서 완벽하게 통제할 수 있게 해주는 가장 강력한 오케스트레이션 도구입니다.

Deep Dive: Under the Hood (왜 그래프인가?)

표면적인 기능 나열은 접어두고, 아키텍처 깊숙한 곳으로 들어가 봅시다. 기존의 RAG나 간단한 챗봇 파이프라인은 DAG(Directed Acyclic Graph, 방향성 비순환 그래프) 구조였습니다. 입력이 들어오면 A -> B -> C를 거쳐 출력을 뱉고 끝나는 일방통행이죠. 하지만 사람이 일하는 방식은 이렇지 않습니다. 코드를 짜고(A), 테스트를 돌려보고(B), 에러가 나면 다시 코드를 수정(A)합니다. 즉, 스스로 생각하고 행동하는 에이전트를 만들려면 반드시 ‘순환(Cycle)’을 표현할 수 있어야 합니다.

LangGraph는 이 순환을 우아하게 처리하기 위해 내부적으로 구글의 Pregel 아키텍처(그래프 처리 모델) 개념을 차용했습니다. 핵심은 세 가지입니다: State, Node, Edge.

1. 중앙 집중식 상태 관리 (The State) 가장 감탄했던 부분입니다. LangGraph는 프론트엔드의 Redux나 Vuex처럼 글로벌 상태(State) 객체를 가집니다. Python의 TypedDict나 Pydantic을 이용해 상태의 스키마를 정의하죠.

1
2
3
4
5
6
7
from typing import Annotated, TypedDict
import operator

class AgentState(TypedDict):
    messages: Annotated[list, operator.add]
    current_context: str
    retry_count: int

여기서 주목할 점은 Annotated[list, operator.add]라는 리듀서(Reducer)의 존재입니다. 노드(Node)들이 실행될 때마다 기존 상태를 덮어쓰는 게 아니라, operator.add를 통해 메시지 리스트에 새로운 메시지를 병합(Append)합니다. 이를 통해 에이전트가 어떤 생각의 과정을 거쳤는지 컨텍스트가 완벽하게 유지됩니다.

2. 조건부 엣지와 순환 (Conditional Edges) 노드(Node)는 단순히 Python 함수입니다. LLM을 호출하든, 외부 API를 찌르든, DB를 조회하든 상관없습니다. 진짜 마법은 조건부 엣지(Conditional Edges)에서 일어납니다. 특정 노드의 실행이 끝난 후, 다음 노드로 갈지 아니면 이전 노드로 돌아갈지를 동적으로 결정합니다.

3. 타임머신과 디버깅 (Checkpointers & Time Travel) 실무에서 에이전트를 돌리다 보면 반드시 뻗는 구간이 생깁니다. LangGraph는 SqliteSaverPostgresSaver를 통해 매 노드가 실행될 때마다 State의 스냅샷을 DB에 저장합니다. 만약 에이전트가 5단계에서 치명적인 논리적 오류를 범했다면? 개발자는 4단계의 스냅샷을 불러와 State의 변수(예: 프롬프트나 파라미터)를 직접 수정한 뒤, 그 시점부터 다시 실행(Resume)할 수 있습니다. 이건 백엔드 개발자 입장에서 정말 미친 기능입니다. 상태의 영속성(Persistence)을 프레임워크 레벨에서 보장한다는 뜻이니까요.

Hands-on: 당장 내 프로젝트에 어떻게 쓸까?

‘그래서 이걸로 뭘 할 수 있는데?’라는 질문이 자연스럽게 떠오르실 겁니다. 저는 최근 사내 데이터 분석 파이프라인에 LangGraph를 도입했습니다.

이전에는 기획자가 데이터를 요청하면 개발자가 SQL을 짜서 추출해 줬습니다. 이제는 ‘데이터 분석 에이전트’가 그 역할을 대신합니다.

  1. 기획자 의도 파악 노드: 기획자의 자연어 요청을 분석해 필요한 테이블과 스키마를 확인합니다.
  2. SQL 작성 노드: LLM이 SQL 쿼리를 작성합니다.
  3. 실행 및 검증 노드 (안전 샌드박스): 실제로 DB(Read-only)에 쿼리를 날려봅니다.
  4. 에러 리플렉션(Reflection) 노드: 만약 구문 에러가 발생하거나 결과가 이상하면, 실행 노드에서 발생한 에러 로그를 캡처하여 다시 ‘SQL 작성 노드’로 보냅니다. (이게 바로 Cycle입니다)
  5. Human-in-the-loop: 최종 쿼리가 완성되면 실행하기 전 interrupt_before 설정을 통해 슬랙(Slack)으로 시니어 개발자의 승인(Approve)을 요청합니다.

이 워크플로우를 도입한 후, 데이터 추출에 들어가던 단순 반복 업무 시간이 80% 이상 줄었습니다. 단순한 챗봇이었다면 에러가 날 때마다 기획자가 다시 프롬프트를 입력해야 했겠지만, 에이전트는 스스로 에러를 읽고 쿼리를 수정해 냅니다.

Honest Review: 빛이 강하면 그림자도 짙은 법

물론 장밋빛 미래만 있는 건 아닙니다. 현업에서 직접 부딪혀보며 느낀 LangGraph의 치명적인 단점과 트레이드오프를 공유합니다.

첫째, 악랄한 러닝 커브와 디버깅의 늪. 기존 선형적인 코드 흐름에 익숙한 백엔드 개발자들에게, 상태 기반의 비동기 그래프 실행 흐름은 디버깅을 지옥으로 만듭니다. 노드 간에 상태가 어떻게 변이(Mutation)되었는지 터미널 로그만으로는 추적하기가 불가능에 가깝습니다. 결국 시각화 도구인 LangSmith에 강력하게 의존하게 되는데, 이는 벤더 락인(Vendor Lock-in)이자 만만치 않은 비용 청구서로 돌아옵니다.

둘째, 컨텍스트 윈도우 폭발 (Token Bloat). 순환 구조는 양날의 검입니다. LLM이 에러를 잡겠다고 10번, 20번씩 루프를 돌기 시작하면 State의 messages 배열이 기하급수적으로 길어집니다. 순식간에 100k 토큰을 훌쩍 넘겨버리고, API 호출 비용은 폭발하며, LLM은 앞선 컨텍스트를 망각하기 시작합니다. 이를 방지하기 위해 일정 루프 이상을 돌면 오래된 메시지를 요약(Summarize)하거나 잘라내는(Truncate) 복잡한 상태 관리 로직을 개발자가 직접 구현해야 합니다.

셋째, 여전히 해결되지 않은 비결정성(Non-determinism). 아무리 오케스트레이션을 잘해도 결국 코어 노드에서 동작하는 것은 확률 모델인 LLM입니다. 어제는 완벽하게 통과했던 그래프가, 오늘은 LLM이 갑자기 엉뚱한 변수를 생성하는 바람에 조건부 엣지에서 길을 잃고 미아 상태가 되기도 합니다. 에이전트 시스템을 운영한다는 것은 이 끝없는 ‘확률과의 싸움’을 견뎌내는 과정이더라고요.

Closing Thoughts

최근 한 달간 LangGraph를 뜯어보고 실무에 적용하면서 느낀 점은 명확합니다. 개발자의 역할이 변하고 있습니다. 과거의 우리가 ‘명시적인 로직(Logic)을 타이핑하는 코더’였다면, 이제는 ‘불확실성을 가진 AI 작업자들을 적재적소에 배치하고, 그들의 상태와 흐름을 통제하는 시스템 오케스트레이터(Orchestrator)’로 진화해야 합니다.

LangGraph는 그 진화의 방향을 가장 뚜렷하게 보여주는 프레임워크입니다. 완벽하진 않지만, 우리가 그토록 원했던 ‘통제 가능한 자율성’의 뼈대를 제공하죠. 이번 주말, 여러분도 뻔한 API 연동에서 벗어나 여러분만의 작은 자율형 워크플로우를 설계해 보는 것은 어떨까요? 프롬프트 창 너머, 상태(State)와 그래프(Graph)가 만들어내는 진짜 에이전트의 세계가 여러분을 기다리고 있습니다.

References

  • https://python.langchain.com/docs/langgraph
  • https://github.com/langchain-ai/langgraph
  • https://smith.langchain.com/
This post is licensed under CC BY 4.0 by the author.