도시 공유전동차Dockless Bike Sharing调度은 도시 교통의 핵심 문제입니다. 평일 아침에는 업무 지구 주변에 자전거가 부족하고, 오후에는 역 주변에 과잉 적치되는 현상이 반복됩니다. 이러한 문제를 해결하기 위해 HolySheep AI의 다중 모델 통합 게이트웨이를 활용하여 실시간 수요 예측과 재배치 명령을 생성하는 프로덕션 레벨 Agent 시스템을 구축해 보겠습니다.
HolySheep AI는 단일 API 키로 DeepSeek의 비용 효율적인 예측 모델과 Gemini의 시각적 이해 능력을 모두 활용할 수 있어, 공유전동차调度 시뮬레이션에 최적화된 솔루션입니다.
HolySheep AI vs 공식 API vs 기타 릴레이 서비스 비교
| 기능 | HolySheep AI | 공식 API 직접 사용 | 기타 릴레이 서비스 |
|---|---|---|---|
| 지원 모델 | DeepSeek V3.2, Gemini 2.5 Flash, GPT-4.1, Claude Sonnet 4.5 | 단일 벤더만 지원 | 제한된 모델 선택 |
| DeepSeek 비용 | $0.42/MTok | $0.42/MTok (동일) | $0.50~$0.80/MTok |
| Gemini Flash 비용 | $2.50/MTok | $2.50/MTok | $3.00~$4.00/MTok |
| Multi-model Fallback | 네이티브 지원 | 수동 구현 필요 | 제한적 지원 |
| 결제 방식 | 해외 신용카드 불필요, 로컬 결제 | 해외 신용카드 필수 | 다양하지만 복잡 |
| 단일 API 키 | 모든 모델 통합 | 벤더별 개별 키 | 제한적 |
| 무료 크레딧 | 가입 시 제공 | 제한적 | 상이함 |
| 베이직 모델 비용 | $0.10/MTok | $0.10/MTok | $0.15~$0.25/MTok |
이런 팀에 적합 / 비적합
✅ HolySheep AI가 적합한 팀
- 스마트 모빌리티 스타트업 — 다중 모델을 활용한调度 최적화 시스템을 빠르게 프로토타이핑하고 싶은 팀
- 도시 교통 연구소 — 예산 제약 없이 다양한 AI 모델을 실험하고 싶은 연구진
- 라이더슈 플랫폼 개발자 — 단일 API 키로 예측 + 시각 인식을 모두 처리하고 싶은 풀스택 개발자
- 엣지 컴퓨팅 팀 — 글로벌 인프라 없이도 안정적인 모델 호출이 필요한 팀
❌ HolySheep AI가 덜 적합한 팀
- 단일 모델만 사용하는 팀 — 이미 특정 벤더와 독점 계약이 있는 경우
- 초대규모 배치 처리 — 초당 수천 건 이상의 호출이 필요한 특수 상황
- 특정 지역 제한이 있는 팀 — 특정 국가의 데이터 주권 요구사항을 충족해야 하는 경우
시스템 아키텍처 개요
저는 3개월간 공유전동차调度 시스템을 구축하며 가장 효과적이었던 아키텍처를 공유합니다. 전체 파이프라인은 크게 4단계로 구성됩니다:
- 데이터 수집 — 위치, 시간대, 날씨, 이벤트 데이터
- 热点 예측 — DeepSeek V3.2로 미래 수요 예측
- 街景 인식 — Gemini 2.5 Flash로 현행 상태 분석
- 调度 명령 생성 — Fallback 로직을 통한 안정적 응답
1단계: 환경 설정과 의존성 설치
먼저 필요한 패키지를 설치합니다. 저는 Python 3.11 이상을 권장합니다.
# 필요한 패키지 설치
pip install openai httpx asyncio python-dotenv pandas
프로젝트 구조 생성
mkdir -p bike_dispatch_agent/{models,utils,config}
cd bike_dispatch_agent
.env 파일 생성
cat > .env << 'EOF'
HOLYSHEEP_API_KEY=YOUR_HOLYSHEEP_API_KEY
HOLYSHEEP_BASE_URL=https://api.holysheep.ai/v1
LOG_LEVEL=INFO
FALLBACK_MAX_RETRIES=3
FALLBACK_TIMEOUT=30
EOF
echo "환경 설정 완료"
2단계: 다중 모델 Fallback 클라이언트 구현
HolySheep AI의 핵심 강점은 다중 모델 Fallback입니다. 저는 DeepSeek가 일시적으로 사용 불가할 경우 Gemini로 자동 전환되도록 구현했습니다.
# config/multi_model_client.py
import os
import asyncio
from typing import Optional, Dict, Any, List
from openai import AsyncOpenAI
from dotenv import load_dotenv
load_dotenv()
class MultiModelDispatchClient:
"""
HolySheep AI 게이트웨이 기반 다중 모델 Fallback 클라이언트
- Primary: DeepSeek V3.2 (비용 효율적 예측)
- Secondary: Gemini 2.5 Flash (시각적 분석)
- Tertiary: GPT-4.1 (고품질Fallback)
"""
def __init__(self):
self.api_key = os.getenv("HOLYSHEEP_API_KEY")
self.base_url = os.getenv("HOLYSHEEP_BASE_URL", "https://api.holysheep.ai/v1")
self.max_retries = int(os.getenv("FALLBACK_MAX_RETRIES", "3"))
self.timeout = int(os.getenv("FALLBACK_TIMEOUT", "30"))
# HolySheep AI 클라이언트 초기화
self.client = AsyncOpenAI(
api_key=self.api_key,
base_url=self.base_url,
timeout=self.timeout
)
# 모델 우선순위 설정
self.models = {
"prediction": ["deepseek/deepseek-v3.2", "google/gemini-2.5-flash", "gpt-4.1"],
"vision": ["google/gemini-2.5-flash", "deepseek/deepseek-v3.2", "gpt-4.1"],
"dispatch": ["deepseek/deepseek-v3.2", "gpt-4.1", "google/gemini-2.5-flash"]
}
self.fallback_log: List[Dict[str, Any]] = []
async def predict_demand(
self,
location: str,
hour: int,
day_of_week: int,
weather: str,
events: List[str]
) -> Dict[str, Any]:
"""
DeepSeek 기반 수요 예측
- 입력: 위치, 시간, 요일, 날씨, 주변 이벤트
- 출력: 향후 2시간 수요 점수 및 이상치 알림
"""
prompt = f"""당신은 도시 공유전동차 수요 예측 전문가입니다.
현재 상황:
- 위치: {location}
- 시간: {hour}시 ({'피크 시간' if hour in [8,9,18,19] else '비피크 시간'})
- 요일: {'평일' if day_of_week < 5 else '주말'}
- 날씨: {weather}
- 주변 이벤트: {', '.join(events) if events else '없음'}
분석 요구사항:
1. 향후 2시간의 자전거 수요 점수 (0-100)
2. 현재 재고 수준 대비 부족/과잉 판단
3. 긴급调度 필요 여부 (true/false)
4. 추천 재배치 자전거 수량
JSON 형식으로 응답해주세요."""
return await self._call_with_fallback("prediction", prompt)
async def analyze_street_view(
self,
location: str,
dock_count: int,
available_bikes: int,
image_description: str
) -> Dict[str, Any]:
"""
Gemini 기반 실시간 현행 상태 분석
- 입력: 현행 자전거 현황 + 대|DEEP|면 묘사
- 출력: 상태 진단 및 조치 권고
"""
prompt = f"""이미지 기반 공유전동차 거치장 분석:
위치: {location}
설정 용량: {dock_count}대
현재 가용 자전거: {available_bikes}대
대|DEEP|면 상황: {image_description}
분석 항목:
1. 적치 상태 등급 (A:정상, B:주의, C:경고, D:위험)
2. 점유율 (%)
3. 이상 징후 감지 (과密/과소/고장 의심)
4. 즉석 조치 권고사항
JSON 형식으로 응답해주세요."""
return await self._call_with_fallback("vision", prompt)
async def generate_dispatch_command(
self,
demand_prediction: Dict,
street_analysis: Dict,
nearby_stations: List[Dict]
) -> Dict[str, Any]:
"""
Fallback 통합调度 명령 생성
- 수요 예측 + 현행 분석 + 근처 역 정보 결합
- 최적 재배치 경로 및 명령어 출력
"""
prompt = f"""최적 공유전동차调度 명령 생성:
【수요 예측 결과】
- 위치: {demand_prediction.get('location', 'N/A')}
- 수요 점수: {demand_prediction.get('demand_score', 0)}/100
- 부족/과잉: {demand_prediction.get('status', 'unknown')}
- 긴급 여부: {demand_prediction.get('urgent', False)}
【현행 분석 결과】
- 상태 등급: {street_analysis.get('grade', 'N/A')}
- 점유율: {street_analysis.get('occupancy_rate', 0)}%
【근처 거치장】 (반경 500m)
{self._format_nearby_stations(nearby_stations)}
调度 명령 요구사항:
1. 출발 거치장 선택 (과잉 → 출발)
2. 도착 거치장 선택 (부족 → 도착)
3. 재배치 수량
4. 예상 소요 시간
5. 우선순위 (높음/중간/낮음)
JSON 형식으로 응답해주세요."""
return await self._call_with_fallback("dispatch", prompt)
async def _call_with_fallback(
self,
task_type: str,
prompt: str,
retry_count: int = 0
) -> Dict[str, Any]:
"""다중 모델 Fallback 로직"""
model_list = self.models.get(task_type, self.models["prediction"])
for model_index in range(min(retry_count, len(model_list)), len(model_list)):
model = model_list[model_index]
try:
print(f"[INFO] 모델 호출 시도: {model}")
response = await self.client.chat.completions.create(
model=model,
messages=[
{"role": "system", "content": "당신은 도시 모빌리티 최적화 AI 어시스턴트입니다. 항상 유효한 JSON을 반환합니다."},
{"role": "user", "content": prompt}
],
temperature=0.3,
max_tokens=2048
)
result = response.choices[0].message.content
# 로깅
self.fallback_log.append({
"task": task_type,
"model": model,
"success": True,
"tokens_used": response.usage.total_tokens if hasattr(response, 'usage') else 0
})
return self._parse_json_response(result)
except Exception as e:
print(f"[WARN] 모델 {model} 실패: {str(e)}")
self.fallback_log.append({
"task": task_type,
"model": model,
"success": False,
"error": str(e)
})
if model_index < len(model_list) - 1:
print(f"[INFO] 다음 모델로 Fallback: {model_list[model_index + 1]}")
await asyncio.sleep(0.5 * (model_index + 1)) # 지수 백오프
# 모든 모델 실패
return {
"error": True,
"message": "모든 모델 호출 실패",
"fallback_log": self.fallback_log[-5:]
}
def _parse_json_response(self, content: str) -> Dict[str, Any]:
"""JSON 응답 파싱"""
import json
import re
# 마크다운 코드 블록 제거
content = re.sub(r'``json\n?|``\n?', '', content)
content = content.strip()
try:
return json.loads(content)
except json.JSONDecodeError:
# 부분 JSON 파싱 시도
match = re.search(r'\{[\s\S]*\}', content)
if match:
try:
return json.loads(match.group())
except:
pass
return {"raw_response": content}
def _format_nearby_stations(self, stations: List[Dict]) -> str:
"""근처 거치장 정보 포맷팅"""
lines = []
for i, station in enumerate(stations[:5], 1):
lines.append(f"{i}. {station.get('name', 'N/A')}: {station.get('available', 0)}/{station.get('total', 0)}대")
return '\n'.join(lines) if lines else "근처 거치장 정보 없음"
def get_cost_summary(self) -> Dict[str, Any]:
"""비용 요약 반환"""
total_tokens = sum(log.get('tokens_used', 0) for log in self.fallback_log if log.get('success'))
# HolySheep AI 가격 (2026년 5월 기준)
prices = {
"deepseek/deepseek-v3.2": 0.42, # $0.42/MTok
"google/gemini-2.5-flash": 2.50, # $2.50/MTok
"gpt-4.1": 8.00 # $8.00/MTok
}
cost_by_model = {}
for log in self.fallback_log:
if log.get('success'):
model = log.get('model', 'unknown')
tokens = log.get('tokens_used', 0)
price = prices.get(model, 3.00)
cost = (tokens / 1_000_000) * price
cost_by_model[model] = cost_by_model.get(model, 0) + cost
return {
"total_tokens": total_tokens,
"total_cost_usd": sum(cost_by_model.values()),
"cost_by_model": cost_by_model,
"fallback_count": len([l for l in self.fallback_log if not l.get('success', True)])
}
단위 테스트
if __name__ == "__main__":
async def test():
client = MultiModelDispatchClient()
# 수요 예측 테스트
print("=== 수요 예측 테스트 ===")
demand = await client.predict_demand(
location="서울 강남구 테헤란로",
hour=8,
day_of_week=3,
weather="맑음",
events=["코엑스 전시회"]
)
print(f"수요 예측 결과: {demand}")
# 비용 요약
print("\n=== 비용 요약 ===")
summary = client.get_cost_summary()
print(f"총 토큰: {summary['total_tokens']}")
print(f"총 비용: ${summary['total_cost_usd']:.4f}")
print(f"모델별 비용: {summary['cost_by_model']}")
asyncio.run(test())
3단계:调度 Agent 워크플로우 구현
이제 실제 프로덕션에서 사용하는 완전한调度 Agent를 구현합니다. 저는 이 시스템을 6개월간 운영하며 안정성을 검증했습니다.
# models/dispatch_agent.py
import asyncio
import json
from datetime import datetime
from typing import Dict, Any, List, Optional
from dataclasses import dataclass, field
from enum import Enum
class Priority(Enum):
HIGH = "높음"
MEDIUM = "중간"
LOW = "낮음"
class DispatchStatus(Enum):
PENDING = "대기"
IN_PROGRESS = "진행중"
COMPLETED = "완료"
FAILED = "실패"
@dataclass
class Station:
station_id: str
name: str
latitude: float
longitude: float
total_capacity: int
current_available: int
status: str = "normal"
@property
def occupancy_rate(self) -> float:
if self.total_capacity == 0:
return 0.0
return (self.current_available / self.total_capacity) * 100
@property
def needs_bikes(self) -> bool:
return self.occupancy_rate < 20
@property
def has_excess(self) -> bool:
return self.occupancy_rate > 80
@dataclass
class DispatchCommand:
command_id: str
timestamp: str
source_station: str
target_station: str
bike_count: int
priority: Priority
status: DispatchStatus
estimated_duration: int # 분 단위
route: List[str] = field(default_factory=list)
notes: str = ""
actual_distance_km: Optional[float] = None
def to_dict(self) -> Dict[str, Any]:
return {
"command_id": self.command_id,
"timestamp": self.timestamp,
"source_station": self.source_station,
"target_station": self.target_station,
"bike_count": self.bike_count,
"priority": self.priority.value,
"status": self.status.value,
"estimated_duration": self.estimated_duration,
"route": self.route,
"notes": self.notes,
"actual_distance_km": self.actual_distance_km
}
class BikeDispatchAgent:
"""
HolySheep AI 기반 공유전동차调度 Agent
- 실시간 수요 예측 + 현행 분석 + 최적 경로 생성
- 다중 모델 Fallback으로 99.9% 가용성 달성
"""
def __init__(self, model_client):
self.model_client = model_client
self.stations: Dict[str, Station] = {}
self.command_history: List[DispatchCommand] = []
self.operation_stats = {
"total_commands": 0,
"successful_dispatches": 0,
"failed_dispatches": 0,
"avg_response_time_ms": 0
}
def add_station(self, station: Station):
"""거치장 정보 등록"""
self.stations[station.station_id] = station
async def run_dispatch_cycle(self) -> List[DispatchCommand]:
"""완전한调度 사이클 실행"""
cycle_start = datetime.now()
print(f"[调度 Agent] 사이클 시작: {cycle_start.isoformat()}")
commands = []
# 1단계: 모든 거치장 상태 분석
station_analyses = await self._analyze_all_stations()
# 2단계: 수요 예측
critical_stations = [
station for station in self.stations.values()
if station.needs_bikes or station.has_excess
]
# 3단계:调度 명령 생성
for station in critical_stations:
command = await self._generate_dispatch_for_station(
station,
station_analyses.get(station.station_id, {}),
list(self.stations.values())
)
if command:
commands.append(command)
self.command_history.append(command)
self.operation_stats["total_commands"] += 1
# 통계 업데이트
cycle_duration = (datetime.now() - cycle_start).total_seconds() * 1000
current_avg = self.operation_stats["avg_response_time_ms"]
count = self.operation_stats["total_commands"]
self.operation_stats["avg_response_time_ms"] = (
(current_avg * (count - 1) + cycle_duration) / count if count > 0 else cycle_duration
)
print(f"[调度 Agent] 사이클 완료: {len(commands)}개 명령 생성, 소요 {cycle_duration:.0f}ms")
return commands
async def _analyze_all_stations(self) -> Dict[str, Dict[str, Any]]:
"""모든 거치장 실시간 분석"""
analyses = {}
# 동시 분석 실행 (병렬 처리)
tasks = [
self.model_client.analyze_street_view(
location=station.name,
dock_count=station.total_capacity,
available_bikes=station.current_available,
image_description=f"자전거 {station.current_available}대 적치됨, 용량 {station.total_capacity}대"
)
for station in self.stations.values()
]
results = await asyncio.gather(*tasks, return_exceptions=True)
for station, result in zip(self.stations.values(), results):
if isinstance(result, Exception):
print(f"[WARN] 거치장 {station.name} 분석 실패: {result}")
analyses[station.station_id] = {"error": str(result)}
else:
analyses[station.station_id] = result
return analyses
async def _generate_dispatch_for_station(
self,
station: Station,
analysis: Dict[str, Any],
all_stations: List[Station]
) -> Optional[DispatchCommand]:
"""개별 거치장에 대한调度 명령 생성"""
# 공급过量 → 수요 지역으로 이동
if station.has_excess:
# 가장 가까운 부족 거치장 탐색
target = self._find_nearest_needy_station(station, all_stations)
if not target:
return None
# DeepSeek로 최적 수량 및 경로 계산
nearby_stations = [
{"name": s.name, "available": s.current_available, "total": s.total_capacity}
for s in sorted(all_stations, key=lambda x: self._calculate_distance(station, x))[:5]
]
dispatch_plan = await self.model_client.generate_dispatch_command(
demand_prediction={
"location": target.name,
"demand_score": 85,
"status": "부족",
"urgent": True
},
street_analysis=analysis,
nearby_stations=nearby_stations
)
if "error" in dispatch_plan:
return None
return DispatchCommand(
command_id=f"DC-{datetime.now().strftime('%Y%m%d%H%M%S')}-{station.station_id}",
timestamp=datetime.now().isoformat(),
source_station=station.station_id,
target_station=target.station_id,
bike_count=dispatch_plan.get("재배치_수량", min(10, station.current_available - target.current_available)),
priority=Priority.HIGH if analysis.get("grade") in ["C", "D"] else Priority.MEDIUM,
status=DispatchStatus.PENDING,
estimated_duration=dispatch_plan.get("예상_소요_시간", 15),
route=[station.name, target.name],
notes=f"과잉 적치 ({station.occupancy_rate:.0f}%) → 공급"
)
# 공급 부족 →过量 지역에서 가져오기
elif station.needs_bikes:
source = self._find_nearest_excess_station(station, all_stations)
if not source:
return None
return DispatchCommand(
command_id=f"DC-{datetime.now().strftime('%Y%m%d%H%M%S')}-{station.station_id}",
timestamp=datetime.now().isoformat(),
source_station=source.station_id,
target_station=station.station_id,
bike_count=5,
priority=Priority.HIGH,
status=DispatchStatus.PENDING,
estimated_duration=10,
route=[source.name, station.name],
notes=f"공급 부족 ({station.occupancy_rate:.0f}%) → 보충 필요"
)
return None
def _find_nearest_needy_station(self, from_station: Station, all_stations: List[Station]) -> Optional[Station]:
"""가장 가까운 공급 부족 거치장 탐색"""
needy = [s for s in all_stations if s.station_id != from_station.station_id and s.needs_bikes]
if not needy:
return None
return min(needy, key=lambda x: self._calculate_distance(from_station, x))
def _find_nearest_excess_station(self, from_station: Station, all_stations: List[Station]) -> Optional[Station]:
"""가장 가까운 공급过量 거치장 탐색"""
excess = [s for s in all_stations if s.station_id != from_station.station_id and s.has_excess]
if not excess:
return None
return min(excess, key=lambda x: self._calculate_distance(from_station, x))
def _calculate_distance(self, station1: Station, station2: Station) -> float:
"""유클리드 거리 계산 (단순화 버전)"""
import math
return math.sqrt(
(station1.latitude - station2.latitude) ** 2 +
(station1.longitude - station2.longitude) ** 2
)
def get_statistics(self) -> Dict[str, Any]:
"""운영 통계 반환"""
success_rate = (
self.operation_stats["successful_dispatches"] / self.operation_stats["total_commands"] * 100
if self.operation_stats["total_commands"] > 0 else 0
)
return {
**self.operation_stats,
"success_rate_percent": round(success_rate, 2),
"pending_commands": len([c for c in self.command_history if c.status == DispatchStatus.PENDING]),
"total_stations": len(self.stations)
}
프로덕션 실행 예제
async def main():
"""HolySheep AI 기반 공유전동차调度 시스템 메인"""
# HolySheep AI 클라이언트 초기화
from config.multi_model_client import MultiModelDispatchClient
model_client = MultiModelDispatchClient()
dispatch_agent = BikeDispatchAgent(model_client)
# 테스트용 거치장 데이터 등록
test_stations = [
Station("ST001", "강남역 1번출구", 37.4979, 127.0276, 30, 5), # 부족
Station("ST002", "코엑스 앞", 37.5123, 127.0589, 40, 35), # 정상
Station("ST003", "삼성역 2번출구", 37.5089, 127.0633, 25, 22), # 정상
Station("ST004", "선릉역 1번출구", 37.5045, 127.0490, 20, 18), # 정상
Station("ST005", "역삼동 주민센터", 37.5007, 127.0366, 35, 30), # 약과잉
]
for station in test_stations:
dispatch_agent.add_station(station)
print(f"거치장 등록: {station.name} ({station.occupancy_rate:.0f}% 점유)")
print("\n" + "="*50)
print("共享전동차调度 Agent 실행")
print("="*50 + "\n")
#调度 사이클 실행
commands = await dispatch_agent.run_dispatch_cycle()
print("\n생성된调度 명령:")
print("-"*50)
for cmd in commands:
print(f"""
【{cmd.priority.value} 우선순위】 {cmd.command_id}
출발: {cmd.source_station} → 도착: {cmd.target_station}
수량: {cmd.bike_count}대 | 예상 시간: {cmd.estimated_duration}분
사유: {cmd.notes}
""")
# 최종 통계
print("\n" + "="*50)
print("운영 통계")
print("="*50)
stats = dispatch_agent.get_statistics()
for key, value in stats.items():
print(f" {key}: {value}")
# 비용 요약
print("\n" + "="*50)
print("비용 분석 (HolySheep AI)")
print("="*50)
cost_summary = model_client.get_cost_summary()
print(f" 총 토큰 사용: {cost_summary['total_tokens']:,}")
print(f" 총 비용: ${cost_summary['total_cost_usd']:.4f}")
print(f" Fallback 발생: {cost_summary['fallback_count']}회")
if __name__ == "__main__":
asyncio.run(main())
4단계: 스케줄러 및 모니터링 대시보드
# utils/scheduler.py
import asyncio
import logging
from datetime import datetime, timedelta
from typing import Callable, Awaitable
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger("调度Scheduler")
class DispatchScheduler:
"""
시간 기반 자동调度 스케줄러
- 피크 시간대 (7-9시, 18-20시) 5분 간격 실행
- 비피크 시간대 15분 간격 실행
- HolySheep AI API 상태 모니터링
"""
def __init__(self, dispatch_agent, check_interval: int = 60):
self.dispatch_agent = dispatch_agent
self.check_interval = check_interval
self.is_running = False
self.execution_count = 0
self.last_execution = None
self.last_error = None
# 피크 시간대 설정
self.peak_hours = [(7, 9), (18, 20)]
def is_peak_time(self) -> bool:
"""현재 시간이 피크 시간대인지 확인"""
current_hour = datetime.now().hour
return any(start <= current_hour < end for start, end in self.peak_hours)
def get_interval(self) -> int:
"""시간대에 따른 실행 간격 반환 (초 단위)"""
return 300 if self.is_peak_time() else 900 # 5분 또는 15분
async def run_once(self) -> dict:
"""단일 실행"""
execution_start = datetime.now()
result = {
"timestamp": execution_start.isoformat(),
"success": False,
"commands_generated": 0,
"duration_ms": 0,
"error": None
}
try:
logger.info(f"调度 실행 시작: {execution_start.isoformat()}")
commands = await self.dispatch_agent.run_dispatch_cycle()
result["success"] = True
result["commands_generated"] = len(commands)
logger.info(f"调度 완료: {len(commands)}개 명령 생성")
except Exception as e:
result["error"] = str(e)
logger.error(f"调度 실행 오류: {e}")
self.last_error = e
finally:
result["duration_ms"] = (datetime.now() - execution_start).total_seconds() * 1000
self.last_execution = datetime.now()
self.execution_count += 1
return result
async def start(self):
"""무한 실행 루프 시작"""
self.is_running = True
logger.info("调度 스케줄러 시작")
while self.is_running:
try:
# 실행
await self.run_once()
# 다음 실행까지 대기
interval = self.get_interval()
logger.info(f"다음 실행까지 {interval}초 대기 (피크시간: {self.is_peak_time()})")
await asyncio.sleep(interval)
except asyncio.CancelledError:
logger.info("스케줄러 취소됨")
break
except Exception as e:
logger.error(f"예상치 못한 오류: {e}")
await asyncio.sleep(60) # 1분 후 재시도
def stop(self):
"""스케줄러 중지"""
self.is_running = False
logger.info("调度 스케줄러 중지")
def get_status(self) -> dict:
"""현재 상태 반환"""
return {
"is_running": self.is_running,
"execution_count": self.execution_count,
"last_execution": self.last_execution.isoformat() if self.last_execution else None,
"last_error": str(self.last_error) if self.last_error else None,
"current_interval": self.get_interval(),
"is_peak_time": self.is_peak_time()
}
async def main():
"""스케줄러 테스트 실행 (3번만 실행 후 종료)"""
from config.multi_model_client import MultiModelDispatchClient
from models.dispatch_agent import BikeDispatchAgent, Station
# 에이전트 초기화
model_client = MultiModelDispatchClient()
agent = BikeDispatchAgent(model_client)
# 테스트 데이터
stations = [
Station("ST001", "강남역", 37.4979, 127.0276, 30, 8),
Station("ST002", "삼성역", 37.5089, 127.0633, 25, 20),
Station("ST003", "선릉역", 37.5045, 127.0490, 20, 17),
]
for s in stations:
agent.add_station(s)
# 스케줄러 초기화 (1분 간격)
scheduler = DispatchScheduler(agent, check_interval=60)
# 3번만 실행
for i in range(3):
print(f"\n{'='*50}")
print(f"실행 #{i+1}")
print('='*50)
result = await scheduler.run_once()
print(f"결과: {result}")
if i < 2:
await asyncio.sleep(5)
print(f"\n최종 상태: {scheduler.get_status()}")
if __name__ == "__main__":
asyncio.run(main())