[๊ฐ•์˜๋…ธํŠธ] LangChain Academy : Introduction to LangGraph (Module 3)

Posted by Euisuk's Dev Log on October 16, 2024

[๊ฐ•์˜๋…ธํŠธ] LangChain Academy : Introduction to LangGraph (Module 3)

์›๋ณธ ๊ฒŒ์‹œ๊ธ€: https://velog.io/@euisuk-chung/3-m82jb3x6

๋žญ์ฒด์ธ(LangChain)๊ณผ ๋žญ๊ทธ๋ž˜ํ”„(LangGraph)๋Š” ๋Œ€๊ทœ๋ชจ ์–ธ์–ด ๋ชจ๋ธ(LLM)์„ ํ™œ์šฉํ•œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๊ฐœ๋ฐœ์„ ์œ„ํ•œ ๋„๊ตฌ๋“ค์ž…๋‹ˆ๋‹ค. ์œ„ ๊ฐ•์˜๋Š” LangChain์—์„œ ์šด์˜ํ•˜๋Š” LangChain Academy์—์„œ ์ œ์ž‘ํ•œ โ€œIntroduction to LangGraphโ€ ๊ฐ•์˜์˜ ๋‚ด์šฉ์„ ์ •๋ฆฌ ๋ฐ ์ถ”๊ฐ€ ์„ค๋ช…ํ•œ ๋‚ด์šฉ์ž…๋‹ˆ๋‹ค.

