ReAct: 추론과 행동을 결합한 AI Agent 구축

Reasoning과 Acting을 결합하여 외부 도구를 활용하는 자율적 AI Agent 구현 기법

ReAct (Reasoning + Acting)의 정의부터 실전 구현까지 체계적으로 설명한다. Yao et al. (2022) “ReAct: Synergizing Reasoning and Acting in Language Models” 연구를 바탕으로 추론(Thought)과 행동(Action)을 번갈아 수행하는 원리, Thought-Action-Observation 사이클 메커니즘, 외부 도구 통합(웹 검색, 계산기, 데이터베이스, API 호출) 전략을 분석한다. HotpotQA, FEVER, WebShop 등 벤치마크에서 CoT 대비 최대 20% 성능 향상 결과를 제시하고, Wikipedia 기반 QA, 수학 계산, 날씨 정보 조회, 전자상거래 등 실무 예시와 Python 구현 코드(Anthropic Claude API, 도구 연동)를 통해 실전 Agent 구축 방법을 상세히 다룬다. 오류 처리 및 복구 전략, 최대 반복 횟수 제한, 도구 선택 최적화, CoT vs ReAct 비교, 자율 Agent 패턴과 안전성 고려사항을 제시한다.

Prompt Engineering
LLM
AI
Agent
저자

Kwangmin Kim

공개

2025년 02월 09일

1 들어가며

LLM은 강력하지만, 행동할 수 없다는 근본적인 한계가 있다. 질문에 답할 수는 있지만, 웹 검색을 하거나, 계산을 수행하거나, 데이터베이스를 조회할 수는 없다.

ReAct (Reasoning + Acting)는 이 한계를 극복한다. LLM이 추론(Reasoning)행동(Acting)을 번갈아 수행하며, 외부 도구를 활용하여 복잡한 문제를 단계적으로 해결한다.

1.1 기존 접근법의 한계

1.1.1 순수 추론만 (Chain-of-Thought)

질문: "2024년 노벨 물리학상 수상자는?"

CoT 응답:
"단계 1: 노벨상은 매년 10월에 발표됩니다.
 단계 2: 물리학상은 중요한 발견에 수여됩니다.
 단계 3: 2024년 수상자는... (할루시네이션 가능)"

문제점: 실제 최신 정보 없이 추측

1.1.2 순수 행동만 (Tool Use)

질문: "2024년 노벨 물리학상 수상자의 주요 업적을 설명해주세요"

Tool Use:
Action: search("2024 Nobel Physics")
Observation: "John Hopfield and Geoffrey Hinton"

응답: "John Hopfield and Geoffrey Hinton"

문제점: 컨텍스트 없이 단순 정보만 반환

1.1.3 ReAct (추론 + 행동)

질문: "2024년 노벨 물리학상 수상자의 주요 업적을 설명해주세요"

Thought 1: 먼저 2024년 수상자가 누구인지 검색해야 함
Action 1: search("2024 Nobel Prize Physics winner")
Observation 1: "John Hopfield and Geoffrey Hinton won for foundational discoveries in artificial neural networks"

Thought 2: 이제 각 수상자의 구체적인 업적을 찾아야 함
Action 2: search("John Hopfield neural networks contribution")
Observation 2: "Hopfield networks (1982), associative memory..."

Thought 3: Geoffrey Hinton의 업적도 확인 필요
Action 3: search("Geoffrey Hinton backpropagation deep learning")
Observation 3: "Backpropagation algorithm, deep learning pioneer..."

Thought 4: 이제 충분한 정보를 모았으므로 종합 답변 가능
Action 4: finish("2024년 노벨 물리학상은 John Hopfield와 Geoffrey Hinton이 수상했습니다. Hopfield는 1982년 Hopfield 네트워크를 개발하여 연상 메모리의 기초를 마련했고, Hinton은 역전파 알고리즘과 심층학습 발전에 기여했습니다...")

차이점: - ✅ 단계별 추론 (왜 이 행동을 하는지) - ✅ 외부 도구 활용 (실제 정보 획득) - ✅ 관찰 결과 반영 (동적 조정) - ✅ 종합적 답변 (컨텍스트 포함)

2 ReAct란?

Yao et al. (2022)이 제안한 ReActReasoning (추론)Acting (행동)을 번갈아 수행하는 프롬프팅 패러다임이다.

2.1 핵심 구조

┌─────────────────────────────────────────┐
│           Thought (추론)                 │
│  "다음에 무엇을 해야 하는가?"               │
└─────────────────────────────────────────┘
                   ↓
┌─────────────────────────────────────────┐
│           Action (행동)                  │
│  도구 호출: search, calculate, etc.       │
└─────────────────────────────────────────┘
                   ↓
┌─────────────────────────────────────────┐
│         Observation (관찰)               │
│  행동의 결과                              │
└─────────────────────────────────────────┘
                   ↓
        (Thought로 돌아가거나 완료)

2.2 Thought-Action-Observation 사이클

import anthropic
from typing import List, Dict, Callable
import re

class ReActAgent:
    """
    ReAct Agent 구현
    """
    
    def __init__(self, api_key: str):
        self.client = anthropic.Anthropic(api_key=api_key)
        self.model = "claude-sonnet-4-20250514"
        
        # 사용 가능한 도구들
        self.tools = {}
        self.register_default_tools()
        
        # 실행 이력
        self.history = []
    
    def register_tool(self, name: str, function: Callable, description: str):
        """
        도구 등록
        
        Args:
            name: 도구 이름
            function: 실행할 함수
            description: 도구 설명
        """
        self.tools[name] = {
            'function': function,
            'description': description
        }
    
    def register_default_tools(self):
        """
        기본 도구 등록
        """
        # 검색 도구
        def search(query: str) -> str:
            """웹 검색 (시뮬레이션)"""
            # 실제로는 Google Search API 등 사용
            return f"검색 결과: {query}에 대한 정보..."
        
        self.register_tool(
            name="search",
            function=search,
            description="웹에서 정보를 검색합니다. 입력: 검색 쿼리"
        )
        
        # 계산기 도구
        def calculate(expression: str) -> str:
            """수학 계산"""
            try:
                result = eval(expression)
                return str(result)
            except Exception as e:
                return f"계산 오류: {e}"
        
        self.register_tool(
            name="calculate",
            function=calculate,
            description="수학 계산을 수행합니다. 입력: 수식 (예: '2+2', '10*5')"
        )
        
        # 완료 도구
        def finish(answer: str) -> str:
            """작업 완료"""
            return answer
        
        self.register_tool(
            name="finish",
            function=finish,
            description="최종 답변을 제공하고 종료합니다. 입력: 최종 답변"
        )
    
    def create_prompt(self, question: str) -> str:
        """
        ReAct 프롬프트 생성
        """
        # 도구 목록
        tools_desc = "\n".join([
            f"- {name}: {info['description']}"
            for name, info in self.tools.items()
        ])
        
        # 기본 프롬프트
        prompt = f"""다음 질문에 답하기 위해 Thought-Action-Observation 사이클을 사용하세요.

        사용 가능한 도구:
        {tools_desc}

        형식:
        Thought: [다음 단계에 대한 추론]
        Action: [도구명][입력]
        Observation: [도구 실행 결과]
        ... (필요한 만큼 반복)
        Thought: 이제 최종 답변 가능
        Action: finish[최종 답변]

        질문: {question}

        Thought 1:"""
        
        # 기존 이력 추가
        if self.history:
            prompt += "\n\n" + self._format_history()
        
        return prompt
    
    def _format_history(self) -> str:
        """
        실행 이력 포맷팅
        """
        formatted = []
        for i, step in enumerate(self.history, 1):
            formatted.append(f"Thought {i}: {step['thought']}")
            formatted.append(f"Action {i}: {step['action']}")
            formatted.append(f"Observation {i}: {step['observation']}")
        
        return "\n".join(formatted)
    
    def parse_action(self, text: str) -> tuple:
        """
        Action 파싱
        
        형식: "Action: tool_name[input]"
        
        Returns:
            (tool_name, input)
        """
        # "Action: " 이후 텍스트 추출
        action_match = re.search(r'Action\s*\d*:\s*(\w+)\[(.*?)\]', text, re.IGNORECASE)
        
        if action_match:
            tool_name = action_match.group(1)
            tool_input = action_match.group(2)
            return tool_name, tool_input
        
        return None, None
    
    def execute_action(self, tool_name: str, tool_input: str) -> str:
        """
        도구 실행
        """
        if tool_name not in self.tools:
            return f"오류: '{tool_name}'는 사용 가능한 도구가 아닙니다."
        
        try:
            tool_func = self.tools[tool_name]['function']
            result = tool_func(tool_input)
            return result
        except Exception as e:
            return f"도구 실행 오류: {e}"
    
    def run(self, question: str, max_steps: int = 10, verbose: bool = True) -> str:
        """
        ReAct 실행
        
        Args:
            question: 질문
            max_steps: 최대 단계 수
            verbose: 진행 과정 출력 여부
        
        Returns:
            최종 답변
        """
        if verbose:
            print(f"🤔 질문: {question}\n")
            print("="*80)
        
        self.history = []
        
        for step in range(1, max_steps + 1):
            if verbose:
                print(f"\n📍 Step {step}")
                print("-"*80)
            
            # Prompt 생성
            prompt = self.create_prompt(question)
            
            # LLM 호출
            message = self.client.messages.create(
                model=self.model,
                max_tokens=500,
                temperature=0,
                messages=[{"role": "user", "content": prompt}]
            )
            
            response = message.content[0].text
            
            # Thought 추출
            thought_match = re.search(
                r'Thought\s*\d*:\s*(.*?)(?=Action|$)',
                response,
                re.IGNORECASE | re.DOTALL
            )
            thought = thought_match.group(1).strip() if thought_match else ""
            
            if verbose:
                print(f"💭 Thought: {thought}")
            
            # Action 파싱
            tool_name, tool_input = self.parse_action(response)
            
            if tool_name is None:
                if verbose:
                    print("⚠️  Action을 파싱할 수 없습니다. 종료합니다.")
                break
            
            if verbose:
                print(f"🔧 Action: {tool_name}[{tool_input}]")
            
            # Action 실행
            observation = self.execute_action(tool_name, tool_input)
            
            if verbose:
                print(f"👁️  Observation: {observation}")
            
            # 이력 저장
            self.history.append({
                'thought': thought,
                'action': f"{tool_name}[{tool_input}]",
                'observation': observation
            })
            
            # finish 도구면 종료
            if tool_name == 'finish':
                if verbose:
                    print("\n" + "="*80)
                    print("✅ 완료!")
                    print("="*80)
                return observation
        
        if verbose:
            print("\n⚠️  최대 단계 수 도달. 종료합니다.")
        
        return "최대 단계 수에 도달했습니다."


