# AI 에이전트 심화: LLM 기반 자율 에이전트 설계와 구현

**⬅️ 이전 시간: [고급 RAG 기법](./part_13.1_advanced_rag_techniques.md)**
**➡️ 다음 시간: [Part 14: AI 윤리 및 거버넌스 실무](../14_ai_ethics/part_14_ai_ethics.md)**

## 📋 학습 목표

이 모듈을 통해 다음을 학습하게 됩니다:

- LLM 기반 에이전트의 작동 원리와 아키텍처를 이해합니다.
- ReAct, Plan-and-Execute 등 다양한 에이전트 패턴을 구현할 수 있습니다.
- LangGraph를 활용하여 복잡한 에이전트 워크플로우를 설계할 수 있습니다.
- 멀티 에이전트 시스템을 구축하고 실제 응용 분야에 적용할 수 있습니다.

## 1. AI 에이전트의 기초와 발전

### 1.1 에이전트의 정의와 구성요소

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

- **Base LLM**: 에이전트의 "두뇌" 역할을 하는 언어 모델
- **Action Space**: 에이전트가 수행할 수 있는 작업들의 집합(도구/API)
- **Memory**: 이전 상호작용과 컨텍스트를 저장하는 메모리 시스템
- **Planner**: 목표 달성을 위한 계획을 수립하는 구성요소
- **Controller**: 에이전트의 전체 흐름을 조정하고 관리하는 구성요소

```python
# 기본 에이전트 아키텍처 예시
from langchain.agents import Tool, initialize_agent, AgentType
from langchain.agents import load_tools
from langchain.llms import OpenAI

# LLM 초기화
llm = OpenAI(temperature=0)

# 도구 정의
tools = load_tools(["serpapi", "llm-math"], llm=llm)

# 에이전트 초기화
agent = initialize_agent(
    tools, 
    llm, 
    agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION,
    verbose=True
)

# 에이전트 실행
agent.run("인공지능의 역사에 대해 알려주고, 2023년 기준으로 몇 년이 지났는지 계산해줘.")
```

### 1.2 에이전트 패턴의 진화

**에이전트 패러다임의 발전:**

1. **단순 LLM**: 단일 프롬프트에 기반한 응답
2. **Tool-Using LLM**: 외부 도구를 사용할 수 있는 에이전트
3. **ReAct 패턴**: 사고(Reasoning)와 행동(Action)을 번갈아 수행
4. **Plan-and-Execute**: 계획을 세우고 단계별로 실행
5. **자율 에이전트**: 스스로 목표를 설정하고 달성하는 에이전트

## 2. ReAct 패턴 심화 구현

### 2.1 ReAct 패턴의 이해

Reasoning(사고)와 Acting(행동)을 번갈아 수행하여 문제 해결 능력 향상

```python
# ReAct 패턴 상세 구현 예시
from langchain.agents import AgentType, initialize_agent, load_tools
from langchain.llms import OpenAI

llm = OpenAI(temperature=0)

# 도구 정의
tools = load_tools(["serpapi", "llm-math"], llm=llm)

# ReAct 에이전트 초기화 with custom prompt
react_agent = initialize_agent(
    tools,
    llm,
    agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION,
    verbose=True
)

# 에이전트 실행 및 중간 사고 과정 시각화
reaction = react_agent.run(
    "2023년 노벨 물리학상 수상자는 누구이고, 그의 나이를 계산해줘."
)
```

### 2.2 ReAct 프롬프트 최적화

```python
# ReAct 프롬프트 최적화 예시
from langchain.prompts import PromptTemplate

# 개선된 ReAct 프롬프트 템플릿
custom_react_template = """
질문에 대답하기 위해, 나는 단계적으로 생각하고 필요한 도구를 사용해야 합니다.

사용 가능한 도구:
{tools}

문제를 해결할 때 다음과 같은 형식을 따릅니다:

사고(Thought): 문제를 해결하기 위해 무엇을 해야 할지 생각합니다.
행동(Action): 사용할 도구 이름
행동 입력(Action Input): 도구에 전달할 입력
관찰(Observation): 도구 실행 결과
... (사고-행동-관찰 반복)
사고(Thought): 이제 최종 답변을 알았습니다
최종 답변(Final Answer): 질문에 대한 최종 답변

시작합니다!

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

custom_react_prompt = PromptTemplate(
    template=custom_react_template,
    input_variables=["input", "tools", "agent_scratchpad"]
)

# 커스텀 프롬프트로 에이전트 초기화
optimized_react_agent = initialize_agent(
    tools,
    llm,
    agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION,
    verbose=True,
    agent_kwargs={"prompt": custom_react_prompt}
)
```

