LangGraph - Tool Binding과 Super-Step 이해하기

twonezero·2026년 2월 4일

📚 시작하며

LangGraph를 학습하면서 가장 중요하게 이해해야 할 개념들을 정리했습니다. 특히 Tool Binding된 LLM 노드를 추가하는 방법과 Super-Step의 개념, 그리고 이것이 메모리 관리와 어떻게 연결되는지를 중심으로 기록했습니다.


🔧 Tool Binding - LLM에 도구를 연결하기

Tool의 기본 구조

LangChain은 일반 함수를 Tool로 변환할 수 있는 래퍼 클래스를 제공합니다. 예를 들어 검색 기능을 Tool로 만드는 과정은 다음과 같습니다.

from langchain.agents import Tool
from langchain_community.utilities import GoogleSerperAPIWrapper

serper = GoogleSerperAPIWrapper()
tool_search = Tool(
    name="search",
    func=serper.run,
    description="Useful for when you need more information from an online search"
)

이렇게 생성된 Tool은 독립적으로 호출할 수 있습니다.

tool_search.invoke("What is the capital of France?")

LLM에 Tool 바인딩하기

Tool을 구현할 때는 항상 2가지 변경사항이 필요합니다:

  1. OpenAI 호출 시 Tool을 JSON 형식으로 제공
  2. 응답 처리: finish_reason=="tool_calls"를 확인하고 함수를 실행한 후 결과 제공

LangGraph에서는 bind_tools() 메서드를 사용하여 LLM에 Tool을 바인딩합니다.

from langchain_openai import ChatOpenAI

tools = [tool_search]

llm = ChatOpenAI(model="gpt-4o-mini")
llm_with_tools = llm.bind_tools(tools)

💡
llm.bind_tools()는 LLM이 필요할 때 특정 도구를 호출할 수 있도록 연결해주는 핵심 메서드입니다. 이를 통해 LLM은 단순히 텍스트를 생성하는 것을 넘어 실제 기능을 수행할 수 있게 됩니다.


🔄 Graph 구조에서 Tool Node 추가하기

State 정의

from typing import Annotated, TypedDict
from langgraph.graph.message import add_messages

class State(TypedDict):
    messages: Annotated[list, add_messages]

TypedDict를 사용하여 State 객체를 정의했습니다. add_messages는 메시지 리스트를 관리하는 reducer입니다.

Graph Builder와 Node 추가

from langgraph.graph import StateGraph, START
from langgraph.prebuilt import ToolNode, tools_condition

# Step 1-2: Graph Builder 초기화
graph_builder = StateGraph(State)

# Step 3: Chatbot Node 추가
def chatbot(state: State):
    return {"messages": [llm_with_tools.invoke(state["messages"])]}

graph_builder.add_node("chatbot", chatbot)
graph_builder.add_node("tools", ToolNode(tools=tools))

# Step 4: Edge 연결
graph_builder.add_conditional_edges("chatbot", tools_condition, "tools")
graph_builder.add_edge("tools", "chatbot")
graph_builder.add_edge(START, "chatbot")

# Step 5: Compile
graph = graph_builder.compile()

💡
ToolNode는 LangGraph에서 제공하는 prebuilt 노드로, Tool 실행을 자동으로 처리합니다. tools_condition은 LLM이 Tool을 호출해야 하는지 판단하는 조건부 분기 입니다.


⚡ Super-Step - LangGraph의 핵심 개념

왜 State가 메모리를 자동으로 처리하지 않을까?

Graph가 State를 유지하고 append하고 있는데, 왜 이를 통해 메모리를 자동으로 관리하지 않을까요? 이것이 LangGraph를 이해하는 핵심 포인트입니다.

Super-Step의 정의

💡
Super-Step은 그래프 노드들의 단일 반복(iteration)으로 간주됩니다.
병렬로 실행되는 노드들은 같은 Super-Step에 속하고, 순차적으로 실행되는 노드들은 서로 다른 Super-Step에 속합니다.

관용적인 LangGraph 사용법

관용적인 LangGraph에서는 각 Super-Step마다, 즉 각 상호작용마다 invoke를 호출합니다.

  • Reducer는 하나의 Super-Step 내에서 자동으로 State 업데이트를 처리합니다.
  • 하지만 Super-Step 사이에서는 자동으로 처리하지 않습니다.

바로 이것이 체크포인팅(Checkpointing)이 달성하는 목표입니다.

