๐Ÿงญ LangGraph ์™„์ „ ์ •๋ณต โ‘ฃ โ€” LangGraph์™€ Tool์˜ ์—ฐ๊ฒฐ

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

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


โš™๏ธ 1. Tool์ด๋ž€ ๋ฌด์—‡์ธ๊ฐ€?

LangGraph์—์„œ Tool์€ โ€œLLM์ด ์ง์ ‘ ํ˜ธ์ถœ ๊ฐ€๋Šฅํ•œ ์™ธ๋ถ€ ํ•จ์ˆ˜ํ˜• ๋ฆฌ์†Œ์Šคโ€์ž…๋‹ˆ๋‹ค.
์ฆ‰, LLM์ด ์ž์—ฐ์–ด๋กœ ๋ฌธ์ œ๋ฅผ ์ดํ•ดํ•˜๊ณ , ๊ทธ์— ๋”ฐ๋ผ Python ํ•จ์ˆ˜๋‚˜ API๋ฅผ ์ง์ ‘ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•˜๋Š” ์—ฐ๊ฒฐ ๋‹จ์œ„์˜ˆ์š”.

์˜ˆ๋ฅผ ๋“ค์–ด LLM์ด โ€œ์„œ์šธ์˜ ๋‚ ์”จ ์•Œ๋ ค์ค˜โ€๋ผ๊ณ  ํ–ˆ์„ ๋•Œ,
โ†’ get_weather("Seoul") ํ•จ์ˆ˜๋ฅผ ์ง์ ‘ ์‹คํ–‰ํ•˜๋Š” ๊ฒƒ์ด ๋ฐ”๋กœ Tool ํ˜ธ์ถœ์ž…๋‹ˆ๋‹ค.


๐Ÿ”ฉ 2. LangChain๊ณผ์˜ ์ฐจ์ด

๋น„๊ต ํ•ญ๋ชฉLangChain AgentLangGraph ToolNode
ํ˜ธ์ถœ ๋ฐฉ์‹LLM์ด ReAct ํŒจํ„ด์„ ํ†ตํ•ด Tool ํ˜ธ์ถœ๊ทธ๋ž˜ํ”„ ๋‚ด์—์„œ Tool์ด ํ•˜๋‚˜์˜ Node๋กœ ํ‘œํ˜„
์ œ์–ด ๋ฐฉ์‹์ถ”์ƒ์  (์—์ด์ „ํŠธ๊ฐ€ ์•Œ์•„์„œ)๋ช…์‹œ์  (๊ฐœ๋ฐœ์ž๊ฐ€ ํ๋ฆ„ ์„ค๊ณ„)
์žฅ์ ๋น ๋ฅธ ์‹œ์ž‘๊ตฌ์กฐ์  ๋””๋ฒ„๊น…, ์กฐ๊ฑด ์ œ์–ด
์˜ˆ์‹œinitialize_agent(tools, llm)graph.add_node("weather_tool", tool_func)

LangGraph๋Š” โ€œ์—์ด์ „ํŠธ์˜ ํ–‰๋™(Act)โ€์„ ๊ฐœ๋ฐœ์ž๊ฐ€ ์ง์ ‘ ์„ค๊ณ„ํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ค๋‹ˆ๋‹ค.
์ฆ‰, ์ž๋™ํ™”๋ณด๋‹ค ํ†ต์ œ ๊ฐ€๋Šฅํ•œ ์ž์œจ์„ฑ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.


๐Ÿง  3. ๊ธฐ๋ณธ ์˜ˆ์‹œ: ๋‚ ์”จ API Tool ์—ฐ๊ฒฐ

from langgraph.graph import StateGraph
from langchain_openai import ChatOpenAI
import requests

# ์ƒํƒœ ์ •์˜
class WeatherState:
    city: str
    report: str