## 3. LangGraph를 활용한 에이전트 워크플로우

### 3.1 에이전트 상태 관리

LangGraph를 사용하여 복잡한 에이전트 상태 관리 구현

```python
# LangGraph를 사용한 에이전트 워크플로우 구현
!pip install langgraph

from typing import TypedDict, Annotated, List, Dict
from langchain_core.messages import AIMessage, HumanMessage, ChatMessage
from langgraph.graph import StateGraph, END

# 에이전트 상태 정의
class AgentState(TypedDict):
    messages: List[Dict]
    next_action: str
    action_results: Dict

# 노드 함수 정의
def process_intent(state: AgentState):
    """사용자 입력을 분석하고 다음 작업 결정"""
    messages = state["messages"]
    last_message = messages[-1]
    
    if not isinstance(last_message, HumanMessage):
        return {"next_action": "generate_response"}
    
    # 의도 분석을 위한 LLM 호출
    intent_prompt = f"사용자 메시지: {last_message.content}\n\n이 메시지의 의도를 파악하세요. 검색이 필요하면 'search', 계산이 필요하면 'calculate', 일반 대화는 'chat'을 반환하세요."
    intent = llm.predict(intent_prompt).strip().lower()
    
    return {"next_action": intent}

def search_web(state: AgentState):
    """웹 검색 수행"""
    messages = state["messages"]
    query = messages[-1].content
    
    # 검색 도구 사용
    search_result = search_tool(query)
    
    return {"action_results": {"search_result": search_result}}

def calculate(state: AgentState):
    """수학 계산 수행"""
    messages = state["messages"]
    query = messages[-1].content
    
    # 계산 도구 사용
    calculation_result = math_tool(query)
    
    return {"action_results": {"calculation": calculation_result}}

def generate_response(state: AgentState):
    """최종 응답 생성"""
    messages = state["messages"]
    action_results = state["action_results"]
    
    # 결과를 바탕으로 응답 생성
    response_prompt = f"""
    사용자 메시지: {messages[-1].content}
    
    사용 가능한 정보:
    {action_results}
    
    위 정보를 바탕으로 사용자에게 도움이 되는 응답을 작성하세요.
    """
    
    response = llm.predict(response_prompt)
    
    new_message = AIMessage(content=response)
    messages.append(new_message)
    
    return {"messages": messages, "next_action": END}

# 워크플로우 그래프 구성
workflow = StateGraph(AgentState)

# 노드 추가
workflow.add_node("process_intent", process_intent)
workflow.add_node("search_web", search_web)
workflow.add_node("calculate", calculate)
workflow.add_node("generate_response", generate_response)

# 엣지 추가
workflow.add_edge("process_intent", "search_web", condition=lambda s: s.get("next_action") == "search")
workflow.add_edge("process_intent", "calculate", condition=lambda s: s.get("next_action") == "calculate")
workflow.add_edge("process_intent", "generate_response", condition=lambda s: s.get("next_action") == "chat")
workflow.add_edge("search_web", "generate_response")
workflow.add_edge("calculate", "generate_response")

# 워크플로우 컴파일
agent_app = workflow.compile()

# 에이전트 실행
initial_state = {
    "messages": [HumanMessage(content="2023년 노벨 물리학상 수상자에 대해 알려주세요.")],
    "next_action": "",
    "action_results": {}
}

final_state = agent_app.invoke(initial_state)
print(final_state["messages"][-1].content)
```

### 3.2 조건부 분기 처리

```python
# 더 복잡한 조건부 분기 처리 구현
def route_by_complexity(state: AgentState):
    """질문의 복잡도에 따라 처리 경로 결정"""
    messages = state["messages"]
    query = messages[-1].content
    
    complexity_prompt = f"""
    다음 질문의 복잡도를 평가하세요:
    "{query}"
    
    평가 결과를 다음 중 하나로만 응답하세요:
    - simple: 간단한 사실 확인이나 정의를 묻는 질문
    - medium: 여러 정보를 조합해야 하는 질문
    - complex: 추론, 분석, 다단계 작업이 필요한 복잡한 질문
    """
    
    complexity = llm.predict(complexity_prompt).strip().lower()
    
    return {"complexity": complexity}

# 복잡도별 처리 노드
workflow.add_node("route_by_complexity", route_by_complexity)
workflow.add_node("handle_simple", simple_handler)
workflow.add_node("handle_complex", complex_handler)

# 분기 엣지 추가
workflow.add_edge("route_by_complexity", "handle_simple", 
                 condition=lambda s: s.get("complexity") in ["simple", "medium"])
workflow.add_edge("route_by_complexity", "handle_complex", 
                 condition=lambda s: s.get("complexity") == "complex")
```

