LangChain과 LangGraph에서의 Agent 생성 메서드 비교분석

박병현·2025년 4월 3일
0

LangChain과 LangGraph에서의 Agent 생성 방식 비교분석

본 게시글은 LLM을 통해 작성된 게시글입니다.

LangChain과 LangGraph는 LLM 기반 애플리케이션을 개발할 때 자주 사용되는 프레임워크입니다. 특히 Agent를 구현하는 방식에 있어서 각각의 접근법과 특징이 있습니다. 이번 포스트에서는 initialize_agent, create_react_agent, 그리고 create_supervisor_agent의 세 가지 함수를 비교 분석해 보겠습니다.

1. 각 함수의 개요

initialize_agent (LangChain)

from langchain.agents.initialize import initialize_agent

initialize_agent는 LangChain의 초기 버전에서 도입된 에이전트 생성 방식으로, 데코레이터를 통해 이미 deprecated 되었음을 알 수 있습니다. 다양한 유형의 에이전트를 생성할 수 있으며, AgentExecutor 객체를 반환합니다.

해당 링크에서 확인해보니, 레거시임메서드임을 확인할 수 있었다. LCEL기반의 create_react_agent를 권장하는 문구가 있다.

create_react_agent (LangChain)

from langgraph.prebuilt import create_react_agent

create_react_agent는 ReAct(Reasoning and Acting) 패턴을 구현한 에이전트를 생성합니다. 이는 initialize_agent보다 더 현대적인 접근법으로, Runnable 인터페이스를 기반으로 합니다. 코드 주석에서도 언급했듯이, 프로덕션 환경에서는 LangGraph의 구현을 사용하는 것이 권장됩니다. LangGraph의 create_react_agentCompiledGraph를 반환하는데, 이는 실행 가능한 그래프 인스턴스입니다.


create_react_agent 를 통해 생성된 Graph를 mermaid로 통해 보면 위와 같은 구조를 갖는다.

create_supervisor_agent (LangGraph)

from langgraph_supervisor import create_supervisor

create_supervisor_agent는 LangGraph에서 제공하는 함수로, 여러 에이전트를 관리하고 조율하는 슈퍼바이저를 생성합니다. 이는 가장 최신의 접근법으로, StateGraph를 반환합니다. 이는 아직 컴파일되지 않은 그래프 정의로, 사용하기 전에 .compile() 메서드를 호출하여 CompiledGraph로 변환해야 합니다.

그림에서 Supervisor 아래의 Agent들은 create_react_agent로 생성된 하위 에이전트들로, 각자 독립적인 SubGraph를 형성합니다. 또는 더 단순한 구현에서는 Tool로 대체될 수도 있습니다.

langgraph-supervisor github에서 소스코드를 확인할 수 있으며, 2025년 2월쯤 Release된, LangGraph 0.3버전에서 추가된 메서드입니다.

2. 다중 에이전트 시스템(Multi-Agent System)의 이해

create_supervisor_agent를 이해하기 위해서는 먼저 다중 에이전트 시스템의 개념을 명확히 해야 합니다. 다중 에이전트 시스템이란 단순히 여러 도구(tools)를 하나의 에이전트가 사용하는 것이 아니라, 서로 다른 역할과 전문성을 가진 여러 독립적인 에이전트가 협업하여 복잡한 작업을 수행하는 시스템을 의미합니다.

다중 에이전트 시스템의 주요 특징:

  1. 역할 및 책임의 분리:

    • 각 에이전트는 특정 도메인에 특화된 전문가 역할을 수행합니다.
    • 예: 연구 에이전트, 수학 에이전트, 글쓰기 에이전트 등
  2. 독립적인 개발 및 확장:

    • 각 에이전트는 독립적으로 개발하고 테스트할 수 있습니다.
    • 팀원들이 각자 담당 에이전트를 개발할 수 있어 협업이 용이합니다.
    • 새로운 기능이 필요할 때 전체 시스템을 수정하지 않고 새 에이전트를 추가할 수 있습니다.
  3. 상태(State) 관리:

    • 각 에이전트는 자체적인 상태를 유지하며, 시스템 전체의 상태도 관리해야 합니다.
    • 현재 어떤 에이전트가 활성화되어 있는지, 대화 컨텍스트는 어떻게 유지할지 등의 복잡한 상태 관리가 필요합니다.
  4. SubGraph 구조:

    • 각 에이전트는 독립적인 SubGraph로 구현될 수 있습니다.
    • 이러한 SubGraph들이 슈퍼바이저에 의해 조율되는 형태로 시스템이 구성됩니다.

