DEV Community

Cover image for AI 에이전트 관리 멈추는 방법
Rihpig
Rihpig

Posted on • Originally published at apidog.com

AI 에이전트 관리 멈추는 방법

TL;DR

AI 에이전트를 베이비시팅하지 않으려면 세 가지를 구축해야 합니다: 가드레일(치명적인 오류를 코드로 막는 제약), 관측 가능성(실행 흐름을 추적하는 로그 및 메트릭), 체크포인트(중대한 결정 직전 자동 일시 정지). 이 세 가지를 갖추면 에이전트는 몇 분이 아닌 몇 시간 동안 자율적으로 실행 가능합니다. Apidog 같은 도구를 활용하면 API 계약을 엄격하게 만들 수 있어, API 계층 자체를 안전망으로 바꿀 수 있습니다.

오늘 Apidog를 사용해보세요

서론

지난주 한 개발자가 시간을 단축할 목적으로 도입한 AI 에이전트를 4시간 동안 계속 감독하는 것을 봤습니다. 몇 분마다 에이전트를 멈추고, 실수를 수정하고, 다시 시작하는 과정을 반복했습니다. 결과적으로 직접 코드를 작성하는 것보다 더 많은 수동 작업이 필요했습니다.

이것이 바로 '베이비시팅 문제'입니다. 대부분의 AI 에이전트 프로젝트가 약속한 결과를 내지 못하는 주요 원인입니다. 도구와 모델은 충분히 강력하지만, 많은 팀이 '항상 감시' 단계에서 탈출하지 못합니다.

대부분의 AI 에이전트 설정은 LLM을 모든 작업에 대해 꼼꼼히 지시해야 하는 주니어 개발자처럼 다룹니다. 하지만 LLM은 경계가 없으면 자신감 있게 잘못된 행동을 하는, 빠르지만 때때로 환각을 일으키는 인턴에 가깝습니다.

💡 API를 구축하거나 API를 호출하는 AI 에이전트와 작업할 때, Apidog는 정확한 요청/응답 스키마를 지정해 에이전트가 위반할 수 없는 계약을 만들 수 있습니다. 즉, 에이전트가 헤매지 않도록 지도를 주는 셈입니다.

AI 에이전트가 따라야 할 API 계약을 정의하세요.

이 가이드를 읽으면 다음을 얻을 수 있습니다:

  • 에이전트 자율성에 대한 사고 모델
  • 가드레일, 관측 가능성, 체크포인트 구현 패턴
  • 바로 사용할 수 있는 코드 예시
  • 에이전트가 감독 없이 실행될 준비가 되었는지 평가하는 체크리스트

왜 에이전트는 지속적인 감독이 필요한가

AI 에이전트는 예측 가능한 방식으로 실패합니다. 주요 실패 모드를 파악하고, 각 모드에 맞는 방어 전략을 세워야 합니다.

실패 모드 1: 범위 확장 (Scope creep)

예: "API 엔드포인트에 인증 추가"를 요청했더니, 인증을 추가하고, 속도 제한도 넣고, 데이터베이스 스키마까지 리팩토링, 심지어 "사용되지 않는" 파일까지 삭제해버립니다. LLM은 '완료'의 개념이 없습니다. 멈추라는 신호가 없으면 끝없이 작업을 이어갑니다.

실패 모드 2: 잘못된 추상화

예: "오류 처리 개선" 요청에, try-catch를 모든 곳에 추가합니다. 기술적으로 문제는 없으나, 코드 품질은 한참 떨어집니다. 의도를 이해하지 못한 채, 가장 단순한 답을 택합니다.

실패 모드 3: 연쇄적인 실패

예: 1단계의 작은 오타가 10단계 이후 모든 API, 테스트를 깨뜨립니다. 각 단계는 개별적으로는 합리적이나, 전체적으로는 심각한 결함을 남깁니다.

실패 모드 4: 자원 고갈

예: 실패한 API 호출을 무한히 재시도하거나, 제한 없이 하위 에이전트를 생성, 과금 한도까지 소진합니다. 자원 제한이 없으면 멈출 줄 모릅니다.

자율성 프레임워크: 가드레일, 관측 가능성, 체크포인트

이 문제들은 세 가지 계층으로 해결합니다.

  1. 가드레일(예방): 치명적 오류를 코드 레벨에서 강제
  2. 관측 가능성(감지): 실행 흐름, 결정, 오류를 추적
  3. 체크포인트(복구): 자동 일시정지 및 인간 승인

