[๊ฐ์๋ ธํธ] LangChain Academy : Introduction to LangGraph (Module 3)
์๋ณธ ๊ฒ์๊ธ: https://velog.io/@euisuk-chung/3-m82jb3x6
๋ญ์ฒด์ธ(LangChain)๊ณผ ๋ญ๊ทธ๋ํ(LangGraph)๋ ๋๊ท๋ชจ ์ธ์ด ๋ชจ๋ธ(LLM)์ ํ์ฉํ ์ ํ๋ฆฌ์ผ์ด์ ๊ฐ๋ฐ์ ์ํ ๋๊ตฌ๋ค์ ๋๋ค. ์ ๊ฐ์๋ LangChain์์ ์ด์ํ๋ LangChain Academy์์ ์ ์ํ โIntroduction to LangGraphโ ๊ฐ์์ ๋ด์ฉ์ ์ ๋ฆฌ ๋ฐ ์ถ๊ฐ ์ค๋ช ํ ๋ด์ฉ์ ๋๋ค.
- ๊ฐ์ ๋งํฌ : https://youtu.be/29XE10U6ooc
- ๋ญ์ฒด์ธ : https://www.langchain.com/
์ด๋ฒ ํฌ์คํธ๋ โModule3โ๋ด์ฉ์ ๋ค๋ฃน๋๋ค:
- Lesson 1: Streaming
- Lesson 2: Breakpoints
- Lesson 3: Editing State and Human Feedback
- Lesson 4: Dynamic Breakpoints
- Lesson 5: Time Travel
Lesson 1: Streaming
- ๋ณธ ๊ฐ์์์๋ ์ง๋๋ฒ์ ์ฌ์ฉํ๋ ์์ฝ LLM Agent๋ฅผ ์ฌ์ฉํ์ฌ ์ค์ต์ ์ํํฉ๋๋ค.
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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
from IPython.display import Image, display
from langchain_openai import ChatOpenAI
from langchain_core.messages import SystemMessage, HumanMessage, RemoveMessage
from langgraph.checkpoint.memory import MemorySaver
from langgraph.graph import StateGraph, START, END
from langgraph.graph import MessagesState
# LLM
model = ChatOpenAI(model="gpt-4o", temperature=0)
# ์ํ
class State(MessagesState):
summary: str
# ๋ชจ๋ธ์ ํธ์ถํ๋ ๋ก์ง ์ ์
def call_model(state: State):
# ์์ฝ์ด ์์ผ๋ฉด ๊ฐ์ ธ์ด
summary = state.get("summary", "")
# ์์ฝ์ด ์์ผ๋ฉด ์ถ๊ฐ
if summary:
# ์์คํ
๋ฉ์์ง์ ์์ฝ ์ถ๊ฐ
system_message = f"์ด์ ๋ํ์ ์์ฝ: {summary}"
# ์๋ก์ด ๋ฉ์์ง์ ์์ฝ ์ถ๊ฐ
messages = [SystemMessage(content=system_message)] + state["messages"]
else:
messages = state["messages"]
response = model.invoke(messages)
return {"messages": response}
def summarize_conversation(state: State):
# ๋จผ์ ๊ธฐ์กด ์์ฝ์ ๊ฐ์ ธ์ด
summary = state.get("summary", "")
# ์์ฝ ํ๋กฌํํธ ์์ฑ
if summary:
# ์ด๋ฏธ ์์ฝ์ด ์กด์ฌํจ
summary_message = (
f"์ง๊ธ๊น์ง์ ๋ํ ์์ฝ: {summary}\n\n"
"์์ ์๋ก์ด ๋ฉ์์ง๋ฅผ ๊ณ ๋ คํ์ฌ ์์ฝ์ ํ์ฅํ์ธ์:"
)
else:
summary_message = "์์ ๋ํ๋ฅผ ์์ฝํ์ธ์:"
# ํ๋กฌํํธ๋ฅผ ํ์คํ ๋ฆฌ์ ์ถ๊ฐ
messages = state["messages"] + [HumanMessage(content=summary_message)]
response = model.invoke(messages)
# ๊ฐ์ฅ ์ต๊ทผ 2๊ฐ์ ๋ฉ์์ง๋ฅผ ์ ์ธํ ๋ชจ๋ ๋ฉ์์ง ์ญ์
delete_messages = [RemoveMessage(id=m.id) for m in state["messages"][:-2]]
return {"summary": response.content, "messages": delete_messages}
# ๋ํ๋ฅผ ์ข
๋ฃํ ์ง ์์ฝํ ์ง ๊ฒฐ์
def should_continue(state: State):
"""๋ค์์ ์คํํ ๋
ธ๋๋ฅผ ๋ฐํํฉ๋๋ค."""
messages = state["messages"]
# ๋ฉ์์ง๊ฐ 6๊ฐ ์ด์์ด๋ฉด ๋ํ๋ฅผ ์์ฝ
if len(messages) > 6:
return "summarize_conversation"
# ๊ทธ๋ ์ง ์์ผ๋ฉด ์ข
๋ฃ
return END
# ์ ๊ทธ๋ํ ์ ์
workflow = StateGraph(State)
workflow.add_node("conversation", call_model)
workflow.add_node(summarize_conversation)
# ์์์ ์ ๋ํ๋ก ์ค์
workflow.add_edge(START, "conversation")
workflow.add_conditional_edges("conversation", should_continue)
workflow.add_edge("summarize_conversation", END)
# ์ปดํ์ผ
memory = MemorySaver()
graph = workflow.compile(checkpointer=memory)
display(Image(graph.get_graph().draw_mermaid_png()))
๊ฐ์
Langraph์ Streaming ๊ธฐ๋ฅ์ ์์ด์ ํธ๊ฐ ์์ ์ค ๋ฐ์ํ๋ ์ด๋ฒคํธ์ ์ํ๋ฅผ ์ค์๊ฐ์ผ๋ก ํ์ธํ ์ ์๋ ๊ธฐ๋ฅ์ ๋๋ค.
์ฃผ์ ๊ฐ๋
์คํธ๋ฆฌ๋ฐ ๋งค์๋
- LangGraph์
.stream()
๊ณผ.astream()
๋ฉ์๋๋ ๊ทธ๋ํ ์คํ ์ค ์ํ๋ฅผ ์คํธ๋ฆฌ๋ฐํ๋ ๋ ๊ฐ์ง ์ฃผ์ ๋ฐฉ๋ฒ์ ๋๋ค. ์ด ๋ ๋ฉ์๋์ ์ฐจ์ด์ ๊ณผ ์ฌ์ฉ ๋ฐฉ๋ฒ์ ์ค๋ช ํด๋๋ฆฌ๊ฒ ์ต๋๋ค:
-
.stream()
- ๋๊ธฐ ๋ฐฉ์ ์คํธ๋ฆฌ๋ฐ:.stream()
: Stream์๋๊ธฐ์(Synchronous)
๋ฐฉ์์ผ๋ก, ๊ฐ ๋ ธ๋๊ฐ ํธ์ถ๋ ๋๋ง๋ค ์ํ ์ ๋ฐ์ดํธ๋ฅผ ์คํธ๋ฆฌ๋ฐํฉ๋๋ค.-
๋๊ธฐ์ ์ผ๋ก ์๋ํ๋ค
๋ ๊ฒ์, ์์ ์ ํ๋์ฉ ์์ฐจ์ ์ผ๋ก ์ฒ๋ฆฌํ๋ ๊ฒ์ ์๋ฏธํฉ๋๋ค. ์์ ์์ ์ด ์๋ฃ๋์ง ์์ผ๋ฉด ๋ค์ ์์ ์ด ์คํ๋์ง ์์ต๋๋ค.- ์๋ฅผ ๋ค์ด, ์น๊ตฌ์๊ฒ ์ ํ๋ฅผ ๊ฑธ์ด์ ์น๊ตฌ๊ฐ ์ ํ๋ฅผ ๋ฐ๊ธฐ ์ ๊น์ง ๊ธฐ๋ค๋ฆฌ๋ ์ํฉ์ ์๊ฐํด ๋ณด์ธ์. ์ ํ๋ฅผ ๋ฐ๊ธฐ ์ ๊น์ง ๋ค๋ฅธ ์ผ์ ํ ์ ์๋ ๊ฒ์ฒ๋ผ, ๋๊ธฐ ๋ฐฉ์์ ํ๋์ ์์ ์ด ๋๋ ๋๊น์ง ๋๊ธฐํ ํ ๋ค์ ์์ ์ ์งํํ๋ ๋ฐฉ์์ ๋๋ค.
-
LangGraph์์
.stream()
๋ฉ์๋๋ ๋๊ธฐ ๋ฐฉ์์ผ๋ก ์๋ํฉ๋๋ค. ์ฆ, ๊ฐ ๋ ธ๋๊ฐ ์๋ฃ๋ ๋๊น์ง ๊ธฐ๋ค๋ฆฐ ํ ๊ทธ ๊ฒฐ๊ณผ๋ฅผ ๋ฐ์์ค๋ ๋ฐฉ์์ ๋๋ค. ์๋ฅผ ๋ค์ด, ๊ทธ๋ํ์ ์ฒซ ๋ฒ์งธ ๋ ธ๋๊ฐ ์คํ์ ์๋ฃํด์ผ ๋ ๋ฒ์งธ ๋ ธ๋๊ฐ ์คํ๋ ์ ์์ต๋๋ค.1 2
for chunk in graph.stream(inputs, stream_mode="values"): print(chunk) # ๊ฐ ๋ ธ๋ ์คํ ํ ์ถ๋ ฅ
- ์ด ์ฝ๋์์๋ ์ฒซ ๋ฒ์งธ ๋
ธ๋์
์คํ์ด ๋๋์ผ
๊ทธ ๊ฒฐ๊ณผ(chunk
)๊ฐ ์ถ๋ ฅ๋๊ณ , ๊ทธ ํ์์ผ ๋ ๋ฒ์งธ ๋ ธ๋๊ฐ ์คํ๋ฉ๋๋ค. - ๋ชจ๋ ์์ ์ด ์์๋๋ก ์ด๋ฃจ์ด์ง๊ธฐ ๋๋ฌธ์ ์ฝ๋์ ํ๋ฆ์ด ์์ธก ๊ฐ๋ฅํ๊ณ ์ง๊ด์ ์ ๋๋ค. ๊ทธ๋ฌ๋ ์์ ์ด ์ค๋ ๊ฑธ๋ฆด ๊ฒฝ์ฐ, ๊ทธ ์์ ์ด ๋๋ ๋๊น์ง ์๋ฌด๊ฒ๋ ํ ์ ์์ต๋๋ค.
- ์ด ์ฝ๋์์๋ ์ฒซ ๋ฒ์งธ ๋
ธ๋์
-
.astream()
- ๋น๋๊ธฐ ๋ฐฉ์ ์คํธ๋ฆฌ๋ฐ:.astream()
: A-Stream์๋น๋๊ธฐ์(Asynchronous)
๋ฐฉ์์ผ๋ก ์ํ ์ ๋ฐ์ดํธ๋ฅผ ์คํธ๋ฆฌ๋ฐํฉ๋๋ค.-
๋น๋๊ธฐ์ ์ผ๋ก ์๋ํ๋ค
๋ ๊ฒ์, ์์ ์ ๋์์ ์ฒ๋ฆฌํ ์ ์๋ค๋ ๊ฒ์ ์๋ฏธํฉ๋๋ค. ์์ ์์ ์ด ๋๋ ๋๊น์ง ๊ธฐ๋ค๋ฆฌ์ง ์๊ณ , ๋ค๋ฅธ ์์ ์ ์งํํ ์ ์์ต๋๋ค.- ๋ค์ ์ ํ๋ฅผ ๊ฑฐ๋ ์์๋ก ๋์๊ฐ๋ฉด, ์น๊ตฌ๊ฐ ์ ํ๋ฅผ ๋ฐ์ ๋๊น์ง ๊ธฐ๋ค๋ฆฌ์ง ์๊ณ ๋ค๋ฅธ ์ผ์ ํ ์ ์๋ ์ํฉ์ ์์ํด ๋ณด์ธ์. ์น๊ตฌ๊ฐ ์ ํ๋ฅผ ๋ฐ์ผ๋ฉด ๊ทธ๋ ๋ค์ ํตํ๋ก ๋์์ฌ ์ ์์ต๋๋ค.
-
LangGraph์์
.astream()
๋ฉ์๋๋ ๋น๋๊ธฐ ๋ฐฉ์์ผ๋ก ์๋ํฉ๋๋ค. ์ฆ, ํ ์์ ์ด ๋๋ ๋๊น์ง ๊ธฐ๋ค๋ฆฌ์ง ์๊ณ ๋ค๋ฅธ ์์ ์ ๋์์ ์ฒ๋ฆฌํ ์ ์์ต๋๋ค. ์ด ๋ฐฉ์์ ํนํ ๋ง์ ์๊ฐ์ด ๊ฑธ๋ฆฌ๋ ์์ (์: ๋คํธ์ํฌ ์์ฒญ, ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ ๊ทผ ๋ฑ)์ ํจ์จ์ ์ผ๋ก ์ฒ๋ฆฌํ๋ ๋ฐ ์ ๋ฆฌํฉ๋๋ค.1 2
async for chunk in graph.astream(inputs, stream_mode="values"): print(chunk) # ๊ฐ ๋ ธ๋ ์คํ์ด ๋๋ ๋๊น์ง ๊ธฐ๋ค๋ฆฌ์ง ์๊ณ ์ฒ๋ฆฌ
- ์ด ์ฝ๋์์๋ ์ฒซ ๋ฒ์งธ ๋
ธ๋์ ์คํ์ด
์๋ฃ๋๊ธฐ ์ ์
๋ ๋ฒ์งธ ๋ ธ๋๊ฐ ์คํ๋ ์ ์์ต๋๋ค. - ๋น๋๊ธฐ ๋ฐฉ์ ๋๋ถ์ ์ฌ๋ฌ ์์ ์ ๋์์ ์ฒ๋ฆฌํ ์ ์์ง๋ง, ๊ทธ๋ก ์ธํด ์ฝ๋์ ์คํ ์์๊ฐ ๋ณต์กํด์ง ์ ์์ต๋๋ค.
- ์ด ์ฝ๋์์๋ ์ฒซ ๋ฒ์งธ ๋
ธ๋์ ์คํ์ด
๐ค ์ธ์ ๋๊ธฐ/๋น๋๊ธฐ๋ฅผ ์ฌ์ฉํด์ผ ํ ๊น?
- ๋๊ธฐ: ์์ ์ด ๋น๊ต์ ๋น ๋ฅด๊ฒ ๋๋๊ณ ์์ฐจ์ ์ผ๋ก ์คํํด์ผ ํ ๋ ์ฌ์ฉํฉ๋๋ค.
- ์๋ฅผ ๋ค์ด, ์์ ๊ท๋ชจ์ ํ๋ก๊ทธ๋จ์ด๋ ํ ๋ฒ์ ํ๋์ฉ ์ฒ๋ฆฌํด์ผ ํ๋ ์์ ์์ ๋๊ธฐ ๋ฐฉ์์ ์ฌ์ฉํ๋ ๊ฒ์ด ์ข์ต๋๋ค.
- ๋น๋๊ธฐ: ๋น๋๊ธฐ๋ ์ฌ๋ฌ ์์ ์ ๋์์ ์ฒ๋ฆฌํด์ผ ํ ๋ ํนํ ์ ์ฉํฉ๋๋ค.
- ์๋ฅผ ๋ค์ด, ๋คํธ์ํฌ ์์ฒญ, ํ์ผ ์ฝ๊ธฐ/์ฐ๊ธฐ, ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ ๊ทผ๊ณผ ๊ฐ์ด ์๊ฐ์ด ์ค๋ ๊ฑธ๋ฆฌ๋ ์์ ์์ ๋น๋๊ธฐ ๋ฐฉ์์ ์ฌ์ฉํ๋ ๊ฒ์ด ๋ ํจ์จ์ ์ ๋๋ค.
์คํธ๋ฆฌ๋ฐ ๋ชจ๋
- ์คํธ๋ฆผ ๋ชจ๋๋ LangGraph๊ฐ ์คํ๋๋ ๋์ ๊ทธ๋ํ์ ์ํ๋ฅผ ์ด๋ค ๋ฐฉ์์ผ๋ก ๋ชจ๋ํฐ๋งํ๊ณ ํ์ธํ ๊ฒ์ธ์ง๋ฅผ ๊ฒฐ์ ํ๋ ์ต์ ์ ๋๋ค.
- ๊ฐ ๋ชจ๋๋ ๊ทธ๋ํ๊ฐ ์คํ๋ ๋ ์์ฑ๋๋ ๋ฐ์ดํฐ๋ฅผ ์ด๋ป๊ฒ ์คํธ๋ฆฌ๋ฐํ ์ง์ ๋ํ ๋ฐฉ์์ ์ ์ํฉ๋๋ค. ์ฝ๊ฒ ๋งํ๋ฉด, ์คํธ๋ฆฌ๋ฐ ๋ชจ๋๋ ์คํ ์ค ๋ฐ์ํ๋ ์ ๋ณด(์ํ๋ ๊ฒฐ๊ณผ๋ฌผ)๋ฅผ ์ค์๊ฐ์ผ๋ก ์ด๋ป๊ฒ ๋ณด๊ณ ํ ๊ฒ์ธ์ง๋ฅผ ๋ํ๋ ๋๋ค.
์ฃผ์ ์คํธ๋ฆผ ๋ชจ๋
-
โvaluesโ ๋ชจ๋:
- ๊ทธ๋ํ์ ๊ฐ ๋ ธ๋๊ฐ ์คํ๋ ํ, ์ ์ฒด ์ํ๋ฅผ ์คํธ๋ฆฌ๋ฐํฉ๋๋ค.
- ์ฆ, ๋ ธ๋๊ฐ ์คํ๋ ํ ํ์ฌ๊น์ง์ ๋ชจ๋ ์ํ๋ฅผ ํ์ธํ ์ ์์ต๋๋ค.
- ์๋ฅผ ๋ค์ด, ๋ ธ๋๊ฐ ์คํ๋ ๋๋ง๋ค ์ ์ฒด ์์ ํ๋ฆ์ ์ค๋ ์ท์ ์ ๊ณต๋ฐ๋ ๊ฒ๊ณผ ๊ฐ์ต๋๋ค. ์ด ๋ฐฉ๋ฒ์ ๊ฐ ๋ ธ๋ ์คํ ํ ์ ์ฒด ์ํ๋ฅผ ๊ฒํ ํ ๋ ์ ์ฉํฉ๋๋ค.
์ฌ์ฉ ์์:
1 2
for chunk in graph.stream(inputs, stream_mode="values"): print(chunk) # ๊ฐ ๋ ธ๋ ์คํ ํ ์ ์ฒด ์ํ ์ถ๋ ฅ
-
โupdatesโ ๋ชจ๋:
- ๊ฐ ๋ ธ๋ ์คํ ํ ๋ณ๊ฒฝ๋ ๋ถ๋ถ๋ง ์คํธ๋ฆฌ๋ฐํฉ๋๋ค.
- ์ฆ, ์ํ๊ฐ ๋ณํ ๋ถ๋ถ๋ง ์ค์๊ฐ์ผ๋ก ๋ณผ ์ ์์ด, ๋ถํ์ํ ๋ฐ์ดํฐ๋ฅผ ์ค์ด๊ณ ๊ด์ฌ ์๋ ๋ถ๋ถ๋ง ํ์ธํ๋ ๋ฐ ์ ๋ฆฌํฉ๋๋ค.
- ์๋ฅผ ๋ค์ด, ๋๊ท๋ชจ ๋ฐ์ดํฐ๋ฅผ ์ฒ๋ฆฌํ๋ ๊ทธ๋ํ์์ ๋ณํ๊ฐ ์๋ ๋ถ๋ถ๋ง ํ์ธํ๊ณ ์ถ์ ๋ ์ ์ฉํฉ๋๋ค.
์ฌ์ฉ ์์:
1 2
for chunk in graph.stream(inputs, stream_mode="updates"): print(chunk) # ๋ณ๊ฒฝ๋ ์ํ๋ง ์ถ๋ ฅ
- LLM ํ ํฐ ์คํธ๋ฆฌ๋ฐ: LLM ํ ํฐ ์คํธ๋ฆฌ๋ฐ์ LangGraph์์ ๋ํ ์ธ์ด ๋ชจ๋ธ(LLM)์ด ์์ฑํ๋
ํ ์คํธ๋ฅผ ์ค์๊ฐ์ผ๋ก ๋ชจ๋ํฐ๋งํ๋ ๊ธฐ๋ฅ
์ ๋๋ค.- LLM์ ์ฃผ๋ก ๊ธด ํ ์คํธ๋ฅผ ์์ฑํ๋๋ฐ, ๋ชจ๋ ๊ฒฐ๊ณผ๊ฐ ํ ๋ฒ์ ๋ฐํ๋๋ ๋์ ํ ํฐ์ด๋ผ๋ ์์ ๋จ์๋ก ๊ฒฐ๊ณผ๋ฅผ ์ ์ง์ ์ผ๋ก ์ ๊ณตํ ์ ์์ต๋๋ค.
- ์ด๋ ์คํธ๋ฆฌ๋ฐ์ ํตํด ๋ชจ๋ธ์ด ์์ฑํ๋ ํ ์คํธ๋ฅผ ํ ๋ฒ์ ๊ธฐ๋ค๋ฆฌ์ง ์๊ณ ์ค์๊ฐ์ผ๋ก ํ์ธํ ์ ์๋ ์ฅ์ ์ด ์์ต๋๋ค. ์ด ๊ธฐ๋ฅ์ ๋ํํ ์์คํ , ๊ธด ํ ์คํธ ์์ฑ ์์ ์์ ํนํ ์ ์ฉํฉ๋๋ค.
๐ก LLM ํ ํฐ ์คํธ๋ฆฌ๋ฐ์ ๋ชฉ์
์ค์๊ฐ ์๋ต ์ ๊ณต: LLM์ด ์๋ต์ ์์ฑํ๋ ๋์ ์ฆ๊ฐ์ ์ธ ํผ๋๋ฐฑ์ ์ฌ์ฉ์์๊ฒ ์ ๊ณตํ์ฌ ๊ธฐ๋ค๋ฆฌ๋ ์๊ฐ์ ์ค์ด๊ณ , ์ฌ์ฉ์ ๊ฒฝํ์ ํฅ์์ํฌ ์ ์์ต๋๋ค.
์ธ๋ฐํ ๋ชจ๋ํฐ๋ง: LLM์ด ์ค๊ฐ์ ์์ฑํ๋ ํ ์คํธ๋ฅผ ๊ด์ฐฐํ์ฌ ์์คํ ์ ์ฑ๋ฅ์ ํ๊ฐํ๊ฑฐ๋ ๋ฌธ์ ๋ฅผ ๋๋ฒ๊น ํ ์ ์์ต๋๋ค.
์ ์ฐํ ์ ์ด: ํน์ ๋ ธ๋์์ ๋ฐ์ํ๋ ํ ํฐ๋ง์ ์ค์๊ฐ์ผ๋ก ํ์ธํ๊ณ , ํ์ํ ๊ฒฝ์ฐ ์ค๊ฐ์ ๊ฐ์ ํ๊ฑฐ๋ ์ ์ดํ ์ ์์ต๋๋ค.
์คํธ๋ฆฌ๋ฐ ํ ํฐ์ ์ฃผ์ ๊ฐ๋
- ์ด๋ฒคํธ ๊ธฐ๋ฐ ์คํธ๋ฆฌ๋ฐ:
- LLM์ด ํ ์คํธ๋ฅผ ์์ฑํ๋ ๋์ ์ด๋ฒคํธ๊ฐ ๋ฐ์ํ๋ฉฐ, ์ด๋ฌํ ์ด๋ฒคํธ๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ํ ํฐ์ด ์คํธ๋ฆฌ๋ฐ๋ฉ๋๋ค.
- LangGraph์์
.astream_events()
๋ฉ์๋๋ฅผ ์ฌ์ฉํด ์ด๋ฌํ ์ด๋ฒคํธ๋ฅผ ๋น๋๊ธฐ์ ์ผ๋ก ์ฒ๋ฆฌํ ์ ์์ต๋๋ค.
1
2
3
4
config = {"configurable": {"thread_id": "3"}}
input_message = HumanMessage(content="ํผํ์์ ๋ํ๋ฏผ๊ตญ์ ์ด๋์ ๋ ์ค๋ ฅ์ธ๊ฐ์?")
async for event in graph.astream_events({"messages": [input_message]}, config, version="v2"):
print(f"๋
ธ๋: {event['metadata'].get('langgraph_node','')}. ์ ํ: {event['event']}. ์ด๋ฆ: {event['name']}")
-
์ถ๋ ฅ ์์:
1 2 3 4 5 6 7 8
๋ ธ๋: . ์ ํ: on_chain_start. ์ด๋ฆ: LangGraph ๋ ธ๋: __start__. ์ ํ: on_chain_start. ์ด๋ฆ: __start__ ๋ ธ๋: __start__. ์ ํ: on_chain_end. ์ด๋ฆ: __start__ ๋ ธ๋: conversation. ์ ํ: on_chain_start. ์ด๋ฆ: conversation ๋ ธ๋: conversation. ์ ํ: on_chat_model_start. ์ด๋ฆ: ChatOpenAI ๋ ธ๋: conversation. ์ ํ: on_chat_model_stream. ์ด๋ฆ: ChatOpenAI (์ค๋ต)
- ์ด ์ฝ๋๋ ๊ทธ๋ํ์ ํน์ ๋ ธ๋์์ ๋ฐ์ํ๋ ์ด๋ฒคํธ๋ฅผ ์ค์๊ฐ์ผ๋ก ์ถ์ ํ์ฌ, ๊ฐ ๋ ธ๋๊ฐ ์ด๋ค ์์ ์ ํ๊ณ ์๋์ง ์ํ ์ ๋ณด๋ฅผ ์ป์ต๋๋ค.
-
ํด๋น ์ฝ๋ ๋ฐ ์ถ๋ ฅ ๊ฒฐ๊ณผ์์
event['metadata']
,event['event']
,event['name']
๋ฑ์ LangGraph์์ ๋ฐ์ํ๋ ์ด๋ฒคํธ์ ๊ตฌ์ฑ ์์์ ๋๋ค.- ์ด๋ฌํ ๊ตฌ์ฑ ์์๋ค์ ๊ทธ๋ํ ์คํ ์ค ๋ฐ์ํ๋ ์ด๋ฒคํธ์ ๋ํ ๋ฉํ๋ฐ์ดํฐ๋ฅผ ์ ๊ณตํฉ๋๋ค. ์ด๋ฅผ ํตํด ์ด๋ค ๋ ธ๋์์ ์ด๋ค ์ ํ์ ์ด๋ฒคํธ๊ฐ ๋ฐ์ํ๋์ง, ๊ทธ ์ด๋ฒคํธ์ ์ด๋ฆ์ด ๋ฌด์์ธ์ง๋ฅผ ์ค์๊ฐ์ผ๋ก ์ถ์ ํ ์ ์์ต๋๋ค.
- ์๋ฅผ ๋ค์ด, ์ ์ถ๋ ฅ ๋ง์ง๋ง์ ๋ํํ AI์์ ํ์ฌ LLM์ด โon_chat_model_streamโ ์ด๋ฒคํธ๋ฅผ ํตํด ํ์ฌ ํด๋น ๋ ธ๋์์ ์๋ต์ ์์ฑํ๋ ์ค์์ ํ์ธํ ์ ์์ต๋๋ค.
-
์ด๋ฒคํธ ๊ตฌ์กฐ:
-
event
: ๋ฐ์ํ์ด๋ฒคํธ์ ์ ํ
์ ๋ํ๋ ๋๋ค. ์๋ฅผ ๋ค์ด, LLM์ด ์คํธ๋ฆฌ๋ฐํ๋ ์ด๋ฒคํธ๋"on_chat_model_stream"
ํน์"on_chain_stream"
๊ฐ์ ์ ํ์ผ๋ก ํ์๋ฉ๋๋ค.on_chat_model_stream
: LLM(๋ํ ์ธ์ด ๋ชจ๋ธ)์ด ์์ฑํ๋ ํ ํฐ์ ์ค์๊ฐ์ผ๋ก ์คํธ๋ฆฌ๋ฐํ๋ ์ด๋ฒคํธ์ ๋๋ค. LLM์ด ์ ๋ ฅ์ ๋ฐ์ ์๋ต์ ์์ฑํ๋ ๊ณผ์ ์์ ์์ฑ๋ ๋ถ๋ถ์ ์ธ ํ ์คํธ(ํ ํฐ)๋ค์ด ์ค์๊ฐ์ผ๋ก ์ ์ก๋ฉ๋๋ค.on_chain_stream
: ๊ทธ๋ํ์ ํน์ ๋ ธ๋์์ ๋ฐ์ํ๋ ์ผ๋ฐ์ ์ธ ์ด๋ฒคํธ ์คํธ๋ฆฌ๋ฐ์ ๋ํ๋ ๋๋ค. ๋ชจ๋ ๋ ธ๋์์ ๋ฐ์ํ ์ ์๋ ์ด๋ฒคํธ์ด๋ฉฐ, ๊ฐ ๋ ธ๋๊ฐ ์คํ๋๋ฉด์ ์ผ์ด๋๋ ์์ ์ ์ํ๋ฅผ ์คํธ๋ฆฌ๋ฐํฉ๋๋ค.
name
:์ด๋ฒคํธ์ ์ด๋ฆ
์ผ๋ก, ์ด๋ค ๋ ธ๋์์ ๋ฐ์ํ ์ด๋ฒคํธ์ธ์ง ๋ช ์ํฉ๋๋ค.data
:์ด๋ฒคํธ์ ๊ด๋ จ๋ ๋ฐ์ดํฐ
๋ฅผ ๋ด๊ณ ์์ต๋๋ค. LLM์ด ์์ฑํ๋ ์ค์ ํ ์คํธ ๋ฐ์ดํฐ(ํ ํฐ)๋ฅผ ํฌํจํฉ๋๋ค. ์ด ๋ฐ์ดํฐ๋AIMessageChunk
์ ๊ฐ์ ํํ๋ก ์ ๊ณต๋ฉ๋๋ค.metadata
: ์ด ํ๋๋์ถ๊ฐ์ ์ธ ๋ฉํ ์ ๋ณด
๋ฅผ ํฌํจํฉ๋๋ค. ์ฌ๊ธฐ์๋ ์ด๋ฒคํธ๊ฐ ๋ฐ์ํ ๋ ธ๋(์:langgraph_node
)์ ๋ํ ์ ๋ณด๋ ํฌํจ๋ฉ๋๋ค.
-
์คํธ๋ฆฌ๋ฐ ๋ฐฉ๋ฒ
- ํน์ ๋
ธ๋์ ํ ํฐ ์คํธ๋ฆฌ๋ฐ:
- LLM์ด ์ฌ๋ฌ ๋ ธ๋์์ ํธ์ถ๋ ์ ์๊ธฐ ๋๋ฌธ์, ํน์ ๋ ธ๋์์ ๋ฐ์ํ๋ ํ ํฐ๋ง์ ์คํธ๋ฆฌ๋ฐํ๊ณ ์ถ์ ๋๊ฐ ์์ต๋๋ค.
- ์ด๋ฅผ ์ํด
metadata['langgraph_node']
๊ฐ์ ์ด์ฉํด ํน์ ๋ ธ๋์ ์ถ๋ ฅ์ ํํฐ๋งํ ์ ์์ต๋๋ค.
on_chain_stream
:
- ์ด ์ฝ๋๋ ํน์ ๋ ธ๋(์:
conversation
)์ ์ผ๋ฐ์ ์ธ ์ํ ๋ณํ๋ ๊ทธ๋ํ ์คํ ์ํ๋ฅผ ์ถ์ ํ๊ธฐ ์ํ ์ฉ๋๋ก ์ฌ์ฉ๋ฉ๋๋ค.- LLM์ ํ ํฐ ์์ฑ๊ณผ๋ ์ง์ ์ ์ธ ๊ด๋ จ์ด ์์ผ๋ฉฐ, ๊ทธ๋ํ์ ๋ ธ๋ ์ํ ๋ณํ๋ฅผ ์ถ์ ํ๋ ๋ฐฉ์์ ๋๋ค.
1
2
3
4
5
6
7
8
# on_chain_stream
node_to_stream = 'conversation'
config = {"configurable": {"thread_id": "1"}}
input_message = HumanMessage(content="ํผํ์์ ๋ํ๋ฏผ๊ตญ์ ์ด๋์ ๋ ์ค๋ ฅ์ธ๊ฐ์?")
async for event in graph.astream_events({"messages": [input_message]}, config, version="v2"):
# ํน์ ๋
ธ๋์ ์ํ ๋ณํ ์ถ์ ํ๊ธฐ
if event["event"] == "on_chain_stream" and event['metadata'].get('langgraph_node','') == node_to_stream:
print(event["data"])
on_chain_stream
: ๊ทธ๋ํ์ ์ํ ๋ณํ๋ ๋ ธ๋ ์คํ ์ํ๊ฐ ๋ฐ๋ ๋ ๋ฐ์ํ๋ ์ด๋ฒคํธ์ ๋๋ค. LLM์ ์๋ต ์์ฑ์ด ๋๋๊ณ ๊ทธ๋ํ์ ์ํ๊ฐ ๋ณํ๋ฉด ์ด ์ด๋ฒคํธ๊ฐ ๋ฐ์ํ๋ฉฐ, ์ด๋ ์ ์ฒด ์๋ต์ด ๋์จ ์ด์ ๋ LLM์ด ๋ต๋ณ์ ์๋ฃํ๊ธฐ ๋๋ฌธ์ด ์๋๋ผ, ๊ทธ๋ํ ๋ด ๋ ธ๋์ ์ํ๊ฐ ๋ณํํ๊ธฐ ๋๋ฌธ์ ๋๋ค.
1
2
3
4
5
6
7
8
9
node_to_stream = 'conversation'
config = {"configurable": {"thread_id": "2"}}
input_message = HumanMessage(content="ํผํ์์ ๋ํ๋ฏผ๊ตญ์ ์ด๋์ ๋ ์ค๋ ฅ์ธ๊ฐ์?")
async for event in graph.astream_events({"messages": [input_message]}, config, version="v2"):
# ํน์ ๋
ธ๋์์ ์ฑํ
๋ชจ๋ธ ํ ํฐ ๊ฐ์ ธ์ค๊ธฐ
if event["event"] == "on_chat_model_stream" and event['metadata'].get('langgraph_node','') == node_to_stream:
# if event["event"] == "on_chain_stream" and event['metadata'].get('langgraph_node','') == node_to_stream:
print(event["data"])
(์ค๋ต)
on_chat_model_stream
:
- ์ด ์ฝ๋๋ LLM์ ์๋ต์ด ์ ์ง์ ์ผ๋ก ์์ฑ๋ ๋๋ง๋ค ๋ฐ์ํ๋ ์ด๋ฒคํธ๋ฅผ ์คํธ๋ฆฌ๋ฐํฉ๋๋ค.
on_chat_model_stream
์ด๋ฒคํธ๋ LLM์ด ์๋ต์ ์์ฑํ ๋ ๋ถ๋ถ์ ์ผ๋ก ์์ฑ๋ ํ ์คํธ(ํ ํฐ)๋ฅผ ์ค์๊ฐ์ผ๋ก ์ถ์ ํ๋ ๋ฐ ์ฌ์ฉ๋ฉ๋๋ค.
1
2
3
4
5
6
7
8
# on_chat_model_stream
node_to_stream = 'conversation'
config = {"configurable": {"thread_id": "2"}}
input_message = HumanMessage(content="ํผํ์์ ๋ํ๋ฏผ๊ตญ์ ์ด๋์ ๋ ์ค๋ ฅ์ธ๊ฐ์?")
async for event in graph.astream_events({"messages": [input_message]}, config, version="v2"):
# ํน์ ๋
ธ๋์์ ์ฑํ
๋ชจ๋ธ ํ ํฐ ์ถ์ ํ๊ธฐ
if event["event"] == "on_chat_model_stream" and event['metadata'].get('langgraph_node','') == node_to_stream:
print(event["data"])
on_chat_model_stream
: LLM์ด ํ ํฐ ๋จ์๋ก ์ ์ง์ ์ผ๋ก ์๋ต์ ์์ฑํ ๋๋ง๋ค ๋ฐ์ํ๋ ์ด๋ฒคํธ์ ๋๋ค. LLM์ด ๊ธด ์๋ต์ ์์ฑํ ๋ ๊ฐ๊ฐ์ ํ ํฐ(์์ ํ ์คํธ ๋จ์)์ด ์ค์๊ฐ์ผ๋ก ์คํธ๋ฆฌ๋ฐ๋์ด ๊ฒฐ๊ณผ๊ฐ ๋์ค๋ ๊ฒ์ ๋๋ค. ์ด ๋ฐฉ์์ ์ค์๊ฐ์ผ๋ก ํ ํฐ์ ์ถ์ ํ๋ฉฐ, ๊ธด ์๋ต์ ๊ธฐ๋ค๋ฆฌ์ง ์๊ณ ์ค๊ฐ ๊ฒฐ๊ณผ๋ฅผ ๋น ๋ฅด๊ฒ ํ์ธํ ์ ์์ต๋๋ค.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
{'chunk': AIMessageChunk(content='๋ํ', additional_kwargs={}, response_metadata={}, id='run-f5d6eb65-2633-4bfe-bb73-82cc16348735')}
{'chunk': AIMessageChunk(content='๋ฏผ๊ตญ', additional_kwargs={}, response_metadata={}, id='run-f5d6eb65-2633-4bfe-bb73-82cc16348735')}
{'chunk': AIMessageChunk(content=' ์ถ', additional_kwargs={}, response_metadata={}, id='run-f5d6eb65-2633-4bfe-bb73-82cc16348735')}
{'chunk': AIMessageChunk(content='๊ตฌ', additional_kwargs={}, response_metadata={}, id='run-f5d6eb65-2633-4bfe-bb73-82cc16348735')}
{'chunk': AIMessageChunk(content=' ๊ตญ๊ฐ', additional_kwargs={}, response_metadata={}, id='run-f5d6eb65-2633-4bfe-bb73-82cc16348735')}
{'chunk': AIMessageChunk(content='๋ํ', additional_kwargs={}, response_metadata={}, id='run-f5d6eb65-2633-4bfe-bb73-82cc16348735')}
{'chunk': AIMessageChunk(content='ํ', additional_kwargs={}, response_metadata={}, id='run-f5d6eb65-2633-4bfe-bb73-82cc16348735')}
{'chunk': AIMessageChunk(content='์', additional_kwargs={}, response_metadata={}, id='run-f5d6eb65-2633-4bfe-bb73-82cc16348735')}
{'chunk': AIMessageChunk(content=' ์', additional_kwargs={}, response_metadata={}, id='run-f5d6eb65-2633-4bfe-bb73-82cc16348735')}
{'chunk': AIMessageChunk(content='์์', additional_kwargs={}, response_metadata={}, id='run-f5d6eb65-2633-4bfe-bb73-82cc16348735')}
{'chunk': AIMessageChunk(content='์์', additional_kwargs={}, response_metadata={}, id='run-f5d6eb65-2633-4bfe-bb73-82cc16348735')}
{'chunk': AIMessageChunk(content=' ๊ฐ', additional_kwargs={}, response_metadata={}, id='run-f5d6eb65-2633-4bfe-bb73-82cc16348735')}
{'chunk': AIMessageChunk(content='๋ ฅ', additional_kwargs={}, response_metadata={}, id='run-f5d6eb65-2633-4bfe-bb73-82cc16348735')}
(์ค๋ต)
- ํ ํฐ ๋ฐ์ดํฐ ์ถ์ถ:
- ์ด๋ฒคํธ์์
event['data']
๊ฐ์ดAIMessageChunk
ํํ๋ก ์ ๊ณต๋๋ฉฐ, ์ด๋ฅผ ์ฌ์ฉํด ์ค์ ํ ํฐ ๋ฐ์ดํฐ๋ฅผ ์ถ์ถํฉ๋๋ค. chunk
ํค๋ฅผ ์ฌ์ฉํ์ฌ ์คํธ๋ฆฌ๋ฐ๋ ํ ์คํธ ๋ฐ์ดํฐ๋ฅผ ์ป๊ณ , ์ด๋ฅผ ์ค์๊ฐ์ผ๋ก ํ์ธํ ์ ์์ต๋๋ค.
- ์ด๋ฒคํธ์์
1
2
3
4
5
6
7
8
config = {"configurable": {"thread_id": "222"}}
input_message = HumanMessage(content="ํผํ์์ ๋ํ๋ฏผ๊ตญ์ ์ด๋์ ๋ ์ค๋ ฅ์ธ๊ฐ์?")
async for event in graph.astream_events({"messages": [input_message]}, config, version="v2"):
# ํน์ ๋
ธ๋์์ ์ฑํ
๋ชจ๋ธ ํ ํฐ ๊ฐ์ ธ์ค๊ธฐ
# if event["event"] == "on_chat_model_stream" and event['metadata'].get('langgraph_node','') == node_to_stream:
if event["event"] == "on_chain_stream" and event['metadata'].get('langgraph_node','') == node_to_stream:
data = event["data"]
print(data["chunk"]['messages'].content)
Lesson 2: Breakpoints
๊ฐ์
Breakpoints๋ ์์ ํ๋ฆ์์ ํน์ ์ง์ ์์ ๊ทธ๋ํ์ ์คํ์ ๋ฉ์ถ๊ณ , ์ฌ์ฉ์๊ฐ ๊ฒํ ํ๊ฑฐ๋ ์์ ์ ์ค๋จํ ์ ์๊ฒ ํด์ฃผ๋ ๊ธฐ๋ฅ์ ๋๋ค.
- ์ด๋
Human-in-the-Loop
์ํฌํ๋ก์ฐ์์ ์ค์ํ ์์๋ก, ๋ฏผ๊ฐํ ์์ (์: ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ฐ๊ธฐ, ์ธ๋ถ ์์คํ ์ ์ฐ๊ธฐ)์ ๋ํด ์ฌ๋์ด ์น์ธ์ ํ ์ ์๋๋ก ํด์ค๋๋ค.
์ฃผ์ ๊ฐ๋
-
Human-in-the-loop (์ธ๊ฐ ๊ฐ์ )
๊ฐ๋ :- AI ์์คํ ์ ์๋ ๊ณผ์ ์ ์ธ๊ฐ์ด ๊ฐ์ ํ์ฌ ๊ฐ๋ , ์์ , ์น์ธ ๋ฑ์ ์ํํ๋ ๋ฐฉ์์ ๋๋ค.
- ์ด๋ AI์ ๊ฒฐ์ ์ด๋ ํ๋์ ๊ฒํ ํ๊ณ ํ์์ ์กฐ์ ํ ์ ์๊ฒ ํด์ค๋๋ค.
-
LangGraph์์์
Human-in-the-loop
๋๊ธฐ:a)
์น์ธ (Approval)
:- AI ์์ด์ ํธ์ ํน์ ํ๋์ด๋ ๊ฒฐ์ ์ ์ฌ์ฉ์๊ฐ ์น์ธํ ์ ์์ต๋๋ค.
- ์ค์ํ ๊ฒฐ์ ์ด๋ ์ํํ ์์ ์ ์ ์ธ๊ฐ์ ๊ฒํ ๋ฅผ ๋ฐ์ ์ ์์ต๋๋ค.
b)
๋๋ฒ๊น (Debugging)
:- ๊ทธ๋ํ ์คํ์ ๋๋๋ ค ๋ฌธ์ ๋ฅผ ์ฌํํ๊ฑฐ๋ ํผํ ์ ์์ต๋๋ค.
- ์ค๋ฅ๊ฐ ๋ฐ์ํ ์ง์ ์ ์ ํํ ํ์ ํ๊ณ ์์ ํ ์ ์์ต๋๋ค.
c)
ํธ์ง (Editing)
:- ๊ทธ๋ํ์ ์ํ๋ฅผ ์์ ํ ์ ์์ต๋๋ค.
- ์คํ ์ค ํ์ํ ๋ณ๊ฒฝ์ฌํญ์ ์ ์ฉํ ์ ์์ต๋๋ค.
-
๋ธ๋ ์ดํฌํฌ์ธํธ (Breakpoints)
:- ๊ทธ๋ํ์ ํน์ ๋จ๊ณ์์ ์คํ์ ์ค๋จํ๋ ๊ธฐ๋ฅ์ ๋๋ค.
- ์ฌ์ฉ์๊ฐ ๊ทธ๋ํ์ ์ํ๋ฅผ ๊ฒ์ฌํ๊ณ ํ์ํ ์กฐ์น๋ฅผ ์ทจํ ์ ์๊ฒ ํฉ๋๋ค.
- ์ฃผ๋ก ์น์ธ ํ๋ก์ธ์ค์ ์ฌ์ฉ๋๋ฉฐ, ํน์ ๋ ธ๋ ์คํ ์ ์ ์ค๋จ์ ์ ์ค์ ํ ์ ์์ต๋๋ค.
๋ธ๋ ์ดํฌํฌ์ธํธ (Breakpoints)
Interrupt Before
๋๋Interrupt After
๋ฅผ ํตํด ๋ธ๋์ดํฌํฌ์ธํธ๋ฅผ ์ค์ ํ ์ ์์ต๋๋ค.
- Interrupt Before: ํน์ ๋ ธ๋๊ฐ ์คํ๋๊ธฐ ์ ์ ๊ทธ๋ํ ์คํ์ ์ค๋จํฉ๋๋ค.
- Interrupt After: ํน์ ๋ ธ๋ ์คํ ํ์ ๊ทธ๋ํ๋ฅผ ๋ฉ์ถฅ๋๋ค.
- ์คํธ๋ฆฌ๋ฐ๊ณผ์ ์ฐ๊ณ:
- ์คํธ๋ฆฌ๋ฐ์ ํตํด ๊ทธ๋ํ ์คํ ์ค ์ถ๋ ฅ์ ์ค์๊ฐ์ผ๋ก ๋ณผ ์ ์์ต๋๋ค.
- ๋ธ๋ ์ดํฌํฌ์ธํธ์ ๊ฒฐํฉํ์ฌ ๋ ์ธ๋ฐํ ์ ์ด์ ๋ชจ๋ํฐ๋ง์ด ๊ฐ๋ฅํฉ๋๋ค.
์์
- ์์ ์ ๋ชจ๋1์์ ์ํํ๋ ์ค์ต ์์ ๋ฅผ ์์ฉํด๋ณด๊ฒ ์ต๋๋ค:
multiply
,add
,divide
ํจ์๊ฐ ์ ์๋์ด ์์ต๋๋ค. ์ด๋ค์ ๊ฐ๋จํ ์ฐ์ ์ฐ์ฐ์ ์ํํฉ๋๋ค.- ์ด ํจ์๋ค์ tools ๋ฆฌ์คํธ์ ํฌํจ๋์ด
bind_tools
๋ฅผ ํตํด LLM์ ๋ฐ์ธ๋ฉ๋ฉ๋๋ค.
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
from langchain_openai import ChatOpenAI
def multiply(a: int, b: int) -> int:
"""a์ b๋ฅผ ๊ณฑํฉ๋๋ค.
์ธ์:
a: ์ฒซ ๋ฒ์งธ ์ ์
b: ๋ ๋ฒ์งธ ์ ์
"""
return a * b
# ์ด๊ฒ์ ๋๊ตฌ๊ฐ ๋ ๊ฒ์
๋๋ค
def add(a: int, b: int) -> int:
"""a์ b๋ฅผ ๋ํฉ๋๋ค.
์ธ์:
a: ์ฒซ ๋ฒ์งธ ์ ์
b: ๋ ๋ฒ์งธ ์ ์
"""
return a + b
def divide(a: int, b: int) -> float:
"""a๋ฅผ b๋ก ๋๋๋๋ค.
์ธ์:
a: ์ฒซ ๋ฒ์งธ ์ ์
b: ๋ ๋ฒ์งธ ์ ์
"""
return a / b
tools = [add, multiply, divide]
llm = ChatOpenAI(model="gpt-4")
llm_with_tools = llm.bind_tools(tools)
- ๊ทธ๋ํ ๊ตฌ์กฐ ๋ฐ ์ปดํ์ผ ์ ์:
assistant ๋ ธ๋
: LLM์ ์ฌ์ฉํ์ฌ ์ฌ์ฉ์ ์ ๋ ฅ์ ์๋ตํฉ๋๋ค.tools ๋ ธ๋
: ์ค์ ์ฐ์ ์ฐ์ฐ์ ์ํํฉ๋๋ค.add_conditional_edges
๋ฉ์๋์tools_condition
์ ์ฌ์ฉํ๋ฉด, ์๋์ผ๋ก ๋๊ตฌ ํธ์ถ ์ฌ๋ถ์ ๋ฐ๋ฅธ ์กฐ๊ฑด๋ถ ๋ผ์ฐํ ์ด ์ค์ ๋ฉ๋๋ค.- ๋๊ตฌ ํธ์ถ์ด ์์ผ๋ฉด โtoolsโ ๋ ธ๋๋ก ๋ผ์ฐํ ํฉ๋๋ค.
- ๋๊ตฌ ํธ์ถ์ด ์์ผ๋ฉด โENDโ๋ก ๋ผ์ฐํ ํฉ๋๋ค.
interrupt_before=["tools"]
: tools ๋ ธ๋ ์คํ ์ ์ ๊ทธ๋ํ๊ฐ ์ค๋จ๋ฉ๋๋ค.MemorySaver
๋ฅผ ์ฌ์ฉํ์ฌ ์ํ๋ฅผ ์ ์ฅํ๊ณ ๊ด๋ฆฌํฉ๋๋ค.
- ๊ทธ๋ํ ์คํ ๋ฐ ์ํ ํ์ธ:
- โ2์ 3์ ๊ณฑํ์ธ์โ๋ผ๋ ์ด๊ธฐ ์ ๋ ฅ์ผ๋ก ๊ทธ๋ํ๋ฅผ ์คํํฉ๋๋ค.
graph.stream()
์ ์ฌ์ฉํ์ฌ ์คํ ๊ณผ์ ์ ์คํธ๋ฆฌ๋ฐํฉ๋๋ค.- ๊ฐ ์ด๋ฒคํธ๋ง๋ค ๋ฉ์์ง๋ฅผ ์ถ๋ ฅํฉ๋๋ค.
graph.get_state()
๋ฅผ ์ฌ์ฉํ์ฌ ํ์ฌ ๊ทธ๋ํ์ ์ํ๋ฅผ ๊ฐ์ ธ์ต๋๋ค.state.next
๋ฅผ ํตํด ๋ค์์ ์คํ๋ ๋ ธ๋๋ฅผ ํ์ธํ ์ ์์ต๋๋ค.
- ์ค๋จ์ (breakpoint): ์ค๋จ์ ์ ์ค์ ํ๋ฉด ๊ทธ๋ํ๊ฐ ํน์ ๋ ธ๋์์ ์คํ์ ๋ฉ์ถ๊ณ , ๊ทธ ์์ ์ ์ํ๋ฅผ ์ ์ฅํฉ๋๋ค. ์ฌ์ฉ์๋ ์ด ์์ ์์ ๊ทธ๋ํ์ ์ํ๋ฅผ ์กฐํํ๊ฑฐ๋, ์ ๋ ฅ์ ๋ฐ์ ๊ทธ๋ํ์ ์คํ์ ๊ณ์ํ ์ง ๊ฒฐ์ ํ ์ ์์ต๋๋ค.
- ์ฌ์คํ (None์ ์ฌ์ฉ): ์ค๋จ๋ ์ํ์์ ๊ทธ๋ํ๋ฅผ None์ผ๋ก ํธ์ถํ๋ฉด, ๊ฐ์ฅ ์ต๊ทผ ์ฒดํฌํฌ์ธํธ์์ ๊ทธ๋ํ๋ฅผ ์ด์ด์ ์คํํฉ๋๋ค. ์ด๋ ๊ทธ๋ํ๋ฅผ ์ค๋จ์ ์์ ๋ค์ ์์ํ๋ ๋งค์ฐ ๊ฐ๋จํ๊ณ ๊ฐ๋ ฅํ ๋ฐฉ๋ฒ์ ๋๋ค.
์์
- ์ฌ์ฉ์ ์น์ธ๊ณผ ์กฐ๊ฑด๋ถ ์คํ: ์๋ฅผ ๋ค์ด, ์ฌ์ฉ์๊ฐ ๋๊ตฌ ํธ์ถ์ ์น์ธํ๋ฉด, ๊ทธ๋ํ๋ ์ค๋จ๋ ์ํ์์ ๋๊ตฌ ๋ ธ๋๋ฅผ ์คํํ๊ณ , ๊ทธ ๊ฒฐ๊ณผ๋ฅผ ๋ํ ๋ชจ๋ธ์ ์ ๋ฌํ์ฌ ์ต์ข ์๋ต์ ์์ฑํฉ๋๋ค.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# ์
๋ ฅ
initial_input = {"messages": HumanMessage(content="2์ 3์ ๊ณฑํ์ธ์")}
# ์ค๋ ๋
thread = {"configurable": {"thread_id": "3"}}
# ์ฒซ ๋ฒ์งธ ์ค๋จ๊น์ง ๊ทธ๋ํ ์คํ
for event in graph.stream(initial_input, thread, stream_mode="values"):
event['messages'][-1].pretty_print()
# ์ฌ์ฉ์ ํผ๋๋ฐฑ ๋ฐ๊ธฐ
user_approval = input("๋๊ตฌ๋ฅผ ํธ์ถํ์๊ฒ ์ต๋๊น? (์/์๋์ค): ")
# ์น์ธ ํ์ธ
if user_approval.lower() == "์":
# ์น์ธ๋ ๊ฒฝ์ฐ, ๊ทธ๋ํ ์คํ ๊ณ์
for event in graph.stream(None, thread, stream_mode="values"):
event['messages'][-1].pretty_print()
else:
print("์ฌ์ฉ์์ ์ํด ์์
์ด ์ทจ์๋์์ต๋๋ค.")
Lesson 3: Editing State and Human Feedback
๊ฐ์
LangGraph๋ ์ค๋จ์ (Breakpoints)์ ํ์ฉํ์ฌ ๊ทธ๋ํ์ ์ํ๋ฅผ ๊ด๋ฆฌํ๊ณ , ํนํ ์ฌ๋์ด ๊ฐ์ ํ๋ ํ๋ฆ์์ ์ํ๋ฅผ ์์ ํ๊ฑฐ๋ ํ์ธํ ์ ์๋ ๊ณผ์ ์ ๋ณด์ฌ์ค๋๋ค.
์ฃผ์ ๊ฐ๋
์ค๋จ์ (Breakpoints) ์ค์
-
์ค๋จ์ ์ ๊ฐ๋ : ๊ทธ๋ํ์ ํน์ ๋ ธ๋์์ ์คํ์ ์ผ์ ์ค๋จํ๊ณ , ์ฌ์ฉ์์ ๊ฐ์ ์ ๊ธฐ๋ค๋ฆฌ๊ฑฐ๋ ์ํ๋ฅผ ์์ ํ๋ ๊ธฐํ๋ฅผ ์ ๊ณตํ๋ ๋ฉ์ปค๋์ฆ์ ๋๋ค. ์๋์ ๋ชฉ์ ์ผ๋ก ์ฌ์ฉ๋ฉ๋๋ค.
- ์น์ธ(Approval): ์ฌ์ฉ์๊ฐ ํน์ ๋๊ตฌ ํธ์ถ์ ์น์ธํ๊ฑฐ๋ ๊ฑฐ์ ํ ์ ์์ต๋๋ค. (Lesson2 ์๊ฐ)
- ๋๋ฒ๊น (Debugging): ๊ทธ๋ํ๊ฐ ์ค๋จ๋ ์ํ์์ ํ์ฌ ์ํ๋ฅผ ๊ฒํ ํ๊ณ , ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ ์ ์์ต๋๋ค. (Lesson2 ์๊ฐ)
- ํธ์ง(Editing): ๊ทธ๋ํ๊ฐ ์ค๋จ๋ ์์ ์์ ์ํ๋ฅผ ์์ ํ์ฌ, ์ํ๋ ๋๋ก ์งํ๋๋๋ก ์กฐ์ ํ ์ ์์ต๋๋ค.
์ฝ๋ ์์
- ์ ์ฝ๋๋ฅผ ์ํํ๋ฉด assistant๋ฅผ ์ํํ๊ธฐ ์ด์ ์ interupption์ด ๋ฐ์ํ๊ฒ ๋ฉ๋๋ค. (
interrupt_before=["assistant"]
)
์ํ ์ ๋ฐ์ดํธ (graph.update_state)
1
2
3
4
graph.update_state(
thread,
{"messages": [HumanMessage(content="์๋์, ์ค์ ๋ก 3๊ณผ 3์ ๊ณฑํ์ธ์!")]}
)
-
์ํ ์ ๋ฐ์ดํธ: ์ด ์ฝ๋๋ ํ์ฌ ๊ทธ๋ํ์ ์ํ๋ฅผ ์ ๋ฐ์ดํธํ๋ ์์์ ๋๋ค.
thread
๋ ํด๋น ๊ทธ๋ํ ์คํ์ ๊ณ ์ ID๋ก, ์ด๋ค ์ํ๋ฅผ ์ ๋ฐ์ดํธํ ๊ฒ์ธ์ง๋ฅผ ์๋ณํ๋ ๋ฐ ์ฌ์ฉ๋ฉ๋๋ค.
-
MessagesState: ๋ฉ์์ง ์ํ๋ฅผ ๊ด๋ฆฌํ๋ LangGraph์ ๋ด์ฅ๋ ํด๋์ค์ ๋๋ค. ์ด ํด๋์ค๋ ๋ฉ์์ง๋ค์ ์ํ๋ฅผ ์ถ์ ํ๊ณ ์ ๋ฐ์ดํธํ ๋ ์ฌ์ฉ๋ฉ๋๋ค.
-
Graph.update_state()
ํธ์ถํ ๋, MessagesState๋ ๋ฉ์์ง๋ฅผ ์ถ๊ฐํ๊ฑฐ๋ ์ ๋ฐ์ดํธํ๋ ์์ ์ ๋ด๋ถ์ ์ผ๋ก ์ฒ๋ฆฌํ๋ฉฐ, ์ด ๊ณผ์ ์์๋ฆฌ๋์(reducer) ์ญํ
์ ์ํํฉ๋๋ค.- ๋ฉ์์ง ์ ์ฅ:
messages ํค
๋ฅผ ํตํด ๋ฉ์์ง ์ํ๋ฅผ ์ ์ง ๋ฐ ๊ด๋ฆฌํฉ๋๋ค. - ์ํ ์
๋ฐ์ดํธ:
update_state()
๋ฉ์๋๋ฅผ ์ฌ์ฉํ์ฌ ์๋ก์ด ๋ฉ์์ง๋ฅผ ์ถ๊ฐํ๊ฑฐ๋ ๊ธฐ์กด ๋ฉ์์ง๋ฅผ ์์ ํ ๋,MessagesState
ํด๋์ค๋ ์ด๋ฅผ ์๋์ผ๋ก ์ฒ๋ฆฌํฉ๋๋ค. - ์ถ๊ฐ/๋ฎ์ด์ฐ๊ธฐ ๋ก์ง: ๋ฉ์์ง๊ฐ ์ถ๊ฐ๋ ๋
id๊ฐ ์์ผ๋ฉด
์๋ก์ด ๋ฉ์์ง๋ฅผ ๋ฆฌ์คํธ์ ์ถ๊ฐํ๊ณ ,id๊ฐ ์์ผ๋ฉด
ํด๋น ๋ฉ์์ง๋ฅผ ๋ฎ์ด์ฐ๋ ๋ฐฉ์์ผ๋ก ๋์ํฉ๋๋ค.
- ๋ฉ์์ง ์ ์ฅ:
-
๐ก(์ฐธ๊ณ )
graph.update_state()
๋ฉ์๋๋ฅผ ํธ์ถํ๋ฉด ๊ธฐ๋ณธ์ ์ผ๋ก ๋ค์๊ณผ ๊ฐ์ ๋์์ด ์ํ๋ฉ๋๋ค:
- โmessagesโ ํค์ ๋ํ ๊ธฐ๋ณธ ๋ฆฌ๋์ ํจ์๊ฐ ์คํ๋ฉ๋๋ค. ์ด ๋ฆฌ๋์๋ ๋ค์๊ณผ ๊ฐ์ด ๋์ํฉ๋๋ค:
- ์ ๋ฉ์์ง์ ID๊ฐ ๊ธฐ์กด ๋ฉ์์ง์ ์ผ์นํ๋ฉด ํด๋น ๋ฉ์์ง๋ฅผ ๋์ฒดํฉ๋๋ค.
- ID๊ฐ ์ผ์นํ์ง ์์ผ๋ฉด ์ ๋ฉ์์ง๋ฅผ ๊ธฐ์กด ๋ฉ์์ง ๋ชฉ๋ก์ ๋์ ์ถ๊ฐํฉ๋๋ค.
์ํ ์ ๋ฐ์ดํธ ํ ๊ทธ๋ํ ์ฌ์คํ
- graph.stream(None, thread, stream_mode=โvaluesโ): ๊ทธ๋ํ๋ฅผ ์ฌ์คํํ๋ฉด์, ์ค๋จ๋์๋ ์ง์ ์์ ์ํ๋ฅผ ํ์ธํ๊ณ , ์ํ๊ฐ ์ ๋ฐ์ดํธ๋ ํ ๋จ์ ๋ ธ๋๋ฅผ ์คํํฉ๋๋ค.
-
์ฌ์คํ ๊ณผ์ :
- ๊ทธ๋ํ ์ํ๋ฅผ ์
๋ฐ์ดํธํ๊ณ ,
None
์ ์ ๋ฌํ์ฌ ์ค๋จ๋ ์ง์ ์์ ๋ค์ ์คํ์ ๊ณ์ํฉ๋๋ค. - ๊ทธ๋ํ๊ฐ ์งํ๋๋ฉด์ ์ต์ข ๋๊ตฌ ํธ์ถ ๋ฐ AI ๋ชจ๋ธ ์๋ต์ ์ํํ๊ฒ ๋ฉ๋๋ค.
- ๊ทธ๋ํ ์ํ๋ฅผ ์
๋ฐ์ดํธํ๊ณ ,
Dummy Node์ ํผ๋๋ฐฑ ์ ์ฉ
- ์ด ๊ธฐ๋ฒ์ ์ค์๊ฐ ์ฌ์ฉ์ ๊ฐ์
์ ํตํด,
LLM๊ณผ์ ์ํธ์์ฉ์ ๋ณด๋ค ๋์
์ผ๋ก ๋ง๋ค ์ ์๋ ๋ฐฉ๋ฒ์ ๋๋ค. - ์ฌ์ฉ์๋ ํน์ ๋ ธ๋์์ ์คํ ์ค๋จ ํ ์ํ๋ฅผ ์์ ํ ์ ์์ผ๋ฉฐ, ๊ทธ ์ดํ๋ก๋ ๊ทธ๋ํ๊ฐ ์์ฐ์ค๋ฝ๊ฒ ๊ณ์ ์คํ๋ฉ๋๋ค.
-
์ด๋ฅผ ํตํด ๋ ๋ณต์กํ ์์ ํ๋ฆ์์ ์ธ๊ฐ ํผ๋๋ฐฑ์ ๋ฐ์ํ ์ ์์ต๋๋ค.
-
Dummy Node๋?
: Dummy Node๋ ์ค์ ๋ก๋ ์๋ฌด ์์ ๋ ํ์ง ์์ง๋ง, ํน์ ์์ ์์ ์ฌ์ฉ์์ ํผ๋๋ฐฑ์ ๋ฐ์ ์ ์๋๋ก ์ค๋จ์ ์ ๋ง๋ค์ด ์ฃผ๋ ๊ฐ์์ ๋ ธ๋์ ๋๋ค.- ์ด ๋ ธ๋๋ ์ค์ง์ ์ธ ์ฐ์ฐ์ด๋ ์ํ ๋ณ๊ฒฝ์ ํ์ง ์๊ณ , ๊ทธ๋ํ๋ฅผ ์ผ์ ์ ์งํ์ฌ ์ฌ์ฉ์๊ฐ ๊ฐ์ ํ ์ ์๋ ์ง์ ์ ๋ง๋ค์ด ์ฃผ๋ ์ญํ ์ ํฉ๋๋ค.
-
ํผ๋๋ฐฑ ์ ์ฉ
: Dummy Node๋ ์ฌ์ฉ์์ ํผ๋๋ฐฑ์ ๋ฐ์ ์ ์๋ ๊ตฌ๊ฐ์ ์ ๊ณตํ๋ฉฐ, ๊ทธ๋ํ ์คํ์ ์ ์ ์ค๋จํฉ๋๋ค. ์ดํ ์ฌ์ฉ์๊ฐ ์ํ ์ ๋ฐ์ดํธ๋ฅผ ์์ฒญํ๋ฉด, ๊ทธ ํผ๋๋ฐฑ์ ๋ฐ์ ๊ทธ๋ํ ์ํ์ ๋ฐ์ํฉ๋๋ค.- ์ฌ์ฉ์์ ์ ๋ ฅ(ํผ๋๋ฐฑ)์ ๊ทธ๋ํ ์ํ์ ์ง์ ์ฃผ์ ๋์ด, ๊ทธ ํ์ ์ด์ด์ง๋ ๋ ธ๋์ ์คํ์ ์ํฅ์ ๋ฏธ์นฉ๋๋ค. ์ด๋ฅผ ํตํด ์ฌ์ฉ์๊ฐ ๊ทธ๋ํ ์คํ ๊ณผ์ ์ ๋์ ์ผ๋ก ๊ฐ์ ํ ์ ์์ต๋๋ค.
-
-
๊ทธ๋ํ ์ ์ : ์ ๊ทธ๋ํ์๋ 3๊ฐ์ ์ฃผ์ ๋ ธ๋๊ฐ ์์ต๋๋ค:
-
human_feedback
: ์ฌ์ฉ์๊ฐ ํผ๋๋ฐฑ์ ์ ๊ณตํ ์ ์๋ ๊ฐ์์ ๋ ธ๋(Dummy Node)์ ๋๋ค.- ์ด ๋ ธ๋์์๋ ์๋ฌด ์์ ๋ ํ์ง ์์ง๋ง, ์ฌ์ฉ์ ํผ๋๋ฐฑ์ ๋ฐ์ ์ง์ ์ ๋ง๋ญ๋๋ค.
assistant
: ์ฌ์ฉ์ ์ ๋ ฅ๊ณผ LLM์ ์ด์ฉํด ๋๊ตฌ ํธ์ถ ๋ฑ์ ์ฐ์ฐ์ ์ํํ๋ ๋ ธ๋์ ๋๋ค.-
tools
: ๋๊ตฌ ํธ์ถ์ ๋ด๋นํ๋ ๋ ธ๋๋ก, ์ค์ ์ฐ์ฐ(์: ๋ง์ , ๊ณฑ์ )์ ์ํํฉ๋๋ค.1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
# ์์คํ ๋ฉ์์ง sys_msg = SystemMessage(content="๋น์ ์ ์ผ๋ จ์ ์ ๋ ฅ์ ๋ํด ์ฐ์ ์ฐ์ฐ์ ์ํํ๋ ๋์์ด ๋๋ ์ด์์คํดํธ์ ๋๋ค.") # ์ค๋จ๋์ด์ผ ํ๋ ๋ฌด์๋ ๋ ธ๋ def human_feedback(state: MessagesState): pass # ์ด์์คํดํธ ๋ ธ๋ def assistant(state: MessagesState): return {"messages": [llm_with_tools.invoke([sys_msg] + state["messages"])]} # ๊ทธ๋ํ ๋น๋ builder = StateGraph(MessagesState) builder.add_node("assistant", assistant) builder.add_node("tools", ToolNode(tools)) builder.add_node("human_feedback", human_feedback)
-
-
์ค๋จ์ ์ ์
- ์ด ์ฝ๋์์๋ ์ค๋จ์ (interrupt)์
human_feedback
๋ ธ๋์ ์ง์ ํ์ต๋๋ค. ์ฆ, ์ด ๋ ธ๋์ ๋๋ฌํ ๋ ๊ทธ๋ํ ์คํ์ด ๋ฉ์ถ๊ณ ์ฌ์ฉ์์ ํผ๋๋ฐฑ์ ๊ธฐ๋ค๋ฆฌ๊ฒ ๋ฉ๋๋ค. interrupt_before=["human_feedback"]
๋ถ๋ถ์ด ์ด ์ญํ ์ ๋ด๋นํฉ๋๋ค.1 2
# ๊ทธ๋ํ๋ฅผ human_feedback ๋ ธ๋ ์ ์ ์ค๋จ graph = builder.compile(interrupt_before=["human_feedback"], checkpointer=memory)
- ์ด ์ฝ๋์์๋ ์ค๋จ์ (interrupt)์
-
์คํ ์ค ํผ๋๋ฐฑ ๋ฐ๊ธฐ
- ๊ทธ๋ํ๋ ์ฒ์์ ์ฌ์ฉ์ ์
๋ ฅ์ ๋ฐ์
human_feedback
๋ ธ๋๊น์ง ์คํ๋ ํ ๋ฉ์ถฅ๋๋ค. - ๊ทธ ํ ์ฌ์ฉ์์ ํผ๋๋ฐฑ์ ๊ธฐ๋ค๋ฆฌ๋ฉฐ,
input()
์ ํตํด ํผ๋๋ฐฑ์ ๋ฐ์ต๋๋ค. - ์ฌ์ฉ์๊ฐ ์
๋ ฅํ ํผ๋๋ฐฑ์
graph.update_state()
๋ฉ์๋๋ฅผ ํตํดhuman_feedback
๋ ธ๋์ ์ํ์ ๋ฐ์๋ฉ๋๋ค. ```์ฌ์ฉ์๋ก๋ถํฐ ํผ๋๋ฐฑ ๋ฐ๊ธฐ
user_input = input(โ์ํ๋ฅผ ์ด๋ป๊ฒ ์ ๋ฐ์ดํธํ๊ณ ์ถ์์ง ๋ง์ํด ์ฃผ์ธ์: โ)
human_feedback ๋ ธ๋์ธ ๊ฒ์ฒ๋ผ ์ํ๋ฅผ ์ ๋ฐ์ดํธ
graph.update_state(thread, {โmessagesโ: user_input}, as_node=โhuman_feedbackโ) ```
- ๊ทธ๋ํ๋ ์ฒ์์ ์ฌ์ฉ์ ์
๋ ฅ์ ๋ฐ์
-
์ํ ์ ๋ฐ์ดํธ ๋ฐ ์คํ ์ฌ๊ฐ
- ํผ๋๋ฐฑ์ ๋ฐ์ ํ,
graph.update_state()
๋ฅผ ํตํด ํด๋น ํผ๋๋ฐฑ์ ์ํ์ ๋ฐ์ํ ๋ค ๊ทธ๋ํ ์คํ์ ๊ณ์ํฉ๋๋ค. - ์ฌ์ฉ์์ ํผ๋๋ฐฑ์ ๋ฐ๋ผ ์ดํ์ ๋๊ตฌ ํธ์ถ์ด๋ ์ด์์คํดํธ ์๋ต์ด ๋ฌ๋ผ์ง ์ ์์ต๋๋ค.
1 2 3
# ๊ทธ๋ํ ์คํ ๊ณ์ for event in graph.stream(None, thread, stream_mode="values"): event["messages"][-1].pretty_print()
- ํผ๋๋ฐฑ์ ๋ฐ์ ํ,
Lesson 4: Dynamic Breakpoints
๊ฐ์
- ๋์ ์ค๋จ์ (Dynamic Breakpoints)์ ๊ทธ๋ํ ์คํ ์ค
ํน์ ์กฐ๊ฑด์ ๋ฐ๋ผ
์๋์ผ๋ก ๋ฐ์ํ๋ ์ค๋จ์
์ ๋๋ค.- ์ฆ, ์คํ๋๋ ์ค๊ฐ์
ํน์ ์ํ
๋์กฐ๊ฑด์ ๊ฐ์ง
ํ์ฌ ์๋์ผ๋ก ๋ฉ์ถ๊ณ , ์ดํ ์ํ๋ฅผ ์์ ํ๊ฑฐ๋ ์ถ๊ฐ์ ์ธ ์ ๋ ฅ์ ๋ฐ๋ ํํ๋ก ์ด์ด์ง ์ ์์ต๋๋ค. - ๋์ ์ค๋จ์ ์ ํนํ ์
๋ ฅ์ด
์์์น ๋ชปํ ๊ฐ์ ๊ฐ์ง ๋
,์ํ ์ ๋ฐ์ดํธ๊ฐ ํ์ํ ๋
, ๋๋ํน์ ์กฐ๊ฑด์ด ์ถฉ์กฑ๋ ๋
๋งค์ฐ ์ ์ฉํฉ๋๋ค.
- ์ฆ, ์คํ๋๋ ์ค๊ฐ์
์ฃผ์ ๊ฐ๋
๋์ ์ค๋จ์ (Dynamic Breakpoints)์ ๋ชฉํ
- ์ผ๋ฐ์ ์ธ ์ค๋จ์ ์ ๊ทธ๋ํ๊ฐ ํน์ ๋
ธ๋์ ๋๋ฌํ ๋ ์๋์ผ๋ก ์ค์ ๋์ง๋ง, ๋์ ์ค๋จ์ ์
๊ทธ๋ํ๊ฐ ์ค์ค๋ก ํน์ ์กฐ๊ฑด์ ๋ฐ๋ผ ์ค๋จ
ํ๋๋ก ํ ์ ์์ต๋๋ค. - ์ด๋ฅผ ํตํด ๊ทธ๋ํ๋ ๋ด๋ถ์์ ์ค์ค๋ก ์ํ๋ฅผ ์ ๊ฒํ๊ณ ํ์์ ์ค๋จํ์ฌ, ์ํ
์ ๋ฐ์ดํธ
๋๋์์
์ด ๊ฐ๋ฅํ๊ฒ ๋ง๋ญ๋๋ค.
๋์ ์ค๋จ์ (Dynamic Breakpoints)์ ์ด์
-
์กฐ๊ฑด๋ถ๋ก ์ค๋จ: ๊ฐ๋ฐ์๊ฐ ์ ์ํ ํน์ ๋ก์ง์ ๋ฐ๋ผ ์กฐ๊ฑด๋ถ๋ก ์ค๋จ์ด ๊ฐ๋ฅํฉ๋๋ค.
- ์๋ฅผ ๋ค์ด, ์ ๋ ฅ๊ฐ์ด ๋๋ฌด ๊ธธ๊ฑฐ๋ ํน์ ๊ธฐ์ค์ ์ด๊ณผํ ๊ฒฝ์ฐ ์๋์ผ๋ก ์ค๋จ๋ฉ๋๋ค.
-
์ค๋จ ์ด์ ์ ๋ฌ: ์ค๋จ์ ์ด ๋ฐ์ํ ์ด์ ๋ฅผ ์ฌ์ฉ์์๊ฒ ๋ช ํํ ์ ๋ฌํ ์ ์์ต๋๋ค.
NodeInterrupt
์ ์ ๋ฌํ ์ ๋ณด๋ฅผ ํฌํจ์์ผ ์ ์ค๋จ์ด ๋ฐ์ํ๋์ง๋ฅผ ์ค๋ช ํ ์ ์์ต๋๋ค.
์ฝ๋ ์ค์ต
1. ๊ทธ๋ํ ์ค์
- ๋จผ์ ,
StateGraph
๋ฅผ ์ฌ์ฉํ์ฌ ๊ฐ๋จํ 3๋จ๊ณ ๊ทธ๋ํ๋ฅผ ์ค์ ํฉ๋๋ค.- ์ด ๊ทธ๋ํ๋
step_1
,step_2
,step_3
์ผ๋ก ๊ตฌ์ฑ๋๋ฉฐ, step_2์์ ์ ๋ ฅ๊ฐ์ ๊ธธ์ด๊ฐ 5์ ์ด์์ผ ๊ฒฝ์ฐ ์๋์ผ๋ก ์ค๋จ์ํค๋NodeInterrupt
๋ฅผ ์ฌ์ฉํฉ๋๋ค.
- ์ด ๊ทธ๋ํ๋
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
35
36
37
38
39
40
41
from typing_extensions import TypedDict
from langgraph.checkpoint.memory import MemorySaver
from langgraph.errors import NodeInterrupt
from langgraph.graph import START, END, StateGraph
# ์ํ ์ ์
class State(TypedDict):
input: str
# 1๋จ๊ณ: ์ํ๋ฅผ ๊ทธ๋๋ก ์ถ๋ ฅ
def step_1(state: State) -> State:
print("---1๋จ๊ณ---")
return state
# 2๋จ๊ณ: ์
๋ ฅ์ ๊ธธ์ด๊ฐ 5์๋ฅผ ๋์ผ๋ฉด ์ค๋จ ๋ฐ์
def step_2(state: State) -> State:
if len(state['input']) > 5:
raise NodeInterrupt(f"5์๋ณด๋ค ๊ธด ์
๋ ฅ์ ๋ฐ์์ต๋๋ค: {state['input']}")
print("---2๋จ๊ณ---")
return state
# 3๋จ๊ณ: ์ํ๋ฅผ ๊ทธ๋๋ก ์ถ๋ ฅ
def step_3(state: State) -> State:
print("---3๋จ๊ณ---")
return state
# ๊ทธ๋ํ ์ค์
builder = StateGraph(State)
builder.add_node("step_1", step_1)
builder.add_node("step_2", step_2)
builder.add_node("step_3", step_3)
builder.add_edge(START, "step_1")
builder.add_edge("step_1", "step_2")
builder.add_edge("step_2", "step_3")
builder.add_edge("step_3", END)
# ๋ฉ๋ชจ๋ฆฌ ์ค์
memory = MemorySaver()
# ๊ทธ๋ํ ์ปดํ์ผ
graph = builder.compile(checkpointer=memory)
2. ๊ทธ๋ํ ์คํ ๋ฐ ์ค๋จ์ ๋ฐ์
์ ๋ ฅ๊ฐ์ด 5์๋ณด๋ค ๊ธธ๋ฉด ๊ทธ๋ํ๋ step_2์์ ์ค๋จ๋ฉ๋๋ค.
1
2
3
4
5
6
initial_input = {"input": "hello world"}
thread_config = {"configurable": {"thread_id": "1"}}
# Run the graph until the first interruption
for event in graph.stream(initial_input, thread_config, stream_mode="values"):
print(event)
์คํ ๊ฒฐ๊ณผ:
1
2
3
{'input': 'hello world'}
---1๋จ๊ณ---
{'input': 'hello world'}
์ด ์์ ์์ ๊ทธ๋ํ๋ step_2์์ ๋ฉ์ถ๊ฒ ๋๊ณ , ์ํ๋ฅผ ํ์ธํ ์ ์์ต๋๋ค.
ํ์ฌ ์ํ๋ฅผ ์กฐ๊ธ ๋ ๋ช ํํ๊ฒ ์ค๋ช ํ์๋ฉด, NodeInterrupt๊ฐ ๋ฐ์ํ๊ธฐ ๋๋ฌธ์ ๋ ธ๋์ ์ฃ์ง ๊ฐ์ ์ ํํ ์์น๋ฅผ ๊ตฌ๋ถํ๋ ๊ฒ์ด ์ค์ํฉ๋๋ค.
- ๋
ธ๋ ์์น ๊ด์ ์์: ์คํ ํ๋ฆ์
step_1
์ ์๋ฃํ๊ณstep_2
๋ฅผ ์คํํ๋ ๋์ค์ ์ค๋จ๋์์ต๋๋ค. ์ด๋ ์์งstep_2
์ ์คํ์ด ์๋ฃ๋์ง ์์์์ ์๋ฏธํ๋ฏ๋ก, ํ์ฌ๋step_2
์ ๋๋ฌํ ์ํ์ ๋๋ค. - ์ฃ์ง ์์น ๊ด์ ์์:
step_1
์์step_2
๋ก์ ์ ์ด๊ฐ ์ด๋ฃจ์ด์ก์ผ๋,step_2
๊ฐ ์๋ฃ๋์ง ์์์ผ๋ฏ๋ก 1-2 ์ฃ์งโ์์step_2
๋ก ๋์ด๊ฐ๋ ์ค์ ๋ฉ์ถ ์ํ๋ผ๊ณ ๋ ๋ณผ ์ ์์ต๋๋ค.
3. ์ํ ํ์ธ ๋ฐ ์์
๊ทธ๋ํ๊ฐ ์ค๋จ๋ ์ํ์์ ๋ค์ ์คํํ ๋
ธ๋๊ฐ step_2
์์ ํ์ธํฉ๋๋ค.
1
2
state = graph.get_state(thread_config)
print(state.next) # ('step_2',)
๊ทธ๋ฆฌ๊ณ ์ค๋จ์ ์ด ๋ฐ์ํ ์ด์ ๋ ํ์ธํ ์ ์์ต๋๋ค.
1
print(state.tasks)
1
(PregelTask(id='ec5598b4-c9cc-ce9e-8387-5f27e35742a5', name='step_2', path=('__pregel_pull', 'step_2'), error=None, interrupts=(Interrupt(value='5์๋ณด๋ค ๊ธด ์
๋ ฅ์ ๋ฐ์์ต๋๋ค: hello world', when='during'),), state=None),)
4. ์ํ ์ ๋ฐ์ดํธ ๋ฐ ๊ทธ๋ํ ์ฌ์คํ
์ํ๋ฅผ ๋ณ๊ฒฝํ์ง ์์ผ๋ฉด ๊ฐ์ ๋ ธ๋์์ ๊ณ์ ๋ฉ์ถฐ์๊ฒ ๋ฉ๋๋ค.
์ํ๋ฅผ โHelloโ์ผ๋ก ๋ณ๊ฒฝํ์ฌ ๊ทธ๋ํ๋ฅผ ์ฌ์คํํฉ๋๋ค. ๋ณ๊ฒฝ ํ์๋ ๊ทธ๋ํ์ ๋๊น์ง ์ ์์ ์ผ๋ก ์คํ๋๋ ๊ฒ์ ํ์ธํ ์ ์์ต๋๋ค.
1
2
3
4
5
6
7
8
9
# ์ํ ์
๋ฐ์ดํธ: ์
๋ ฅ์ "Hello"์ผ๋ก ๋ณ๊ฒฝ
graph.update_state(
thread_config,
{"input": "Hello"},
)
# ๊ทธ๋ํ ์ฌ์คํ
for event in graph.stream(None, thread_config, stream_mode="values"):
print(event)
์ ๋ฐ์ดํธ ํ ์คํ ๊ฒฐ๊ณผ:
1
2
3
4
5
{'input': 'Hello'}
---2๋จ๊ณ---
{'input': 'Hello'}
---3๋จ๊ณ---
{'input': 'Hello'}
โจ ๋์ ์ค๋จ์ ์ ํต์ฌ ๊ฐ๋
์๋ ์ค๋จ์ ์ค์ : ๊ทธ๋ํ๋ ํน์ ์กฐ๊ฑด(์: ์ ๋ ฅ ๊ธธ์ด ์ด๊ณผ)์ ๋ง์กฑํ ๋ ์๋์ผ๋ก ๋ฉ์ถ๊ณ , ์ค๋จ์ ์ ๋ฐ์์ํต๋๋ค.
์ํ ํ์ธ ๋ฐ ์ ๋ฐ์ดํธ: ๊ทธ๋ํ๊ฐ ์ค๋จ๋๋ฉด ํ์ฌ ์ํ๋ฅผ ํ์ธํ๊ณ , ํ์์ ์ํ๋ฅผ ์ ๋ฐ์ดํธํ ์ ์์ต๋๋ค.
๊ทธ๋ํ ์ฌ์คํ: ์ํ๊ฐ ์ ๋ฐ์ดํธ๋ ํ, ๊ทธ๋ํ๋ ์ค๋จ๋ ์์ ๋ถํฐ ๋ค์ ์คํ๋ฉ๋๋ค.
์ ์ฐํ ์ํฌํ๋ก์ฐ ๊ตฌ์ฑ: ๋์ ์ค๋จ์ ์ ์ฌ์ฉํ๋ฉด, ์ฌ์ฉ์์ ์น์ธ, ๋๋ฒ๊น , ์ํ ์์ ๋ฑ์ ์ ์ฐํ๊ฒ ์ฒ๋ฆฌํ ์ ์์ต๋๋ค.
Lesson 5: Time Travel
๊ฐ์
Time Travel
์ ๊ทธ๋ํ์ ์ด์ ์ํ๋ก ๋์๊ฐ๊ฑฐ๋, ๊ทธ ์ํ๋ฅผ ์ฌ์ํ์ฌ ๊ณผ๊ฑฐ์ ์คํ ํ๋ฆ์ ์ดํด๋ณด๊ณ ๋ฌธ์ ๋ฅผ ๋ถ์ํ๋ ๋ฐ ์ ์ฉํ ๊ธฐ๋ฅ์
๋๋ค.
- ์ด ๊ธฐ๋ฅ์ ๊ทธ๋ํ๊ฐ ์คํ๋ ๋ชจ๋ ์ํ๋ฅผ ๊ธฐ๋กํ๊ณ , ๊ทธ ์ค ์ด๋ ์ง์ ์์๋ ๋ค์ ์์ํ ์ ์๋๋ก ํด์ค๋๋ค.
- Time Travel์
๋๋ฒ๊น
,์ฑ๋ฅ ์ต์ ํ
,์ฌ๋ฌ ๊ฒฝ๋ก ํ์
๋ฑ์ ๋์์ ์ค๋๋ค.- ์ฌ์(Replay)๊ณผ ๋ถ๊ธฐ(Fork)์ ๋ ๊ฐ์ง ํต์ฌ ๊ฐ๋ ์ ํตํด ๊ทธ๋ํ์ ์คํ ํ๋ฆ์ ์์ ํ๊ฑฐ๋ ๋ค์ํ ์๋๋ฆฌ์ค๋ฅผ ์คํํ ์ ์์ต๋๋ค.
์ฃผ์ ๊ฐ๋
-
Replay(์ฌ์):
- ํน์ ์ฒดํฌํฌ์ธํธ์์ ๊ทธ๋ํ๋ฅผ ๋ค์ ์ฌ์ํ์ฌ ๊ณผ๊ฑฐ์ ์คํ์ ๋ณต์ํฉ๋๋ค.
- ๊ทธ๋ํ์ ๋ชจ๋ ์ํ๊ฐ ์ ์ฅ๋๊ธฐ ๋๋ฌธ์ ์ด์ ์ ์๋ฃ๋ ์์ ์ ๋ฐ๋ณต ์คํํ ํ์ ์์ด ๊ทธ ์ํ๋ฅผ ๊ทธ๋๋ก ์ฌ์ํ ์ ์์ต๋๋ค.
- ์ด๋ ๋ฌธ์ ๋ฅผ ์ฌํํ๊ฑฐ๋ ๋ถ์ํ ๋ ๋งค์ฐ ์ ์ฉํฉ๋๋ค.
-
Fork(๋ถ๊ธฐ):
- ํน์ ์ฒดํฌํฌ์ธํธ์์ ์ํ๋ฅผ ์์ ํ์ฌ ์๋ก์ด ์คํ ํ๋ฆ์ ํ์ํ ์ ์์ต๋๋ค.
- ์ด ๊ธฐ๋ฅ์ ๊ทธ๋ํ์ ํน์ ์ง์ ์์ ๋ค๋ฅธ ๊ฒฝ๋ก๋ก ์งํํ๋๋ก ์ํ๋ฅผ ์ ๋ฐ์ดํธํ ํ, ๊ทธ ๊ฒฝ๋ก์ ๋ฐ๋ฅธ ์คํ ๊ฒฐ๊ณผ๋ฅผ ํ์ธํ๋ ๋ฐฉ์์ผ๋ก ํ์ฉ๋ฉ๋๋ค.
- Fork๋ ์๋ก์ด ์๋๋ฆฌ์ค๋ฅผ ํ ์คํธํ๊ฑฐ๋, ์์์น ๋ชปํ ์ ๋ ฅ์ ๋ฐ๋ฅธ ํ๋ฆ์ ์คํํ ๋ ์ ์ฉํฉ๋๋ค.
Time Travel์ ์ฃผ์ ๋จ๊ณ
0. Agent Define
- ์ด์ ๊ณผ ๋ง์ฐฌ๊ฐ์ง๋ก agent๋ฅผ ์ ์ํด๋ณด๊ฒ ์ต๋๋ค. ๊ณ์ํด์ ์ฌ์ฉํ๋ agent์ด๋ฏ๋ก ์์ธํ ์ค๋ช ์ ์๋ตํ๊ฒ ์ต๋๋ค.
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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
from langchain_openai import ChatOpenAI
def multiply(a: int, b: int) -> int:
"""a์ b๋ฅผ ๊ณฑํฉ๋๋ค.
์ธ์:
a: ์ฒซ ๋ฒ์งธ ์ ์
b: ๋ ๋ฒ์งธ ์ ์
"""
return a * b
# ์ด๊ฒ์ ๋๊ตฌ๊ฐ ๋ ๊ฒ์
๋๋ค
def add(a: int, b: int) -> int:
"""a์ b๋ฅผ ๋ํฉ๋๋ค.
์ธ์:
a: ์ฒซ ๋ฒ์งธ ์ ์
b: ๋ ๋ฒ์งธ ์ ์
"""
return a + b
def divide(a: int, b: int) -> float:
"""a๋ฅผ b๋ก ๋๋๋๋ค.
์ธ์:
a: ์ฒซ ๋ฒ์งธ ์ ์
b: ๋ ๋ฒ์งธ ์ ์
"""
return a / b
tools = [add, multiply, divide]
llm = ChatOpenAI(model="gpt-4")
llm_with_tools = llm.bind_tools(tools)
from IPython.display import Image, display
from langgraph.checkpoint.memory import MemorySaver
from langgraph.graph import MessagesState
from langgraph.graph import START, END, StateGraph
from langgraph.prebuilt import tools_condition, ToolNode
from langchain_core.messages import AIMessage, HumanMessage, SystemMessage
# ์์คํ
๋ฉ์์ง
sys_msg = SystemMessage(content="๋น์ ์ ์ผ๋ จ์ ์
๋ ฅ์ ๋ํด ์ฐ์ ์ฐ์ฐ์ ์ํํ๋ ๋์์ด ๋๋ ์ด์์คํดํธ์
๋๋ค.")
# ๋
ธ๋
def assistant(state: MessagesState):
return {"messages": [llm_with_tools.invoke([sys_msg] + state["messages"])]}
# ๊ทธ๋ํ
builder = StateGraph(MessagesState)
# ๋
ธ๋ ์ ์: ์ด๋ค์ด ์์
์ ์ํํฉ๋๋ค
builder.add_node("assistant", assistant)
builder.add_node("tools", ToolNode(tools))
# ์ฃ์ง ์ ์: ์ด๋ค์ด ์ ์ด ํ๋ฆ์ ๊ฒฐ์ ํฉ๋๋ค
builder.add_edge(START, "assistant")
builder.add_conditional_edges(
"assistant",
# ์ด์์คํดํธ์ ์ต์ ๋ฉ์์ง(๊ฒฐ๊ณผ)๊ฐ ๋๊ตฌ ํธ์ถ์ด๋ฉด -> tools_condition์ด tools๋ก ๋ผ์ฐํ
ํฉ๋๋ค
# ์ด์์คํดํธ์ ์ต์ ๋ฉ์์ง(๊ฒฐ๊ณผ)๊ฐ ๋๊ตฌ ํธ์ถ์ด ์๋๋ฉด -> tools_condition์ด END๋ก ๋ผ์ฐํ
ํฉ๋๋ค
tools_condition,
)
builder.add_edge("tools", "assistant")
memory = MemorySaver()
graph = builder.compile(checkpointer=MemorySaver())
# ํ์
display(Image(graph.get_graph(xray=True).draw_mermaid_png()))
1. ํ์ฌ ์ํ ์กฐํ
- LangGraph์์
get_state
๋ฉ์๋๋ฅผ ํธ์ถํ์ฌ ๊ทธ๋ํ์ ํ์ฌ ์ํ๋ฅผ ํ์ธํ ์ ์์ต๋๋ค. ์ด๋ ํ์ฌ ์คํ ์ค์ด๊ฑฐ๋ ์๋ฃ๋ ๊ทธ๋ํ์ ์ํ๋ฅผ ๊ฐ์ ธ์ต๋๋ค.
1
2
state = graph.get_state({'configurable': {'thread_id': '1'}})
print(state)
- ์ด๋ฅผ ํตํด ํ์ฌ ๊ทธ๋ํ์ ๋ชจ๋ ๋ฉ์์ง, ๋ฉํ๋ฐ์ดํฐ, ์ฒดํฌํฌ์ธํธ ID ๋ฑ์ ํ์ธํ ์ ์์ต๋๋ค.
2. ์ํ ํ์คํ ๋ฆฌ ์กฐํ
get_state_history
๋ฉ์๋๋ฅผ ์ฌ์ฉํ๋ฉด ๊ทธ๋ํ ์คํ ๋์ ๋ฐ์ํ ๋ชจ๋ ์ํ์ ํ์คํ ๋ฆฌ๋ฅผ ์กฐํํ ์ ์์ต๋๋ค. ์ด ํ์คํ ๋ฆฌ๋ ๊ฐ ๋จ๊ณ์์ ๊ทธ๋ํ๊ฐ ์ด๋ค ์ํ๋ฅผ ๊ฐ์ก๋์ง ๋ณด์ฌ์ค๋๋ค.
1
2
3
history = graph.get_state_history(thread)
for state in history:
print(state)
- ์ด ํ์คํ ๋ฆฌ๋ ๊ฐ ์ํ์ ์ฒดํฌํฌ์ธํธ๋ฅผ ๊ธฐ๋กํ๋ฉฐ, ๊ฐ๋ฐ์๋ ์ด ํ์คํ ๋ฆฌ๋ฅผ ํตํด ์ด์ ์ํ๋ฅผ ์ฌ์ํ๊ฑฐ๋ ์์ ํ ์ ์์ต๋๋ค.
1
2
3
4
history = graph.get_state_history(thread)
for i, state in enumerate(history,1):
print(i)
print(state)
(์ฐธ๊ณ ) ์ค๊ฐ ์ ๋ฆฌ
์ฒดํฌํฌ์ธํธ
๋ ์ฌํ ์ค ์ฐ์ ์ฌ์ง ํ ์ฅ์ด๊ณ ,์ค๋ ๋
๋ ๊ทธ ์ฌํ์ ์ ์ฒด ์ฌ์ง ์จ๋ฒ์ด๋ผ๊ณ ํ ์ ์์ต๋๋ค.
-
Graph: ๊ทธ๋ํ๋
๋ ธ๋
์์ฃ์ง
๋ก ๊ตฌ์ฑ๋์ด ์๋์ปจํธ๋กค ํ๋ก์ฐ
์ ๋๋ค.- ๊ฐ ๋ ธ๋๋ ์ด์์คํดํธ, ๋๊ตฌ ์ฌ์ฉ ๋ฑ์ ํน์ ๋จ๊ณ(์ํ, state)๋ฅผ ๋ํ๋ ๋๋ค.
-
Checkpoints:
์ฒดํฌํฌ์ธํฐ(checkpointer)
๊ฐ ๊ฐ๋ ธ๋์ ์ํ
๋ฅผ ์ฒดํฌํฌ์ธํธ๋ก ๊ธฐ๋กํฉ๋๋ค.์ฒดํฌํฌ์ธํธ
์๋ ์ํ ์ ๋ณด์ ํจ๊ป โ๋ค์์ ์ด๋๋ก ๊ฐ์งโ์ ๊ฐ์ ๋ฉํ๋ฐ์ดํฐ๋ ํฌํจ๋ฉ๋๋ค.
- Thread: ์ค๋ ๋(thread)๋
์ฒดํฌํฌ์ธํธ์ ๋ชจ์
์ ๋๋ค. - StateSnapshot: ์ํ ์ค๋
์ท(StateSnapshot)์
์ฒดํฌํฌ์ธํธ์ ๋ฐ์ดํฐ ํ์
๋๋ค. - Graph.get_state():
ํ์ฌ(๊ฐ์ฅ ์ต๊ทผ์) ์ฒดํฌํฌ์ธํธ
๋ฅผ ๋ฐํํ๋ ํจ์์ ๋๋ค. - Graph.get_state_history():
๋ชจ๋ ์ฒดํฌํฌ์ธํธ์ ๋ฆฌ์คํธ
๋ฅผ ๋ฐํํ์ฌ, ์ํ ๋ณํ์ ํ์คํ ๋ฆฌ๋ฅผ ์ ๊ณตํฉ๋๋ค.
3. Replay (๋ค์ ์ฌ์)
Replay
๋ ๊ทธ๋ํ ์คํ์ด ์๋ฃ๋ ํ, ํน์ ์ฒดํฌํฌ์ธํธ์์ ๊ทธ๋ํ๋ฅผ ๋ค์ ์คํํ์ง ์๊ณ ๊ทธ ์ํ๋ฅผ ๊ทธ๋๋ก ์ฌ์ํ๋ ๊ธฐ๋ฅ์ ๋๋ค.- ์ด๋ ๊ณผ๊ฑฐ ์ํ์์ ๋ฌธ์ ๊ฐ ๋ฐ์ํ ์ง์ ์ ์ฌํํ๊ฑฐ๋, ๋ถ์์ ์ํ ๋๋ฒ๊น ๋ชฉ์ ์ผ๋ก ์ฌ์ฉ๋ฉ๋๋ค.
- Graph.stream(None,{thread_id}): ์ด ํจ์๋
ํ์ฌ ์ํ
์์ ๊ทธ๋ํ๋ฅผ์คํ
ํฉ๋๋ค.
- ๊ฒฐ๊ณผ๊ฐ์ผ๋ก
StateSnapshot()์ ๋ฐํ
ํฉ๋๋ค.
- Graph.stream(None,{checkpoint_id, thread_id}): ์ด ํจ์๋ ํน์ ์ฒดํฌํฌ์ธํธ์์ ๊ทธ๋ํ๋ฅผ
๋ค์ ์คํ(re-play)
ํฉ๋๋ค.
- ์ฃผ๋ชฉํ ์ ์
์ค์ ๋ก ๋ ธ๋๋ค์ด ์คํ๋์ง ์๋๋ค
๋ ๊ฒ์ ๋๋ค.
1
2
3
# ํน์ ์ฒดํฌํฌ์ธํธ ID๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ์ฌ์
checkpoint_id = history[-2]['config']['checkpoint_id']
graph.stream(None, {'configurable': {'thread_id': '1', 'checkpoint_id': checkpoint_id}}, stream_mode="values")
- Replay๋ ์ด๋ฏธ ์คํ๋ ์ฒดํฌํฌ์ธํธ๋ฅผ ์ฌ์ํ๊ธฐ ๋๋ฌธ์, ๊ทธ๋ํ์ ๋ ธ๋๋ฅผ ๋ค์ ์คํํ์ง ์๊ณ ๊ธฐ๋ก๋ ์ํ๋ฅผ ๋น ๋ฅด๊ฒ ๋ณต์ํ ์ ์์ต๋๋ค.
4. Forking (๋ถ๊ธฐ)
Forking
์ ํน์ ์ฒดํฌํฌ์ธํธ์์ ์ํ๋ฅผ ์์ ํ๊ณ ์๋ก์ด ์คํ ํ๋ฆ์ ๋ง๋๋ ๊ธฐ๋ฅ์ ๋๋ค.- ์ด๋ฅผ ํตํด ์ฌ์ฉ์๋ ๊ทธ๋ํ์ ์ํ๋ฅผ ๋ณ๊ฒฝํ์ฌ ๋ค๋ฅธ ๊ฒฝ๋ก๋ฅผ ํ์ํ๊ฑฐ๋ ์คํํ ์ ์์ต๋๋ค.
- ์๋ฅผ ๋ค์ด, ํน์ ๋จ๊ณ์์ ์๋ก์ด ์ ๋ ฅ์ ์ฃผ์ด ๋ค๋ฅธ ๊ฒฐ๊ณผ๊ฐ ๋์ค๋์ง ํ์ธํ ์ ์์ต๋๋ค.
-
โ ํ์ฌ ์ํ์์ ํฌํน
- ์ฐ๋ฆฌ๋ LangGraph์
Graph.update_state
๋ฉ์๋๋ฅผ ์ฌ์ฉํ์ฌ ํ์ฌ ์ํ๋ฅผ ํฌํนํ ์ ์์ต๋๋ค. -
์๋ฅผ ๋ค์ด, ๋ค์๊ณผ ๊ฐ์ ์ฝ๋๋ฅผ ํตํด ์ํ๋ฅผ ์ ๋ฐ์ดํธํ ์ ์์ต๋๋ค:
1
Graph.update_state({thread_id}, {state: "I like cold brew"})
- ์ ์ฝ๋๋ฅผ ์คํํ๋ฉด thread_id์ ํ์ฌ ๊ทธ๋ํ ์ํ๊ฐ ์ ๋ฐ์ดํธ๋๋ฉด์ ์๋ก์ด ์ฒดํฌํฌ์ธํธ๊ฐ ์์ฑ๋ฉ๋๋ค. ์ด๋ฅผ โํ์ฌ ์ํ์์ ํฌํนโํ๋ค๊ณ ํํํ ์ ์์ต๋๋ค.
- ์ํ๊ฐ ๊ธฐ๋ก๋๊ณ ์๋ก์ด ์ฒดํฌํฌ์ธํธ๊ฐ ์์ฑ๋๋ฉด, ํฌํฌ๋ ์๋ก์ด ์ํ ์ค๋ ์ท(StateSnapshot)์ด ๋ง๋ค์ด์ง๋๋ค.
- ์ ๊ทธ๋ฆผ์ ์ฒซ๋ฒ์งธ ์์์์๋ โ
I heart Langchain
โ โถ โI like cold brew
โ๋ผ๋ ์ํ๋ก ์๋ก์ด ์ฒดํฌํฌ์ธํธ๊ฐ ๋ง๋ค์ด์ง๋๋ค.
- ์ฐ๋ฆฌ๋ LangGraph์
-
โก ๊ณผ๊ฑฐ ์ฒดํฌํฌ์ธํธ์์ ํฌํน
- ํฌํน์ ๋ ๋ค๋ฅธ ๋ฐฉ์์ ๊ณผ๊ฑฐ์ ํน์ ์ฒดํฌํฌ์ธํธ์์ ์๋ก์ด ์ํ๋ฅผ ํฌํนํ๋ ๊ฒ์ ๋๋ค.
- ์ด๋ฅผ ์ํด, ์ฐ๋ฆฌ๋ ํน์
checkpoint_id
๋ฅผ ์ ๊ณตํ์ฌ ๊ทธ ์์ ์ ์ํ๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ํฌํฌํ ์ ์์ต๋๋ค. -
์๋ฅผ ๋ค์ด, ๋ค์๊ณผ ๊ฐ์ ์ฝ๋๋ก ํน์ ์ฒดํฌํฌ์ธํธ์์ ์ํ๋ฅผ ํฌํนํ ์ ์์ต๋๋ค:
1
Graph.update_state({checkpoint_id}, thread_id, {state: "I like"})
- ์ด ๊ฒฝ์ฐ, ์ด์ ์ ์์ฑ๋ ํน์ ์ฒดํฌํฌ์ธํธ์์ ์ํ๊ฐ ์ ๋ฐ์ดํธ๋๊ณ ์๋ก์ด ์ฒดํฌํฌ์ธํธ๊ฐ ์์ฑ๋ฉ๋๋ค.
- ๊ณผ๊ฑฐ์ ์ํ๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ์๋ก์ด ์ํ๋ฅผ ๋ง๋ค์ด ํฌํฌํ๋ ๊ณผ์ ์ด๋ผ๊ณ ํ ์ ์์ต๋๋ค.
- ์ด๋ฅผ ํตํด ํน์ ์์ ์ผ๋ก ๋์๊ฐ ์๋ก์ด ์คํ ํ๋ฆ์ ๋ง๋ค ์ ์์ต๋๋ค.
๐ฌ Re-play vs Re-Execute
- ์ฌ์(Replaying): ์ด๋ฏธ ์คํ๋ ์ฒดํฌํฌ์ธํธ๋ฅผ ๋ค์ ์คํํ ๋, ๊ทธ๋ํ๋ ๊ทธ ์ํ๊ฐ ์ด๋ฏธ ์คํ๋์์์ ์ธ์งํ๊ณ ์ฌ์ํฉ๋๋ค. ์ฆ, ๊ฐ์ ์ํ๋ฅผ ๊ทธ๋๋ก ์ ์งํ ์ฑ ์คํ์ด ๋ฐ๋ณต๋ฉ๋๋ค. ์๋ฅผ ๋ค์ด, ์ด์ ์ ์คํ๋ ์ํ ์ค๋ ์ท์ ์ ๋ฌํ๋ฉด, ๊ทธ ์ํ์ ๋ฐ๋ผ ๊ทธ๋ํ๊ฐ ์ด๋ฏธ ์คํ๋ ๊ฒ์ฒ๋ผ ๋์ํ๊ฒ ๋ฉ๋๋ค.
- ์ฌ์คํ(Re-executing): ๋ฐ๋ฉด, ์๋ก์ด ์ฒดํฌํฌ์ธํธ๋ฅผ ํตํด ๊ทธ๋ํ๋ฅผ ์คํํ๋ฉด, ๊ทธ๋ํ๋ ์ด์ ์ ์ด ์ํ๊ฐ ์คํ๋ ์ ์ด ์๋ค๋ ๊ฒ์ ์ธ์ํ๊ณ ์๋กญ๊ฒ ์คํ์ ์์ํฉ๋๋ค. ์ด๋ฅผ ์ฌ์คํ์ด๋ผ๊ณ ๋ถ๋ฆ ๋๋ค. ํฌํฌ๋ ์ฒดํฌํฌ์ธํธ๊ฐ ์ฒ์ ์คํ๋๋ ๊ฒฝ์ฐ, ์๋ก์ด ์ํ๋ฅผ ๋ฐํ์ผ๋ก ๊ทธ๋ํ๊ฐ ๋ค์ ์คํ๋ฉ๋๋ค.
-
์ํ ๋ง๋ถ์(Appending)๊ณผ ๋ฎ์ด์ฐ๊ธฐ(Overwriting)
- ์ํ ์ ๋ฐ์ดํธ ์ ์ค์ํ ๋ถ๋ถ์ ๋ง๋ถ์(Appending)๊ณผ ๋ฎ์ด์ฐ๊ธฐ(Overwriting)์ ๋๋ค.
- ์๋ฅผ ๋ค์ด, ๋ฉ์์ง์ ๊ฐ์ ์ํ๋ ๊ธฐ๋ณธ์ ์ผ๋ก ๋ง๋ถ์ฌ์ง์ง๋ง, ํน์
id
๋ฅผ ์ ๋ฌํ๋ฉด ํด๋น ๋ฉ์์ง๊ฐ ๋ฎ์ด์ฐ๊ธฐ ๋ฉ๋๋ค. -
์ด๋
add messages reducer
์ ๊ฐ์ ๋ฐฉ์์์ ๋ฐ์ํฉ๋๋ค.๐ ์ฃผ์
messages
์ ๋ํ ๋ฆฌ๋์๊ฐ ์ด๋ป๊ฒ ์๋ํ๋์ง ๊ธฐ์ตํ์ธ์!- ๋ฉ์์ง ID๋ฅผ ์ ๊ณตํ์ง ์์ผ๋ฉด ๋ค์ ์ถ๊ฐ(append)ํฉ๋๋ค.
- ์ํ์ ์ถ๊ฐํ๋ ๋์ ๋ฉ์์ง๋ฅผ ๋ฎ์ด์ฐ๊ธฐ(overwrite) ์ํด ๋ฉ์์ง ID๋ฅผ ์ ๊ณตํด์ผํฉ๋๋ค!
1
Graph.update_state({checkpoint_id}, thread_id, {state: "I like", id: "message_id"})
- ์ ์ฝ๋๋ฅผ ์ํํ๋ฉด ์๋ก์ด ๋ฉ์์ง๊ฐ ์ถ๊ฐ๋์ง ์๊ณ ๊ธฐ์กด ๋ฉ์์ง๊ฐ ๋ฎ์ด์์์ง๋ฉฐ ์ ๋ฐ์ดํธ๋ฉ๋๋ค.
- ๋ฉ์์ง ์ํ๋ฅผ ์ด๋ป๊ฒ ๊ด๋ฆฌํ ์ง์ ๋ฐ๋ผ ๋ง๋ถ์ผ์ง ๋๋ ๋ฎ์ด์ธ์ง๋ฅผ ๊ฒฐ์ ํ ์ ์์ต๋๋ค.
-
์ํ ํ์คํ ๋ฆฌ ๊ด๋ฆฌ
- ๊ฐ ์ํ ์ ๋ฐ์ดํธ๋ ๊ทธ๋ํ์ ํ์คํ ๋ฆฌ์์ ์๋ก์ด ์ฒดํฌํฌ์ธํธ๋ฅผ ์์ฑํฉ๋๋ค. ์๋ฅผ ๋ค์ด, โI like cold brewโ๋ผ๋ ์ํ๋ฅผ ์ ๋ฐ์ดํธํ๊ณ ์๋ก์ด ์ฒดํฌํฌ์ธํธ๊ฐ ์์ฑ๋์๋ค๋ฉด, ์ด ์ฒดํฌํฌ์ธํธ๋ ํ์คํ ๋ฆฌ์ ๊ธฐ๋ก๋ฉ๋๋ค.
- ์ฌ๋ฌ ๋ฒ ์ํ๋ฅผ ์ ๋ฐ์ดํธํ๋ฉด, ๊ฐ ์ํ๋ ๊ณ ์ ํ ์ฒดํฌํฌ์ธํธ๋ก ๊ด๋ฆฌ๋๊ณ , ํ์ํ ๊ฒฝ์ฐ ๊ณผ๊ฑฐ์ ํน์ ์ฒดํฌํฌ์ธํธ๋ก ๋์๊ฐ ์๋ก์ด ํฌํฌ๋ฅผ ๋ง๋ค ์ ์์ต๋๋ค.
- ํ์คํ ๋ฆฌ์์ ์๋ก์ด ์ฒดํฌํฌ์ธํธ๋ฅผ ์ถ๊ฐํ๋ ๋ฐฉ์์ผ๋ก ์ํ๋ฅผ ๊ด๋ฆฌํ ์ ์์ผ๋ฉฐ, ๊ทธ๋ํ์ ๊ฐ ์ค๋ ๋๋ ์ฌ๋ฌ ์ฒดํฌํฌ์ธํธ๋ก ๊ตฌ์ฑ๋ฉ๋๋ค. ์ด๋ ๊ฒ ํ๋ฉด ๋งค์ฐ ์ ์ฐํ๊ฒ ์ํ๋ฅผ ๊ด๋ฆฌํ ์ ์์ต๋๋ค.
-
ํฌํฌ๋ ์ฒดํฌํฌ์ธํธ๋ก ์ฌ์คํ
- ํฌํฌ๋ ์ฒดํฌํฌ์ธํธ๋ฅผ ํตํด ๊ทธ๋ํ๋ฅผ ๋ค์ ์คํํ ๋, LangGraph๋ ํด๋น ์ฒดํฌํฌ์ธํธ๊ฐ ์ด์ ์ ์คํ๋ ์ ์ด ์๋์ง ํ์ธํ๊ณ ์ฌ์คํํฉ๋๋ค.
- ์๋ฅผ ๋ค์ด, ์๋ก์ด
checkpoint_id
๋ก ์ฌ์คํ์ ํ๋ฉด, ๊ณผ๊ฑฐ ์ํ์์ ์๋ก์ด ์คํ ํ๋ฆ์ด ์์๋ฉ๋๋ค. -
๋ค์๊ณผ ๊ฐ์ด ์๋ก์ด ํฌํฌ๋ ์ฒดํฌํฌ์ธํธ๋ฅผ ์ ๊ณตํ์ฌ ์ฌ์คํํ ์ ์์ต๋๋ค:
1
Graph.stream(None, {checkpoint_id}, thread_id)
- ์ด ์ฝ๋๋ฅผ ํตํด ํฌํฌ๋ ์ฒดํฌํฌ์ธํธ๋ก ๊ทธ๋ํ๊ฐ ์ฌ์คํ๋๊ณ , ๊ทธ์ ๋ฐ๋ผ ์๋ก์ด ์ํ๊ฐ ์์ฑ๋ฉ๋๋ค.
์ด ๊ฐ์ ๋ด์ฉ์ Langraph์ ๋ค์ํ ๊ธฐ๋ฅ๊ณผ Human-in-the-Loop์ ํ์ฉ ๋ฐฉ์์ ์ค์ฌ์ผ๋ก ๊ตฌ์ฑ๋์ด ์์ผ๋ฉฐ, ๊ฐ๊ฐ์ ๊ธฐ๋ฅ์ ์ค์ ์ํฌํ๋ก์ฐ์์ ๋งค์ฐ ์ ์ฉํ๊ฒ ์ฌ์ฉ๋ฉ๋๋ค. ๊ฐ ๋ ์จ์ ์ธ๋ถ ๋ด์ฉ์ ์ฒ ์ ํ ์ดํดํ๋ฉด Langraph๋ฅผ ์ด์ฉํ ๊ณ ๊ธ ์์ ํ๋ฆ์ ์ฝ๊ฒ ๊ตฌํํ ์ ์์ ๊ฒ์ ๋๋ค.