feat: Agent Office — AI 에이전트 가상 오피스 #2

Merged
gahusb merged 15 commits from feat/agent-office into main 2026-04-11 13:35:25 +09:00
Showing only changes of commit e33219af0b - Show all commits

View File

@@ -0,0 +1,444 @@
# 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 │ │ │ │
│ └──────────┘ └─────────────────┘ │
└─────────────────────────────────────────┘
```
### 렌더링 계층 (아래→위)
1. **바닥 타일**: 카펫, 나무 바닥
2. **가구**: 데스크, 의자, 소파, 화분, 커피머신
3. **캐릭터**: 에이전트 스프라이트 (상태별 애니메이션)
4. **오버레이**: 말풍선, 상태 아이콘, 이름표
### 스프라이트 에셋
- 무료 픽셀아트 에셋팩 활용 (타일셋, 가구)
- 에이전트 캐릭터: 기본 인물 스프라이트 + 액세서리로 구분
- 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 인터페이스
```python
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%
```
### 양방향 흐름
1. 에이전트 → `telegram_bot.send_message()` → 텔레그램
2. 사용자 → 인라인 버튼 클릭 or 텍스트 입력
3. 텔레그램 → Webhook POST → `telegram_bot.handle_webhook()`
4. `handle_webhook()``telegram_state` 조회 → 에이전트 `on_approval()` 호출
5. 에이전트 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 프로토콜
**서버 → 클라이언트:**
```json
{"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"}
```
**클라이언트 → 서버:**
```json
{"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 추가
```yaml
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 라우팅 추가
```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)
```javascript
// 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 호출) |