단일 에이전트 vs 다중 에이전트 비교:

특성단일 에이전트 (create_react_agent)다중 에이전트 (create_supervisor_agent)
구조하나의 에이전트가 모든 도구 사용여러 전문 에이전트가 각자의 도구 사용
상태 관리단일 상태 흐름복잡한 상태 전이 및 에이전트 간 전환
확장성도구를 추가하여 확장새로운 에이전트를 추가하여 확장
모듈성제한적높음 (각 에이전트는 독립적인 모듈)
개발 방식중앙집중식분산식 (여러 개발자가 각 에이전트 개발 가능)

3. 함수 시그니처 및 파라미터 비교

각 함수는 서로 다른 파라미터와 반환 값을 가지고 있습니다. 아래에서 주요 차이점을 살펴보겠습니다.

공통 파라미터

  • 모델: 세 함수 모두 언어 모델을 필요로 합니다. initialize_agentcreate_react_agentllm이라는 이름으로, create_supervisor_agentmodel이라는 이름으로 받습니다.
  • 도구: 세 함수 모두 에이전트가 사용할 수 있는 도구(tools)를 필요로 합니다.

주요 차이점

  • 에이전트 타입: initialize_agentagent 파라미터를 통해 다양한 에이전트 타입(AgentType)을 지정할 수 있습니다.
  • 프롬프트: create_react_agentcreate_supervisor_agent는 명시적으로 프롬프트를 받습니다.
  • 에이전트 목록: create_supervisor_agent는 여러 에이전트(agents)를 입력으로 받습니다. 이는 다중 에이전트 구조를 반영합니다.
  • 병렬 도구 호출: create_supervisor_agentparallel_tool_calls 파라미터를 통해 병렬 도구 호출을 지원합니다. 이를 통해 여러 에이전트에 동시에 작업을 할당할 수 있습니다.
  • 출력 모드: create_supervisor_agentoutput_mode 파라미터를 통해 메시지 히스토리에 에이전트 출력을 추가하는 방식을 지정할 수 있습니다.
  • 반환 타입:
    • initialize_agent: AgentExecutor 반환 (바로 사용 가능)
    • create_react_agent: CompiledGraph 반환 (바로 사용 가능)
    • create_supervisor_agent: StateGraph 반환 (사용 전 .compile() 필요)

4. 함수 구현 방식 비교

각 함수는 서로 다른 방식으로 에이전트를 구현합니다. 이제 그 차이점을 자세히 살펴보겠습니다.

initialize_agent의 구현 방식

앞서 언급했듯이, 이제 LangChain에서는 Legacy 메서드입니다.

initialize_agent는 가장 전통적인 방식으로 에이전트를 생성합니다:

  1. 에이전트 타입(AgentType)이나 경로(agent_path)를 통해 에이전트 클래스를 결정합니다.
  2. 해당 클래스의 from_llm_and_tools 메서드를 호출하여 에이전트 객체를 생성합니다.
  3. AgentExecutor.from_agent_and_tools 메서드를 통해 최종 에이전트 실행기를 반환합니다.

이 접근법은 직관적이지만, 확장성과 유연성이 제한되어 있습니다.

# 에이전트 타입 또는 경로 결정
if agent is None and agent_path is None:
    agent = AgentType.ZERO_SHOT_REACT_DESCRIPTION
    
# 에이전트 객체 생성
agent_obj = agent_cls.from_llm_and_tools(
    llm, tools, callback_manager=callback_manager, **agent_kwargs
)

# AgentExecutor 반환
return AgentExecutor.from_agent_and_tools(
    agent=agent_obj,
    tools=tools,
    callback_manager=callback_manager,
    tags=tags_,
    **kwargs,
)

create_react_agent의 구현 방식

create_react_agent는 Runnable 인터페이스를 기반으로 한 더 현대적인 접근법을 사용합니다:

  1. 필요한 프롬프트 변수가 있는지 확인합니다.
  2. 도구 렌더링을 통해 프롬프트를, 도구 목록의 일부로 사용합니다.
  3. 모델의 중지 시퀀스를 설정합니다.
  4. RunnablePassthrough를 사용하여 파이프라인을 구성합니다.