# 사용 예시
def main():
    agent = ReActAgent(api_key="your-api-key")
    
    question = "15와 23의 합에 3을 곱한 값은?"
    
    answer = agent.run(question, max_steps=10, verbose=True)
    
    print(f"\n최종 답변: {answer}")


if __name__ == "__main__":
    main()

3 실행 예시

🤔 질문: 15와 23의 합에 3을 곱한 값은?

================================================================================

📍 Step 1
--------------------------------------------------------------------------------
💭 Thought: 먼저 15와 23을 더해야 합니다.
🔧 Action: calculate[15+23]
👁️  Observation: 38

📍 Step 2
--------------------------------------------------------------------------------
💭 Thought: 이제 38에 3을 곱해야 합니다.
🔧 Action: calculate[38*3]
👁️  Observation: 114

📍 Step 3
--------------------------------------------------------------------------------
💭 Thought: 계산이 완료되었습니다. 최종 답변을 제공할 수 있습니다.
🔧 Action: finish[15와 23의 합은 38이고, 이에 3을 곱하면 114입니다.]
👁️  Observation: 15와 23의 합은 38이고, 이에 3을 곱하면 114입니다.

================================================================================
✅ 완료!
================================================================================

최종 답변: 15와 23의 합은 38이고, 이에 3을 곱하면 114입니다.

4 외부 도구 통합

4.1 도구 1: 웹 검색

def register_real_search_tool(agent: ReActAgent):
    """
    실제 웹 검색 도구 등록
    """
    def web_search(query: str) -> str:
        """
        Google Search API 사용
        """
        import requests
        
        # Google Custom Search API
        api_key = "your-google-api-key"
        search_engine_id = "your-search-engine-id"
        
        url = "https://www.googleapis.com/customsearch/v1"
        params = {
            'key': api_key,
            'cx': search_engine_id,
            'q': query,
            'num': 3  # 상위 3개 결과
        }
        
        try:
            response = requests.get(url, params=params)
            results = response.json()
            
            # 결과 포맷팅
            if 'items' in results:
                summaries = []
                for item in results['items'][:3]:
                    title = item.get('title', '')
                    snippet = item.get('snippet', '')
                    summaries.append(f"{title}: {snippet}")
                
                return "\n".join(summaries)
            else:
                return "검색 결과를 찾을 수 없습니다."
        
        except Exception as e:
            return f"검색 오류: {e}"
    
    agent.register_tool(
        name="search",
        function=web_search,
        description="웹에서 최신 정보를 검색합니다. 입력: 검색 쿼리"
    )

4.2 도구 2: Wikipedia

def register_wikipedia_tool(agent: ReActAgent):
    """
    Wikipedia 도구 등록
    """
    def wikipedia_lookup(title: str) -> str:
        """
        Wikipedia 페이지 요약 가져오기
        """
        import wikipedia
        
        try:
            # 페이지 검색
            page = wikipedia.page(title, auto_suggest=True)
            
            # 요약 (처음 500자)
            summary = page.summary[:500]
            
            return f"{page.title}:\n{summary}..."
        
        except wikipedia.exceptions.DisambiguationError as e:
            # 동음이의어 페이지
            options = e.options[:5]
            return f"여러 의미가 있습니다: {', '.join(options)}"
        
        except wikipedia.exceptions.PageError:
            return f"'{title}' 페이지를 찾을 수 없습니다."
        
        except Exception as e:
            return f"Wikipedia 오류: {e}"
    
    agent.register_tool(
        name="wikipedia",
        function=wikipedia_lookup,
        description="Wikipedia에서 정보를 가져옵니다. 입력: 페이지 제목"
    )

4.3 도구 3: 데이터베이스 쿼리

def register_database_tool(agent: ReActAgent):
    """
    데이터베이스 쿼리 도구
    """
    def query_database(sql: str) -> str:
        """
        SQL 쿼리 실행 (안전하게)
        """
        import sqlite3
        
        # 안전성 체크
        sql_lower = sql.lower().strip()
        
        # SELECT만 허용
        if not sql_lower.startswith('select'):
            return "오류: SELECT 쿼리만 허용됩니다."
        
        # 위험한 키워드 체크
        dangerous = ['drop', 'delete', 'update', 'insert', 'alter']
        if any(kw in sql_lower for kw in dangerous):
            return "오류: 데이터 수정 쿼리는 허용되지 않습니다."
        
        try:
            # 연결 (예시)
            conn = sqlite3.connect('database.db')
            cursor = conn.cursor()
            
            cursor.execute(sql)
            results = cursor.fetchall()
            
            conn.close()
            
            # 결과 포맷팅
            if results:
                formatted = "\n".join([str(row) for row in results[:10]])
                return formatted
            else:
                return "결과 없음"
        
        except Exception as e:
            return f"데이터베이스 오류: {e}"
    
    agent.register_tool(
        name="query_db",
        function=query_database,
        description="데이터베이스에서 정보를 조회합니다 (SELECT만 가능). 입력: SQL 쿼리"
    )

4.4 도구 4: Python 코드 실행

