LangGraph 실전 — Reflection Agent와 Planning 패턴

LangGraph 실전 — Reflection Agent와 Planning 패턴
Part 1에서 만든 ReAct Agent는 한 가지 치명적 약점이 있습니다: 자기가 틀렸는지 모릅니다. "서울 인구가 5천만"이라고 답해도, 본인은 확신에 차 있죠. Reflection 패턴은 에이전트에게 "자기 검증" 능력을 부여합니다. 그리고 Planning 패턴은 복잡한 태스크를 체계적으로 분해하는 능력을 줍니다.
시리즈: Part 1: ReAct 패턴 | Part 2 (이 글) | Part 3: MCP + Multi-Agent | Part 4: 프로덕션 배포
Self-Critique: 에이전트가 자기 출력을 검증하는 법
사람도 글을 쓰고 나면 퇴고를 합니다. 첫 번째 초안이 완벽한 경우는 거의 없죠. LLM Agent도 마찬가지입니다. 한 번에 완벽한 답을 기대하는 건 비현실적이고, 스스로 검증하고 개선하는 루프를 만들어주면 품질이 확연히 달라집니다.
핵심 아이디어는 간단합니다:
- Generator — 결과물을 생성합니다
- Reflector — 그 결과물을 비판적으로 평가합니다
- Refined Output — 피드백을 반영해 개선된 결과물을 다시 생성합니다
이 사이클을 반복하면, 매번 구체적인 약점을 지적하고 그것을 해결하기 때문에 측정 가능한 품질 향상이 일어납니다. "칭찬"이 아니라 "무엇을 고쳐야 하는지"에 집중하는 게 핵심입니다.
Reflection Agent 구현
가장 기본적인 Reflection Agent를 만들어봅시다. Generator와 Reflector를 분리하고, 반복적 개선 루프를 구성합니다.
from openai import OpenAI
import json
client = OpenAI()
def generator(topic: str, feedback: str = "") -> str:
"""주어진 주제로 에세이를 생성. 피드백이 있으면 반영."""
prompt = f"Write a detailed essay about: {topic}"
if feedback:
prompt += f"\n\nPrevious feedback to address:\n{feedback}"
response = client.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "user", "content": prompt}],
temperature=0.7
)
return response.choices[0].message.content
def reflector(essay: str) -> dict:
"""에세이를 비판적으로 평가하고 개선 피드백을 반환."""
prompt = f"""You are a strict essay critic. Critique this essay:
{essay}
Focus on what needs to be FIXED, not what's good.
Return JSON: {{"score": 1-10, "feedback": "specific improvements needed", "is_good_enough": true/false}}
Score 8+ means is_good_enough = true."""
response = client.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "user", "content": prompt}],
temperature=0.3 # 평가 일관성을 위해 낮은 temperature
)
return json.loads(response.choices[0].message.content)Generator는 생성, Reflector는 비평가 역할입니다. 이제 둘을 연결하는 루프를 만듭니다.
def reflection_loop(topic: str, max_iterations: int = 3) -> str:
"""Generator → Reflector → Generator ... 루프를 실행합니다."""
essay = generator(topic)
print(f"[초안 생성 완료] 길이: {len(essay)}자")
for i in range(max_iterations):
critique = reflector(essay)
print(f"[반복 {i+1}] 점수: {critique['score']}/10")
if critique["is_good_enough"]:
print("✓ 품질 기준 충족 — 반복 종료")
return essay
print(f" 피드백: {critique['feedback'][:100]}...")
essay = generator(topic, critique["feedback"])
print("⚠ 최대 반복 도달 — 마지막 버전 반환")
return essay
# 실행
result = reflection_loop("한국의 AI 산업 현황과 전망")실행하면 매 반복마다 점수가 5 -> 7 -> 8처럼 올라가는 걸 확인할 수 있습니다. Reflector의 temperature를 낮게 유지해야 평가 일관성이 보장됩니다.
Self-Debugging Agent
Reflection 패턴은 코드 생성에 특히 강력합니다. 코드는 "실행해보면" 맞는지 틀린지 바로 알 수 있기 때문이죠.
import subprocess, tempfile
def generate_code(task: str, error: str = "") -> str:
prompt = f"Write Python code to: {task}\nReturn ONLY code."
if error:
prompt += f"\n\nPrevious attempt failed:\n{error}\nFix the error."
resp = client.chat.completions.create(
model="gpt-4o-mini", messages=[{"role": "user", "content": prompt}]
)
return resp.choices[0].message.content.replace("```python","").replace("```","").strip()
def execute_code(code: str) -> dict:
with tempfile.NamedTemporaryFile(mode="w", suffix=".py", delete=False) as f:
f.write(code)
f.flush()
r = subprocess.run(["python", f.name], capture_output=True, text=True, timeout=10)
return {"success": r.returncode == 0, "output": r.stdout, "error": r.stderr}
def self_debugging_loop(task: str, max_attempts: int = 3) -> str:
code = generate_code(task)
for attempt in range(max_attempts):
result = execute_code(code)
if result["success"]:
print(f"✓ 성공 (시도 {attempt + 1}회)")
return code
print(f"✗ 에러 (시도 {attempt + 1}회): {result['error'][:100]}")
code = generate_code(task, result["error"])
return code사람이 개입하지 않아도 에러 메시지 자체가 피드백입니다. NameError, TypeError 같은 구체적인 에러가 Reflector 역할을 대신하는 셈이죠.
Planning Agent: 복잡한 태스크 분해
ReAct와 Reflection은 한 번에 하나의 일을 처리합니다. 하지만 "한국과 일본의 AI 투자 규모를 비교하고, 정책을 요약한 뒤, 전망을 표로 정리해줘" 같은 복잡한 요청은 한 번에 처리하면 빠뜨리는 부분이 생깁니다. Planning Agent는 먼저 계획을 세우고, 단계별로 실행합니다.
Chain of Thought에서 영감을 받다
Wei et al. (2022)의 연구에 따르면, 프롬프트에 "Let's think step by step"을 추가하는 것만으로 GSM8K 수학 벤치마크에서 정확도가 17.9%에서 78.7%로 뛰었습니다.
Planning Agent는 이 아이디어를 시스템 수준으로 확장합니다.
Pydantic으로 구조화된 계획 생성
계획을 자유 텍스트가 아닌 구조화된 객체로 만들면, 프로그래밍적으로 각 단계를 추적하고 실행할 수 있습니다.
from pydantic import BaseModel, Field
from typing import List
class PlanStep(BaseModel):
step_number: int = Field(description="단계 번호 (1부터 시작)")
action: str = Field(description="이 단계에서 수행할 작업")
tool: str = Field(description="사용할 도구 (search, calculate, summarize)")
expected_output: str = Field(description="예상 결과물")
class Plan(BaseModel):
goal: str = Field(description="최종 목표")
steps: List[PlanStep] = Field(description="순서대로 실행할 단계 목록")Plan-and-Execute 아키텍처
세 가지 핵심 컴포넌트로 구성됩니다:
- Planner — 태스크를 분석하고 구조화된 계획을 생성
- Executor — 각 단계를 도구를 사용해 실행
- Synthesizer — 모든 결과를 종합해 최종 답변 생성
def create_plan(task: str) -> Plan:
response = client.beta.chat.completions.parse(
model="gpt-4o-mini",
messages=[
{"role": "system", "content": "You are a task planner."},
{"role": "user", "content": f"Create a plan to: {task}"}
],
response_format=Plan
)
return response.choices[0].message.parsed
def execute_plan(plan: Plan, tools: dict) -> dict:
results = {}
for step in plan.steps:
print(f"[Step {step.step_number}] {step.action}")
if step.tool in tools:
results[step.step_number] = tools[step.tool](step.action)
return results
def synthesize(goal: str, results: dict) -> str:
results_text = "\n".join(f"Step {k}: {v}" for k, v in results.items())
resp = client.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "user",
"content": f"Goal: {goal}\nResults:\n{results_text}\nSynthesize a final answer."}]
)
return resp.choices[0].message.content
# 실행
tools = {"search": web_search, "calculate": calculator, "summarize": summarize}
plan = create_plan("한국과 일본의 AI 투자 규모 비교 보고서 작성")
results = execute_plan(plan, tools)
answer = synthesize(plan.goal, results)Replanning: 실행 중 계획 수정
현실에서는 계획대로 되지 않는 경우가 많습니다. 검색 결과가 비어 있거나, API 호출이 실패하거나, 예상과 다른 데이터가 나오죠. Replanning은 실행 도중 실패를 감지하고 계획을 동적으로 수정합니다.
def plan_and_execute_with_replan(task: str, tools: dict, max_replans: int = 2) -> str:
plan = create_plan(task)
replan_count = 0
results = {}
for step in plan.steps:
try:
results[step.step_number] = tools[step.tool](step.action)
except Exception as e:
if replan_count >= max_replans:
break
# 실패 지점부터 남은 작업만 재계획
context = f"Goal: {task}\nDone: {results}\nFailed: {step.action}\nError: {e}"
plan = create_plan(context)
replan_count += 1
return synthesize(plan.goal, results)핵심은 실패 지점부터 새 계획을 세운다는 것입니다. 이미 완료된 결과는 유지하고, 남은 작업만 재구성합니다.
ReAct vs Planning: 언제 뭘 쓸까?
두 패턴은 상호 배타적이지 않습니다. 상황에 따라 선택하면 됩니다.
실전 가이드라인:
- ReAct 선택: 질문이 1-2번의 도구 호출로 해결 가능할 때, 탐색적 태스크일 때
- Planning 선택: 태스크가 3단계 이상이고 순서가 중요할 때, 보고서 생성처럼 구조가 필요한 태스크
- Reflection 추가: 출력 품질이 중요할 때 어떤 패턴에든 결합 가능
패턴 조합: Reflection + Planning
실전에서는 이 패턴들을 조합합니다. Planning Agent가 만든 계획을 Reflection으로 검증한 후 실행하면, 첫 실행부터 더 나은 결과를 얻을 수 있습니다.
def plan_with_reflection(task: str) -> Plan:
plan = create_plan(task)
# 계획 자체를 Reflector로 평가
critique_prompt = f"""Review this plan for: {task}
Plan: {plan.model_dump_json(indent=2)}
Are there missing steps? Return JSON: {{"is_good": bool, "feedback": "..."}}"""
critique = json.loads(client.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "user", "content": critique_prompt}]
).choices[0].message.content)
if not critique["is_good"]:
plan = create_plan(f"{task}\n\nImprove based on: {critique['feedback']}")
return plan계획을 세우기 전에 한 번 더 생각하는 것이죠. Generator-Reflector 루프는 에세이든, 코드든, 계획이든 어디에나 적용 가능합니다.
전체 실습은 Agent Cookbook에서
이 글의 모든 코드는 Jupyter Notebook으로 직접 실행해볼 수 있습니다:
- Week 2: LangGraph + Reflection 노트북 — Reflection Agent 전체 구현
- Week 2: RAG & Memory + Planning — Planning Agent와 Replanning
- Weekend Project — 직접 만들어보는 실전 프로젝트
다음 편 예고
Part 3에서는 MCP(Model Context Protocol)와 Multi-Agent 아키텍처를 다룹니다. 에이전트 하나로는 부족할 때, 여러 에이전트가 역할을 나눠 협업하는 방법을 알아봅니다.
참고 자료
- Wei, J. et al. (2022). *Chain-of-Thought Prompting Elicits Reasoning in Large Language Models*. NeurIPS 2022.
- Shinn, N. et al. (2023). *Reflexion: Language Agents with Verbal Reinforcement Learning*. NeurIPS 2023.
- Yao, S. et al. (2023). *ReAct: Synergizing Reasoning and Acting in Language Models*. ICLR 2023.
- Wang, L. et al. (2023). *Plan-and-Solve Prompting*. ACL 2023.
- LangGraph Documentation
- LLM Agent Cookbook — 이 시리즈의 실습 자료