์ด๋ฒˆ ํฌ์ŠคํŠธ๋Š” โ€œ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() ๋ฉ”์„œ๋“œ๋Š” ๊ทธ๋ž˜ํ”„ ์‹คํ–‰ ์ค‘ ์ƒํƒœ๋ฅผ ์ŠคํŠธ๋ฆฌ๋ฐํ•˜๋Š” ๋‘ ๊ฐ€์ง€ ์ฃผ์š” ๋ฐฉ๋ฒ•์ž…๋‹ˆ๋‹ค. ์ด ๋‘ ๋ฉ”์„œ๋“œ์˜ ์ฐจ์ด์ ๊ณผ ์‚ฌ์šฉ ๋ฐฉ๋ฒ•์„ ์„ค๋ช…ํ•ด๋“œ๋ฆฌ๊ฒ ์Šต๋‹ˆ๋‹ค:
  1. .stream() - ๋™๊ธฐ ๋ฐฉ์‹ ์ŠคํŠธ๋ฆฌ๋ฐ:

    • .stream(): Stream์€ ๋™๊ธฐ์‹(Synchronous) ๋ฐฉ์‹์œผ๋กœ, ๊ฐ ๋…ธ๋“œ๊ฐ€ ํ˜ธ์ถœ๋  ๋•Œ๋งˆ๋‹ค ์ƒํƒœ ์—…๋ฐ์ดํŠธ๋ฅผ ์ŠคํŠธ๋ฆฌ๋ฐํ•ฉ๋‹ˆ๋‹ค.
    • ๋™๊ธฐ์ ์œผ๋กœ ์ž‘๋™ํ•œ๋‹ค๋Š” ๊ฒƒ์€, ์ž‘์—…์„ ํ•˜๋‚˜์”ฉ ์ˆœ์ฐจ์ ์œผ๋กœ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ฒƒ์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค. ์•ž์˜ ์ž‘์—…์ด ์™„๋ฃŒ๋˜์ง€ ์•Š์œผ๋ฉด ๋’ค์˜ ์ž‘์—…์ด ์‹คํ–‰๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

      • ์˜ˆ๋ฅผ ๋“ค์–ด, ์นœ๊ตฌ์—๊ฒŒ ์ „ํ™”๋ฅผ ๊ฑธ์–ด์„œ ์นœ๊ตฌ๊ฐ€ ์ „ํ™”๋ฅผ ๋ฐ›๊ธฐ ์ „๊นŒ์ง€ ๊ธฐ๋‹ค๋ฆฌ๋Š” ์ƒํ™ฉ์„ ์ƒ๊ฐํ•ด ๋ณด์„ธ์š”. ์ „ํ™”๋ฅผ ๋ฐ›๊ธฐ ์ „๊นŒ์ง€ ๋‹ค๋ฅธ ์ผ์„ ํ•  ์ˆ˜ ์—†๋Š” ๊ฒƒ์ฒ˜๋Ÿผ, ๋™๊ธฐ ๋ฐฉ์‹์€ ํ•˜๋‚˜์˜ ์ž‘์—…์ด ๋๋‚  ๋•Œ๊นŒ์ง€ ๋Œ€๊ธฐํ•œ ํ›„ ๋‹ค์Œ ์ž‘์—…์„ ์ง„ํ–‰ํ•˜๋Š” ๋ฐฉ์‹์ž…๋‹ˆ๋‹ค.
    • LangGraph์—์„œ .stream() ๋ฉ”์„œ๋“œ๋Š” ๋™๊ธฐ ๋ฐฉ์‹์œผ๋กœ ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค. ์ฆ‰, ๊ฐ ๋…ธ๋“œ๊ฐ€ ์™„๋ฃŒ๋  ๋•Œ๊นŒ์ง€ ๊ธฐ๋‹ค๋ฆฐ ํ›„ ๊ทธ ๊ฒฐ๊ณผ๋ฅผ ๋ฐ›์•„์˜ค๋Š” ๋ฐฉ์‹์ž…๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ๊ทธ๋ž˜ํ”„์˜ ์ฒซ ๋ฒˆ์งธ ๋…ธ๋“œ๊ฐ€ ์‹คํ–‰์„ ์™„๋ฃŒํ•ด์•ผ ๋‘ ๋ฒˆ์งธ ๋…ธ๋“œ๊ฐ€ ์‹คํ–‰๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

      1
      2
      
      for chunk in graph.stream(inputs, stream_mode="values"):
          print(chunk)  # ๊ฐ ๋…ธ๋“œ ์‹คํ–‰ ํ›„ ์ถœ๋ ฅ
      
      • ์ด ์ฝ”๋“œ์—์„œ๋Š” ์ฒซ ๋ฒˆ์งธ ๋…ธ๋“œ์˜ ์‹คํ–‰์ด ๋๋‚˜์•ผ ๊ทธ ๊ฒฐ๊ณผ(chunk)๊ฐ€ ์ถœ๋ ฅ๋˜๊ณ , ๊ทธ ํ›„์—์•ผ ๋‘ ๋ฒˆ์งธ ๋…ธ๋“œ๊ฐ€ ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค.
      • ๋ชจ๋“  ์ž‘์—…์ด ์ˆœ์„œ๋Œ€๋กœ ์ด๋ฃจ์–ด์ง€๊ธฐ ๋•Œ๋ฌธ์— ์ฝ”๋“œ์˜ ํ๋ฆ„์ด ์˜ˆ์ธก ๊ฐ€๋Šฅํ•˜๊ณ  ์ง๊ด€์ ์ž…๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ์ž‘์—…์ด ์˜ค๋ž˜ ๊ฑธ๋ฆด ๊ฒฝ์šฐ, ๊ทธ ์ž‘์—…์ด ๋๋‚  ๋•Œ๊นŒ์ง€ ์•„๋ฌด๊ฒƒ๋„ ํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.
  2. .astream() - ๋น„๋™๊ธฐ ๋ฐฉ์‹ ์ŠคํŠธ๋ฆฌ๋ฐ:

    • .astream(): A-Stream์€ ๋น„๋™๊ธฐ์‹(Asynchronous) ๋ฐฉ์‹์œผ๋กœ ์ƒํƒœ ์—…๋ฐ์ดํŠธ๋ฅผ ์ŠคํŠธ๋ฆฌ๋ฐํ•ฉ๋‹ˆ๋‹ค.
    • ๋น„๋™๊ธฐ์ ์œผ๋กœ ์ž‘๋™ํ•œ๋‹ค๋Š” ๊ฒƒ์€, ์ž‘์—…์„ ๋™์‹œ์— ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ๊ฒƒ์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค. ์•ž์˜ ์ž‘์—…์ด ๋๋‚  ๋•Œ๊นŒ์ง€ ๊ธฐ๋‹ค๋ฆฌ์ง€ ์•Š๊ณ , ๋‹ค๋ฅธ ์ž‘์—…์„ ์ง„ํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

      • ๋‹ค์‹œ ์ „ํ™”๋ฅผ ๊ฑฐ๋Š” ์˜ˆ์‹œ๋กœ ๋Œ์•„๊ฐ€๋ฉด, ์นœ๊ตฌ๊ฐ€ ์ „ํ™”๋ฅผ ๋ฐ›์„ ๋•Œ๊นŒ์ง€ ๊ธฐ๋‹ค๋ฆฌ์ง€ ์•Š๊ณ  ๋‹ค๋ฅธ ์ผ์„ ํ•  ์ˆ˜ ์žˆ๋Š” ์ƒํ™ฉ์„ ์ƒ์ƒํ•ด ๋ณด์„ธ์š”. ์นœ๊ตฌ๊ฐ€ ์ „ํ™”๋ฅผ ๋ฐ›์œผ๋ฉด ๊ทธ๋•Œ ๋‹ค์‹œ ํ†ตํ™”๋กœ ๋Œ์•„์˜ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
    • LangGraph์—์„œ .astream() ๋ฉ”์„œ๋“œ๋Š” ๋น„๋™๊ธฐ ๋ฐฉ์‹์œผ๋กœ ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค. ์ฆ‰, ํ•œ ์ž‘์—…์ด ๋๋‚  ๋•Œ๊นŒ์ง€ ๊ธฐ๋‹ค๋ฆฌ์ง€ ์•Š๊ณ  ๋‹ค๋ฅธ ์ž‘์—…์„ ๋™์‹œ์— ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด ๋ฐฉ์‹์€ ํŠนํžˆ ๋งŽ์€ ์‹œ๊ฐ„์ด ๊ฑธ๋ฆฌ๋Š” ์ž‘์—…(์˜ˆ: ๋„คํŠธ์›Œํฌ ์š”์ฒญ, ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ ‘๊ทผ ๋“ฑ)์„ ํšจ์œจ์ ์œผ๋กœ ์ฒ˜๋ฆฌํ•˜๋Š” ๋ฐ ์œ ๋ฆฌํ•ฉ๋‹ˆ๋‹ค.

      1
      2
      
      async for chunk in graph.astream(inputs, stream_mode="values"):
          print(chunk)  # ๊ฐ ๋…ธ๋“œ ์‹คํ–‰์ด ๋๋‚  ๋•Œ๊นŒ์ง€ ๊ธฐ๋‹ค๋ฆฌ์ง€ ์•Š๊ณ  ์ฒ˜๋ฆฌ
      
      • ์ด ์ฝ”๋“œ์—์„œ๋Š” ์ฒซ ๋ฒˆ์งธ ๋…ธ๋“œ์˜ ์‹คํ–‰์ด ์™„๋ฃŒ๋˜๊ธฐ ์ „์— ๋‘ ๋ฒˆ์งธ ๋…ธ๋“œ๊ฐ€ ์‹คํ–‰๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
      • ๋น„๋™๊ธฐ ๋ฐฉ์‹ ๋•๋ถ„์— ์—ฌ๋Ÿฌ ์ž‘์—…์„ ๋™์‹œ์— ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์ง€๋งŒ, ๊ทธ๋กœ ์ธํ•ด ์ฝ”๋“œ์˜ ์‹คํ–‰ ์ˆœ์„œ๊ฐ€ ๋ณต์žกํ•ด์งˆ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๐Ÿค” ์–ธ์ œ ๋™๊ธฐ/๋น„๋™๊ธฐ๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ• ๊นŒ?

  • ๋™๊ธฐ: ์ž‘์—…์ด ๋น„๊ต์  ๋น ๋ฅด๊ฒŒ ๋๋‚˜๊ณ  ์ˆœ์ฐจ์ ์œผ๋กœ ์‹คํ–‰ํ•ด์•ผ ํ•  ๋•Œ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.
    • ์˜ˆ๋ฅผ ๋“ค์–ด, ์ž‘์€ ๊ทœ๋ชจ์˜ ํ”„๋กœ๊ทธ๋žจ์ด๋‚˜ ํ•œ ๋ฒˆ์— ํ•˜๋‚˜์”ฉ ์ฒ˜๋ฆฌํ•ด์•ผ ํ•˜๋Š” ์ž‘์—…์—์„œ ๋™๊ธฐ ๋ฐฉ์‹์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค.
  • ๋น„๋™๊ธฐ: ๋น„๋™๊ธฐ๋Š” ์—ฌ๋Ÿฌ ์ž‘์—…์„ ๋™์‹œ์— ์ฒ˜๋ฆฌํ•ด์•ผ ํ•  ๋•Œ ํŠนํžˆ ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค.
    • ์˜ˆ๋ฅผ ๋“ค์–ด, ๋„คํŠธ์›Œํฌ ์š”์ฒญ, ํŒŒ์ผ ์ฝ๊ธฐ/์“ฐ๊ธฐ, ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ ‘๊ทผ๊ณผ ๊ฐ™์ด ์‹œ๊ฐ„์ด ์˜ค๋ž˜ ๊ฑธ๋ฆฌ๋Š” ์ž‘์—…์—์„œ ๋น„๋™๊ธฐ ๋ฐฉ์‹์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ๋” ํšจ์œจ์ ์ž…๋‹ˆ๋‹ค.