# Tool (์™ธ๋ถ€ API ํ˜ธ์ถœ)
def get_weather(state: WeatherState):
    city = state.city
    # ์‹ค์ œ๋กœ๋Š” OpenWeather API ๋“ฑ ํ˜ธ์ถœ ๊ฐ€๋Šฅ
    fake_response = {"Seoul": "๋ง‘์Œ โ˜€๏ธ", "Busan": "ํ๋ฆผ ๐ŸŒฅ๏ธ"}
    state.report = fake_response.get(city, "์ •๋ณด ์—†์Œ")
    print(f"[Tool ํ˜ธ์ถœ] {city} ๋‚ ์”จ: {state.report}")
    return state

# LLM ๋…ธ๋“œ
def ask_user(state: WeatherState):
    llm = ChatOpenAI(model="gpt-4o-mini")
    prompt = f"{state.city}์˜ ๋‚ ์”จ๋ฅผ ์š”์•ฝํ•ด์„œ ๋งํ•ด์ค˜: {state.report}"
    reply = llm.invoke(prompt)
    print(f"[LLM ์‘๋‹ต] {reply.content}")
    return state

# ๊ทธ๋ž˜ํ”„ ๊ตฌ์„ฑ
graph = StateGraph(WeatherState)
graph.add_node("weather_tool", get_weather)
graph.add_node("llm_summary", ask_user)
graph.add_edge("weather_tool", "llm_summary")

graph.set_entry_point("weather_tool")
graph.set_finish_point("llm_summary")

app = graph.compile()
app.invoke({"city": "Seoul"})

๐Ÿงฉ ์‹คํ–‰ ๊ฒฐ๊ณผ ์˜ˆ์‹œ:

[Tool ํ˜ธ์ถœ] Seoul ๋‚ ์”จ: ๋ง‘์Œ โ˜€๏ธ
[LLM ์‘๋‹ต] ์„œ์šธ์˜ ๋‚ ์”จ๋Š” ๋ง‘์œผ๋ฉฐ, ์ข‹์€ ๋‚ ์”จ์ž…๋‹ˆ๋‹ค.

์ด๋ ‡๊ฒŒ LangGraph์—์„œ๋Š” LLM๊ณผ Tool์˜ ํ˜ธ์ถœ ์ˆœ์„œ๋ฅผ ๋ช…์‹œ์ ์œผ๋กœ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค.


๐Ÿ”€ 4. ์—ฌ๋Ÿฌ ToolNode ๋ณ‘๋ ฌ ํ˜ธ์ถœํ•˜๊ธฐ

LangGraph์—์„œ๋Š” ์—ฌ๋Ÿฌ ToolNode๋ฅผ ๋ณ‘๋ ฌ ์‹คํ–‰ํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.
์˜ˆ๋ฅผ ๋“ค์–ด, โ€œ๋‚ ์”จ + ๋‰ด์Šค + ํ™˜์œจโ€์„ ๋™์‹œ์— ๊ฐ€์ ธ์˜ค๋Š” ๊ตฌ์กฐ์ฃ .

from langgraph.graph import StateGraph
import asyncio

class MultiState:
    weather: str
    news: str
    fx: str

async def get_weather(state): state.weather = "๋ง‘์Œ"; return state
async def get_news(state): state.news = "๊ตญ์ œ ๋‰ด์Šค ์—…๋ฐ์ดํŠธ"; return state
async def get_fx(state): state.fx = "1 USD = 1,350 KRW"; return state

async def merge_result(state):
    print(f"{state.weather} / {state.news} / {state.fx}")
    return state

graph = StateGraph(MultiState)
graph.add_node("weather", get_weather)
graph.add_node("news", get_news)
graph.add_node("fx", get_fx)
graph.add_node("merge", merge_result)

# ๋ณ‘๋ ฌ ์‹คํ–‰ ์—ฃ์ง€ ๊ตฌ์„ฑ
graph.add_edge("weather", "merge")
graph.add_edge("news", "merge")
graph.add_edge("fx", "merge")

