LangChain 에이전트는 다양한 내장 툴(예: 검색 엔진, 계산기)을 활용하여 복잡한 질문에 답하고 작업을 처리할 수 있습니다. 하지만 때로는 우리의 에이전트가 데이터베이스 조회, 특정 API 호출, 사내 시스템과의 연동 등 고유한 비즈니스 로직이나 특정 도메인 지식에 접근해야 할 필요가 생깁니다. 이럴 때 필요한 것이 바로 커스텀 툴입니다.
에이전트가 커스텀 툴을 활용하여 외부 시스템과 상호작용하는 개념도 (이미지는 LangChain Academy를 참고하여 재구성되었습니다)
위 그림처럼, 에이전트는 사용자의 요청을 받으면 어떤 툴을 사용해야 할지 결정하고, 그 툴을 통해 외부 데이터를 가져오거나 특정 작업을 수행합니다. 커스텀 툴은 이 과정에서 에이전트의 '손과 발'이 되어주는 것이죠.
💡 LangChain 커스텀 툴의 핵심 원리
LangChain에서 커스텀 툴을 만드는 것은 BaseTool 클래스를 상속받아 몇 가지 핵심 메소드를 구현하는 것으로 시작합니다. 다음과 같은 방식으로 커스텀 툴을 생성하고 사용할 수 있습니다.
- BaseTool 클래스 상속: 모든 커스텀 툴은 langchain_core.tools.BaseTool 또는 langchain.tools.BaseTool (레거시)을 상속받습니다.
- name 속성 정의: 툴의 고유한 이름입니다. 에이전트가 툴을 선택할 때 이 이름을 참조합니다.
- description 속성 정의: 툴의 기능에 대한 설명입니다. 에이전트가 어떤 상황에서 이 툴을 사용해야 할지 판단하는 데 중요한 역할을 합니다. 명확하고 구체적으로 작성해야 합니다.
- _run 메소드 구현: 동기적으로 툴을 실행하는 로직을 담습니다. 입력값을 받아 결과를 반환합니다.
- _arun 메소드 구현 (선택 사항): 비동기적으로 툴을 실행하는 로직을 담습니다. async/await 패턴을 사용할 때 유용합니다.
노트북에서 제공된 예시 코드:
# 필요한 라이브러리 임포트
from langchain.agents import initialize_agent, AgentType
from langchain.llms import OpenAI
from langchain.tools import BaseTool # 또는 from langchain_core.tools import BaseTool
# 1. 커스텀 툴 클래스 정의
class SearchGoogleTool(BaseTool):
name = "SearchGoogle"
description = "Searches Google for a given query and returns the results."
def _run(self, query: str) -> str:
"""Use the tool synchronously."""
# 이 부분에 실제 구글 검색 API 호출 로직이 들어갑니다.
# 여기서는 예시를 위해 더미 결과를 반환합니다.
print(f"DEBUG: Performing Google search for: {query}")
if "날씨" in query:
return "서울의 현재 날씨는 맑고 25도입니다."
elif "환율" in query:
return "1달러 = 1350원 입니다."
else:
return f"'{query}'에 대한 검색 결과: [검색 엔진에서 찾은 정보]"
async def _arun(self, query: str) -> str:
"""Use the tool asynchronously."""
raise NotImplementedError("SearchGoogleTool does not support async yet")
# 2. LLM 초기화 (여기서는 예시를 위해 OpenAI를 사용하지만, 다른 LLM도 가능)
llm = OpenAI(temperature=0) # 실제 사용 시에는 API key 설정 필요
# 3. 툴 인스턴스 생성
my_search_tool = SearchGoogleTool()
# 4. 에이전트 초기화
# 에이전트는 주어진 툴 목록에서 적절한 툴을 선택합니다.
agent = initialize_agent(
tools=[my_search_tool],
llm=llm,
agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION,
verbose=True # 에이전트의 추론 과정을 볼 수 있습니다.
)
# 5. 에이전트 실행
print("--- 에이전트 실행 1 ---")
agent.run("오늘 서울 날씨는 어때?")
print("\n--- 에이전트 실행 2 ---")
agent.run("환율 정보 좀 알려줘")
print("\n--- 에이전트 실행 3 ---")
agent.run("LangChain에 대해 검색해줘")
이 코드를 실행하면 에이전트가 SearchGoogleTool의 description을 읽고 질문에 따라 적절히 이 툴을 호출하는 것을 볼 수 있습니다. verbose=True 덕분에 에이전트의 사고 과정(Thought)을 추적할 수 있죠. 🧐
'재고 조회 및 상품 정보' 툴 만들기 🛒
이제 위의 내용을 바탕으로 우리만의 독창적인 커스텀 툴을 만들어봅시다. 많은 비즈니스에서 "현재 재고가 얼마나 되는지", "특정 상품의 가격은 얼마인지"와 같은 질문에 답해야 할 때가 있습니다. 이를 위해 가상의 '상품 정보 시스템'과 연동하는 툴을 만들어보겠습니다.
문제 상황
AI 에이전트가 고객 서비스 챗봇의 역할을 수행한다고 가정해 봅시다. 고객이 특정 상품의 재고나 가격을 문의할 때, 에이전트는 실제 데이터베이스(또는 API)에서 정보를 조회하여 응답해야 합니다.
목표
ProductInfoTool이라는 커스텀 툴을 만들어, 특정 상품의 이름(혹은 ID)을 입력받아 재고 및 가격 정보를 반환하도록 합니다.
🛠️ 코드 제작
# 필요한 라이브러리 임포트
from langchain.agents import initialize_agent, AgentType
from langchain.llms import OpenAI # 실제 사용 시에는 gpt-3.5-turbo 등 다양한 LLM 사용 가능
from langchain.tools import BaseTool
from typing import Optional, Type
from pydantic import BaseModel, Field # Pydantic을 사용하여 입력 스키마 정의
# 가상의 상품 데이터베이스 (실제로는 API 호출이나 DB 쿼리가 들어갈 부분)
PRODUCT_DB = {
"노트북": {"재고": 100, "가격": 1200000, "설명": "고성능 비즈니스 노트북"},
"스마트폰": {"재고": 50, "가격": 800000, "설명": "최신형 스마트폰, 듀얼 카메라"},
"키보드": {"재고": 200, "가격": 80000, "설명": "무선 기계식 키보드"},
"마우스": {"재고": 300, "가격": 30000, "설명": "인체공학 무선 마우스"},
}
# Pydantic을 사용하여 툴의 입력 스키마 정의 (LangChain 툴에 입력 유효성 검사 및 구조화)
class ProductInfoInput(BaseModel):
product_name: str = Field(description="재고 및 가격을 조회할 상품의 이름")
class ProductInfoTool(BaseTool):
name = "ProductInfo"
description = "특정 상품의 현재 재고, 가격 및 간략한 설명을 조회합니다. '상품 이름'을 입력해야 합니다."
args_schema: Type[BaseModel] = ProductInfoInput # Pydantic 스키마를 연결합니다.
def _run(self, product_name: str) -> str:
"""Use the tool synchronously to get product information."""
product = PRODUCT_DB.get(product_name)
if product:
return (
f"상품명: {product_name}, "
f"현재 재고: {product['재고']}개, "
f"가격: {product['가격']}원, "
f"설명: {product['설명']}"
)
else:
return f"'{product_name}' 상품을 찾을 수 없습니다. 다른 상품 이름을 시도해 보세요."
async def _arun(self, product_name: str) -> str:
"""Use the tool asynchronously."""
# 비동기 로직이 필요한 경우 여기에 구현합니다.
# 예시에서는 동기 함수를 호출합니다.
return self._run(product_name)
# LLM 초기화 (여러분의 API Key로 교체하세요)
# os.environ["OPENAI_API_KEY"] = "YOUR_OPENAI_API_KEY"
llm = OpenAI(temperature=0) # OpenAI 모델을 사용합니다.
# 툴 인스턴스 생성
product_info_tool = ProductInfoTool()
# 에이전트 초기화
# tools=[product_info_tool] 여기에 우리가 만든 커스텀 툴을 추가합니다.
agent = initialize_agent(
tools=[product_info_tool],
llm=llm,
agent=AgentType.OPENAI_FUNCTIONS, # OpenAI Functions 모델 사용 시 더 자연스러운 툴 호출 가능
verbose=True
)
# 에이전트 실행 및 테스트
print("--- 에이전트 실행: 노트북 재고 문의 ---")
agent.run("노트북 재고가 얼마나 남아있나요?")
print("\n--- 에이전트 실행: 키보드 가격 문의 ---")
agent.run("키보드 가격은 얼마야?")
print("\n--- 에이전트 실행: 없는 상품 문의 ---")
agent.run("커피 머신 재고 좀 알려줘")
print("\n--- 에이전트 실행: 복합적인 질문 ---")
agent.run("스마트폰은 얼마고, 재고는 몇 개나 있어요?")
🧑💻 코드 설명
- PRODUCT_DB: 실제 환경에서는 데이터베이스 쿼리나 REST API 호출로 대체될 부분입니다. 여기서는 간단한 파이썬 딕셔너리로 상품 정보를 시뮬레이션합니다.
- ProductInfoInput: Pydantic을 사용하여 툴의 입력 스키마를 정의합니다. 이렇게 하면 에이전트가 툴을 호출할 때 어떤 파라미터가 필요한지 명확하게 이해하고, 정확한 형식으로 데이터를 전달할 수 있게 됩니다. product_name이라는 필드가 필수임을 명시했습니다.
- ProductInfoTool 클래스:
- name: "ProductInfo"로 설정하여 에이전트가 이 툴을 지칭할 이름을 정합니다.
- description: "특정 상품의 현재 재고, 가격 및 간략한 설명을 조회합니다. '상품 이름'을 입력해야 합니다." 에이전트가 이 설명을 통해 툴의 용도를 이해하고, 언제 이 툴을 사용해야 할지 판단합니다. 특히 '상품 이름'이라는 필수 입력값을 명시하여 LLM이 올바른 인자를 전달하도록 유도합니다.
- args_schema: 위에서 정의한 ProductInfoInput Pydantic 스키마를 연결합니다. 이는 LLM이 Function Calling을 사용하거나 툴의 입력 스키마를 이해하는 데 매우 중요합니다.
- _run 메소드: product_name을 인자로 받아 PRODUCT_DB에서 상품을 찾아 정보를 반환합니다. 상품이 없으면 "상품을 찾을 수 없습니다"라는 메시지를 반환합니다.
- 에이전트 초기화: initialize_agent 함수에 우리가 만든 product_info_tool을 tools 리스트에 담아 전달합니다. AgentType.OPENAI_FUNCTIONS는 OpenAI의 Function Calling 기능을 활용하여 LLM이 툴을 더욱 정확하게 호출하도록 돕습니다.
이 예시를 통해 에이전트가 사용자의 질문을 분석하고, 필요한 정보를 얻기 위해 ProductInfoTool을 호출한 다음, 툴이 반환한 정보를 바탕으로 최종 답변을 생성하는 과정을 직접 확인할 수 있습니다.
이 글에서 다룬 내용은 LangChain Academy Module 6 - Creating.ipynb 노트북의 내용을 기반으로 작성되었으며, 더 자세한 내용은 원본 노트북을 참고해 주세요!
'LLM' 카테고리의 다른 글
| LLM 에이전트의 '반복 답변' 문제 해결! 자기 성찰(Self-Reflection)로 AI 성능 올리기 (0) | 2025.11.21 |
|---|---|
| RunnableSequence와 RunnableParallel로 LLM 체인 병렬 처리 및 순차 실행 (0) | 2025.11.21 |
| LangChain Memory (0) | 2025.11.21 |
| LangChain Memory 마스터 (0) | 2025.11.21 |
| LangChain과 Pydantic으로 사용자 프로필 자동 업데이트 (0) | 2025.11.21 |