AI 에이전트란?
AI 에이전트는 LLM이 단순 텍스트 생성을 넘어 스스로 판단하고, 도구를 사용하고, 목표를 달성하는 자율 시스템입니다. 비서에게 “이 일 처리해줘”라고 맡기면, 비서가 스스로 계획을 세우고, 필요한 도구를 찾아 쓰고, 결과를 확인하는 것과 같습니다.
에이전트의 핵심 구성 요소:
| 구성 요소 | 역할 | 비유 |
|---|---|---|
| LLM (두뇌) | 추론, 판단, 계획 | 사람의 두뇌 |
| Tools (도구) | 외부 시스템과 상호작용 | 도구함 |
| Memory (기억) | 과거 행동과 결과 저장 | 메모장 |
| Planning (계획) | 목표 달성을 위한 단계 수립 | 할 일 목록 |
| Observation (관찰) | 도구 실행 결과 해석 | 눈과 귀 |
패턴 1: ReAct (Reasoning + Acting)
ReAct는 가장 기본적이고 널리 사용되는 에이전트 패턴입니다. 생각(Thought) → 행동(Action) → 관찰(Observation) 루프를 반복하여 문제를 해결합니다.
동작 흐름
사용자 질문 → [Thought] 무엇을 해야 하는지 추론
→ [Action] 적절한 도구 선택 및 실행
→ [Observation] 도구 실행 결과 확인
→ [Thought] 추가 행동이 필요한지 판단
→ ... (반복)
→ [Final Answer] 최종 답변 생성
구현 예시
from langchain_openai import ChatOpenAI
from langchain.agents import create_react_agent, AgentExecutor
from langchain_core.prompts import PromptTemplate
from langchain_core.tools import tool
@tool
def search_database(query: str) -> str:
"""데이터베이스에서 사용자 정보를 검색합니다."""
# 실제 DB 조회 로직
db = {"user:1001": {"name": "김철수", "plan": "프로", "usage": "85%"}}
return str(db.get(query, "결과 없음"))
@tool
def send_notification(message: str) -> str:
"""사용자에게 알림을 전송합니다."""
return f"알림 전송 완료: {message}"
# ReAct 프롬프트 (Thought/Action/Observation 구조)
react_prompt = PromptTemplate.from_template("""
다음 도구를 사용할 수 있습니다:
{tools}
도구 이름: {tool_names}
다음 형식을 사용하세요:
Thought: 무엇을 해야 하는지 생각
Action: 사용할 도구 이름
Action Input: 도구에 전달할 입력
Observation: 도구 실행 결과
... (필요한 만큼 반복)
Thought: 최종 답변을 알았습니다
Final Answer: 사용자에게 전달할 최종 답변
질문: {input}
{agent_scratchpad}
""")
llm = ChatOpenAI(model="gpt-4o", temperature=0)
tools = [search_database, send_notification]
agent = create_react_agent(llm, tools, react_prompt)
executor = AgentExecutor(agent=agent, tools=tools, verbose=True)
result = executor.invoke({"input": "user:1001의 사용량을 확인하고, 80% 이상이면 알림 보내줘"})
# Thought: user:1001의 사용량을 먼저 확인해야 합니다
# Action: search_database
# Action Input: user:1001
# Observation: {'name': '김철수', 'plan': '프로', 'usage': '85%'}
# Thought: 사용량이 85%로 80% 이상이므로 알림을 보내야 합니다
# Action: send_notification
# Action Input: 김철수님, 현재 사용량이 85%입니다. 요금제 업그레이드를 검토해주세요.
# Observation: 알림 전송 완료
# Final Answer: 김철수님의 사용량은 85%로 확인되었고, 알림을 전송했습니다.
ReAct의 장단점:
- 장점: 구현이 단순, 추론 과정이 투명, 디버깅 용이
- 단점: 단계별 순차 실행으로 속도 느림, 복잡한 작업에서 루프에 빠질 수 있음
패턴 2: Plan-and-Execute
Plan-and-Execute는 먼저 전체 계획을 수립한 후, 각 단계를 순서대로 실행합니다. 복잡한 작업에서 ReAct보다 안정적입니다.
동작 흐름
사용자 질문 → [Planner] 전체 계획 수립 (단계 1, 2, 3...)
→ [Executor] 단계 1 실행
→ [Executor] 단계 2 실행
→ [Re-planner] 필요시 계획 수정
→ [Executor] 단계 3 실행
→ [Final] 결과 종합 및 답변
구현 예시
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from typing import TypedDict
class AgentState(TypedDict):
task: str # 원래 작업
plan: list[str] # 실행 계획
current_step: int # 현재 단계
results: list[str] # 각 단계 결과
final_answer: str # 최종 답변
llm = ChatOpenAI(model="gpt-4o", temperature=0)
# 1단계: 계획 수립
planner_prompt = ChatPromptTemplate.from_template("""
다음 작업을 수행하기 위한 단계별 계획을 세우세요.
각 단계는 한 줄로, 번호를 붙여 작성하세요.
작업: {task}
계획:
""")
planner_chain = planner_prompt | llm | StrOutputParser()
# 2단계: 각 단계 실행
executor_prompt = ChatPromptTemplate.from_template("""
다음 단계를 실행하고 결과를 보고하세요.
전체 작업: {task}
현재 단계: {step}
이전 단계 결과: {previous_results}
""")
executor_chain = executor_prompt | llm | StrOutputParser()
# Plan-and-Execute 실행
def plan_and_execute(task: str) -> str:
"""계획을 세우고 단계별로 실행합니다."""
# 계획 수립
plan_text = planner_chain.invoke({"task": task})
steps = [s.strip() for s in plan_text.strip().split("\n") if s.strip()]
print(f"계획 수립 완료: {len(steps)}단계")
# 각 단계 실행
results = []
for i, step in enumerate(steps):
print(f"\n실행 중: 단계 {i+1} - {step}")
result = executor_chain.invoke({
"task": task,
"step": step,
"previous_results": "\n".join(results) if results else "없음"
})
results.append(f"단계 {i+1}: {result}")
return "\n".join(results)
# 실행
answer = plan_and_execute("Python FastAPI로 사용자 CRUD API를 설계해줘")
# 계획 수립 완료: 4단계
# 실행 중: 단계 1 - 데이터 모델 정의 (User 스키마)
# 실행 중: 단계 2 - CRUD 엔드포인트 설계
# 실행 중: 단계 3 - 에러 핸들링 추가
# 실행 중: 단계 4 - API 문서 및 테스트 코드 작성
Plan-and-Execute의 장단점:
- 장점: 복잡한 작업에서 안정적, 전체 흐름을 미리 파악 가능
- 단점: 초기 계획 수립 비용, 계획 변경 시 재계획 필요
패턴 3: Multi-Agent (다중 에이전트)
Multi-Agent는 전문화된 여러 에이전트가 협업하여 작업을 수행합니다. 각 에이전트가 고유한 역할과 도구를 가지고, 오케스트레이터가 작업을 분배합니다.
아키텍처
사용자 질문 → [Orchestrator] 작업 분석 및 할당
├─→ [Research Agent] 정보 수집
├─→ [Code Agent] 코드 작성
└─→ [Review Agent] 결과 검토
→ [Orchestrator] 결과 통합 및 최종 답변
구현 예시
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
llm = ChatOpenAI(model="gpt-4o", temperature=0)
# 전문 에이전트 정의
def create_specialist(role: str, expertise: str):
"""역할별 전문 에이전트를 생성합니다."""
prompt = ChatPromptTemplate.from_messages([
("system", f"당신은 {role}입니다. {expertise} 분야의 전문가입니다."),
("human", "작업: {task}\n\n이전 결과:\n{context}")
])
return prompt | llm | StrOutputParser()
# 역할별 에이전트 생성
researcher = create_specialist(
"리서처", "기술 트렌드 조사와 문서 분석"
)
architect = create_specialist(
"아키텍트", "시스템 설계와 아키텍처 결정"
)
reviewer = create_specialist(
"리뷰어", "코드 품질 검토와 개선 제안"
)
# 오케스트레이터: 작업을 분배하고 결과를 통합
def orchestrate(task: str) -> str:
"""멀티 에이전트 워크플로우를 실행합니다."""
# 1단계: 리서처가 조사
print("리서처 작업 중...")
research = researcher.invoke({"task": task, "context": "없음"})
# 2단계: 아키텍트가 설계
print("아키텍트 작업 중...")
design = architect.invoke({"task": task, "context": research})
# 3단계: 리뷰어가 검토
print("리뷰어 작업 중...")
review = reviewer.invoke({
"task": f"다음 설계를 검토해주세요: {task}",
"context": design
})
# 결과 통합
return f"## 조사 결과\n{research}\n\n## 설계\n{design}\n\n## 리뷰\n{review}"
result = orchestrate("마이크로서비스 인증 시스템 설계")
print(result)
Multi-Agent의 장단점:
- 장점: 전문화된 역할 분담, 복잡한 작업 병렬 처리 가능
- 단점: 에이전트 간 통신 오버헤드, 조율 복잡성, 비용 증가
패턴 비교 요약
| 기준 | ReAct | Plan-and-Execute | Multi-Agent |
|---|---|---|---|
| 복잡도 | 낮음 | 중간 | 높음 |
| 적합한 작업 | 단순 도구 호출 | 다단계 작업 | 전문 분야 결합 |
| LLM 호출 수 | 단계당 1회 | 계획 + 단계당 1회 | 에이전트 수 x 단계 |
| 디버깅 | 쉬움 | 보통 | 어려움 |
| 비용 | 낮음 | 중간 | 높음 |
| 안정성 | 루프 위험 | 안정적 | 조율에 의존 |
프레임워크별 지원
| 프레임워크 | ReAct | Plan-and-Execute | Multi-Agent |
|---|---|---|---|
| LangChain/LangGraph | 기본 지원 | LangGraph로 구현 | LangGraph로 구현 |
| CrewAI | - | - | 핵심 기능 |
| AutoGen | - | - | 핵심 기능 |
| OpenAI Assistants | 기본 지원 | 커스텀 구현 | 커스텀 구현 |
실전 팁
- 단순한 패턴부터 시작하세요: 대부분의 작업은 ReAct로 충분합니다. 복잡한 패턴은 ReAct가 한계를 보일 때 도입하세요.
- 가드레일을 설정하세요: 최대 반복 횟수(
max_iterations), 타임아웃, 비용 한도를 반드시 설정하여 무한 루프를 방지하세요. - 도구 설명을 구체적으로 작성하세요: 에이전트가 올바른 도구를 선택하려면, 도구의 docstring이 명확해야 합니다. 도구가 무엇을 하는지, 언제 사용해야 하는지 모두 기술하세요.
- 관찰 결과를 제한하세요: 도구가 반환하는 데이터가 너무 크면 컨텍스트 윈도우를 낭비합니다. 결과를 요약하거나 필요한 부분만 추출하세요.
- LangGraph를 고려하세요: 복잡한 에이전트 워크플로우에는 LangGraph가 상태 관리와 분기 로직을 그래프로 명확하게 표현할 수 있어 유리합니다.
- 평가 체계를 만드세요: 에이전트의 정확도, 비용, 속도를 정량적으로 측정하는 평가 파이프라인을 구축하면 패턴 선택과 개선에 도움이 됩니다.