계층 1: 가드레일 (예방)

가드레일은 코드로 강제되는 제약입니다.

1. 파일 시스템 제약

# 하지 마세요: 에이전트의 프롬프트에만 의존
agent.run("Only modify files in the src/ directory")

# 하세요: 실제 코드로 경계 강제
import os
from pathlib import Path

ALLOWED_DIRECTORIES = {"src", "tests", "docs"}

def validate_file_path(path: str) -> bool:
    """허용된 폴더 외부엔 에이전트가 접근 불가"""
    abs_path = Path(path).resolve()
    return any(
        str(abs_path).startswith(str(Path(d).resolve()))
        for d in ALLOWED_DIRECTORIES
    )

def agent_write_file(path: str, content: str):
    if not validate_file_path(path):
        raise ValueError(f"Cannot write to {path}: outside allowed directories")
    with open(path, 'w') as f:
        f.write(content)
Enter fullscreen mode Exit fullscreen mode

2. API 스키마 제약

에이전트가 API를 호출할 때는 반드시 스키마를 활용해 요청을 검증하세요. Apidog는 이를 쉽게 만듭니다.

// apidog-schema.ts
export const CreateUserSchema = {
  type: 'object',
  required: ['email', 'name'],
  properties: {
    email: { type: 'string', format: 'email' },
    name: { type: 'string', minLength: 1, maxLength: 100 },
    role: { type: 'string', enum: ['user', 'admin', 'guest'] }
  },
  additionalProperties: false
}

// 유효성 검사 적용
function validateRequest(schema: object, data: unknown): void {
  const valid = ajv.validate(schema, data)
  if (!valid) {
    throw new Error(`Invalid request: ${JSON.stringify(ajv.errors)}`)
  }
}
Enter fullscreen mode Exit fullscreen mode

3. 예산 제약

import time
from dataclasses import dataclass

@dataclass
class AgentBudget:
    max_steps: int = 50
    max_tokens: int = 100000
    max_time_seconds: int = 600  # 10분
    max_api_calls: int = 100

class BudgetEnforcer:
    def __init__(self, budget: AgentBudget):
        self.budget = budget
        self.start_time = time.time()
        self.steps = 0
        self.tokens_used = 0
        self.api_calls = 0

    def check(self) -> bool:
        elapsed = time.time() - self.start_time
        if self.steps >= self.budget.max_steps:
            raise RuntimeError(f"Step limit reached: {self.steps}")
        if self.tokens_used >= self.budget.max_tokens:
            raise RuntimeError(f"Token limit reached: {self.tokens_used}")
        if elapsed >= self.budget.max_time_seconds:
            raise RuntimeError(f"Time limit reached: {elapsed:.0f}s")
        if self.api_calls >= self.budget.max_api_calls:
            raise RuntimeError(f"API call limit reached: {self.api_calls}")
        return True

    def record_step(self, tokens: int, api_calls: int = 0):
        self.steps += 1
        self.tokens_used += tokens
        self.api_calls += api_calls
        self.check()
Enter fullscreen mode Exit fullscreen mode

계층 2: 관측 가능성 (감지)

에이전트의 실행 흐름, 결정, 문제를 자동으로 추적하세요.

1. 구조화된 로깅

import json
from datetime import datetime
from typing import Any

class AgentLogger:
    def __init__(self, log_file: str = "agent_trace.jsonl"):
        self.log_file = log_file
        self.entries = []

    def log(self, event: str, data: dict[str, Any] | None = None):
        entry = {
            "timestamp": datetime.utcnow().isoformat(),
            "event": event,
            "data": data or {}
        }
        self.entries.append(entry)
        with open(self.log_file, 'a') as f:
            f.write(json.dumps(entry) + '\n')

    def log_decision(self, decision: str, reasoning: str, confidence: float):
        self.log("decision", {
            "decision": decision,
            "reasoning": reasoning,
            "confidence": confidence
        })

    def log_action(self, action: str, params: dict, result: str):
        self.log("action", {
            "action": action,
            "params": params,
            "result": result[:200]
        })

    def log_error(self, error: str, context: dict):
        self.log("error", {
            "error": error,
            "context": context
        })

