PF-06 · AI · FastAPI
AetherCrew: AI 직원 11명의 가상 회사
직접 만든 오케스트레이터로 AI 직원 11명의 업무 배정, 모델 선택, 비용 추적을 구현한 가상 회사입니다. 에이전트 작업은 시뮬레이션이며 언어 모델 API는 호출하지 않습니다.
- 역할
- 설계 · 구현
- 기간
- 개인 프로젝트 · 2026.11 공개
- CSP
- 해당 없음
- 규모
- AI 직원 11명 · 3개 부서 · 대시보드 8페이지
- 핵심 기술
- Python · FastAPI · WebSocket · pytest · Slack · Jira · GitHub
- 11
- AI 직원
- 3
- 부서
- 8
- 웹 대시보드 페이지
한눈에 보기
문제
여러 AI 직원이 역할별로 일을 나눠 맡으면 누가 어떤 모델을 쓰는지, 비용이 어디까지 갔는지, 외부 시스템에 무엇을 쓸 수 있는지 코드로 통제해야 합니다.
접근
CEO 지시를 비서(경영기획실장)가 받아 계획을 쓰고 역할별 Task로 나눕니다. 직원별로 Anthropic, OpenAI, Google, DeepSeek 모델의 우선순위를 두고 실패하면 다음 모델로 전환합니다. 예상 비용을 쌓아 80%와 95%에서 경고하고 100%에서 작업을 막습니다. 외부 쓰기는 기본 차단, 파일 접근은 프로젝트 루트로 제한합니다.
결과
FastAPI 웹 대시보드 8페이지에서 조직도, 비용, 로그, 픽셀 오피스 CCTV를 봅니다. API 키 없이 실행되고 언어 모델은 호출하지 않으므로 실제 작업 결과물은 나오지 않습니다. Slack, Jira, GitHub 클라이언트는 실제 코드이며 INTEGRATIONS_LIVE_WRITE를 켤 때만 동작합니다.
자세히 보기
모델 폴백
이 코드가 하는 일: 직원의 모델 우선순위 목록과 사용할 수 없는 모델 목록을 받아, 앞에서부터 차례로 확인해 사용 가능한 첫 모델을 고릅니다. 건너뛴 모델은 fallbacks에 기록해 몇 번째 모델로 넘어갔는지 남기고, 목록이 비어 있거나 모든 모델을 쓸 수 없으면 조용히 넘어가지 않고 오류를 냅니다. 목록 길이만큼만 확인하므로 폴백이 끝없이 이어지지 않습니다.
class FallbackManager:
"""Selects next available agent with bounded fallback traversal."""
def select(
self,
priority_list: list[str],
unavailable_agents: set[str] | None = None,
) -> FallbackResult:
if not priority_list:
raise ValueError("priority_list must not be empty")
blocked = unavailable_agents or set()
fallbacks: list[str] = []
for idx, model in enumerate(priority_list):
if model in blocked:
fallbacks.append(model)
continue
return FallbackResult(selected_agent=model, selected_index=idx, fallbacks=fallbacks)
raise RuntimeError("All agents are unavailable; fallback exhausted")
예산 상태
이 코드가 하는 일: 기록된 비용을 모두 더해 예산 대비 사용률을 계산하고, 그 값에 따라 OK, WARN_80, WARN_95, HARD_STOP 네 상태 중 하나를 돌려줍니다. 예산이 0 이하로 설정되면 사용률을 100%로 보아 바로 HARD_STOP이 되므로, 예산 설정 실수가 무제한 실행으로 이어지지 않습니다. 오케스트레이터는 이 상태가 HARD_STOP이면 새 작업을 막습니다.
@property
def total_cost(self) -> float:
return round(sum(e.cost_usd for e in self.entries), 6)
def percent_used(self) -> float:
if self.budget_policy.total_budget <= 0:
return 100.0
return (self.total_cost / self.budget_policy.total_budget) * 100
def budget_state(self) -> str:
pct = self.percent_used()
if pct >= self.budget_policy.hard_stop_at_pct:
return "HARD_STOP"
if pct >= self.budget_policy.warn_at_pct[1]:
return "WARN_95"
if pct >= self.budget_policy.warn_at_pct[0]:
return "WARN_80"
return "OK"배운 점
실제로 에이전트끼리 일을 넘기게 해 보니, 한 에이전트의 잘못된 결과를 다음 에이전트가 검증 없이 그대로 믿었습니다. 그때 신뢰 경계와 권한 범위는 프롬프트의 당부가 아니라 코드 안에 있어야 한다는 것을 체감했습니다. 그래서 이 프로젝트에서는 모델 선택, 예산 차단, 외부 쓰기, 파일 접근 범위처럼 위험이 생길 수 있는 지점을 모두 코드의 규칙으로 두었습니다. 외부 쓰기는 기본으로 막고 설정을 켤 때만 허용하며, 예산은 80%와 95%에서 경고하고 100%에서 멈춥니다. AI가 많은 일을 맡을수록 사람이 정해야 하는 것은 무엇을 시킬지보다 어디까지 허용할지라는 점을 배웠습니다.