์ŠคํŠธ๋ฆฌ๋ฐ ๋ชจ๋“œ

  • ์ŠคํŠธ๋ฆผ ๋ชจ๋“œ๋Š” LangGraph๊ฐ€ ์‹คํ–‰๋˜๋Š” ๋™์•ˆ ๊ทธ๋ž˜ํ”„์˜ ์ƒํƒœ๋ฅผ ์–ด๋–ค ๋ฐฉ์‹์œผ๋กœ ๋ชจ๋‹ˆํ„ฐ๋งํ•˜๊ณ  ํ™•์ธํ•  ๊ฒƒ์ธ์ง€๋ฅผ ๊ฒฐ์ •ํ•˜๋Š” ์˜ต์…˜์ž…๋‹ˆ๋‹ค.
  • ๊ฐ ๋ชจ๋“œ๋Š” ๊ทธ๋ž˜ํ”„๊ฐ€ ์‹คํ–‰๋  ๋•Œ ์ƒ์„ฑ๋˜๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ์–ด๋–ป๊ฒŒ ์ŠคํŠธ๋ฆฌ๋ฐํ• ์ง€์— ๋Œ€ํ•œ ๋ฐฉ์‹์„ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค. ์‰ฝ๊ฒŒ ๋งํ•˜๋ฉด, ์ŠคํŠธ๋ฆฌ๋ฐ ๋ชจ๋“œ๋Š” ์‹คํ–‰ ์ค‘ ๋ฐœ์ƒํ•˜๋Š” ์ •๋ณด(์ƒํƒœ๋‚˜ ๊ฒฐ๊ณผ๋ฌผ)๋ฅผ ์‹ค์‹œ๊ฐ„์œผ๋กœ ์–ด๋–ป๊ฒŒ ๋ณด๊ณ ํ•  ๊ฒƒ์ธ์ง€๋ฅผ ๋‚˜ํƒ€๋ƒ…๋‹ˆ๋‹ค.

์ฃผ์š” ์ŠคํŠธ๋ฆผ ๋ชจ๋“œ

  1. โ€œvaluesโ€ ๋ชจ๋“œ:

    • ๊ทธ๋ž˜ํ”„์˜ ๊ฐ ๋…ธ๋“œ๊ฐ€ ์‹คํ–‰๋œ ํ›„, ์ „์ฒด ์ƒํƒœ๋ฅผ ์ŠคํŠธ๋ฆฌ๋ฐํ•ฉ๋‹ˆ๋‹ค.
    • ์ฆ‰, ๋…ธ๋“œ๊ฐ€ ์‹คํ–‰๋œ ํ›„ ํ˜„์žฌ๊นŒ์ง€์˜ ๋ชจ๋“  ์ƒํƒœ๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
    • ์˜ˆ๋ฅผ ๋“ค์–ด, ๋…ธ๋“œ๊ฐ€ ์‹คํ–‰๋  ๋•Œ๋งˆ๋‹ค ์ „์ฒด ์ž‘์—… ํ๋ฆ„์˜ ์Šค๋ƒ…์ƒท์„ ์ œ๊ณต๋ฐ›๋Š” ๊ฒƒ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค. ์ด ๋ฐฉ๋ฒ•์€ ๊ฐ ๋…ธ๋“œ ์‹คํ–‰ ํ›„ ์ „์ฒด ์ƒํƒœ๋ฅผ ๊ฒ€ํ† ํ•  ๋•Œ ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค.

    ์‚ฌ์šฉ ์˜ˆ์‹œ:

    1
    2
    
    for chunk in graph.stream(inputs, stream_mode="values"):
        print(chunk)  # ๊ฐ ๋…ธ๋“œ ์‹คํ–‰ ํ›„ ์ „์ฒด ์ƒํƒœ ์ถœ๋ ฅ
    
  2. โ€œupdatesโ€ ๋ชจ๋“œ:

    • ๊ฐ ๋…ธ๋“œ ์‹คํ–‰ ํ›„ ๋ณ€๊ฒฝ๋œ ๋ถ€๋ถ„๋งŒ ์ŠคํŠธ๋ฆฌ๋ฐํ•ฉ๋‹ˆ๋‹ค.
    • ์ฆ‰, ์ƒํƒœ๊ฐ€ ๋ณ€ํ•œ ๋ถ€๋ถ„๋งŒ ์‹ค์‹œ๊ฐ„์œผ๋กœ ๋ณผ ์ˆ˜ ์žˆ์–ด, ๋ถˆํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ์ค„์ด๊ณ  ๊ด€์‹ฌ ์žˆ๋Š” ๋ถ€๋ถ„๋งŒ ํ™•์ธํ•˜๋Š” ๋ฐ ์œ ๋ฆฌํ•ฉ๋‹ˆ๋‹ค.
    • ์˜ˆ๋ฅผ ๋“ค์–ด, ๋Œ€๊ทœ๋ชจ ๋ฐ์ดํ„ฐ๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ทธ๋ž˜ํ”„์—์„œ ๋ณ€ํ™”๊ฐ€ ์žˆ๋Š” ๋ถ€๋ถ„๋งŒ ํ™•์ธํ•˜๊ณ  ์‹ถ์„ ๋•Œ ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค.

    ์‚ฌ์šฉ ์˜ˆ์‹œ:

    1
    2
    
    for chunk in graph.stream(inputs, stream_mode="updates"):
        print(chunk)  # ๋ณ€๊ฒฝ๋œ ์ƒํƒœ๋งŒ ์ถœ๋ ฅ
    

  1. LLM ํ† ํฐ ์ŠคํŠธ๋ฆฌ๋ฐ: LLM ํ† ํฐ ์ŠคํŠธ๋ฆฌ๋ฐ์€ LangGraph์—์„œ ๋Œ€ํ˜• ์–ธ์–ด ๋ชจ๋ธ(LLM)์ด ์ƒ์„ฑํ•˜๋Š” ํ…์ŠคํŠธ๋ฅผ ์‹ค์‹œ๊ฐ„์œผ๋กœ ๋ชจ๋‹ˆํ„ฐ๋งํ•˜๋Š” ๊ธฐ๋Šฅ์ž…๋‹ˆ๋‹ค.
    • LLM์€ ์ฃผ๋กœ ๊ธด ํ…์ŠคํŠธ๋ฅผ ์ƒ์„ฑํ•˜๋Š”๋ฐ, ๋ชจ๋“  ๊ฒฐ๊ณผ๊ฐ€ ํ•œ ๋ฒˆ์— ๋ฐ˜ํ™˜๋˜๋Š” ๋Œ€์‹  ํ† ํฐ์ด๋ผ๋Š” ์ž‘์€ ๋‹จ์œ„๋กœ ๊ฒฐ๊ณผ๋ฅผ ์ ์ง„์ ์œผ๋กœ ์ œ๊ณตํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
    • ์ด๋•Œ ์ŠคํŠธ๋ฆฌ๋ฐ์„ ํ†ตํ•ด ๋ชจ๋ธ์ด ์ƒ์„ฑํ•˜๋Š” ํ…์ŠคํŠธ๋ฅผ ํ•œ ๋ฒˆ์— ๊ธฐ๋‹ค๋ฆฌ์ง€ ์•Š๊ณ  ์‹ค์‹œ๊ฐ„์œผ๋กœ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋Š” ์žฅ์ ์ด ์žˆ์Šต๋‹ˆ๋‹ค. ์ด ๊ธฐ๋Šฅ์€ ๋Œ€ํ™”ํ˜• ์‹œ์Šคํ…œ, ๊ธด ํ…์ŠคํŠธ ์ƒ์„ฑ ์ž‘์—…์—์„œ ํŠนํžˆ ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค.