def register_python_exec_tool(agent: ReActAgent):
    """
    Python 코드 실행 도구 (샌드박스)
    """
    def execute_python(code: str) -> str:
        """
        Python 코드 실행 (안전하게)
        """
        import sys
        from io import StringIO
        import contextlib
        
        # 출력 캡처
        output = StringIO()
        
        # 안전성 체크
        dangerous_imports = [
            'os', 'sys', 'subprocess', 'eval', 'exec',
            '__import__', 'open', 'file'
        ]
        
        code_lower = code.lower()
        if any(danger in code_lower for danger in dangerous_imports):
            return "오류: 위험한 코드가 감지되었습니다."
        
        try:
            # 코드 실행 (타임아웃 필요)
            with contextlib.redirect_stdout(output):
                # 제한된 환경에서 실행
                allowed_globals = {
                    '__builtins__': {
                        'print': print,
                        'range': range,
                        'len': len,
                        'sum': sum,
                        'max': max,
                        'min': min,
                    }
                }
                
                exec(code, allowed_globals)
            
            result = output.getvalue()
            return result if result else "실행 완료 (출력 없음)"
        
        except Exception as e:
            return f"실행 오류: {e}"
    
    agent.register_tool(
        name="python",
        function=execute_python,
        description="Python 코드를 실행합니다 (제한된 환경). 입력: Python 코드"
    )

4.5 도구 5: API 호출

def register_api_tool(agent: ReActAgent):
    """
    외부 API 호출 도구
    """
    def call_api(endpoint: str) -> str:
        """
        REST API 호출
        
        형식: "GET https://api.example.com/endpoint"
        """
        import requests
        
        parts = endpoint.split(' ', 1)
        
        if len(parts) != 2:
            return "오류: 형식은 'METHOD URL' 입니다."
        
        method, url = parts
        method = method.upper()
        
        if method not in ['GET', 'POST']:
            return "오류: GET 또는 POST만 지원됩니다."
        
        try:
            if method == 'GET':
                response = requests.get(url, timeout=5)
            else:
                response = requests.post(url, timeout=5)
            
            # 응답 (처음 500자)
            content = response.text[:500]
            
            return f"Status: {response.status_code}\n{content}"
        
        except Exception as e:
            return f"API 호출 오류: {e}"
    
    agent.register_tool(
        name="api_call",
        function=call_api,
        description="외부 API를 호출합니다. 입력: 'METHOD URL' (예: 'GET https://api.example.com/data')"
    )

5 복잡한 예시: 다단계 문제 해결

def complex_example():
    """
    복잡한 다단계 문제
    """
    agent = ReActAgent(api_key="your-api-key")
    
    # 추가 도구 등록
    register_wikipedia_tool(agent)
    
    question = """
    2024년 노벨 물리학상 수상자 중 한 명의 출생 연도를 찾고,
    그 사람이 몇 살에 수상했는지 계산해주세요.
    """
    
    answer = agent.run(question, max_steps=15, verbose=True)
    
    return answer

실행 과정:

📍 Step 1
💭 Thought: 먼저 2024년 노벨 물리학상 수상자가 누구인지 검색해야 합니다.
🔧 Action: search[2024 Nobel Prize Physics winner]
👁️  Observation: John Hopfield and Geoffrey Hinton won the 2024 Nobel Prize in Physics...

📍 Step 2
💭 Thought: John Hopfield의 출생 연도를 Wikipedia에서 찾겠습니다.
🔧 Action: wikipedia[John Hopfield]
👁️  Observation: John Hopfield: Born July 15, 1933, American scientist...

📍 Step 3
💭 Thought: 1933년생이고 2024년에 수상했으므로, 나이를 계산해야 합니다.
🔧 Action: calculate[2024-1933]
👁️  Observation: 91

📍 Step 4
💭 Thought: 이제 충분한 정보를 모았습니다. 최종 답변을 작성할 수 있습니다.
🔧 Action: finish[John Hopfield는 1933년 7월 15일생으로, 2024년 노벨 물리학상을 91세에 수상했습니다.]
👁️  Observation: John Hopfield는 1933년 7월 15일생으로, 2024년 노벨 물리학상을 91세에 수상했습니다.

✅ 완료!

6 실험 결과 분석

Yao et al. (2022)의 논문 결과를 분석하자.

6.1 벤치마크 성능

6.1.1 HotpotQA (Multi-hop Question Answering)

설정: - 두 개 이상의 Wikipedia 페이지 정보 필요 - 예: “The director of the 2003 film which has a song Frozen, directed 5 films in which year?”

결과:

방법 정확도 (EM) F1 Score
Standard Prompting 28.7% 35.1%
CoT (Chain-of-Thought) 29.4% 36.6%
ReAct 27.4% 34.2%
ReAct + Self-Consistency 34.2% 42.9%

관찰: - ReAct 단독: CoT와 유사 - ReAct + Self-Consistency: 최고 성능 (+19% vs Standard)

6.1.2 FEVER (Fact Verification)

설정: - 주장이 사실인지 검증 - Wikipedia 검색 필요

결과:

방법 정확도
Standard Prompting 56.3%
CoT 58.1%
ReAct 64.6%

개선폭: +8.3% (vs Standard)

ReAct가 특히 효과적인 이유: - Wikipedia 검색으로 실제 정보 확인 - 단계별로 증거 수집 - 모순 발견 시 추가 검색

6.2 행동 유형 분석

ReAct가 사용한 행동들:

행동 유형 빈도 예시
검색 (Search) 58% “search[John Hopfield]”
조회 (Lookup) 23% “lookup[birth year]”
계산 (Calculate) 12% “calculate[2024-1933]”
완료 (Finish) 7% “finish[answer]”

6.3 추론 유형 분석

Thought의 목적:

목적 비율 예시
계획 35% “먼저 X를 찾아야 함”
관찰 해석 28% “이 정보에 따르면…”
전략 수정 19% “이 방법이 안 되니 다른 접근”
결론 18% “충분한 정보를 모았음”

6.4 오류 회복 능력

시나리오: 검색이 실패한 경우

CoT (추론만):

질문: "X의 생년월일은?"

Thought: X는 유명인이다.
Thought: 유명인은 보통 1900-2000년에 태어난다.
Answer: 약 1950년 (추측)

→ 오류 회복 불가능

ReAct:

질문: "X의 생년월일은?"

Thought: X에 대해 검색해야 한다.
Action: search[X]
Observation: X에 대한 정보를 찾을 수 없음.

Thought: 검색어가 모호할 수 있다. 전체 이름으로 다시 검색.
Action: search[X full name]
Observation: X (1965년 3월 10일생)은...

→ 오류 회복 가능!

오류 회복 성공률: - CoT: 12% - ReAct: 67%

7 고급 ReAct 패턴

7.1 패턴 1: 계층적 ReAct

복잡한 작업을 하위 작업으로 분해하여 처리.

