from dotenv import load_dotenv
 
 
load_dotenv(".env", override=True)

๐Ÿง ๐Ÿค–Deep Agents

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

โ€œDeep Researchโ€, โ€œManusโ€, โ€œClaude Codeโ€์™€ ๊ฐ™์€ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์€ ๋„ค ๊ฐ€์ง€ ๊ธฐ๋Šฅ์˜ ์กฐํ•ฉ์„ ๊ตฌํ˜„ํ•˜์—ฌ ์ด ํ•œ๊ณ„๋ฅผ ๊ทน๋ณตํ–ˆ์Šต๋‹ˆ๋‹ค: ๊ณ„ํš ๋„๊ตฌ, ํ•˜์œ„ ์—์ด์ „ํŠธ, ํŒŒ์ผ ์‹œ์Šคํ…œ ์ ‘๊ทผ, ๊ทธ๋ฆฌ๊ณ  ์ƒ์„ธํ•œ ํ”„๋กฌํ”„ํŠธ.

deep agent

deepagents๋Š” ์ผ๋ฐ˜์ ์ธ ์šฉ๋„๋กœ ์ด๋“ค์„ ๊ตฌํ˜„ํ•˜๋Š” Python ํŒจํ‚ค์ง€์ด๋ฏ€๋กœ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์œ„ํ•ด Deep Agent๋ฅผ ์‰ฝ๊ฒŒ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

deepagents์˜ ์ „์ฒด ๊ฐœ์š”์™€ ๋น ๋ฅธ ์‹œ์ž‘์„ ์œ„ํ•ด์„œ๋Š” Deep Agents overview ๋ฌธ์„œ๋ฅผ ์ฐธ๊ณ ํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค.

์„ค์น˜

pip install deepagents tavily-python

์‚ฌ์šฉ๋ฒ•

ํ™˜๊ฒฝ์— TAVILY_API_KEY๋ฅผ ์„ค์ •ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์—ฌ๊ธฐ์—์„œ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

import os
from typing import Literal
 
from deepagents import create_deep_agent
from tavily import TavilyClient
 
 
tavily_client = TavilyClient(api_key=os.environ["TAVILY_API_KEY"])
 
 
# ์›น ๊ฒ€์ƒ‰ ๋„๊ตฌ
def internet_search(
    query: str,
    max_results: int = 5,
    topic: Literal["general", "news", "finance"] = "general",
    include_raw_content: bool = False,
):
    """์›น ๊ฒ€์ƒ‰ ์‹คํ–‰"""
    return tavily_client.search(
        query,
        max_results=max_results,
        include_raw_content=include_raw_content,
        topic=topic,
    )
 
 
# ์—์ด์ „ํŠธ๋ฅผ ์ „๋ฌธ ์—ฐ๊ตฌ์›์œผ๋กœ ์œ ๋„ํ•˜๋Š” ์‹œ์Šคํ…œ ํ”„๋กฌํ”„ํŠธ
research_instructions = """๋‹น์‹ ์€ ์ „๋ฌธ ์—ฐ๊ตฌ์›์ž…๋‹ˆ๋‹ค. ๋‹น์‹ ์˜ ์—ญํ• ์€ ์ฒ ์ €ํ•œ ์—ฐ๊ตฌ๋ฅผ ์ˆ˜ํ–‰ํ•˜๊ณ  ์ •๊ตํ•œ ๋ณด๊ณ ์„œ๋ฅผ ์ž‘์„ฑํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.
 
์ธํ„ฐ๋„ท ๊ฒ€์ƒ‰ ๋„๊ตฌ์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ์ด๊ฒƒ์ด ์ •๋ณด ์ˆ˜์ง‘์˜ ์ฃผ์š” ์ˆ˜๋‹จ์ž…๋‹ˆ๋‹ค.
 
## `internet_search`
 
์ฃผ์–ด์ง„ ์ฟผ๋ฆฌ์— ๋Œ€ํ•ด ์ธํ„ฐ๋„ท ๊ฒ€์ƒ‰์„ ์‹คํ–‰ํ•˜๋ ค๋ฉด ์ด ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ๋ฐ˜ํ™˜ํ•  ๊ฒฐ๊ณผ์˜ ์ตœ๋Œ€ ์ˆ˜, ์ฃผ์ œ, ์›๋ณธ ์ฝ˜ํ…์ธ  ํฌํ•จ ์—ฌ๋ถ€๋ฅผ ์ง€์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
"""
 
# Deep Agent ์ƒ์„ฑ
agent = create_deep_agent(
    tools=[internet_search],
    system_prompt=research_instructions,
)
agent

from utils import format_messages
 
 
# ์—์ด์ „ํŠธ ํ˜ธ์ถœ
result = agent.invoke(
    {"messages": [{"role": "user", "content": "langgraph๋ž€ ๋ฌด์—‡์ธ๊ฐ€์š”?"}]}
)
format_messages(result)

๋” ๋ณต์žกํ•œ ์˜ˆ์ œ๋Š” research_agent.py๋ฅผ ์ฐธ๊ณ ํ•˜์„ธ์š”.

create_deep_agent๋กœ ๋งŒ๋“  ์—์ด์ „ํŠธ๋Š” ๋‹จ์ˆœํ•œ LangGraph ๊ทธ๋ž˜ํ”„์ด๋ฏ€๋กœ, LangGraph ์—์ด์ „ํŠธ์™€ ๊ฐ™์€ ๋ฐฉ์‹์œผ๋กœ(์ŠคํŠธ๋ฆฌ๋ฐ, ํœด๋จผ-์ธ-๋”-๋ฃจํ”„, ๋ฉ”๋ชจ๋ฆฌ, ์ŠคํŠœ๋””์˜ค) ์ƒํ˜ธ์ž‘์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ฐธ๊ณ : create_deep_agent๋Š” ๋ชจ๋ธ๋ช…์„ ์ƒ๋žตํ•˜๋ฉด ๊ธฐ๋ณธ ๋ชจ๋ธ๋กœ Claude Sonnet 4.5๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค.

def get_default_model() -> ChatAnthropic:
    """Get the default model for deep agents.
 
    Returns:
        ChatAnthropic instance configured with Claude Sonnet 4.
    """
    return ChatAnthropic(
        model_name="claude-sonnet-4-5-20250929",
        max_tokens=20000,
    )

์†Œ์Šค์ฝ”๋“œ: https://github.com/langchain-ai/deepagents/blob/338787bbb186004b0d1f0a8333274e3e34f8d61e/libs/deepagents/deepagents/graph.py#L28-L37

ํ•ต์‹ฌ ๊ธฐ๋Šฅ

๊ณ„ํš ๋ฐ ์ž‘์—… ๋ถ„ํ•ด

Deep Agents๋Š” ์—์ด์ „ํŠธ๊ฐ€ ๋ณต์žกํ•œ ์ž‘์—…์„ ๊ฐœ๋ณ„ ๋‹จ๊ณ„๋กœ ๋ถ„ํ•ดํ•˜๊ณ , ์ง„ํ–‰ ์ƒํ™ฉ์„ ์ถ”์ ํ•˜๊ณ , ์ƒˆ๋กœ์šด ์ •๋ณด๊ฐ€ ๋‚˜ํƒ€๋‚˜๋ฉด ๊ณ„ํš์„ ์ˆ˜์ •ํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•˜๋Š” ๋‚ด์žฅ write_todos ๋„๊ตฌ๋ฅผ ํฌํ•จํ•ฉ๋‹ˆ๋‹ค.

์ปจํ…์ŠคํŠธ ๊ด€๋ฆฌ

ํŒŒ์ผ ์‹œ์Šคํ…œ ๋„๊ตฌ(ls, read_file, write_file, edit_file, glob, grep)๋ฅผ ํ†ตํ•ด ์—์ด์ „ํŠธ๋Š” ํฐ ์ปจํ…์ŠคํŠธ๋ฅผ ๋ฉ”๋ชจ๋ฆฌ๋กœ ์˜คํ”„๋กœ๋“œํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ์ปจํ…์ŠคํŠธ ์œˆ๋„์šฐ ์˜ค๋ฒ„ํ”Œ๋กœ์šฐ๋ฅผ ๋ฐฉ์ง€ํ•˜๊ณ  ๊ฐ€๋ณ€ ๊ธธ์ด ๋„๊ตฌ ๊ฒฐ๊ณผ๋กœ ์ž‘์—…ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

