Some checks failed
CD Pipeline / deploy (push) Has been cancelled
- Add ElephantService, AutonomousEngine, Orchestrator, DecisionRouter (EA 4-file stack) - Fix 10 bugs: URL typo, SQL schema mismatches (price_records JOIN), enum mapping, metadata_json, NemoTron PriceThreat dispatch, async/await mismatch, broken imports - Wire ADR-012 Agent Action Ladder: EventRouter L2 → EA first + AIOrch fallback; all decisions dual-write DB + triaged_alert Telegram; momo: callback prefix - Wire ADR-013 AutoHeal: resource_optimization trigger → AutoHealService - Add W3 guards: connection cache 300s TTL, $5/hr cost hard limit - Add W4 persistence: routing decisions + agent performance snapshots → ai_insights - Add Migration 015: confidence + created_by columns on ai_insights - Fix run_scheduler.py broken imports (DecisionTracker service didn't exist) - Fix verify_elephant_integration.py: check_status() → check_connection() Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
121 lines
4.1 KiB
Python
121 lines
4.1 KiB
Python
# services/event_router.py
|
||
#
|
||
# ADR-012 §③: 單一入口 dispatch(event) — L0/L1/L2 分流
|
||
# W2-C: L2 優先走 Elephant Alpha Orchestrator;EA 不可用時 fallback AIOrchestrator
|
||
#
|
||
import asyncio
|
||
import logging
|
||
from typing import Any, Dict, Optional
|
||
|
||
from services.ai_orchestrator import AIOrchestrator
|
||
from services.telegram_templates import alert
|
||
from database.manager import get_session
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
|
||
async def _handle_l1(event: Dict[str, Any], session_id: str) -> Dict[str, Any]:
|
||
"""L1: semantic translation + reason analysis (Hermes)."""
|
||
orchestrator = AIOrchestrator()
|
||
return await orchestrator.handle_l1(event, session_id)
|
||
|
||
|
||
async def _handle_l2(event: Dict[str, Any], session_id: str) -> Dict[str, Any]:
|
||
"""
|
||
L2: W2-C — EA Orchestrator 優先(動態路由 256K ctx);
|
||
EA 不可用(API key 未設或連線失敗)時 fallback AIOrchestrator。
|
||
ADR-012: audit trail 由 EA._log_decision + triaged_alert 雙寫保證。
|
||
"""
|
||
try:
|
||
from services.elephant_service import elephant_service
|
||
from services.elephant_alpha_orchestrator import elephant_orchestrator
|
||
|
||
# 護欄:EA API key 未設定則直接 fallback,不嘗試連線
|
||
if not elephant_service.api_key:
|
||
raise RuntimeError("OPENROUTER_API_KEY not configured, using fallback")
|
||
|
||
# 護欄:連線快取確認(W3-A cache 300s,不會每次都 ping)
|
||
if not elephant_service.check_connection():
|
||
raise RuntimeError("EA connection unavailable, using fallback")
|
||
|
||
decision = await elephant_orchestrator.analyze_and_coordinate({
|
||
"event": event,
|
||
"tier": "L2",
|
||
"session_id": session_id,
|
||
"urgency": "high",
|
||
"complexity": "medium",
|
||
"task_type": event.get("event_type", "general_analysis"),
|
||
})
|
||
|
||
return {
|
||
"source": "elephant_alpha",
|
||
"priority": decision.priority,
|
||
"confidence": decision.confidence,
|
||
"execution_plan": decision.execution_plan,
|
||
"agents_required": decision.agents_required,
|
||
"reasoning": decision.reasoning,
|
||
}
|
||
|
||
except Exception as e:
|
||
logger.warning(f"[EventRouter] EA L2 failed ({e}), fallback → AIOrchestrator")
|
||
orchestrator = AIOrchestrator()
|
||
return await orchestrator.handle_l2(event, session_id)
|
||
|
||
|
||
async def _handle_l0(event: Dict[str, Any]) -> Dict[str, Any]:
|
||
"""L0: return raw event (compatibility/monitoring)."""
|
||
return {"status": "ok", "echo": event.get("event_type")}
|
||
|
||
|
||
async def dispatch(event: Dict[str, Any], admin_chat_ids: Optional[list] = None) -> Dict[str, Any]:
|
||
"""
|
||
Main event routing entry (ADR-012 §③ — 唯一入口).
|
||
Output format compatible with routes/bot_api_routes.
|
||
"""
|
||
tier = _classify(event)
|
||
session_id = f"evt:{event.get('event_type')}:{event.get('source', 'unknown')}"
|
||
|
||
try:
|
||
if tier == "L0":
|
||
result = await _handle_l0(event)
|
||
elif tier == "L1":
|
||
result = await _handle_l1(event, session_id)
|
||
elif tier == "L2":
|
||
result = await _handle_l2(event, session_id)
|
||
else:
|
||
result = await _handle_l0(event)
|
||
|
||
return {
|
||
"tier": tier,
|
||
"sent": 1,
|
||
"errors": [],
|
||
"latency_ms": 0,
|
||
"payload": result,
|
||
}
|
||
except Exception as e:
|
||
logger.exception(f"[EventRouter] dispatch failed: {e}")
|
||
return {
|
||
"tier": tier,
|
||
"sent": 0,
|
||
"errors": [str(e)],
|
||
"latency_ms": 0,
|
||
"payload": None,
|
||
}
|
||
|
||
|
||
def _classify(event: Dict[str, Any]) -> str:
|
||
sev = event.get("severity", "info")
|
||
has_trace = bool(event.get("trace"))
|
||
event_type = event.get("event_type", "")
|
||
|
||
if sev in ("info", "success"):
|
||
return "L0"
|
||
if sev == "warning":
|
||
return "L1" if has_trace else "L0"
|
||
if sev == "alert":
|
||
if event_type in {"price_threat", "db_connection_error", "crawler_timeout",
|
||
"nim_quota_exhausted", "embedding_failure"}:
|
||
return "L2"
|
||
return "L1"
|
||
return "L0"
|