๐Ÿ’ก LLM ํ† ํฐ ์ŠคํŠธ๋ฆฌ๋ฐ์˜ ๋ชฉ์ 

  1. ์‹ค์‹œ๊ฐ„ ์‘๋‹ต ์ œ๊ณต: LLM์ด ์‘๋‹ต์„ ์ƒ์„ฑํ•˜๋Š” ๋™์•ˆ ์ฆ‰๊ฐ์ ์ธ ํ”ผ๋“œ๋ฐฑ์„ ์‚ฌ์šฉ์ž์—๊ฒŒ ์ œ๊ณตํ•˜์—ฌ ๊ธฐ๋‹ค๋ฆฌ๋Š” ์‹œ๊ฐ„์„ ์ค„์ด๊ณ , ์‚ฌ์šฉ์ž ๊ฒฝํ—˜์„ ํ–ฅ์ƒ์‹œํ‚ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

  2. ์„ธ๋ฐ€ํ•œ ๋ชจ๋‹ˆํ„ฐ๋ง: LLM์ด ์ค‘๊ฐ„์— ์ƒ์„ฑํ•˜๋Š” ํ…์ŠคํŠธ๋ฅผ ๊ด€์ฐฐํ•˜์—ฌ ์‹œ์Šคํ…œ์˜ ์„ฑ๋Šฅ์„ ํ‰๊ฐ€ํ•˜๊ฑฐ๋‚˜ ๋ฌธ์ œ๋ฅผ ๋””๋ฒ„๊น…ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

  3. ์œ ์—ฐํ•œ ์ œ์–ด: ํŠน์ • ๋…ธ๋“œ์—์„œ ๋ฐœ์ƒํ•˜๋Š” ํ† ํฐ๋งŒ์„ ์‹ค์‹œ๊ฐ„์œผ๋กœ ํ™•์ธํ•˜๊ณ , ํ•„์š”ํ•œ ๊ฒฝ์šฐ ์ค‘๊ฐ„์— ๊ฐœ์ž…ํ•˜๊ฑฐ๋‚˜ ์ œ์–ดํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ŠคํŠธ๋ฆฌ๋ฐ ํ† ํฐ์˜ ์ฃผ์š” ๊ฐœ๋…

  1. ์ด๋ฒคํŠธ ๊ธฐ๋ฐ˜ ์ŠคํŠธ๋ฆฌ๋ฐ:
    • 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โ€ ์ด๋ฒคํŠธ๋ฅผ ํ†ตํ•ด ํ˜„์žฌ ํ•ด๋‹น ๋…ธ๋“œ์—์„œ ์‘๋‹ต์„ ์ƒ์„ฑํ•˜๋Š” ์ค‘์ž„์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  1. ์ด๋ฒคํŠธ ๊ตฌ์กฐ:

    • 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)์— ๋Œ€ํ•œ ์ •๋ณด๋„ ํฌํ•จ๋ฉ๋‹ˆ๋‹ค.

์ŠคํŠธ๋ฆฌ๋ฐ ๋ฐฉ๋ฒ•

  1. ํŠน์ • ๋…ธ๋“œ์˜ ํ† ํฐ ์ŠคํŠธ๋ฆฌ๋ฐ:
    • 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')}
(์ค‘๋žต)
  1. ํ† ํฐ ๋ฐ์ดํ„ฐ ์ถ”์ถœ:
    • ์ด๋ฒคํŠธ์—์„œ 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 ์›Œํฌํ”Œ๋กœ์šฐ์—์„œ ์ค‘์š”ํ•œ ์š”์†Œ๋กœ, ๋ฏผ๊ฐํ•œ ์ž‘์—…(์˜ˆ: ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์“ฐ๊ธฐ, ์™ธ๋ถ€ ์‹œ์Šคํ…œ์— ์“ฐ๊ธฐ)์— ๋Œ€ํ•ด ์‚ฌ๋žŒ์ด ์Šน์ธ์„ ํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•ด์ค๋‹ˆ๋‹ค.