💡
Super-Step의 개념을 이해하지 못하면 LangGraph의 메모리 관리와 State 전파 메커니즘을 제대로 활용할 수 없습니다. 각 invoke 호출이 하나의 Super-Step을 의미한다는 점을 명심해야 합니다.


💾 Checkpointing으로 메모리 구현하기

MemorySaver를 사용한 기본 메모리

from langgraph.checkpoint.memory import MemorySaver

memory = MemorySaver()

# Graph 컴파일 시 checkpointer 추가
graph = graph_builder.compile(checkpointer=memory)

Config를 통한 Thread 관리

config = {"configurable": {"thread_id": "1"}}

def chat(user_input: str, history):
    result = graph.invoke(
        {"messages": [{"role": "user", "content": user_input}]}, 
        config=config
    )
    return result["messages"][-1].content

State History 조회

# 현재 State 확인
graph.get_state(config)

# State 히스토리 조회 (최신순)
list(graph.get_state_history(config))

💡
LangGraph는 특정 시점으로 되돌아가거나 분기할 수 있는 도구를 제공합니다:

config = {"configurable": {"thread_id": "1", "checkpoint_id": ...}}
graph.invoke(None, config=config)

이를 통해 이전 체크포인트에서 복구하고 재실행할 수 있는 안정적인 시스템을 구축할 수 있습니다.


🗄️ SQL 기반 영구 메모리

SQLite를 이용해 기억하기

인메모리 저장소 대신 SQLite 데이터베이스를 사용하면 영구적인 메모리 관리가 가능합니다.

import sqlite3
from langgraph.checkpoint.sqlite import SqliteSaver

db_path = "memory.db"
conn = sqlite3.connect(db_path, check_same_thread=False)
sql_memory = SqliteSaver(conn)

# Graph에 SQL 메모리 적용
graph = graph_builder.compile(checkpointer=sql_memory)
config = {"configurable": {"thread_id": "3"}}

def chat(user_input: str, history):
    result = graph.invoke(
        {"messages": [{"role": "user", "content": user_input}]}, 
        config=config
    )
    return result["messages"][-1].content

💡
단순한 인메모리 상태 관리를 넘어, 데이터베이스 기반의 영구적이고 반복 가능하며 견고한 시스템을 구축할 수 있습니다. 이를 통해 에이전트 시스템에 자연스러운 기억력을 심어줄 수 있습니다.


📝 핵심 요약

LangGraph의 주요 구성 요소

  1. State - 현재 앱의 스냅샷을 표현하는 불변 객체

    • Reducer를 통해 State 업데이트
    • 여러 노드의 동시 실행 가능
  2. Nodes - 실제 작업을 수행하는 함수 단위

    • Chatbot Node: LLM 호출 담당
    • Tool Node: 도구 실행 담당
  3. Edges - 노드 간 흐름 제어

    • Conditional Edges: 조건부 분기
    • Normal Edges: 고정된 전환

Tool Binding 핵심 포인트

  • llm.bind_tools(tools)로 LLM에 도구 연결
  • ToolNodetools_condition으로 자동 처리
  • LLM이 필요시 적절한 도구를 선택하여 실행

Super-Step 이해

  • 하나의 Super-Step = 하나의 invoke 호출
  • Reducer는 Super-Step 내에서만 자동 작동
  • Super-Step 간 상태 유지는 Checkpointing이 담당

Checkpointing의 중요성

  • MemorySaver: 인메모리 임시 저장
  • SqliteSaver: 영구 데이터베이스 저장
  • Thread ID로 대화 세션 관리
  • 특정 체크포인트로 복구 및 분기 가능

🎯 마치며

LangGraph는 단순한 상태 관리 라이브러리가 아니라, 복잡한 에이전트 시스템을 구축하기 위한 강력한 프레임워크입니다. Tool Binding을 통해 LLM에 실제 기능을 부여하고, Super-Step 개념으로 상태 전파를 제어하며, Checkpointing으로 견고하고 반복 가능한 시스템을 만들 수 있습니다.

특히 Super-Step과 Checkpointing의 관계를 정확히 이해하는 것이 LangGraph를 제대로 활용하는 핵심입니다. Reducer가 자동으로 모든 것을 처리해줄 것이라는 착각에서 벗어나, 명시적인 체크포인팅을 통해 상태를 관리해야 합니다.

이러한 개념들을 바탕으로 실제 프로덕션 환경에서 사용 가능한 에이전트 시스템을 구축할 수 있을 것입니다.

profile
I Enjoy Learn-and-Run Vibe😊

0개의 댓글