💡 LangGraph를 활용하여 LLM을 보다 스마트하게 만들기 위한 핵심 개념들
– Router, Agent, ReAct, State, 그리고 Memory –
에 대해 알아보겠습니다.
1️⃣ Router: LLM이 어떻게 경로를 결정하는가?
LangGraph에서 Router(라우터)는 입력된 메시지를 보고 적절한 처리 방식을 결정하는 역할을 합니다.

✅ Router의 실행 흐름
- Start → Step 1: 첫 번째 단계 실행
- Step 1 → LLM: LLM이 입력을 분석
- LLM이 판단:
- Step 2로 이동 → 자연어 응답 반환
- Step 3로 이동 → Tool 호출 후 결과 반환
즉, Router는 LLM이 단순 응답을 할지, Tool을 활용할지 결정하는 중요한 요소입니다.
✅ Tool을 사용하면 어떻게 동작이 달라질까?
예를 들어, 곱셈을 수행하는 multiply 함수가 있다고 가정해보겠습니다.
def multiply(a: int, b: int) -> int:
"""Multiply a and b.
Args:
a: first int
b: second int
"""
return a * b
이제 LLM이 다음과 같은 질문을 받았을 때, Tool을 사용할지 여부에 따라 결과가 달라집니다.
from langchain_core.messages import HumanMessage
messages = [HumanMessage(content="Hello, what is 2 multiplied by 2? .")]
messages = graph.invoke({"messages": messages})
for m in messages['messages']:
m.pretty_print()
✅ Tool을 사용한 경우:
================================ Human Message =================================
Hello, what is 2 multiplied by 2? .
================================== Ai Message ==================================
Tool Calls:
multiply (call_aB8WOVnTjdQTqrxmOkR967VC)
Call ID: call_aB8WOVnTjdQTqrxmOkR967VC
Args:
a: 2
b: 2
================================= Tool Message =================================
Name: multiply
4
✅ Tool을 사용하지 않은 경우:
from langchain_core.messages import HumanMessage
messages = [HumanMessage(content="Hello, what is 2 plus by 2? .")]
messages = graph.invoke({"messages": messages})
for m in messages['messages']:
m.pretty_print()
================================ Human Message =================================
Hello, what is 2 plus by 2? .
================================== Ai Message ==================================
The result of 2 plus 2 is 4.
💡 LLM이 필요하다고 판단하면 Tool을 사용하지만, 단순한 계산은 바로 응답하기도 합니다.
2️⃣ Agent: ReAct를 적용하여 더 똑똑하게 만들기
✅ 기존 방식 (Router만 사용)
LLM이 입력을 받고 두 가지 방식으로 처리할 수 있어:

- 바로 응답하는 경우 → 자연어 응답을 반환하고 끝 (End)
- 도구를 호출하는 경우 → 도구 실행 후 결과를 받아서 다시 처리 (Tools → End)
하지만 더 발전된 방식으로 ReAct 아키텍처를 적용할 수 있습니다.
✅ ReAct(Reasoning + Acting)란?
ReAct는 LLM이 도구를 사용한 후, 결과를 다시 활용하여 더 깊이 있는 응답을 생성하는 방식입니다.
ReAct의 3단계 흐름
- Act (행동, Acting)
- 모델이 입력을 분석하고, 도구(tool)를 호출할지 자연어로 답할지 결정함.
- 만약 도구 호출이 필요하면, ToolMessage(도구 호출 정보)를 생성.
- Observe (관찰, Observing)
- 도구를 호출한 후, 그 결과를 모델에게 다시 전달함.
- 즉, LLM이 도구의 결과를 보고 추가적인 판단을 내릴 수 있도록 함.
- Reason (추론, Reasoning)
- 모델이 도구의 결과를 분석하여 다음 행동을 결정.
- 여기서 다시 (1) 새로운 도구를 호출할 수도 있고, (2) 최종적으로 사용자에게 응답할 수도 있음.

여기서 중요한건 tool -> End로 가는게 아니라 LLM을 한번 더 거친다는것!!
✅ 기존 방식과 ReAct 방식 비교
🔴 기존 방식 (Tool을 한 번 호출하면 끝)
# 기존: tools 실행 후 바로 END
# builder.add_edge("tools", END) # 이 부분을 삭제하고 아래처럼 수정
# 🔥 루프 추가: tools 실행 후 다시 LLM 실행 가능
# builder.add_edge("tools", "tool_calling_llm")
🟢 ReAct 방식 (연속적인 계산 가능)
messages = [HumanMessage(content="Add 3 and 4. Multiply the output by 2. Divide the output by 5")]