# 사용 예시
logger = AgentLogger()
logger.log_decision(
    decision="API에 속도 제한 추가",
    reasoning="현재 엔드포인트는 악용 방지 기능이 없습니다",
    confidence=0.85
)
logger.log_action(
    action="write_file",
    params={"path": "src/middleware/rate-limit.ts"},
    result="성공적으로 45줄 작성"
)
Enter fullscreen mode Exit fullscreen mode

2. 메트릭 대시보드

from collections import Counter
from dataclasses import dataclass, field

@dataclass
class AgentMetrics:
    actions_taken: Counter = field(default_factory=Counter)
    files_modified: list[str] = field(default_factory=list)
    api_calls: dict[str, int] = field(default_factory=dict)
    errors: list[str] = field(default_factory=list)
    decisions_by_confidence: dict[str, int] = field(default_factory=lambda: {
        "high (>0.9)": 0,
        "medium (0.7-0.9)": 0,
        "low (<0.7)": 0
    })

    def record_action(self, action: str):
        self.actions_taken[action] += 1

    def record_file_modification(self, path: str):
        if path not in self.files_modified:
            self.files_modified.append(path)

    def record_api_call(self, endpoint: str):
        self.api_calls[endpoint] = self.api_calls.get(endpoint, 0) + 1

    def record_error(self, error: str):
        self.errors.append(error)

    def record_decision(self, confidence: float):
        if confidence > 0.9:
            self.decisions_by_confidence["high (>0.9)"] += 1
        elif confidence >= 0.7:
            self.decisions_by_confidence["medium (0.7-0.9)"] += 1
        else:
            self.decisions_by_confidence["low (<0.7)"] += 1

    def summary(self) -> str:
        return f"""
에이전트 메트릭 요약
=====================
작업: {dict(self.actions_taken)}
수정된 파일: {len(self.files_modified)}
API 호출: {self.api_calls}
오류: {len(self.errors)}
신뢰도별 결정: {self.decisions_by_confidence}
"""
Enter fullscreen mode Exit fullscreen mode

계층 3: 체크포인트 (복구)

중대한 작업 전/후에는 자동 체크포인트를 두어, 인간 승인을 기다리게 하세요.

from enum import Enum
from typing import Callable
from dataclasses import dataclass

class CheckpointTrigger(Enum):
    BEFORE_FILE_WRITE = "before_file_write"
    BEFORE_API_CALL = "before_api_call"
    BEFORE_GIT_COMMIT = "before_git_commit"
    BEFORE_DELETE = "before_delete"
    AFTER_N_STEPS = "after_n_steps"

@dataclass
class Checkpoint:
    trigger: CheckpointTrigger
    description: str
    data: dict
    requires_approval: bool = True

class CheckpointManager:
    def __init__(self, auto_approve: set[CheckpointTrigger] | None = None):
        self.auto_approve = auto_approve or set()
        self.pending: list[Checkpoint] = []

    def create_checkpoint(
        self, 
        trigger: CheckpointTrigger, 
        description: str, 
        data: dict
    ) -> bool:
        if trigger in self.auto_approve:
            return True
        checkpoint = Checkpoint(
            trigger=trigger,
            description=description,
            data=data
        )
        self.pending.append(checkpoint)
        return False  # 실제 시스템에서는 알림 후 승인을 기다립니다

    def approve(self, checkpoint_id: int) -> None:
        if 0 <= checkpoint_id < len(self.pending):
            self.pending.pop(checkpoint_id)

    def reject(self, checkpoint_id: int) -> None:
        raise RuntimeError(f"Checkpoint rejected: {self.pending[checkpoint_id]}")

# 사용 예시
checkpoints = CheckpointManager(
    auto_approve={CheckpointTrigger.BEFORE_FILE_WRITE}
)
if not checkpoints.create_checkpoint(
    trigger=CheckpointTrigger.BEFORE_DELETE,
    description="src/legacy/ 디렉토리 삭제 예정",
    data={"path": "src/legacy/", "files": ["old_handler.ts", "deprecated.ts"]}
):
    agent.pause("파일 삭제 승인 대기 중")
Enter fullscreen mode Exit fullscreen mode

Apidog로 자율 에이전트 구축

AI 에이전트가 API와 상호작용할 때, 하위 시스템 오류의 주요 원인은 잘못된 형식의 요청입니다. Apidog는 정확한 API 스키마를 정의하여, 에이전트가 반드시 따라야 하는 제약을 API 레이어에 부여합니다.