class HierarchicalReAct:
    """
    계층적 ReAct Agent
    
    고수준 계획 → 저수준 실행
    """
    
    def __init__(self, api_key: str):
        self.planner = ReActAgent(api_key)  # 고수준 계획자
        self.executor = ReActAgent(api_key)  # 저수준 실행자
        
        # 계획자용 특수 도구
        self.register_planner_tools()
    
    def register_planner_tools(self):
        """
        계획자용 도구: 하위 작업 생성
        """
        def create_subtask(task_description: str) -> str:
            """하위 작업 생성"""
            return f"하위 작업 생성: {task_description}"
        
        self.planner.register_tool(
            name="create_subtask",
            function=create_subtask,
            description="복잡한 작업을 하위 작업으로 분해합니다."
        )
    
    def solve(self, complex_question: str) -> str:
        """
        계층적 문제 해결
        
        1. 계획자: 고수준 계획 수립
        2. 실행자: 각 하위 작업 실행
        3. 통합: 결과 종합
        """
        print("🏗️  계층적 ReAct 시작\n")
        
        # Phase 1: 고수준 계획
        print("=" * 80)
        print("Phase 1: 계획 수립")
        print("=" * 80)
        
        planning_prompt = f"""다음 복잡한 질문을 해결하기 위한 계획을 세우세요.

        질문: {complex_question}

        단계별 하위 작업으로 분해하세요:
        1."""
        
        plan = self.planner.run(planning_prompt, max_steps=5, verbose=False)
        
        # 하위 작업 추출
        subtasks = self._extract_subtasks(plan)
        
        print(f"\n📋 계획된 하위 작업:")
        for i, task in enumerate(subtasks, 1):
            print(f"  {i}. {task}")
        
        # Phase 2: 각 하위 작업 실행
        print(f"\n{'=' * 80}")
        print("Phase 2: 하위 작업 실행")
        print("=" * 80)
        
        subtask_results = []
        
        for i, task in enumerate(subtasks, 1):
            print(f"\n🔹 하위 작업 {i}: {task}")
            print("-" * 80)
            
            result = self.executor.run(task, max_steps=10, verbose=True)
            subtask_results.append({
                'task': task,
                'result': result
            })
        
        # Phase 3: 결과 통합
        print(f"\n{'=' * 80}")
        print("Phase 3: 결과 통합")
        print("=" * 80)
        
        final_answer = self._synthesize_results(
            complex_question,
            subtask_results
        )
        
        return final_answer
    
    def _extract_subtasks(self, plan: str) -> List[str]:
        """계획에서 하위 작업 추출"""
        import re
        
        # "1. ...", "2. ..." 형식 찾기
        subtasks = []
        for line in plan.split('\n'):
            match = re.match(r'^\d+\.\s*(.+)$', line.strip())
            if match:
                subtasks.append(match.group(1))
        
        return subtasks
    
    def _synthesize_results(
        self,
        question: str,
        results: List[Dict]
    ) -> str:
        """하위 작업 결과 통합"""
        results_str = "\n\n".join([
            f"작업: {r['task']}\n결과: {r['result']}"
            for r in results
        ])
        
        synthesis_prompt = f"""다음 하위 작업 결과들을 종합하여 원래 질문에 답하세요.

        원래 질문: {question}

        하위 작업 결과들:
        {results_str}

        종합 답변:"""
        
        # LLM 호출
        import anthropic
        client = anthropic.Anthropic(api_key="your-api-key")
        
        message = client.messages.create(
            model="claude-sonnet-4-20250514",
            max_tokens=1500,
            temperature=0,
            messages=[{"role": "user", "content": synthesis_prompt}]
        )
        
        return message.content[0].text

사용 예시:

hierarchical = HierarchicalReAct(api_key="your-api-key")

complex_question = """
2024년 노벨 물리학상 수상자들의 주요 업적을 비교하고,
현대 AI 발전에 미친 영향을 분석하세요.
"""

answer = hierarchical.solve(complex_question)

# 출력:
# 📋 계획된 하위 작업:
#   1. 2024년 노벨 물리학상 수상자 확인
#   2. 각 수상자의 주요 업적 조사
#   3. 업적 간 공통점과 차이점 비교
#   4. AI 발전에 미친 구체적 영향 분석
#   5. 종합 결론 도출

7.2 패턴 2: 반성적 ReAct (Reflexion)

실패에서 학습하여 전략을 개선.

class ReflexiveReAct:
    """
    반성적 ReAct Agent
    
    실패 → 반성 → 개선 → 재시도
    """
    
    def __init__(self, api_key: str):
        self.agent = ReActAgent(api_key)
        self.memory = []  # 과거 시도 기록
    
    def solve_with_reflection(
        self,
        question: str,
        max_attempts: int = 3
    ) -> str:
        """
        반성적 문제 해결
        """
        print(f"🔄 반성적 ReAct 시작 (최대 {max_attempts}회 시도)\n")
        
        for attempt in range(1, max_attempts + 1):
            print(f"{'=' * 80}")
            print(f"시도 {attempt}/{max_attempts}")
            print(f"{'=' * 80}\n")
            
            # 이전 실패 반성 추가
            if self.memory:
                reflection = self._generate_reflection()
                print(f"💡 반성:")
                print(f"   {reflection}\n")
                
                # 반성을 프롬프트에 포함
                augmented_question = f"""{question}

                이전 시도의 반성:
                {reflection}

                이를 고려하여 다시 시도하세요."""
            else:
                augmented_question = question
            
            # 실행
            try:
                answer = self.agent.run(
                    augmented_question,
                    max_steps=15,
                    verbose=True
                )
                
                # 성공 여부 평가
                is_correct = self._evaluate_answer(question, answer)
                
                if is_correct:
                    print(f"\n✅ 시도 {attempt}에서 성공!")
                    return answer
                else:
                    print(f"\n❌ 시도 {attempt} 실패")
                    
                    # 실패 기록
                    self.memory.append({
                        'attempt': attempt,
                        'question': question,
                        'answer': answer,
                        'history': self.agent.history.copy()
                    })
            
            except Exception as e:
                print(f"\n⚠️  오류 발생: {e}")
                
                self.memory.append({
                    'attempt': attempt,
                    'question': question,
                    'error': str(e)
                })
        
        print(f"\n{max_attempts}회 시도 후에도 해결 실패")
        return "문제를 해결할 수 없습니다."
    
    def _generate_reflection(self) -> str:
        """
        이전 실패에서 반성 생성
        """
        last_attempt = self.memory[-1]
        
        reflection_prompt = f"""이전 시도가 실패했습니다. 무엇이 잘못되었는지 분석하세요.

        실패한 시도:
        {self._format_attempt(last_attempt)}

        반성 (무엇이 잘못되었고, 어떻게 개선할 수 있는가?):"""
        
        import anthropic
        client = anthropic.Anthropic(api_key="your-api-key")
        
        message = client.messages.create(
            model="claude-sonnet-4-20250514",
            max_tokens=300,
            temperature=0,
            messages=[{"role": "user", "content": reflection_prompt}]
        )
        
        return message.content[0].text
    
    def _format_attempt(self, attempt: Dict) -> str:
        """시도 기록 포맷팅"""
        if 'error' in attempt:
            return f"오류: {attempt['error']}"
        
        history = attempt.get('history', [])
        formatted = []
        
        for step in history:
            formatted.append(f"Thought: {step['thought']}")
            formatted.append(f"Action: {step['action']}")
            formatted.append(f"Observation: {step['observation']}")
        
        formatted.append(f"\n최종 답변: {attempt.get('answer', 'N/A')}")
        
        return "\n".join(formatted)
    
    def _evaluate_answer(self, question: str, answer: str) -> bool:
        """
        답변 정확성 평가
        
        실제로는 더 정교한 평가 필요
        """
        eval_prompt = f"""다음 답변이 질문에 올바르게 답했는지 평가하세요.

질문: {question}
답변: {answer}

올바른가? (yes/no):"""
        
        import anthropic
        client = anthropic.Anthropic(api_key="your-api-key")
        
        message = client.messages.create(
            model="claude-sonnet-4-20250514",
            max_tokens=10,
            temperature=0,
            messages=[{"role": "user", "content": eval_prompt}]
        )
        
        response = message.content[0].text.strip().lower()
        
        return 'yes' in response

실행 예시:

🔄 반성적 ReAct 시작 (최대 3회 시도)

================================================================================
시도 1/3
================================================================================

[... ReAct 실행 ...]

❌ 시도 1 실패

================================================================================
시도 2/3
================================================================================

💡 반성:
   이전 시도에서는 검색 쿼리가 너무 일반적이었습니다.
   더 구체적인 쿼리를 사용하고, 연도를 명시해야 합니다.

[... 개선된 ReAct 실행 ...]

✅ 시도 2에서 성공!

7.3 패턴 3: 멀티 에이전트 협력

여러 Agent가 협력하여 문제 해결.

