๐Ÿงญ LangGraph ์™„์ „ ์ •๋ณต โ‘ค โ€” ReAct ํŒจํ„ด๊ณผ LangGraph ์—์ด์ „ํŠธํ™”

okorionยท2025๋…„ 10์›” 4์ผ

โ€œReason โ†’ Act โ†’ Observe โ†’ Repeatโ€
LLM์ด ์Šค์Šค๋กœ ์ƒ๊ฐํ•˜๊ณ  ํ–‰๋™ํ•˜๋Š” ์ž์œจํ˜• ๊ทธ๋ž˜ํ”„์˜ ํƒ„์ƒ.


๐Ÿค– 1. ReAct ํŒจํ„ด์ด๋ž€?

ReAct๋Š” โ€œReasoning + Actingโ€์˜ ํ•ฉ์„ฑ์–ด๋กœ, LLM์ด ์‚ฌ๊ณ (Reason) โ†’ ํ–‰๋™(Act) โ†’ ๊ด€์ฐฐ(Observe) โ†’ ์žฌ์‚ฌ๊ณ (Reflect) ์˜ ์ˆœํ™˜์„ ๋ฐ˜๋ณตํ•˜๋ฉฐ ์Šค์Šค๋กœ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๋„๋ก ์„ค๊ณ„๋œ ๊ตฌ์กฐ์ž…๋‹ˆ๋‹ค.
LangChain์˜ Agent๋„ ์ด ํŒจํ„ด์„ ๊ธฐ๋ฐ˜์œผ๋กœ ํ•˜์ฃ .
LangGraph์—์„œ๋Š” ์ด ๊ณผ์ •์„ ๋ช…์‹œ์  ๊ทธ๋ž˜ํ”„ ํ๋ฆ„์œผ๋กœ ํ‘œํ˜„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.


๐Ÿงฉ 2. ReAct ํŒจํ„ด์˜ ๊ธฐ๋ณธ ๊ตฌ์กฐ

LLM์˜ ๋ฐ˜๋ณต ๋ฃจํ”„๋ฅผ ๊ทธ๋ž˜ํ”„๋กœ ์‹œ๊ฐํ™”ํ•˜๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

์ด ๊ตฌ์กฐ๋ฅผ LangGraph๋กœ ๊ตฌํ˜„ํ•˜๋ฉด, LLM์ด Tool์„ ์‚ฌ์šฉํ•˜๊ณ  ๊ฒฐ๊ณผ๋ฅผ ์Šค์Šค๋กœ ํ‰๊ฐ€ํ•˜์—ฌ ๋‹ค์‹œ ์‹œ๋„ํ•˜๋Š” ์ž์œจํ˜• ๊ทธ๋ž˜ํ”„๊ฐ€ ๋งŒ๋“ค์–ด์ง‘๋‹ˆ๋‹ค.


โš™๏ธ 3. ํ•ต์‹ฌ ๊ตฌ์„ฑ ์š”์†Œ

๊ตฌ์„ฑ ์š”์†Œ์„ค๋ช…์˜ˆ์‹œ
ReasonNodeLLM์ด ๋ฌธ์ œ๋ฅผ ๋ถ„์„ํ•˜๊ณ , ์–ด๋–ค ๋„๊ตฌ๋ฅผ ์“ธ์ง€ ํŒ๋‹จโ€œํ˜„์žฌ ๋‚ ์”จ๋ฅผ ์•Œ๋ ค๋ฉด get_weather๋ฅผ ์จ์•ผ๊ฒ ๋‹ค.โ€
ToolNodeLLM์ด ์„ ํƒํ•œ ๋„๊ตฌ๋ฅผ ์‹คํ–‰get_weather("Seoul")
ObserveNodeTool ๊ฒฐ๊ณผ๋ฅผ ๊ฒ€ํ† ํ•˜๊ณ  ํŒ๋‹จโ€œ๊ฒฐ๊ณผ๊ฐ€ ์ด์ƒํ•˜๋ฉด ๋‹ค์‹œ ์‹œ๋„.โ€
MemorySaver์ƒํƒœ ์ €์žฅ ๋ฐ ๋ฃจํ”„ ์ถ”์ ์ด์ „ reasoning ๋ฐ tool ๊ฒฐ๊ณผ ๋ˆ„์ 

