AI 에이전트 설계 패턴 — ReAct, Plan-and-Execute, Multi-Agent

AI 에이전트란?

AI 에이전트는 LLM이 단순 텍스트 생성을 넘어 스스로 판단하고, 도구를 사용하고, 목표를 달성하는 자율 시스템입니다. 비서에게 “이 일 처리해줘”라고 맡기면, 비서가 스스로 계획을 세우고, 필요한 도구를 찾아 쓰고, 결과를 확인하는 것과 같습니다.

에이전트의 핵심 구성 요소:

구성 요소역할비유
LLM (두뇌)추론, 판단, 계획사람의 두뇌
Tools (도구)외부 시스템과 상호작용도구함
Memory (기억)과거 행동과 결과 저장메모장
Planning (계획)목표 달성을 위한 단계 수립할 일 목록
Observation (관찰)도구 실행 결과 해석눈과 귀

패턴 1: ReAct (Reasoning + Acting)

ReAct는 가장 기본적이고 널리 사용되는 에이전트 패턴입니다. 생각(Thought) → 행동(Action) → 관찰(Observation) 루프를 반복하여 문제를 해결합니다.

동작 흐름

사용자 질문 → [Thought] 무엇을 해야 하는지 추론
            → [Action]  적절한 도구 선택 및 실행
            → [Observation] 도구 실행 결과 확인
            → [Thought] 추가 행동이 필요한지 판단
            → ... (반복)
            → [Final Answer] 최종 답변 생성

구현 예시

from langchain_openai import ChatOpenAI
from langchain.agents import create_react_agent, AgentExecutor
from langchain_core.prompts import PromptTemplate
from langchain_core.tools import tool

@tool
def search_database(query: str) -> str:
    """데이터베이스에서 사용자 정보를 검색합니다."""
    # 실제 DB 조회 로직
    db = {"user:1001": {"name": "김철수", "plan": "프로", "usage": "85%"}}
    return str(db.get(query, "결과 없음"))

@tool
def send_notification(message: str) -> str:
    """사용자에게 알림을 전송합니다."""
    return f"알림 전송 완료: {message}"

# ReAct 프롬프트 (Thought/Action/Observation 구조)
react_prompt = PromptTemplate.from_template("""
다음 도구를 사용할 수 있습니다:
{tools}

도구 이름: {tool_names}

다음 형식을 사용하세요:
Thought: 무엇을 해야 하는지 생각
Action: 사용할 도구 이름
Action Input: 도구에 전달할 입력
Observation: 도구 실행 결과

... (필요한 만큼 반복)

Thought: 최종 답변을 알았습니다
Final Answer: 사용자에게 전달할 최종 답변

질문: {input}
{agent_scratchpad}
""")

llm = ChatOpenAI(model="gpt-4o", temperature=0)
tools = [search_database, send_notification]

agent = create_react_agent(llm, tools, react_prompt)
executor = AgentExecutor(agent=agent, tools=tools, verbose=True)

result = executor.invoke({"input": "user:1001의 사용량을 확인하고, 80% 이상이면 알림 보내줘"})
# Thought: user:1001의 사용량을 먼저 확인해야 합니다
# Action: search_database
# Action Input: user:1001
# Observation: {'name': '김철수', 'plan': '프로', 'usage': '85%'}
# Thought: 사용량이 85%로 80% 이상이므로 알림을 보내야 합니다
# Action: send_notification
# Action Input: 김철수님, 현재 사용량이 85%입니다. 요금제 업그레이드를 검토해주세요.
# Observation: 알림 전송 완료
# Final Answer: 김철수님의 사용량은 85%로 확인되었고, 알림을 전송했습니다.

ReAct의 장단점:

  • 장점: 구현이 단순, 추론 과정이 투명, 디버깅 용이
  • 단점: 단계별 순차 실행으로 속도 느림, 복잡한 작업에서 루프에 빠질 수 있음

패턴 2: Plan-and-Execute

Plan-and-Execute는 먼저 전체 계획을 수립한 후, 각 단계를 순서대로 실행합니다. 복잡한 작업에서 ReAct보다 안정적입니다.

동작 흐름

사용자 질문 → [Planner] 전체 계획 수립 (단계 1, 2, 3...)
            → [Executor] 단계 1 실행
            → [Executor] 단계 2 실행
            → [Re-planner] 필요시 계획 수정
            → [Executor] 단계 3 실행
            → [Final] 결과 종합 및 답변

구현 예시

from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from typing import TypedDict

class AgentState(TypedDict):
    task: str             # 원래 작업
    plan: list[str]       # 실행 계획
    current_step: int     # 현재 단계
    results: list[str]    # 각 단계 결과
    final_answer: str     # 최종 답변

llm = ChatOpenAI(model="gpt-4o", temperature=0)

# 1단계: 계획 수립
planner_prompt = ChatPromptTemplate.from_template("""
다음 작업을 수행하기 위한 단계별 계획을 세우세요.
각 단계는 한 줄로, 번호를 붙여 작성하세요.

작업: {task}

계획:
""")

planner_chain = planner_prompt | llm | StrOutputParser()

# 2단계: 각 단계 실행
executor_prompt = ChatPromptTemplate.from_template("""
다음 단계를 실행하고 결과를 보고하세요.

전체 작업: {task}
현재 단계: {step}
이전 단계 결과: {previous_results}
""")

executor_chain = executor_prompt | llm | StrOutputParser()

