🔥 고급2026-07-186~8분
멀티 에이전트 환경에서 안전한 Tool 실행: 격리 레이어와 재시도 예산 설계
여러 에이전트가 동일 tool을 병렬 호출할 때 발생하는 경쟁 조건과 무한 재시도를 막기 위한 실전 아키텍처와 코드 패턴을 정리한다.
multi-agenttool-executionreliability
멀티 에이전트 Tool 실행의 실제 위험
단일 에이전트는 tool 결과를 순차적으로 처리하지만, 오케스트레이터-서브에이전트 구조에서는 동일 tool이 동시에 N회 호출된다. 대표적 장애 시나리오:
- 파일 쓰기 경쟁: 두 서브에이전트가 동시에
write_file(path)호출 → 마지막 쓰기가 이전 결과 덮어쓰기 - 외부 API 중복 실행: 결제 API가 2회 호출되어 이중 청구
- 무한 재시도 루프: 에이전트가 tool 실패를 받아 자체 판단으로 재시도 → 오케스트레이터도 재시도 → 지수적 폭발
실측 사례: 10개 서브에이전트 구조에서 재시도 예산 미설정 시 단일 작업에 API 호출 340회 발생, 비용 $12 초과.
격리 레이어와 재시도 예산 구현
핵심 설계 원칙: tool 실행 레이어를 에이전트 외부로 분리하고, 중앙 레지스트리에서 호출 횟수·락·예산을 관리한다.
import anthropic
import asyncio
from collections import defaultdict
from dataclasses import dataclass, field
@dataclass
class ToolBudget:
max_calls: int = 5
calls_made: int = 0
lock: asyncio.Lock = field(default_factory=asyncio.Lock)
class SafeToolExecutor:
def __init__(self):
self.budgets: dict[str, ToolBudget] = defaultdict(ToolBudget)
self.client = anthropic.Anthropic()
async def execute(self, tool_name: str, tool_input: dict) -> dict:
budget = self.budgets[tool_name]
async with budget.lock:
if budget.calls_made >= budget.max_calls:
return {"error": f"budget_exceeded: {tool_name} called {budget.calls_made} times"}
budget.calls_made += 1
call_id = budget.calls_made
try:
# 실제 tool 로직 (예: 파일 쓰기, API 호출)
result = await self._dispatch(tool_name, tool_input)
return {"result": result, "call_id": call_id}
except Exception as e:
# 예외 시 카운트 롤백하지 않음 → 실패도 예산 소모
return {"error": str(e), "call_id": call_id}
async def _dispatch(self, name: str, inp: dict) -> str:
# 실제 구현체 연결
if name == "write_file":
return f"written: {inp.get('path')}"
return "ok"
executor = SafeToolExecutor()
executor.budgets["write_file"] = ToolBudget(max_calls=3)
executor.budgets["call_payment_api"] = ToolBudget(max_calls=1)
트레이드오프:
max_calls=1(멱등하지 않은 API): 안전하지만 에이전트가 오류로 판단해 다른 경로 탐색 가능성max_calls=3~5(읽기/분석 tool): 유연성과 안전성 균형- 락 경합 오버헤드: 10개 에이전트 기준 ~2ms 추가 지연, 무시 가능
실패 모드와 운영 체크리스트
실패 모드 3가지:
- 에이전트가 budget_exceeded를 재시도 신호로 해석 → tool result에
"retry": false명시적 포함 필수 - 오케스트레이터와 서브에이전트 각각 재시도 로직 보유 → 지수 백오프 레이어를 오케스트레이터 단 하나에만 구현
- tool 예산 초기화 타이밍 → 작업(task) 단위로 리셋, 요청(request) 단위 아님
운영 체크리스트:
- [ ] 모든 외부 부작용 tool에
max_calls명시적 설정 - [ ] tool 호출 로그에
agent_id+call_id포함 (디버깅 필수) - [ ] 멱등하지 않은 API는
max_calls=1+ 고유 idempotency key 주입 - [ ] 재시도 로직은 오케스트레이터 레이어 1곳에만 존재하도록 아키텍처 문서화
- [ ] 작업 완료 후
calls_made지표를 모니터링 대시보드에 적재