109 lines
3.8 KiB
Python
109 lines
3.8 KiB
Python
# services/ai_orchestrator.py
|
|
import asyncio
|
|
import logging
|
|
from typing import Any, Dict, Optional
|
|
|
|
from services.hermes_analyst_service import HermesAnalystService
|
|
from services.nemoton_dispatcher_service import NemotronDispatcher
|
|
from database.manager import get_session
|
|
from database.ai_models import AgentContext, ActionPlan
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class AIOrchestrator:
|
|
"""
|
|
Coordination hub: handles EventRouter L1/L2, agent shared context, and closed-loop decision tracking.
|
|
Single file <= 100 lines.
|
|
"""
|
|
|
|
def __init__(self):
|
|
self.hermes = HermesAnalystService()
|
|
self.nemotron = NemotronDispatcher()
|
|
|
|
async def handle_l1(self, event: Dict[str, Any], session_id: str) -> Dict[str, Any]:
|
|
"""
|
|
L1: semantic translation + reason analysis (provided by Hermes).
|
|
Writes to agent_context and can be used as L2 context.
|
|
"""
|
|
ctx = await self._get_context(session_id)
|
|
result = await self.hermes.handle_l1(event, ctx)
|
|
await self._save_context(session_id, "hermes", result)
|
|
return result
|
|
|
|
async def handle_l2(self, event: Dict[str, Any], session_id: str) -> Dict[str, Any]:
|
|
"""
|
|
L2: planning + review gate.
|
|
Produces an ActionPlan awaiting approval (handled via Telegram callback).
|
|
"""
|
|
ctx = await self._get_context(session_id) # includes hermes analysis
|
|
result = await self.nemotron.handle_l2(event, ctx)
|
|
await self._save_action_plan(result)
|
|
# review gate handled by routes/bot_api_routes callback
|
|
return result
|
|
|
|
async def _get_context(self, session_id: str) -> Dict[str, Any]:
|
|
session = get_session()
|
|
try:
|
|
rows = session.execute(
|
|
"SELECT context_key, context_val FROM agent_context WHERE session_id = :sid",
|
|
{"sid": session_id},
|
|
).fetchall()
|
|
return {r[0]: r[1] for r in rows}
|
|
finally:
|
|
session.close()
|
|
|
|
async def _save_context(self, session_id: str, agent: str, payload: Dict[str, Any]) -> None:
|
|
session = get_session()
|
|
try:
|
|
session.execute(
|
|
"DELETE FROM agent_context WHERE session_id = :sid AND agent_name = :ag",
|
|
{"sid": session_id, "ag": agent},
|
|
)
|
|
session.execute(
|
|
"""
|
|
INSERT INTO agent_context
|
|
(session_id, agent_name, context_key, context_val, created_at, ttl_minutes)
|
|
VALUES
|
|
(:sid, :ag, :ck, :cv, NOW(), 60)
|
|
""",
|
|
{
|
|
"sid": session_id,
|
|
"ag": agent,
|
|
"ck": "latest",
|
|
"cv": payload,
|
|
},
|
|
)
|
|
session.commit()
|
|
except Exception as e:
|
|
session.rollback()
|
|
logger.error(f"[AIOrchestrator] save_context failed: {e}")
|
|
raise
|
|
finally:
|
|
session.close()
|
|
|
|
async def _save_action_plan(self, plan: Dict[str, Any]) -> None:
|
|
session = get_session()
|
|
try:
|
|
session.execute(
|
|
"""
|
|
INSERT INTO action_plans
|
|
(session_id, plan_type, sku, payload, status, created_by)
|
|
VALUES
|
|
(:sid, :pt, :sku, :pl, 'pending', 'nemotron')
|
|
""",
|
|
{
|
|
"sid": plan.get("session_id"),
|
|
"pt": plan.get("plan_type"),
|
|
"sku": plan.get("sku"),
|
|
"pl": plan,
|
|
},
|
|
)
|
|
session.commit()
|
|
except Exception as e:
|
|
session.rollback()
|
|
logger.error(f"[AIOrchestrator] save_action_plan failed: {e}")
|
|
raise
|
|
finally:
|
|
session.close()
|