본 콘텐츠의 이미지 및 내용은 AI로 생성되었습니다.
본 콘텐츠의 이미지 및 내용을 무단으로 복제, 배포, 수정하여 사용할 경우 저작권법에 의해 법적 제재를 받을 수 있습니다.
이미지 로딩 중...
AI Generated
2025. 12. 12. · 13 Views
Graph API StateGraph 기초 완벽 가이드
LangGraph의 StateGraph를 활용하여 AI 워크플로우를 구축하는 방법을 초급자도 쉽게 이해할 수 있도록 설명합니다. 그래프의 3요소부터 실행까지 실무 사례로 배워봅시다.
목차
- StateGraph 클래스 이해
- 그래프의 3요소: State, Node, Edge
- add_node() 노드 추가
- add_edge() 엣지 추가
- compile() 그래프 컴파일
- invoke() 실행하기
- 최종 State 반환:
{'number': 15, 'result': '최종 결과: 15'}
1. StateGraph 클래스 이해
어느 날 김개발 씨가 AI 챗봇 프로젝트를 시작하게 되었습니다. 선배 박시니어 씨가 말했습니다.
"이번에는 LangGraph의 StateGraph를 사용해보세요. 복잡한 AI 워크플로우를 쉽게 관리할 수 있답니다."
StateGraph는 LangGraph에서 상태 기반 워크플로우를 구성하는 핵심 클래스입니다. 마치 퍼즐 조각들을 연결하듯이, 여러 단계의 작업을 그래프 형태로 연결할 수 있습니다.
AI 에이전트나 복잡한 처리 파이프라인을 만들 때 매우 유용합니다.
다음 코드를 살펴봅시다.
from langgraph.graph import StateGraph
from typing import TypedDict
# State 타입 정의
class GraphState(TypedDict):
message: str
count: int
# StateGraph 인스턴스 생성
graph = StateGraph(GraphState)
# 이제 노드와 엣지를 추가할 준비가 되었습니다
print("StateGraph 생성 완료!")
김개발 씨는 입사 6개월 차 주니어 개발자입니다. 최근 회사에서 고객 문의를 처리하는 AI 챗봇을 개발하라는 과제를 받았습니다.
단순히 질문에 답변하는 것이 아니라, 여러 단계를 거쳐야 했습니다. 먼저 고객의 의도를 파악하고, 데이터베이스를 검색하고, 결과를 정리해서 답변해야 합니다.
김개발 씨는 고민에 빠졌습니다. "이걸 어떻게 깔끔하게 구현하지?" 박시니어 씨가 다가와 말했습니다.
"LangGraph의 StateGraph를 사용해보세요. 복잡한 워크플로우를 시각적으로 관리할 수 있답니다." 그렇다면 StateGraph란 정확히 무엇일까요?
쉽게 비유하자면, StateGraph는 마치 요리 레시피와 같습니다. 요리할 때 재료 준비, 썰기, 볶기, 간하기 같은 단계가 있듯이, AI 작업도 여러 단계로 나눌 수 있습니다.
StateGraph는 이런 단계들을 정의하고 순서를 정해주는 역할을 합니다. StateGraph가 없던 시절에는 어땠을까요?
개발자들은 복잡한 if-else 문과 함수 호출로 워크플로우를 관리했습니다. 코드가 스파게티처럼 엉켜 있었고, 새로운 단계를 추가하거나 순서를 바꾸기가 매우 어려웠습니다.
더 큰 문제는 각 단계의 상태를 추적하기가 힘들었다는 것입니다. 프로젝트가 커질수록 "이 변수가 어디서 바뀌었지?" 같은 질문에 답하기 위해 수십 개의 파일을 뒤져야 했습니다.
디버깅은 악몽이었습니다. 바로 이런 문제를 해결하기 위해 StateGraph가 등장했습니다.
StateGraph를 사용하면 워크플로우를 시각적으로 이해하기 쉬운 그래프 구조로 표현할 수 있습니다. 또한 각 단계의 상태를 명확하게 추적할 수 있습니다.
무엇보다 유지보수가 쉬워진다는 큰 이점이 있습니다. 위의 코드를 한 줄씩 살펴보겠습니다.
먼저 StateGraph와 TypedDict를 임포트합니다. TypedDict는 파이썬에서 타입을 명시하는 도구입니다.
다음으로 GraphState 클래스를 정의합니다. 이것이 바로 우리 그래프가 관리할 상태의 구조입니다.
message는 문자열, count는 정수를 저장합니다. 그리고 StateGraph(GraphState)로 그래프 인스턴스를 생성합니다.
이제 이 그래프에 노드와 엣지를 추가할 준비가 끝났습니다. 실제 현업에서는 어떻게 활용할까요?
예를 들어 전자상거래 사이트에서 주문 처리 시스템을 개발한다고 가정해봅시다. 주문 접수 → 재고 확인 → 결제 처리 → 배송 준비 같은 여러 단계가 있습니다.
StateGraph를 사용하면 각 단계를 노드로 만들고, 상태를 공유하면서 순차적으로 처리할 수 있습니다. 네이버, 카카오 같은 대기업에서도 복잡한 AI 파이프라인을 구축할 때 이런 그래프 기반 접근법을 적극 활용하고 있습니다.
하지만 주의할 점도 있습니다. 초보 개발자들이 흔히 하는 실수 중 하나는 상태 타입을 너무 복잡하게 정의하는 것입니다.
처음부터 모든 필드를 넣으려다 보면 코드가 복잡해집니다. 따라서 필요한 필드만 간단하게 정의하고, 나중에 필요하면 추가하는 것이 좋습니다.
다시 김개발 씨의 이야기로 돌아가 봅시다. 박시니어 씨의 설명을 들은 김개발 씨는 고개를 끄덕였습니다.
"아, 그래서 그래프라는 이름이 붙었군요!" StateGraph를 제대로 이해하면 복잡한 AI 워크플로우도 명확하게 구조화할 수 있습니다. 여러분도 오늘 배운 내용을 실제 프로젝트에 적용해 보세요.
실전 팁
💡 - 상태 타입은 TypedDict로 명확하게 정의하세요
- 처음에는 간단한 필드만 포함하고 점진적으로 확장하세요
2. 그래프의 3요소: State, Node, Edge
StateGraph를 만들었으니 이제 뭘 해야 할까요? 김개발 씨가 박시니어 씨에게 물었습니다.
"그래프에 뭘 추가해야 하나요?" 박시니어 씨는 웃으며 대답했습니다. "그래프는 세 가지 요소로 이루어져 있어요.
State, Node, Edge입니다."
그래프는 State(상태), Node(노드, 작업 단위), Edge(엣지, 연결선) 세 가지 핵심 요소로 구성됩니다. State는 데이터를 저장하고, Node는 작업을 수행하며, Edge는 실행 순서를 결정합니다.
이 세 요소를 이해하면 어떤 복잡한 워크플로우도 구성할 수 있습니다.
다음 코드를 살펴봅시다.
from langgraph.graph import StateGraph
from typing import TypedDict
# 1. State: 데이터 구조 정의
class GraphState(TypedDict):
input_text: str
processed: bool
result: str
# 2. Node: 작업을 수행하는 함수
def process_node(state: GraphState) -> GraphState:
# 입력 텍스트를 대문자로 변환
state["result"] = state["input_text"].upper()
state["processed"] = True
return state
# 3. Edge: 노드 간 연결 (다음 섹션에서 자세히)
# add_edge("A", "B")는 A 노드 다음에 B 노드 실행
김개발 씨는 StateGraph를 생성했지만, 아직 빈 껍데기일 뿐이었습니다. "이제 뭘 해야 하지?" 고민하던 김개발 씨에게 박시니어 씨가 화이트보드를 꺼내 들었습니다.
"그래프를 만들려면 세 가지를 이해해야 해요. State, Node, Edge입니다." 박시니어 씨가 화이트보드에 동그라미와 화살표를 그리기 시작했습니다.
그렇다면 이 세 요소는 각각 무엇일까요? 쉽게 비유하자면, 그래프는 마치 공장의 생산 라인과 같습니다.
State는 원자재와 중간 생산물을 담는 컨베이어 벨트입니다. Node는 각 작업 공정입니다.
조립하고, 검사하고, 포장하는 단계들이죠. Edge는 컨베이어 벨트의 방향을 결정하는 레일입니다.
어느 공정 다음에 어느 공정으로 갈지 정해줍니다. 이 세 요소가 없던 시절에는 어땠을까요?
개발자들은 전역 변수에 데이터를 저장하고, 함수를 하나씩 호출하면서 결과를 전달했습니다. 문제는 "지금 이 데이터가 어디서 왔지?" 같은 질문에 답하기 어려웠다는 것입니다.
함수 호출 순서를 바꾸려면 코드 여러 곳을 수정해야 했습니다. 더 큰 문제는 병렬 처리나 조건부 분기를 구현하기가 매우 복잡했다는 것입니다.
"A가 끝나면 B와 C를 동시에 실행하고, 결과에 따라 D 또는 E로 가야 해"라는 요구사항을 구현하려면 머리가 지끈거렸습니다. 바로 이런 문제를 해결하기 위해 그래프 구조가 등장했습니다.
State를 사용하면 데이터의 흐름을 명확하게 추적할 수 있습니다. Node를 사용하면 각 작업을 독립적으로 개발하고 테스트할 수 있습니다.
Edge를 사용하면 실행 순서를 선언적으로 정의할 수 있습니다. 무엇보다 코드의 의도가 명확해진다는 큰 이점이 있습니다.
위의 코드를 한 줄씩 살펴보겠습니다. 먼저 GraphState 클래스가 State를 정의합니다.
input_text는 사용자 입력을, processed는 처리 여부를, result는 최종 결과를 저장합니다. 이것이 그래프 전체에서 공유되는 데이터 구조입니다.
다음으로 process_node 함수가 Node입니다. 이 함수는 State를 받아서, 작업을 수행하고, 수정된 State를 반환합니다.
여기서는 입력 텍스트를 대문자로 변환하는 간단한 작업을 합니다. Edge는 코드에 직접 나타나지 않지만, add_edge() 함수로 추가합니다.
다음 섹션에서 자세히 배울 예정입니다. 실제 현업에서는 어떻게 활용할까요?
예를 들어 이력서 분석 시스템을 만든다고 가정해봅시다. State에는 원본 이력서 텍스트, 파싱된 데이터, 점수 같은 정보가 들어갑니다.
Node는 PDF 파싱, 텍스트 분석, 점수 계산 같은 각 단계입니다. Edge는 "파싱 → 분석 → 점수 계산" 같은 순서를 정의합니다.
링크드인, 원티드 같은 채용 플랫폼에서는 이런 그래프 구조로 복잡한 데이터 처리 파이프라인을 구축하고 있습니다. 하지만 주의할 점도 있습니다.
초보 개발자들이 흔히 하는 실수 중 하나는 Node에서 State를 직접 수정하지 않고 새로운 딕셔너리를 반환하는 것입니다. 위 예제처럼 기존 state를 수정하고 반환해야 합니다.
그렇지 않으면 이전 상태가 사라질 수 있습니다. 또 다른 실수는 Node 함수를 너무 크게 만드는 것입니다.
한 Node는 한 가지 일만 해야 합니다. 여러 일을 한다면 여러 Node로 나누는 것이 좋습니다.
다시 김개발 씨의 이야기로 돌아가 봅시다. 박시니어 씨의 설명을 들은 김개발 씨는 화이트보드를 바라보며 말했습니다.
"아, 마치 레고 블록을 조립하는 것 같네요!" State, Node, Edge를 제대로 이해하면 복잡한 워크플로우도 간단한 조각으로 나눠서 생각할 수 있습니다. 여러분도 오늘 배운 내용을 실제 프로젝트에 적용해 보세요.
실전 팁
💡 - State는 그래프 전체에서 공유되는 데이터 저장소입니다
- Node는 한 가지 작업만 수행하도록 작게 만드세요
- Edge는 실행 순서를 명확하게 정의합니다
3. add node() 노드 추가
이제 그래프에 실제 작업을 추가할 차례입니다. 김개발 씨가 코드를 작성하려고 하자, 박시니어 씨가 말했습니다.
"먼저 add_node()로 노드를 추가해야 해요. 노드 이름과 함수를 지정하면 됩니다."
add_node() 메서드는 그래프에 작업 노드를 추가합니다. 첫 번째 파라미터는 노드 이름(문자열), 두 번째는 실행할 함수입니다.
각 노드 함수는 State를 받아서 수정된 State를 반환해야 합니다. 이렇게 추가된 노드들이 실제 작업을 수행합니다.
다음 코드를 살펴봅시다.
from langgraph.graph import StateGraph
from typing import TypedDict
class GraphState(TypedDict):
message: str
count: int
# 첫 번째 노드 함수
def step_one(state: GraphState) -> GraphState:
state["message"] = "첫 번째 단계 완료"
state["count"] = state.get("count", 0) + 1
return state
# 두 번째 노드 함수
def step_two(state: GraphState) -> GraphState:
state["message"] += " -> 두 번째 단계 완료"
state["count"] += 1
return state
graph = StateGraph(GraphState)
# 노드 추가
graph.add_node("step1", step_one)
graph.add_node("step2", step_two)
김개발 씨는 드디어 그래프에 실제 작업을 추가할 준비가 되었습니다. 하지만 어떻게 추가해야 할지 막막했습니다.
박시니어 씨가 키보드를 가져와 직접 보여주기 시작했습니다. "노드를 추가하는 건 아주 간단해요.
add_node()만 호출하면 됩니다." 박시니어 씨가 코드를 타이핑했습니다. 그렇다면 add_node()는 정확히 어떻게 동작할까요?
쉽게 비유하자면, add_node()는 마치 식당에 주방 직원을 배치하는 것과 같습니다. "이 직원은 '전처리' 역할이고, 이름은 prep_chef입니다"라고 등록하는 것이죠.
나중에 "prep_chef 일 시작!"이라고 부르면 그 직원이 자신의 일을 합니다. 노드를 추가하지 않으면 어떻게 될까요?
그래프는 빈 껍데기일 뿐입니다. 아무것도 실행되지 않습니다.
마치 공장 건물만 지어놓고 기계를 설치하지 않은 것과 같습니다. 원자재가 들어와도 처리할 수 없습니다.
실제로 초보 개발자들이 StateGraph를 만들고 바로 compile()하려다가 에러를 만나는 경우가 많습니다. "노드가 하나도 없는데요?"라는 에러 메시지를 보고 당황하죠.
바로 이런 문제를 해결하기 위해 add_node()가 있습니다. add_node()를 사용하면 그래프에 실제 기능을 추가할 수 있습니다.
또한 노드 이름으로 쉽게 참조할 수 있습니다. 나중에 엣지를 연결할 때 이 이름을 사용합니다.
무엇보다 각 노드를 독립적으로 개발하고 테스트할 수 있다는 큰 이점이 있습니다. 위의 코드를 한 줄씩 살펴보겠습니다.
먼저 step_one 함수를 정의합니다. 이 함수는 state를 받아서 message를 업데이트하고, count를 1 증가시킵니다.
state.get("count", 0)는 count가 없으면 0을 기본값으로 사용한다는 의미입니다. step_two 함수도 비슷합니다.
기존 message에 문자열을 추가하고, count를 또 1 증가시킵니다. 이처럼 각 노드는 이전 상태를 받아서 수정하고 반환합니다.
그리고 graph.add_node("step1", step_one)으로 첫 번째 노드를 추가합니다. "step1"이라는 이름으로 step_one 함수를 등록하는 것입니다.
마찬가지로 "step2"도 추가합니다. 실제 현업에서는 어떻게 활용할까요?
예를 들어 뉴스 요약 서비스를 개발한다고 가정해봅시다. "기사 크롤링" 노드, "텍스트 전처리" 노드, "AI 요약" 노드, "결과 저장" 노드를 각각 만들 수 있습니다.
각 노드는 독립적으로 개발하고, 나중에 그래프로 연결하면 됩니다. 네이버 뉴스, 구글 뉴스 같은 서비스에서도 이런 식으로 복잡한 처리 파이프라인을 모듈화해서 관리하고 있습니다.
하지만 주의할 점도 있습니다. 초보 개발자들이 흔히 하는 실수 중 하나는 노드 이름을 중복해서 사용하는 것입니다.
같은 이름으로 add_node()를 두 번 호출하면 나중 것이 이전 것을 덮어씁니다. 따라서 각 노드에 고유한 이름을 붙여야 합니다.
또 다른 실수는 노드 함수에서 State를 반환하지 않는 것입니다. 반드시 수정된 state를 return해야 합니다.
그렇지 않으면 다음 노드가 None을 받게 됩니다. 다시 김개발 씨의 이야기로 돌아가 봅시다.
박시니어 씨의 설명을 들은 김개발 씨는 직접 코드를 작성해봤습니다. "오, 정말 간단하네요!" add_node()를 제대로 이해하면 복잡한 작업도 작은 단위로 나눠서 관리할 수 있습니다.
여러분도 오늘 배운 내용을 실제 프로젝트에 적용해 보세요.
실전 팁
💡 - 노드 이름은 고유해야 합니다 (중복 사용 금지)
- 노드 함수는 반드시 State를 반환해야 합니다
- 노드는 한 가지 일만 하도록 작게 만드세요
4. add edge() 엣지 추가
노드를 추가했지만, 아직 실행 순서가 정해지지 않았습니다. 김개발 씨가 물었습니다.
"step1 다음에 step2를 실행하려면 어떻게 해야 하나요?" 박시니어 씨가 대답했습니다. "add_edge()로 엣지를 추가하면 됩니다."
add_edge() 메서드는 노드 간의 연결을 추가합니다. 첫 번째 파라미터는 시작 노드 이름, 두 번째는 도착 노드 이름입니다.
엣지를 추가하면 시작 노드가 끝난 후 자동으로 도착 노드가 실행됩니다. 이렇게 워크플로우의 실행 순서를 정의합니다.
다음 코드를 살펴봅시다.
from langgraph.graph import StateGraph, END
from typing import TypedDict
class GraphState(TypedDict):
value: int
def increment(state: GraphState) -> GraphState:
state["value"] = state.get("value", 0) + 1
return state
def double(state: GraphState) -> GraphState:
state["value"] = state["value"] * 2
return state
graph = StateGraph(GraphState)
graph.add_node("increment", increment)
graph.add_node("double", double)
# 엣지 추가: increment -> double -> 종료
graph.set_entry_point("increment") # 시작점 지정
graph.add_edge("increment", "double") # increment 다음에 double 실행
graph.add_edge("double", END) # double 다음에 종료
김개발 씨는 step1과 step2 노드를 추가했습니다. 하지만 아직 이 둘은 서로 독립적입니다.
연결되지 않았기 때문에 그래프가 어떤 순서로 실행해야 할지 모릅니다. 박시니어 씨가 화이트보드에 화살표를 그리며 설명을 시작했습니다.
"노드는 점이고, 엣지는 선이에요. 선으로 점을 연결하면 경로가 만들어집니다." 박시니어 씨가 간단한 그림을 그렸습니다.
그렇다면 add_edge()는 정확히 무엇을 하는 걸까요? 쉽게 비유하자면, add_edge()는 마치 지하철 노선도를 그리는 것과 같습니다.
역은 이미 만들어져 있습니다(노드). 하지만 어느 역에서 어느 역으로 가는지 선로를 깔아야 합니다(엣지).
"강남역 다음은 신논현역"처럼 경로를 정의하는 것이죠. 엣지를 추가하지 않으면 어떻게 될까요?
노드들은 각자 존재하지만 연결되지 않았습니다. 그래프를 실행하려고 하면 에러가 발생합니다.
"시작점이 어디죠? 다음에 뭘 실행해야 하죠?" 같은 질문에 답할 수 없기 때문입니다.
실제로 많은 초보 개발자들이 노드만 추가하고 엣지를 빼먹어서 "그래프를 실행할 수 없습니다"라는 에러를 만납니다. 마치 재료는 준비했는데 요리 순서를 정하지 않은 것과 같습니다.
바로 이런 문제를 해결하기 위해 add_edge()가 있습니다. add_edge()를 사용하면 워크플로우의 실행 순서를 명확하게 정의할 수 있습니다.
또한 복잡한 분기와 병합도 표현할 수 있습니다. 무엇보다 코드만 봐도 데이터의 흐름을 이해할 수 있다는 큰 이점이 있습니다.
위의 코드를 한 줄씩 살펴보겠습니다. 먼저 increment와 double 두 노드를 추가합니다.
increment는 값을 1 증가시키고, double은 값을 2배로 만듭니다. graph.set_entry_point("increment")는 그래프의 시작점을 지정합니다.
실행이 시작되면 가장 먼저 increment 노드가 실행됩니다. graph.add_edge("increment", "double")은 increment가 끝나면 자동으로 double이 실행되도록 연결합니다.
이것이 바로 엣지입니다. graph.add_edge("double", END)는 double이 끝나면 그래프가 종료되도록 합니다.
END는 LangGraph에서 제공하는 특별한 상수입니다. 실제 현업에서는 어떻게 활용할까요?
예를 들어 이미지 처리 파이프라인을 만든다고 가정해봅시다. "이미지 로드" → "리사이즈" → "필터 적용" → "저장" 같은 순서로 처리해야 합니다.
각 단계를 노드로 만들고, add_edge()로 순서대로 연결하면 됩니다. 포토샵, 인스타그램 같은 이미지 편집 앱에서도 이런 파이프라인 구조로 복잡한 필터와 효과를 구현합니다.
하지만 주의할 점도 있습니다. 초보 개발자들이 흔히 하는 실수 중 하나는 순환 참조를 만드는 것입니다.
A → B → A 같은 순환 구조를 만들면 그래프가 무한 루프에 빠질 수 있습니다. 순환이 필요하다면 조건부 엣지를 사용해야 합니다.
또 다른 실수는 시작점을 지정하지 않는 것입니다. set_entry_point()를 호출하지 않으면 그래프가 어디서 시작해야 할지 모릅니다.
다시 김개발 씨의 이야기로 돌아가 봅시다. 박시니어 씨의 설명을 들은 김개발 씨는 직접 엣지를 추가해봤습니다.
"이제 데이터가 어떻게 흐르는지 한눈에 보이네요!" add_edge()를 제대로 이해하면 복잡한 워크플로우도 명확하게 표현할 수 있습니다. 여러분도 오늘 배운 내용을 실제 프로젝트에 적용해 보세요.
실전 팁
💡 - 반드시 set_entry_point()로 시작점을 지정하세요
- 마지막 노드는 END로 연결하세요
- 순환 참조를 주의하세요 (무한 루프 위험)
5. compile() 그래프 컴파일
노드도 추가하고, 엣지도 연결했습니다. 이제 실행할 수 있을까요?
김개발 씨가 invoke()를 호출하려고 하자, 박시니어 씨가 손을 들어 막았습니다. "잠깐!
먼저 compile()을 해야 해요."
compile() 메서드는 그래프를 실행 가능한 형태로 컴파일합니다. 노드와 엣지의 구성을 검증하고, 최적화된 실행 계획을 만듭니다.
compile()을 호출해야 비로소 그래프를 실행할 수 있는 런타임 객체를 얻게 됩니다. 이 단계를 빼먹으면 실행할 수 없습니다.
다음 코드를 살펴봅시다.
from langgraph.graph import StateGraph, END
from typing import TypedDict
class GraphState(TypedDict):
message: str
def greet(state: GraphState) -> GraphState:
state["message"] = "안녕하세요!"
return state
def farewell(state: GraphState) -> GraphState:
state["message"] += " 안녕히 가세요!"
return state
# 그래프 구성
graph = StateGraph(GraphState)
graph.add_node("greet", greet)
graph.add_node("farewell", farewell)
graph.set_entry_point("greet")
graph.add_edge("greet", "farewell")
graph.add_edge("farewell", END)
# 컴파일! 이제 실행 가능한 객체가 됩니다
app = graph.compile()
# app을 사용해서 실행할 수 있습니다
김개발 씨는 완벽하게 그래프를 구성했다고 생각했습니다. 노드도 추가했고, 엣지도 연결했습니다.
이제 바로 실행하면 되겠죠? 하지만 박시니어 씨가 손을 들어 막았습니다.
"아직 한 단계가 남았어요. compile()을 해야 합니다." 김개발 씨는 고개를 갸우뚱했습니다.
"왜 컴파일이 필요한가요? 이미 다 만들었는데요?" 그렇다면 compile()은 정확히 무엇을 하는 걸까요?
쉽게 비유하자면, compile()은 마치 조립 가구를 완성하는 것과 같습니다. 나사, 판넬, 다리 같은 부품은 이미 준비되어 있습니다(노드와 엣지).
하지만 아직 조립 설명서대로 실제로 조립하지는 않았습니다. compile()은 "자, 이제 진짜로 조립해서 사용 가능한 가구를 만들자!"라고 하는 단계입니다.
compile()을 하지 않으면 어떻게 될까요? 그래프는 여전히 설계도일 뿐입니다.
실행할 수 없습니다. StateGraph 객체는 단지 "이런 노드가 있고, 이런 엣지가 있어요"라는 정보만 담고 있습니다.
실제로 상태를 관리하고, 노드를 순서대로 실행하고, 결과를 추적하는 런타임 로직은 아직 없습니다. 많은 초보 개발자들이 "왜 graph.invoke()가 없다고 에러가 나죠?"라고 물어봅니다.
StateGraph 객체 자체는 invoke()를 제공하지 않기 때문입니다. 바로 이런 문제를 해결하기 위해 compile()이 있습니다.
compile()을 호출하면 실행 가능한 런타임 객체를 얻게 됩니다. 또한 그래프 구성에 오류가 있는지 검증합니다.
무엇보다 실행 성능을 최적화한다는 큰 이점이 있습니다. 예를 들어 병렬로 실행 가능한 노드를 찾아내서 효율적으로 처리합니다.
위의 코드를 한 줄씩 살펴보겠습니다. 먼저 greet와 farewell 두 노드를 정의하고 추가합니다.
그리고 엣지로 연결합니다. 여기까지는 이전 섹션에서 배운 내용입니다.
핵심은 app = graph.compile() 부분입니다. 이 한 줄이 마법을 일으킵니다.
graph라는 설계도를 받아서, app이라는 실제 실행 가능한 객체로 변환합니다. app은 invoke(), stream() 같은 메서드를 제공합니다.
이제 graph는 더 이상 사용하지 않습니다. app만 사용하면 됩니다.
실제 현업에서는 어떻게 활용할까요? 예를 들어 웹 애플리케이션에서 AI 챗봇을 서비스한다고 가정해봅시다.
서버가 시작될 때 그래프를 한 번만 컴파일해서 app 객체를 만듭니다. 그리고 사용자 요청이 올 때마다 app.invoke()를 호출합니다.
매번 컴파일하는 것보다 훨씬 효율적입니다. 카카오톡, 네이버 클로바 같은 대규모 챗봇 서비스에서도 이런 패턴을 사용합니다.
한 번 컴파일하고, 여러 번 실행하는 것이죠. 하지만 주의할 점도 있습니다.
초보 개발자들이 흔히 하는 실수 중 하나는 매번 컴파일하는 것입니다. compile()은 비용이 큰 작업입니다.
따라서 한 번만 호출하고, 결과를 재사용해야 합니다. 매 요청마다 컴파일하면 성능이 크게 떨어집니다.
또 다른 실수는 컴파일 에러를 무시하는 것입니다. compile()에서 에러가 발생하면 그래프 구성에 문제가 있다는 뜻입니다.
시작점이 없거나, 연결되지 않은 노드가 있을 수 있습니다. 에러 메시지를 잘 읽고 수정해야 합니다.
다시 김개발 씨의 이야기로 돌아가 봅시다. 박시니어 씨의 설명을 들은 김개발 씨는 고개를 끄덕였습니다.
"아, 그래서 compile()이 필요하군요!" compile()을 제대로 이해하면 그래프를 효율적으로 실행할 수 있습니다. 여러분도 오늘 배운 내용을 실제 프로젝트에 적용해 보세요.
실전 팁
💡 - compile()은 한 번만 호출하고 결과를 재사용하세요
- compile() 에러는 그래프 구성 문제를 알려줍니다
- 컴파일된 app 객체만 invoke() 할 수 있습니다
6. invoke() 실행하기
드디어 마지막 단계입니다. 김개발 씨가 신이 나서 물었습니다.
"이제 진짜로 실행할 수 있나요?" 박시니어 씨가 웃으며 대답했습니다. "네, invoke()를 호출하면 됩니다.
초기 상태만 넣어주세요."
invoke() 메서드는 컴파일된 그래프를 실행합니다. 파라미터로 초기 State를 전달하면, 그래프가 시작점부터 끝까지 실행되고 최종 State를 반환합니다.
이것이 바로 우리가 만든 워크플로우를 실제로 동작시키는 순간입니다.
다음 코드를 살펴봅시다.
from langgraph.graph import StateGraph, END
from typing import TypedDict
class GraphState(TypedDict):
number: int
result: str
def add_ten(state: GraphState) -> GraphState:
state["number"] = state.get("number", 0) + 10
return state
def format_result(state: GraphState) -> GraphState:
state["result"] = f"최종 결과: {state['number']}"
return state
# 그래프 구성 및 컴파일
graph = StateGraph(GraphState)
graph.add_node("add", add_ten)
graph.add_node("format", format_result)
graph.set_entry_point("add")
graph.add_edge("add", "format")
graph.add_edge("format", END)
app = graph.compile()
# 실행!
initial_state = {"number": 5}
final_state = app.invoke(initial_state)
print(final_state) # {'number': 15, 'result': '최종 결과: 15'}
김개발 씨는 모든 준비를 마쳤습니다. 그래프를 설계하고, 노드를 추가하고, 엣지로 연결하고, 컴파일까지 했습니다.
이제 정말로 실행할 차례입니다. 박시니어 씨가 마지막 코드를 보여줬습니다.
"invoke()만 호출하면 됩니다. 초기 상태만 넣어주세요." 김개발 씨는 숨을 깊이 들이마시고 엔터키를 눌렀습니다.
결과가 화면에 나타났습니다! 그렇다면 invoke()는 정확히 무엇을 하는 걸까요?
쉽게 비유하자면, invoke()는 마치 공장의 시동 버튼을 누르는 것과 같습니다. 모든 기계와 컨베이어 벨트가 준비되어 있습니다(컴파일된 그래프).
원자재를 투입하고(초기 State), 시동 버튼을 누르면(invoke()), 공장이 돌아가기 시작합니다. 각 공정을 거쳐서 최종 제품이 나옵니다(최종 State).
invoke()를 호출하지 않으면 어떻게 될까요? 아무것도 실행되지 않습니다.
그래프는 여전히 잠들어 있습니다. 마치 자동차 엔진이 꺼져 있는 상태입니다.
시동을 걸어야(invoke) 움직이기 시작합니다. 많은 초보 개발자들이 그래프를 만들어 놓고 "어떻게 실행하죠?"라고 물어봅니다.
답은 간단합니다. invoke()를 호출하면 됩니다.
바로 이 순간을 위해 우리는 모든 준비를 해왔습니다. invoke()를 호출하면 전체 워크플로우가 자동으로 실행됩니다.
시작 노드부터 끝까지, 엣지가 정의한 순서대로 모든 노드가 실행됩니다. 또한 각 노드가 State를 수정하는 과정을 추적할 수 있습니다.
무엇보다 최종 결과를 간단하게 얻을 수 있다는 큰 이점이 있습니다. 위의 코드를 한 줄씩 살펴보겠습니다.
먼저 add_ten과 format_result 두 노드를 정의합니다. add_ten은 숫자에 10을 더하고, format_result는 결과를 문자열로 포맷합니다.
그래프를 구성하고 컴파일해서 app 객체를 얻습니다. 여기까지는 이전 섹션들에서 배운 내용입니다.
핵심은 final_state = app.invoke(initial_state) 부분입니다. initial_state는 {"number": 5}입니다.
이것이 그래프의 시작 상태입니다. invoke()가 호출되면 다음 일이 일어납니다:
3. 최종 State 반환: {'number': 15, 'result': '최종 결과: 15'}
실전 팁
💡 - 초기 상태에 필수 필드를 모두 포함하세요
- invoke() 결과를 반드시 확인하세요
- 실행 과정을 디버깅하려면 stream()도 활용하세요
이상으로 학습을 마칩니다. 위 내용을 직접 코드로 작성해보면서 익혀보세요!
댓글 (0)
함께 보면 좋은 카드 뉴스
Helm 마이크로서비스 패키징 완벽 가이드
Kubernetes 환경에서 마이크로서비스를 효율적으로 패키징하고 배포하는 Helm의 핵심 기능을 실무 중심으로 학습합니다. Chart 생성부터 릴리스 관리까지 체계적으로 다룹니다.
보안 아키텍처 구성 완벽 가이드
프로젝트의 보안을 처음부터 설계하는 방법을 배웁니다. AWS 환경에서 VPC부터 WAF, 암호화, 접근 제어까지 실무에서 바로 적용할 수 있는 보안 아키텍처를 단계별로 구성해봅니다.
AWS Organizations 완벽 가이드
여러 AWS 계정을 체계적으로 관리하고 통합 결제와 보안 정책을 적용하는 방법을 실무 스토리로 쉽게 배워봅니다. 초보 개발자도 바로 이해할 수 있는 친절한 설명과 실전 예제를 제공합니다.
AWS KMS 암호화 완벽 가이드
AWS KMS(Key Management Service)를 활용한 클라우드 데이터 암호화 방법을 초급 개발자를 위해 쉽게 설명합니다. CMK 생성부터 S3, EBS 암호화, 봉투 암호화까지 실무에 필요한 모든 내용을 담았습니다.
AWS Secrets Manager 완벽 가이드
AWS에서 데이터베이스 비밀번호, API 키 등 민감한 정보를 안전하게 관리하는 Secrets Manager의 핵심 개념과 실무 활용법을 배워봅니다. 초급 개발자도 쉽게 따라할 수 있도록 실전 예제와 함께 설명합니다.