LangChain 도구 (Tools)

LangChain에서 도구(Tool) 는 LLM이 외부 기능(API, 계산, DB 등)에 접근할 수 있게 해주는 핵심 구성 요소입니다.


1. 도구란 무엇인가?

기본적으로 LLM은 텍스트만 생성할 수 있습니다.
하지만, 도구를 연결하면 외부 세계와 상호작용할 수 있는 “실행 가능한 지능(Executable Intelligence)” 을 만들 수 있습니다.

💡 쉽게 말해, 도구는 LLM이 사용할 수 있는 “함수”,
에이전트는 어떤 함수를 사용할지 “결정하는 뇌” 역할을 합니다.


2. 도구 기본 사용법

2.1 @tool 데코레이터

도구를 만드는 가장 간단한 방법은 @tool 데코레이터를 사용하는 것.
함수의 docstring은 모델이 도구를 언제 사용해야 하는지 이해하는 데 도움이 되는 도구 설명.

from langchain.tools import tool

@tool
def search_database(query: str, limit: int = 10) -> str:
    """고객 DB에서 검색어에 맞는 레코드를 찾습니다."""
    return f"'{query}'에 대한 검색 결과 {limit}건을 찾았습니다."

# ✅ 테스트
result = search_database.invoke({"query": "홍길동", "limit": 3})
print("🔍 Tool 실행 결과:", result)

출력 예시

🔍 Tool 실행 결과: '홍길동'에 대한 검색 결과 3건을 찾았습니다.

2.2 도구 이름 및 설명 설정

@tool("web_search", description="웹 검색을 수행합니다.")
def search(query: str) -> str:
    """웹에서 관련 정보를 검색합니다."""
    return f"'{query}'에 대한 웹 검색 결과입니다."

# ✅ 테스트
print(search.invoke({"query": "LangChain"}))

2.3 Pydantic 모델 활용

from pydantic import BaseModel, Field

class WeatherInput(BaseModel):
    location: str = Field(description="도시 이름")
    units: str = Field(default="celsius", description="온도 단위")
    include_forecast: bool = Field(default=False, description="5일 예보 포함 여부")

@tool(args_schema=WeatherInput)
def get_weather(location: str, units: str = "celsius", include_forecast: bool = False) -> str:
    """현재 날씨와 선택적 예보를 반환합니다."""
    temp = 22 if units == "celsius" else 72
    result = f"{location}의 온도는 {temp}{units}입니다."
    if include_forecast:
        result += " (5일 예보: 맑음)"
    return result

# ✅ 테스트
print(get_weather.invoke({"location": "서울", "units": "celsius", "include_forecast": True}))

2.4 ToolRuntime: 실행 중 상태 접근하기

from langchain_openai import ChatOpenAI
from langchain.tools import tool
from langchain.agents import create_agent
from langgraph.checkpoint.memory import InMemorySaver  

# ✅ 1️⃣ 도구 정의
@tool
def summarize_conversation(runtime):
    """현재까지의 대화를 요약합니다."""
    messages = runtime.state["messages"]
    human_msgs = sum(1 for m in messages if m.__class__.__name__ == "HumanMessage")
    ai_msgs = sum(1 for m in messages if m.__class__.__name__ == "AIMessage")
    return f"현재까지 사용자 메시지 {human_msgs}개, AI 응답 {ai_msgs}개가 있습니다."

# ✅ 2️⃣ 모델 및 체크포인트 초기화
llm = ChatOpenAI(model="gpt-5-nano", temperature=0)
memory = InMemorySaver()  # LangGraph는 상태 기반이므로 메모리 저장소 필요

# ✅ 3️⃣ Agent 생성 (1.0 버전 호환)
agent_executor = create_agent(
    model=llm,
    tools=[summarize_conversation],
    checkpointer=memory,
    system_prompt="당신은 유용한 어시스턴트입니다." ,
    debug=True
)

