LangChain Agent vs LangGraph: Super-Sub 에이전트 구조 구현 비교
들어가며
안녕하세요, 기술 블로그 독자 여러분! 오늘은 AI 에이전트 시스템을 구축할 때 많이 사용되는 두 가지 접근 방식에 대해 알아보려고 합니다. 바로 LangChain Agent만을 사용한 Super-Sub 구조와 LangGraph를 활용한 Super-Sub 구조의 차이점입니다. 두 방식 모두 복잡한 작업을 여러 전문 에이전트에게 분배하는 구조를 가지고
있지만, 구현 방식과 특징에는 큰 차이가 있습니다.
LangChain Agent로 구현하는 Super-Sub 구조
LangChain은 대규모 언어 모델(LLM)을 활용한 애플리케이션 개발을 위한 프레임워크로, 다양한 도구와 에이전트를 쉽게 구현할 수 있게 해줍니다.
구현 방식
LangChain만으로 Super-Sub 구조를 구현할 때는 주로 다음과 같은 방식을 사용합니다:
class SuperAgent:
def __init__(self, model_name: str = DEFAULT_MODEL):
self.llm = ChatGoogleGenerativeAI(model=model_name)
# 서브 에이전트들 초기화
self.db_agent = DBAgent(self.llm)
self.search_agent = SearchAgent(self.llm)
self.rag_agent = RAGAgent()
# 슈퍼 에이전트용 도구 설정
self.tools = [
Tool(
name="DB_작업",
func=self.db_agent.run,
description="데이터베이스 관련 작업이 필요할 때 사용"
),
Tool(
name="정보_검색",
func=self.search_agent.run,
description="웹에서 정보를 검색해야 할 때 사용"
),
Tool(
name="문서_검색(RAG)",
func=self.rag_agent.answer_question,
description="저장된 문서에서 관련 정보를 검색할 때 사용"
)
]
# 슈퍼 에이전트 생성
prompt = hub.pull("hwchase17/react")
self.agent = create_react_agent(self.llm, self.tools, prompt)
self.agent_executor = AgentExecutor(agent=self.agent, tools=self.tools, verbose=True)
이 방식에서는 슈퍼 에이전트가 Tool 객체 형태로 서브 에이전트들을 관리하며, 사용자 질문에 따라 적절한 도구(서브 에이전트)를 선택하여 작업을 수행합니다.
장점
- 구현이 간단합니다: LangChain의 Tool과 AgentExecutor를 사용하면 비교적 적은 코드로 구현할 수 있습니다.
- 직관적인 구조: 슈퍼 에이전트가 도구를 선택하는 방식이 직관적이고 이해하기 쉽습니다.
- 빠른 프로토타이핑: 새로운 기능이나 에이전트를 추가하기 쉬워 빠르게 프로토타입을 만들 수 있습니다.
단점
- 복잡한 워크플로우 관리의 어려움: 여러 에이전트 간의 복잡한 상호작용이나 조건부 실행 흐름을 관리하기 어렵습니다.
- 상태 관리의 한계: 에이전트 간 상태 공유나 복잡한 상태 전이를 구현하기 어렵습니다.
- 디버깅의 어려움: 복잡한 에이전트 시스템에서 문제가 발생했을 때 디버깅이 어려울 수 있습니다.
LangGraph로 구현하는 Super-Sub 구조
LangGraph는 LangChain 위에 구축된 라이브러리로, 복잡한 에이전트 시스템을 그래프 기반으로 구현할 수 있게 해줍니다.
구현 방식
LangGraph를 사용한 Super-Sub 구조는 다음과 같이 구현할 수 있습니다:
# 상태 정의
class AgentState(TypedDict):
question: str
messages: Annotated[Sequence[BaseMessage], operator.add]
current_agent: str
agent_results: Dict[str, Any]
final_answer: Optional[str]
error: Optional[str]
iteration_count: int
# 그래프 생성 함수
def create_agent_graph(llm: Optional[BaseLanguageModel] = None) -> StateGraph:
# 기본 LLM 설정
if llm is None:
llm = ChatGoogleGenerativeAI(model=DEFAULT_MODEL)
# 노드 생성
super_agent_node = SuperAgentNode(llm)
db_agent_node = DBAgentNode(llm)
rag_agent_node = RAGAgentNode()
error_handler_node = ErrorHandlerNode(llm)
# 그래프 생성
workflow = StateGraph(AgentState)
# 노드 추가
workflow.add_node(AgentType.SUPER, super_agent_node)
workflow.add_node(AgentType.DB, db_agent_node)
workflow.add_node(AgentType.RAG, rag_agent_node)
workflow.add_node("error_handler", error_handler_node)
# 조건부 엣지 추가
workflow.add_conditional_edges(
AgentType.SUPER,
lambda state: "error_handler" if state.get("error") else state["current_agent"],
{
"error_handler": "error_handler",
AgentType.DB: AgentType.DB,
AgentType.RAG: AgentType.RAG,
END: END
}
)
# 각 에이전트에서 슈퍼 에이전트로 돌아가는 엣지
workflow.add_edge(AgentType.DB, AgentType.SUPER)
workflow.add_edge(AgentType.RAG, AgentType.SUPER)
# 시작 노드 설정
workflow.set_entry_point(AgentType.SUPER)
return workflow
이 방식에서는 에이전트 시스템을 그래프로 모델링하며, 각 에이전트는 노드로, 에이전트 간 전환은 엣지로 표현됩니다. 상태(State)를 통해 에이전트 간 정보를 공유하고, 조건부 엣지를 통해 복잡한 워크플로우를 구현할 수 있습니다.
장점
- 복잡한 워크플로우 관리: 그래프 기반 접근 방식으로 복잡한 에이전트 간 상호작용과 조건부 실행 흐름을 명확하게 모델링할 수 있습니다.
- 상태 관리의 용이성: TypedDict를 사용한 상태 정의로 에이전트 간 상태 공유와 전이를 체계적으로 관리할 수 있습니다.
- 시각화 가능: 그래프 구조를 시각화하여 복잡한 에이전트 시스템의 흐름을 쉽게 이해하고 디버깅할 수 있습니다.
- 에러 처리의 유연성: 에러 처리 노드를 별도로 구현하여 다양한 예외 상황에 대응할 수 있습니다.
- 반복 실행 제어: 반복 횟수를 추적하고 제한하여 무한 루프를 방지할 수 있습니다.
- 확장성: 새로운 에이전트나 기능을 그래프에 쉽게 추가할 수 있어 시스템 확장이 용이합니다.
단점
- 구현 복잡성: LangChain만 사용하는 것보다 초기 구현이 더 복잡하고 코드량이 많습니다.
- 학습 곡선: 그래프 기반 접근 방식과 상태 관리에 대한 이해가 필요하여 학습 곡선이 높을 수 있습니다.
- 오버헤드: 간단한 작업에는 불필요한 복잡성과 오버헤드가 발생할 수 있습니다.
- 디버깅 복잡성: 그래프 구조가 복잡해질수록 디버깅이 어려워질 수 있습니다.
실제 사용 사례 비교
LangChain Agent만 사용한 경우
async def process_message(self, message: str) -> str:
"""메시지 처리 및 응답 생성"""
# RAG 관련 키워드 확인
rag_keywords = ["찾아줘", "검색해줘", "관련 정보", "문서에서", "문서 검색"]
try:
if any(keyword in message for keyword in rag_keywords):
# RAG 도구 사용
context = self.rag_agent.answer_question(message)
prompt = f"""다음 컨텍스트를 기반으로 질문에 답변해주세요:
컨텍스트:
{context}
질문: {message}
"""
return await self._generate_response(prompt)
# 일반적인 에이전트 실행
result = await self.agent_executor.ainvoke({"input": message})
return result["output"]
except Exception as e:
return f"죄송합니다. 오류가 발생했습니다: {str(e)}"
이 방식에서는 키워드 기반으로 간단하게 에이전트를 선택하거나, AgentExecutor에게 도구 선택을 위임합니다. 구현은 간단하지만, 복잡한 의사결정이나 에이전트 간 상호작용을 구현하기 어렵습니다.
LangGraph를 사용한 경우
def decide_agent(self, state: AgentState) -> Dict[str, Any]:
"""어떤 에이전트를 사용할지 결정하는 함수"""
# 반복 횟수 확인 - 최대 반복 횟수에 도달하면 강제 종료
iteration_count = state.get("iteration_count", 0)
if iteration_count >= 10:
print(f"[슈퍼 에이전트] 최대 반복 횟수({iteration_count})에 도달하여 현재까지의 결과로 답변합니다.")
return {
"reasoning": f"최대 반복 횟수({iteration_count})에 도달하여 현재까지의 결과로 답변합니다.",
"selected_agent": "super_agent",
"is_complete": True
}
# 현재까지의 에이전트 결과를 문자열로 변환
agent_results_str = json.dumps(state["agent_results"], ensure_ascii=False, indent=2)
# 이미 시도한 에이전트 확인
tried_agents = set(state["agent_results"].keys())
# DB 에이전트 결과 확인
if "db_agent" in state["agent_results"]:
db_result = state["agent_results"]["db_agent"]
# 결과 분석 로직...
# RAG 에이전트 결과 확인
if "rag_agent" in state["agent_results"]:
rag_result = state["agent_results"]["rag_agent"]
# 결과 분석 로직...
# 아직 어떤 에이전트도 시도하지 않았다면, 질문 유형에 따라 에이전트 선택
if not tried_agents:
# 프롬프트에 질문과 에이전트 결과 전달
chain = self.agent_selection_prompt | self.llm | StrOutputParser()
result = chain.invoke({
"question": state["question"],
"agent_results": agent_results_str
})
# 결과 파싱 및 반환...
LangGraph를 사용하면 상태 기반으로 복잡한 의사결정 로직을 구현할 수 있습니다. 이전 에이전트의 결과를 분석하고, 반복 횟수를 추적하며, 다양한 조건에 따라 다음 에이전트를 선택할 수 있습니다.
어떤 방식을 선택해야 할까?
LangChain Agent만 사용하는 것이 좋은 경우
- 간단한 에이전트 시스템: 복잡한 워크플로우가 필요 없는 간단한 에이전트 시스템
- 빠른 프로토타이핑: 빠르게 개념 증명(PoC)을 구현해야 하는 경우
- 리소스 제약: 개발 리소스나 시간이 제한적인 경우
- 단일 에이전트 중심: 여러 에이전트 간 복잡한 상호작용이 적은 경우
LangGraph를 사용하는 것이 좋은 경우
- 복잡한 워크플로우: 여러 에이전트 간 복잡한 상호작용과 조건부 실행이 필요한 경우
- 상태 기반 의사결정: 이전 결과와 상태에 기반한 복잡한 의사결정이 필요한 경우
- 확장성 중시: 시스템이 점진적으로 확장될 것으로 예상되는 경우
- 엄격한 에러 처리: 다양한 예외 상황에 대한 체계적인 처리가 필요한 경우
- 반복 실행 제어: 에이전트 간 반복적인 상호작용을 제어해야 하는 경우
결론
LangChain Agent만 사용하는 방식은 구현이 간단하고 직관적이지만, 복잡한 워크플로우와 상태 관리에 한계가 있습니다. 반면 LangGraph를 사용하는 방식은 초기 구현이 복잡하지만, 복잡한 에이전트 시스템을 체계적으로 모델링하고 관리할 수 있습니다.
프로젝트의 복잡성, 확장성 요구사항, 개발 리소스 등을 고려하여 적절한 방식을 선택하는 것이 중요합니다. 간단한 프로젝트는 LangChain Agent만으로 충분할 수 있지만, 복잡한 에이전트 시스템을 구축하려면 LangGraph의 그래프 기반 접근 방식이 더 적합할 수 있습니다.
우리 회사의 경우, 초기에는 LangChain Agent만으로 프로토타입을 빠르게 개발했지만, 시스템이 복잡해지면서 LangGraph로 마이그레이션하여 더 체계적인 에이전트 시스템을 구축할 수 있었습니다. 두 접근 방식의 장단점을 이해하고 프로젝트 요구사항에 맞게 선택하는 것이 성공적인 AI 에이전트 시스템 구축의 핵심입니다.
'AI' 카테고리의 다른 글
LangGraph란? (0) | 2025.03.21 |
---|---|
AI 에이전트 vs LangChain 에이전트 (0) | 2025.03.21 |
[AI] LangChain 에이전트 구현하기: ReAct 프레임워크 기반 자동화 시스템 (1) | 2025.03.06 |
[AI] Google Generative AI 모델 사용하기: 모델 리스트 조회 및 프롬프트 테스트 (0) | 2025.03.06 |
[AI] 에이전트란 무엇인가? - (초보자를 위한 쉬운 설명) (0) | 2025.03.05 |