์ฃผ์š” ๊ฐœ๋…

  1. Human-in-the-loop (์ธ๊ฐ„ ๊ฐœ์ž…) ๊ฐœ๋…:

    • AI ์‹œ์Šคํ…œ์˜ ์ž‘๋™ ๊ณผ์ •์— ์ธ๊ฐ„์ด ๊ฐœ์ž…ํ•˜์—ฌ ๊ฐ๋…, ์ˆ˜์ •, ์Šน์ธ ๋“ฑ์„ ์ˆ˜ํ–‰ํ•˜๋Š” ๋ฐฉ์‹์ž…๋‹ˆ๋‹ค.
    • ์ด๋Š” AI์˜ ๊ฒฐ์ •์ด๋‚˜ ํ–‰๋™์„ ๊ฒ€ํ† ํ•˜๊ณ  ํ•„์š”์‹œ ์กฐ์ •ํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ค๋‹ˆ๋‹ค.
  2. LangGraph์—์„œ์˜ Human-in-the-loop ๋™๊ธฐ:

    a) ์Šน์ธ (Approval):

    • AI ์—์ด์ „ํŠธ์˜ ํŠน์ • ํ–‰๋™์ด๋‚˜ ๊ฒฐ์ •์„ ์‚ฌ์šฉ์ž๊ฐ€ ์Šน์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
    • ์ค‘์š”ํ•œ ๊ฒฐ์ •์ด๋‚˜ ์œ„ํ—˜ํ•œ ์ž‘์—… ์ „์— ์ธ๊ฐ„์˜ ๊ฒ€ํ† ๋ฅผ ๋ฐ›์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

    b) ๋””๋ฒ„๊น… (Debugging):

    • ๊ทธ๋ž˜ํ”„ ์‹คํ–‰์„ ๋˜๋Œ๋ ค ๋ฌธ์ œ๋ฅผ ์žฌํ˜„ํ•˜๊ฑฐ๋‚˜ ํ”ผํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
    • ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•œ ์ง€์ ์„ ์ •ํ™•ํžˆ ํŒŒ์•…ํ•˜๊ณ  ์ˆ˜์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

    c) ํŽธ์ง‘ (Editing):

    • ๊ทธ๋ž˜ํ”„์˜ ์ƒํƒœ๋ฅผ ์ˆ˜์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
    • ์‹คํ–‰ ์ค‘ ํ•„์š”ํ•œ ๋ณ€๊ฒฝ์‚ฌํ•ญ์„ ์ ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  3. ๋ธŒ๋ ˆ์ดํฌํฌ์ธํŠธ (Breakpoints):

    • ๊ทธ๋ž˜ํ”„์˜ ํŠน์ • ๋‹จ๊ณ„์—์„œ ์‹คํ–‰์„ ์ค‘๋‹จํ•˜๋Š” ๊ธฐ๋Šฅ์ž…๋‹ˆ๋‹ค.
    • ์‚ฌ์šฉ์ž๊ฐ€ ๊ทธ๋ž˜ํ”„์˜ ์ƒํƒœ๋ฅผ ๊ฒ€์‚ฌํ•˜๊ณ  ํ•„์š”ํ•œ ์กฐ์น˜๋ฅผ ์ทจํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ฉ๋‹ˆ๋‹ค.
    • ์ฃผ๋กœ ์Šน์ธ ํ”„๋กœ์„ธ์Šค์— ์‚ฌ์šฉ๋˜๋ฉฐ, ํŠน์ • ๋…ธ๋“œ ์‹คํ–‰ ์ „์— ์ค‘๋‹จ์ ์„ ์„ค์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋ธŒ๋ ˆ์ดํฌํฌ์ธํŠธ (Breakpoints)

  • Interrupt Before ๋˜๋Š” Interrupt After๋ฅผ ํ†ตํ•ด ๋ธŒ๋ž˜์ดํฌํฌ์ธํŠธ๋ฅผ ์„ค์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
    1. Interrupt Before: ํŠน์ • ๋…ธ๋“œ๊ฐ€ ์‹คํ–‰๋˜๊ธฐ ์ „์— ๊ทธ๋ž˜ํ”„ ์‹คํ–‰์„ ์ค‘๋‹จํ•ฉ๋‹ˆ๋‹ค.
    2. Interrupt After: ํŠน์ • ๋…ธ๋“œ ์‹คํ–‰ ํ›„์— ๊ทธ๋ž˜ํ”„๋ฅผ ๋ฉˆ์ถฅ๋‹ˆ๋‹ค.
  1. ์ŠคํŠธ๋ฆฌ๋ฐ๊ณผ์˜ ์—ฐ๊ณ„:
    • ์ŠคํŠธ๋ฆฌ๋ฐ์„ ํ†ตํ•ด ๊ทธ๋ž˜ํ”„ ์‹คํ–‰ ์ค‘ ์ถœ๋ ฅ์„ ์‹ค์‹œ๊ฐ„์œผ๋กœ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
    • ๋ธŒ๋ ˆ์ดํฌํฌ์ธํŠธ์™€ ๊ฒฐํ•ฉํ•˜์—ฌ ๋” ์„ธ๋ฐ€ํ•œ ์ œ์–ด์™€ ๋ชจ๋‹ˆํ„ฐ๋ง์ด ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.

์˜ˆ์ œ

  • ์˜ˆ์ „์— ๋ชจ๋“ˆ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) ์„ค์ •

  • ์ค‘๋‹จ์ ์˜ ๊ฐœ๋…: ๊ทธ๋ž˜ํ”„์˜ ํŠน์ • ๋…ธ๋“œ์—์„œ ์‹คํ–‰์„ ์ผ์‹œ ์ค‘๋‹จํ•˜๊ณ , ์‚ฌ์šฉ์ž์˜ ๊ฐœ์ž…์„ ๊ธฐ๋‹ค๋ฆฌ๊ฑฐ๋‚˜ ์ƒํƒœ๋ฅผ ์ˆ˜์ •ํ•˜๋Š” ๊ธฐํšŒ๋ฅผ ์ œ๊ณตํ•˜๋Š” ๋ฉ”์ปค๋‹ˆ์ฆ˜์ž…๋‹ˆ๋‹ค. ์•„๋ž˜์˜ ๋ชฉ์ ์œผ๋กœ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค.

    1. ์Šน์ธ(Approval): ์‚ฌ์šฉ์ž๊ฐ€ ํŠน์ • ๋„๊ตฌ ํ˜ธ์ถœ์„ ์Šน์ธํ•˜๊ฑฐ๋‚˜ ๊ฑฐ์ ˆํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. (Lesson2 ์†Œ๊ฐœ)
    2. ๋””๋ฒ„๊น…(Debugging): ๊ทธ๋ž˜ํ”„๊ฐ€ ์ค‘๋‹จ๋œ ์ƒํƒœ์—์„œ ํ˜„์žฌ ์ƒํƒœ๋ฅผ ๊ฒ€ํ† ํ•˜๊ณ , ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. (Lesson2 ์†Œ๊ฐœ)
    3. ํŽธ์ง‘(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โ€): ๊ทธ๋ž˜ํ”„๋ฅผ ์žฌ์‹คํ–‰ํ•˜๋ฉด์„œ, ์ค‘๋‹จ๋˜์—ˆ๋˜ ์ง€์ ์—์„œ ์ƒํƒœ๋ฅผ ํ™•์ธํ•˜๊ณ , ์ƒํƒœ๊ฐ€ ์—…๋ฐ์ดํŠธ๋œ ํ›„ ๋‚จ์€ ๋…ธ๋“œ๋ฅผ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค.
  • ์žฌ์‹คํ–‰ ๊ณผ์ •:

    1. ๊ทธ๋ž˜ํ”„ ์ƒํƒœ๋ฅผ ์—…๋ฐ์ดํŠธํ•˜๊ณ , None์„ ์ „๋‹ฌํ•˜์—ฌ ์ค‘๋‹จ๋œ ์ง€์ ์—์„œ ๋‹ค์‹œ ์‹คํ–‰์„ ๊ณ„์†ํ•ฉ๋‹ˆ๋‹ค.
    2. ๊ทธ๋ž˜ํ”„๊ฐ€ ์ง„ํ–‰๋˜๋ฉด์„œ ์ตœ์ข… ๋„๊ตฌ ํ˜ธ์ถœ ๋ฐ AI ๋ชจ๋ธ ์‘๋‹ต์„ ์ˆ˜ํ–‰ํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