class MultiAgentSystem:
    """
    멀티 에이전트 협력 시스템
    """
    
    def __init__(self, api_key: str):
        # 전문화된 Agent들
        self.researcher = self._create_researcher(api_key)
        self.analyst = self._create_analyst(api_key)
        self.writer = self._create_writer(api_key)
    
    def _create_researcher(self, api_key: str) -> ReActAgent:
        """연구자 Agent: 정보 수집 전문"""
        agent = ReActAgent(api_key)
        
        # 검색 도구만 사용
        register_wikipedia_tool(agent)
        register_real_search_tool(agent)
        
        return agent
    
    def _create_analyst(self, api_key: str) -> ReActAgent:
        """분석가 Agent: 데이터 분석 전문"""
        agent = ReActAgent(api_key)
        
        # 계산, Python 실행 도구
        register_python_exec_tool(agent)
        
        return agent
    
    def _create_writer(self, api_key: str) -> ReActAgent:
        """작가 Agent: 최종 작성 전문"""
        agent = ReActAgent(api_key)
        
        # 작성 도구만
        # (finish 도구는 기본 등록됨)
        
        return agent
    
    def collaborate(self, task: str) -> str:
        """
        협력적 문제 해결
        
        1. Researcher: 정보 수집
        2. Analyst: 데이터 분석
        3. Writer: 최종 작성
        """
        print("🤝 멀티 에이전트 협력 시작\n")
        
        # Phase 1: 정보 수집
        print("=" * 80)
        print("Phase 1: Researcher - 정보 수집")
        print("=" * 80)
        
        research_task = f"다음 주제에 대한 정보를 수집하세요: {task}"
        research_results = self.researcher.run(
            research_task,
            max_steps=10,
            verbose=True
        )
        
        print(f"\n📚 수집된 정보:\n{research_results}\n")
        
        # Phase 2: 데이터 분석
        print("=" * 80)
        print("Phase 2: Analyst - 데이터 분석")
        print("=" * 80)
        
        analysis_task = f"""다음 정보를 분석하세요:

수집된 정보:
{research_results}

원래 작업: {task}"""
        
        analysis_results = self.analyst.run(
            analysis_task,
            max_steps=10,
            verbose=True
        )
        
        print(f"\n📊 분석 결과:\n{analysis_results}\n")
        
        # Phase 3: 최종 작성
        print("=" * 80)
        print("Phase 3: Writer - 최종 작성")
        print("=" * 80)
        
        writing_task = f"""다음 정보와 분석을 바탕으로 최종 답변을 작성하세요:

원래 작업: {task}

수집된 정보:
{research_results}

분석 결과:
{analysis_results}"""
        
        final_answer = self.writer.run(
            writing_task,
            max_steps=5,
            verbose=True
        )
        
        return final_answer

8 Agent 아키텍처 설계

8.1 아키텍처 1: 단순 루프 (Simple Loop)

┌──────────────────────────┐
│   User Question          │
└──────────────────────────┘
            ↓
┌──────────────────────────┐
│   ReAct Loop             │
│   ┌──────────────────┐   │
│   │ Thought          │   │
│   │      ↓           │   │
│   │ Action           │   │
│   │      ↓           │   │
│   │ Observation      │   │
│   └──────────────────┘   │
│          ↓               │
│   (반복 또는 종료)        │
└──────────────────────────┘
            ↓
┌──────────────────────────┐
│   Final Answer           │
└──────────────────────────┘

장점: 간단, 이해하기 쉬움 단점: 복잡한 작업에 한계

8.2 아키텍처 2: 계획-실행-반성 (Plan-Execute-Reflect)

┌──────────────────────────┐
│   User Question          │
└──────────────────────────┘
            ↓
┌──────────────────────────┐
│   Planner                │
│   - 고수준 계획 수립      │
│   - 하위 작업 생성        │
└──────────────────────────┘
            ↓
┌──────────────────────────┐
│   Executor               │
│   - 각 하위 작업 실행     │
│   - ReAct Loop           │
└──────────────────────────┘
            ↓
┌──────────────────────────┐
│   Reflector              │
│   - 결과 평가             │
│   - 재계획 필요 시 반복   │
└──────────────────────────┘
            ↓
┌──────────────────────────┐
│   Final Answer           │
└──────────────────────────┘

장점: 복잡한 작업 처리 가능, 오류 회복 단점: 더 많은 LLM 호출, 복잡도 증가

8.3 아키텍처 3: 도구 중심 (Tool-Centric)

class ToolCentricAgent:
    """
    도구 중심 Agent
    
    각 도구가 언제 사용되어야 하는지 명시적으로 정의
    """
    
    def __init__(self, api_key: str):
        self.client = anthropic.Anthropic(api_key=api_key)
        self.model = "claude-sonnet-4-20250514"
        
        # 도구와 사용 조건
        self.tools = {
            'search': {
                'function': self._search,
                'when_to_use': '최신 정보나 사실 확인이 필요할 때',
                'examples': ['누가 2024년에 X를 했나?', 'Y의 최근 소식은?']
            },
            'calculate': {
                'function': self._calculate,
                'when_to_use': '수학 계산이 필요할 때',
                'examples': ['15 + 23은?', '면적 계산']
            },
            'code_exec': {
                'function': self._execute_code,
                'when_to_use': '복잡한 데이터 처리나 알고리즘 실행이 필요할 때',
                'examples': ['리스트 정렬', '통계 계산']
            }
        }
    
    def select_tool(self, question: str, context: str = "") -> str:
        """
        질문과 컨텍스트를 바탕으로 적절한 도구 선택
        """
        # 도구 설명 생성
        tools_desc = ""
        for name, info in self.tools.items():
            tools_desc += f"""
도구: {name}
사용 시기: {info['when_to_use']}
예시: {', '.join(info['examples'])}
"""
        
        # 도구 선택 프롬프트
        prompt = f"""다음 질문을 해결하기 위해 어떤 도구를 사용해야 할까요?

사용 가능한 도구:
{tools_desc}

질문: {question}

{f'현재 컨텍스트: {context}' if context else ''}

가장 적절한 도구 이름만 답하세요 (또는 'none' if 도구 불필요):"""
        
        message = self.client.messages.create(
            model=self.model,
            max_tokens=50,
            temperature=0,
            messages=[{"role": "user", "content": prompt}]
        )
        
        tool_name = message.content[0].text.strip().lower()
        
        return tool_name if tool_name in self.tools else None
    
    def run(self, question: str, max_steps: int = 10) -> str:
        """
        도구 중심 실행
        """
        context = ""
        
        for step in range(1, max_steps + 1):
            print(f"\n📍 Step {step}")
            
            # 도구 선택
            tool_name = self.select_tool(question, context)
            
            if tool_name is None or tool_name == 'none':
                # 도구 불필요 - 직접 답변
                print("💬 도구 불필요 - 직접 답변")
                return self._generate_final_answer(question, context)
            
            print(f"🔧 선택된 도구: {tool_name}")
            
            # 도구 입력 생성
            tool_input = self._generate_tool_input(question, tool_name, context)
            print(f"📥 입력: {tool_input}")
            
            # 도구 실행
            tool_func = self.tools[tool_name]['function']
            observation = tool_func(tool_input)
            print(f"👁️  관찰: {observation}")
            
            # 컨텍스트 업데이트
            context += f"\n[{tool_name}] {observation}"
            
            # 완료 조건 체크
            if self._is_sufficient(question, context):
                print("\n✅ 충분한 정보 수집 완료")
                return self._generate_final_answer(question, context)
        
        return self._generate_final_answer(question, context)
    
    def _generate_tool_input(
        self,
        question: str,
        tool_name: str,
        context: str
    ) -> str:
        """도구 입력 생성"""
        prompt = f"""질문: {question}

도구: {tool_name}

{f'현재까지 수집한 정보: {context}' if context else ''}

이 도구에 어떤 입력을 제공해야 할까요? (입력만 답하세요):"""
        
        message = self.client.messages.create(
            model=self.model,
            max_tokens=100,
            temperature=0,
            messages=[{"role": "user", "content": prompt}]
        )
        
        return message.content[0].text.strip()
    
    def _is_sufficient(self, question: str, context: str) -> bool:
        """충분한 정보를 모았는지 확인"""
        if not context:
            return False
        
        prompt = f"""질문: {question}

수집한 정보:
{context}

이 정보로 질문에 충분히 답할 수 있습니까? (yes/no):"""
        
        message = self.client.messages.create(
            model=self.model,
            max_tokens=10,
            temperature=0,
            messages=[{"role": "user", "content": prompt}]
        )
        
        response = message.content[0].text.strip().lower()
        
        return 'yes' in response
    
    def _generate_final_answer(self, question: str, context: str) -> str:
        """최종 답변 생성"""
        prompt = f"""질문: {question}

수집한 정보:
{context}

이를 바탕으로 질문에 답하세요:"""
        
        message = self.client.messages.create(
            model=self.model,
            max_tokens=1000,
            temperature=0,
            messages=[{"role": "user", "content": prompt}]
        )
        
        return message.content[0].text
    
    def _search(self, query: str) -> str:
        """검색 구현"""
        return f"[검색 결과: {query}]"
    
    def _calculate(self, expression: str) -> str:
        """계산 구현"""
        try:
            return str(eval(expression))
        except:
            return "계산 오류"
    
    def _execute_code(self, code: str) -> str:
        """코드 실행 구현"""
        return f"[코드 실행: {code}]"