API 계약 설정 방법

  1. Apidog에서 OpenAPI 사양을 정의 또는 가져오기
  2. 내장 유효성 검사 기능으로 클라이언트 코드 생성
  3. 에이전트에 유효성 검증된 클라이언트 제공
// (권장) 유효성 검증된 클라이언트 사용
import { UsersApi } from './generated/apidog-client'

const usersApi = new UsersApi()
// 에이전트는 스키마를 위반하는 요청을 보낼 수 없음
const response = await usersApi.createUser({
  email: 'user@example.com',
  name: 'Test User',
  role: 'user'  // enum 값만 허용
})
Enter fullscreen mode Exit fullscreen mode

API 계층이 곧 가드레일이 됩니다. 잘못된 데이터는 실제로 전송되기 전에 차단됩니다.

AI 에이전트를 위한 유효성 검증 API 클라이언트를 생성하세요.

검증된 패턴 및 흔한 실수

패턴 1: 승인 샌드위치

위험한 작업 전/후 모두 승인 절차를 두세요.

def risky_operation(agent, operation):
    # 사전 승인
    if not agent.checkpoint(f"다음 작업을 수행하려고 합니다: {operation.description}"):
        return "사용자에 의해 취소됨"
    # 실행
    result = operation.execute()
    # 사후 승인
    if not agent.checkpoint(f"작업 결과를 확인하세요: {operation.description}"):
        operation.rollback()
        return "사용자에 의해 롤백됨"
    return result
Enter fullscreen mode Exit fullscreen mode

패턴 2: 신뢰도 임계값

낮은 신뢰도의 결정이 즉시 실행되지 않게 하세요.

MIN_CONFIDENCE = 0.75

def agent_decide(options: list[dict]) -> dict:
    best = max(options, key=lambda x: x.get('confidence', 0))
    if best['confidence'] < MIN_CONFIDENCE:
        # 인간 승인 필요
        return {
            'action': 'escalate',
            'reason': f"최적 옵션의 신뢰도 {best['confidence']:.2f} < {MIN_CONFIDENCE}",
            'options': options
        }
    return best
Enter fullscreen mode Exit fullscreen mode

패턴 3: 멱등성 작업

에이전트의 작업은 부작용 없이 여러 번 반복 가능해야 합니다.

import hashlib
import os

def idempotent_write(path: str, content: str) -> bool:
    """내용이 변경된 경우에만 파일을 씁니다."""
    content_hash = hashlib.sha256(content.encode()).hexdigest()
    existing_hash = None
    if os.path.exists(path):
        with open(path, 'r') as f:
            existing_hash = hashlib.sha256(f.read().encode()).hexdigest()
    if content_hash == existing_hash:
        logger.log_action("write_file", {"path": path}, "건너뜀 - 변경 사항 없음")
        return False
    with open(path, 'w') as f:
        f.write(content)
    logger.log_action("write_file", {"path": path}, f"{len(content)} 바이트 작성 완료")
    return True
Enter fullscreen mode Exit fullscreen mode

피해야 할 흔한 실수

  • 프롬프트에만 의존하지 마세요. "파일을 삭제하지 마시오"라는 프롬프트는 제약이 아닙니다. 코드와 권한으로 강제하세요.
  • 롤백 계획 없는 작업 금지. Git 또는 백업 없이 에이전트가 되돌릴 수 없는 작업을 하게 두지 마세요.
  • 신뢰도 점수 무시 금지. 낮은 신뢰도는 반드시 인간 확인이 필요합니다.
  • 과도한 모니터링 지양. 모든 단계를 직접 확인 중이면 자율 시스템이 아닙니다.
  • 명확한 종료 조건 정의. "버그 수정"이 아닌 "버그 수정 후 모든 테스트 통과"처럼 완료 기준을 명확히 하세요.

대안 및 비교

접근 방식 자율성 위험 가장 적합한 경우
수동 코딩 없음 낮음 복잡하고 중요한 작업
AI와 페어 프로그래밍 낮음 낮음 학습, 탐색
감독형 에이전트 중간 중간 일상적인 작업
가드레일이 있는 자율 에이전트 높음 통제 가능 대량 작업, 마이그레이션
완전 자율 에이전트 매우 높음 높음 신뢰할 수 있고 잘 테스트된 워크플로우

실제 대부분의 팀은 "가드레일이 있는 자율" 단계가 가장 생산적입니다. 10%의 위험으로 80%의 시간 절약을 얻을 수 있습니다.


실제 사용 사례