Dummy Node์™€ ํ”ผ๋“œ๋ฐฑ ์ ์šฉ

  • ์ด ๊ธฐ๋ฒ•์€ ์‹ค์‹œ๊ฐ„ ์‚ฌ์šฉ์ž ๊ฐœ์ž…์„ ํ†ตํ•ด, LLM๊ณผ์˜ ์ƒํ˜ธ์ž‘์šฉ์„ ๋ณด๋‹ค ๋™์ ์œผ๋กœ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋Š” ๋ฐฉ๋ฒ•์ž…๋‹ˆ๋‹ค.
  • ์‚ฌ์šฉ์ž๋Š” ํŠน์ • ๋…ธ๋“œ์—์„œ ์‹คํ–‰ ์ค‘๋‹จ ํ›„ ์ƒํƒœ๋ฅผ ์ˆ˜์ •ํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ๊ทธ ์ดํ›„๋กœ๋„ ๊ทธ๋ž˜ํ”„๊ฐ€ ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ๊ณ„์† ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค.
  • ์ด๋ฅผ ํ†ตํ•ด ๋” ๋ณต์žกํ•œ ์ž‘์—… ํ๋ฆ„์—์„œ ์ธ๊ฐ„ ํ”ผ๋“œ๋ฐฑ์„ ๋ฐ˜์˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

    • Dummy Node๋ž€?: Dummy Node๋Š” ์‹ค์ œ๋กœ๋Š” ์•„๋ฌด ์ž‘์—…๋„ ํ•˜์ง€ ์•Š์ง€๋งŒ, ํŠน์ • ์‹œ์ ์—์„œ ์‚ฌ์šฉ์ž์˜ ํ”ผ๋“œ๋ฐฑ์„ ๋ฐ›์„ ์ˆ˜ ์žˆ๋„๋ก ์ค‘๋‹จ์ ์„ ๋งŒ๋“ค์–ด ์ฃผ๋Š” ๊ฐ€์ƒ์˜ ๋…ธ๋“œ์ž…๋‹ˆ๋‹ค.

      • ์ด ๋…ธ๋“œ๋Š” ์‹ค์งˆ์ ์ธ ์—ฐ์‚ฐ์ด๋‚˜ ์ƒํƒœ ๋ณ€๊ฒฝ์„ ํ•˜์ง€ ์•Š๊ณ , ๊ทธ๋ž˜ํ”„๋ฅผ ์ผ์‹œ ์ •์ง€ํ•˜์—ฌ ์‚ฌ์šฉ์ž๊ฐ€ ๊ฐœ์ž…ํ•  ์ˆ˜ ์žˆ๋Š” ์ง€์ ์„ ๋งŒ๋“ค์–ด ์ฃผ๋Š” ์—ญํ• ์„ ํ•ฉ๋‹ˆ๋‹ค.
    • ํ”ผ๋“œ๋ฐฑ ์ ์šฉ: Dummy Node๋Š” ์‚ฌ์šฉ์ž์˜ ํ”ผ๋“œ๋ฐฑ์„ ๋ฐ›์„ ์ˆ˜ ์žˆ๋Š” ๊ตฌ๊ฐ„์„ ์ œ๊ณตํ•˜๋ฉฐ, ๊ทธ๋ž˜ํ”„ ์‹คํ–‰์„ ์ž ์‹œ ์ค‘๋‹จํ•ฉ๋‹ˆ๋‹ค. ์ดํ›„ ์‚ฌ์šฉ์ž๊ฐ€ ์ƒํƒœ ์—…๋ฐ์ดํŠธ๋ฅผ ์š”์ฒญํ•˜๋ฉด, ๊ทธ ํ”ผ๋“œ๋ฐฑ์„ ๋ฐ›์•„ ๊ทธ๋ž˜ํ”„ ์ƒํƒœ์— ๋ฐ˜์˜ํ•ฉ๋‹ˆ๋‹ค.

      • ์‚ฌ์šฉ์ž์˜ ์ž…๋ ฅ(ํ”ผ๋“œ๋ฐฑ)์€ ๊ทธ๋ž˜ํ”„ ์ƒํƒœ์— ์ง์ ‘ ์ฃผ์ž…๋˜์–ด, ๊ทธ ํ›„์— ์ด์–ด์ง€๋Š” ๋…ธ๋“œ์˜ ์‹คํ–‰์— ์˜ํ–ฅ์„ ๋ฏธ์นฉ๋‹ˆ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด ์‚ฌ์šฉ์ž๊ฐ€ ๊ทธ๋ž˜ํ”„ ์‹คํ–‰ ๊ณผ์ •์— ๋™์ ์œผ๋กœ ๊ฐœ์ž…ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

  • ๊ทธ๋ž˜ํ”„ ์ •์˜ : ์œ„ ๊ทธ๋ž˜ํ”„์—๋Š” 3๊ฐœ์˜ ์ฃผ์š” ๋…ธ๋“œ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค:

    1. human_feedback: ์‚ฌ์šฉ์ž๊ฐ€ ํ”ผ๋“œ๋ฐฑ์„ ์ œ๊ณตํ•  ์ˆ˜ ์žˆ๋Š” ๊ฐ€์ƒ์˜ ๋…ธ๋“œ(Dummy Node)์ž…๋‹ˆ๋‹ค.

      • ์ด ๋…ธ๋“œ์—์„œ๋Š” ์•„๋ฌด ์ž‘์—…๋„ ํ•˜์ง€ ์•Š์ง€๋งŒ, ์‚ฌ์šฉ์ž ํ”ผ๋“œ๋ฐฑ์„ ๋ฐ›์„ ์ง€์ ์„ ๋งŒ๋“ญ๋‹ˆ๋‹ค.
    2. assistant: ์‚ฌ์šฉ์ž ์ž…๋ ฅ๊ณผ LLM์„ ์ด์šฉํ•ด ๋„๊ตฌ ํ˜ธ์ถœ ๋“ฑ์˜ ์—ฐ์‚ฐ์„ ์ˆ˜ํ–‰ํ•˜๋Š” ๋…ธ๋“œ์ž…๋‹ˆ๋‹ค.
    3. 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)
      

  1. ์ค‘๋‹จ์  ์ •์˜

    • ์ด ์ฝ”๋“œ์—์„œ๋Š” ์ค‘๋‹จ์ (interrupt)์„ human_feedback ๋…ธ๋“œ์— ์ง€์ •ํ–ˆ์Šต๋‹ˆ๋‹ค. ์ฆ‰, ์ด ๋…ธ๋“œ์— ๋„๋‹ฌํ•  ๋•Œ ๊ทธ๋ž˜ํ”„ ์‹คํ–‰์ด ๋ฉˆ์ถ”๊ณ  ์‚ฌ์šฉ์ž์˜ ํ”ผ๋“œ๋ฐฑ์„ ๊ธฐ๋‹ค๋ฆฌ๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.
    • interrupt_before=["human_feedback"] ๋ถ€๋ถ„์ด ์ด ์—ญํ• ์„ ๋‹ด๋‹นํ•ฉ๋‹ˆ๋‹ค.
      1
      2
      
      # ๊ทธ๋ž˜ํ”„๋ฅผ human_feedback ๋…ธ๋“œ ์ „์— ์ค‘๋‹จ
      graph = builder.compile(interrupt_before=["human_feedback"], checkpointer=memory)
      
  2. ์‹คํ–‰ ์ค‘ ํ”ผ๋“œ๋ฐฑ ๋ฐ›๊ธฐ

    • ๊ทธ๋ž˜ํ”„๋Š” ์ฒ˜์Œ์— ์‚ฌ์šฉ์ž ์ž…๋ ฅ์„ ๋ฐ›์•„ human_feedback ๋…ธ๋“œ๊นŒ์ง€ ์‹คํ–‰๋œ ํ›„ ๋ฉˆ์ถฅ๋‹ˆ๋‹ค.
    • ๊ทธ ํ›„ ์‚ฌ์šฉ์ž์˜ ํ”ผ๋“œ๋ฐฑ์„ ๊ธฐ๋‹ค๋ฆฌ๋ฉฐ, input()์„ ํ†ตํ•ด ํ”ผ๋“œ๋ฐฑ์„ ๋ฐ›์Šต๋‹ˆ๋‹ค.
    • ์‚ฌ์šฉ์ž๊ฐ€ ์ž…๋ ฅํ•œ ํ”ผ๋“œ๋ฐฑ์€ graph.update_state() ๋ฉ”์„œ๋“œ๋ฅผ ํ†ตํ•ด human_feedback ๋…ธ๋“œ์˜ ์ƒํƒœ์— ๋ฐ˜์˜๋ฉ๋‹ˆ๋‹ค. ```

      ์‚ฌ์šฉ์ž๋กœ๋ถ€ํ„ฐ ํ”ผ๋“œ๋ฐฑ ๋ฐ›๊ธฐ

      user_input = input(โ€œ์ƒํƒœ๋ฅผ ์–ด๋–ป๊ฒŒ ์—…๋ฐ์ดํŠธํ•˜๊ณ  ์‹ถ์€์ง€ ๋ง์”€ํ•ด ์ฃผ์„ธ์š”: โ€œ)

    human_feedback ๋…ธ๋“œ์ธ ๊ฒƒ์ฒ˜๋Ÿผ ์ƒํƒœ๋ฅผ ์—…๋ฐ์ดํŠธ

    graph.update_state(thread, {โ€œmessagesโ€: user_input}, as_node=โ€human_feedbackโ€) ```

  1. ์ƒํƒœ ์—…๋ฐ์ดํŠธ ๋ฐ ์‹คํ–‰ ์žฌ๊ฐœ

    • ํ”ผ๋“œ๋ฐฑ์„ ๋ฐ›์€ ํ›„, 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'}