ํ•˜์œ„ ์—์ด์ „ํŠธ ์ƒ์„ฑ

๋‚ด์žฅ task ๋„๊ตฌ๋ฅผ ํ†ตํ•ด ์—์ด์ „ํŠธ๋Š” ์ปจํ…์ŠคํŠธ ๊ฒฉ๋ฆฌ๋ฅผ ์œ„ํ•ด ํŠนํ™”๋œ ํ•˜์œ„ ์—์ด์ „ํŠธ๋ฅผ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Š” ๋ฉ”์ธ ์—์ด์ „ํŠธ์˜ ์ปจํ…์ŠคํŠธ๋ฅผ ๊น”๋”ํ•˜๊ฒŒ ์œ ์ง€ํ•˜๋ฉด์„œ ํŠน์ • ํ•˜์œ„ ์ž‘์—…์„ ๊นŠ์ด ์žˆ๊ฒŒ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.

์žฅ๊ธฐ ๋ฉ”๋ชจ๋ฆฌ

LangGraph์˜ Store๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์—์ด์ „ํŠธ๋ฅผ ์Šค๋ ˆ๋“œ ๊ฐ„ ์ง€์†์ ์ธ ๋ฉ”๋ชจ๋ฆฌ๋กœ ํ™•์žฅํ•ฉ๋‹ˆ๋‹ค. ์—์ด์ „ํŠธ๋Š” ์ด์ „ ๋Œ€ํ™”์—์„œ ์ •๋ณด๋ฅผ ์ €์žฅํ•˜๊ณ  ๊ฒ€์ƒ‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Deep Agents ์ปค์Šคํ„ฐ๋งˆ์ด์ง•

create_deep_agent์— ์ „๋‹ฌํ•  ์ˆ˜ ์žˆ๋Š” ์—ฌ๋Ÿฌ ๋งค๊ฐœ๋ณ€์ˆ˜๊ฐ€ ์žˆ์–ด ์ž์‹ ์˜ ์ปค์Šคํ…€ Deep Agent๋ฅผ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

model

๊ธฐ๋ณธ์ ์œผ๋กœ deepagents๋Š” "claude-sonnet-4-5-20250929"๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ๋ชจ๋“  LangChain ๋ชจ๋ธ ๊ฐ์ฒด๋ฅผ ์ „๋‹ฌํ•˜์—ฌ ์ปค์Šคํ„ฐ๋งˆ์ด์ง•ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

from deepagents import create_deep_agent
 
from langchain.chat_models import init_chat_model
 
 
model = init_chat_model("openai:gpt-4o")
agent = create_deep_agent(
    model=model,
)
from deepagents import create_deep_agent
 
from langchain.chat_models import init_chat_model
 
 
model = init_chat_model("openai:gpt-4o")
agent = create_deep_agent(
    model=model,
)

system_prompt

Deep Agents๋Š” ๋‚ด์žฅ ์‹œ์Šคํ…œ ํ”„๋กฌํ”„ํŠธ๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๊ฒƒ์€ Claude Code์˜ ์‹œ์Šคํ…œ ํ”„๋กฌํ”„ํŠธ๋ฅผ ๋ณต์ œํ•˜๋ ค๋Š” ์‹œ๋„์— ๊ธฐ๋ฐ˜์„ ๋‘๊ณ  ์˜๊ฐ์„ ๋ฐ›์€ ์ƒ๋Œ€์ ์œผ๋กœ ์ƒ์„ธํ•œ ํ”„๋กฌํ”„ํŠธ์ž…๋‹ˆ๋‹ค. Claude Code์˜ ์‹œ์Šคํ…œ ํ”„๋กฌํ”„ํŠธ๋ณด๋‹ค ๋” ์ผ๋ฐ˜์ ์ธ ์šฉ๋„๋กœ ๋งŒ๋“ค์–ด์กŒ์Šต๋‹ˆ๋‹ค. ๊ธฐ๋ณธ ํ”„๋กฌํ”„ํŠธ๋Š” ๋‚ด์žฅ ๊ณ„ํš ๋„๊ตฌ, ํŒŒ์ผ ์‹œ์Šคํ…œ ๋„๊ตฌ, ํ•˜์œ„ ์—์ด์ „ํŠธ ์‚ฌ์šฉ ๋ฐฉ๋ฒ•์— ๋Œ€ํ•œ ์ƒ์„ธํ•œ ์ง€์นจ์„ ํฌํ•จํ•ฉ๋‹ˆ๋‹ค.

๊ฐ ์‚ฌ์šฉ ์‚ฌ๋ก€์— ๋งž์ถ˜ Deep Agent๋Š” ๊ทธ ์‚ฌ์šฉ ์‚ฌ๋ก€์— ํŠนํ™”๋œ ์ปค์Šคํ…€ ์‹œ์Šคํ…œ ํ”„๋กฌํ”„ํŠธ๋„ ํฌํ•จํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์„ฑ๊ณต์ ์ธ Deep Agent๋ฅผ ๋งŒ๋“œ๋Š” ๋ฐ ์žˆ์–ด ํ”„๋กฌํ”„ํŒ…์˜ ์ค‘์š”์„ฑ์„ ์•„๋ฌด๋ฆฌ ๊ฐ•์กฐํ•ด๋„ ๋ถ€์กฑํ•ฉ๋‹ˆ๋‹ค.

from deepagents import create_deep_agent
 
 
research_instructions = """๋‹น์‹ ์€ ์ „๋ฌธ ์—ฐ๊ตฌ์›์ž…๋‹ˆ๋‹ค.
๋‹น์‹ ์˜ ์—ญํ• ์€ ์ฒ ์ €ํ•œ ์—ฐ๊ตฌ๋ฅผ ์ˆ˜ํ–‰ํ•˜๊ณ  ์ •๊ตํ•œ ๋ณด๊ณ ์„œ๋ฅผ ์ž‘์„ฑํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.
"""
 
agent = create_deep_agent(
    system_prompt=research_instructions,
)

tools

๋„๊ตฌ ํ˜ธ์ถœ ์—์ด์ „ํŠธ์™€ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ Deep Agent์— ์ ‘๊ทผ ๊ฐ€๋Šฅํ•œ ๋„๊ตฌ ์„ธํŠธ๋ฅผ ์ œ๊ณตํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

import os
from typing import Literal
 
from deepagents import create_deep_agent
from tavily import TavilyClient
 
 
tavily_client = TavilyClient(api_key=os.environ["TAVILY_API_KEY"])
 
 
def internet_search(
    query: str,
    max_results: int = 5,
    topic: Literal["general", "news", "finance"] = "general",
    include_raw_content: bool = False,
):
    """์›น ๊ฒ€์ƒ‰ ์‹คํ–‰"""
    return tavily_client.search(
        query,
        max_results=max_results,
        include_raw_content=include_raw_content,
        topic=topic,
    )
 
 
agent = create_deep_agent(tools=[internet_search])

middleware

create_deep_agent๋Š” ์ปค์Šคํ„ฐ๋งˆ์ด์ง• ๊ฐ€๋Šฅํ•œ ๋ฏธ๋“ค์›จ์–ด๋กœ ๊ตฌํ˜„๋ฉ๋‹ˆ๋‹ค.

๊ธฐ๋Šฅ์„ ํ™•์žฅํ•˜๊ณ , ๋„๊ตฌ๋ฅผ ์ถ”๊ฐ€ํ•˜๊ฑฐ๋‚˜, ์ปค์Šคํ…€ ํ›…์„ ๊ตฌํ˜„ํ•˜๊ธฐ ์œ„ํ•ด ์ถ”๊ฐ€ ๋ฏธ๋“ค์›จ์–ด๋ฅผ ์ œ๊ณตํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

