Files
microdao-daarion/services/router/crewai_client.py
Apple ef3473db21 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>
2026-02-09 08:46:46 -08:00

146 lines
4.7 KiB
Python

"""
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)}