9 도구 선택 전략

9.1 전략 1: 규칙 기반 (Rule-Based)

def rule_based_tool_selection(question: str) -> str:
    """
    규칙 기반 도구 선택
    
    키워드 매칭
    """
    question_lower = question.lower()
    
    # 검색 키워드
    search_keywords = ['누구', 'who', '언제', 'when', '최신', 'latest', '뉴스']
    if any(kw in question_lower for kw in search_keywords):
        return 'search'
    
    # 계산 키워드
    calc_keywords = ['계산', 'calculate', '+', '-', '*', '/', '=']
    if any(kw in question_lower for kw in calc_keywords):
        return 'calculate'
    
    # 코드 실행 키워드
    code_keywords = ['정렬', 'sort', '필터', 'filter', '알고리즘']
    if any(kw in question_lower for kw in code_keywords):
        return 'code_exec'
    
    # 기본값
    return 'none'

장점: 빠름, 예측 가능 단점: 유연성 부족, 모호한 경우 처리 어려움

9.2 전략 2: LLM 기반 (LLM-Based)

def llm_based_tool_selection(
    question: str,
    tools: Dict,
    context: str = ""
) -> str:
    """
    LLM을 사용한 도구 선택
    
    더 지능적이지만 비용 증가
    """
    tools_desc = "\n".join([
        f"{name}: {info['description']}"
        for name, info in tools.items()
    ])
    
    prompt = f"""질문을 해결하기 위해 어떤 도구가 필요한가요?

도구 목록:
{tools_desc}

질문: {question}

{f'컨텍스트: {context}' if context else ''}

가장 적절한 도구 이름 (하나만):"""
    
    # LLM 호출
    # ...
    
    return tool_name

장점: 유연함, 컨텍스트 고려 단점: 비용, 지연 시간

9.3 전략 3: 하이브리드 (Hybrid)

def hybrid_tool_selection(
    question: str,
    tools: Dict,
    context: str = ""
) -> str:
    """
    규칙 + LLM 하이브리드
    
    1. 규칙으로 빠른 선택 시도
    2. 불확실하면 LLM 사용
    """
    # Step 1: 규칙 기반 시도
    rule_result = rule_based_tool_selection(question)
    
    # Step 2: 확신도 평가
    confidence = evaluate_rule_confidence(question, rule_result)
    
    if confidence > 0.8:
        # 높은 확신 → 규칙 결과 사용
        return rule_result
    else:
        # 낮은 확신 → LLM 사용
        return llm_based_tool_selection(question, tools, context)

장점: 비용 효율적, 높은 정확도 단점: 복잡도 증가

10 실무 적용 사례

10.1 사례 1: 고객 지원 Agent

class CustomerSupportAgent:
    """
    고객 지원 ReAct Agent
    """
    
    def __init__(self, api_key: str):
        self.agent = ReActAgent(api_key)
        
        # 고객 지원 특화 도구
        self.register_support_tools()
    
    def register_support_tools(self):
        """고객 지원 도구 등록"""
        
        # 주문 조회
        def lookup_order(order_id: str) -> str:
            """주문 정보 조회"""
            # 데이터베이스 쿼리
            return f"주문 #{order_id}: 배송 중 (예상 도착: 2일 후)"
        
        self.agent.register_tool(
            name="lookup_order",
            function=lookup_order,
            description="주문 번호로 주문 정보를 조회합니다."
        )
        
        # 환불 정책 조회
        def get_refund_policy(category: str) -> str:
            """환불 정책 조회"""
            policies = {
                'electronics': '구매 후 14일 이내 미개봉 상태',
                'clothing': '구매 후 30일 이내, 택 부착',
                'food': '환불 불가 (단, 불량 시 교환)'
            }
            return policies.get(category, '일반 정책: 7일 이내')
        
        self.agent.register_tool(
            name="refund_policy",
            function=get_refund_policy,
            description="상품 카테고리별 환불 정책을 조회합니다."
        )
        
        # 티켓 생성
        def create_ticket(issue: str) -> str:
            """고객 지원 티켓 생성"""
            ticket_id = f"TK-{random.randint(1000, 9999)}"
            return f"티켓 {ticket_id} 생성 완료. 담당자가 24시간 내 연락드립니다."
        
        self.agent.register_tool(
            name="create_ticket",
            function=create_ticket,
            description="복잡한 문제에 대해 지원 티켓을 생성합니다."
        )
    
    def handle_inquiry(self, customer_message: str) -> str:
        """고객 문의 처리"""
        return self.agent.run(customer_message, max_steps=10, verbose=True)


# 사용
support = CustomerSupportAgent(api_key="your-api-key")

inquiry = "주문 번호 12345의 배송 상태를 알려주세요. 그리고 마음에 안 들면 환불 가능한가요?"

response = support.handle_inquiry(inquiry)

# 실행:
# Thought 1: 먼저 주문 상태를 확인해야 합니다.
# Action 1: lookup_order[12345]
# Observation 1: 주문 #12345: 배송 중 (예상 도착: 2일 후)
#
# Thought 2: 환불 정책을 확인해야 합니다. 상품 카테고리가 필요합니다.
# Action 2: lookup_order[12345]
# Observation 2: ... (카테고리: electronics)
#
# Thought 3: 전자제품 환불 정책을 조회합니다.
# Action 3: refund_policy[electronics]
# Observation 3: 구매 후 14일 이내 미개봉 상태
#
# Thought 4: 충분한 정보를 모았습니다.
# Action 4: finish[주문 #12345는 현재 배송 중이며 2일 후 도착 예정입니다. 전자제품은 구매 후 14일 이내 미개봉 상태로 환불 가능합니다.]

10.2 사례 2: 데이터 분석 Agent

class DataAnalysisAgent:
    """
    데이터 분석 ReAct Agent
    """
    
    def __init__(self, api_key: str, data_path: str):
        self.agent = ReActAgent(api_key)
        self.data_path = data_path
        
        # 데이터 분석 도구
        self.register_analysis_tools()
    
    def register_analysis_tools(self):
        """데이터 분석 도구 등록"""
        
        # 데이터 로드
        def load_data(filename: str) -> str:
            """CSV 데이터 로드"""
            import pandas as pd
            
            try:
                df = pd.read_csv(f"{self.data_path}/{filename}")
                
                info = f"""데이터 로드 완료:
- 행 수: {len(df)}
- 열 수: {len(df.columns)}
- 열 이름: {', '.join(df.columns)}
- 처음 5행:
{df.head().to_string()}"""
                
                # 전역 변수에 저장 (다른 도구에서 사용)
                globals()['current_df'] = df
                
                return info
            except Exception as e:
                return f"데이터 로드 실패: {e}"
        
        self.agent.register_tool(
            name="load_data",
            function=load_data,
            description="CSV 파일을 로드합니다. 입력: 파일명"
        )
        
        # 통계 계산
        def compute_statistics(column: str) -> str:
            """열의 기술 통계"""
            df = globals().get('current_df')
            
            if df is None:
                return "먼저 데이터를 로드하세요."
            
            if column not in df.columns:
                return f"'{column}' 열이 존재하지 않습니다."
            
            stats = df[column].describe()
            
            return f"{column} 통계:\n{stats.to_string()}"
        
        self.agent.register_tool(
            name="statistics",
            function=compute_statistics,
            description="특정 열의 기술 통계를 계산합니다. 입력: 열 이름"
        )
        
        # 데이터 필터링
        def filter_data(condition: str) -> str:
            """조건에 맞는 데이터 필터링"""
            df = globals().get('current_df')
            
            if df is None:
                return "먼저 데이터를 로드하세요."
            
            try:
                # 조건 평가 (주의: 실제로는 더 안전한 방법 필요)
                filtered = df.query(condition)
                
                result = f"""필터링 결과:
- 매칭 행 수: {len(filtered)}
- 처음 5행:
{filtered.head().to_string()}"""
                
                return result
            except Exception as e:
                return f"필터링 실패: {e}"
        
        self.agent.register_tool(
            name="filter",
            function=filter_data,
            description="조건에 맞는 데이터를 필터링합니다. 입력: pandas query 조건"
        )
    
    def analyze(self, question: str) -> str:
        """데이터 분석 실행"""
        return self.agent.run(question, max_steps=15, verbose=True)


