AgentAI 에이전트 기초강의 6개 중 3번째난이도 중급

도구 설계와 LangGraph — 실제로 움직이는 에이전트 만들기

발행 읽는 시간 약 1 분

  • Agent
  • LangGraph
  • Tools
  • 에이전트
  • Python

강의 소개

LangGraph로 상태 기반 에이전트를 구현하고, 커스텀 도구를 설계해 LLM이 실제 작업을 수행하게 만드는 방법을 배웁니다.

LangGraph란?#

LangGraph는 에이전트의 실행 흐름을 상태(State) 기반 그래프로 표현합니다.

Diagram · Mermaid

에이전트는 본질적으로 "결과를 보고 다음 행동을 결정하는 루프"이므로 LangGraph가 적합합니다.


설치#

Bash
pip install langgraph langchain-openai langchain-community

도구(Tool) 설계#

Python
from langchain_core.tools import tool

@tool
def search_web(query: str) -> str:
    """웹에서 정보를 검색합니다. 최신 정보나 사실 확인이 필요할 때 사용합니다."""
    # 실제 구현에서는 Tavily, SerpAPI 등 사용
    return f"'{query}' 검색 결과: [검색 결과 내용]"

@tool
def calculate(expression: str) -> str:
    """수학 계산을 수행합니다. 예: '25 * 4 + 100'"""
    try:
        result = eval(expression)
        return f"{expression} = {result}"
    except Exception as e:
        return f"계산 오류: {e}"

@tool
def save_file(filename: str, content: str) -> str:
    """결과를 파일로 저장합니다."""
    with open(filename, "w", encoding="utf-8") as f:
        f.write(content)
    return f"파일 저장 완료: {filename}"

tools = [search_web, calculate, save_file]

@tool 데코레이터가 함수의 독스트링을 LLM이 읽는 도구 설명으로 사용합니다. 명확한 설명이 중요합니다.


LangGraph 에이전트 구현#

Diagram · Mermaid
Python
from typing import Annotated
from typing_extensions import TypedDict
from langgraph.graph import StateGraph, END
from langgraph.graph.message import add_messages
from langgraph.prebuilt import ToolNode, tools_condition
from langchain_openai import ChatOpenAI

# 1. 상태 정의
class AgentState(TypedDict):
    messages: Annotated[list, add_messages]

# 2. LLM + 도구 바인딩
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
llm_with_tools = llm.bind_tools(tools)

# 3. 에이전트 노드: LLM이 판단
def agent_node(state: AgentState):
    response = llm_with_tools.invoke(state["messages"])
    return {"messages": [response]}

# 4. 그래프 구성
graph_builder = StateGraph(AgentState)

graph_builder.add_node("agent", agent_node)
graph_builder.add_node("tools", ToolNode(tools))  # 도구 실행 노드

graph_builder.set_entry_point("agent")

# 조건부 엣지: 도구 호출 여부에 따라 분기
graph_builder.add_conditional_edges(
    "agent",
    tools_condition,  # tool_calls 있으면 tools, 없으면 END
)
graph_builder.add_edge("tools", "agent")  # 도구 실행 후 에이전트로 복귀

agent = graph_builder.compile()

에이전트 실행#

Python
from langchain_core.messages import HumanMessage

def run_agent(question: str):
    result = agent.invoke({
        "messages": [HumanMessage(content=question)]
    })
    return result["messages"][-1].content

# 테스트
print(run_agent("123 * 456을 계산하고 결과를 result.txt에 저장해줘"))

실행 흐름:

Text
[에이전트] 계산이 필요하다 → calculate("123 * 456") 호출
[도구] 123 * 456 = 56088 반환
[에이전트] 파일 저장이 필요하다 → save_file("result.txt", "56088") 호출
[도구] 파일 저장 완료 반환
[에이전트] 모든 작업 완료 → 최종 답변 생성

스트리밍으로 실행 과정 보기#

Python
def run_agent_verbose(question: str):
    for event in agent.stream(
        {"messages": [HumanMessage(content=question)]},
        stream_mode="values"
    ):
        last_message = event["messages"][-1]
        
        if hasattr(last_message, "tool_calls") and last_message.tool_calls:
            for tc in last_message.tool_calls:
                print(f"🔧 도구 호출: {tc['name']}({tc['args']})")
        elif last_message.type == "tool":
            print(f"📦 결과: {last_message.content[:100]}")
        else:
            print(f"💬 답변: {last_message.content}")

run_agent_verbose("서울 날씨 검색하고 화씨로 변환해줘")

도구 설계 원칙#

Diagram · Mermaid
Python
# ❌ 나쁜 도구
@tool
def do_stuff(input: str) -> str:
    """작업을 수행합니다."""
    pass

# ✅ 좋은 도구
@tool
def get_stock_price(symbol: str) -> str:
    """주식 티커 심볼로 현재 주가를 조회합니다.
    
    Args:
        symbol: 주식 티커 (예: 'AAPL', 'MSFT', '005930.KS')
    
    Returns:
        현재 주가와 등락률 정보
    """
    try:
        # 실제 API 호출
        return f"{symbol}: $150.00 (+1.5%)"
    except Exception as e:
        return f"주가 조회 실패 ({symbol}): {str(e)}"

체크포인트: 중간 상태 저장#

장시간 실행되는 에이전트는 중간 상태를 저장해야 합니다.

Python
from langgraph.checkpoint.memory import MemorySaver

# 메모리 체크포인터 추가
checkpointer = MemorySaver()
agent = graph_builder.compile(checkpointer=checkpointer)

# thread_id로 이어서 실행 가능
config = {"configurable": {"thread_id": "session-1"}}

result1 = agent.invoke(
    {"messages": [HumanMessage("내 이름은 김철수야")]},
    config=config
)

result2 = agent.invoke(
    {"messages": [HumanMessage("내 이름이 뭐야?")]},
    config=config
)
# → "김철수님이라고 하셨습니다." (이전 대화 기억)

실전 도구 연동: Tavily 웹 검색#

Python
from langchain_community.tools import TavilySearchResults

# pip install tavily-python
# TAVILY_API_KEY 환경변수 필요 (무료 티어 있음)
search_tool = TavilySearchResults(max_results=3)

tools = [search_tool, calculate, save_file]
llm_with_tools = llm.bind_tools(tools)

정리#

개념내용
@tool 데코레이터함수를 LLM이 호출 가능한 도구로 변환
AgentState에이전트가 유지하는 상태 (메시지 등)
ToolNode도구를 실행하는 LangGraph 노드
tools_condition도구 호출 여부에 따른 조건부 엣지
MemorySaver세션 간 상태 저장 체크포인터

다음 편에서는 메모리와 상태 관리 — 에이전트가 장기적으로 정보를 기억하고 활용하는 방법을 배웁니다.

Follow · Ask · Work

이 글이 도움이 됐다면

짧은 작업 기록과 데모는 인스타그램에 올립니다. 만들고 싶은 것이 있다면 프로젝트 의뢰나 협업 제안도 편하게 보내 주세요.

글쓴이

파란여우

직접 만든 AI 앱·제품의 개발·배포를 공개하는 실전형 테크 크리에이터

파란여우 소개 Instagram @bluefox._.hi

이어서 보기

만들고 싶은 것이 있다면 프로젝트 의뢰로 알려 주세요.