Plan-and-Execute 패턴
왜 계획이 먼저인가?
섹션 제목: “왜 계획이 먼저인가?”ReAct 패턴은 관찰 결과에 따라 유연하게 방향을 바꾸는 강점이 있지만, 전체 그림 없이 한 발짝씩 나아가기 때문에 비효율적인 경로를 택할 수 있습니다.
예를 들어, “새로운 결제 시스템을 구현하라”는 태스크를 ReAct로 처리하면:
- 한 번에 한 파일씩 탐색하며 무엇이 필요한지 파악
- 중간에 방향을 바꾸며 이미 한 작업을 되돌리는 경우 발생
- 전체 진행 상황 파악이 어려움
Plan-and-Execute는 이를 해결합니다. 먼저 전체 계획을 세우고, 그 계획에 따라 체계적으로 실행합니다.
두 단계 구조
섹션 제목: “두 단계 구조”┌─────────────────────────────────────────┐│ PLANNING PHASE (계획 단계) ││ ││ 입력: 최종 목표 ││ 출력: 순서 있는 단계 목록 ││ ││ 사용 도구: read-only (탐색만 허용) │└──────────────────┬──────────────────────┘ ↓┌─────────────────────────────────────────┐│ EXECUTION PHASE (실행 단계) ││ ││ 각 단계를 순차 또는 병렬 실행 ││ 실패 시 → Re-planning 트리거 ││ ││ 사용 도구: full access │└─────────────────────────────────────────┘코드 구현
섹션 제목: “코드 구현”from dataclasses import dataclassfrom typing import Literal
@dataclassclass 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단계 시작” |
ReAct vs Plan-and-Execute 선택 기준
섹션 제목: “ReAct vs Plan-and-Execute 선택 기준”태스크 유형 판단:┌─ 목표가 명확하고 단계가 예측 가능한가?│ 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 패턴을 다룹니다.