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)