from deepagents import create_deep_agent
from langchain_core.tools import tool
 
from langchain.agents.middleware import AgentMiddleware
 
 
@tool
def get_weather(city: str) -> str:
    """๋„์‹œ์˜ ๋‚ ์”จ๋ฅผ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค."""
    return f"{city}์˜ ๋‚ ์”จ๋Š” ๋ง‘์Šต๋‹ˆ๋‹ค."
 
 
@tool
def get_temperature(city: str) -> str:
    """๋„์‹œ์˜ ๊ธฐ์˜จ์„ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค."""
    return f"{city}์˜ ๊ธฐ์˜จ์€ ํ™”์”จ 70๋„์ž…๋‹ˆ๋‹ค."
 
 
class WeatherMiddleware(AgentMiddleware):
    tools = [get_weather, get_temperature]
 
 
agent = create_deep_agent(
    model="anthropic:claude-sonnet-4-20250514",
    middleware=[WeatherMiddleware()],
)

subagents

Deep Agents์˜ ์ฃผ์š” ๊ธฐ๋Šฅ์€ ํ•˜์œ„ ์—์ด์ „ํŠธ๋ฅผ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. subagents ๋งค๊ฐœ๋ณ€์ˆ˜์— ์—์ด์ „ํŠธ๊ฐ€ ์ž‘์—…์„ ์œ„์ž„ํ•  ์ˆ˜ ์žˆ๋Š” ์ปค์Šคํ…€ ํ•˜์œ„ ์—์ด์ „ํŠธ๋ฅผ ์ง€์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํ•˜์œ„ ์—์ด์ „ํŠธ๋Š” ์ปจํ…์ŠคํŠธ ๊ฒฉ๋ฆฌ(๋ฉ”์ธ ์—์ด์ „ํŠธ์˜ ์ „์ฒด ์ปจํ…์ŠคํŠธ๊ฐ€ ์˜ค์—ผ๋˜๋Š” ๊ฒƒ์„ ๋ฐฉ์ง€ํ•˜๊ธฐ ์œ„ํ•ด)์™€ ์ปค์Šคํ…€ ์ง€์นจ ์ œ๊ณต์— ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค.

subagents๋Š” ๊ฐ ๋”•์…”๋„ˆ๋ฆฌ๊ฐ€ ๋‹ค์Œ ์Šคํ‚ค๋งˆ๋ฅผ ๋”ฐ๋ฅด๋Š” ๋”•์…”๋„ˆ๋ฆฌ ๋ชฉ๋ก์ด์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค:

from collections.abc import Callable, Sequence
from typing import Any, NotRequired, TypedDict
 
from langchain_core.runnables import Runnable
from langchain_core.tools import BaseTool
 
from langchain.agents.middleware import InterruptOnConfig
from langchain.chat_models import BaseChatModel
 
 
class SubAgent(TypedDict):
    name: str
    description: str
    prompt: str
    tools: Sequence[BaseTool | Callable | dict[str, Any]]
    model: NotRequired[str | BaseChatModel]
    middleware: NotRequired[list[AgentMiddleware]]
    interrupt_on: NotRequired[dict[str, bool | InterruptOnConfig]]
 
 
class CompiledSubAgent(TypedDict):
    name: str
    description: str
    runnable: Runnable

SubAgent ํ•„๋“œ:

  • name: ํ•˜์œ„ ์—์ด์ „ํŠธ์˜ ์ด๋ฆ„์ด๋ฉฐ, ๋ฉ”์ธ ์—์ด์ „ํŠธ๊ฐ€ ํ•˜์œ„ ์—์ด์ „ํŠธ๋ฅผ ํ˜ธ์ถœํ•  ๋•Œ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.
  • description: ๋ฉ”์ธ ์—์ด์ „ํŠธ์—๊ฒŒ ํ‘œ์‹œ๋˜๋Š” ํ•˜์œ„ ์—์ด์ „ํŠธ์˜ ์„ค๋ช…์ž…๋‹ˆ๋‹ค.
  • prompt: ํ•˜์œ„ ์—์ด์ „ํŠธ์— ์‚ฌ์šฉ๋˜๋Š” ํ”„๋กฌํ”„ํŠธ์ž…๋‹ˆ๋‹ค.
  • tools: ํ•˜์œ„ ์—์ด์ „ํŠธ๊ฐ€ ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋Š” ๋„๊ตฌ ๋ชฉ๋ก์ž…๋‹ˆ๋‹ค.
  • model: ์„ ํƒ์  ๋ชจ๋ธ ์ด๋ฆ„ ๋˜๋Š” ๋ชจ๋ธ ์ธ์Šคํ„ด์Šค์ž…๋‹ˆ๋‹ค.
  • middleware: ํ•˜์œ„ ์—์ด์ „ํŠธ์— ์ฒจ๋ถ€ํ•  ์ถ”๊ฐ€ ๋ฏธ๋“ค์›จ์–ด์ž…๋‹ˆ๋‹ค. ๋ฏธ๋“ค์›จ์–ด ์†Œ๊ฐœ์™€ ์ž‘๋™ ๋ฐฉ์‹์— ๋Œ€ํ•ด์„œ๋Š” ์—ฌ๊ธฐ๋ฅผ ์ฐธ๊ณ ํ•˜์„ธ์š”.
  • interrupt_on: ๋„๊ตฌ์— ๋Œ€ํ•œ ํœด๋จผ-์ธ-๋”-๋ฃจํ”„ ์ƒํ˜ธ์ž‘์šฉ์„ ์ง€์ •ํ•˜๋Š” ์ปค์Šคํ…€ ์ธํ„ฐ๋ŸฝํŠธ ๊ตฌ์„ฑ์ž…๋‹ˆ๋‹ค.

CompiledSubAgent ํ•„๋“œ:

  • name: ํ•˜์œ„ ์—์ด์ „ํŠธ์˜ ์ด๋ฆ„์ด๋ฉฐ, ๋ฉ”์ธ ์—์ด์ „ํŠธ๊ฐ€ ํ•˜์œ„ ์—์ด์ „ํŠธ๋ฅผ ํ˜ธ์ถœํ•  ๋•Œ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.
  • description: ๋ฉ”์ธ ์—์ด์ „ํŠธ์—๊ฒŒ ํ‘œ์‹œ๋˜๋Š” ํ•˜์œ„ ์—์ด์ „ํŠธ์˜ ์„ค๋ช…์ž…๋‹ˆ๋‹ค.
  • runnable: ํ•˜์œ„ ์—์ด์ „ํŠธ๋กœ ์‚ฌ์šฉ๋  ๋ฏธ๋ฆฌ ๊ตฌ์ถ•๋œ LangGraph ๊ทธ๋ž˜ํ”„/์—์ด์ „ํŠธ์ž…๋‹ˆ๋‹ค.

SubAgent ์‚ฌ์šฉ

from collections.abc import Sequence
import os
from typing import Literal
 
from deepagents import CompiledSubAgent, SubAgent, create_deep_agent
from tavily import TavilyClient
 
 
tavily_client = TavilyClient(api_key=os.environ["TAVILY_API_KEY"])
 
 
def internet_search(
    query: str,
    max_results: int = 5,
    topic: Literal["general", "news", "finance"] = "general",
    include_raw_content: bool = False,
):
    """์›น ๊ฒ€์ƒ‰ ์‹คํ–‰"""
    return tavily_client.search(
        query,
        max_results=max_results,
        include_raw_content=include_raw_content,
        topic=topic,
    )
 
 
research_subagent: SubAgent = {
    "name": "research-agent",
    "description": "๋” ์‹ฌํ™”๋œ ์งˆ๋ฌธ์„ ์กฐ์‚ฌํ•˜๋Š” ๋ฐ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค",
    "system_prompt": "๋‹น์‹ ์€ ํ›Œ๋ฅญํ•œ ์—ฐ๊ตฌ์›์ž…๋‹ˆ๋‹ค",
    "tools": [internet_search],
    "model": "openai:gpt-4o",  # ์„ ํƒ์  ์˜ค๋ฒ„๋ผ์ด๋“œ, ๊ธฐ๋ณธ๊ฐ’์€ ๋ฉ”์ธ ์—์ด์ „ํŠธ ๋ชจ๋ธ
}
 