# 사용
analyst = DataAnalysisAgent(
    api_key="your-api-key",
    data_path="./data"
)

question = "sales.csv를 로드하고, 매출이 1000 이상인 제품들의 평균 가격을 구해주세요."

answer = analyst.analyze(question)

# 실행:
# Thought 1: 먼저 sales.csv를 로드해야 합니다.
# Action 1: load_data[sales.csv]
# Observation 1: 데이터 로드 완료: 행 수: 150, 열: product, price, sales...
#
# Thought 2: 매출이 1000 이상인 데이터를 필터링합니다.
# Action 2: filter[sales >= 1000]
# Observation 2: 필터링 결과: 매칭 행 수: 45...
#
# Thought 3: 이제 price 열의 통계를 계산합니다.
# Action 3: statistics[price]
# Observation 3: price 통계: mean: 1250.5, std: 320.2...
#
# Thought 4: 답변 가능합니다.
# Action 4: finish[매출이 1000 이상인 제품은 45개이며, 평균 가격은 $1,250.5입니다.]

10.3 사례 3: 연구 보조 Agent

class ResearchAssistantAgent:
    """
    연구 보조 Agent
    
    논문 검색, 요약, 비교
    """
    
    def __init__(self, api_key: str):
        self.agent = ReActAgent(api_key)
        
        # 연구 도구
        self.register_research_tools()
    
    def register_research_tools(self):
        """연구 도구 등록"""
        
        # arXiv 검색
        def search_arxiv(query: str) -> str:
            """arXiv 논문 검색"""
            import arxiv
            
            search = arxiv.Search(
                query=query,
                max_results=5,
                sort_by=arxiv.SortCriterion.Relevance
            )
            
            results = []
            for paper in search.results():
                results.append(f"""제목: {paper.title}
저자: {', '.join([a.name for a in paper.authors])}
게시일: {paper.published.date()}
요약: {paper.summary[:200]}...
URL: {paper.entry_id}
""")
            
            return "\n\n".join(results)
        
        self.agent.register_tool(
            name="search_papers",
            function=search_arxiv,
            description="arXiv에서 논문을 검색합니다. 입력: 검색 쿼리"
        )
        
        # 논문 다운로드 및 요약
        def summarize_paper(arxiv_id: str) -> str:
            """논문 요약"""
            import arxiv
            
            paper = next(arxiv.Search(id_list=[arxiv_id]).results())
            
            # 전문 다운로드 및 요약 (간략화)
            summary = f"""논문: {paper.title}

주요 내용:
{paper.summary}

기여:
(여기서는 초록 기반, 실제로는 전문 분석 필요)"""
            
            return summary
        
        self.agent.register_tool(
            name="summarize",
            function=summarize_paper,
            description="arXiv 논문을 요약합니다. 입력: arXiv ID"
        )
    
    def research(self, research_question: str) -> str:
        """연구 질문에 답변"""
        return self.agent.run(research_question, max_steps=20, verbose=True)


# 사용
researcher = ResearchAssistantAgent(api_key="your-api-key")

question = "ReAct 프롬프팅에 대한 최근 논문을 찾고, 핵심 아이디어를 요약해주세요."

answer = researcher.research(question)

11 한계점 및 해결책

11.1 한계 1: 무한 루프

문제: Agent가 같은 행동을 반복

# 나쁜 예
Thought 1: X를 검색해야 함
Action 1: search[X]
Observation 1: X에 대한 정보...

Thought 2: X를 검색해야 함  ← 반복!
Action 2: search[X]
Observation 2: X에 대한 정보...

(무한 반복...)

해결책: 중복 행동 감지 및 방지

class LoopPreventionReAct(ReActAgent):
    """
    무한 루프 방지 ReAct
    """
    
    def __init__(self, api_key: str):
        super().__init__(api_key)
        self.action_history = []  # 행동 이력
    
    def execute_action(self, tool_name: str, tool_input: str) -> str:
        """
        행동 실행 (중복 체크 포함)
        """
        action_key = f"{tool_name}[{tool_input}]"
        
        # 중복 체크
        if action_key in self.action_history:
            # 이미 실행한 행동
            count = self.action_history.count(action_key)
            
            if count >= 2:
                # 3번 이상 반복 → 경고
                return f"경고: 이 행동을 이미 {count}번 실행했습니다. 다른 접근을 시도하세요."
        
        # 행동 기록
        self.action_history.append(action_key)
        
        # 실행
        return super().execute_action(tool_name, tool_input)

11.2 한계 2: 도구 선택 오류

문제: 잘못된 도구 선택

# 나쁜 예
질문: "15 + 23은?"
Action: search[15 + 23]  ← 잘못된 도구!
Observation: 검색 결과... (계산이 아님)

해결책: 도구 선택 검증

def validate_tool_choice(
    question: str,
    selected_tool: str,
    tools: Dict
) -> bool:
    """
    도구 선택이 적절한지 검증
    """
    validation_prompt = f"""질문: {question}

선택된 도구: {selected_tool}
도구 설명: {tools[selected_tool]['description']}

이 도구 선택이 적절한가요? (yes/no):"""
    
    # LLM으로 검증
    # ...
    
    return is_appropriate

11.3 한계 3: 긴 실행 시간

문제: 많은 단계로 인한 지연

해결책: 조기 종료 및 최적화

class OptimizedReAct(ReActAgent):
    """
    최적화된 ReAct
    """
    
    def run(
        self,
        question: str,
        max_steps: int = 10,
        timeout: int = 60  # 초
    ) -> str:
        """
        타임아웃 포함 실행
        """
        import time
        
        start_time = time.time()
        
        for step in range(1, max_steps + 1):
            # 타임아웃 체크
            if time.time() - start_time > timeout:
                print(f"⏰ 타임아웃 ({timeout}초)")
                return self._emergency_answer(question)
            
            # 정상 실행
            # ...
    
    def _emergency_answer(self, question: str) -> str:
        """
        타임아웃 시 긴급 답변
        
        지금까지 수집한 정보로 최선의 답변
        """
        context = self._format_history()
        
        prompt = f"""시간 제약으로 인해 조사를 완료하지 못했습니다.
지금까지 수집한 정보로 최선의 답변을 제공하세요.

질문: {question}

수집한 정보:
{context}

답변 (정보가 불완전함을 명시):"""
        
        # LLM 호출
        # ...
        
        return answer

11.4 한계 4: 비용

문제: 많은 LLM 호출로 인한 높은 비용

해결책: 비용 최적화 전략

class CostOptimizedReAct:
    """
    비용 최적화 ReAct
    """
    
    def __init__(
        self,
        api_key: str,
        budget_per_query: float = 0.10  # 쿼리당 $0.10
    ):
        self.agent = ReActAgent(api_key)
        self.budget = budget_per_query
        self.cost_per_call = 0.01  # 예상 비용
    
    def run_with_budget(self, question: str) -> str:
        """
        예산 제약 하 실행
        """
        max_steps = int(self.budget / self.cost_per_call)
        
        print(f"💰 예산: ${self.budget}")
        print(f"   최대 단계: {max_steps}\n")
        
        # 실행
        answer = self.agent.run(
            question,
            max_steps=max_steps,
            verbose=True
        )
        
        # 실제 비용 계산
        actual_cost = len(self.agent.history) * self.cost_per_call
        
        print(f"\n💵 실제 비용: ${actual_cost:.3f}")
        
        return answer

12 베스트 프랙티스

12.1 1. 명확한 도구 설명

# ❌ 나쁜 예
agent.register_tool(
    name="tool1",
    function=my_func,
    description="데이터 처리"  # 너무 모호
)

# ✅ 좋은 예
agent.register_tool(
    name="filter_data",
    function=my_func,
    description="""CSV 데이터를 조건에 따라 필터링합니다.
입력: pandas query 형식의 조건 (예: 'age > 30 and city == "Seoul"')
출력: 필터링된 행의 수와 처음 5행의 미리보기"""
)