이 접근법은 함수형 프로그래밍 스타일을 사용하며, 파이프라인 방식으로 데이터를 처리합니다.

# 프롬프트 변수 확인
missing_vars = {"tools", "tool_names", "agent_scratchpad"}.difference(
    prompt.input_variables + list(prompt.partial_variables)
)

# 프롬프트 준비
prompt = prompt.partial(
    tools=tools_renderer(list(tools)),
    tool_names=", ".join([t.name for t in tools]),
)

# Runnable 파이프라인 구성
agent = (
    RunnablePassthrough.assign(
        agent_scratchpad=lambda x: format_log_to_str(x["intermediate_steps"]),
    )
    | prompt
    | llm_with_stop
    | output_parser
)

create_supervisor_agent의 구현 방식

create_supervisor_agent는 LangGraph의 StateGraph를 사용하여 여러 에이전트를 관리하는 더 복잡한 시스템을 구현합니다:

  1. 에이전트 이름의 유효성을 검사합니다.
  2. Handoff 도구를 설정합니다 (에이전트 간 작업 전환용).
  3. 모델에 도구를 바인딩합니다.
  4. StateGraph를 구성하고 노드와 엣지를 추가합니다.

이 접근법은 그래프 기반 워크플로우를 사용하여 여러 에이전트 간의 복잡한 상호작용을 가능하게 합니다. 각 에이전트는 SubGraph로 구현되며, 이러한 SubGraph들이 슈퍼바이저에 의해 조율됩니다.

# 에이전트 이름 검증
agent_names = set()
for agent in agents:
    if agent.name is None or agent.name == "LangGraph":
        raise ValueError(...)
    if agent.name in agent_names:
        raise ValueError(...)
    agent_names.add(agent.name)

# Handoff 도구 설정 (에이전트 간 작업 전환용)
handoff_destinations = [create_handoff_tool(agent_name=agent.name) for agent in agents]

# StateGraph 구성
builder = StateGraph(state_schema, config_schema=config_schema)
builder.add_node(supervisor_agent, destinations=tuple(agent_names) + (END,))
builder.add_edge(START, supervisor_agent.name)
for agent in agents:
    builder.add_node(...)
    builder.add_edge(agent.name, supervisor_agent.name)

5. Supervisor Agent와 ReAct 패턴의 관계

create_supervisor_agent와 ReAct 패턴의 관계는 다음과 같습니다:

Supervisor Agent와 ReAct 패턴

create_supervisor_agent내부적으로 ReAct 패턴을 활용합니다. 코드를 자세히 살펴보면:

supervisor_agent = create_react_agent(
    name=supervisor_name,
    model=model,
    tools=all_tools,
    prompt=prompt,
    state_schema=state_schema,
    response_format=response_format,
)

여기서 주목할 점은 슈퍼바이저 자체가 create_react_agent 함수를 통해 생성된다는 것입니다. 이는 슈퍼바이저도 ReAct 패턴을 기반으로 작동함을 의미합니다.

또한, 슈퍼바이저가 관리하는 개별 에이전트들도 일반적으로 ReAct 패턴을 사용합니다:

# 전문 에이전트 생성 예시
researcher = create_react_agent(
    name="researcher",
    model=model,
    tools=[...],  # 리서치 도구
)

ReAct 패턴이란?

ReAct 패턴은 "Reasoning and Acting"의 약자로, LLM이 다음과 같은 단계를 통해 문제를 해결하는 방식입니다:

  1. Reasoning(사고): 현재 상황을 분석하고 다음 단계를 계획합니다.
  2. Acting(행동): 계획에 따라 도구를 사용하거나 행동을 취합니다.
  3. Observation(관찰): 행동의 결과를 관찰합니다.
  4. 이 과정을 반복하며 최종 답변에 도달합니다.

Supervisor Agent에서의 ReAct 패턴 활용