## 4. 멀티 에이전트 시스템 구축

### 4.1 특화된 역할의 에이전트 설계

```python
# 다양한 역할의 에이전트 구현
from langchain.prompts.chat import ChatPromptTemplate, SystemMessagePromptTemplate

# 조사원(Researcher) 에이전트
researcher_template = """당신은 전문 조사 에이전트입니다. 
주어진 주제에 대해 깊이 있는 조사를 수행하고 관련 정보를 수집하는 것이 목표입니다.
항상 신뢰할 수 있는 정보를 찾고, 출처를 명시하세요.
"""
researcher_prompt = ChatPromptTemplate.from_messages([
    SystemMessagePromptTemplate.from_template(researcher_template),
    ("human", "{input}")
])

researcher_agent = researcher_prompt | llm

# 작가(Writer) 에이전트
writer_template = """당신은 전문 작가 에이전트입니다.
주어진 정보를 바탕으로 명확하고 잘 구조화된 콘텐츠를 작성하는 것이 목표입니다.
복잡한 개념을 이해하기 쉽게 설명하고, 핵심 포인트를 강조하세요.
"""
writer_prompt = ChatPromptTemplate.from_messages([
    SystemMessagePromptTemplate.from_template(writer_template),
    ("human", "{input}")
])

writer_agent = writer_prompt | llm

# 비평가(Critic) 에이전트
critic_template = """당신은 전문 비평가 에이전트입니다.
주어진 콘텐츠를 비판적으로 검토하고, 오류를 찾아내며, 개선점을 제안하는 것이 목표입니다.
논리적 오류, 사실 관계 오류, 불명확한 설명에 주의를 기울이세요.
"""
critic_prompt = ChatPromptTemplate.from_messages([
    SystemMessagePromptTemplate.from_template(critic_template),
    ("human", "{input}")
])

critic_agent = critic_prompt | llm
```

### 4.2 협업 프레임워크 구축

```python
# 에이전트 간 협업 프레임워크 구현
def collaborative_research(topic):
    """여러 전문 에이전트가 협업하는 연구 프로세스"""
    # 1. 연구원이 정보 수집
    research_prompt = f"다음 주제에 대한 포괄적인 정보를 수집해주세요: {topic}"
    research_findings = researcher_agent.invoke({"input": research_prompt})
    
    print("🔎 연구 결과 수집 완료")
    
    # 2. 작가가 초안 작성
    writing_prompt = f"""
    다음 연구 결과를 바탕으로 잘 구조화된 글을 작성해주세요:
    
    {research_findings.content}
    """
    draft = writer_agent.invoke({"input": writing_prompt})
    
    print("✍️ 초안 작성 완료")
    
    # 3. 비평가가 검토 및 피드백 제공
    review_prompt = f"""
    다음 글을 검토하고 개선점을 제안해주세요:
    
    {draft.content}
    """
    critique = critic_agent.invoke({"input": review_prompt})
    
    print("🔍 검토 완료")
    
    # 4. 작가가 피드백을 반영하여 최종본 작성
    revision_prompt = f"""
    초안:
    {draft.content}
    
    검토 의견:
    {critique.content}
    
    위 검토 의견을 반영하여 최종 글을 작성해주세요.
    """
    final_version = writer_agent.invoke({"input": revision_prompt})
    
    print("📝 최종본 완성")
    
    return {
        "research": research_findings.content,
        "draft": draft.content,
        "critique": critique.content,
        "final": final_version.content
    }

# 협업 프로세스 실행
result = collaborative_research("양자 컴퓨팅의 최근 발전과 실용적 응용")
```

## 5. 자율 에이전트 설계

### 5.1 BabyAGI/AutoGPT 스타일 에이전트 구현

