1. LangGraph의 구성 요소

구성 요소

  • Node 노드 : 하나의 작업이나 단계.
  • Edge 엣지 : 노드간의 연결. 방향성을 가짐
  • State 상태 : 노드가 작업한 결과를 기록해 두는 작업 일지
  • Graph 그래프 : 노드와 엣지로 이루어진, 흐름을 가진 작업의 그룹

(1) Node 노드

  • Node는 실제 작업을 수행하는 Python 함수. 또는 작업자.
  • 각 노드들은 독립적이며 서로 어떠한 작업을 어떻게 수행하는지 모른다.
  • 따라서 State(상태)라는 것을 주고 받으면서 노드간에 현재 상태 공유한다.
  • 하나의 노드는 이전 노드로부터 State를 받아서 작업 처리 후 새로운 State를 반환한다.
1
2
3
4
5
6
7
# 카운터를 1 증가시키는 노드
def add_one(state):
    return {"count": state["count"] + 1}

# 이름을 설정하는 노드  
def set_name(state):
    return {"name": "Alice"}

Node의 규칙
1) 항상 state를 첫 번째 매개변수로 받는다.
2) 딕셔너리 형태로 새로운 상태를 반환한다.
3) 반환하지 않은 필드는 상태에서 기존 값을 유지한다.

(2) State 상태

  • 현재 상태 및 데이터 저장소
  • State는 모든 노드가 공유하는 데이터 저장소로, 랭그래프 APP의 현재 상태를 기록한다.
  • 작업의 결과나 현재 상태를 기록하는 “일지”와 같다고 볼 수 있다.
  • 각 노드들은 이전 노드로부터 State를 받아, 작업을 처리하고 필요시 State를 업데이트한다.
1
2
3
4
5
6
7
8
9
10
11
# 간단한 State 예제 : 카운트, 이름
class SimpleState(TypedDict):
    count: int
    name: str

# 또는  
from pydantic import BaseModel
class SimpleState(BaseModel):
    query: str # 사용자 질의
    documents: list[str] # 검색된 문서
    response: str # LLM 답변

State의 규칙
1) 모든 노드는 State를 보고, 수정할 수 있다.
2) TypedDict 또는 Pydantic의 BaseModel을 사용해 정의하곤 한다.
3) 어떠한 종류의 데이터든 저장할 수 있다.

(3) Edge 엣지

  • 노드를 연결하는 화살표
  • Edge는 노드들 사이의 연결을 정의한다.
  • 즉, 이 작업 다음에 할 작업을 알려주는 역할. 연결자
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
from langgraph.graph import START, END

# 기본적인 엣지 연결
graph.add_edge("노드1", "노드2")  # 노드1 → 노드2

# 시작과 끝 연결 (START, END 상수 사용)
graph.add_edge(START, "첫번째노드")  # 시작 → 첫번째노드
graph.add_edge("마지막노드", END)   # 마지막노드 → 끝

# 병렬 실행 (Parallel)
builder.add_edge("query_refiner", "retriever")
builder.add_edge("query_refiner", "web_searcher")

# 병렬 실행 집결 (Fan-in)
builder.add_edge("retriever", "llm")
builder.add_edge("web_searcher", "llm")

# 분기 처리
builder.add_conditional_edges(
    "response_evaluator",
    lambda x: "web_searcher" if x["response_grade"] == "back to web searcher" else\
              "query_rewriter" if x["response_grade"] == "back to query" else\
              "response_refiner" if x["response_grade"] == "pass" else "",
    {"web_searcher": "web_searcher", "query_rewriter": "query_rewriter", "response_refiner":"response_refiner"}
)

(4) Graph

  • Node와 Edge를 모아 하나의 실행 흐름으로 만든 것
  • 단순한 그림이 아니라, 실제로 실행 가능한 워크플로우
  • “어떤 상태(State)”를 가지고, “어떤 노드(Node)”들을, “어떤 순서와 조건(Edge)”로 실행할지 정의한 전체 설계도이자 실행 단위
  • LangGraph에서는 보통 StateGraph를 사용한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
from typing import TypedDict
from langgraph.graph import StateGraph, START, END

# 1) State 정의
class MyState(TypedDict):
    count: int
    name: str

# 2) Node 정의
def add_one(state: MyState):
    return {"count": state["count"] + 1}

def set_name(state: MyState):
    return {"name": "Alice"}

# 3) Graph 생성
builder = StateGraph(MyState)

# 4) Node 등록
builder.add_node("add_one", add_one)
builder.add_node("set_name", set_name)

# 5) Edge 연결
builder.add_edge(START, "add_one")
builder.add_edge("add_one", "set_name")
builder.add_edge("set_name", END)

# 6) 컴파일
graph = builder.compile()

# 7) 실행
result = graph.invoke({"count": 0, "name": ""})
print(result)
# {"count": 1, "name": "Alice"}

StateGraph의 특징
1) “State(상태)”를 중심으로 동작한다.
2) Node와 Edge로 구성된다.

2. 간단한 그래프 만들어보기

(1) 단일 노드 - 단순 숫자 카운팅 랭그래프

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
from langgraph.graph import StateGraph, START, END
from typing import TypedDict

# (1) State: 카운터를 저장하는 상자
class CounterState(TypedDict):
    count: int

# (2) Node: 카운터를 증가시키는 함수
def increment(state):
    print(f"현재 카운트: {state['count']}")
    new_count = state["count"] + 1
    print(f"새로운 카운트: {new_count}")
    return {"count": new_count}

# (3) Edge: 노드들을 연결하는 그래프
graph = StateGraph(CounterState)
graph.add_node("increment", increment)
graph.add_edge(START, "increment")
graph.add_edge("increment", END)

# 실행해보기
app = graph.compile()
result = app.invoke({"count": 0})
print(f"최종 결과: {result}")

1
2
3
현재 카운트: 0
새로운 카운트: 1
최종 결과: {'count': 1}

(2) 노드 연결 - 단순 숫자 카운팅 랭그래프

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 첫 번째 증가 함수
def first_increment(state):
    print("첫 번째 증가")
    return {"count": state["count"] + 1}

# 두 번째 증가 함수  
def second_increment(state):
    print("두 번째 증가")
    return {"count": state["count"] + 10}

# 그래프 구성
graph = StateGraph(CounterState)
graph.add_node("first", first_increment)
graph.add_node("second", second_increment)

# 연결: START → first → second → END
graph.add_edge(START, "first")
graph.add_edge("first", "second") 
graph.add_edge("second", END)

# 실행
app = graph.compile()
result = app.invoke({"count": 0})
print(f"최종 결과: {result}")
1
2
3
 번째 증가
 번째 증가
최종 결과: {'count': 11}

3. 랭그래프 아키텍처

  • Google의 Pregel 시스템의 메시지 전달(Message Passing) 아키텍처에서 영감을 받음
  • Pregel에서 각 정점(vertex)은 현재 단계에서 자신의 계산을 수행한다.
  • 정점들은 서로 독립적인 형태로, 상대 정점이 어떤 작업을 하는지 모른다.
  • 정점 간 데이터 공유가 필요할 때에는 한 정점이 상대 정점에게 메시지를 보낸다.
  • LangGraph는 이와 비슷하게(하지만 다름) State(상태)라는 것을 노드 간 공유하면서, 상태 중심의 인터페이스를 가지며 동작한다.

Reference

Do it! LLM을 활용한 AI 에이전트 개발 입문 (이성용 저)

https://wikidocs.net/261579

https://www.youtube.com/watch?v=W_uwR_yx4-c

Comments