๐Ÿง  4. ReAct ๊ทธ๋ž˜ํ”„ ๊ตฌํ˜„ ์˜ˆ์‹œ

์•„๋ž˜๋Š” โ€œ์งˆ๋ฌธ โ†’ ์‚ฌ๊ณ  โ†’ ํ–‰๋™ โ†’ ๊ด€์ฐฐ โ†’ ๋ฐ˜๋ณตโ€์„ ๊ตฌํ˜„ํ•œ ์ตœ์†Œ ์˜ˆ์‹œ์ž…๋‹ˆ๋‹ค.

from langgraph.graph import StateGraph
from langchain_openai import ChatOpenAI

# ์ƒํƒœ ์ •์˜
class AgentState:
    question: str
    action: str
    observation: str
    retry: int = 0

# Reason ๋…ธ๋“œ
def reason(state: AgentState):
    llm = ChatOpenAI(model="gpt-4o-mini")
    prompt = f"Q: {state.question}\n์–ด๋–ค ํ–‰๋™์„ ํ•ด์•ผ ๋‹ต์„ ์ฐพ์„ ์ˆ˜ ์žˆ์„๊นŒ?"
    res = llm.invoke(prompt)
    state.action = res.content.strip()
    print(f"[Reason] ๊ฒฐ์ •๋œ ํ–‰๋™: {state.action}")
    return state

# Act ๋…ธ๋“œ
def act(state: AgentState):
    if "๋‚ ์”จ" in state.action:
        state.observation = "์„œ์šธ์˜ ๋‚ ์”จ๋Š” ๋ง‘์Œ โ˜€๏ธ"
    else:
        state.observation = "์ ์ ˆํ•œ ๋„๊ตฌ๋ฅผ ์ฐพ์ง€ ๋ชปํ•จ"
    print(f"[Act] ๊ด€์ฐฐ ๊ฒฐ๊ณผ: {state.observation}")
    return state

# Observe ๋…ธ๋“œ
def observe(state: AgentState):
    if "๋ง‘์Œ" in state.observation or state.retry >= 1:
        print("[Observe] ๊ฒฐ๊ณผ ๋งŒ์กฑ. ์ข…๋ฃŒํ•ฉ๋‹ˆ๋‹ค.")
        return "finish"
    print("[Observe] ๊ฒฐ๊ณผ ๋ถˆ๋งŒ์กฑ. ๋‹ค์‹œ Reason ๋‹จ๊ณ„๋กœ.")
    state.retry += 1
    return "reason"

# ๊ทธ๋ž˜ํ”„ ๊ตฌ์„ฑ
graph = StateGraph(AgentState)
graph.add_node("reason", reason)
graph.add_node("act", act)
graph.add_node("observe", observe)

graph.add_edge("reason", "act")
graph.add_edge("act", "observe")
graph.add_conditional_edges("observe", observe)

graph.set_entry_point("reason")
graph.set_finish_point("observe")

app = graph.compile()
app.invoke({"question": "์„œ์šธ์˜ ๋‚ ์”จ ์•Œ๋ ค์ค˜"})

๐Ÿ“ค ์‹คํ–‰ ์˜ˆ์‹œ:

[Reason] ๊ฒฐ์ •๋œ ํ–‰๋™: ๋‚ ์”จ API๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
[Act] ๊ด€์ฐฐ ๊ฒฐ๊ณผ: ์„œ์šธ์˜ ๋‚ ์”จ๋Š” ๋ง‘์Œ โ˜€๏ธ
[Observe] ๊ฒฐ๊ณผ ๋งŒ์กฑ. ์ข…๋ฃŒํ•ฉ๋‹ˆ๋‹ค.

LLM์ด ์Šค์Šค๋กœ โ€œ๋ฌด์—‡์„ ํ•ด์•ผ ํ•˜๋Š”์ง€โ€ ํŒ๋‹จํ•˜๊ณ , ํ–‰๋™ ๊ฒฐ๊ณผ๋ฅผ ๊ด€์ฐฐํ•˜์—ฌ ๋‹ค์Œ ๋‹จ๊ณ„๋ฅผ ๊ฒฐ์ •ํ•˜๋Š” ReAct ๋ฃจํ”„๊ฐ€ ๋งŒ๋“ค์–ด์กŒ์Šต๋‹ˆ๋‹ค.