# ✅ 4️⃣ 실행
response = agent_executor.invoke(
    {"messages": [{"role": "user", "content": "가을 여행지 어디가 좋아 추천좀 해줘"}]},
    config={"configurable": {"thread_id": "user-1"}},  # ✅ 세션 ID
)
print("🤖 모델 응답:", response["messages"][-1].content)

# ✅ 4️⃣ 실행
response = agent_executor.invoke(
    {"messages": [{"role": "user", "content": "그중에서 한곳만 더 자세한 일정을 짜줄래"}]},
    config={"configurable": {"thread_id": "user-1"}},  # ✅ 세션 ID
)
print("🤖 모델 응답:", response["messages"][-1].content)

2.5 Context 예시 (사용자 정보 접근)

from dataclasses import dataclass
from langchain_openai import ChatOpenAI
from langchain.tools import tool, ToolRuntime
from langchain.agents import create_agent
from langgraph.checkpoint.memory import MemorySaver

# 1️⃣ 가상 사용자 데이터베이스
USER_DB = {
    "user123": {"name": "홍길동", "balance": 5000},
    "user456": {"name": "김철수", "balance": 1200},
}

# 2️⃣ 사용자 Context 클래스 정의
@dataclass
class UserContext:
    user_id: str

# 3️⃣ Tool 정의
@tool
def get_account_info(runtime: ToolRuntime[UserContext]) -> str:
    """현재 사용자 계좌 정보를 조회합니다."""
    user_id = runtime.context.user_id  # LangGraph가 context 주입
    user = USER_DB.get(user_id)
    if not user:
        return "❌ 사용자 정보를 찾을 수 없습니다."
    return f"✅ 이름: {user['name']}, 잔액: {user['balance']}원"

# 4️⃣ LangGraph Agent 생성
llm = ChatOpenAI(model="gpt-5-nano", temperature=0)
memory = MemorySaver()  # 대화 상태 저장용

agent_executor = create_agent(
    model=llm,
    tools=[get_account_info],
    context_schema=UserContext,  # ✅ context 타입 지정
    checkpointer=memory,
    system_prompt="너는 금융 상담 어시스턴트야. 사용자의 계좌를 확인하고 응답해줘.",
    debug=True 
)

# 5️⃣ 테스트 실행 (UserContext 주입)
response = agent_executor.invoke(
    {
        "messages": [
            {"role": "user", "content": "내 잔액이 얼마야?"}
        ]
    },
    context=UserContext(user_id="user123"),  # ✅ Context 주입
    config={"configurable": {"thread_id": "session-1"}}
)

# 6️⃣ 결과 출력
if isinstance(response, dict) and "messages" in response:
    print("🤖 모델 응답:", response["messages"][-1].content)
else:
    print("🤖 모델 응답:", getattr(response, "content", str(response)))

실행흐름

  • UserContext(user_id=”user123”)를 생성
  • LangGraph가 context를 ToolRuntime에 자동 주입
  • 모델이 get_account_info 도구를 호출함
  • 도구 내부에서 runtime.context.user_id로 사용자 ID 접근
  • DB에서 이름과 잔액 조회 후 문자열 반환
  • 모델이 최종 답변 생성 (예: “홍길동님 잔액은 5000원입니다”)

2.6 Store를 활용한 장기 메모리 도구

from langchain.tools import tool, ToolRuntime
from langchain.agents import create_agent
from langgraph.store.memory import InMemoryStore
from typing import Any

store = InMemoryStore()

@tool
def save_user_info(user_id: str, user_info: dict[str, Any], runtime: ToolRuntime) -> str:
    """사용자 정보를 저장합니다."""
    runtime.store.put(("users",), user_id, user_info)
    return "✅ 사용자 정보 저장 완료"

@tool
def get_user_info(user_id: str, runtime: ToolRuntime) -> str:
    """저장된 사용자 정보를 불러옵니다."""
    info = runtime.store.get(("users",), user_id)
    return str(info.value) if info else "❌ 정보 없음"

agent = create_agent(
    model="openai:gpt-4o",
    tools=[save_user_info, get_user_info],
    store=store,
    debug=True 
)