โœจ ๋™์  ์ค‘๋‹จ์ ์˜ ํ•ต์‹ฌ ๊ฐœ๋…

  1. ์ž๋™ ์ค‘๋‹จ์  ์„ค์ •: ๊ทธ๋ž˜ํ”„๋Š” ํŠน์ • ์กฐ๊ฑด(์˜ˆ: ์ž…๋ ฅ ๊ธธ์ด ์ดˆ๊ณผ)์„ ๋งŒ์กฑํ•  ๋•Œ ์ž๋™์œผ๋กœ ๋ฉˆ์ถ”๊ณ , ์ค‘๋‹จ์ ์„ ๋ฐœ์ƒ์‹œํ‚ต๋‹ˆ๋‹ค.

  2. ์ƒํƒœ ํ™•์ธ ๋ฐ ์—…๋ฐ์ดํŠธ: ๊ทธ๋ž˜ํ”„๊ฐ€ ์ค‘๋‹จ๋˜๋ฉด ํ˜„์žฌ ์ƒํƒœ๋ฅผ ํ™•์ธํ•˜๊ณ , ํ•„์š”์‹œ ์ƒํƒœ๋ฅผ ์—…๋ฐ์ดํŠธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

  3. ๊ทธ๋ž˜ํ”„ ์žฌ์‹คํ–‰: ์ƒํƒœ๊ฐ€ ์—…๋ฐ์ดํŠธ๋œ ํ›„, ๊ทธ๋ž˜ํ”„๋Š” ์ค‘๋‹จ๋œ ์‹œ์ ๋ถ€ํ„ฐ ๋‹ค์‹œ ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค.

  4. ์œ ์—ฐํ•œ ์›Œํฌํ”Œ๋กœ์šฐ ๊ตฌ์„ฑ: ๋™์  ์ค‘๋‹จ์ ์„ ์‚ฌ์šฉํ•˜๋ฉด, ์‚ฌ์šฉ์ž์˜ ์Šน์ธ, ๋””๋ฒ„๊น…, ์ƒํƒœ ์ˆ˜์ • ๋“ฑ์„ ์œ ์—ฐํ•˜๊ฒŒ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Lesson 5: Time Travel