๐Ÿช„ 5. MemorySaver๋กœ ์ƒํƒœ ์œ ์ง€

MemorySaver๋ฅผ ์ถ”๊ฐ€ํ•˜๋ฉด, ๋ฃจํ”„๊ฐ€ ๋Œ์•„๋„ ์ด์ „ Reason/Act/Observe ๊ฒฐ๊ณผ๋ฅผ ๋ชจ๋‘ ๊ธฐ์–ตํ•ฉ๋‹ˆ๋‹ค.

from langgraph.checkpoint.memory import MemorySaver

memory = MemorySaver()
app = graph.compile(checkpointer=memory)

์ด์ œ ๊ฐ ๋ฃจํ”„๋งˆ๋‹ค ์ƒํƒœ ๋ณ€ํ™”๊ฐ€ ์ถ•์ ๋˜์–ด, โ€œ์™œ ์ด๋Ÿฐ ๊ฒฐ์ •์„ ๋‚ด๋ ธ๋Š”๊ฐ€โ€๋ฅผ ๋‚˜์ค‘์— ์ถ”์ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. (LLM์˜ reasoning trace ๋ถ„์„์—๋„ ๋งค์šฐ ์œ ์šฉ)


๐Ÿงฑ 6. LangGraph์˜ ReAct ๊ตฌํ˜„ ๋ฐฉ์‹ ์š”์•ฝ

๊ตฌ์„ฑ ์š”์†ŒLangGraph ํ‘œํ˜„์˜๋ฏธ
ReasonNode (LLM ํ˜ธ์ถœ)์‚ฌ๊ณ  ๋‹จ๊ณ„
ActToolNodeํ–‰๋™ ๋‹จ๊ณ„
ObserveConditional Edgeํ”ผ๋“œ๋ฐฑ / ๋ฃจํ”„ ์ œ์–ด
MemorySaverCheckpoint์ƒํƒœ ์ง€์†
LoopEdge ์ˆœํ™˜ReAct ๋ฃจํ”„ ๊ตฌํ˜„

LangChain์—์„œ๋Š” โ€œ์ž๋™โ€์œผ๋กœ ๋Œ์•„๊ฐ€๋˜ ๋ฃจํ”„๋ฅผ LangGraph์—์„œ๋Š” ๋ช…์‹œ์ ์œผ๋กœ ์ฝ”๋“œ๋กœ ์ œ์–ดํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.


โšก๏ธ 7. ์‘์šฉ ์˜ˆ์‹œ: ๋‹ค๋‹จ๊ณ„ ์ถ”๋ก  ์—์ด์ „ํŠธ

์˜ˆ๋ฅผ ๋“ค์–ด, โ€œ์„œ์šธ์˜ ๋‚ ์”จ์™€ ํ˜„์žฌ ํ™˜์œจ์„ ํ•จ๊ป˜ ์•Œ๋ ค์ค˜โ€ ๊ฐ™์€ ์š”์ฒญ์€ ReAct ๊ทธ๋ž˜ํ”„๋ฅผ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ํ™•์žฅํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๊ฐ ToolNode๋Š” ๋ณ‘๋ ฌ ์‹คํ–‰ ๊ฐ€๋Šฅํ•˜๋ฉฐ, LLM์€ ์ƒํ™ฉ์— ๋”ฐ๋ผ ์–ด๋–ค Tool์„ ํ˜ธ์ถœํ• ์ง€ ๊ฒฐ์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. (์ด๋ฅผ ์œ„ํ•ด โ‘ฃํŽธ์—์„œ ๋ฐฐ์šด ToolCondition ๊ตฌ์กฐ๊ฐ€ ๊ทธ๋Œ€๋กœ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค.)


๐Ÿงญ 8. ๋‹ค์Œ ํšŒ์ฐจ ์˜ˆ๊ณ 