graph.set_entry_point("weather")  # ๋ณ‘๋ ฌ ์‹œ์ž‘์ 
graph.set_finish_point("merge")

app = graph.compile()
asyncio.run(app.ainvoke({}))

๐Ÿ“Š Mermaid ์‹œ๊ฐํ™”:

โœ… ToolNode๋Š” ๋น„๋™๊ธฐ(async) ํ•จ์ˆ˜๋กœ ์ •์˜ํ•˜๋ฉด ์ž๋™ ๋ณ‘๋ ฌ ์‹คํ–‰์ด ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.


๐Ÿงฉ 5. LangChain Runnable๊ณผ ํ†ตํ•ฉ

LangGraph์˜ ๋…ธ๋“œ๋Š” Runnable ๊ฐ์ฒด๋ฅผ ๊ทธ๋Œ€๋กœ ์‚ฌ์šฉํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.
์ฆ‰, LangChain์—์„œ ์ด๋ฏธ ๋งŒ๋“  ์ฒด์ธยทToolยทLLM์„ ๊ทธ๋ž˜ํ”„์— ์‚ฝ์ž… ๊ฐ€๋Šฅํ•˜์ฃ .

from langchain.tools import Tool
from langgraph.graph import StateGraph
from langchain_openai import ChatOpenAI

# LangChain Tool ์ •์˜
def calc(x, y): return x + y
sum_tool = Tool(name="add", func=calc, description="๋‘ ์ˆ˜๋ฅผ ๋”ํ•จ")

class CalcState:
    result: int

def run_tool(state: CalcState):
    state.result = sum_tool.run(5, 7)
    return state

def summarize(state: CalcState):
    llm = ChatOpenAI(model="gpt-4o-mini")
    reply = llm.invoke(f"7๊ณผ 5๋ฅผ ๋”ํ•˜๋ฉด {state.result}์ž…๋‹ˆ๋‹ค. ํ•œ ์ค„ ์š”์•ฝํ•ด์ค˜.")
    print(reply.content)
    return state

graph = StateGraph(CalcState)
graph.add_node("tool", run_tool)
graph.add_node("summary", summarize)
graph.add_edge("tool", "summary")
graph.set_entry_point("tool")
graph.set_finish_point("summary")
app = graph.compile()
app.invoke({})

์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ๊ธฐ์กด LangChain ์ž์‚ฐ์„ ๊ทธ๋Œ€๋กœ ์žฌ์‚ฌ์šฉํ•˜๋ฉด์„œ LangGraph์˜ ๊ตฌ์กฐ์  ์ œ์–ด๋ฅผ ์–น์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.


๐Ÿงฑ 6. ์กฐ๊ฑด๋ถ€ Tool ํ˜ธ์ถœ (ToolCondition)

LLM์˜ ํŒ๋‹จ์— ๋”ฐ๋ผ ํŠน์ • Tool๋งŒ ์‹คํ–‰ํ•˜๋„๋ก ํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.

def choose_tool(state):
    # LLM์ด ํˆด ์„ ํƒ ๋กœ์ง ์ˆ˜ํ–‰ํ–ˆ๋‹ค๊ณ  ๊ฐ€์ •
    state.tool_choice = "weather" if "๋‚ ์”จ" in state.query else "news"
    return state

graph.add_node("chooser", choose_tool)
graph.add_conditional_edges("chooser", lambda s: s.tool_choice)

์ด ๋ฐฉ์‹์€ ๋‹ค์Œ ํšŒ์ฐจ์˜ ReAct ๊ทธ๋ž˜ํ”„ ์„ค๊ณ„์˜ ๊ธฐ์ดˆ๊ฐ€ ๋ฉ๋‹ˆ๋‹ค. (LLM์ด ์Šค์Šค๋กœ โ€œ์–ด๋–ค ๋„๊ตฌ๋ฅผ ์–ธ์ œ ์“ธ์ง€โ€ ๊ฒฐ์ •)


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