create_supervisor_agent는 다음과 같이 ReAct 패턴을 확장하여 다중 에이전트 시스템에 적용합니다:

  1. 슈퍼바이저 ReAct: 슈퍼바이저 자체가 ReAct 패턴을 사용하여 문제를 분석하고, 어떤 하위 에이전트에게 작업을 위임할지 결정합니다.

  2. 하위 에이전트 ReAct: 각 하위 에이전트도 ReAct 패턴을 사용하여 자신에게 할당된 특정 작업을 처리합니다.

  3. 다단계 ReAct: 슈퍼바이저는 하위 에이전트의 결과를 관찰하고, 추가 작업이 필요한지 결정하여 전체 워크플로우를 조율합니다.

이 과정을 통해 슈퍼바이저는 문제를 분석(Reasoning)하고, 적절한 하위 에이전트에게 작업을 위임(Acting)한 후, 결과를 수집(Observation)하는 확장된 ReAct 패턴을 구현합니다.

Supervisor Agent와 ReAct의 주요 차이점

슈퍼바이저가 ReAct 패턴을 사용하지만, 일반적인 ReAct 에이전트와는 다음과 같은 차이점이 있습니다:

  1. 다중 에이전트 관리: 슈퍼바이저는 여러 ReAct 에이전트를 관리하고 조율합니다.
  2. 상태 관리 메커니즘: StateGraph를 통해 더 복잡한 상태 전이를 관리합니다.
  3. 병렬 처리 가능: parallel_tool_calls 파라미터를 통해 여러 하위 에이전트에 병렬로 작업을 할당할 수 있습니다.
  4. 에이전트 간 핸드오프: 특수한 핸드오프 메커니즘을 통해 에이전트 간 작업 전환을 관리합니다.
  5. SubGraph 구조: 각 에이전트는 독립적인 SubGraph로 구현되어 모듈성과 재사용성이 향상됩니다.

6. 사용 사례 및 적합한 상황

각 함수는 서로 다른 사용 사례와 상황에 적합합니다. 이제 언제 어떤 함수를 사용해야 하는지 살펴보겠습니다.

initialize_agent 사용 사례

  • 레거시 코드와의 호환성: 기존 LangChain 코드를 유지해야 하는 경우
  • 단순한 에이전트: 복잡한 상호작용이 필요 없는 간단한 단일 에이전트
  • 빠른 프로토타이핑: 빠르게 에이전트를 구현하고 테스트해야 하는 경우
from langchain.agents import initialize_agent, AgentType
from langchain_openai import ChatOpenAI

llm = ChatOpenAI(model_name="gpt-4o-mini", temperature=0)
tools = [...]  # 도구 목록

agent = initialize_agent(
    tools, 
    llm, 
    agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION,
    verbose=True
)

agent.run("주식 시장 동향을 분석해줘")

create_react_agent 사용 사례

  • ReAct 패턴 구현: 추론과 행동을 번갈아 수행하는 에이전트가 필요한 경우
  • Runnable 인터페이스 활용: LangChain의 최신 Runnable 인터페이스를 활용하고 싶은 경우
  • 커스텀 프롬프트 엔지니어링: 프롬프트를 세밀하게 제어해야 하는 경우
  • 단일 책임 원칙: 하나의 에이전트가 특정 도메인의 모든 작업을 처리해야 하는 경우
from langchain_openai import ChatOpenAI
from langchain.agents import AgentExecutor, create_react_agent
from langchain_core.prompts import PromptTemplate

# ReAct 패턴을 구현하는 프롬프트 템플릿
prompt = PromptTemplate.from_template("""다음 질문에 답변해주세요. 당신은 도구를 사용할 수 있습니다:

{tools}

질문에 답하기 위해 다음 형식을 사용하세요:

질문: 사용자의 질문
생각: 다음에 무엇을 해야 할지 생각합니다
행동: 사용할 도구 이름, 다음 중 하나여야 합니다 - [{tool_names}]
행동 입력: 도구에 전달할 입력
관찰: 도구의 결과
... (이 생각/행동/행동 입력/관찰 단계를 여러 번 반복할 수 있습니다)
생각: 이제 최종 답변을 알고 있습니다
최종 답변: 질문에 대한 최종 답변


질문: {input}""")

model = ChatOpenAI(model_name="gpt-4o-mini")
tools = [...]  # 도구 목록

agent = create_react_agent(model, tools, prompt)
agent_executor = AgentExecutor(agent=agent, tools=tools)

result = agent_executor.invoke({"input": "오늘 날씨 어때?"})