그리고 결과가 항상 tool 을 사용하는것도 아니다 llm 이 판단 할때마다
필요하다고 생각하면 tool 을 사용해서 답변 하고 아니면 그냥 답변한다
💡 하지만 하나 확실한건 ReAct를 적용하면 복잡한 계산도 단계적으로 해결할 수 있다!
3️⃣ LangGraph의 기본 문제: 상태(State)가 휘발됨
LangGraph는 기본적으로 한 번 실행되면 상태(State)가 사라집니다.
✅ 상태가 사라지는 문제 예시
User: "What is 3 + 4?"
LLM: "7"
🚀 이제 사용자가 추가 질문을 하면?
User: "Now multiply that by 2."
LLM: "I don't know what 'that' refers to."
😡 문제: LLM이 이전 응답을 기억하지 못함. LangGraph는 기본적으로 한 번 실행된 후 상태가 사라지기 때문.
4️⃣ 해결책: Checkpointer로 상태 유지하기
LangGraph에서는 Checkpointer를 사용하면 이전 실행 상태를 저장하고 유지할 수 있습니다.
즉, LLM이 "7"을 기억하고 다음 계산을 수행할 수 있도록 하는 기능!
✅ 간단한 해결책: MemorySaver 사용하기
🚀 LangGraph는 기본적으로 여러 가지 Checkpointer를 제공하지만, 가장 간단한 방법은 MemorySaver를 사용.
🚀 MemorySaver는 Key-Value Store 방식으로 상태를 저장
from langgraph.checkpoint import MemorySaver
memory = MemorySaver() # 🔥 메모리 저장소 생성
graph = builder.compile(checkpointer=memory) # 🔥 체크포인터와 함께 그래프 컴파일
✅ 이제 실행될 때마다 상태가 자동으로 저장됨!
- LangGraph는 각 스텝(step) 실행 후 상태를 저장하고,
- 다시 실행할 때 저장된 상태에서 이어서 실행할 수 있음.
체크포인트를 저장하는 thread_id란
💡 thread_id는 LangGraph에서 상태(state)와 체크포인트를 저장하는 고유한 식별자(ID)
- LangGraph에서 메모리를 유지하려면, 여러 개의 상태를 저장해야 할 수 있음.
- 각 실행 흐름(Thread)은 여러 개의 상태(Checkpoint)를 저장하고,
- 나중에 이 실행 흐름을 다시 불러오려면 저장된 thread_id를 사용해야 함.
✅체크포인터(Checkpoint)를 사용하면 실행 상태를 저장할 수 있음.
✅thread_id를 사용하면 LangGraph가 여러 개의 상태를 저장하고, 특정 세션을 이어서 실행할 수 있음.
✅즉, thread_id는 LangGraph에서 "메모리 기능"을 제공하는 핵심 요소!🚀🔥

State란?
LangGraph에서 **State(상태)**는 그래프 내에서 데이터(값)를 저장하고 전달하는 구조
그래프에서 실행되는 모든 노드(Node)와 엣지(Edge)는 State를 입력(Input)으로 받고, 결과(Output)로 새로운 State를 생성.
즉, State는 그래프 전체의 "기억" 역할을 하는 핵심 요소
✅ State의 특징
- State에는 "Schema(구조)"가 필요함.
- State는 어떤 데이터를 저장하는지 정의해야 함.
- TypedDict 또는 Pydantic 모델을 사용해서 정의 가능.
- 모든 노드(Node)는 State를 입력으로 받고, 새로운 State를 출력함.
- 즉, 노드는 기존 상태를 수정하거나 새로운 데이터를 추가할 수 있음.
- State를 업데이트할 때 Reducer Function을 사용.
- Reducer는 기존 State를 어떻게 업데이트할지 정의하는 함수야.
- 예를 들어, 새로운 값을 추가할 수도 있고, 기존 값을 덮어쓸 수도 있음.
TypedDict vs dataclass vs Pydantic 비교
정의 방식 | class MyState(TypedDict): | @dataclass class MyState: | class MyState(BaseModel): |
속성 접근 | state["key"] | state.key | state.key |
유효성 검사 | ❌ 없음 | ❌ 없음 | ✅ 자동 검증 |
잘못된 값 입력 시 | 오류 없음 | 오류 없음 | ✅ 오류 발생 |
LangGraph 호환 | ✅ 가능 | ✅ 가능 | ✅ 가능 (가장 안전) |
🚀 마무리
✅ Router → LLM이 도구를 사용할지 판단
✅ Agent + ReAct → LLM이 도구를 반복적으로 활용 가능
✅ Checkpointer + thread_id → 실행 상태를 유지하여 문맥 기억
✅ State 사용 → 데이터 유지 및 업데이트 가능
'AI' 카테고리의 다른 글
GenAI + Auth0로 배우는 인증과 인가: 주식 투자 자동화 사례로 이해하기 (0) | 2025.03.09 |
---|---|
LangGraph 기반 AI 채팅의 상태 관리: Branching과 Custom Reducer의 역할 (0) | 2025.03.02 |
LangChain에서 LLM을 더 강력하게 만드는 방법: Control Flow와 Agent 개념 정리 (0) | 2025.03.01 |