๐Ÿ‘‰ 5ํŽธ: ReAct ํŒจํ„ด๊ณผ LangGraph ์—์ด์ „ํŠธํ™”

  • LLM์˜ Reasoning โ†’ Action โ†’ Observation ๋ฃจํ”„ ์„ค๊ณ„
  • ToolCondition + MemorySaver๋ฅผ ํ™œ์šฉํ•œ ์ž์œจ์  ์˜์‚ฌ๊ฒฐ์ • ํ๋ฆ„ ๊ตฌํ˜„

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

์ฃผ์ œํ•™์Šต ์ด์œ ์ถ”์ฒœ ํ•™์Šต ๋ฐฉํ–ฅ
LangChain Runnable ์ฒด๊ณ„LangGraph ๋‚ด๋ถ€ ํ˜ธ์ถœ ๊ตฌ์กฐ์˜ ํ•ต์‹ฌRunnableLambda, RunnableParallel, RunnableMap
Async & Await ๋ณ‘๋ ฌ Tool ์„ค๊ณ„์—ฌ๋Ÿฌ API Tool ๋ณ‘๋ ฌ ์‹คํ–‰asyncio, await app.ainvoke()
Tool ์„ ํƒ ๋…ผ๋ฆฌ(Policy Design)LLM์˜ โ€œ์–ด๋–ค ๋„๊ตฌ๋ฅผ ์“ธ์ง€โ€ ํŒ๋‹จ ์ œ์–ดToolCondition, LLM ์„ ํƒ ํ”„๋กฌํ”„ํŠธ
Custom ToolKit ๊ตฌ์„ฑํ”„๋กœ์ ํŠธ๋ณ„ Tool ์„ธํŠธ ์„ค๊ณ„โ€œ๊ฒ€์ƒ‰, ์š”์•ฝ, ๊ณ„์‚ฐ, DBโ€ ๋“ฑ ๊ธฐ๋Šฅํ™”
Graph Logging & VisualizationTool ์‹คํ–‰ ํ๋ฆ„ ์ถ”์ graph.visualize("toolflow.png"), ์ƒํƒœ ๋กœ๊ทธ
LangGraph + LangServeTool ํฌํ•จ ๊ทธ๋ž˜ํ”„๋ฅผ ์„œ๋น„์Šคํ™”REST API / WebSocket ๊ธฐ๋ฐ˜ ๋ฐฐํฌ

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

  • ToolNode๋Š” โ€œLLM์˜ ์†โ€์ด๋‹ค.
  • LangGraph์—์„œ Tool์„ ๋…ธ๋“œ๋กœ ์—ฐ๊ฒฐํ•˜๋ฉด, LLM์ด ์ง์ ‘ APIยทํ•จ์ˆ˜ยท๋ฐ์ดํ„ฐ๋ฅผ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.
  • ๋ณ‘๋ ฌ ํ˜ธ์ถœ, ์กฐ๊ฑด ํ˜ธ์ถœ, Runnable ํ†ตํ•ฉ์œผ๋กœ ์‹ค์ „ ์—์ด์ „ํŠธ ์•„ํ‚คํ…์ฒ˜๊ฐ€ ์™„์„ฑ๋œ๋‹ค.

๐Ÿ’ก ๋‹ค์Œ์€ LLM์ด ์Šค์Šค๋กœ โ€œ์–ด๋–ค ๋„๊ตฌ๋ฅผ ์“ธ์ง€โ€ ๊ฒฐ์ •ํ•˜๊ณ  ํ”ผ๋“œ๋ฐฑ์„ ํ†ตํ•ด ์ˆ˜์ •ํ•˜๋Š” ๋‹จ๊ณ„๋‹ค.

profile
okorion's Tech Study Blog.

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