```python
# BabyAGI 타입 자율 에이전트 구현
from typing import List, Dict, Any
import time

class AutoAgent:
    """자율적으로 목표를 달성하는 에이전트"""
    
    def __init__(self, llm, tools=None, max_iterations=5):
        self.llm = llm
        self.tools = tools or []
        self.max_iterations = max_iterations
        self.memory = []
        self.task_list = []
    
    def add_memory(self, content):
        """에이전트 메모리에 정보 추가"""
        timestamp = time.strftime("%Y-%m-%d %H:%M:%S")
        self.memory.append({"time": timestamp, "content": content})
    
    def get_memory_str(self, limit=5):
        """최근 메모리 문자열로 반환"""
        recent_memory = self.memory[-limit:] if len(self.memory) > limit else self.memory
        return "\n".join([f"[{m['time']}] {m['content']}" for m in recent_memory])
    
    def create_tasks(self, objective):
        """목표를 달성하기 위한 태스크 생성"""
        prompt = f"""
        목표: {objective}
        
        이 목표를 달성하기 위해 필요한 단계별 작업을 5개 이내로 생성해주세요.
        각 작업은 구체적이고 실행 가능해야 합니다.
        작업 목록만 번호를 매겨 반환해주세요.
        """
        
        response = self.llm.predict(prompt)
        
        # 작업 목록 파싱
        tasks = []
        for line in response.split("\n"):
            line = line.strip()
            if line and (line[0].isdigit() or line[0] == "-"):
                tasks.append(line.split(".", 1)[-1].strip() if "." in line else line.lstrip("-").strip())
        
        self.task_list = tasks
        self.add_memory(f"목표 '{objective}'에 대한 작업 계획 생성: {', '.join(tasks)}")
        return tasks
    
    def execute_task(self, task):
        """단일 작업 실행"""
        memory_context = self.get_memory_str()
        
        prompt = f"""
        작업: {task}
        
        지금까지의 정보:
        {memory_context}
        
        위 작업을 수행하기 위해 어떤 도구를 사용해야 할지 결정하고, 해당 작업을 수행하세요.
        사용 가능한 도구: {', '.join([tool.name for tool in self.tools]) if self.tools else '없음'}
        
        결과를 명확하게 설명해주세요.
        """
        
        # 도구 사용 여부 결정
        tool_choice = None
        if self.tools:
            tool_decision_prompt = f"작업 '{task}'를 수행하기 위해 다음 도구 중 어떤 것을 사용해야 할까요? 도구 이름만 답하세요: {', '.join([tool.name for tool in self.tools])}"
            tool_name = self.llm.predict(tool_decision_prompt).strip()
            
            for tool in self.tools:
                if tool.name.lower() == tool_name.lower():
                    tool_choice = tool
                    break
        
        # 도구 사용 또는 직접 응답
        if tool_choice:
            tool_input_prompt = f"도구 {tool_choice.name}를 사용하기 위한 입력을 생성하세요. 작업: {task}"
            tool_input = self.llm.predict(tool_input_prompt)
            result = tool_choice(tool_input)
            self.add_memory(f"도구 '{tool_choice.name}' 사용 결과: {result}")
        else:
            result = self.llm.predict(prompt)
            self.add_memory(f"작업 '{task}' 수행 결과: {result}")
        
        return result
    
    def run(self, objective):
        """에이전트 실행"""
        print(f"🎯 목표: {objective}")
        
        # 1. 목표 기반 작업 계획 생성
        tasks = self.create_tasks(objective)
        print(f"📋 작업 계획:")
        for i, task in enumerate(tasks):
            print(f"  {i+1}. {task}")
        
        # 2. 작업 순차적 실행
        results = []
        for i, task in enumerate(self.task_list):
            if i >= self.max_iterations:
                print(f"⚠️ 최대 반복 횟수({self.max_iterations})에 도달했습니다.")
                break
                
            print(f"\n[작업 {i+1}/{len(self.task_list)}] {task}")
            result = self.execute_task(task)
            results.append({"task": task, "result": result})
            print(f"✅ 결과: {result}")
        
        # 3. 최종 요약
        summary_prompt = f"""
        목표: {objective}
        
        수행한 작업 및 결과:
        {self.get_memory_str(limit=10)}
        
        위 정보를 바탕으로 목표 달성 결과를 간결하게 요약해주세요.
        """
        
        summary = self.llm.predict(summary_prompt)
        print(f"\n📊 최종 요약:\n{summary}")
        
        return {
            "objective": objective,
            "tasks": self.task_list,
            "results": results,
            "summary": summary,
            "memory": self.memory
        }

# 에이전트 사용 예시
tools = [
    Tool(
        name="웹검색",
        func=lambda x: "검색 결과: 양자 컴퓨팅은 양자역학 원리를 활용한 계산 시스템입니다...",
        description="인터넷에서 정보를 검색합니다"
    ),
    Tool(
        name="계산기",
        func=lambda x: eval(x),
        description="수학 계산을 수행합니다"
    )
]

auto_agent = AutoAgent(llm=llm, tools=tools, max_iterations=3)
agent_result = auto_agent.run("양자 컴퓨팅에 대해 조사하고 주요 특징을 요약하세요")
```

