프로덕션에서 AI 에이전트가 폭주하는 걸 막는 7가지 패턴
AI 에이전트가 개발 환경에서는 완벽하게 동작해요. 테스트 전부 통과, 데모 시나리오 깔끔하게 소화, 스프린트 리뷰에서 관계자들 박수까지 받죠. 배포합니다. 48시간 안에 재귀 루프에 빠져서 API 비용 $400 태우고, 고객한테 옆집 사람 개인정보가 담긴 이메일 날리고, 프로덕션 DB에서 인덱스를 떨구는 SQL을 자신만만하게 생성해요.
가상 시나리오가 아닙니다. 2026년 업계 곳곳에서 실제로 터지고 있는 패턴이에요. "데모에서 잘 돌아감"이랑 "프로덕션에서 안전함" 사이의 간극, 대부분의 팀이 생각하는 것보다 훨씬 큽니다. 장애 패턴 자체가 기존 소프트웨어랑 근본적으로 달라요. REST API가 질문을 바꿔서 대답하진 않잖아요. DB 드라이버가 테이블 이름을 환각하지도 않고요. 근데 AI 에이전트는 둘 다 해요. 확신에 차서.
이 가이드에서 프로덕션 AI 에이전트의 안정성을 지키는 7가지 실전 패턴을 다룹니다. 이론 아니에요. 실제 장애 포스트모템, 프로덕션 사고, 대규모 에이전트 운영에서 피로 얻은 교훈입니다.
패턴 1: 서킷 브레이커
기존 소프트웨어에선 다운스트림 서비스 터졌을 때 연쇄 장애 막으려고 서킷 브레이커를 쓰죠. AI 에이전트도 필요한데 포인트가 좀 달라요. HTTP 500만 막는 게 아니라, 모델이 쓰레기를 뱉기 시작하는 상황까지 막아야 합니다.
AI 에이전트에 서킷 브레이커가 필요한 이유
실패하는 도구를 호출하는 AI 에이전트는 크래시하지 않아요. 리트라이합니다. 또 리트라이합니다. "지능적"이니까 매번 살짝 다른 방식으로 시도하기도 하죠. 전부 실패하고, 전부 토큰을 소모합니다. 서킷 브레이커 없으면 깨진 도구 하나가 일일 API 예산 전체를 몇 분 만에 녹일 수 있어요.
구현
class AgentCircuitBreaker { private failures: Map<string, { count: number; lastFailure: number }> = new Map(); private readonly threshold = 5; // 오픈까지 실패 횟수 private readonly resetTimeout = 60000; // 1분 쿨다운 async callTool(toolName: string, fn: () => Promise<any>): Promise<any> { const state = this.failures.get(toolName) || { count: 0, lastFailure: 0 }; // 서킷 오픈 상태인지 확인 if (state.count >= this.threshold) { const elapsed = Date.now() - state.lastFailure; if (elapsed < this.resetTimeout) { throw new CircuitOpenError( `도구 "${toolName}" 일시 비활성화. ` + `${Math.ceil((this.resetTimeout - elapsed) / 1000)}초 후 재시도.` ); } // 반개방: 한 번 시도 허용 state.count = this.threshold - 1; } try { const result = await fn(); // 성공: 실패 카운트 리셋 this.failures.set(toolName, { count: 0, lastFailure: 0 }); return result; } catch (error) { state.count++; state.lastFailure = Date.now(); this.failures.set(toolName, state); throw error; } } }
핵심 포인트
서킷이 열렸을 때가 중요해요. 에러를 에이전트한테 컨텍스트로 돌려줘야 합니다. 예외만 던지고 끝내면 안 돼요. 모델한테 "이 도구 지금 못 써" 라고 알려주고 대안을 찾게 해야 해요:
if (error instanceof CircuitOpenError) { return { role: 'tool', content: `${toolName} 서비스가 일시적으로 사용 불가합니다 (서킷 브레이커 오픈). ` + `사용자에게 해당 기능이 일시적으로 중단되었다고 안내하거나, ` + `이 도구를 쓰지 않는 대안을 시도해주세요.` }; }
이렇게 하면 뻑 나는 대신 graceful하게 대응할 수 있어요. 에이전트가 사용자한테 사과하고, 우회 방법 제안하거나, 해당 단계를 건너뛰거나. 조용히 루프 돌면서 토큰 태우는 것보단 백 배 낫죠.
패턴 2: 리트라이-분류 (맹목적 리트라이 금지)
단순 리트라이, 즉 "실패하면 똑같은 걸 다시 해"는 AI 에이전트에서 오히려 독이 돼요. 모델이 잘못된 API 호출을 만들었으면, 같은 프롬프트로 다시 때려봤자 같은 잘못된 호출이 나옵니다. 같은 실패에 돈만 두 배 내는 꼴이에요.
리트라이-분류 패턴
맹목적 리트라이 대신, 에러를 먼저 분류하고 적절한 복구 전략으로 라우팅합니다:
class RetryClassifier: def classify(self, error: Exception, tool_name: str) -> RetryStrategy: if isinstance(error, RateLimitError): return RetryStrategy.BACKOFF # 대기 후 재시도 if isinstance(error, ValidationError): return RetryStrategy.REPAIR # LLM에게 에러 전달, 수정 요청 if isinstance(error, AuthenticationError): return RetryStrategy.FAIL_FAST # 리트라이 금지, 즉시 에스컬레이션 if isinstance(error, TimeoutError): return RetryStrategy.BACKOFF # 일시적 문제일 가능성 높음 if isinstance(error, ToolNotFoundError): return RetryStrategy.FALLBACK # 대체 도구 시도 return RetryStrategy.FAIL_FAST # 알 수 없는 에러: 리트라이 금지 async def execute_with_retry(agent, action, max_retries=3): classifier = RetryClassifier() for attempt in range(max_retries): try: return await agent.execute(action) except Exception as e: strategy = classifier.classify(e, action.tool_name) if strategy == RetryStrategy.FAIL_FAST: raise # 토큰 낭비 방지 if strategy == RetryStrategy.BACKOFF: wait = (2 ** attempt) + random.uniform(0, 1) # 지수 + 지터 await asyncio.sleep(wait) continue if strategy == RetryStrategy.REPAIR: # LLM에게 에러를 피드백해서 수정 요청 action = await agent.repair_action(action, error=str(e)) continue if strategy == RetryStrategy.FALLBACK: action = agent.get_fallback_action(action) continue raise MaxRetriesExceeded(f"{max_retries}회 시도 후 실패")
수리(REPAIR) 전략 상세
REPAIR 전략이 정말 흥미로운 부분입니다. 같은 프롬프트를 리트라이하는 대신, 에러 메시지를 추가 컨텍스트로 모델에 피드백하는 거예요:
async def repair_action(self, failed_action, error: str): repair_prompt = f"""이전 도구 호출이 다음 에러로 실패했습니다: 도구: {failed_action.tool_name} 입력: {json.dumps(failed_action.input)} 에러: {error} 에러를 분석하고 수정된 도구 호출을 생성하세요. 실패를 일으킨 것과 동일한 입력을 반복하지 마세요.""" corrected = await self.llm.generate(repair_prompt) return corrected
이 패턴은 첫 수리 시도에서 상당수의 밸리데이션 에러를 해결합니다. 날짜 형식 오류, 누락된 필수 필드, 범위 초과 값이 그런 것들이에요. 구체적인 에러 메시지를 보여주면 모델이 스스로 고칠 수 있는 구조적 에러들이죠. 실무에서 스키마 수준 오류의 수리 성공률은 50%를 크게 웃도는 수준으로 보고됩니다.
패턴 3: 예산 거버너
AI 에이전트에서 진짜 무서운 건 크래시가 아니에요. 비용 폭주입니다. 추론 루프에 빠진 에이전트가 아무도 모르는 사이에 수백 달러 API 비용을 펑펑 태울 수 있거든요. 예산 거버너는 이걸 강제로 막는 하드 리밋이에요.
3층 예산 관리
interface BudgetConfig { maxTokensPerRequest: number; // 단일 LLM 호출 제한 maxTokensPerSession: number; // 전체 대화 제한 maxToolCallsPerSession: number; // 무한 도구 루프 방지 maxCostPerSession: number; // 달러 상한선 maxDurationSeconds: number; // 벽시계 타임아웃 } class BudgetGovernor { private usage = { tokens: 0, toolCalls: 0, cost: 0, startTime: Date.now() }; check(config: BudgetConfig): void { if (this.usage.tokens > config.maxTokensPerSession) { throw new BudgetExceededError('토큰 예산 초과'); } if (this.usage.toolCalls > config.maxToolCallsPerSession) { throw new BudgetExceededError('도구 호출 제한 초과 — 무한 루프 의심'); } if (this.usage.cost > config.maxCostPerSession) { throw new BudgetExceededError(`비용 상한 도달: $${this.usage.cost.toFixed(2)}`); } const elapsed = (Date.now() - this.usage.startTime) / 1000; if (elapsed > config.maxDurationSeconds) { throw new BudgetExceededError(`세션 타임아웃: ${elapsed.toFixed(0)}초`); } } recordUsage(tokens: number, cost: number, isToolCall: boolean): void { this.usage.tokens += tokens; this.usage.cost += cost; if (isToolCall) this.usage.toolCalls++; } }
적절한 제한값 설정하기
너무 빡빡하면 정상 워크플로우가 깨지고, 너무 느슨하면 실질적 피해를 막지 못해요. 환경별 기준값:
| 예산 유형 | 개발 | 스테이징 | 프로덕션 |
|---|---|---|---|
| 세션당 토큰 | 50,000 | 30,000 | 20,000 |
| 세션당 도구 호출 | 50 | 25 | 15 |
| 세션당 비용 | $5.00 | $2.00 | $0.50 |
| 타임아웃 | 5분 | 3분 | 2분 |
프로덕션에서 빡빡하게 시작해서 실제 사용 데이터 보고 풀어주세요. 제한 올리는 건 쉽지만 $2,000 깜짝 청구서를 해명하는 건 어렵습니다.
"갇힘 감지" 패턴
예산 제한이 폭주를 잡지만, 반복 행동을 감시하면 더 일찍 잡을 수 있어요:
def detect_stuck_agent(tool_call_history: list[str], window: int = 5) -> bool: """에이전트가 진전 없이 같은 도구를 반복 호출하는지 감지""" if len(tool_call_history) < window: return False recent = tool_call_history[-window:] # 최근 호출의 80% 이상이 같은 도구면 갇힌 것으로 판단 most_common = max(set(recent), key=recent.count) return recent.count(most_common) / len(recent) >= 0.8
갇힘이 감지되면 메타 프롬프트를 주입합니다:
같은 행동을 반복하면서 진전이 없는 것 같습니다.
멈추고 접근 방식을 재고하세요.
완전히 다른 전략을 시도하거나,
이 작업을 완료할 수 없다고 사용자에게 알려주세요.
패턴 4: 출력 가드레일
모델은 결국 출력하면 안 되는 걸 출력합니다. 고객 대면 응답에 개인정보. 웹훅 페이로드에 SQL문. 피싱 사이트로 연결되는 환각 URL. 출력 가드레일은 에이전트의 출력이 사용자나 외부 시스템에 도달하기 전 마지막 방어선입니다.
가드레일 파이프라인
모든 에이전트 출력을 시스템 밖으로 내보내기 전에 검증 파이프라인을 통과시킵니다:
interface Guardrail { name: string; check(output: string, context: AgentContext): GuardrailResult; } class GuardrailPipeline { private guardrails: Guardrail[] = []; async validate(output: string, context: AgentContext): Promise<string> { for (const guardrail of this.guardrails) { const result = guardrail.check(output, context); if (result.action === 'BLOCK') { throw new GuardrailViolation(guardrail.name, result.reason); } if (result.action === 'REDACT') { output = result.redactedOutput; // 민감 정보 대체 } if (result.action === 'FLAG') { await this.alertOncall(guardrail.name, output, result.reason); // 계속 진행하되 팀에 알림 } } return output; } }
프로덕션 필수 가드레일
1. 개인정보(PII) 탐지
const piiGuardrail: Guardrail = { name: 'pii-detector', check(output: string): GuardrailResult { const patterns = { ssn: /\b\d{3}-\d{2}-\d{4}\b/, email: /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/, phone: /\b(\+\d{1,3}[-.]?)?\(?\d{3}\)?[-.]?\d{3}[-.]?\d{4}\b/, creditCard: /\b\d{4}[-\s]?\d{4}[-\s]?\d{4}[-\s]?\d{4}\b/, }; for (const [type, pattern] of Object.entries(patterns)) { if (pattern.test(output)) { return { action: 'REDACT', reason: `출력에서 ${type} 탐지됨`, redactedOutput: output.replace(pattern, `[REDACTED_${type.toUpperCase()}]`) }; } } return { action: 'PASS' }; } };
2. 코드 인젝션 방지
const codeInjectionGuardrail: Guardrail = { name: 'code-injection', check(output: string, context: AgentContext): GuardrailResult { const dangerousPatterns = [ /DROP\s+TABLE/i, /DELETE\s+FROM/i, /UPDATE\s+.*SET/i, /<script\b[^>]*>/i, /eval\s*\(/i, /exec\s*\(/i, /rm\s+-rf/i ]; if (context.responseType === 'user-facing') { for (const pattern of dangerousPatterns) { if (pattern.test(output)) { return { action: 'BLOCK', reason: `위험 패턴 탐지: ${pattern}` }; } } } return { action: 'PASS' }; } };
3. 환각 앵커
const groundednessGuardrail: Guardrail = { name: 'groundedness', check(output: string, context: AgentContext): GuardrailResult { const urls = output.match(/https?:\/\/[^\s)]+/g) || []; const sourceUrls = context.retrievedDocuments.flatMap(d => d.content.match(/https?:\/\/[^\s)]+/g) || [] ); const fabricatedUrls = urls.filter(url => !sourceUrls.includes(url)); if (fabricatedUrls.length > 0) { return { action: 'FLAG', reason: `날조 의심 URL: ${fabricatedUrls.join(', ')}` }; } return { action: 'PASS' }; } };
패턴 5: 킬 스위치
모든 프로덕션 AI 에이전트에는 비상 정지 메커니즘이 필요합니다. "몇 분에 걸쳐 점진적으로 종료"가 아니에요. 모든 인스턴스의 모든 에이전트 활동을 즉시 정지시키는 하드 스톱.
왜 필요한가
킬 스위치는 일반적 에러 핸들링용이 아닙니다. 이런 시나리오를 위한 겁니다:
- 에이전트가 고객에게 부적절한 콘텐츠를 보내기 시작
- 프롬프트 인젝션 공격이 실시간으로 악용되는 중
- 에이전트가 프로덕션 데이터를 무단 변경
- 예산 거버너가 못 잡는 비용 폭주 (설정 오류)
구현: 피처 플래그 + 리모트 컨피그
가장 간단하고 신뢰성 있는 킬 스위치는 피처 플래그입니다:
class AgentKillSwitch { async checkBeforeAction(agentId: string): Promise<void> { const config = await this.getRemoteConfig(); if (config.globalKillSwitch) { throw new AgentHaltedError('글로벌 킬 스위치로 전체 에이전트 중단됨'); } if (config.disabledAgents.includes(agentId)) { throw new AgentHaltedError(`에이전트 ${agentId} 대상 킬 스위치 작동`); } if (await this.abuseDetector.isCompromised(agentId)) { await this.activateKillSwitch(agentId, '자동: 악용 감지'); throw new AgentHaltedError('에이전트 중단: 악용 패턴 감지'); } } async activateKillSwitch(agentId: string, reason: string): Promise<void> { await this.remoteConfig.set(`agents.${agentId}.killed`, true); await this.alerting.sendPagerDutyAlert({ severity: 'critical', summary: `에이전트 ${agentId} 킬 스위치 작동: ${reason}`, }); await this.auditLog.record('KILL_SWITCH_ACTIVATED', { agentId, reason }); } }
절대 규칙
킬 스위치 체크는 모든 LLM 호출과 모든 도구 실행 전에 일어나야 합니다. 세션 시작 시에만이 아니에요. 킬 스위치 작동 전에 시작된 에이전트 세션도 실행 도중에 멈춰야 합니다.
while (hasMoreSteps) { await killSwitch.checkBeforeAction(this.agentId); // ← 매 반복마다 const response = await llm.chat(messages); await killSwitch.checkBeforeAction(this.agentId); // ← LLM 후, 도구 전 if (response.toolCalls) { for (const call of response.toolCalls) { await killSwitch.checkBeforeAction(this.agentId); // ← 각 도구 전 await executeTool(call); } } }
패턴 6: 옵저버빌리티와 트레이싱
안 보이면 못 고쳐요. AI 에이전트는 진짜 블랙박스거든요. 같은 입력 넣어도 다른 추론 체인 타고, 다른 도구 호출 순서 밟고, 다른 출력이 나올 수 있어요. 기존 모니터링(응답 시간, 에러율)으로는 에이전트가 왜 터졌는지 거의 파악이 안 됩니다.
트레이싱 대상
모든 에이전트 실행은 구조화된 트레이스를 남겨야 합니다:
interface AgentTrace { traceId: string; sessionId: string; timestamp: string; steps: AgentStep[]; metrics: { totalTokens: number; totalCost: number; totalDuration: number; toolCallCount: number; retryCount: number; guardrailTriggered: boolean; }; outcome: 'success' | 'failure' | 'timeout' | 'killed' | 'budget_exceeded'; error?: string; } interface AgentStep { stepIndex: number; type: 'llm_call' | 'tool_call' | 'guardrail_check'; inputTokens?: number; outputTokens?: number; model?: string; toolName?: string; toolInput?: Record<string, any>; toolOutput?: string; toolDuration?: number; guardrailName?: string; guardrailAction?: 'PASS' | 'BLOCK' | 'REDACT' | 'FLAG'; duration: number; error?: string; }
필요한 세 가지 대시보드
1. 실시간 운영 대시보드
| 지표 | 알 수 있는 것 |
|---|---|
| 활성 세션 | 현재 실행 중인 에이전트 수 |
| 에러율 (5분 윈도우) | 방금 뭔가 깨졌는지 |
| P95 레이턴시 | 사용자 경험 저하 여부 |
| 분당 비용 | 예산 소진 속도 |
| 서킷 브레이커 상태 | 어떤 도구가 장애인지 |
2. 품질 대시보드 (일간)
| 지표 | 알 수 있는 것 |
|---|---|
| 작업 완료율 | 에이전트가 실제로 문제를 해결하는지 |
| 가드레일 트리거 비율 | 모델이 얼마나 자주 문제를 일으키는지 |
| 도구별 리트라이율 | 어느 통합이 불안정한지 |
| 작업당 평균 스텝 수 | 프롬프트 최적화 필요 여부 |
| 사용자 만족도 | 궁극적으로 유일하게 중요한 지표 |
3. 장애 조사 뷰
문제가 생겼을 때 정확한 시퀀스를 재생할 수 있어야 합니다: 모든 메시지, 모든 LLM 응답, 모든 도구 호출 입출력, 모든 가드레일 체크. 트레이스는 최소 30일 보관하세요. 장애 발생 시 이 트레이스가 디지털 포렌식 증거입니다.
실전 팁: 응답만 말고 프롬프트도 로깅하세요
대부분의 팀이 LLM 응답은 찍어두는데 보낸 프롬프트는 안 남겨요. 이러면 디버깅 지옥이에요. LLM 호출할 때마다 전체 프롬프트(시스템 메시지 + 대화 히스토리 + 도구 정의)를 통째로 남기세요. 장황하죠. 스토리지 비용도 들고요. 근데 장애 터졌을 때 디버깅 시간 몇 시간은 아껴줍니다.
패턴 7: 사람 개입(HITL) 승인 게이트
완전 자율은 목표지, 출발점이 아닙니다. 가장 안정적인 프로덕션 에이전트는 계층적 권한을 씁니다. 저위험 작업은 자율적으로 하지만 고위험 작업은 사람의 승인이 필요한 구조.
위험 등급 정의
enum RiskTier { LOW = 'low', // 자율: 데이터 읽기, 검색, 텍스트 생성 MEDIUM = 'medium', // 통보: 이메일 발송, 레코드 수정, 설정 변경 HIGH = 'high', // 승인: 데이터 삭제, 금융 거래, 외부 API 쓰기 CRITICAL = 'critical', // 다중 승인: 스키마 변경, 접근 제어, 대량 작업 } const toolRiskMap: Record<string, RiskTier> = { 'search_documents': RiskTier.LOW, 'generate_summary': RiskTier.LOW, 'send_email': RiskTier.MEDIUM, 'update_customer_record': RiskTier.MEDIUM, 'delete_records': RiskTier.HIGH, 'execute_sql': RiskTier.HIGH, 'modify_billing': RiskTier.CRITICAL, 'update_permissions': RiskTier.CRITICAL, };
승인 플로우
async function executeWithApproval( agent: Agent, toolCall: ToolCall, context: AgentContext ): Promise<ToolResult> { const risk = toolRiskMap[toolCall.name] || RiskTier.HIGH; switch (risk) { case RiskTier.LOW: return await executeTool(toolCall); case RiskTier.MEDIUM: const result = await executeTool(toolCall); await notifyTeam(toolCall, result, context); return result; case RiskTier.HIGH: const approval = await requestApproval({ toolCall, context, timeout: 300_000, // 5분 타임아웃 }); if (approval.approved) { return await executeTool(toolCall); } else { return { role: 'tool', content: `리뷰어가 작업을 거부했습니다: ${approval.reason}. ` + `사용자에게 알리고 대안을 제안해주세요.` }; } case RiskTier.CRITICAL: const approvals = await requestMultiApproval({ toolCall, context, requiredApprovals: 2, timeout: 600_000, // 10분 타임아웃 }); if (approvals.every(a => a.approved)) { return await executeTool(toolCall); } else { return { role: 'tool', content: '추가 승인이 필요합니다.' }; } } }
현실적인 이야기
사람 개입은 레이턴시를 만듭니다. 시니어 엔지니어가 승인 요청을 검토하는 데 2-5분 걸려요. 그동안 에이전트는 멈추고, 사용자는 대기하고, 리소스는 열린 채로 유지됩니다.
이걸 완화하는 방법:
- 자주 나오는 패턴 사전 승인. 비슷한 파라미터의 같은 도구 호출이 20번 승인되면, 이후 자동 승인
- 승인 묶기. 관련 고위험 작업들을 하나의 리뷰로 그룹화 ("에이전트가 고객 레코드 3건 수정, 이메일 2건 발송 원함 — 전부 승인?")
- 비동기 워크플로우. 급하지 않은 작업은 큐에 넣고 승인·완료 시 사용자에게 알림
- 점진적 신뢰. 처음엔 전부 HITL로 시작하고, 에이전트 신뢰도가 올라가면 특정 도구의 위험 등급을 체계적으로 내림
종합: 안정성 스택 구성
이 7가지 패턴은 방어 계층을 이룹니다. 단일 패턴만으로는 부족해요. 안정성은 조합에서 나옵니다:
┌─────────────────────────────────────────┐
│ 사람 개입 (HITL) │ ← 고위험 작업 게이트
├─────────────────────────────────────────┤
│ 출력 가드레일 │ ← PII, 인젝션, 환각
├─────────────────────────────────────────┤
│ 예산 거버너 │ ← 비용, 토큰, 시간, 도구 호출
├─────────────────────────────────────────┤
│ 킬 스위치 │ ← 비상 정지
├─────────────────────────────────────────┤
│ 서킷 브레이커 │ ← 도구 장애 격리
├─────────────────────────────────────────┤
│ 리트라이-분류 │ ← 지능형 에러 복구
├─────────────────────────────────────────┤
│ 옵저버빌리티 │ ← 모든 결정의 전체 트레이스
└─────────────────────────────────────────┘
구현 순서
7개를 한꺼번에 출시하려 하지 마세요. 리스크 대비 노력 비율 기준으로 이 순서로:
- 예산 거버너 (Day 1) — 재정적 피해를 즉시 방지
- 킬 스위치 (Day 1) — 안 쓰더라도 비상 브레이크는 있어야
- 옵저버빌리티 (Week 1) — 측정 못 하면 개선 못 함
- 출력 가드레일 (Week 1-2) — 나쁜 콘텐츠가 사용자에게 도달하는 걸 차단
- 서킷 브레이커 (Week 2) — 도구 장애 격리
- 리트라이-분류 (Week 2-3) — 성공률 향상
- 사람 개입 (Week 3-4) — 고위험 작업에 신뢰 추가
2026년의 현실
AI 에이전트 생태계가 빠르게 성숙하고 있어요. LangGraph, CrewAI, OpenAI/Google의 Agents SDK 같은 프레임워크들이 빌트인 안정성 기능을 계속 늘려가고 있죠. 근데 그것만으론 부족합니다. 프레임워크 기본값은 관대해요. 데모 쉽게 만들도록 설계된 거지, 프로덕션을 안전하게 지키려고 만든 게 아니거든요.
에이전트는 결국 예상 못 한 짓을 합니다. "만약"이 아니라 "언제"의 문제고, 그때 안정성 스택이 사용자, DB, 청구 시스템에 닿기 전에 잡아주느냐가 관건이에요.
최고의 AI 에이전트는 가장 똑똑한 게 아닙니다. 가장 깔끔하게 실패하는 놈이에요.
관련 도구 둘러보기
Pockit의 무료 개발자 도구를 사용해 보세요