save_user_info 호출 테스트

# 테스트
result = agent.invoke({
    "messages": [{"role": "user", "content": "user_123의 이름은 John, 나이는 30으로 저장해줘"}]
})
print(result["messages"][-1].content)

실행 결과

사용자 "user_123"의 정보가 성공적으로 저장되었습니다. 이름은 "John"이고 나이는 30세입니다.

get_user_info 호출 테스트

# 테스트
result = agent.invoke({
    "messages": [{"role": "user", "content": "user_123의 정보를 알려줘"}]
})
print(result["messages"][-1].content)

실행 결과

user_123의 정보는 다음과 같습니다:

- 이름: John
- 나이: 30세

2.7 실시간 스트리밍 예시

from langchain.tools import tool, ToolRuntime
from langchain.agents import create_agent
from langchain_openai import ChatOpenAI

@tool
def get_weather(city: str, runtime: ToolRuntime) -> str:
    """도시 날씨를 조회하며 진행 상황을 스트리밍합니다."""
    writer = runtime.stream_writer
    writer(f"[1/2] '{city}' 데이터 조회 중...")
    writer(f"[2/2] '{city}' 데이터 수집 완료!")
    return f"{city}의 날씨는 항상 맑음 ☀️"

# 에이전트 생성
agent = create_agent(
    model=ChatOpenAI(model="gpt-4o"),
    tools=[get_weather]
)

# 스트리밍 모드로 실행
# stream_mode="custom"은 writer()로 보낸 커스텀 데이터만 스트리밍
for chunk in agent.stream(
    {"messages": [{"role": "user", "content": "샌프란시스코 날씨는?"}]},
    stream_mode=["updates", "custom"]
):
  print(chunk)

3. 외부 API 호출 Tool 구현하기 (예: 날씨 API, 뉴스 API)

실제 외부 API를 호출하는 커스텀 툴을 구현해보겠습니다. 예시로 날씨 정보 조회 툴뉴스 검색 툴 두 가지를 만들어보겠습니다.

3.1 환경 설정 및 라이브러리 설치

  • API 키 준비: OpenAI API 키와 OpenWeatherMap, NewsAPI 등의 키를 발급받아 .env 파일에 저장합니다.
    News API 에서 키를 새로 발급
    OpenWeatherMap 에 default 값으로 생성되어 있음(그대로 사용 새로 발급 시 바로 사용못함.)
OPENAI_API_KEY=<발급받은 OpenAI 키>
OPENWEATHERMAP_API_KEY=<OpenWeatherMap 키>
NEWSAPI_API_KEY=<NewsAPI 키>

이제 파이썬에서 dotenv를 통해 환경변수를 불러오겠습니다.

# .env 파일 로드
from dotenv import load_dotenv
import os

load_dotenv()  # .env 파일의 환경변수 로드
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
OWM_API_KEY = os.getenv("OPENWEATHERMAP_API_KEY")
NEWS_API_KEY = os.getenv("NEWSAPI_API_KEY")

# 키 값 확인 (None이 아니라면 성공적으로 불러온 것입니다)
print("OpenAI Key present?", OPENAI_API_KEY is not None)
print("Weather API Key present?", OWM_API_KEY is not None)
print("News API Key present?", NEWS_API_KEY is not None)

3.2 날씨 정보 조회 툴 구현

OpenWeatherMap의 Current Weather Data API를 호출하여 특정 도시의 날씨를 가져오는 함수를 작성해보겠습니다. 이 함수는 도시 이름을 입력받아 현재 기온과 날씨 상태를 문자열로 리턴합니다.

from langchain.tools import tool
from pydantic import BaseModel, Field
import requests

# 입력 스키마 정의
class WeatherInput(BaseModel):
    city: str = Field(description="날씨를 조회할 도시 이름 (영문)")