agent = create_deep_agent(
    model="anthropic:claude-sonnet-4-20250514",
    subagents=[research_subagent],
)

CustomSubAgent ์‚ฌ์šฉ

๋” ๋ณต์žกํ•œ ์‚ฌ์šฉ ์‚ฌ๋ก€๋ฅผ ์œ„ํ•ด ์ž์‹ ์˜ ๋ฏธ๋ฆฌ ๊ตฌ์ถ•๋œ LangGraph ๊ทธ๋ž˜ํ”„๋ฅผ ํ•˜์œ„ ์—์ด์ „ํŠธ๋กœ ์ œ๊ณตํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

from langchain.agents import create_agent
 
 
specialized_tools = []
 
# ๋ฏธ๋ฆฌ ๊ตฌ์ถ•๋œ ์ปค์Šคํ…€ ์—์ด์ „ํŠธ ๊ทธ๋ž˜ํ”„
custom_graph = create_agent(
    model=model,
    tools=specialized_tools,
    system_prompt="๋‹น์‹ ์€ ๋ฐ์ดํ„ฐ ๋ถ„์„์„ ์œ„ํ•œ ์ „๋ฌธํ™”๋œ ์—์ด์ „ํŠธ์ž…๋‹ˆ๋‹ค...",
)
 
# ์ปค์Šคํ…€ ํ•˜์œ„ ์—์ด์ „ํŠธ๋กœ ์‚ฌ์šฉ
custom_subagent = CompiledSubAgent(
    name="data-analyzer",
    description="๋ณต์žกํ•œ ๋ฐ์ดํ„ฐ ๋ถ„์„ ์ž‘์—…์„ ์œ„ํ•œ ์ „๋ฌธํ™”๋œ ์—์ด์ „ํŠธ",
    runnable=custom_graph,
)
 
subagents = [custom_subagent]
 
agent = create_deep_agent(
    model="anthropic:claude-sonnet-4-20250514",
    tools=[internet_search],
    system_prompt=research_instructions,
    subagents=subagents,
)

interrupt_on

์—์ด์ „ํŠธ์˜ ์ผ๋ฐ˜์ ์ธ ํ˜„์‹ค์€ ์ผ๋ถ€ ๋„๊ตฌ ์ž‘์—…์ด ๋ฏผ๊ฐํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ ์‹คํ–‰ ์ „์— ์‚ฌ๋žŒ์˜ ์Šน์ธ์ด ํ•„์š”ํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. Deep Agents๋Š” LangGraph์˜ ์ธํ„ฐ๋ŸฝํŠธ ๊ธฐ๋Šฅ์„ ํ†ตํ•ด ํœด๋จผ-์ธ-๋”-๋ฃจํ”„ ์›Œํฌํ”Œ๋กœ์šฐ๋ฅผ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค. ์ฒดํฌํฌ์ธํ„ฐ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์–ด๋–ค ๋„๊ตฌ๊ฐ€ ์Šน์ธ์„ ์š”๊ตฌํ•˜๋Š”์ง€ ๊ตฌ์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ด๋Ÿฌํ•œ ๋„๊ตฌ ๊ตฌ์„ฑ์€ ๋ฏธ๋ฆฌ ๊ตฌ์ถ•๋œ HITL ๋ฏธ๋“ค์›จ์–ด์— ์ „๋‹ฌ๋˜๋ฏ€๋กœ ์—์ด์ „ํŠธ๊ฐ€ ์‹คํ–‰์„ ์ผ์‹œ ์ค‘์ง€ํ•˜๊ณ  ๊ตฌ์„ฑ๋œ ๋„๊ตฌ๋ฅผ ์‹คํ–‰ํ•˜๊ธฐ ์ „์— ์‚ฌ์šฉ์ž์˜ ํ”ผ๋“œ๋ฐฑ์„ ๊ธฐ๋‹ค๋ฆฝ๋‹ˆ๋‹ค.

from deepagents import create_deep_agent
from langchain_core.tools import tool
 
from langgraph.checkpoint.memory import InMemorySaver
 
 
@tool
def get_weather(city: str) -> str:
    """๋„์‹œ์˜ ๋‚ ์”จ๋ฅผ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค."""
    return f"{city}์˜ ๋‚ ์”จ๋Š” ๋ง‘์Šต๋‹ˆ๋‹ค."
 
 
# Deep Agent ์ธ์Šคํ„ด์Šค ์ƒ์„ฑ
agent = create_deep_agent(
    # ์‚ฌ์šฉํ•  LLM ๋ชจ๋ธ ์ง€์ •
    model="anthropic:claude-sonnet-4-20250514",
    # ์—์ด์ „ํŠธ๊ฐ€ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ๋„๊ตฌ ๋ชฉ๋ก
    tools=[get_weather],
    # ํœด๋จผ-์ธ-๋”-๋ฃจํ”„(Human-in-the-Loop) ์„ค์ •
    # ํŠน์ • ๋„๊ตฌ๊ฐ€ ํ˜ธ์ถœ๋  ๋•Œ ์‚ฌ๋žŒ์˜ ๊ฐœ์ž…์„ ์š”์ฒญํ•˜๋„๋ก ์„ค์ •
    # get_weather ๋„๊ตฌ๊ฐ€ ํ˜ธ์ถœ๋˜๋ ค ํ•  ๋•Œ:
    # - "approve": ์‚ฌ๋žŒ์ด ์Šน์ธํ•˜๋ฉด ๋„๊ตฌ ์‹คํ–‰
    # - "edit": ์‚ฌ๋žŒ์ด ๋„๊ตฌ ์ž…๋ ฅ๊ฐ’์„ ์ˆ˜์ •
    # - "reject": ์‚ฌ๋žŒ์ด ๊ฑฐ์ ˆํ•˜๋ฉด ๋„๊ตฌ ์‹คํ–‰ ์•ˆ ํ•จ
    interrupt_on={
        "get_weather": {"allowed_decisions": ["approve", "edit", "reject"]},
    },
    checkpointer=InMemorySaver(),
)
from uuid import uuid4
 
from langchain_core.runnables import RunnableConfig
 
 
config = RunnableConfig(configurable={"thread_id": uuid4()})
 
# ์—์ด์ „ํŠธ ํ˜ธ์ถœ
result = agent.invoke(
    {"messages": [{"role": "user", "content": "์˜ค๋Š˜ ์„œ์šธ ๋‚ ์”จ๋Š”?"}]},
    config=config,
)
result
{'messages': [HumanMessage(content='์˜ค๋Š˜ ์„œ์šธ ๋‚ ์”จ๋Š”?', additional_kwargs={}, response_metadata={}, id='4d0bf508-ca45-4015-94b6-269a11344a43'),
  AIMessage(content=[{'text': '์„œ์šธ์˜ ์˜ค๋Š˜ ๋‚ ์”จ๋ฅผ ํ™•์ธํ•ด๋“œ๋ฆฌ๊ฒ ์Šต๋‹ˆ๋‹ค.', 'type': 'text'}, {'id': 'toolu_01MeKvCBnJ66DsCnqrXvARJB', 'input': {'city': '์„œ์šธ'}, 'name': 'get_weather', 'type': 'tool_use'}], additional_kwargs={}, response_metadata={'id': 'msg_01XYTekbagQ7pUAf4qoDn9jH', 'model': 'claude-sonnet-4-20250514', 'stop_reason': 'tool_use', 'stop_sequence': None, 'usage': {'cache_creation': {'ephemeral_1h_input_tokens': 0, 'ephemeral_5m_input_tokens': 0}, 'cache_creation_input_tokens': 0, 'cache_read_input_tokens': 5676, 'input_tokens': 3, 'output_tokens': 80, 'server_tool_use': None, 'service_tier': 'standard'}, 'model_name': 'claude-sonnet-4-20250514', 'model_provider': 'anthropic'}, id='lc_run--4b14eb35-a9af-49b0-afa7-a9ce2232c48e-0', tool_calls=[{'name': 'get_weather', 'args': {'city': '์„œ์šธ'}, 'id': 'toolu_01MeKvCBnJ66DsCnqrXvARJB', 'type': 'tool_call'}], usage_metadata={'input_tokens': 5679, 'output_tokens': 80, 'total_tokens': 5759, 'input_token_details': {'cache_read': 5676, 'cache_creation': 0, 'ephemeral_5m_input_tokens': 0, 'ephemeral_1h_input_tokens': 0}})],
 '__interrupt__': [Interrupt(value={'action_requests': [{'name': 'get_weather', 'args': {'city': '์„œ์šธ'}, 'description': "Tool execution requires approval\n\nTool: get_weather\nArgs: {'city': '์„œ์šธ'}"}], 'review_configs': [{'action_name': 'get_weather', 'allowed_decisions': ['approve', 'edit', 'reject']}]}, id='be4534667c08a16555d41340e83a8fcb')]}