# Plan-and-Execute 실행
def plan_and_execute(task: str) -> str:
    """계획을 세우고 단계별로 실행합니다."""

    # 계획 수립
    plan_text = planner_chain.invoke({"task": task})
    steps = [s.strip() for s in plan_text.strip().split("\n") if s.strip()]
    print(f"계획 수립 완료: {len(steps)}단계")

    # 각 단계 실행
    results = []
    for i, step in enumerate(steps):
        print(f"\n실행 중: 단계 {i+1} - {step}")
        result = executor_chain.invoke({
            "task": task,
            "step": step,
            "previous_results": "\n".join(results) if results else "없음"
        })
        results.append(f"단계 {i+1}: {result}")

    return "\n".join(results)

# 실행
answer = plan_and_execute("Python FastAPI로 사용자 CRUD API를 설계해줘")
# 계획 수립 완료: 4단계
# 실행 중: 단계 1 - 데이터 모델 정의 (User 스키마)
# 실행 중: 단계 2 - CRUD 엔드포인트 설계
# 실행 중: 단계 3 - 에러 핸들링 추가
# 실행 중: 단계 4 - API 문서 및 테스트 코드 작성

Plan-and-Execute의 장단점:

  • 장점: 복잡한 작업에서 안정적, 전체 흐름을 미리 파악 가능
  • 단점: 초기 계획 수립 비용, 계획 변경 시 재계획 필요

패턴 3: Multi-Agent (다중 에이전트)

Multi-Agent는 전문화된 여러 에이전트가 협업하여 작업을 수행합니다. 각 에이전트가 고유한 역할과 도구를 가지고, 오케스트레이터가 작업을 분배합니다.

아키텍처

사용자 질문 → [Orchestrator] 작업 분석 및 할당
            ├─→ [Research Agent] 정보 수집
            ├─→ [Code Agent] 코드 작성
            └─→ [Review Agent] 결과 검토
            → [Orchestrator] 결과 통합 및 최종 답변

구현 예시

from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser

llm = ChatOpenAI(model="gpt-4o", temperature=0)

# 전문 에이전트 정의
def create_specialist(role: str, expertise: str):
    """역할별 전문 에이전트를 생성합니다."""
    prompt = ChatPromptTemplate.from_messages([
        ("system", f"당신은 {role}입니다. {expertise} 분야의 전문가입니다."),
        ("human", "작업: {task}\n\n이전 결과:\n{context}")
    ])
    return prompt | llm | StrOutputParser()

# 역할별 에이전트 생성
researcher = create_specialist(
    "리서처", "기술 트렌드 조사와 문서 분석"
)
architect = create_specialist(
    "아키텍트", "시스템 설계와 아키텍처 결정"
)
reviewer = create_specialist(
    "리뷰어", "코드 품질 검토와 개선 제안"
)

# 오케스트레이터: 작업을 분배하고 결과를 통합
def orchestrate(task: str) -> str:
    """멀티 에이전트 워크플로우를 실행합니다."""

    # 1단계: 리서처가 조사
    print("리서처 작업 중...")
    research = researcher.invoke({"task": task, "context": "없음"})

    # 2단계: 아키텍트가 설계
    print("아키텍트 작업 중...")
    design = architect.invoke({"task": task, "context": research})

    # 3단계: 리뷰어가 검토
    print("리뷰어 작업 중...")
    review = reviewer.invoke({
        "task": f"다음 설계를 검토해주세요: {task}",
        "context": design
    })

    # 결과 통합
    return f"## 조사 결과\n{research}\n\n## 설계\n{design}\n\n## 리뷰\n{review}"

result = orchestrate("마이크로서비스 인증 시스템 설계")
print(result)

Multi-Agent의 장단점:

  • 장점: 전문화된 역할 분담, 복잡한 작업 병렬 처리 가능
  • 단점: 에이전트 간 통신 오버헤드, 조율 복잡성, 비용 증가

패턴 비교 요약

기준ReActPlan-and-ExecuteMulti-Agent
복잡도낮음중간높음
적합한 작업단순 도구 호출다단계 작업전문 분야 결합
LLM 호출 수단계당 1회계획 + 단계당 1회에이전트 수 x 단계
디버깅쉬움보통어려움
비용낮음중간높음
안정성루프 위험안정적조율에 의존

프레임워크별 지원

프레임워크ReActPlan-and-ExecuteMulti-Agent
LangChain/LangGraph기본 지원LangGraph로 구현LangGraph로 구현
CrewAI--핵심 기능
AutoGen--핵심 기능
OpenAI Assistants기본 지원커스텀 구현커스텀 구현

실전 팁

  • 단순한 패턴부터 시작하세요: 대부분의 작업은 ReAct로 충분합니다. 복잡한 패턴은 ReAct가 한계를 보일 때 도입하세요.
  • 가드레일을 설정하세요: 최대 반복 횟수(max_iterations), 타임아웃, 비용 한도를 반드시 설정하여 무한 루프를 방지하세요.
  • 도구 설명을 구체적으로 작성하세요: 에이전트가 올바른 도구를 선택하려면, 도구의 docstring이 명확해야 합니다. 도구가 무엇을 하는지, 언제 사용해야 하는지 모두 기술하세요.
  • 관찰 결과를 제한하세요: 도구가 반환하는 데이터가 너무 크면 컨텍스트 윈도우를 낭비합니다. 결과를 요약하거나 필요한 부분만 추출하세요.
  • LangGraph를 고려하세요: 복잡한 에이전트 워크플로우에는 LangGraph가 상태 관리와 분기 로직을 그래프로 명확하게 표현할 수 있어 유리합니다.
  • 평가 체계를 만드세요: 에이전트의 정확도, 비용, 속도를 정량적으로 측정하는 평가 파이프라인을 구축하면 패턴 선택과 개선에 도움이 됩니다.

이 글이 도움이 되었나요?