## 6. 산업 적용 사례

### 6.1 연구 보조 에이전트

```python
# 연구 보조 에이전트 구현 예시
from langchain.agents import AgentExecutor, Tool
from langchain_community.tools import ArxivQueryRun

# Arxiv 검색 도구
arxiv_tool = ArxivQueryRun()

# 논문 요약 도구
def summarize_paper(paper_content):
    summary_prompt = f"""
    다음 논문의 주요 내용을 500자 이내로 요약해주세요:
    
    {paper_content}
    
    요약은 다음 구조를 따라주세요:
    1. 연구 목적
    2. 사용된 방법론
    3. 주요 발견점
    4. 결론 및 의의
    """
    return llm.predict(summary_prompt)

# 관련 연구 찾기 도구
def find_related_research(topic):
    query_prompt = f"""
    다음 주제와 관련된 최신 연구를 찾기 위한 Arxiv 검색 쿼리를 생성해주세요:
    
    {topic}
    
    검색어만 반환해주세요 (AND, OR 연산자 사용 가능).
    """
    query = llm.predict(query_prompt).strip()
    return arxiv_tool.run(query)

# 연구 보조 에이전트 도구 정의
research_tools = [
    Tool(
        name="ArxivSearch",
        func=arxiv_tool.run,
        description="학술 논문을 검색할 때 사용합니다. 검색어를 입력으로 받습니다."
    ),
    Tool(
        name="FindRelatedResearch",
        func=find_related_research,
        description="특정 주제와 관련된 최신 연구를 찾을 때 사용합니다. 연구 주제를 입력으로 받습니다."
    ),
    Tool(
        name="SummarizePaper",
        func=summarize_paper,
        description="논문 내용을 요약할 때 사용합니다. 논문 텍스트를 입력으로 받습니다."
    )
]

# 연구 보조 에이전트 생성
research_assistant = initialize_agent(
    research_tools,
    llm,
    agent=AgentType.STRUCTURED_CHAT_ZERO_SHOT_REACT_DESCRIPTION,
    verbose=True
)

# 에이전트 사용 예시
research_query = "생성형 AI 모델의 편향성에 관한 최신 연구를 3개 찾고 요약해주세요."
research_result = research_assistant.run(research_query)
```

### 6.2 데이터 분석 에이전트