12.2 2. 도구 결과 포맷팅

def well_formatted_tool(input_data: str) -> str:
    """
    결과를 명확하게 포맷팅
    """
    result = process_data(input_data)
    
    # ✅ 구조화된 출력
    return f"""처리 완료:
- 입력 크기: {len(input_data)}
- 처리된 항목: {result['count']}
- 성공: {result['success']}
- 실패: {result['failures']}

처음 3개 결과:
{format_results(result['items'][:3])}"""

12.3 3. 오류 처리

def robust_tool(input_data: str) -> str:
    """
    견고한 오류 처리
    """
    try:
        result = risky_operation(input_data)
        return f"성공: {result}"
    
    except ValueError as e:
        return f"입력 오류: {e}. 올바른 형식: [예시]"
    
    except ConnectionError:
        return "네트워크 오류. 나중에 다시 시도하거나 다른 도구를 사용하세요."
    
    except Exception as e:
        return f"예상치 못한 오류: {e}"

12.4 4. 진행 상황 로깅

class LoggingReAct(ReActAgent):
    """
    상세한 로깅이 있는 ReAct
    """
    
    def __init__(self, api_key: str, log_file: str = "react.log"):
        super().__init__(api_key)
        
        import logging
        logging.basicConfig(
            filename=log_file,
            level=logging.INFO,
            format='%(asctime)s - %(levelname)s - %(message)s'
        )
        self.logger = logging.getLogger(__name__)
    
    def execute_action(self, tool_name: str, tool_input: str) -> str:
        """로깅 포함 행동 실행"""
        
        self.logger.info(f"도구 호출: {tool_name}[{tool_input}]")
        
        try:
            result = super().execute_action(tool_name, tool_input)
            self.logger.info(f"도구 결과: {result[:100]}...")
            return result
        
        except Exception as e:
            self.logger.error(f"도구 실행 오류: {e}")
            raise

12.5 5. 테스트

def test_react_agent():
    """
    ReAct Agent 테스트
    """
    agent = ReActAgent(api_key="test-key")
    
    # 테스트 케이스
    test_cases = [
        {
            'question': "2 + 2는?",
            'expected_tool': 'calculate',
            'expected_answer': '4'
        },
        {
            'question': "2024년 노벨상 수상자는?",
            'expected_tool': 'search',
            'expected_answer_contains': '노벨'
        }
    ]
    
    for i, test in enumerate(test_cases, 1):
        print(f"\n테스트 {i}: {test['question']}")
        
        answer = agent.run(test['question'], max_steps=5, verbose=False)
        
        # 검증
        if 'expected_answer' in test:
            assert answer == test['expected_answer'], f"기대: {test['expected_answer']}, 실제: {answer}"
        
        if 'expected_answer_contains' in test:
            assert test['expected_answer_contains'] in answer, f"'{test['expected_answer_contains']}'가 답변에 없음"
        
        # 도구 사용 확인
        used_tools = [step['action'].split('[')[0] for step in agent.history]
        assert test['expected_tool'] in used_tools, f"기대 도구 {test['expected_tool']} 사용 안 됨"
        
        print("✅ 통과")

13 다른 Agent 프레임워크 비교

13.1 ReAct vs LangChain Agents

특성 ReAct LangChain
구조 Thought-Action-Observation Tool → Output
추론 가시성 높음 (명시적 Thought) 낮음 (암묵적)
구현 복잡도 중간 낮음 (추상화)
커스터마이징 쉬움 제한적
디버깅 쉬움 어려움

13.2 ReAct vs AutoGPT

특성 ReAct AutoGPT
자율성 중간 (사람이 목표 설정) 높음 (자체 목표)
안정성 높음 낮음
비용 예측 가능 높음 (무제한 실행)
적용 범위 특정 작업 범용

14 정리 및 시리즈 종합

14.1 ReAct 핵심 요약

ReAct (Reasoning + Acting): - Thought-Action-Observation 사이클 - 외부 도구 통합으로 LLM 능력 확장 - HotpotQA에서 CoT 대비 +19% 개선 (Self-Consistency 결합 시) - 오류 회복 능력 67% (CoT: 12%)

주요 구성 요소: 1. Thought: 다음 단계에 대한 추론 2. Action: 도구 호출 3. Observation: 결과 관찰 4. Tools: 검색, 계산, API 호출 등

언제 사용할 것인가: - ✅ 외부 정보 필요 (검색, DB) - ✅ 복잡한 다단계 문제 - ✅ 동적 환경 (상황에 따라 전략 변경) - ✅ 도구 통합 필요

언제 사용하지 말 것인가: - ❌ 단순 질문 (CoT로 충분) - ❌ 비용 매우 민감 - ❌ 실시간 응답 필수 (지연 시간)

14.2 고급 프롬프트 엔지니어링 시리즈 전체 정리

Part 1-10 핵심 기법 요약:

기법 핵심 아이디어 주요 개선 권장 사용
Generate Knowledge 배경 지식 먼저 생성 +8% 상식 추론
Prompt Chaining 작업 분해 및 연결 복잡도 관리 다단계 작업
Tree of Thoughts 트리 탐색 + 백트래킹 +70% (Game24) 최적해 필요
RAG 외부 지식 검색 할루시네이션 감소 지식 기반 작업
APE 프롬프트 자동 생성 +4.5% 대량 최적화
OPRO LLM이 LLM 최적화 +8.4% 프롬프트 발견
Active-Prompt 불확실한 예시 선별 비용 50% 절감 Few-shot 효율
Directional Stimulus 힌트로 유도 +14% 도메인 특화
ReAct 추론 + 행동 결합 +19% 도구 사용

기법 선택 가이드:

질문 유형에 따른 선택:

단순 사실 질문
→ Zero-shot 또는 Few-shot

복잡한 추론
→ CoT 또는 Tree of Thoughts

최신 정보 필요
→ RAG 또는 ReAct

도메인 특화
→ Directional Stimulus 또는 Active-Prompt

프롬프트 최적화
→ APE 또는 OPRO

다단계 작업
→ Prompt Chaining 또는 ReAct

14.3 실무 적용 로드맵

Week 1-2: 기초 - CoT, Few-shot 마스터 - 기본 프롬프트 엔지니어링 원칙

Week 3-4: 중급 - Prompt Chaining 구현 - RAG 시스템 구축

Week 5-6: 고급 - ReAct Agent 개발 - 도메인 특화 최적화

Week 7-8: 프로덕션 - 성능 최적화 - 모니터링 및 A/B 테스트

14.4 미래 방향

현재 트렌드: 1. Agentic AI: ReAct 기반 자율 Agent 2. Multi-modal: 텍스트 + 이미지 + 비디오 3. Long-context: 100K+ 토큰 윈도우 4. Tool Learning: 도구 자동 발견 및 학습

기대되는 발전: - 더 스마트한 도구 선택 - 자가 개선 Agent - 멀티 Agent 협업 프레임워크 - 저비용 고효율 추론


15 참고문헌

  1. Yao, S., Zhao, J., Yu, D., Du, N., Shafran, I., Narasimhan, K., & Cao, Y. (2022). ReAct: Synergizing reasoning and acting in language models. arXiv preprint arXiv:2210.03629.

  2. Schick, T., et al. (2023). Toolformer: Language models can teach themselves to use tools. arXiv preprint arXiv:2302.04761.

  3. Shinn, N., et al. (2023). Reflexion: Language agents with verbal reinforcement learning. arXiv preprint arXiv:2303.11366.

16 추가적 기법

  • Prompt Compression
    • LLMLingua, AutoCompressor
    • 긴 프롬프트를 압축
  • In-Context Learning 변형
    • Meta-Prompting: 프롬프트가 프롬프트를 생성
    • Instruction Induction: 예시에서 instruction 추론
    • Rephrase and Respond (RaR): 질문 재구성 후 답변
  • Multimodal Prompting
    • Vision-Language Prompting: 이미지+텍스트
    • Audio Prompting: 음성 입력
    • Video Understanding: 비디오 분석

Subscribe

Enjoy this blog? Get notified of new posts by email: