## Summary - 2D 픽셀아트 가상 오피스에서 AI 에이전트(Stock, Music)가 실제 작업 수행 - FastAPI + WebSocket 실시간 상태 동기화 + 텔레그램 봇 양방향 알림/승인 - BaseAgent FSM (idle/working/waiting/reporting/break), 서비스 프록시 패턴 - Docker Compose 서비스 (port 18900) + Nginx WebSocket 프록시 ## Changes (13 commits) - Backend scaffold: config, db, models, Dockerfile - WebSocket manager + Service proxy - BaseAgent FSM + StockAgent + MusicAgent - Telegram bot + Scheduler - FastAPI main (REST + WS endpoints) - Infrastructure: docker-compose + nginx - Code review fixes: HTTPException, async polling, input validation Reviewed-on: #2
19 KiB
19 KiB
Agent Office - AI 에이전트 사무실 시각화 설계
개요
Lab 하위에 2D 픽셀아트 스타일의 가상 사무실을 구현하여, AI 에이전트들이 실시간으로 작업하는 모습을 게임처럼 시각화하고 상호작용하는 페이지.
핵심 컨셉
- 게임 같은 사무실: 2D 픽셀아트 오픈 오피스에 에이전트 캐릭터들이 배치
- 실제 작업 수행: 에이전트들이 기존 백엔드 서비스 API를 호출하여 실제 결과물 생성
- 직접 지시: 에이전트 클릭 → 채팅/명령 패널로 지시, 승인 요청 시 알림 표시
- 텔레그램 양방향: 알림 발송 + 인라인 버튼으로 승인/거절/수정
- 아이들 행동: 장시간 명령 없으면 휴게실에서 커피, 졸기, 동료 잡담 등
MVP 범위
- 에이전트 2개: StockAgent (주식 뉴스/주가 알람), MusicAgent (작곡 파이프라인)
- 사무실: 단일 오픈 오피스 (향후 방/층 확장 가능)
- 텔레그램: 양방향 (알림 + 인라인 버튼 승인)
1. 아키텍처
┌─────────────────────────────────────────────────┐
│ Frontend (React) │
│ │
│ ┌──────────────┐ ┌─────────────────────────┐ │
│ │ OfficeCanvas │ │ React Overlay │ │
│ │ (Canvas 2D) │ │ - ChatPanel │ │
│ │ - 타일맵 렌더 │ │ - AgentStatus │ │
│ │ - 스프라이트 │ │ - TaskHistory │ │
│ │ - 클릭 히트맵 │ │ - ApprovalDialog │ │
│ └──────────────┘ └─────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────┐ │
│ │ useAgentManager (상태 + WebSocket) │ │
│ └──────────────────────────────────────────┘ │
└──────────────────┬──────────────────────────────┘
│ WebSocket + REST
┌──────────────────▼──────────────────────────────┐
│ Backend: agent-office (새 서비스, 포트 18900) │
│ │
│ ┌────────────┐ ┌────────────┐ ┌──────────────┐ │
│ │ Scheduler │ │ Agent FSM │ │ Telegram Bot │ │
│ │(APScheduler)│ │ (상태머신) │ │ (양방향) │ │
│ └────────────┘ └────────────┘ └──────────────┘ │
│ │
│ ┌──────────────────────────────────────────┐ │
│ │ Service Proxy (기존 서비스 API 호출) │ │
│ │ stock-lab / music-lab 등 │ │
│ └──────────────────────────────────────────┘ │
└─────────────────────────────────────────────────┘
핵심 결정
- agent-office: 새 백엔드 서비스 (포트 18900). 기존 서비스는 수정하지 않음
- Service Proxy 패턴: agent-office가 기존 서비스 API를 HTTP 호출
- WebSocket: 에이전트 상태 변화를 실시간 전달
- Canvas + React 오버레이 하이브리드: 게임 렌더링은 Canvas, UI 패널은 React DOM
2. 에이전트 상태 머신 (FSM)
상태 전이
┌──────┐ 스케줄/지시 ┌──────────┐ 완료 ┌──────────┐
│ idle │ ──────────────→ │ working │ ───────→ │ reporting│
└──┬───┘ └────┬─────┘ └────┬─────┘
│ │ 승인 필요 │
│ 장시간 idle ▼ │ 결과 전달 후
│ ┌───────────┐ │
▼ │ waiting │ │
┌──────┐ │ (승인대기) │ │
│ break│ └───────────┘ │
│ (휴식)│ │
└──┬───┘◄───────────────────────────────────────────┘
│ 새 작업 발생
└──────────→ idle
상태별 시각화
| 상태 | 캐릭터 행동 | 위치 | 오버레이 |
|---|---|---|---|
idle |
모니터 보며 대기 애니메이션 | 자기 데스크 | 없음 |
working |
타이핑 애니메이션, 모니터에 진행 표시 | 자기 데스크 | 작업명 말풍선 |
waiting |
살짝 좌우 흔들림 | 자기 데스크 | ❗ 아이콘 (클릭 유도) |
reporting |
결과물 들고 걸어감 | 데스크 → 회의 테이블 | 결과 요약 말풍선 |
break |
커피 마시기/졸기/산책/잡담 | 휴게실/복도 | ☕/💤 아이콘 |
아이들 행동 규칙
- idle 상태 5분 경과 → 50% 확률로 break 전환
- break 지속: 1~3분 랜덤 → idle 복귀
- break 중 에이전트끼리 근처에 있으면 잡담 애니메이션
- 새 작업 발생 시 즉시 break 종료 → idle → working
승인 흐름별 분류
| 에이전트 | 자동 실행 | 승인 필요 |
|---|---|---|
| Stock | 뉴스 요약, 주가 알람 | - |
| Music | - | 작곡 (프롬프트 확인 후) |
| Lotto (향후) | 통계 분석, 추천번호 | 구매 관련 |
| Blog (향후) | - | 키워드 제시 후 글 생성 |
| Realestate (향후) | 공고 수집, 매칭 | - |
| Claude AI (향후) | - | 직접 지시 + 승인 |
3. 사무실 맵 & 렌더링
타일맵 구조 (MVP: 단일 오픈 오피스)
┌─────────────────────────────────────────┐
│ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ │
│ │Stock│ │Music│ │Claude│ │ (빈) │ │
│ │Desk │ │Desk │ │Desk │ │향후용│ │
│ └─────┘ └─────┘ └─────┘ └─────┘ │
│ │
│ ┌───────────┐ │
│ │ 회의 테이블 │ │
│ │ (보고구역) │ │
│ └───────────┘ │
│ │
│ ┌──────────┐ ┌─────────────────┐ │
│ │ 휴게실 │ │ CEO 데스크 (나) │ │
│ │ coffee │ │ │ │
│ └──────────┘ └─────────────────┘ │
└─────────────────────────────────────────┘
렌더링 계층 (아래→위)
- 바닥 타일: 카펫, 나무 바닥
- 가구: 데스크, 의자, 소파, 화분, 커피머신
- 캐릭터: 에이전트 스프라이트 (상태별 애니메이션)
- 오버레이: 말풍선, 상태 아이콘, 이름표
스프라이트 에셋
- 무료 픽셀아트 에셋팩 활용 (타일셋, 가구)
- 에이전트 캐릭터: 기본 인물 스프라이트 + 액세서리로 구분
- Stock: 넥타이 + 차트 모니터
- Music: 헤드폰 + 음표 이펙트
- Claude: 보라색 톤 + AI 아이콘
- 스프라이트시트: 4방향 × 4프레임 (idle, walk, work, break)
Canvas 렌더링 엔진
- 게임 루프:
requestAnimationFrame기반, 60fps 타겟 - 카메라: 고정 뷰 (MVP), 향후 줌/팬 추가 가능
- 클릭 히트맵: 캐릭터 바운딩 박스 체크 → 클릭 시 React 이벤트 발생
- 이동: 웨이포인트 기반 lerp (데스크↔회의실↔휴게실)
4. 백엔드: agent-office 서비스
파일 구조
agent-office/
├── app/
│ ├── main.py # FastAPI + WebSocket + lifespan
│ ├── db.py # SQLite (agent_tasks, agent_logs, agent_config)
│ ├── config.py # 환경변수, 서비스 URL 설정
│ ├── scheduler.py # APScheduler 스케줄 관리
│ ├── telegram_bot.py # Telegram Bot API 양방향
│ ├── websocket_manager.py # WebSocket 연결 관리 + 브로드캐스트
│ ├── service_proxy.py # 기존 서비스 API 호출 래퍼
│ ├── agents/
│ │ ├── base.py # BaseAgent (FSM, 공통 로직)
│ │ ├── stock.py # StockAgent
│ │ └── music.py # MusicAgent
│ └── models.py # Pydantic 모델
├── Dockerfile
└── requirements.txt
DB 테이블 (agent_office.db)
agent_config
| 컬럼 | 타입 | 설명 |
|---|---|---|
| agent_id | TEXT PK | 에이전트 식별자 (stock, music, ...) |
| display_name | TEXT | 표시명 ("주식 트레이더") |
| enabled | BOOLEAN | 활성 상태 |
| schedule_config | TEXT (JSON) | 스케줄 설정 |
| custom_config | TEXT (JSON) | 에이전트별 커스텀 설정 (감시 종목 등) |
| created_at | TEXT | 생성 시각 |
| updated_at | TEXT | 수정 시각 |
agent_tasks
| 컬럼 | 타입 | 설명 |
|---|---|---|
| id | TEXT PK (UUID) | 작업 ID |
| agent_id | TEXT FK | 에이전트 |
| task_type | TEXT | 작업 유형 (news_summary, price_alert, compose, ...) |
| status | TEXT | pending / approved / working / succeeded / failed |
| input_data | TEXT (JSON) | 입력 파라미터 |
| result_data | TEXT (JSON) | 결과 데이터 |
| requires_approval | BOOLEAN | 승인 필요 여부 |
| approved_at | TEXT | 승인 시각 |
| approved_via | TEXT | 승인 경로 (web / telegram) |
| created_at | TEXT | 생성 시각 |
| completed_at | TEXT | 완료 시각 |
agent_logs
| 컬럼 | 타입 | 설명 |
|---|---|---|
| id | INTEGER PK | 자동 증가 |
| agent_id | TEXT FK | 에이전트 |
| task_id | TEXT FK | 관련 작업 (nullable) |
| level | TEXT | info / warn / error |
| message | TEXT | 로그 메시지 |
| created_at | TEXT | 시각 |
telegram_state
| 컬럼 | 타입 | 설명 |
|---|---|---|
| callback_id | TEXT PK | 텔레그램 콜백 ID |
| task_id | TEXT FK | 매핑된 작업 |
| agent_id | TEXT FK | 매핑된 에이전트 |
| action | TEXT | approve / reject / modify |
| responded | BOOLEAN | 응답 완료 여부 |
| created_at | TEXT | 생성 시각 |
BaseAgent 인터페이스
class BaseAgent:
agent_id: str
state: str # idle, working, waiting, reporting, break
async def on_schedule(self) -> None:
"""스케줄러에 의해 호출. 자동 작업 실행."""
async def on_command(self, command: str, params: dict) -> dict:
"""사용자 직접 지시 처리."""
async def on_approval(self, task_id: str, approved: bool, feedback: str) -> None:
"""승인/거절 콜백."""
async def get_status(self) -> dict:
"""현재 상태 + 최근 작업 요약."""
MVP 에이전트 상세
StockAgent:
- 스케줄: 매일 08:00
on_schedule()→stock-lab GET /api/stock/news호출 - AI 요약: 뉴스 데이터를 Ollama(192.168.45.59)로 요약 생성
- 텔레그램 전송: 요약 결과를 포맷팅하여 발송 (자동, 승인 불필요)
- 주가 알람:
agent_config.custom_config에 감시 종목/조건 저장, 주기적 체크 - 상태 전이: idle → working(뉴스 수집) → reporting(텔레그램 전송) → idle
MusicAgent:
- 트리거: 사용자 웹/텔레그램 지시 →
on_command() - 프롬프트 확인: 사용자 입력 프롬프트를 텔레그램으로 전송 + 인라인 버튼
- 승인 시:
music-lab POST /api/music/generate호출 - 상태 폴링:
music-lab GET /api/music/status/{task_id}→ 완료까지 반복 - 결과 알림: 생성된 음악 URL을 텔레그램 + 웹에 전달
- 상태 전이: idle → waiting(프롬프트 승인 대기) → working(생성 중) → reporting(결과 전달) → idle
5. 텔레그램 봇
구성
- Telegram Bot API + Webhook 수신 (NAS에서)
- agent-office 서비스 내부에 통합 (별도 프로세스 아님)
- Nginx:
/api/agent-office/telegram/webhook→agent-office:8000
환경변수
TELEGRAM_BOT_TOKEN: Bot Father에서 발급TELEGRAM_CHAT_ID: 사용자 채팅 ID (1:1 봇)TELEGRAM_WEBHOOK_URL: Webhook 수신 URL (NAS 외부 접근 가능 URL)
메시지 포맷
자동 알림 (뉴스 요약):
📈 [주식 에이전트] 아침 뉴스 요약
━━━━━━━━━━━━━━━━
• 삼성전자: 반도체 수출 호조...
• 코스피: 외인 순매수 전환...
• 미국 CPI 발표 예정...
📊 관심종목 현황
삼성전자 82,500원 (+2.1%)
AAPL $185.20 (+1.2%)
승인 요청 (작곡):
🎵 [음악 에이전트] 작곡 요청
━━━━━━━━━━━━━━━━
프롬프트: "Lo-fi hip hop, rainy day, piano"
스타일: Chill, Ambient
모델: V5.5
[✅ 승인] [❌ 거절] [✏️ 수정]
주가 알람:
🚨 [주식 에이전트] 주가 알림
━━━━━━━━━━━━━━━━
삼성전자 82,500원
조건: 82,000원 이상 → 도달!
현재 등락: +2.1%
양방향 흐름
- 에이전트 →
telegram_bot.send_message()→ 텔레그램 - 사용자 → 인라인 버튼 클릭 or 텍스트 입력
- 텔레그램 → Webhook POST →
telegram_bot.handle_webhook() handle_webhook()→telegram_state조회 → 에이전트on_approval()호출- 에이전트 FSM 상태 전이 → WebSocket 브로드캐스트 → 프론트엔드 반영
6. 프론트엔드 구조
파일 구조
src/pages/agent-office/
├── AgentOffice.jsx # 메인 페이지 (Canvas + Overlay 컨테이너)
├── AgentOffice.css # 스타일
├── canvas/
│ ├── OfficeRenderer.js # Canvas 렌더링 엔진 (게임루프)
│ ├── SpriteSheet.js # 스프라이트시트 로더 + 프레임 애니메이션
│ ├── TileMap.js # 타일맵 데이터 + 렌더링
│ └── AgentSprite.js # 에이전트 캐릭터 (위치, 상태, 이동, 애니메이션)
├── components/
│ ├── ChatPanel.jsx # 에이전트 채팅/명령 패널
│ ├── AgentBubble.jsx # 말풍선/상태 아이콘 오버레이
│ ├── TaskHistory.jsx # 작업 이력 사이드패널
│ └── ApprovalDialog.jsx # 승인 요청 다이얼로그
├── hooks/
│ ├── useAgentManager.js # WebSocket + 에이전트 상태 관리
│ └── useOfficeCanvas.js # Canvas 초기화 + 이벤트 바인딩
└── assets/
├── tileset.png # 사무실 타일셋 (16x16 or 32x32)
├── agents.png # 에이전트 스프라이트시트
└── office-map.json # 타일맵 데이터
WebSocket 프로토콜
서버 → 클라이언트:
{"type": "agent_state", "agent": "stock", "state": "working", "detail": "뉴스 수집 중..."}
{"type": "agent_state", "agent": "music", "state": "waiting", "detail": "프롬프트 승인 대기", "task_id": "abc-123"}
{"type": "task_complete", "agent": "stock", "task_id": "...", "result": {"summary": "..."}}
{"type": "agent_move", "agent": "stock", "target": "break_room"}
클라이언트 → 서버:
{"type": "command", "agent": "music", "action": "compose", "params": {"prompt": "...", "style": "..."}}
{"type": "approval", "agent": "music", "task_id": "abc-123", "approved": true}
{"type": "query", "agent": "stock", "action": "status"}
ChatPanel 기능
- 에이전트별 채팅 히스토리 표시
- 텍스트 입력 + 빠른 액션 버튼
- 승인 대기 중인 작업 강조 표시
- 최근 작업 결과 인라인 표시
7. 인프라 변경
Docker Compose 추가
agent-office:
build: ./agent-office
container_name: agent-office
ports:
- "18900:8000"
volumes:
- ${RUNTIME_PATH}/data:/app/data
environment:
- TELEGRAM_BOT_TOKEN=${TELEGRAM_BOT_TOKEN}
- TELEGRAM_CHAT_ID=${TELEGRAM_CHAT_ID}
- TELEGRAM_WEBHOOK_URL=${TELEGRAM_WEBHOOK_URL}
- STOCK_LAB_URL=http://stock-lab:8000
- MUSIC_LAB_URL=http://music-lab:8000
depends_on:
- stock-lab
- music-lab
restart: unless-stopped
Nginx 라우팅 추가
location /api/agent-office/ {
proxy_pass http://agent-office:8000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade"; # WebSocket 지원
}
라우팅 (React Router)
// routes.jsx
{ path: 'agent-office', lazy: () => import('./pages/agent-office/AgentOffice') }
Lab 페이지(EffectLab.jsx)의 LAB_ITEMS에 Agent Office 항목 추가.
8. 향후 확장 (Phase 2+)
| 단계 | 내용 |
|---|---|
| Phase 2 | LottoAgent, BlogAgent, RealestateAgent 추가 |
| Phase 3 | Claude AI Agent (자연어 복합 지시) |
| Phase 4 | 방/층 확장 (부서별 공간 분리) |
| Phase 5 | 에이전트 간 협업 시각화 (회의 테이블에서 토론) |
| Phase 6 | 에이전트 커스텀 (이름, 외형, 성격 설정) |
9. 기술 스택 요약
| 레이어 | 기술 |
|---|---|
| 사무실 렌더링 | HTML5 Canvas 2D (커스텀 엔진) |
| 프론트엔드 | React 18 + Vite |
| 실시간 통신 | WebSocket (FastAPI) |
| 백엔드 | FastAPI (Python 3.12) |
| DB | SQLite (agent_office.db) |
| 스케줄러 | APScheduler |
| 메시징 | Telegram Bot API (Webhook) |
| 서비스 연동 | HTTP Proxy (기존 서비스 API 호출) |