LangChain AI Agent 구축 가이드: 실전 구현과 최적화 전략
LangChain을 활용한 AI 에이전트 개발 방법을 실제 코드와 함께 상세히 알아봅니다. GPT 기반 지능형 에이전트 구축부터 최적화까지 완벽 가이드.
LangChain AI Agent 구축 가이드: 실전 구현과 최적화 전략
AI 에이전트는 현재 가장 주목받는 AI 기술 중 하나로, 복잡한 작업을 자율적으로 수행할 수 있는 지능형 시스템입니다. LangChain은 이러한 AI 에이전트 개발을 위한 강력한 프레임워크로, GPT와 같은 대형 언어 모델(LLM)을 활용해 실용적인 AI 솔루션을 구축할 수 있게 해줍니다.
LangChain AI Agent의 핵심 개념
LangChain AI Agent는 언어 모델을 중심으로 하여 다양한 도구(Tool)와 메모리(Memory)를 활용해 복잡한 작업을 수행하는 시스템입니다. 기존의 단순한 질의응답 시스템과 달리, 에이전트는 상황을 분석하고 적절한 도구를 선택하여 단계별로 문제를 해결합니다.
에이전트의 주요 구성 요소는 다음과 같습니다:
- LLM(Large Language Model): 의사결정의 핵심 엔진
- Tools: 외부 API, 데이터베이스, 계산기 등 실제 작업을 수행하는 도구들
- Memory: 이전 대화나 작업 내용을 기억하는 메모리 시스템
- Agent Executor: 전체 에이전트 실행을 관리하는 컨트롤러
기본 AI Agent 구현하기
먼저 간단한 AI 에이전트를 구현해보겠습니다. Python 환경에서 LangChain을 사용한 기본적인 에이전트 구성 코드입니다:
from langchain.agents import initialize_agent, AgentType
from langchain.agents import Tool
from langchain.llms import OpenAI
from langchain.utilities import SerpAPIWrapper
import requests
# OpenAI LLM 초기화
llm = OpenAI(temperature=0, openai_api_key="your-api-key")
# 검색 도구 설정
search = SerpAPIWrapper(serpapi_api_key="your-serpapi-key")
# 계산기 도구 함수
def calculator(expression):
try:
result = eval(expression)
return f"계산 결과: {result}"
except:
return "잘못된 수식입니다."
# 도구 목록 정의
tools = [
Tool(
name="Search",
func=search.run,
description="최신 정보를 검색할 때 유용합니다."
),
Tool(
name="Calculator",
func=calculator,
description="수학 계산을 수행할 때 사용합니다."
)
]
# 에이전트 초기화
agent = initialize_agent(
tools,
llm,
agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION,
verbose=True
)
# 에이전트 실행
response = agent.run("2023년 AI 시장 규모를 찾아서 2024년 예상 성장률 15%를 곱해주세요")
print(response)
고급 에이전트 패턴 구현
실제 프로덕션 환경에서는 더 복잡한 에이전트 패턴이 필요합니다. ReAct(Reasoning + Acting) 패턴을 활용한 고급 에이전트를 구현해보겠습니다:
from langchain.agents import Tool, AgentExecutor, LLMSingleActionAgent
from langchain.prompts import StringPromptTemplate
from langchain.llms import OpenAI
from langchain.utilities import SerpAPIWrapper
from langchain.chains import LLMChain
from langchain.memory import ConversationBufferWindowMemory
import re
class CustomPromptTemplate(StringPromptTemplate):
template: str
tools: list[Tool]
def format(self, **kwargs) -> str:
intermediate_steps = kwargs.pop("intermediate_steps")
thoughts = ""
for action, observation in intermediate_steps:
thoughts += action.log
thoughts += f"\nObservation: {observation}\nThought: "
kwargs["agent_scratchpad"] = thoughts
kwargs["tools"] = "\n".join([f"{tool.name}: {tool.description}" for tool in self.tools])
kwargs["tool_names"] = ", ".join([tool.name for tool in self.tools])
return self.template.format(**kwargs)
# 커스텀 프롬프트 템플릿
template = """다음 질문에 답하기 위해 단계별로 생각하고 행동하세요.
사용 가능한 도구들:
{tools}
다음 형식을 사용하세요:
Question: 입력 질문
Thought: 무엇을 해야 할지 생각해보세요
Action: 취할 행동 [{tool_names} 중 하나]
Action Input: 행동에 대한 입력
Observation: 행동의 결과
... (이 Thought/Action/Action Input/Observation을 반복)
Thought: 이제 최종 답을 알았습니다
Final Answer: 원래 입력 질문에 대한 최종 답
시작하세요!
Question: {input}
Thought:{agent_scratchpad}"""
# 메모리 설정
memory = ConversationBufferWindowMemory(k=5)
# 고급 에이전트 구성
class AdvancedAgent:
def __init__(self, llm, tools):
self.llm = llm
self.tools = tools
self.prompt = CustomPromptTemplate(
template=template,
tools=tools,
input_variables=["input", "intermediate_steps"]
)
self.llm_chain = LLMChain(llm=llm, prompt=self.prompt)
self.agent_executor = AgentExecutor.from_agent_and_tools(
agent=LLMSingleActionAgent(
llm_chain=self.llm_chain,
output_parser=self.parse_output,
stop=["\nObservation:"]
),
tools=tools,
verbose=True,
memory=memory
)
def parse_output(self, llm_output: str):
regex = r"Action\s*\d*\s*:(.*?)\nAction\s*\d*\s*Input\s*\d*\s*:[\s]*(.*)"
match = re.search(regex, llm_output, re.DOTALL)
if not match:
return AgentFinish(
return_values={"output": llm_output.split("Final Answer:")[-1].strip()},
log=llm_output,
)
action = match.group(1).strip()
action_input = match.group(2)
return AgentAction(tool=action, tool_input=action_input.strip(" ").strip('"'), log=llm_output)
메모리 시스템과 컨텍스트 관리
AI 에이전트의 핵심 기능 중 하나는 이전 상호작용을 기억하고 컨텍스트를 유지하는 것입니다. LangChain에서 제공하는 다양한 메모리 타입을 활용해보겠습니다:
from langchain.memory import ConversationSummaryBufferMemory
from langchain.memory import VectorStoreRetrieverMemory
from langchain.embeddings.openai import OpenAIEmbeddings
from langchain.vectorstores import Chroma
class MemoryEnhancedAgent:
def __init__(self, llm, tools):
self.llm = llm
self.tools = tools
# 요약 기반 메모리 (긴 대화 요약)
self.summary_memory = ConversationSummaryBufferMemory(
llm=llm,
max_token_limit=1000,
return_messages=True
)
# 벡터 기반 메모리 (의미적 검색)
embeddings = OpenAIEmbeddings()
vectorstore = Chroma(embedding_function=embeddings)
self.vector_memory = VectorStoreRetrieverMemory(
vectorstore=vectorstore,
memory_key="history",
input_key="input"
)
def save_context(self, inputs, outputs):
"""대화 컨텍스트 저장"""
self.summary_memory.save_context(inputs, outputs)
self.vector_memory.save_context(inputs, outputs)
def get_relevant_context(self, query, k=3):
"""관련 컨텍스트 검색"""
return self.vector_memory.load_memory_variables({"prompt": query})
도구 체인과 워크플로우 구성
복잡한 작업을 위해서는 여러 도구를 연결한 워크플로우가 필요합니다. 다음은 데이터 분석 워크플로우를 구현한 예시입니다:
from langchain.agents import Tool
import pandas as pd
import matplotlib.pyplot as plt
import io
import base64
class DataAnalysisTools:
def __init__(self):
self.data_store = {}
def load_data(self, file_path_or_url):
"""데이터 로드 도구"""
try:
if file_path_or_url.endswith('.csv'):
df = pd.read_csv(file_path_or_url)
elif file_path_or_url.endswith('.json'):
df = pd.read_json(file_path_or_url)
else:
return "지원하지 않는 파일 형식입니다."
data_id = f"dataset_{len(self.data_store)}"
self.data_store[data_id] = df
return f"데이터 로드 완료: {data_id} (행: {len(df)}, 열: {len(df.columns)})"
except Exception as e:
return f"데이터 로드 실패: {str(e)}"
def analyze_data(self, data_id, analysis_type):
"""데이터 분석 도구"""
if data_id not in self.data_store:
return "존재하지 않는 데이터셋입니다."
df = self.data_store[data_id]
if analysis_type == "describe":
return df.describe().to_string()
elif analysis_type == "info":
buffer = io.StringIO()
df.info(buf=buffer)
return buffer.getvalue()
elif analysis_type == "head":
return df.head().to_string()
else:
return "지원하지 않는 분석 타입입니다."
def create_visualization(self, data_id, chart_type, x_column, y_column=None):
"""시각화 도구"""
if data_id not in self.data_store:
return "존재하지 않는 데이터셋입니다."
df = self.data_store[data_id]
plt.figure(figsize=(10, 6))
if chart_type == "histogram":
plt.hist(df[x_column], bins=30)
plt.xlabel(x_column)
plt.ylabel('빈도')
elif chart_type == "scatter" and y_column:
plt.scatter(df[x_column], df[y_column])
plt.xlabel(x_column)
plt.ylabel(y_column)
else:
return "지원하지 않는 차트 타입이거나 필수 매개변수가 누락되었습니다."
plt.title(f"{chart_type.title()} Chart")
# 이미지를 base64로 인코딩
buffer = io.BytesIO()
plt.savefig(buffer, format='png')
buffer.seek(0)
image_base64 = base64.b64encode(buffer.getvalue()).decode()
plt.close()
return f"시각화 생성 완료. Base64 이미지 길이: {len(image_base64)}"
# 도구 체인 구성
data_tools = DataAnalysisTools()
analysis_tools = [
Tool(
name="LoadData",
func=data_tools.load_data,
description="CSV나 JSON 파일을 로드합니다. 파일 경로나 URL을 입력하세요."
),
Tool(
name="AnalyzeData",
func=lambda x: data_tools.analyze_data(*x.split(',')),
description="데이터를 분석합니다. 'data_id,analysis_type' 형식으로 입력하세요."
),
Tool(
name="CreateVisualization",
func=lambda x: data_tools.create_visualization(*x.split(',')),
description="시각화를 생성합니다. 'data_id,chart_type,x_column,y_column' 형식으로 입력하세요."
)
]
에러 처리와 예외 상황 관리
프로덕션 환경에서는 robust한 에러 처리가 필수적입니다. 다음은 포괄적인 에러 처리 시스템을 구현한 예시입니다:
import logging
from typing import Optional, Dict, Any
from langchain.callbacks import BaseCallbackHandler
class ErrorHandlingAgent:
def __init__(self, llm, tools, max_retries=3):
self.llm = llm
self.tools = tools
self.max_retries = max_retries
self.logger = self.setup_logger()
def setup_logger(self):
logger = logging.getLogger('ai_agent')
logger.setLevel(logging.INFO)
handler = logging.StreamHandler()
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
handler.setFormatter(formatter)
logger.addHandler(handler)
return logger
def safe_execute(self, query: str) -> Dict[str, Any]:
"""안전한 에이전트 실행"""
for attempt in range(self.max_retries):
try:
self.logger.info(f"에이전트 실행 시도 {attempt + 1}/{self.max_retries}")
# 입력 검증
if not self.validate_input(query):
return {
"success": False,
"error": "유효하지 않은 입력입니다.",
"attempt": attempt + 1
}
# 에이전트 실행
result = self.agent_executor.run(query)
self.logger.info("에이전트 실행 성공")
return {
"success": True,
"result": result,
"attempt": attempt + 1
}
except Exception as e:
self.logger.error(f"시도 {attempt + 1} 실패: {str(e)}")
if attempt == self.max_retries - 1:
return {
"success": False,
"error": f"최대 재시도 횟수 초과: {str(e)}",
"attempt": attempt + 1
}
# 재시도 전 대기
import time
time.sleep(2 ** attempt) # 지수 백오프
def validate_input(self, query: str) -> bool:
"""입력 검증"""
if not query or len(query.strip()) == 0:
return False
if len(query) > 10000: # 너무 긴 입력 제한
return False
# 악성 패턴 검사
malicious_patterns = ['rm -rf', 'DELETE FROM', 'DROP TABLE']
for pattern in malicious_patterns:
if pattern.lower() in query.lower():
return False
return True
class AgentCallbackHandler(BaseCallbackHandler):
"""에이전트 실행 모니터링"""
def on_agent_action(self, action, **kwargs):
print(f"🤖 Action: {action.tool} - {action.tool_input}")
def on_agent_finish(self, finish, **kwargs):
print(f"✅ Agent finished: {finish.return_values}")
def on_tool_start(self, serialized, input_str, **kwargs):
print(f"🔧 Tool started: {serialized['name']} with input: {input_str}")
def on_tool_end(self, output, **kwargs):
print(f"🔧 Tool finished with output: {output}")
def on_tool_error(self, error, **kwargs):
print(f"❌ Tool error: {error}")
성능 최적화와 모니터링
AI 에이전트의 성능을 최적화하고 모니터링하는 것은 매우 중요합니다. 다음은 성능 최적화 기법들입니다:
import time
from functools import wraps
from typing import List, Dict
import asyncio
from langchain.cache import InMemoryCache
from langchain.globals import set_llm_cache
class PerformanceOptimizedAgent:
def __init__(self, llm, tools):
# LLM 캐싱 설정
set_llm_cache(InMemoryCache())
self.llm = llm
self.tools = tools
self.performance_metrics = {
'total_requests': 0,
'total_time': 0,
'cache_hits': 0,
'tool_usage': {}
}
def performance_monitor(func):
"""성능 모니터링 데코레이터"""
@wraps(func)
def wrapper(self, *args, **kwargs):
start_time = time.time()
try:
result = func(self, *args, **kwargs)
self.performance_metrics['total_requests'] += 1
execution_time = time.time() - start_time
self.performance_metrics['total_time'] += execution_time
return result
except Exception as e:
self.logger.error(f"성능 모니터링 중 오류: {e}")
raise
return wrapper
@performance_monitor
def execute_with_monitoring(self, query: str):
"""모니터링과 함께 실행"""
return self.agent_executor.run(query)
def get_performance_report(self) -> Dict:
"""성능 리포트 생성"""
avg_time = (self.performance_metrics['total_time'] /
self.performance_metrics['total_requests']
if self.performance_metrics['total_requests'] > 0 else 0)
return {
'total_requests': self.performance_metrics['total_requests'],
'average_response_time': round(avg_time, 2),
'cache_hit_rate': (self.performance_metrics['cache_hits'] /
self.performance_metrics['total_requests']
if self.performance_metrics['total_requests'] > 0 else 0),
'tool_usage_stats': self.performance_metrics['tool_usage']
}
async def batch_execute(self, queries: List[str]) -> List[Dict]:
"""배치 처리로 성능 향상"""
async def execute_single(query):
loop = asyncio.get_event_loop()
return await loop.run_in_executor(None, self.execute_with_monitoring, query)
tasks = [execute_single(query) for query in queries]
results = await asyncio.gather(*tasks, return_exceptions=True)
return results
마무리
LangChain을 활용한 AI 에이전트 개발은 단순한 챗봇을 넘어서 실제 업무를 자동화할 수 있는 강력한 도구를 만들 수 있게 해줍니다. 기본적인 에이전트 구성부터 고급 패턴, 메모리 관리, 에러 처리, 성능 최적화까지 체계적으로 접근하면 프로덕션 환경에서도 안정적으로 동작하는 AI 에이전트를 구축할 수 있습니다. 지속적인 모니터링과 개선을 통해 더욱 지능적이고 효율적인 AI 에이전트를 만들어 나가시기 바랍니다.
관련 게시글
LLM Fine-tuning vs RAG: 최적의 AI 전략 선택 가이드
LLM 개발 시 Fine-tuning과 RAG 중 어떤 전략을 선택해야 할지 고민이신가요? 각 방법론의 장단점, 핵심 원리, 실제 구현 코드, 그리고 프로젝트 요구사항에 따른 최적의 선택 기준을 AI/ML 개발자 관점에서 심층적으로 다룹니다.
Fine-tuning vs. RAG: LLM 애플리케이션 최적화 선택 가이드
LLM 애플리케이션 개발 시 Fine-tuning과 RAG 중 어떤 전략을 선택해야 할지 고민이신가요? 이 가이드에서 두 기술의 장단점, 핵심 비교, 그리고 실제 시나리오별 선택 기준을 심층적으로 분석하여 최적의 결정에 도움을 드립니다.
LangChain AI Agent 심층 가이드: LLM 기반 자율 에이전트 구축
LangChain을 활용하여 LLM 기반의 AI 에이전트를 구축하는 방법에 대해 심층적으로 다룹니다. ReAct 패턴, Tool 사용법, Memory 관리 등 실제 구현 예시와 함께 최신 개발 트렌드를 소개합니다.