@tool(args_schema=WeatherInput)
def get_weather(city: str) -> str:
    """주어진 도시의 현재 날씨를 반환합니다."""
    api_key = os.getenv("OPENWEATHERMAP_API_KEY")
    url = "http://api.openweathermap.org/data/2.5/weather"
    params = {
        "q": city,
        "appid": api_key,
        "units": "metric",      # 섭씨 온도
        "lang": "kr"            # 한국어 응답 (설명이 한국어로 오도록)
    }
    response = requests.get(url, params=params)
    data = response.json()
    # API 응답에 따른 처리
    if data.get("cod") != 200:
        # 도시 정보를 찾지 못한 경우
        return f"'{city}'의 날씨 정보를 찾을 수 없습니다."
    # 필요한 정보 파싱
    temp = data["main"]["temp"]
    desc = data["weather"][0]["description"]
    city_name = data["name"]  # 응답에 있는 도시 이름 (영문일 수 있음)
    return f"{city_name}의 현재 기온은 {temp}℃, 날씨는 {desc}입니다."

위 코드에서 units=metric으로 설정하여 온도를 섭씨로 받고, lang=kr을 사용해 날씨 설명(description)을 한국어로 요청했습니다. get_weather("Seoul")처럼 호출하면 서울의 현재 날씨 정보를 얻을 수 있습니다.

Note: 사용자가 도시명을 한글로 입력할 수 있습니다. OpenWeatherMap API는 도시명을 영문으로 요청하는 것이 일반적이므로, 사용자가 “서울”처럼 입력한 경우 적절히 영문 (“Seoul”)으로 변환하거나, q 파라미터에 "Seoul,KR" 등 국가 코드를 포함하는 방식을 고려해야 합니다. (이번 예시에서는 간단히 영문 도시명을 사용하는 것으로 가정합니다.)

3.3 뉴스 검색 툴 구현

두 번째로 뉴스 검색 툴을 구현해보겠습니다. NewsAPI를 사용하여 특정 키워드에 대한 최신 뉴스를 가져오는 함수를 작성합니다. 뉴스 헤드라인과 간략한 설명을 반환하도록 합시다.

# 입력 스키마 정의
class NewsInput(BaseModel):
    keyword: str = Field(description="최신 뉴스를 검색할 키워드 (한글 또는 영문)")


@tool(args_schema=NewsInput)
def get_news(keyword: str) -> str:
    """주어진 키워드에 대한 최신 뉴스 제목을 반환합니다."""
    api_key = os.getenv("NEWSAPI_API_KEY")
    url = "https://newsapi.org/v2/everything"
    params = {
        "q": keyword,
        "language": "ko",         # 한국어 뉴스 기사 검색
        "sortBy": "publishedAt",  # 최신 뉴스 우선 정렬
        "pageSize": 1,            # 가장 최근 기사 1건만 가져오기
        "apiKey": api_key
    }
    res = requests.get(url, params=params)
    data = res.json()
    articles = data.get("articles")
    if not articles:
        return f"'{keyword}'에 대한 최신 뉴스가 없습니다."
    # 가장 첫 번째 뉴스를 선택
    top_article = articles[0]
    title = top_article.get("title", "(제목 없음)")
    source = top_article.get("source", {}).get("name", "")
    return f"'{keyword}' 관련 뉴스: {title} - {source}"

이 함수는 주어진 키워드로 뉴스 기사를 검색하고, 가장 최신 기사 하나의 제목과 출처를 문자열로 반환합니다.

3.4 툴 사용

from langchain.agents import create_agent
from langchain_openai import ChatOpenAI

# LLM 초기화
llm = ChatOpenAI(model="gpt-4o")

# 도구 리스트 준비
tools = [get_weather, get_news]

# 에이전트 생성
agent = create_agent(
    model=llm,
    tools=tools,
    system_prompt="당신은 유용한 어시스턴트입니다." ,
    debug=True 
)

# 에이전트 실행
result = agent.invoke({
    "messages": [{"role": "user", "content": "부산의 오늘 날씨와 최신 뉴스를 알려줘"}]
})

# 결과 출력
print("=== 최종 결과 ===")
print(result["messages"][-1].content)