LangChain Agents Architecture
LangChain 에이전트의 구체적 아키텍처, 정적/동적 구성, Middleware 확장성.
설명
Agent의 기본 루프
LangChain 에이전트는 메시지 기반 상태 머신이다:
사용자 입력
↓
[1] AgentState 업데이트 (messages 리스트에 추가)
↓
[2] LLM 호출 (상태 전체를 프롬프트 컨텍스트로 제공)
↓
[3] LLM 응답 파싱 → 도구 호출 또는 최종 답변 판단
↓
[4-a] 도구 호출: Tool Executor가 실행 → 결과를 상태에 추가 → 반복
[4-b] 최종 답변: 사용자에게 반환
핵심: 모든 정보가 메시지 리스트에 누적된다. 따라서 멀티턴 대화에서도 전체 컨텍스트 보존.
정적 구성 (Static)
모델, 도구, 시스템 프롬프트를 agent 생성 시점에 고정:
from langchain.agents import create_agent
from langchain.chat_models import ChatAnthropic
model = ChatAnthropic(model="claude-opus", temperature=0.5)
tools = [calculator, wikipedia_search, database_query]
system_prompt = "You are a helpful assistant with access to tools."
agent = create_agent(
model=model,
tools=tools,
system_prompt=system_prompt
)장점:
- 간단, 예측 가능
- 프로덕션 배포에 안정적
단점:
- 런타임에 동작 조정 불가
- 모든 사용자/컨텍스트에 동일 설정
동적 구성 (Dynamic)
런타임 조건(사용자 역할, 대화 복잡도 등)에 따라 모델/도구를 변경:
동적 모델 선택
from langchain.agents import wrap_model_call
@wrap_model_call
def select_model(state):
message_count = len(state["messages"])
if message_count > 10: # 복잡한 대화
return ChatAnthropic(model="claude-opus", temperature=0.7)
else: # 단순 질문
return ChatAnthropic(model="claude-haiku", temperature=0.5)
agent = create_agent(
model=select_model,
tools=tools
)효과:
- 저가 모델로 단순 쿼리 처리 → 비용 절감
- 복잡 쿼리는 강력한 모델 사용 → 정확도 ↑
동적 도구 선택
def select_tools(state, user_role):
base_tools = [calculator, search]
if user_role == "admin":
return base_tools + [database_query, system_admin]
elif user_role == "user":
return base_tools # 관리 도구 제외
else:
return []효과: 권한에 따른 도구 접근 제어, 보안 강화
동적 시스템 프롬프트
@dynamic_prompt
def generate_prompt(state, user_role):
base_instruction = "You are a helpful assistant."
if user_role == "admin":
return base_instruction + "\nYou have access to admin tools."
else:
return base_instruction + "\nYou cannot access admin functions."
agent = create_agent(
model=model,
tools=tools,
system_prompt=generate_prompt
)Tool Use 패턴
Tool 등록
@tool
def calculator(expression: str) -> str:
"""Calculate a mathematical expression"""
return str(eval(expression))
@tool
def search_wiki(query: str) -> str:
"""Search Wikipedia for information"""
...
tools = [calculator, search_wiki]Tool 호출 전략
순차 호출 (Sequential)
도구 1 → 결과 → 도구 2 (기본값, 안정적)
병렬 호출 (Parallel)
여러 도구 동시 실행 (I/O 작업 효율화)
# LangChain이 자동으로 병렬화 감지
tools_can_run_in_parallel = True
agent.invoke({...}, parallel_execution=True)조건부 호출
이전 결과에 따라 도구 동적 선택
Memory와 State
AgentState 기본 구조
class AgentState(BaseModel):
messages: List[BaseMessage] # 메시지 누적AgentState 확장
커스텀 정보 추적:
from langchain.schema import AgentState
class CustomAgentState(AgentState):
user_preferences: dict # 사용자 선호도
session_goals: list[str] # 현재 세션 목표
conversation_sentiment: str # 대화 분위기효과: 에이전트가 사용자 문맥을 더 깊이 이해 가능
Middleware 패턴
모델 호출 전처리
@agent.middleware
def preprocess_state(state):
# 토큰 수 제한 적용
if len(state["messages"]) > 50:
# 오래된 메시지 압축
state["messages"] = compress_old_messages(state["messages"])
return state도구 오류 처리
@agent.on_tool_error
def handle_tool_error(error, tool_name):
# 도구 실패 시 대체 도구 실행
if tool_name == "database_query":
return "Database unavailable. Using cached results."응답 검증
@agent.post_process
def validate_response(response):
# 환각 탐지
if "I'm not sure" in response:
# Reflection 패턴: 재시도
return agent.invoke({...}, reflect=True)
return response실전 패턴
Reflection (자체 검증)
고위험 출력(코드, 법률 문서)을 LLM이 스스로 검증:
response = agent.invoke({...})
# Reflection
reflection = model.invoke(f"Is this answer correct? {response}")
if "correct" in reflection.lower():
return response
else:
return agent.invoke({...}, retry=True)Tool Selector
복잡한 쿼리일 때 어떤 도구를 먼저 쓸지 고민:
# 에이전트가 자동으로 우선순위 결정
agent.invoke({
"messages": [{
"role": "user",
"content": "Complex query..."
}]
})
# → 에이전트가 tool_a 먼저, tool_b 나중에 결정관련 개념
- agentic-ai-patterns — Tool Use, Reflection, Routing 패턴
- rag — Tool로서의 RAG 검색