페이지 목차
이 페이지에서 검색
멀티 에이전트
이 페이지에 묻기
멀티 에이전트 · 현재 차시와 목차 본문에서만 찾아 답합니다. 답을 지어내지 않고 해당하는 대목을 그대로 보여 주며, 여기에 없으면 없다고 알려 드립니다.
생성형 AI가 아니라 이 페이지 본문을 찾아 그대로 보여 주는 방식입니다. 외부로 나가는 요청이 없습니다.
MULTI · AGENT
멀티 에이전트
생성 수정
도구 실행·검색·기억·그래프 흐름·전문 에이전트의 협업을 구성하고 권한·평가·사람의 확인으로 통제합니다.
26개 문서 · 3일차 · 19 / 26
18차시 · 그래프 기반 워크플로우
지도 위치 : 3일차 · README 4장의 ⑤ 협업 층(시작) | 이전: 17차시 장기 기억 관리 | 다음: 19차시 조건 분기와 라우팅 키워드 : State, Node, Edge, 그래프 실행
이 차시를 다 읽으면
- 선형 체인과 그래프를 분기 · 되돌아감 · 병렬 의 유무로 가를 수 있다.
- State · Node · Edge를 유와 종차로 정의하고, State와 Node를 축 ③ 속성 ↔ 기능 으로 구별할 수 있다.
- LangGraph
StateGraph코드에서 칸(노드) · 선(엣지) · 공유 값(State) 을 짚어 읽을 수 있다. - 노드가 State 전체가 아니라 바뀐 부분만 돌려주는 이유와 리듀서의 역할을 말할 수 있다.
- 그래프가 "누가 흐름을 정하는가" 축에서 어느 자리에 있는지 (큰 흐름은 코드, 칸 안 판단은 모델) 설명할 수 있다.
- 오피스봇 흐름을 그래프로 다시 그리고, 그래프 그림으로 설계와 구현이 같은지 검수할 수 있다.
0. 이 차시의 판별 축
| # | 축 | 왼쪽 (0) | 오른쪽 (1) | 이 축으로 갈리는 것 | 큰 축 (README 5장) |
|---|---|---|---|---|---|
| 1 | 선형 ↔ 그래프 ⭐ | 체인 (A → B → C) | 그래프 (분기 · 되돌아감 · 병렬) | 흐름이 한 줄인가 | ⑤ 하나 ↔ 여럿 (파생) |
| 2 | 속성 ↔ 기능 ⭐ | State — 노드들이 함께 보는 값 | Node — 그 값을 읽고 바꾸는 동작 | 그래프의 무엇이 데이터이고 무엇이 동작인가 | ③ 속성 ↔ 기능 |
| 3 | 누가 흐름을 정하는가 | 코드 — 엣지를 미리 그어 둠 | 모델 — 매번 다음 행동을 고름 | 그래프 전체 모양은 누가 정하나 | 5.2 중심 축 |
| 4 | 무상태 ↔ 상태 | 노드 함수 자체는 기억이 없음 | State가 실행 내내 값을 들고 다님 | 단계 사이에 무엇이 이어지나 | ⑦ 무상태 ↔ 상태 |
이 차시가 답하는 질문은 "한 줄로 이어지지 않는 처리 흐름을 어떻게 설계하고 실행하나?" 입니다. 앞에서 배운 LangChain(11차시)의 체인은 단계를 한 줄로 이었습니다. 오피스봇은 이미 한 줄이 아닙니다 — 의도에 따라 갈라지고(4 · 7차시), 검색에 실패하면 다시 시도하고(15차시), 승인을 기다려야 합니다(8차시). 이 차시는 그 흐름을 그래프라는 하나의 그림 으로 다시 그립니다. 3일차 나머지(분기 · 분해 · 멀티에이전트 · 사람의 승인)는 모두 이 그림 위에 얹힙니다.
1. 선형에서 그래프로
1.1 정의
체인 — [처리 흐름]인데, [ 단계가 한 줄로만 이어져 앞 단계의 출력이 다음 단계의 입력이 된다 ]는 점에서 그래프와 구별된다. 앞에서 배운 LangChain(11차시)은 모델 · 도구 · 프롬프트를 부품으로 묶어 주는 프레임워크였고, 체인은 그 부품을 한 줄로 잇는 방식이었습니다.
그래프 — [처리 흐름의 표현]인데, [ 단계(노드)를 선(엣지)으로 잇되, 갈라지고 · 되돌아가고 · 동시에 갈 수 있다 ]는 점에서 한 줄로만 이어지는 체인과 구별된다.
| 체인 (선형) | 그래프 | |
|---|---|---|
| 모양 | A → B → C | A → (B 또는 C) → D → A로 되돌아가기 가능 |
| 분기 | 체인 바깥의 if 문으로 처리 | 조건부 엣지 로 그림 안에 표시 (19차시) |
| 반복 | 표현하기 어려움 | 되돌아가는 엣지로 표현 |
| 단계 사이에 넘기는 것 | 앞 단계의 출력 하나 | State 전체 (모든 노드가 함께 봄) |
| 맞는 경우 | 단계가 고정된 단순 처리 | 갈라지고 · 다시 시도하고 · 멈췄다 재개하는 처리 |
1.2 반례 — 그래프처럼 보이지만 아닌 것
| 대상 | 왜 이 차시의 그래프가 아닌가 |
|---|---|
| 회의에서 그린 순서도 | 그림일 뿐 실행되지 않음 . 이 차시의 그래프는 그림이 곧 실행 코드 |
| 지식 그래프 | 사실 사이의 관계를 저장하는 데이터 구조 . 처리 흐름이 아님 |
| if 문 몇 개가 든 함수 | 갈라지긴 하지만 흐름이 코드 안에 숨어 있어 그림으로 볼 수 없음. 단순하면 이걸로 충분함 |
⚠️ 마지막 반례가 중요합니다. 단계가 셋이고 갈림길이 하나라면 if 문이 더 단순합니다. 그래프는 갈림길 · 되돌아감 · 멈춤이 늘어 흐름을 그림으로 보고 검수해야 할 때 값을 합니다. 문장 1(가장 단순한 것부터)이 여기에도 적용됩니다.
1.3 LangGraph
LangGraph — [에이전트 프레임워크]인데, [ 처리 흐름을 State를 가진 그래프로 정의하고 실행한다 ]는 점에서 부품 연결 중심의 LangChain과 구별된다. 같은 회사가 만들어 LangChain의 모델 · 도구를 노드 안에서 그대로 쓸 수 있습니다.
⚠️ LangGraph는 버전에 따라 함수 이름과 위치가 바뀝니다. 이 노트의 코드는 구조를 보이기 위한 예시이고, 실제로 쓸 때는 설치된 버전과 공식 문서를 확인 하세요.
💡 비유 (기억 보조용) — 체인은 컨베이어 벨트 , 그래프는 지하철 노선도 입니다. 노선도에는 갈아타는 역(분기), 순환선(되돌아감), 두 갈래로 동시에 가는 길(병렬)이 있습니다.
2. ⭐ State · Node · Edge — 축 ③
2.1 세 부품의 정의
State — [그래프의 구성 요소]인데, [ 실행 내내 모든 노드가 함께 읽고 쓰는 값의 묶음 ]이라는 점에서 동작인 노드와 구별된다.
Node — [그래프의 구성 요소]인데, [ State를 받아 일을 하고 바꿀 값을 돌려주는 함수 ]라는 점에서 값의 묶음인 State와 구별된다.
Edge — [그래프의 구성 요소]인데, [ 한 노드가 끝난 뒤 다음에 실행할 노드를 정하는 연결 ]이라는 점에서 일을 하는 노드와 구별된다.
| 부품 | 축 ③에서 | 오피스봇 예 | 코드에서 |
|---|---|---|---|
| State | 속성 (값) | 질문, 의도, 검색 결과, 답, 반복 횟수 | class State(TypedDict) |
| Node | 기능 (동작) | 의도 분류, 규정 검색, 답변 작성 | def classify(state) + add_node |
| Edge | 기능의 순서 | 분류 → 검색 → 답변 | add_edge, add_conditional_edges |
앞에서 배운 도구 호출(5 · 6차시)에서도 같은 축이 나왔습니다. 도구 정의와 결과 는 값(속성)이고 도구 실행 은 동작(기능)이었습니다. 그래프에서도 똑같이 값과 동작을 분리 합니다. 분리하면 State만 보고 "지금 어디까지 왔나"를 알 수 있습니다.
2.2 반례 — 헷갈리는 짝
| 헷갈리는 것 | 바로잡기 |
|---|---|
| "State = 대화 이력" | ❌ 대화 이력은 State의 한 필드 일 수 있을 뿐. State는 이 그래프가 필요로 하는 모든 값 |
| "State = 장기 기억" | ❌ State는 한 번의 실행 안 의 값(메모리 쪽, 17차시 축 ①). 저장하려면 체크포인터(3.3) |
| "노드 = 에이전트" | ❌ 노드는 함수 하나. 그 안에서 LLM을 한 번 부를 수도, 에이전트 루프를 돌릴 수도, LLM 없이 규칙만 쓸 수도 있음 |
2.3 노드는 바뀐 부분만 돌려준다 — 리듀서
import operator
from typing import Annotated, TypedDict
class State(TypedDict):
question: str
intent: str
answer: str
sources: Annotated[list[str], operator.add] # 리듀서: 덮어쓰지 말고 이어 붙여라
def classify(state: State) -> dict:
return {"intent": "규정질문"} # 바뀐 필드만
def search_policy(state: State) -> dict:
return {"sources": ["취업규칙 제23조"]} # 기존 sources 뒤에 붙음
🔍 읽는 포인트
return {"intent": ...}— 노드는 State 전체를 다시 쓰지 않고 바꿀 필드만 돌려줍니다. 나머지 필드는 그대로 남습니다. 그래서 노드끼리 서로의 값을 실수로 지우지 않습니다.Annotated[list[str], operator.add]— 리듀서 (합치는 규칙)입니다. 리듀서가 없으면 새 값이 옛 값을 덮어쓰고 , 있으면 정한 규칙대로 합칩니다. 여러 노드가 출처를 하나씩 보태는 경우에 씁니다.- ⚠️ 리듀서 없는 리스트 필드에 두 노드가 동시에 값을 쓰면 하나가 사라지거나 오류가 납니다. 병렬 실행(20차시)에서 자주 만나는 실수입니다.
3. StateGraph — 정의 · 컴파일 · 실행
3.1 그래프 정의
from langgraph.graph import StateGraph, START, END
g = StateGraph(State) # 이 State를 들고 다니는 그래프
g.add_node("classify", classify) # 칸: 이름 + 함수
g.add_node("search_policy", search_policy)
g.add_node("answer", write_answer)
g.add_edge(START, "classify") # 선: 시작 → 분류
g.add_edge("classify", "search_policy")
g.add_edge("search_policy", "answer")
g.add_edge("answer", END) # 선: 답변 → 끝
🔍 읽는 포인트
add_node("이름", 함수)— 무엇을 하는 칸 인가. 이름은 그림과 실행 추적에 그대로 찍힙니다.add_edge(A, B)— A가 끝나면 B로 .START·END는 입구와 출구입니다.- 이 예는 아직 한 줄입니다. 갈림길(
add_conditional_edges)은 19차시에서 붙입니다.
3.2 컴파일과 실행
그래프 실행 — [프로그램 실행]인데, [ State를 입구에 넣고 엣지를 따라 노드를 차례로 돌리며 State를 갱신하다가 END에 닿으면 마지막 State를 돌려준다 ]는 점에서 함수를 한 번 부르는 일반 호출과 구별된다.
app = g.compile() # 정의 → 실행 가능한 객체
result = app.invoke({"question": "육아휴직 며칠까지 돼요?", "sources": []})
print(result["answer"], result["sources"])
for update in app.stream({"question": "..."}, stream_mode="updates"):
print(update) # 노드 하나 끝날 때마다 바뀐 값
🔍 읽는 포인트
compile()— 정의를 검사하고 실행 가능한 형태로 만듭니다. 없는 노드를 가리키는 엣지나 입구(START)가 없는 그래프처럼 구조가 틀리면 여기서 오류 가 납니다. 실행 전에 걸러지는 것이 그래프의 장점입니다.invoke(...)— 끝까지 돌리고 마지막 State 를 돌려줍니다.stream(..., stream_mode="updates")— 노드마다 무엇이 바뀌었는지 차례로 보여 줍니다. 디버깅과 화면의 "검색 중..." 표시(24차시)에 씁니다.stream_mode값은 버전에 따라 다를 수 있으니 문서를 확인하세요.
3.3 체크포인터 — 미리 보기
compile()에 체크포인터 를 넘기면 노드가 끝날 때마다 State를 저장소에 남깁니다. 실행이 중간에 멈춰도 저장된 State에서 다시 시작할 수 있습니다. 축 ⑦에서 State를 메모리(이번 실행)에서 디스크(저장소) 쪽으로 옮기는 장치입니다.
앞으로 다시 만납니다 — 23차시에서 체크포인터가 핵심이 됩니다. 장비 신청을 사람이 승인할 때까지 그래프를 멈추고 , 몇 시간 뒤 승인이 오면 그 자리에서 재개 하려면 멈춘 State가 저장되어 있어야 합니다.
4. 누가 흐름을 정하는가 — 그래프의 자리
README 5.2의 중심 축 위에 지금까지 만든 것을 놓으면 이렇습니다.
| 1일차 워크플로우 | 9차시 에이전트 루프 | 18차시 그래프 | |
|---|---|---|---|
| 다음 단계를 정하는 것 | 코드 (if 문) | 모델 (매 반복 판단) | 큰 흐름은 엣지(코드), 칸 안의 판단은 모델 |
| 예측 가능성 | 높음 | 낮음 | 큰 흐름은 높음 |
| 실패 위치 찾기 | 쉬움 | 추적을 읽어야 함 | 어느 노드에서 인지 바로 보임 |
| 비용 | 낮음 | 반복 수만큼 | 노드 수만큼 (예측 가능) |
⭐ 그래프는 축의 왼쪽과 오른쪽을 섞는 방법 입니다. 앞에서 배운 에이전트 루프(9차시)에서는 모델이 "도구를 부를지, 끝낼지"를 매번 정했습니다. 그래프에서는 "분류 → 검색 → 답변"이라는 큰 순서를 코드가 그림으로 고정 하고, 각 칸 안에서 "이 질문의 의도는?", "이 근거로 어떻게 답할까?" 같은 판단만 모델에게 맡깁니다.
| 칸 안에 넣을 수 있는 것 | 축의 자리 |
|---|---|
| 규칙 함수 (LLM 없음) | 왼쪽 끝 |
| LLM 한 번 호출 (분류 · 요약) | 왼쪽에 가까움 |
| 에이전트 루프 (도구를 고르며 반복) | 오른쪽 — 칸 하나가 통째로 에이전트 |
앞으로 다시 만납니다 — 22차시의 관리 에이전트(Supervisor)는 그래프 안에서 모델이 다음 노드를 고르게 합니다. 같은 그래프라도 엣지를 누가 고르느냐에 따라 축의 오른쪽으로 옮겨 갑니다.
5. 오피스봇 흐름을 그래프로 다시 그리기
5.1 그림
1 · 2일차에 만든 부품을 노드로 놓으면 이렇습니다. 실선은 이 차시의 고정 엣지, 점선은 19차시 이후에 붙일 엣지입니다.
START
│
[load_profile] ← 17차시 장기 기억 주입
│
[classify] ← 4차시 의도 분류기
┊ ┊ ┊
[hr_tools] [search_policy] [it_tools] ← 6차시 도구 · 13~15차시 검색
┊ │ ┊
[answer] ← 14차시 근거 기반 답변
│
[save_memory] ← 17차시 기억 쓰기
│
END
| 노드 | 읽는 State | 쓰는 State |
|---|---|---|
| load_profile | user_id | profile |
| classify | question, profile | intent, confidence |
| search_policy | question | sources (리듀서로 누적) |
| answer | question, sources, profile | answer |
| save_memory | 전체 대화 | (DB에 저장, State는 그대로) |
💡 이 표를 먼저 쓰고 코드를 맡기세요. "누가 무엇을 읽고 쓰는가" 가 정해지면 노드끼리의 충돌(같은 필드를 두 노드가 덮어쓰기)을 미리 볼 수 있습니다.
5.2 그래프 그림으로 검수하기
print(app.get_graph().draw_mermaid()) # Mermaid 형식의 그림 문자열
🔍 읽는 포인트
- 컴파일된 그래프에서 실제 구조 를 그림으로 뽑습니다. 출력은 Mermaid라는 그림 문법 글이고, Mermaid를 지원하는 편집기나 웹 미리보기에 붙이면 그림이 됩니다. 함수 이름은 버전에 따라 다를 수 있으니 문서를 확인하세요.
- ⭐ 내가 그린 설계 그림(5.1)과 나란히 놓고 비교 합니다. 코드를 읽지 않고도 "빠진 노드", "엉뚱한 곳으로 가는 선", "END에 닿지 않는 길"을 찾을 수 있습니다. AI가 만든 그래프를 검수하는 가장 싼 방법입니다.
6. 실습 — 맡기고 검수하기
지시
오피스봇의 규정 질문 흐름을 LangGraph StateGraph로 다시 구성해줘.
참고 공식 문서: LangGraph 공식 문서의 StateGraph, State 리듀서, compile/invoke/stream, 그래프 시각화 항목.
설치된 langgraph 버전을 먼저 출력하고, 그 버전 문서 기준으로 작성할 것.
구조: START → load_profile → classify → search_policy → answer → save_memory → END (분기는 아직 만들지 말 것)
제약:
- State 필드: user_id, question, profile, intent, confidence, sources(리듀서로 누적), answer
- 각 노드는 바뀐 필드만 dict로 반환. 노드마다 [읽는 필드 | 쓰는 필드] 표를 함께 보고할 것.
- 기존 분류기 · 검색 · 답변 함수는 고치지 말고 노드로 감싸기만 할 것.
테스트: 평가셋의 규정 질문 10개를 invoke로 돌리고, 그중 1개는 stream(stream_mode="updates") 출력 원문을 붙일 것.
보고 형식: [질문 | intent | sources | 답 요약 | 기존 구현과 답이 같은가] 표 + draw_mermaid() 출력 원문.
추측하지 말고 실행 출력 원문을 붙여.
받을 증거
langgraph버전 출력 과 참고한 문서 주소- 노드별 읽는 필드 / 쓰는 필드 표
draw_mermaid()출력 원문, stream 출력 원문, 10개 질문 결과표
내가 직접 확인할 것
| 볼 것 | 이걸로 아는 것 |
|---|---|
| Mermaid 그림과 내 설계 그림(5.1)이 같은 모양 인가 | 설계대로 구현됐는가 (5.2) |
| stream 출력에서 노드가 설계한 순서대로 찍히는가 | 엣지가 맞게 이어졌는가 (3.2) |
sources가 덮어써지지 않고 누적 되는가 | 리듀서가 걸렸는가 (2.3) |
| 기존 구현과 답이 같은 비율 | 그래프로 옮기며 동작이 바뀌지 않았는가 (회귀 확인, 11차시) |
| 노드 표에서 두 노드가 같은 필드를 쓰는가 | 충돌 위험 |
부록 A. 스스로 확인하기
| # | 질문 | 답이 있는 곳 |
|---|---|---|
| 1 | 체인과 그래프를 가르는 세 가지(분기 · 되돌아감 · 병렬)는? | 1.1 |
| 2 | if 문으로 충분한데 그래프를 쓰면 안 되는 경우는? | 1.2 |
| 3 | State · Node · Edge를 유와 종차로 정의하면? | 2.1 |
| 4 | State가 장기 기억이 아닌 이유는? | 2.2 |
| 5 | 노드가 바뀐 부분만 돌려주는 이유와 리듀서의 역할은? | 2.3 |
| 6 | compile()에서 오류가 나는 것이 왜 장점인가? | 3.2 |
| 7 | 그래프가 "누가 흐름을 정하는가" 축에서 어디에 있나? | 4 |
| 8 | 코드를 읽지 않고 그래프 구현을 검수하는 방법은? | 5.2 |
부록 B. 자주 하는 실수
| 실수 | 바로잡기 |
|---|---|
| "그래프가 체인보다 발전된 것" | ❌ 단순한 흐름은 체인이나 if 문이 낫습니다. 갈림길 · 되돌아감이 늘 때 씁니다 |
| 노드가 State 전체를 새로 만들어 돌려줌 | ⚠️ 다른 노드가 쓴 값을 지울 수 있습니다. 바뀐 필드만 돌려주세요 |
| 리스트 필드에 리듀서 없이 여러 노드가 씀 | ❌ 덮어써집니다. Annotated[..., operator.add] 같은 리듀서를 거세요 |
| "노드 하나 = 에이전트 하나" | ❌ 노드는 함수입니다. 규칙 · LLM 한 번 · 에이전트 루프 무엇이든 담길 수 있습니다 |
| AI가 쓴 LangGraph 코드를 버전 확인 없이 실행 | ⚠️ 이름이 자주 바뀝니다. 버전 출력과 문서 주소를 받으세요 |
| 그래프 그림을 안 뽑고 코드만 검수 | ⭕ draw_mermaid()로 설계 그림과 비교하는 것이 가장 쌉니다 |
부록 C. NotebookLM 복습 프롬프트
이 18차시 노트만 근거로 비전공자용 5문제를 내줘.
"체인과 그래프의 경계", "State와 Node를 축 ③으로 구별하기", "리듀서", "큰 흐름은 코드, 칸 안 판단은 모델"은 반드시 포함해줘.
내가 답한 뒤에 채점하고, 틀린 이유를 노트의 절 번호를 근거로 설명해줘.
LangTailor