코드베이스 마이그레이션

한 팀이 200개 API 엔드포인트를 REST에서 GraphQL로 마이그레이션. 가드레일로 스키마 변경을 방지, 체크포인트로 삭제 전 승인 절차. 3주 걸릴 작업을 3일 만에, 사고 없이 완료.

문서 자동 생성

에이전트가 코드에서 API 문서를 자동 생성. 가드레일로 읽기 가능한 디렉토리 제한, 체크포인트로 발행 전 일시정지. 팀은 매주 한번만 검토.

테스트 커버리지 향상

에이전트가 누락된 테스트 자동 작성. 예산 제약으로 무한 생성 방지, 신뢰도 임계값으로 불확실한 테스트는 인간 검토. 한 달 만에 커버리지 60%→85% 상승.

마무리

정리하자면:

  • AI 에이전트는 범위 확장, 잘못된 추상화, 연쇄적 실패, 자원 고갈 등 예측 가능한 방식으로 실패합니다.
  • 가드레일(예방), 관측 가능성(감지), 체크포인트(복구) 세 가지 계층으로 문제를 방지할 수 있습니다.
  • 가드레일은 프롬프트가 아니라 코드로 강제하세요.
  • 관측 가능성은 로그와 메트릭으로 자동 추적하세요.
  • 체크포인트는 인간 검증을 위한 자동 일시정지입니다.
  • Apidog의 스키마 기반 API는 API 계층을 가드레일로 전환합니다.

다음 단계:

  1. 반복적이고 AI 활용이 많은 작업을 파악하세요.
  2. 에이전트가 절대 해서는 안 될 행위를 코드로 강제(가드레일)하세요.
  3. 구조화된 로깅으로 실행 흐름을 추적하세요.
  4. 고위험 작업에 체크포인트를 추가하세요.
  5. 30분간 실행 후, 로그를 검토하세요.

목표는 인간을 루프에서 제거하는 것이 아니라, 반복적 실수 대신 중요한 결정을 내리도록 루프의 위치를 바꾸는 것입니다.

AI 에이전트를 위한 API 가드레일을 무료로 구축해보세요.

자주 묻는 질문

AI 에이전트와 AI 어시스턴트의 차이점은?

어시스턴트는 요청이 있을 때만 응답, 에이전트는 목표를 향해 스스로 계획하고 실행합니다. 어시스턴트는 모든 루프에 인간이 필요하고, 에이전트는 체크포인트나 완료 시점까지 스스로 동작합니다.

내 에이전트가 자율 실행 준비가 되었는지 어떻게 알 수 있나요?

10회 감독 세션을 실행해 개입 시점을 기록하세요. 세션당 2회 미만 개입, 모두 사소한 명확화라면 준비된 것입니다. 자주 개입하거나 롤백 필요하다면 더 많은 가드레일이 필요합니다.

자율 에이전트의 가장 큰 위험은?

연쇄적인 실패를 에이전트가 인지하지 못할 때입니다. 초기 실수가 누적되어 심각한 문제로 발전합니다. 체크포인트를 통해 연쇄를 차단해야 합니다.

어떤 LLM에서도 이 패턴을 적용할 수 있나요?

네. 가드레일·관측 가능성·체크포인트는 모델에 무관하게 적용 가능합니다.

관측 가능성이 에이전트 속도를 얼마나 늦추나요?

매우 미미합니다. 로그 기록은 마이크로초 단위이므로 병목이 아닙니다. 주요 속도 저하는 체크포인트(인간 승인 대기)에서 발생합니다.

에이전트가 내가 동의하지 않는 결정을 내리면?

체크포인트에서 거부하면 됩니다. 그럼 에이전트가 롤백하거나, 대안을 시도합니다. 더 나은 방법은, 지침에 선호도를 포함시켜 학습하도록 하는 것입니다.

감독형 에이전트로 시작해야 하나요?

항상 감독형으로 시작하세요. 신뢰할 때까지 모든 주요 작업에 체크포인트를 두고, 점차적으로 자율 범위를 확대하세요.

Apidog는 AI 에이전트에 어떻게 도움이 되나요?

Apidog는 스키마 기반 유효성 검증 API 클라이언트를 생성합니다. 에이전트가 이를 쓸 때, 잘못된 요청이 백엔드에 도달하기 전에 차단되어, 데이터/포맷 오류로 인한 실패를 방지합니다.

Top comments (0)