from langgraph.graph.state import Command
 
 
result = agent.invoke(
    Command(resume={"decisions": [{"type": "approve"}]}),
    config=config,
)
result
{'messages': [HumanMessage(content='์˜ค๋Š˜ ์„œ์šธ ๋‚ ์”จ๋Š”?', additional_kwargs={}, response_metadata={}, id='4d0bf508-ca45-4015-94b6-269a11344a43'),
  AIMessage(content=[{'text': '์„œ์šธ์˜ ์˜ค๋Š˜ ๋‚ ์”จ๋ฅผ ํ™•์ธํ•ด๋“œ๋ฆฌ๊ฒ ์Šต๋‹ˆ๋‹ค.', 'type': 'text'}, {'id': 'toolu_01MeKvCBnJ66DsCnqrXvARJB', 'input': {'city': '์„œ์šธ'}, 'name': 'get_weather', 'type': 'tool_use'}], additional_kwargs={}, response_metadata={'id': 'msg_01XYTekbagQ7pUAf4qoDn9jH', 'model': 'claude-sonnet-4-20250514', 'stop_reason': 'tool_use', 'stop_sequence': None, 'usage': {'cache_creation': {'ephemeral_1h_input_tokens': 0, 'ephemeral_5m_input_tokens': 0}, 'cache_creation_input_tokens': 0, 'cache_read_input_tokens': 5676, 'input_tokens': 3, 'output_tokens': 80, 'server_tool_use': None, 'service_tier': 'standard'}, 'model_name': 'claude-sonnet-4-20250514', 'model_provider': 'anthropic'}, id='lc_run--4b14eb35-a9af-49b0-afa7-a9ce2232c48e-0', tool_calls=[{'name': 'get_weather', 'args': {'city': '์„œ์šธ'}, 'id': 'toolu_01MeKvCBnJ66DsCnqrXvARJB', 'type': 'tool_call'}], usage_metadata={'input_tokens': 5679, 'output_tokens': 80, 'total_tokens': 5759, 'input_token_details': {'cache_creation': 0, 'cache_read': 5676, 'ephemeral_5m_input_tokens': 0, 'ephemeral_1h_input_tokens': 0}}),
  ToolMessage(content='์„œ์šธ์˜ ๋‚ ์”จ๋Š” ๋ง‘์Šต๋‹ˆ๋‹ค.', name='get_weather', id='0a18ce11-39f6-466f-916a-90109c8bc5c6', tool_call_id='toolu_01MeKvCBnJ66DsCnqrXvARJB'),
  AIMessage(content='์˜ค๋Š˜ ์„œ์šธ์˜ ๋‚ ์”จ๋Š” **๋ง‘์Šต๋‹ˆ๋‹ค**. ์ข‹์€ ๋‚ ์”จ๋„ค์š”!', additional_kwargs={}, response_metadata={'id': 'msg_01TmMB9PowPsDUxUzfVoEzSi', 'model': 'claude-sonnet-4-20250514', 'stop_reason': 'end_turn', 'stop_sequence': None, 'usage': {'cache_creation': {'ephemeral_1h_input_tokens': 0, 'ephemeral_5m_input_tokens': 104}, 'cache_creation_input_tokens': 104, 'cache_read_input_tokens': 5676, 'input_tokens': 5, 'output_tokens': 35, 'server_tool_use': None, 'service_tier': 'standard'}, 'model_name': 'claude-sonnet-4-20250514', 'model_provider': 'anthropic'}, id='lc_run--371f3335-656e-4665-8bde-b913e679747e-0', usage_metadata={'input_tokens': 5785, 'output_tokens': 35, 'total_tokens': 5820, 'input_token_details': {'cache_read': 5676, 'cache_creation': 104, 'ephemeral_5m_input_tokens': 104, 'ephemeral_1h_input_tokens': 0}})]}
config = RunnableConfig(configurable={"thread_id": uuid4()})
result = agent.invoke(
    {"messages": [{"role": "user", "content": "์˜ค๋Š˜ ์„œ์šธ ๋‚ ์”จ๋Š”?"}]},
    config=config,
)
result = agent.invoke(
    Command(resume={"decisions": [{"type": "reject"}]}),
    config=config,
)
result
{'messages': [HumanMessage(content='์˜ค๋Š˜ ์„œ์šธ ๋‚ ์”จ๋Š”?', additional_kwargs={}, response_metadata={}, id='1d58209f-13d9-4066-aa11-300fcd9e5f81'),
  AIMessage(content=[{'text': '์„œ์šธ์˜ ์˜ค๋Š˜ ๋‚ ์”จ๋ฅผ ํ™•์ธํ•ด๋“œ๋ฆฌ๊ฒ ์Šต๋‹ˆ๋‹ค.', 'type': 'text'}, {'id': 'toolu_01SneWTNnYpRBtKowmzWwSwe', 'input': {'city': '์„œ์šธ'}, 'name': 'get_weather', 'type': 'tool_use'}], additional_kwargs={}, response_metadata={'id': 'msg_01ESgyzVktH7ocXAJ4LziqSM', 'model': 'claude-sonnet-4-20250514', 'stop_reason': 'tool_use', 'stop_sequence': None, 'usage': {'cache_creation': {'ephemeral_1h_input_tokens': 0, 'ephemeral_5m_input_tokens': 0}, 'cache_creation_input_tokens': 0, 'cache_read_input_tokens': 5676, 'input_tokens': 3, 'output_tokens': 80, 'server_tool_use': None, 'service_tier': 'standard'}, 'model_name': 'claude-sonnet-4-20250514', 'model_provider': 'anthropic'}, id='lc_run--f29fa7e8-7d3e-4188-bc9b-86dab8a2e857-0', tool_calls=[{'name': 'get_weather', 'args': {'city': '์„œ์šธ'}, 'id': 'toolu_01SneWTNnYpRBtKowmzWwSwe', 'type': 'tool_call'}], usage_metadata={'input_tokens': 5679, 'output_tokens': 80, 'total_tokens': 5759, 'input_token_details': {'cache_creation': 0, 'cache_read': 5676, 'ephemeral_5m_input_tokens': 0, 'ephemeral_1h_input_tokens': 0}}),
  ToolMessage(content='User rejected the tool call for `get_weather` with id toolu_01SneWTNnYpRBtKowmzWwSwe', name='get_weather', id='bfd73daf-5505-4f24-9c74-18bead370b2c', tool_call_id='toolu_01SneWTNnYpRBtKowmzWwSwe', status='error'),
  AIMessage(content='์ฃ„์†กํ•ฉ๋‹ˆ๋‹ค. ๋‚ ์”จ ์ •๋ณด๋ฅผ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ๋‚ ์”จ ์ •๋ณด๋ฅผ ํ™•์ธํ•˜์‹œ๋ ค๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๋ฐฉ๋ฒ•์„ ์ถ”์ฒœ๋“œ๋ฆฝ๋‹ˆ๋‹ค:\n\n1. **๋‚ ์”จ ์•ฑ ์‚ฌ์šฉ**: ์Šค๋งˆํŠธํฐ์˜ ๊ธฐ๋ณธ ๋‚ ์”จ ์•ฑ์ด๋‚˜ ๋„ค์ด๋ฒ„ ๋‚ ์”จ, ์›จ๋”์ฑ„๋„ ๋“ฑ์˜ ์•ฑ ํ™œ์šฉ\n2. **ํฌํ„ธ ์‚ฌ์ดํŠธ**: ๋„ค์ด๋ฒ„, ๋‹ค์Œ ๋“ฑ์—์„œ "์„œ์šธ ๋‚ ์”จ" ๊ฒ€์ƒ‰\n3. **๊ธฐ์ƒ์ฒญ ํ™ˆํŽ˜์ด์ง€**: weather.go.kr์—์„œ ์ •ํ™•ํ•œ ๊ธฐ์ƒ์ •๋ณด ํ™•์ธ\n4. **์‹ค์‹œ๊ฐ„ ๊ฒ€์ƒ‰**: "์„œ์šธ ๋‚ ์”จ"๋กœ ๊ฒ€์ƒ‰ํ•˜๋ฉด ํ˜„์žฌ ๊ธฐ์˜จ, ์Šต๋„, ๊ฐ•์ˆ˜ํ™•๋ฅ  ๋“ฑ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค\n\n๋‹ค๋ฅธ ๋„์›€์ด ํ•„์š”ํ•˜์‹œ๋ฉด ์–ธ์ œ๋“  ๋ง์”€ํ•ด ์ฃผ์„ธ์š”!', additional_kwargs={}, response_metadata={'id': 'msg_01MVzFJpFx6j2kr6ZAKX4tu8', 'model': 'claude-sonnet-4-20250514', 'stop_reason': 'end_turn', 'stop_sequence': None, 'usage': {'cache_creation': {'ephemeral_1h_input_tokens': 0, 'ephemeral_5m_input_tokens': 132}, 'cache_creation_input_tokens': 132, 'cache_read_input_tokens': 5676, 'input_tokens': 7, 'output_tokens': 271, 'server_tool_use': None, 'service_tier': 'standard'}, 'model_name': 'claude-sonnet-4-20250514', 'model_provider': 'anthropic'}, id='lc_run--a5f43739-4766-4c09-aca7-07fc33339f38-0', usage_metadata={'input_tokens': 5815, 'output_tokens': 271, 'total_tokens': 6086, 'input_token_details': {'cache_read': 5676, 'cache_creation': 132, 'ephemeral_5m_input_tokens': 132, 'ephemeral_1h_input_tokens': 0}})]}