```python
# 데이터 분석 에이전트 구현 예시
import pandas as pd
import matplotlib.pyplot as plt
from io import BytesIO
import base64

# 데이터 로드 도구
def load_dataset(dataset_name):
    # 예시 데이터셋
    datasets = {
        "iris": "https://raw.githubusercontent.com/mwaskom/seaborn-data/master/iris.csv",
        "titanic": "https://raw.githubusercontent.com/datasciencedojo/datasets/master/titanic.csv",
        "housing": "https://raw.githubusercontent.com/datasciencedojo/datasets/master/housing.csv"
    }
    
    if dataset_name.lower() in datasets:
        url = datasets[dataset_name.lower()]
        df = pd.read_csv(url)
        return f"데이터셋 '{dataset_name}' 로드 완료. 행: {df.shape[0]}, 열: {df.shape[1]}, 컬럼: {', '.join(df.columns.tolist())}"
    else:
        return f"데이터셋 '{dataset_name}'을 찾을 수 없습니다. 사용 가능한 데이터셋: {', '.join(datasets.keys())}"

# SQL 쿼리 실행 도구
def run_sql_query(query):
    # 간단한 SQL 구문 파싱 및 실행 (실제로는 SQLAlchemy 등을 사용할 수 있음)
    try:
        # 예시 데이터
        df = pd.read_csv("https://raw.githubusercontent.com/mwaskom/seaborn-data/master/iris.csv")
        
        # 매우 기본적인 SQL 파싱 (실제 환경에서는 더 정교한 방법 사용)
        if "SELECT" in query.upper() and "FROM" in query.upper():
            # 결과 반환 (실제 환경에서는 SQL 엔진 사용)
            return f"쿼리 결과: {str(df.head())}"
        else:
            return "유효한 SQL 쿼리가 아닙니다. SELECT ... FROM ... 형식이어야 합니다."
    except Exception as e:
        return f"SQL 쿼리 실행 중 오류 발생: {str(e)}"

# 데이터 시각화 도구
def create_visualization(viz_type, params):
    try:
        # 예시 데이터
        df = pd.read_csv("https://raw.githubusercontent.com/mwaskom/seaborn-data/master/iris.csv")
        
        plt.figure(figsize=(10, 6))
        
        if viz_type.lower() == "histogram":
            # 히스토그램 생성
            column = params.get("column", "sepal_length")
            if column in df.columns:
                plt.hist(df[column], bins=10)
                plt.title(f"{column} 히스토그램")
                plt.xlabel(column)
                plt.ylabel("빈도")
            else:
                return f"컬럼 '{column}'을 찾을 수 없습니다."
                
        elif viz_type.lower() == "scatter":
            # 산점도 생성
            x_col = params.get("x", "sepal_length")
            y_col = params.get("y", "sepal_width")
            
            if x_col in df.columns and y_col in df.columns:
                plt.scatter(df[x_col], df[y_col])
                plt.title(f"{x_col} vs {y_col}")
                plt.xlabel(x_col)
                plt.ylabel(y_col)
            else:
                return f"컬럼 '{x_col}' 또는 '{y_col}'을 찾을 수 없습니다."
        else:
            return f"시각화 유형 '{viz_type}'이 지원되지 않습니다."
        
        # 이미지를 문자열로 변환 (실제 환경에서는 파일로 저장하거나 웹에 표시)
        buf = BytesIO()
        plt.savefig(buf, format='png')
        buf.seek(0)
        img_str = base64.b64encode(buf.read()).decode('utf-8')
        
        return f"시각화 생성 완료. (base64 인코딩 이미지 데이터)"
    except Exception as e:
        return f"시각화 생성 중 오류 발생: {str(e)}"

# 데이터 분석 에이전트 도구 정의
data_analysis_tools = [
    Tool(
        name="LoadDataset",
        func=load_dataset,
        description="데이터셋을 로드합니다. 데이터셋 이름을 입력으로 받습니다."
    ),
    Tool(
        name="RunSQLQuery",
        func=run_sql_query,
        description="SQL 쿼리를 실행합니다. SQL 쿼리문을 입력으로 받습니다."
    ),
    Tool(
        name="CreateVisualization",
        func=create_visualization,
        description="""데이터 시각화를 생성합니다. JSON 형식으로 시각화 유형과 파라미터를 입력받습니다.
        예: {"type": "histogram", "params": {"column": "sepal_length"}}
        또는: {"type": "scatter", "params": {"x": "sepal_length", "y": "sepal_width"}}"""
    )
]

# 데이터 분석 에이전트 생성
data_analysis_agent = initialize_agent(
    data_analysis_tools,
    llm,
    agent=AgentType.STRUCTURED_CHAT_ZERO_SHOT_REACT_DESCRIPTION,
    verbose=True
)

# 에이전트 사용 예시
analysis_query = "iris 데이터셋을 로드하고, 꽃받침 길이(sepal_length)의 히스토그램을 생성해주세요."
analysis_result = data_analysis_agent.run(analysis_query)
```

## 7. 실습 과제

1. ReAct 패턴을 구현하고 커스텀 프롬프트로 성능을 최적화하세요.
2. LangGraph를 사용하여 조건부 분기를 포함한 에이전트 워크플로우를 설계하세요.
3. 연구자, 작가, 비평가로 구성된 멀티 에이전트 시스템을 구현하여 특정 주제에 대한 보고서를 생성하세요.
4. 자율 에이전트를 구현하여 복잡한 정보 수집 및 분석 작업을 자동화하세요.

## 📚 추가 자료

- [LangGraph 공식 문서](https://github.com/langchain-ai/langgraph)
- [ReAct 패턴 논문](https://arxiv.org/abs/2210.03629)
- [AutoGPT GitHub 저장소](https://github.com/Significant-Gravitas/Auto-GPT)
- [BabyAGI GitHub 저장소](https://github.com/yoheinakajima/babyagi)
- [LangChain Agent 문서](https://js.langchain.com/docs/modules/agents/) 