Files
ewoooc/services/ai_orchestrator.py
ogt e9e0ddf54f
All checks were successful
CD Pipeline / deploy (push) Successful in 1m22s
fix: json.dumps dict before psycopg2 insert + remove fatal raise in save_context
save_context/_save_action_plan passed raw Python dicts as SQL bind params,
causing psycopg2.ProgrammingError that propagated via raise and crashed the
entire AI pipeline, forcing every natural language message to keyword fallback.

Also increase Hermes intent timeout 15s→30s for qwen2.5 cold-start latency.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-25 10:12:20 +08:00

110 lines
3.9 KiB
Python

# services/ai_orchestrator.py
import asyncio
import json
import logging
from typing import Any, Dict, Optional
from sqlalchemy import text
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(
text("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(
text("DELETE FROM agent_context WHERE session_id = :sid AND agent_name = :ag"),
{"sid": session_id, "ag": agent},
)
session.execute(
text("""
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": json.dumps(payload, ensure_ascii=False),
},
)
session.commit()
except Exception as e:
session.rollback()
logger.warning(f"[AIOrchestrator] save_context failed (non-fatal): {e}")
finally:
session.close()
async def _save_action_plan(self, plan: Dict[str, Any]) -> None:
session = get_session()
try:
session.execute(
text("""
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": json.dumps(plan, ensure_ascii=False),
},
)
session.commit()
except Exception as e:
session.rollback()
logger.warning(f"[AIOrchestrator] save_action_plan failed (non-fatal): {e}")
finally:
session.close()