snapshot: NODE1 production state 2026-02-09
Complete snapshot of /opt/microdao-daarion/ from NODE1 (144.76.224.179).
This represents the actual running production code that has diverged
significantly from the previous main branch.
Key changes from old main:
- Gateway (http_api.py): expanded from ~40KB to 164KB with full agent support
- Router: new /v1/agents/{id}/infer endpoint with vision + DeepSeek routing
- Behavior Policy: SOWA v2.2 (3-level: FULL/ACK/SILENT)
- Agent Registry: config/agent_registry.yml as single source of truth
- 13 agents configured (was 3)
- Memory service integration
- CrewAI teams and roles
Excluded from snapshot: venv/, .env, data/, backups, .tgz archives
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
145
services/router/crewai_client.py
Normal file
145
services/router/crewai_client.py
Normal file
@@ -0,0 +1,145 @@
|
||||
"""
|
||||
CrewAI Client for Router
|
||||
Handles decision: direct LLM vs CrewAI orchestration
|
||||
"""
|
||||
import os
|
||||
import json
|
||||
import logging
|
||||
import httpx
|
||||
from typing import Dict, Any, Optional, Tuple, List
|
||||
from pathlib import Path
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
CREWAI_URL = os.getenv("CREWAI_URL", "http://dagi-staging-crewai-service:9010")
|
||||
CREWAI_ENABLED = os.getenv("CREWAI_ENABLED", "true").lower() == "true"
|
||||
|
||||
CREWAI_AGENTS_PATH = os.getenv("CREWAI_AGENTS_PATH", "/config/crewai_agents.json")
|
||||
FALLBACK_CREWAI_PATH = "/app/config/crewai_agents.json"
|
||||
|
||||
MIN_PROMPT_LENGTH_FOR_CREW = 100
|
||||
COMPLEXITY_KEYWORDS = [
|
||||
"план", "plan", "аналіз", "analysis", "дослідження", "research",
|
||||
"порівняй", "compare", "розроби", "develop", "створи стратегію",
|
||||
"декомпозиція", "decompose", "крок за кроком", "step by step",
|
||||
"детально", "in detail", "комплексний", "comprehensive"
|
||||
]
|
||||
|
||||
_crewai_cache = None
|
||||
|
||||
|
||||
def load_crewai_config():
|
||||
global _crewai_cache
|
||||
if _crewai_cache is not None:
|
||||
return _crewai_cache
|
||||
|
||||
for path in [CREWAI_AGENTS_PATH, FALLBACK_CREWAI_PATH]:
|
||||
try:
|
||||
p = Path(path)
|
||||
if p.exists():
|
||||
with open(p, "r", encoding="utf-8") as f:
|
||||
data = json.load(f)
|
||||
logger.info(f"Loaded CrewAI config from {path}")
|
||||
_crewai_cache = data
|
||||
return data
|
||||
except Exception as e:
|
||||
logger.warning(f"Could not load from {path}: {e}")
|
||||
return None
|
||||
|
||||
|
||||
def get_agent_crewai_info(agent_id):
|
||||
config = load_crewai_config()
|
||||
if not config:
|
||||
return {"enabled": False, "orchestrator": False, "team": []}
|
||||
|
||||
orchestrators = config.get("orchestrators", [])
|
||||
teams = config.get("teams", {})
|
||||
|
||||
is_orchestrator = any(o.get("id") == agent_id for o in orchestrators)
|
||||
team_info = teams.get(agent_id, {})
|
||||
team_members = team_info.get("members", [])
|
||||
|
||||
return {
|
||||
"enabled": is_orchestrator,
|
||||
"orchestrator": is_orchestrator,
|
||||
"team": team_members
|
||||
}
|
||||
|
||||
|
||||
def should_use_crewai(agent_id, prompt, agent_config, force_crewai=False):
|
||||
"""
|
||||
Decide whether to use CrewAI orchestration or direct LLM.
|
||||
Returns: (use_crewai: bool, reason: str)
|
||||
"""
|
||||
if not CREWAI_ENABLED:
|
||||
return False, "crewai_disabled_globally"
|
||||
|
||||
if force_crewai:
|
||||
return True, "force_crewai_requested"
|
||||
|
||||
crewai_info = get_agent_crewai_info(agent_id)
|
||||
|
||||
if not crewai_info.get("enabled", False):
|
||||
return False, "agent_crewai_disabled"
|
||||
|
||||
if not crewai_info.get("orchestrator", False):
|
||||
return False, "agent_not_orchestrator"
|
||||
|
||||
team = crewai_info.get("team", [])
|
||||
if not team:
|
||||
return False, "agent_has_no_team"
|
||||
|
||||
if len(prompt) < MIN_PROMPT_LENGTH_FOR_CREW:
|
||||
return False, "prompt_too_short"
|
||||
|
||||
prompt_lower = prompt.lower()
|
||||
has_complexity = any(kw in prompt_lower for kw in COMPLEXITY_KEYWORDS)
|
||||
|
||||
if has_complexity:
|
||||
return True, "complexity_keywords_detected"
|
||||
|
||||
return False, "default_direct_llm"
|
||||
|
||||
|
||||
async def call_crewai(agent_id, task, context=None, team=None):
|
||||
try:
|
||||
if not team:
|
||||
crewai_info = get_agent_crewai_info(agent_id)
|
||||
team = crewai_info.get("team", [])
|
||||
|
||||
async with httpx.AsyncClient(timeout=180.0) as client:
|
||||
payload = {
|
||||
"task": task,
|
||||
"orchestrator": agent_id,
|
||||
"context": context or {},
|
||||
}
|
||||
if team:
|
||||
payload["team"] = [
|
||||
m.get("role", str(m)) if isinstance(m, dict) else m
|
||||
for m in team
|
||||
]
|
||||
|
||||
logger.info(f"CrewAI call: agent={agent_id}, team={len(team)} members")
|
||||
|
||||
response = await client.post(f"{CREWAI_URL}/crew/run", json=payload)
|
||||
|
||||
if response.status_code == 200:
|
||||
data = response.json()
|
||||
success = data.get("success")
|
||||
logger.info(f"CrewAI response: success={success}")
|
||||
return data
|
||||
else:
|
||||
logger.error(f"CrewAI error: {response.status_code}")
|
||||
return {"success": False, "result": None, "agents_used": [], "error": f"HTTP {response.status_code}"}
|
||||
except Exception as e:
|
||||
logger.error(f"CrewAI exception: {e}")
|
||||
return {"success": False, "result": None, "agents_used": [], "error": str(e)}
|
||||
|
||||
|
||||
async def get_crewai_health():
|
||||
try:
|
||||
async with httpx.AsyncClient(timeout=5.0) as client:
|
||||
response = await client.get(f"{CREWAI_URL}/health")
|
||||
return response.json()
|
||||
except Exception as e:
|
||||
return {"status": "error", "error": str(e)}
|
||||
Reference in New Issue
Block a user