create_supervisor_agent 사용 사례

  • 다중 에이전트 시스템: 여러 전문 에이전트가 협력해야 하는 경우
  • 복잡한 워크플로우: 에이전트 간의 제어 흐름이 복잡한 경우
  • 상태 관리: 복잡한 상태와 컨텍스트를 관리해야 하는 경우
  • 병렬 처리: 여러 태스크를 병렬로 처리해야 하는 경우
  • 역할 분리: 개발자 간 책임 영역을 명확히 구분해야 하는 대규모 프로젝트
  • SubGraph 재사용: 여러 시스템에서 동일한 에이전트 기능을 재사용해야 하는 경우
from langgraph_supervisor import create_supervisor
from langgraph.prebuilt.chat_agent_executor import create_react_agent
from langchain_community.chat_models import ChatOpenAI

model = ChatOpenAI()

# 전문 에이전트 생성 (각각이 독립적인 SubGraph)
researcher = create_react_agent(
    name="researcher",
    model=model,
    tools=[web_search],  # 리서치 도구
)

math_agent = create_react_agent(
    name="math_expert",
    model=model,
    tools=[add, multiply],  # 수학 도구
)

# 슈퍼바이저 생성 (여러 SubGraph를 조율)
supervisor = create_supervisor(
    agents=[researcher, math_agent],
    model=model,
    prompt=(
        "You are a team supervisor managing a research expert and a math expert. "
        "For current events, use researcher. "
        "For math problems, use math_expert."
    ),
    parallel_tool_calls=True,  # 병렬 처리 활성화
    output_mode="last_message",  # 메시지 관리 방식 지정
)

# StateGraph를 CompiledGraph로 변환
graph = supervisor.compile()
result = graph.invoke({
    "messages": [{"content": "FAANG 회사들의 2024년 총 직원 수는?"}]
})

7. 결론: 어떤 함수를 선택해야 할까?

세 함수를 비교 분석한 결과, 다음과 같은 결론을 내릴 수 있습니다:

  1. initialize_agent:

    • 레거시 코드이므로 새 프로젝트에서는 사용을 피하는 것이 좋습니다.
    • 단순한 프로토타이핑이나 기존 코드와의 호환성이 필요한 경우에만 고려하세요.
    • AgentExecutor를 반환하여 바로 사용 가능합니다.
  2. create_react_agent:

    • 단일 에이전트를 구현하는 현대적인 방식입니다.
    • Runnable 인터페이스를 활용한 유연한 파이프라인을 구성할 수 있습니다.
    • 중간 복잡도의 태스크에 적합합니다.
    • CompiledGraph를 반환하여 바로 사용 가능합니다.
    • 명확한 단일 책임을 가진 에이전트에 적합합니다.
  3. create_supervisor_agent:

    • 다중 에이전트 시스템을 구현하는 가장 발전된 방식입니다.
    • 복잡한 워크플로우와 상태 관리가 필요한 경우에 적합합니다.
    • 각 에이전트는 독립적인 SubGraph로 구현되어 모듈성과 재사용성이 향상됩니다.
    • 여러 개발자가 협업하여 각자 담당 에이전트를 개발할 수 있어 대규모 프로젝트에 적합합니다.
    • 내부적으로 ReAct 패턴을 활용하지만, 그 위에 다중 에이전트 관리와 상태 전이 기능을 추가합니다.
    • StateGraph를 반환하므로 사용 전에 .compile()을 호출해야 합니다. 이는 추가 구성 옵션(메모리, 체크포인터 등)을 제공하는 장점이 있습니다.

현재 각 프로젝트의 복잡성, 필요한 기능, 그리고 개발 팀의 친숙도를 고려하여 적절한 함수를 선택하는 것이 중요합니다. 일반적으로, 새로운 프로젝트라면 다음과 같이 선택하는 것을 추천합니다:

  • 단일 에이전트 시스템: create_react_agent
  • 다중 에이전트 시스템: create_supervisor_agent

각 함수는 LangChain과 LangGraph의 발전 과정을 보여주며, 더 복잡한 AI 시스템을 구현하기 위한 프레임워크의 진화를 반영합니다. 이러한 이해를 바탕으로 프로젝트에 가장 적합한 도구를 선택할 수 있을 것입니다.

profile
AI Application Engineer

0개의 댓글