Post-0002. LangGraph 학습 일지 — Day 02: 엣지는 "거기로 가"라고 말하고, 조건부 엣지는 "먼저 확인해 볼게"라고
요약
LangGraph를 활용하여 상태 유지 AI 에이전트를 구축하는 학습 시리즈의 두 번째 단계입니다. 고정된 경로를 넘어 라우터와 조건부 엣지를 통해 에이전트가 상황에 따라 의사결정을 내리고 경로를 분기하는 방법을 다룹니다.
핵심 포인트
- 라우터: 현재 상태를 분석하여 다음 경로를 결정하는 함수
- 조건부 엣지: 라우터의 결정에 따라 다음 노드를 매핑하는 기능
- 상태 유지 에이전트: 결정, 분기, 재시도, 루프가 가능한 시스템 설계
Day 2 · 작성자 Kunal Hore (@KunalOnTech)
빠른 이동 (Quick Jump)
- Day 1 요약
- 왜 이것을 공개적으로 배우고 있는가
- 오늘의 지도
-
- 라우터 (The Router)
-
- 조건부 엣지 (The Conditional Edge)
-
- 구축 및 실행 — 전체 실습
- Kunal의 인터뷰 코너
- 오늘의 핵심 요약
Day 1 요약 (Quick Recap of Day 1)
Day 1에서 우리는 첫 번째 LangGraph를 구축했습니다. 상태 (state) (그래프가 기억하는 사실들), 노드 (nodes) (상태를 읽고 업데이트하는 함수), 그리고 엣지 (edges) (노드 사이의 고정된 연결)를 사용한 음식 주문 워크플로우였습니다.
하지만 그 그래프에는 한 가지 한계가 있었습니다. 오직 하나의 고정된 경로만 따를 수 있다는 점이었습니다. 모든 실행이 정확히 똑같이 보였습니다:
배고픔을 느낌 → 앱 열기 → 식당 둘러보기 → 메뉴 선택 → 주문하기
어떤 일이 일어나더라도, 그래프는 항상 그 순서를 따랐습니다.
실제 세계의 워크플로우는 그렇게 예측 가능하지 않습니다. 만약 식당이 문을 닫았다면 어떻게 될까요? 그래프가 여전히 메뉴를 선택하고 주문을 시도해야 할까요?
그것이 오늘의 교훈입니다: 그래프에게 의사결정을 내리는 방법을 가르치는 것입니다.
Kunal의 한 줄 평: "엣지는 거기로 가라고 말합니다. 조건부 엣지는 먼저 확인해 보겠다고 말합니다."
처음 오셨나요? 이 시리즈의 Day 2입니다. 먼저 Day 1을 완료하세요. 오늘의 레슨은 거기서 만든 그래프를 바탕으로 직접적으로 구축됩니다.
왜 이것을 공개적으로 배우고 있는가
저는 LangGraph를 기초부터 배우고 있습니다 — 하루에 하나의 개념씩 말이죠. 지름길은 없으며, 이미 알고 있는 척하지도 않습니다. 매일 배우는 것이 무엇이든, 그날 바로 공유합니다: 성공한 순간, 혼란스러웠던 순간, "아, 이게 그 뜻이었구나" 하는 순간들, 그 모든 것을요.
저의 목표는 단순합니다:
- 하루에 하나의 개념을 일상적인 비유로 설명하기 — 전문 용어의 벽을 만들지 않기
- 매일 실제 코드 작성하기, 이론만으로는 배울 수 없고 구축하며 배우기 때문입니다
- 누군가 나에게 설명해 주었으면 했던 방식으로 쓰기
저와 함께 배우는 여러분의 목표는: 이 시리즈가 끝날 때쯤, 우리 모두는 단순히 한 번 응답하는 것이 아니라, 결정하고, 분기하고, 재시도하고, 루프를 돌 수 있는 시스템인 상태 유지 AI 에이전트 (stateful AI agents)를 설계하고 구축할 수 있게 될 것입니다.
그리고 저는 배우면서 진행하고 있기 때문에, 무언가를 놓치거나 세부 사항을 틀릴 수도 있습니다. 만약 읽으시다가 제가 간과한 부분이 있다고 느껴지신다면 댓글로 남겨주세요. 이는 제가 성장하는 데 진정으로 도움이 되며, 이 시리즈를 따라오시는 모든 분들을 위해 더 나은 콘텐츠를 만드는 밑거름이 됩니다.
군더더기 없이. 하루에 한 입씩.
오늘의 지도
- 라우터 (The Router) — 현재 상태를 읽고 다음에 어떤 경로를 택할지 결정하는 함수.
- 조건부 엣지 (The Conditional Edge) — 라우터의 결정과 다음 노드를 연결하는 매핑 (mapping).
- 빌드 및 실행 (Build & Run) — 첫 번째 조건부 분기 (conditional branch)를 추가하여 Day 1의 그래프를 확장하기.
1. 라우터 (The Router)
앞에 여러 갈래의 길이 있는 교차로에 서 있다고 상상해 보세요. 앞으로 나아가기 전에, 누군가는 현재 상황을 살펴보고 여정이 어느 방향으로 향해야 할지 결정해야 합니다. 그것이 바로 라우터가 하는 일입니다.
라우터는 단순히 현재 상태를 읽고, 라우팅 결정 (routing decision)을 내린 뒤, "restaurant_open" 또는 "restaurant_closed"와 같은 문자열 레이블 (string label)을 반환하는 함수입니다. 이 레이블은 결정을 나타내며, 조건부 엣지 (conditional edge)가 다음에 어떤 노드를 실행할지 결정하는 데 사용됩니다.
라우터의 책임은 결정을 내린 후 종료됩니다. 라우터는 상태를 업데이트하거나 실제 처리를 수행하지 않습니다. 그 책임은 노드 (nodes)에게 있습니다.
| 역할 | 동작 |
|---|---|
| 라우터 (Router) | 결정하고 레이블을 반환함 |
| ... |
# 라우터 (Router) — 상태를 읽고, 문자열 레이블을 반환하며, 상태를 절대 수정하지 않음
def route_by_availability(state):
if state["is_restaurant_open"]:
...
이것을 기억하세요:
- 라우터(Router)는 결정할 뿐, 수정하지 않습니다. 라우터의 유일한 역할은 현재 상태(state)를 읽고, 경로를 결정하며, 레이블(label)을 반환하는 것입니다. 라우터는 상태 자체를 절대 수정해서는 안 됩니다. 의사 결정 과정을 상태 업데이트와 분리하면 그래프를 더 쉽게 이해하고 디버깅할 수 있습니다. 만약 라우터가 값을 변경하기 시작하면, 해당 변경이 라우터에 의한 것인지 아니면 노드(node)에 의한 것인지 불분명해집니다.
- 라우터는 매번 새로 실행됩니다. 실행이 조건부 엣지(conditional edge)에 도달할 때마다, LangGraph는 라우터 함수를 다시 실행합니다. 이전의 결정을 기억하거나 재사용하지 않습니다. 대신, 그 순간의 현재 상태를 읽고 사용 가능한 최신 정보에 기반하여 새로운 라우팅 결정을 내립니다. 이는 이전 노드가 조건부 엣지에 도달하기 전에 상태를 업데이트했다면, 라우터가 자동으로 해당 변경 사항을 확인하여 다른 레이블을 반환할 수 있음을 의미하며, 이를 통해 그래프가 올바른 경로를 따를 수 있게 합니다.
2. 조건부 엣지 (The Conditional Edge)
조건부 엣지(conditional edge)는 단순히 라우터의 결정과 다음에 실행될 노드 사이의 연결입니다. 이는 각 레이블이 어디로 가야 하는지를 정의하는 매핑 딕셔너리(mapping dictionary)를 사용합니다. 예를 들어, "restaurant_open" 레이블은 "pick_dish" 노드로 매핑되고, "restaurant_closed" 레이블은 "notify_closed" 노드로 매핑됩니다.
라우터가 레이블을 반환하면, 조건부 엣지는 매핑 딕셔너리에서 해당 레이블을 찾아 실행을 상응하는 노드로 라우팅합니다. 즉, 라우터는 레이블의 형태로 자신의 결정을 전달하고, 조건부 엣지는 그 레이블을 사용하여 그래프의 다음 노드를 결정합니다.
graph.add_conditional_edges(
"check_availability", # 소스 노드 (Source node)
route_by_availability, # 라우터 함수 (Router function)
...
이것을 기억하세요:
- 매핑 딕셔너리의 키(Mapping dictionary keys)는 라우터의 출력과 반드시 정확히 일치해야 합니다. 매핑 딕셔너리의 모든 키는 라우터가 반환하는 레이블(label)과 동일해야 합니다. 만약 라우터가 "restaurant_open"을 반환한다면, 매핑에서도 "open"이나 "Restaurant_Open"이 아닌 반드시 "restaurant_open"을 사용해야 합니다. 아주 작은 오타나 대소문자 불일치라도 조건부 엣지(conditional edge)가 일치하는 경로를 찾지 못하게 만들어, 런타임(runtime) 시 그래프에서 에러를 발생시킵니다. 라우터의 반환 값과 매핑 딕셔너리의 키는 하나의 계약(contract)을 형성하므로, 항상 정확히 일치해야 합니다.
- 세 개의 인자, 순서대로.
add_conditional_edges()는 항상 고정된 순서로 세 개의 인자를 받습니다:- 소스 노드 (Source node) — 라우팅 결정이 일어나는 곳.
- 라우터 함수 (Router function) — 상태(state)를 읽고, 결정을 내린 뒤, 레이블을 반환하는 함수.
- 매핑 딕셔너리 (Mapping dictionary) — 각 레이블을 다음 노드로 연결하는 조회 테이블(lookup table).
기억하기 쉬운 방법: 소스(Source) → 라우터(Router) → 매핑(Mapping)
3. 빌드 및 실행 — 전체 실습
문제 정의: Day 1에서 다룬 음식 주문 그래프를 확장하여 첫 번째 조건부 분기(conditional branch)를 추가해 봅니다. 메뉴를 살펴본 후, 식당이 영업 중인지 확인합니다. 영업 중이라면 음식을 선택하고 주문을 진행합니다. 영업 중이 아니라면 사용자에게 알리고 워크플로(workflow)를 종료합니다. 하나의 라우터와 두 개의 가능한 분기가 존재합니다.
이 시리즈는 Python 3.11+를 사용합니다 — NotRequired는 3.11 이상의 버전이 필요합니다.
from langgraph.graph import StateGraph, START, END
from typing import TypedDict, NotRequired
...
출력 결과:
Final State:
Biryani House is closed. Can't order right now.
{'mood': 'hungry', 'app_opened': True, 'restaurant': 'Biryani House', 'dish': '', 'order_placed': False, 'is_restaurant_open': False}
dish가 여전히 비어 있고 order_placed가 여전히 False라는 점에 주목하세요. 이는 그래프가 Branch 1(분기 1)에 진입하지 않았기 때문입니다. check_availability 노드가 is_restaurant_open을 False로 설정했고, 라우터(router)가 그 값을 읽어 "restaurant_closed"라는 레이블을 반환했으며, 조건부 엣지(conditional edge)가 실행을 폐점 분기(closed branch)로 보냈습니다.
이제 check_availability 노드 내부의 is_restaurant_open을 True로 변경하고 그래프를 다시 실행해 보세요. 그래프 자체는 변하지 않으며, 해당 노드에 의해 생성된 상태(state)만 변합니다. 이번에는 라우터가 다른 레이블을 반환하고, 조건부 엣지가 개점 분기(open branch)를 따르며, 그래프가 주문을 넣기 전에 요리(dish)를 선택합니다. 이것이 조건부 라우팅(conditional routing)의 힘입니다.
Kunal의 인터뷰 코너
오늘의 레슨에 대해 누군가가 실제로 물어볼 법한 네 가지 질문입니다. 이는 조용히 읽어 내려갈 때는 드러나지 않는 공백을 드러내는 질문들입니다. 이것들은 암기해야 할 정의가 아닙니다. 각 질문에는 면접관이 듣고 싶어 하는 구조인 **답변 프레임워크 (answer framework)**가 함께 제공됩니다. 클릭하기 전에 직접 답해 보세요.
Q1. 일반 엣지(normal edge)와 조건부 엣지(conditional edge)의 차이점은 무엇인가요?
· 답변 프레임워크: 두 가지를 대조하기 → 머릿속에 그려지는 이미지를 제공하기
· 답변: 일반 엣지는 항상 동일한 경로를 따릅니다. 즉, 한 노드가 끝나면 다음 노드가 고정되어 있습니다. 반면 조건부 엣지는 현재 상태(state)를 기반으로 런타임(runtime)에 다음 경로를 선택합니다. 일반 엣지를 고정된 로드맵으로, 조건부 엣지를 현재 상황에 따라 최적의 경로를 선택하는 GPS로 생각하세요.
Q2. LangGraph에서 라우터 함수(router function)란 무엇인가요?
· 답변 프레임워크: 역할을 정의하기 → 황금률(golden rule) 언급하기
· 답변: 라우터는 현재 상태를 읽고, 어떤 경로를 택할지 결정하며, 문자열 레이블을 반환하는 일반 함수입니다. 라우터의 유일한 역할은 라우팅 결정을 내리는 것입니다. 이를 네비게이터(navigator)라고 생각하세요. 어디로 갈지는 알려주지만, 직접 차를 운전하지는 않습니다.
Q3. 라우터가 상태(state)를 수정할 수 있나요?
· 답변 프레임워크: 직접적인 답변 → 이유 → 그로 인한 결과
· 답변: 아니요. 라우터(Router)는 상태(state)를 읽고 다음 경로를 결정하기만 해야 합니다. 상태를 업데이트하는 것은 노드(node)의 책임입니다. 이러한 책임이 섞이면 값이 어디서 변경되었는지 파악하기 어려워지고 디버깅(debugging)이 훨씬 까다로워집니다.
Q4. 라우터가 매핑 딕셔너리(mapping dictionary)에 없는 레이블을 반환하면 어떻게 되나요?
· 답변 프레임워크: 사실 명시 → 동작 설명
· 답변: 그래프가 다음에 어디로 갈지 결정할 수 없으므로, 실행이 에러와 함께 실패합니다. 라우터의 반환 값은 매핑 딕셔너리의 키(key) 중 하나와 정확히 일치해야 합니다. 아주 작은 오타나 대소문자 차이만 있어도 경로가 깨집니다.
"노트를 확인하지 않고 이 네 가지 질문에 답할 수 있다면, Day 2의 내용은 진정으로 머릿속에 각인된 것입니다."
오늘의 핵심 요약 (Today's Takeaway)
오늘 배운 것 중 딱 세 가지만 기억해야 한다면, 다음을 기억하세요:
| 용어 | 의미 |
|---|---|
| 라우터 (Router) | 현재 상태를 읽고, 그래프가 어떤 경로를 따라야 할지 결정하며, 조건부 엣지(conditional edge)가 실행 경로를 지정하는 데 사용하는 레이블을 반환합니다. |
| ... |
Day 1은 그래프에 몸(body)을 부여했습니다 — 즉, 고정된 단계의 시퀀스(sequence)입니다.
Day 2는 그래프에 뇌(brain)를 부여했습니다 — 즉, 현재 상태를 읽고 다음 경로를 선택하는 능력입니다.
다음 단계 → Day 3: 루프 (Loops) — 그래프가 이전 노드를 다시 방문할 때
Day 1에서 배운 한 줄 요약을 기억하시나요?
"되돌아올 수 있는 코드는 생각할 수 있다."
우리는 그래프에게 경로를 선택하는 방법을 가르쳤습니다. 다음에는 그래프가 실제 세계의 워크플로(workflow)를 모델링하기 위해 이전 노드를 어떻게 다시 방문할 수 있는지 탐구할 것입니다.
함께하고 계신가요? LinkedIn · YouTube: @kunalontech에서 저와 연결하세요.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기