Deep Agents ๋ฏธ๋“ค์›จ์–ด

Deep Agents๋Š” ๋ชจ๋“ˆ์‹ ๋ฏธ๋“ค์›จ์–ด ์•„ํ‚คํ…์ฒ˜๋กœ ๊ตฌ์ถ•๋ฉ๋‹ˆ๋‹ค. ์ƒ๊ธฐํ–ˆ๋“ฏ์ด, Deep Agents๋Š” ๋‹ค์Œ์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

  • ๊ณ„ํš ๋„๊ตฌ
  • ์ปจํ…์ŠคํŠธ์™€ ์žฅ๊ธฐ ๋ฉ”๋ชจ๋ฆฌ๋ฅผ ์ €์žฅํ•˜๊ธฐ ์œ„ํ•œ ํŒŒ์ผ ์‹œ์Šคํ…œ
  • ํ•˜์œ„ ์—์ด์ „ํŠธ๋ฅผ ์ƒ์„ฑํ•˜๋Š” ๊ธฐ๋Šฅ

์ด๋Ÿฌํ•œ ๊ฐ ๊ธฐ๋Šฅ์€ ๋ณ„๋„์˜ ๋ฏธ๋“ค์›จ์–ด๋กœ ๊ตฌํ˜„๋ฉ๋‹ˆ๋‹ค. create_deep_agent๋กœ Deep Agent๋ฅผ ๋งŒ๋“ค ๋•Œ, ์šฐ๋ฆฌ๋Š” ์ž๋™์œผ๋กœ TodoListMiddleware, FilesystemMiddleware, SubAgentMiddleware๋ฅผ ์—์ด์ „ํŠธ์— ์ฒจ๋ถ€ํ•ฉ๋‹ˆ๋‹ค.

๋ฏธ๋“ค์›จ์–ด๋Š” ๊ตฌ์„ฑ ๊ฐ€๋Šฅํ•œ ๊ฐœ๋…์ด๋ฉฐ, ์‚ฌ์šฉ ์‚ฌ๋ก€์— ๋”ฐ๋ผ ์—์ด์ „ํŠธ์— ์›ํ•˜๋Š” ๋งŒํผ ๋งŽ๊ฑฐ๋‚˜ ์ ์€ ๋ฏธ๋“ค์›จ์–ด๋ฅผ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ฆ‰, ์•ž์„œ ์–ธ๊ธ‰ํ•œ ๋ฏธ๋“ค์›จ์–ด ์ค‘ ์–ด๋А ๊ฒƒ๋„ ๋…๋ฆฝ์ ์œผ๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค!

TodoListMiddleware

๊ณ„ํš์€ ๋ณต์žกํ•œ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๋Š” ๋ฐ ํ•„์ˆ˜์ ์ž…๋‹ˆ๋‹ค. ์ตœ๊ทผ์— Claude Code๋ฅผ ์‚ฌ์šฉํ•ด๋ณธ ์ ์ด ์žˆ๋‹ค๋ฉด, ๋ณต์žกํ•œ ๋‹ค์ค‘ ๋ถ€๋ถ„ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•˜๊ธฐ ์ „์— ์–ด๋–ป๊ฒŒ To-Do ๋ชฉ๋ก์„ ์ž‘์„ฑํ•˜๋Š”์ง€ ์•Œ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ƒˆ๋กœ์šด ์ •๋ณด๊ฐ€ ๋“ค์–ด์˜ค๋ฉด์„œ ์ด To-Do ๋ชฉ๋ก์„ ์ฆ‰์„์—์„œ ์–ด๋–ป๊ฒŒ ์ˆ˜์ •ํ•˜๊ณ  ์—…๋ฐ์ดํŠธํ•  ์ˆ˜ ์žˆ๋Š”์ง€๋„ ์•Œ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

TodoListMiddleware๋Š” ์—์ด์ „ํŠธ์— ์ด To-Do ๋ชฉ๋ก์„ ์—…๋ฐ์ดํŠธํ•˜๊ธฐ ์œ„ํ•œ ํŠน์ • ๋„๊ตฌ๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ๋‹ค์ค‘ ๋ถ€๋ถ„ ์ž‘์—…์„ ์‹คํ–‰ํ•˜๊ธฐ ์ „ํ›„์— ์—์ด์ „ํŠธ๋Š” write_todos ๋„๊ตฌ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ž์‹ ์ด ํ•˜๊ณ  ์žˆ๋Š” ์ž‘์—…๊ณผ ๋‚จ์€ ์ž‘์—…์„ ์ถ”์ ํ•˜๋ผ๋Š” ํ”„๋กฌํ”„ํŠธ๊ฐ€ ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค.

from langchain.agents import create_agent
from langchain.agents.middleware import TodoListMiddleware
 
 
# TodoListMiddleware๋Š” create_deep_agent์—์„œ ๊ธฐ๋ณธ์ ์œผ๋กœ ํฌํ•จ๋ฉ๋‹ˆ๋‹ค
# ์ปค์Šคํ…€ ์—์ด์ „ํŠธ๋ฅผ ๊ตฌ์ถ•ํ•  ๋•Œ ์ปค์Šคํ„ฐ๋งˆ์ด์ง•ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค
agent = create_agent(
    model="anthropic:claude-haiku-4-5",
    # ์ปค์Šคํ…€ ๊ณ„ํš ์ง€์นจ์€ ๋ฏธ๋“ค์›จ์–ด๋ฅผ ํ†ตํ•ด ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค
    middleware=[
        TodoListMiddleware(
            system_prompt="write_todos ๋„๊ตฌ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ..."  # ์„ ํƒ์ : ์‹œ์Šคํ…œ ํ”„๋กฌํ”„ํŠธ์— ๋Œ€ํ•œ ์ปค์Šคํ…€ ์ถ”๊ฐ€
        ),
    ],
)