๐Ÿ‘‰ 6ํŽธ: ๊ทธ๋ž˜ํ”„ ๊ธฐ๋ฐ˜ RAG (๊ฒ€์ƒ‰+์ƒ์„ฑ ์‹œ์Šคํ…œ)
ReAct ๊ตฌ์กฐ๋ฅผ ํ™•์žฅํ•˜์—ฌ โ€œ์งˆ๋ฌธ โ†’ ๊ฒ€์ƒ‰ โ†’ ์ƒ์„ฑ โ†’ ํ‰๊ฐ€ โ†’ ์ˆ˜์ •โ€์œผ๋กœ ์ด์–ด์ง€๋Š”
Adaptive / Self / Corrective RAG ๊ทธ๋ž˜ํ”„๋ฅผ ์„ค๊ณ„ํ•ฉ๋‹ˆ๋‹ค.


๐ŸŽ“ 9. ๋” ๊นŠ์ด ๋ฐฐ์šฐ๊ธฐ ์œ„ํ•œ ๊ณ ๊ธ‰ ํ™•์žฅ ํ•™์Šต ๊ฐ€์ด๋“œ

์ฃผ์ œํ•™์Šต ์ด์œ ์ถ”์ฒœ ํ•™์Šต ๋ฐฉํ–ฅ
Cognitive ArchitectureReAct์˜ ๊ทผ๋ณธ ์›๋ฆฌ (ACT-R, Soar ๋“ฑ) ์ดํ•ด์ธ์ง€ ๋ชจ๋ธ ๊ธฐ๋ฐ˜ LLM ๋ฃจํ”„ ์„ค๊ณ„
LangGraph MemorySaver Internals์ƒํƒœ ์ฒดํฌํฌ์ธํŠธ์˜ ๊ตฌ์กฐ์™€ ๋กœ๊น… ์ดํ•ดMemorySaver + graph.get_state()
LLM Reasoning Trace ๋ถ„์„์‚ฌ๊ณ  ๊ณผ์ • ์‹œ๊ฐํ™” ๋ฐ ํ‰๊ฐ€Reasoning log / reflection system
Multi-Tool Planning์—ฌ๋Ÿฌ Tool ์ค‘ ์„ ํƒ์  ํ˜ธ์ถœ ์„ค๊ณ„PolicyNode / ToolCondition ๊ณ ๊ธ‰ ํŒจํ„ด
Self-Reflective AgentLLM ์Šค์Šค๋กœ reasoning ํ‰๊ฐ€Reflection node + critic model
LangGraph + LangServeReAct ์—์ด์ „ํŠธ๋ฅผ API ์„œ๋น„์Šคํ™”REST + StreamLog ํ†ตํ•ฉ

๐Ÿ“š ํ•ต์‹ฌ ์š”์•ฝ

  • ReAct๋Š” โ€œReason โ†’ Act โ†’ Observeโ€์˜ ๋ฃจํ”„ ๊ตฌ์กฐ๋กœ LLM์˜ ์ž์œจ์„ฑ์„ ๊ตฌํ˜„ํ•œ๋‹ค.
  • LangGraph์—์„œ๋Š” ์ด ๋ฃจํ”„๋ฅผ ๋ช…์‹œ์  ๊ทธ๋ž˜ํ”„๋กœ ํ‘œํ˜„ํ•˜๊ณ ,
    MemorySaver๋กœ reasoning trace๋ฅผ ๊ด€๋ฆฌํ•œ๋‹ค.
  • ์ด ๊ตฌ์กฐ๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ RAG, ํ‰๊ฐ€ ๋ฃจํ”„, ์ž์œจํ˜• ์—์ด์ „ํŠธ ๋“ฑ์œผ๋กœ ํ™•์žฅํ•  ์ˆ˜ ์žˆ๋‹ค.

๐Ÿ’ฌ ์ด์ œ ์šฐ๋ฆฌ๋Š” โ€œ๋ช…๋ น๋ฐ›๋Š” LLMโ€์ด ์•„๋‹Œ, โ€œ์Šค์Šค๋กœ ์‚ฌ๊ณ ํ•˜๋Š” ์‹œ์Šคํ…œโ€์„ ๋‹ค๋ฃฐ ์ˆ˜ ์žˆ๋‹ค.

profile
okorion's Tech Study Blog.

0๊ฐœ์˜ ๋Œ“๊ธ€