콘텐츠로 이동

Plan-and-Execute 패턴

ReAct 패턴은 관찰 결과에 따라 유연하게 방향을 바꾸는 강점이 있지만, 전체 그림 없이 한 발짝씩 나아가기 때문에 비효율적인 경로를 택할 수 있습니다.

예를 들어, “새로운 결제 시스템을 구현하라”는 태스크를 ReAct로 처리하면:

  • 한 번에 한 파일씩 탐색하며 무엇이 필요한지 파악
  • 중간에 방향을 바꾸며 이미 한 작업을 되돌리는 경우 발생
  • 전체 진행 상황 파악이 어려움

Plan-and-Execute는 이를 해결합니다. 먼저 전체 계획을 세우고, 그 계획에 따라 체계적으로 실행합니다.

┌─────────────────────────────────────────┐
│ PLANNING PHASE (계획 단계) │
│ │
│ 입력: 최종 목표 │
│ 출력: 순서 있는 단계 목록 │
│ │
│ 사용 도구: read-only (탐색만 허용) │
└──────────────────┬──────────────────────┘
┌─────────────────────────────────────────┐
│ EXECUTION PHASE (실행 단계) │
│ │
│ 각 단계를 순차 또는 병렬 실행 │
│ 실패 시 → Re-planning 트리거 │
│ │
│ 사용 도구: full access │
└─────────────────────────────────────────┘
from dataclasses import dataclass
from typing import Literal
@dataclass
class Step:
id: int
description: str
status: Literal["pending", "in_progress", "done", "failed"]
result: str | None = None
class PlanAndExecuteAgent:
def run(self, goal: str) -> str:
# Phase 1: 계획 수립
plan = self._create_plan(goal)
# Phase 2: 단계별 실행
for step in plan:
success = self._execute_step(step)
if not success:
# 실패 시 남은 계획 재수립
remaining = [s for s in plan if s.status == "pending"]
plan = self._replan(goal, step, remaining)
return self._synthesize_results(plan)
def _create_plan(self, goal: str) -> list[Step]:
prompt = f"""
목표: {goal}
위 목표를 달성하기 위한 단계별 계획을 작성하세요.
각 단계는 독립적으로 검증 가능해야 합니다.
"""
raw_plan = self.llm.complete(prompt, tools=READ_ONLY_TOOLS)
return self._parse_steps(raw_plan)
def _execute_step(self, step: Step) -> bool:
step.status = "in_progress"
try:
result = self.llm.complete(
f"다음 단계를 실행하세요: {step.description}",
tools=ALL_TOOLS,
)
step.result = result
step.status = "done"
return True
except Exception as e:
step.result = str(e)
step.status = "failed"
return False
def _replan(self, goal: str, failed_step: Step, remaining: list[Step]) -> list[Step]:
prompt = f"""
원래 목표: {goal}
실패한 단계: {failed_step.description}
실패 원인: {failed_step.result}
남은 단계들: {[s.description for s in remaining]}
실패를 고려하여 나머지 계획을 수정하세요.
"""
return self._parse_steps(self.llm.complete(prompt))

좋은 계획은 다음 조건을 만족합니다:

조건나쁜 예좋은 예
검증 가능성”인증 시스템 구현""login() 함수 작성 후 단위 테스트 통과 확인”
원자성”백엔드 전체 수정""UserRepository.find_by_email() 메서드 추가”
순서 명시임의 나열”1. 스키마 → 2. 모델 → 3. API → 4. 테스트”
의존성 표시의존성 무시”2단계 완료 후에만 3단계 시작”
태스크 유형 판단:
┌─ 목표가 명확하고 단계가 예측 가능한가?
│ YES → Plan-and-Execute
│ NO ─┐
│ ↓
│ 중간 결과에 따라 방향이 크게 달라지는가?
│ YES → ReAct
│ NO → 둘 다 적합 (복잡도 따라 선택)
└──────────────────────────────────────
상황추천 패턴
기능 구현 (요구사항 명확)Plan-and-Execute
버그 디버깅 (원인 불명)ReAct
코드 리팩토링 (범위 확정)Plan-and-Execute
코드베이스 탐색/이해ReAct
대규모 마이그레이션Plan-and-Execute + Re-planning

Plan-and-Execute는 먼저 전체 그림을 그린 뒤 체계적으로 실행합니다. 목표가 명확하고 예측 가능한 태스크에서 ReAct보다 효율적입니다. Re-planning 메커니즘 덕분에 중간 실패에도 유연하게 대응할 수 있습니다. 다음 챕터에서는 에이전트가 스스로 출력을 비판하고 수정하는 Reflection 패턴을 다룹니다.