FilesystemMiddleware

์ปจํ…์ŠคํŠธ ์—”์ง€๋‹ˆ์–ด๋ง์€ ํšจ๊ณผ์ ์ธ ์—์ด์ „ํŠธ๋ฅผ ๊ตฌ์ถ•ํ•˜๋Š” ๋ฐ ์žˆ์–ด ์ฃผ์š” ๊ณผ์ œ ์ค‘ ํ•˜๋‚˜์ž…๋‹ˆ๋‹ค. ํŠนํžˆ ๊ฐ€๋ณ€ ๊ธธ์ด ๊ฒฐ๊ณผ๋ฅผ ๋ฐ˜ํ™˜ํ•  ์ˆ˜ ์žˆ๋Š” ๋„๊ตฌ(์˜ˆ: web_search, rag)๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ๋Š” ๋”์šฑ ์–ด๋ ค์šธ ์ˆ˜ ์žˆ์œผ๋ฉฐ, ๊ธด ToolResults๊ฐ€ ๋น ๋ฅด๊ฒŒ ์ปจํ…์ŠคํŠธ ์œˆ๋„์šฐ๋ฅผ ์ฑ„์šธ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

FilesystemMiddleware๋Š” ์—์ด์ „ํŠธ์— ๋‹จ๊ธฐ ๋ฐ ์žฅ๊ธฐ ๋ฉ”๋ชจ๋ฆฌ์™€ ์ƒํ˜ธ์ž‘์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๋„ค ๊ฐ€์ง€ ๋„๊ตฌ๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

  • ls: ํŒŒ์ผ ์‹œ์Šคํ…œ์˜ ํŒŒ์ผ ๋ชฉ๋ก ํ‘œ์‹œ
  • read_file: ํŒŒ์ผ ์ „์ฒด ๋˜๋Š” ํŒŒ์ผ์˜ ํŠน์ • ํ–‰ ์ˆ˜ ์ฝ๊ธฐ
  • write_file: ํŒŒ์ผ ์‹œ์Šคํ…œ์— ์ƒˆ ํŒŒ์ผ ์ž‘์„ฑ
  • edit_file: ํŒŒ์ผ ์‹œ์Šคํ…œ์˜ ๊ธฐ์กด ํŒŒ์ผ ํŽธ์ง‘
from deepagents.middleware.filesystem import FilesystemMiddleware
 
from langchain.agents import create_agent
 
 
# FilesystemMiddleware๋Š” create_deep_agent์—์„œ ๊ธฐ๋ณธ์ ์œผ๋กœ ํฌํ•จ๋ฉ๋‹ˆ๋‹ค
# ์ปค์Šคํ…€ ์—์ด์ „ํŠธ๋ฅผ ๊ตฌ์ถ•ํ•  ๋•Œ ์ปค์Šคํ„ฐ๋งˆ์ด์ง•ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค
agent = create_agent(
    model="anthropic:claude-sonnet-4-20250514",
    middleware=[
        FilesystemMiddleware(
            # backend=...,  # ์„ ํƒ์ : ์ €์žฅ์†Œ ๋ฐฑ์—”๋“œ ์ปค์Šคํ„ฐ๋งˆ์ด์ง•
            system_prompt="ํŒŒ์ผ ์‹œ์Šคํ…œ์— ์“ธ ๋•Œ...",  # ์„ ํƒ์ : ์ปค์Šคํ…€ ์‹œ์Šคํ…œ ํ”„๋กฌํ”„ํŠธ ์˜ค๋ฒ„๋ผ์ด๋“œ
            custom_tool_descriptions={
                "ls": "๋‹ค์Œ์˜ ๊ฒฝ์šฐ์— ls ๋„๊ตฌ๋ฅผ ์‚ฌ์šฉํ•˜์„ธ์š”...",
                "read_file": "read_file ๋„๊ตฌ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ...",
            },  # ์„ ํƒ์ : ํŒŒ์ผ ์‹œ์Šคํ…œ ๋„๊ตฌ์— ๋Œ€ํ•œ ์ปค์Šคํ…€ ์„ค๋ช…
        ),
    ],
)

SubAgentMiddleware

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

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

from deepagents.middleware.subagents import SubAgentMiddleware
from langchain_core.tools import tool
 
from langchain.agents import create_agent
 
 
@tool
def get_weather(city: str) -> str:
    """๋„์‹œ์˜ ๋‚ ์”จ๋ฅผ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค."""
    return f"{city}์˜ ๋‚ ์”จ๋Š” ๋ง‘์Šต๋‹ˆ๋‹ค."
 
 
agent = create_agent(
    model="claude-sonnet-4-20250514",
    middleware=[
        SubAgentMiddleware(
            default_model="anthropic:claude-haiku-4-5",
            default_tools=[],
            subagents=[
                {
                    "name": "weather",
                    "description": "์ด ํ•˜์œ„ ์—์ด์ „ํŠธ๋Š” ๋„์‹œ์˜ ๋‚ ์”จ๋ฅผ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.",
                    "system_prompt": "get_weather ๋„๊ตฌ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋„์‹œ์˜ ๋‚ ์”จ๋ฅผ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค.",
                    "tools": [get_weather],
                    "model": "gpt-4.1",
                    "middleware": [],
                }
            ],
        )
    ],
)

๋” ๋ณต์žกํ•œ ์‚ฌ์šฉ ์‚ฌ๋ก€์˜ ๊ฒฝ์šฐ ์ž์‹ ์˜ ๋ฏธ๋ฆฌ ๊ตฌ์ถ•๋œ LangGraph ๊ทธ๋ž˜ํ”„๋ฅผ ํ•˜์œ„ ์—์ด์ „ํŠธ๋กœ ์ œ๊ณตํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

from langgraph import StateGraph
 
 
# ์ปค์Šคํ…€ LangGraph ๊ทธ๋ž˜ํ”„ ์ƒ์„ฑ
def create_weather_graph():
    workflow = StateGraph(...)
    # ์ปค์Šคํ…€ ๊ทธ๋ž˜ํ”„ ๊ตฌ์ถ•
    return workflow.compile()
 
 
weather_graph = create_weather_graph()
 
# CompiledSubAgent์— ๋ž˜ํ•‘
weather_subagent = CompiledSubAgent(
    name="weather",
    description="์ด ํ•˜์œ„ ์—์ด์ „ํŠธ๋Š” ๋„์‹œ์˜ ๋‚ ์”จ๋ฅผ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.",
    runnable=weather_graph,
)
 
agent = create_agent(
    model="anthropic:claude-sonnet-4-20250514",
    middleware=[
        SubAgentMiddleware(
            default_model="anthropic:claude-haiku-4-5",
            default_tools=[],
            subagents=[weather_subagent],
        )
    ],
)

๋™๊ธฐ ๋Œ€ ๋น„๋™๊ธฐ

deepagents์˜ ์ด์ „ ๋ฒ„์ „์€ ๋™๊ธฐ ๋ฐ ๋น„๋™๊ธฐ ์—์ด์ „ํŠธ ํŒฉํ† ๋ฆฌ๋ฅผ ๊ตฌ๋ถ„ํ–ˆ์Šต๋‹ˆ๋‹ค.

async_create_deep_agent๊ฐ€ create_deep_agent๋กœ ํ†ตํ•ฉ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

๋™๊ธฐ ๋ฐ ๋น„๋™๊ธฐ ์—์ด์ „ํŠธ ๋ชจ๋‘์— ๋Œ€ํ•ด ํŒฉํ† ๋ฆฌ๋กœ create_deep_agent๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

MCP

deepagents ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋Š” MCP ๋„๊ตฌ์™€ ํ•จ๊ป˜ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Š” Langchain MCP Adapter ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋‹ฌ์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ฐธ๊ณ : MCP ๋„๊ตฌ๋Š” ๋น„๋™๊ธฐ์ด๋ฏ€๋กœ from deepagents import async_create_deep_agent๋ฅผ ์‚ฌ์šฉํ•˜๋ ค๊ณ  ํ•ฉ๋‹ˆ๋‹ค.

