2장에서 손으로 쌓던 대화, 이제는 라이브러리에게
한 줄 요약
지금까지 만든 네 에이전트는 한 번 묻고 답하면 끝입니다. "그거" 가 안 통하죠. 2장에서 손으로 쌓던 대화 기록을, 이번 장에서 LangGraph 에게 넘깁니다.
1. 지금의 문제
손님: P0003 스마트워치 재고가 얼마나 남았나요?
봇 : 3개 남았습니다.
손님: 그거 재주문 기준은요?
봇 : 어떤 상품을 말씀하시는지 알려주시면 확인해드리겠습니다.
한 줄 전에 답해 놓고 "그거"를 모릅니다. 2장 5절에서 본 그 현상입니다.
원인도 그때와 같습니다 — LLM 호출은 무상태이고, 우리가 ask() 를 부를 때마다 새 대화가 시작되기 때문입니다.
아래는 개념 설명용 코드입니다 — 실습 파일에 넣지 않습니다.
def ask(question: str) -> str:
out = agent.invoke({"messages": [{"role": "user", "content": question}]})
# └─ 매번 이 질문 하나만 들어간다
return out["messages"][-1].text.strip()
2. 2장에서는 이렇게 해결했습니다
아래는 개념 설명용 코드입니다 — 실습 파일에 넣지 않습니다.
history = []
history.append(say("user", first_turn))
answer = chat_multi(SYSTEM, history)
history.append(say("model", answer))
history.append(say("user", second_turn))
answer = chat_multi(SYSTEM, history)
history.append(say("model", answer))
리스트에 쌓아 매번 통째로 보내는 것. 원리는 이게 전부입니다.
그리고 2장 6절에서 이 방식의 한계 셋을 짚었습니다.
| 한계 | 무슨 뜻 |
|---|---|
| ① 손님이 여러 명이면? | history 가 하나뿐이라 대화가 섞인다 |
| ② 프로그램을 끄면 사라진다 | 메모리에만 있다 |
| ③ 무한정 길어진다 | 잘라내거나 요약해야 한다 |
이 세 가지를 직접 구현하면 금방 복잡해집니다.
3. LangGraph가 대신합니다
아래는 개념 설명용 코드입니다 — 실습 파일에 넣지 않습니다.
chatbot = graph.compile(checkpointer=InMemorySaver())
chatbot.invoke({"messages": [{"role": "user", "content": "..."}]},
{"configurable": {"thread_id": "손님-오지우"}})
두 가지가 새로 나옵니다.
| 새로운 것 | 2장의 무엇을 대신하나 |
|---|---|
checkpointer |
history.append(...) — 자동으로 쌓고 보관 |
thread_id |
손님마다 history 를 따로 두는 일 |
③(길이 관리)은 대화를 잘라 보내는 처리를 한 줄로 추가할 수 있게 됩니다. 다만 무엇을 얼마나 남길지는 여전히 사람이 정합니다 — 라이브러리가 알아서 잘라 주지는 않습니다. 4절에서 그 이유를 봅니다.
4. LangGraph란
LangChain 계열의 라이브러리입니다. 이름 그대로 그래프를 다룹니다.
LangChain 모델 연결, 도구, 에이전트, RAG
LangGraph 상태 · 기억 · 흐름 제어 ← 이번 장
5장에서 쓴 create_agent 도 사실 LangGraph 위에 만들어져 있습니다. 5장 4절에서 그림으로 봤던 그것입니다.
이 그림이 그래프입니다. 이번 장에서는 이런 그래프를 직접 조립해 봅니다.
5. 왜 "그래프"인가
프로그램을 상태를 들고 노드 사이를 이동하는 것으로 보는 방식입니다.
상태(state)를 들고
START ──▶ [노드 A] ──▶ [노드 B] ──▶ END
일을 하고 일을 하고
상태를 바꿈 상태를 바꿈
| 용어 | 무엇 | 2장으로 치면 |
|---|---|---|
| 상태(state) | 계속 들고 다니는 값 | history 리스트 |
| 노드(node) | 상태를 받아 일을 하고, 바뀐 부분을 돌려주는 함수 | chat_multi() |
| 엣지(edge) | 다음에 어느 노드로 갈지 | 코드의 실행 순서 |
어렵지 않습니다. 노드는 그냥 함수이고, 상태는 그냥 딕셔너리입니다.
그래서 클래스 이름이
StateGraph입니다 — 상태(State)를 들고 다니는 그래프(Graph). 이름이 곧 설명입니다. 다음 절에서 이 이름을 그대로 씁니다."이걸 왜 그래프로 그리나" — 흐름이 단순하면 그냥 함수를 순서대로 부르면 됩니다. 그래프가 유용한 때는 ① 상태를 자동으로 저장·복원해야 할 때(이번 장)와 ② 흐름이 갈라지거나 되돌아올 때(8장)입니다.
6. 이번 장의 순서
[1] StateGraph 세 가지 — 상태·노드·엣지 (2절)
[2] 직접 조립해 보기 — 노드 하나짜리 챗봇 (3절)
[3] 체크포인터 한 줄로 기억이 생긴다 (4절)
[4] 5장 CSV 에이전트에 기억 붙이기 (5·6절)
[5] thread_id 로 손님 구분하기 (7·8절)
작은 것부터 만듭니다. 노드 하나짜리 그래프를 먼저 만들어 보고, 그 다음에 앞에서 만든 에이전트에 붙입니다.
7. 이번 장이 도착하는 곳
[1] 같은 thread_id — 앞말을 기억한다
손님: 제 이름은 오지우입니다. 스마트워치를 샀어요.
봇 : 오지우 고객님, 스마트워치 구매 건으로 연락 주셨군요.
손님: 제가 뭘 샀다고 했죠?
봇 : 고객님께서는 스마트워치를 구매하셨다고 말씀해주셨습니다.
[2] thread_id 를 바꾸면 다른 손님 — 대화가 섞이지 않는다
손님: 제가 뭘 샀다고 했죠?
봇 : 아직 어떤 상품을 구매하셨는지 말씀해주시지 않았습니다.
[3] 기억을 붙인 CSV 분석 에이전트 — '그거'가 통한다
손님: P0003 스마트워치 재고가 얼마나 남았나요?
봇 : P0003 스마트워치 재고는 3개 남았습니다.
손님: 그거 재주문 기준은요?
봇 : P0003 스마트워치의 재주문 기준은 30개입니다.
손님: 그럼 지금 발주해야 하나요?
봇 : 네, 발주해야 합니다.
[3] 이 이번 장의 목표입니다. 5장에서 만든 에이전트가 그대로인데 "그거"가 통합니다.
8. 미리 짚어 둘 것
기억이 생긴다고 모든 게 해결되지는 않습니다.
| 해결되는 것 | 여전히 남는 문제 |
|---|---|
| "그거", "아까 그 상품"이 통한다 | 대화가 길어지면 비용이 는다 (2장 7절) |
| 손님별로 대화가 분리된다 | 어디까지 기억할지는 여전히 사람이 정한다 |
| 프로그램 재시작에도 남길 수 있다 | 잘못된 기억이 쌓이면 계속 영향을 준다 |
세 번째가 특히 실전에서 문제가 됩니다. 초반에 모델이 잘못 이해하면 그 오해가 대화 내내 따라다닙니다.
AI가 하는 것과 사람이 정하는 것 — LangGraph는 저장과 복원을 대신해 줍니다. 무엇을 기억할지, 언제 지울지는 여전히 사람의 설계입니다.
핵심 정리
- 지금까지의 네 에이전트는 한 번 묻고 답하면 끝입니다. "그거"가 안 통합니다.
- 2장에서는 리스트에 쌓아 매번 통째로 보내 해결했습니다. 원리는 그게 전부입니다.
- 손으로 하면 한계 셋 — 여러 손님 분리 · 저장 · 길이 관리.
- LangGraph 가 이걸 대신합니다.
checkpointer(자동 저장)와thread_id(손님 구분) 두 가지가 새로 나옵니다. - LangGraph는 프로그램을 상태를 들고 노드 사이를 이동하는 그래프로 봅니다.
- 노드는 함수, 상태는 딕셔너리입니다. 어렵지 않습니다.
- 5장의
create_agent도 LangGraph 위에 만들어져 있습니다. StateGraph는 이름 그대로 상태(State)를 들고 다니는 그래프(Graph) 입니다.- 기억이 생겨도 무엇을 기억할지는 여전히 사람의 설계입니다. 길이 관리는 라이브러리가 대신해 주지 않습니다.