실제 작업을 수행하는 자율 AI 에이전트 구축
Source: Dev.to

“AI‑Powered” 도구의 문제점
모든 SaaS 제품이 이제 랜딩 페이지에 “AI‑powered” 라는 문구를 붙이지만, 그 중 99 %는 단일 LLM 호출을 감싸는 얇은 UI에 불과합니다. 여전히 해야 할 일은 다음과 같습니다:
- 무엇을 할지 결정하기
- 프롬프트 작성하기
- 출력 결과 검토하기
- 다음에 할 일을 결정하기
- 영원히 반복하기
이것은 자동화가 아니라, 기계가 타이핑을 하는 동안 당신이 모든 생각을 하는 것입니다.
실제 에이전트가 어떻게 보이는가
자율 에이전트는 단일 프롬프트‑응답이 아니라 루프를 따라 동작합니다. 제가 사용하는 패턴은 ReAct 프레임워크에 기반합니다:
┌─────────────────────────────────────┐
│ AGENT LOOP │
│ │
│ ┌──────────┐ │
│ │ PERCEIVE │◄── APIs, databases, │
│ └────┬─────┘ live data sources │
│ │ │
│ ┌────▼─────┐ │
│ │ REASON │◄── LLM + context │
│ └────┬─────┘ from RAG store │
│ │ │
│ ┌────▼─────┐ │
│ │ ACT │──► API calls, DB │
│ └────┬─────┘ writes, alerts │
│ │ │
│ └──────────► loop back ◄──────│
└─────────────────────────────────────┘
각 반복에서 에이전트는:
- Perceives(인식) – API, 데이터베이스, 실시간 데이터를 통해 환경을 파악합니다.
- Reasons(추론) – LLM과 검색된 컨텍스트를 활용해 사고합니다.
- Acts(행동) – API 호출, DB 쓰기, 알림 전송 등 실제 작업을 수행합니다.
핵심 인사이트: 에이전트가 언제 멈출지를 결정한다는 점이며, 이는 사용자가 아닌 에이전트가 판단합니다.
아키텍처: 단일‑에이전트가 아닌 멀티‑에이전트
하나의 “큰” 에이전트는 금방 환각을 일으키고 컨텍스트를 잃어버립니다. 대신 전문화된 서브‑에이전트에 책임을 분산합니다.
from dataclasses import dataclass
from enum import Enum
class AgentRole(Enum):
RESEARCHER = "researcher"
AUDITOR = "auditor"
STRATEGIST = "strategist"
EXECUTOR = "executor"
@dataclass
class AgentTask:
role: AgentRole
objective: str
constraints: list[str]
context: dict
class AgentOrchestrator:
def __init__(self, agents: dict[AgentRole, "Agent"]):
self.agents = agents
self.shared_state: dict = {}
async def run_pipeline(self, trigger: dict):
# 1️⃣ Researcher gathers data
research = await self.agents[AgentRole.RESEARCHER].execute(
AgentTask(
role=AgentRole.RESEARCHER,
objective="Analyze SERP changes for target keywords",
constraints=["Use DataForSEO API", "Max 500 queries"],
context=trigger,
)
)
# 2️⃣ Auditor validates against guidelines
audit = await self.agents[AgentRole.AUDITOR].execute(
AgentTask(
role=AgentRole.AUDITOR,
objective="Check findings against brand guidelines",
constraints=["Flag confidence 0.8:
await self.agents[AgentRole.EXECUTOR].execute(
AgentTask(
role=AgentRole.EXECUTOR,
objective="Implement approved changes",
constraints=["Dry‑run first", "Log all mutations"],
context={"strategy": strategy},
)
)
각 서브‑에이전트는 좁은 범위의 역할을 가집니다:
- Researcher는 절대 콘텐츠를 작성하지 않습니다.
- Executor는 절대 전략을 결정하지 않습니다.
이러한 분리 덕분에 환각이 제한됩니다.
Source:
각 서브‑에이전트 내부의 ReAct 루프
각 서브‑에이전트는 ReAct 패턴을 사용하여 자체 추론 루프를 실행합니다 – Action 이전에 Thought 를 생성합니다.
import json
import openai
from typing import Callable
class Agent:
def __init__(self, role: AgentRole, tools: list[Callable]):
self.role = role
self.tools = {t.__name__: t for t in tools}
self.client = openai.AsyncOpenAI()
async def execute(self, task: AgentTask, max_steps: int = 10):
messages = [
{"role": "system", "content": self._build_system_prompt(task)},
]
for step in range(max_steps):
response = await self.client.chat.completions.create(
model="gpt-4o",
messages=messages,
tools=self._tool_schemas(),
)
message = response.choices[0].message
# If the LLM decides to call a tool, run it and feed the result back
if message.tool_calls:
for call in message.tool_calls:
tool_name = call.function.name
args = json.loads(call.function.arguments)
result = await self.tools[tool_name](**args)
messages.append({"role": "tool", "content": str(result)})
continue
# No tool call → final answer
messages.append({"role": "assistant", "content": message.content})
break
return messages[-1]["content"]
# -----------------------------------------------------------------
# Helper methods (implementation details omitted for brevity)
# -----------------------------------------------------------------
def _build_system_prompt(self, task: AgentTask) -> str:
return (
f"You are a {task.role.value} agent.\n"
f"Objective: {task.objective}\n"
f"Constraints: {', '.join(task.constraints)}\n"
f"Context: {json.dumps(task.context)}"
)
def _tool_schemas(self) -> list[dict]:
"""Return OpenAI‑compatible tool specifications for self.tools."""
# Placeholder – in a real implementation you would generate JSON schema
# objects for each callable in self.tools.
return []
각 에이전트는:
- 시스템 프롬프트를 받아 자신의 역할, 목표, 제약 조건 및 컨텍스트를 정의합니다.
- Thought(LLM의 다음 메시지)를 생성합니다.
- Thought에 툴 호출이 포함되어 있으면 해당 툴을 실행하고, 결과를
tool메시지로 다시 전달합니다. - 툴 호출이 없고 최종 답변이 생성될 때까지 루프를 반복합니다.
요점
- 루프 기반 에이전트 (인식 → 추론 → 행동)는 단일 프롬프트 UI보다 훨씬 강력합니다.
- 전문화된 하위 에이전트는 전체 시스템을 기반에 두고 환상을 줄입니다.
- ReAct 패턴은 각 에이전트에게 체계적인 추론 사이클을 제공하여 외부 도구를 호출할 시점과 중단할 시점을 결정하게 합니다.
이 아키텍처를 사용하면 자율적인 SEO 어시스턴트를 밤새 실행하도록 하여 SERP를 지속적으로 모니터링하고, 브랜드 준수 여부를 감사하며, 전략을 수립하고, 변경을 실행할 수 있습니다—모두 인간 개입 없이. 🚀
예시 워크플로우 스니펫
| Step | Reason | Act |
|---|---|---|
| 단계가 15 % 증가했지만 인상 수는 변함이 없었습니다. | 플래그된 각 페이지에 대해 현재 SERP를 가져옵니다. LLM에 다음을 물어봅니다: “검색 의도가 바뀌었나요? 경쟁자는 다른 콘텐츠 형식을 사용하고 있나요?” | 구체적인 권장 사항이 포함된 리프레시 브리프를 생성합니다. 신뢰도가 충분히 높다면 WordPress API를 통해 제목‑태그 업데이트를 직접 푸시합니다. |
전체 과정은 24 시간마다 실행됩니다. 찾은 내용과 수행한 작업을 Slack 알림으로 받습니다. 대부분의 날은 아무 것도 찾지 못하지만, 어떤 날은 내가 수동으로 발견하기 몇 주 전에 감소 패턴을 포착합니다.
이것이 크론 스크립트와 다른 점
크론 스크립트는 매번 같은 작업을 수행합니다. 에이전트는 보는 것에 대해 추론합니다.
- SERP가 리스트형 기사에서 비디오 캐러셀로 바뀔 때, 크론 스크립트는 계속해서 리스트형 기사를 최적화합니다.
- 에이전트는 형식 변화를 감지하고 권장 사항을 조정합니다.
이 차이는 중요합니다: 단계별 지시가 아니라 목표와 제약 조건을 정의합니다. 에이전트가 그 단계를 스스로 찾아냅니다.
실제로 중요한 가드레일
- 드라이런 모드 – 모든 변형은 실행 전에 기록됩니다. 에이전트가 제안하고, 인간(또는 두 번째 에이전트)이 승인합니다.
- 신뢰도 임계값 – 신뢰도가 0.7 이하인 행동은 자동 실행 대신 인간 검토를 위해 대기열에 넣습니다.
- 감사 추적 – 모든 생각, 도구 호출 및 결과가 기록됩니다. 블랙 박스가 없습니다.
- 예산 상한 – 실행당 최대 API 호출 수, 에이전트당 최대 토큰 수, 사이클당 최대 변형 수를 제한합니다.
목표는 “인간을 루프에서 제외”하는 것이 아니라 “인간을 루프에 포함”하는 것입니다. 당신이 경계를 설정하면 에이전트는 그 안에서 작동하고, 당신은 대시보드를 검토합니다.
시작하기
복잡한 다중‑에이전트 시스템은 첫날에 필요하지 않습니다. 한 가지 일을 하는 단일 에이전트부터 시작하세요:
- 매주 반복하는 작업 하나를 선택합니다.
- 데이터를 가져오는 perceive 단계(데이터 수집)를 수행하는 파이썬 스크립트를 작성합니다.
- 데이터를 분석하는 reason 단계(데이터 분석)를 위해 LLM 호출을 추가합니다.
- 분석 결과를 활용하는 act 단계(무언가 수행)를 위해 API 호출을 추가합니다.
- 종료 조건이 있는 루프로 감쌉니다.
그것이 에이전트입니다. 나머지는 최적화에 불과합니다.
추가 읽을거리
전체 아키텍처 세부 정보를 포함한 심층 탐구:
Agentic AI Workflows: Beyond Basic Content Generation