(์•„๋ž˜ ์˜ˆ์ œ๋ฅผ ์‹คํ–‰ํ•˜๋ ค๋ฉด pip install langchain-mcp-adapters๋ฅผ ์„ค์น˜ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.)

import asyncio
 
from deepagents import create_deep_agent
from langchain_mcp_adapters.client import MultiServerMCPClient
import nest_asyncio
 
 
nest_asyncio.apply()
 
 
async def main():
    # MCP ๋„๊ตฌ ์ˆ˜์ง‘
    mcp_client = MultiServerMCPClient(
        {
            "fetch": {
                "transport": "stdio",
                "command": "uvx",
                "args": ["mcp-server-fetch"],
            }
        }
    )
    mcp_tools = await mcp_client.get_tools()
 
    # ์—์ด์ „ํŠธ ์ƒ์„ฑ
    agent = create_deep_agent(tools=mcp_tools)
 
    # ์—์ด์ „ํŠธ ์ŠคํŠธ๋ฆฌ๋ฐ
    async for chunk in agent.astream(
        {"messages": [{"role": "user", "content": "langgraph๋ž€ ๋ฌด์—‡์ธ๊ฐ€์š”?"}]},
        stream_mode="values",
    ):
        if "messages" in chunk:
            chunk["messages"][-1].pretty_print()
 
 
asyncio.run(main())
================================ Human Message =================================

langgraph๋ž€ ๋ฌด์—‡์ธ๊ฐ€์š”?
================================ Human Message =================================

langgraph๋ž€ ๋ฌด์—‡์ธ๊ฐ€์š”?
================================== Ai Message ==================================

LangGraph๋Š” **LangChain ์ƒํƒœ๊ณ„์˜ ์ผ๋ถ€๋กœ ๊ฐœ๋ฐœ๋œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ**๋กœ, LLM(๋Œ€๊ทœ๋ชจ ์–ธ์–ด ๋ชจ๋ธ) ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ **๋ณต์žกํ•œ ์—์ด์ „ํŠธ ์›Œํฌํ”Œ๋กœ์šฐ๋ฅผ ๊ทธ๋ž˜ํ”„ ํ˜•ํƒœ๋กœ ๊ตฌ์ถ•**ํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ฃผ๋Š” ํ”„๋ ˆ์ž„์›Œํฌ์ž…๋‹ˆ๋‹ค.

## ์ฃผ์š” ํŠน์ง•

### 1. **๊ทธ๋ž˜ํ”„ ๊ธฐ๋ฐ˜ ์›Œํฌํ”Œ๋กœ์šฐ**
- ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ํ๋ฆ„์„ ๋…ธ๋“œ(node)์™€ ์—ฃ์ง€(edge)๋กœ ๊ตฌ์„ฑ๋œ ๊ทธ๋ž˜ํ”„๋กœ ํ‘œํ˜„
- ๊ฐ ๋…ธ๋“œ๋Š” ํŠน์ • ์ž‘์—…์ด๋‚˜ ํ•จ์ˆ˜๋ฅผ ๋‚˜ํƒ€๋‚ด๊ณ , ์—ฃ์ง€๋Š” ์‹คํ–‰ ํ๋ฆ„์„ ์ •์˜

### 2. **์ƒํƒœ ๊ด€๋ฆฌ(Stateful)**
- ๋Œ€ํ™”๋‚˜ ์ž‘์—…์˜ ์ƒํƒœ๋ฅผ ์ง€์†์ ์œผ๋กœ ๊ด€๋ฆฌ
- ์ด์ „ ์ƒํ˜ธ์ž‘์šฉ์˜ ์ปจํ…์ŠคํŠธ๋ฅผ ์œ ์ง€ํ•˜๋ฉด์„œ ๋ณต์žกํ•œ ๋‹ค๋‹จ๊ณ„ ์ž‘์—… ์ฒ˜๋ฆฌ

### 3. **์ˆœํ™˜ ๊ตฌ์กฐ ์ง€์›**
- ์ผ๋ฐ˜์ ์ธ DAG(๋ฐฉํ–ฅ์„ฑ ๋น„์ˆœํ™˜ ๊ทธ๋ž˜ํ”„)์™€ ๋‹ฌ๋ฆฌ **์ˆœํ™˜(cycle)**์„ ํ—ˆ์šฉ
- ์—์ด์ „ํŠธ๊ฐ€ ์ด์ „ ๋‹จ๊ณ„๋กœ ๋Œ์•„๊ฐ€๊ฑฐ๋‚˜ ๋ฐ˜๋ณต ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ์Œ

### 4. **์ฒดํฌํฌ์ธํŠธ์™€ ์‹œ๊ฐ„ ์—ฌํ–‰**
- ์›Œํฌํ”Œ๋กœ์šฐ์˜ ํŠน์ • ์‹œ์ ์„ ์ €์žฅํ•˜๊ณ  ๋ณต์› ๊ฐ€๋Šฅ
- ๋””๋ฒ„๊น…์ด๋‚˜ ์˜ค๋ฅ˜ ๋ณต๊ตฌ์— ์œ ์šฉ

## ์ฃผ์š” ์‚ฌ์šฉ ์‚ฌ๋ก€

- **๋ณต์žกํ•œ AI ์—์ด์ „ํŠธ**: ๋‹ค์–‘ํ•œ ๋„๊ตฌ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์ž์œจ ์—์ด์ „ํŠธ
- **๋ฉ€ํ‹ฐ ์—์ด์ „ํŠธ ์‹œ์Šคํ…œ**: ์—ฌ๋Ÿฌ ์—์ด์ „ํŠธ๊ฐ€ ํ˜‘์—…ํ•˜๋Š” ์‹œ์Šคํ…œ
- **๋Œ€ํ™”ํ˜• ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜**: ๋ณต์žกํ•œ ๋Œ€ํ™” ํ๋ฆ„์„ ๊ฐ€์ง„ ์ฑ—๋ด‡
- **์›Œํฌํ”Œ๋กœ์šฐ ์ž๋™ํ™”**: ์กฐ๊ฑด๋ถ€ ๋ถ„๊ธฐ์™€ ๋ฃจํ”„๊ฐ€ ์žˆ๋Š” ์ž‘์—… ์ž๋™ํ™”

## ๊ธฐ๋ณธ ๊ตฌ์กฐ ์˜ˆ์‹œ

```python
from langgraph.graph import StateGraph

# ๊ทธ๋ž˜ํ”„ ์ •์˜
workflow = StateGraph(State)

# ๋…ธ๋“œ ์ถ”๊ฐ€
workflow.add_node("agent", agent_function)
workflow.add_node("tool", tool_function)

# ์—ฃ์ง€ ์ถ”๊ฐ€ (ํ๋ฆ„ ์ •์˜)
workflow.add_edge("agent", "tool")
workflow.add_conditional_edges("tool", should_continue)

# ์ปดํŒŒ์ผ
app = workflow.compile()
```

LangGraph๋Š” ํŠนํžˆ **๋ณต์žกํ•œ ์˜์‚ฌ๊ฒฐ์ • ๋กœ์ง**์ด ํ•„์š”ํ•˜๊ฑฐ๋‚˜, **์—ฌ๋Ÿฌ ๋‹จ๊ณ„๋ฅผ ๊ฑฐ์ณ์•ผ ํ•˜๋Š” AI ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜**์„ ๊ตฌ์ถ•ํ•  ๋•Œ ๋งค์šฐ ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค.

๋” ์ž์„ธํ•œ ์ •๋ณด๊ฐ€ ํ•„์š”ํ•˜์‹œ๊ฑฐ๋‚˜ ํŠน์ • ์‚ฌ์šฉ ์˜ˆ์‹œ๋ฅผ ์•Œ๊ณ  ์‹ถ์œผ์‹œ๋‹ค๋ฉด ๋ง์”€ํ•ด ์ฃผ์„ธ์š”!