๊ฐœ์š”

Time Travel์€ ๊ทธ๋ž˜ํ”„์˜ ์ด์ „ ์ƒํƒœ๋กœ ๋Œ์•„๊ฐ€๊ฑฐ๋‚˜, ๊ทธ ์ƒํƒœ๋ฅผ ์žฌ์ƒํ•˜์—ฌ ๊ณผ๊ฑฐ์˜ ์‹คํ–‰ ํ๋ฆ„์„ ์‚ดํŽด๋ณด๊ณ  ๋ฌธ์ œ๋ฅผ ๋ถ„์„ํ•˜๋Š” ๋ฐ ์œ ์šฉํ•œ ๊ธฐ๋Šฅ์ž…๋‹ˆ๋‹ค.

  • ์ด ๊ธฐ๋Šฅ์€ ๊ทธ๋ž˜ํ”„๊ฐ€ ์‹คํ–‰๋œ ๋ชจ๋“  ์ƒํƒœ๋ฅผ ๊ธฐ๋กํ•˜๊ณ , ๊ทธ ์ค‘ ์–ด๋А ์ง€์ ์—์„œ๋“  ๋‹ค์‹œ ์‹œ์ž‘ํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•ด์ค๋‹ˆ๋‹ค.
  • Time Travel์€ ๋””๋ฒ„๊น…, ์„ฑ๋Šฅ ์ตœ์ ํ™”, ์—ฌ๋Ÿฌ ๊ฒฝ๋กœ ํƒ์ƒ‰ ๋“ฑ์— ๋„์›€์„ ์ค๋‹ˆ๋‹ค.
    • ์žฌ์ƒ(Replay)๊ณผ ๋ถ„๊ธฐ(Fork)์˜ ๋‘ ๊ฐ€์ง€ ํ•ต์‹ฌ ๊ฐœ๋…์„ ํ†ตํ•ด ๊ทธ๋ž˜ํ”„์˜ ์‹คํ–‰ ํ๋ฆ„์„ ์ˆ˜์ •ํ•˜๊ฑฐ๋‚˜ ๋‹ค์–‘ํ•œ ์‹œ๋‚˜๋ฆฌ์˜ค๋ฅผ ์‹คํ—˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ฃผ์š” ๊ฐœ๋…

  1. Replay(์žฌ์ƒ):

    • ํŠน์ • ์ฒดํฌํฌ์ธํŠธ์—์„œ ๊ทธ๋ž˜ํ”„๋ฅผ ๋‹ค์‹œ ์žฌ์ƒํ•˜์—ฌ ๊ณผ๊ฑฐ์˜ ์‹คํ–‰์„ ๋ณต์›ํ•ฉ๋‹ˆ๋‹ค.
    • ๊ทธ๋ž˜ํ”„์˜ ๋ชจ๋“  ์ƒํƒœ๊ฐ€ ์ €์žฅ๋˜๊ธฐ ๋•Œ๋ฌธ์— ์ด์ „์— ์™„๋ฃŒ๋œ ์ž‘์—…์„ ๋ฐ˜๋ณต ์‹คํ–‰ํ•  ํ•„์š” ์—†์ด ๊ทธ ์ƒํƒœ๋ฅผ ๊ทธ๋Œ€๋กœ ์žฌ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
    • ์ด๋Š” ๋ฌธ์ œ๋ฅผ ์žฌํ˜„ํ•˜๊ฑฐ๋‚˜ ๋ถ„์„ํ•  ๋•Œ ๋งค์šฐ ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค.
  2. 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๋Š” ๊ทธ๋ž˜ํ”„ ์‹คํ–‰์ด ์™„๋ฃŒ๋œ ํ›„, ํŠน์ • ์ฒดํฌํฌ์ธํŠธ์—์„œ ๊ทธ๋ž˜ํ”„๋ฅผ ๋‹ค์‹œ ์‹คํ–‰ํ•˜์ง€ ์•Š๊ณ  ๊ทธ ์ƒํƒœ๋ฅผ ๊ทธ๋Œ€๋กœ ์žฌ์ƒํ•˜๋Š” ๊ธฐ๋Šฅ์ž…๋‹ˆ๋‹ค.
    • ์ด๋Š” ๊ณผ๊ฑฐ ์ƒํƒœ์—์„œ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•œ ์ง€์ ์„ ์žฌํ˜„ํ•˜๊ฑฐ๋‚˜, ๋ถ„์„์„ ์œ„ํ•œ ๋””๋ฒ„๊น… ๋ชฉ์ ์œผ๋กœ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค.

  1. Graph.stream(None,{thread_id}): ์ด ํ•จ์ˆ˜๋Š” ํ˜„์žฌ ์ƒํƒœ์—์„œ ๊ทธ๋ž˜ํ”„๋ฅผ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค.
  • ๊ฒฐ๊ณผ๊ฐ’์œผ๋กœ StateSnapshot()์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
  1. 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โ€œ๋ผ๋Š” ์ƒํƒœ๋กœ ์ƒˆ๋กœ์šด ์ฒดํฌํฌ์ธํŠธ๊ฐ€ ๋งŒ๋“ค์–ด์ง‘๋‹ˆ๋‹ค.

  • โ‘ก ๊ณผ๊ฑฐ ์ฒดํฌํฌ์ธํŠธ์—์„œ ํฌํ‚น

    • ํฌํ‚น์˜ ๋˜ ๋‹ค๋ฅธ ๋ฐฉ์‹์€ ๊ณผ๊ฑฐ์˜ ํŠน์ • ์ฒดํฌํฌ์ธํŠธ์—์„œ ์ƒˆ๋กœ์šด ์ƒํƒœ๋ฅผ ํฌํ‚นํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.
    • ์ด๋ฅผ ์œ„ํ•ด, ์šฐ๋ฆฌ๋Š” ํŠน์ • 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๋ฅผ ์ด์šฉํ•œ ๊ณ ๊ธ‰ ์ž‘์—… ํ๋ฆ„์„ ์‰ฝ๊ฒŒ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค.



-->